@Override public void update(AnActionEvent e) { super.update(e); Presentation presentation = e.getPresentation(); Project project = e.getProject(); if (project == null) { presentation.setEnabled(false); presentation.setVisible(false); return; } VirtualFile[] vFiles = e.getData(CommonDataKeys.VIRTUAL_FILE_ARRAY); if (vFiles == null || vFiles.length != 1 || vFiles[0] == null) { // only 1 file for now presentation.setEnabled(false); presentation.setVisible(true); return; } GitRepositoryManager manager = GitUtil.getRepositoryManager(project); GitRepository repository = manager.getRepositoryForFile(vFiles[0]); if (repository == null || repository.isFresh() || noBranchesToCompare(repository)) { presentation.setEnabled(false); presentation.setVisible(true); return; } presentation.setEnabled(true); presentation.setVisible(true); }
/** * Fetches all specified roots. Once a root has failed, stops and displays the notification. If * needed, displays the successful notification at the end. * * @param roots roots to fetch. * @param errorNotificationTitle if specified, this notification title will be used instead of the * standard "Fetch failed". Use this when fetch is a part of a compound process. * @param notifySuccess if set to {@code true} successful notification will be displayed. * @return true if all fetches were successful, false if at least one fetch failed. */ public boolean fetchRootsAndNotify( @NotNull Collection<GitRepository> roots, @Nullable String errorNotificationTitle, boolean notifySuccess) { Map<VirtualFile, String> additionalInfo = new HashMap<VirtualFile, String>(); for (GitRepository repository : roots) { LOG.info("fetching " + repository); GitFetchResult result = fetch(repository); String ai = result.getAdditionalInfo(); if (!StringUtil.isEmptyOrSpaces(ai)) { additionalInfo.put(repository.getRoot(), ai); } if (!result.isSuccess()) { Collection<Exception> errors = new ArrayList<Exception>(getErrors()); errors.addAll(result.getErrors()); displayFetchResult(myProject, result, errorNotificationTitle, errors); return false; } } if (notifySuccess) { GitUIUtil.notifySuccess(myProject, "", "Fetched successfully"); } String addInfo = makeAdditionalInfoByRoot(additionalInfo); if (!StringUtil.isEmptyOrSpaces(addInfo)) { Notificator.getInstance(myProject) .notify( GitVcs.MINOR_NOTIFICATION, "Fetch details", addInfo, NotificationType.INFORMATION); } return true; }
@Override protected void save(@NotNull Collection<VirtualFile> rootsToSave) throws VcsException { LOG.info("saving " + rootsToSave); for (VirtualFile root : rootsToSave) { final String message = GitHandlerUtil.formatOperationName("Stashing changes from", root); LOG.info(message); final String oldProgressTitle = myProgressIndicator.getText(); myProgressIndicator.setText(message); GitRepository repository = myRepositoryManager.getRepositoryForRoot(root); if (repository == null) { LOG.error("Repository is null for root " + root); } else { GitCommandResult result = myGit.stashSave(repository, myStashMessage); if (result.success() && somethingWasStashed(result)) { myStashedRoots.add(root); } else { String error = "stash " + repository.getRoot() + ": " + result.getErrorOutputAsJoinedString(); if (!result.success()) { throw new VcsException(error); } else { LOG.warn(error); } } } myProgressIndicator.setText(oldProgressTitle); } }
@NotNull private Set<VcsRef> readBranches(@NotNull GitRepository repository) { VirtualFile root = repository.getRoot(); Collection<GitLocalBranch> localBranches = repository.getBranches().getLocalBranches(); Collection<GitRemoteBranch> remoteBranches = repository.getBranches().getRemoteBranches(); Set<VcsRef> refs = new HashSet<VcsRef>(localBranches.size() + remoteBranches.size()); for (GitLocalBranch localBranch : localBranches) { refs.add( myVcsObjectsFactory.createRef( HashImpl.build(localBranch.getHash()), localBranch.getName(), GitRefManager.LOCAL_BRANCH, root)); } for (GitRemoteBranch remoteBranch : remoteBranches) { refs.add( myVcsObjectsFactory.createRef( HashImpl.build(remoteBranch.getHash()), remoteBranch.getNameForLocalOperations(), GitRefManager.REMOTE_BRANCH, root)); } String currentRevision = repository.getCurrentRevision(); if (currentRevision != null) { // null => fresh repository refs.add( myVcsObjectsFactory.createRef( HashImpl.build(currentRevision), "HEAD", GitRefManager.HEAD, root)); } return refs; }
@NotNull private GitFetchResult fetchAll( @NotNull GitRepository repository, @NotNull GitFetchResult fetchResult) { for (GitRemote remote : repository.getRemotes()) { String url = remote.getFirstUrl(); if (url == null) { LOG.error("URL is null for remote " + remote.getName()); continue; } if (GitHttpAdapter.shouldUseJGit(url)) { GitFetchResult res = GitHttpAdapter.fetch(repository, remote, url, null); res.addPruneInfo(fetchResult.getPrunedRefs()); fetchResult = res; myErrors.addAll(fetchResult.getErrors()); if (!fetchResult.isSuccess()) { break; } } else { GitFetchResult res = fetchNatively(repository.getRoot(), remote, null); res.addPruneInfo(fetchResult.getPrunedRefs()); fetchResult = res; if (!fetchResult.isSuccess()) { break; } } } return fetchResult; }
/** * Returns absolute paths which have changed remotely comparing to the current branch, i.e. * performs <code>git diff --name-only master..origin/master</code> * * <p>Paths are absolute, Git-formatted (i.e. with forward slashes). */ @NotNull public static Collection<String> getPathsDiffBetweenRefs( @NotNull Git git, @NotNull GitRepository repository, @NotNull String beforeRef, @NotNull String afterRef) throws VcsException { List<String> parameters = Arrays.asList("--name-only", "--pretty=format:"); String range = beforeRef + ".." + afterRef; GitCommandResult result = git.diff(repository, parameters, range); if (!result.success()) { LOG.info( String.format( "Couldn't get diff in range [%s] for repository [%s]", range, repository.toLogString())); return Collections.emptyList(); } final Collection<String> remoteChanges = new HashSet<String>(); for (StringScanner s = new StringScanner(result.getOutputAsJoinedString()); s.hasMoreData(); ) { final String relative = s.line(); if (StringUtil.isEmptyOrSpaces(relative)) { continue; } final String path = repository.getRoot().getPath() + "/" + unescapePath(relative); remoteChanges.add(path); } return remoteChanges; }
/** * Checks if update is possible, saves local changes and updates all roots. In case of error shows * notification and returns false. If update completes without errors, returns true. * * <p>Perform update on all roots. 0. Blocks reloading project on external change, saving/syncing * on frame deactivation. 1. Checks if update is possible (rebase/merge in progress, no tracked * branches...) and provides merge dialog to solve problems. 2. Finds updaters to use (merge or * rebase). 3. Preserves local changes if needed (not needed for merge sometimes). 4. Updates via * 'git pull' or equivalent. 5. Restores local changes if update completed or failed with error. * If update is incomplete, i.e. some unmerged files remain, local changes are not restored. */ @NotNull public GitUpdateResult update(final UpdateMethod updateMethod) { LOG.info("update started|" + updateMethod); String oldText = myProgressIndicator.getText(); myProgressIndicator.setText("Updating..."); for (GitRepository repository : myRepositories) { repository.update(); } // check if update is possible if (checkRebaseInProgress() || isMergeInProgress() || areUnmergedFiles() || !checkTrackedBranchesConfigured()) { return GitUpdateResult.NOT_READY; } if (!fetchAndNotify()) { return GitUpdateResult.NOT_READY; } AccessToken token = DvcsUtil.workingTreeChangeStarted(myProject); GitUpdateResult result; try { result = updateImpl(updateMethod); } finally { DvcsUtil.workingTreeChangeFinished(myProject, token); } myProgressIndicator.setText(oldText); return result; }
private boolean doDeleteRemote( @NotNull String branchName, @NotNull Collection<GitRepository> repositories) { Couple<String> pair = splitNameOfRemoteBranch(branchName); String remoteName = pair.getFirst(); String branch = pair.getSecond(); GitCompoundResult result = new GitCompoundResult(myProject); for (GitRepository repository : repositories) { GitCommandResult res; GitRemote remote = getRemoteByName(repository, remoteName); if (remote == null) { String error = "Couldn't find remote by name: " + remoteName; LOG.error(error); res = GitCommandResult.error(error); } else { res = pushDeletion(repository, remote, branch); if (!res.success() && isAlreadyDeletedError(res.getErrorOutputAsJoinedString())) { res = myGit.remotePrune(repository, remote); } } result.append(repository, res); repository.update(); } if (!result.totalSuccess()) { VcsNotifier.getInstance(myProject) .notifyError( "Failed to delete remote branch " + branchName, result.getErrorOutputWithReposIndication()); } return result.totalSuccess(); }
/** * {@code git checkout <reference>} <br> * {@code git checkout -b <newBranch> <reference>} */ @NotNull @Override public GitCommandResult checkout( @NotNull GitRepository repository, @NotNull String reference, @Nullable String newBranch, boolean force, @NotNull GitLineHandlerListener... listeners) { final GitLineHandler h = new GitLineHandler(repository.getProject(), repository.getRoot(), GitCommand.CHECKOUT); h.setSilent(false); h.setStdoutSuppressed(false); if (force) { h.addParameters("--force"); } if (newBranch == null) { // simply checkout h.addParameters(reference); } else { // checkout reference as new branch h.addParameters("-b", newBranch, reference); } for (GitLineHandlerListener listener : listeners) { h.addLineListener(listener); } return run(h); }
/** * TODO this is non-optimal and even incorrect, since such diff shows the difference between * committed changes For each of the given repositories looks to the diff between current branch * and the given branch and converts it to the list of local changes. */ @NotNull Map<GitRepository, List<Change>> collectLocalChangesConflictingWithBranch( @NotNull Collection<GitRepository> repositories, @NotNull String currentBranch, @NotNull String otherBranch) { Map<GitRepository, List<Change>> changes = new HashMap<GitRepository, List<Change>>(); for (GitRepository repository : repositories) { try { Collection<String> diff = GitUtil.getPathsDiffBetweenRefs(myGit, repository, currentBranch, otherBranch); List<Change> changesInRepo = convertPathsToChanges(repository, diff, false); if (!changesInRepo.isEmpty()) { changes.put(repository, changesInRepo); } } catch (VcsException e) { // ignoring the exception: this is not fatal if we won't collect such a diff from other // repositories. // At worst, use will get double dialog proposing the smart checkout. LOG.warn( String.format( "Couldn't collect diff between %s and %s in %s", currentBranch, otherBranch, repository.getRoot()), e); } } return changes; }
@NotNull @Override public GitCommandResult config(@NotNull GitRepository repository, String... params) { final GitLineHandler h = new GitLineHandler(repository.getProject(), repository.getRoot(), GitCommand.CONFIG); h.addParameters(params); return run(h); }
private static boolean noBranchesToCompare(@NotNull GitRepository repository) { int locals = repository.getBranches().getLocalBranches().size(); boolean haveRemotes = !repository.getBranches().getRemoteBranches().isEmpty(); if (repository.isOnBranch()) { // there are other branches to compare return locals < 2 && !haveRemotes; } return locals == 0 && !haveRemotes; // there are at least 1 branch to compare }
@Override @NotNull public GitCommandResult resetHard(@NotNull GitRepository repository, @NotNull String revision) { final GitLineHandler handler = new GitLineHandler(repository.getProject(), repository.getRoot(), GitCommand.RESET); handler.addParameters("--hard", revision); return run(handler); }
@NotNull @Override public GitCommandResult show(@NotNull GitRepository repository, @NotNull String... params) { final GitLineHandler handler = new GitLineHandler(repository.getProject(), repository.getRoot(), GitCommand.SHOW); handler.addParameters(params); return run(handler); }
@NotNull private static Collection<String> getRemoteNames(@NotNull GitRepository repository) { Collection<String> names = new ArrayList<String>(repository.getRemotes().size()); for (GitRemote remote : repository.getRemotes()) { names.add(remote.getName()); } return names; }
/** * Updates the recently visited branch in the settings. This is to be performed after successful * checkout operation. */ protected void updateRecentBranch() { if (getRepositories().size() == 1) { GitRepository repository = myRepositories.iterator().next(); mySettings.setRecentBranchOfRepository(repository.getRoot().getPath(), myCurrentBranchOrRev); } else { mySettings.setRecentCommonBranch(myCurrentBranchOrRev); } }
/** * Returns the last (tip) commit on the given branch.<br> * {@code git rev-list -1 <branchName>} */ @NotNull @Override public GitCommandResult tip(@NotNull GitRepository repository, @NotNull String branchName) { final GitLineHandler h = new GitLineHandler(repository.getProject(), repository.getRoot(), GitCommand.REV_LIST); h.addParameters("-1"); h.addParameters(branchName); return run(h); }
/** Create branch without checking it out. {@code git branch <branchName>} */ @Override @NotNull public GitCommandResult branchCreate( @NotNull GitRepository repository, @NotNull String branchName) { final GitLineHandler h = new GitLineHandler(repository.getProject(), repository.getRoot(), GitCommand.BRANCH); h.addParameters(branchName); return run(h); }
/** Get branches containing the commit. {@code git branch --contains <commit>} */ @Override @NotNull public GitCommandResult branchContains( @NotNull GitRepository repository, @NotNull String commit) { final GitLineHandler h = new GitLineHandler(repository.getProject(), repository.getRoot(), GitCommand.BRANCH); h.addParameters("--contains", commit); return run(h); }
@NotNull @Override public GitCommandResult stashSave(@NotNull GitRepository repository, @NotNull String message) { final GitLineHandler h = new GitLineHandler(repository.getProject(), repository.getRoot(), GitCommand.STASH); h.addParameters("save"); h.addParameters(message); return run(h); }
@NotNull @Override public GitCommandResult getUnmergedFiles(@NotNull GitRepository repository) { GitLineHandler h = new GitLineHandler(repository.getProject(), repository.getRoot(), GitCommand.LS_FILES); h.addParameters("--unmerged"); h.setSilent(true); return run(h); }
@Nullable public static GithubCreatePullRequestWorker createPullRequestWorker( @NotNull final Project project, @Nullable final VirtualFile file) { Git git = ServiceManager.getService(Git.class); GitRepository gitRepository = GithubUtil.getGitRepository(project, file); if (gitRepository == null) { GithubNotifications.showError( project, CANNOT_CREATE_PULL_REQUEST, "Can't find git repository"); return null; } gitRepository.update(); Pair<GitRemote, String> remote = GithubUtil.findGithubRemote(gitRepository); if (remote == null) { GithubNotifications.showError( project, CANNOT_CREATE_PULL_REQUEST, "Can't find GitHub remote"); return null; } String remoteName = remote.getFirst().getName(); String remoteUrl = remote.getSecond(); GithubFullPath path = GithubUrlUtil.getUserAndRepositoryFromRemoteUrl(remoteUrl); if (path == null) { GithubNotifications.showError( project, CANNOT_CREATE_PULL_REQUEST, "Can't process remote: " + remoteUrl); return null; } GitLocalBranch currentBranch = gitRepository.getCurrentBranch(); if (currentBranch == null) { GithubNotifications.showError(project, CANNOT_CREATE_PULL_REQUEST, "No current branch"); return null; } GithubAuthData auth; try { auth = GithubUtil.computeValueInModal( project, "Access to GitHub", new ThrowableConvertor<ProgressIndicator, GithubAuthData, IOException>() { @Override public GithubAuthData convert(ProgressIndicator indicator) throws IOException { return GithubUtil.getValidAuthDataFromConfig(project, indicator); } }); } catch (GithubAuthenticationCanceledException e) { return null; } catch (IOException e) { GithubNotifications.showError(project, CANNOT_CREATE_PULL_REQUEST, e); return null; } return new GithubCreatePullRequestWorker( project, git, gitRepository, path, remoteName, remoteUrl, currentBranch.getName(), auth); }
@NotNull @Override public GitCommandResult stashPop( @NotNull GitRepository repository, @NotNull GitLineHandlerListener... listeners) { final GitLineHandler handler = new GitLineHandler(repository.getProject(), repository.getRoot(), GitCommand.STASH); handler.addParameters("pop"); addListeners(handler, listeners); return run(handler); }
@Override @NotNull public GitCommandResult resetMerge(@NotNull GitRepository repository, @Nullable String revision) { final GitLineHandler handler = new GitLineHandler(repository.getProject(), repository.getRoot(), GitCommand.RESET); handler.addParameters("--merge"); if (revision != null) { handler.addParameters(revision); } return run(handler); }
private boolean isRepositoryReady(@NotNull VirtualFile root) { GitRepository repository = getRepository(root); if (repository == null) { LOG.error("Repository not found for root " + root); return false; } else if (repository.isFresh()) { LOG.info("Fresh repository: " + root); return false; } return true; }
@Override public CloudGitApplication deploy() throws ServerRuntimeException { CloudGitApplication application = findOrCreateApplication(); GitRepository repository = findOrCreateRepository(); addOrResetGitRemote(application, repository); add(); commit(); repository.update(); pushApplication(application); return application; }
@Nullable @Override public String getCurrentBranch(@NotNull VirtualFile root) { GitRepository repository = myRepositoryManager.getRepositoryForRoot(root); if (repository == null) return null; String currentBranchName = repository.getCurrentBranchName(); if (currentBranchName == null && repository.getCurrentRevision() != null) { return "HEAD"; } return currentBranchName; }
protected void fetch() throws ServerRuntimeException { final VirtualFile contentRoot = getContentRoot(); GitRepository repository = getRepository(); final GitLineHandler fetchHandler = new GitLineHandler(getProject(), contentRoot, GitCommand.FETCH); fetchHandler.setSilent(false); fetchHandler.addParameters(getRemoteName()); fetchHandler.addLineListener(createGitLineHandlerListener()); performRemoteGitTask(fetchHandler, CloudBundle.getText("fetching.application", getCloudName())); repository.update(); }
/** * Setup remotes combobox. The default remote for the current branch is selected by default. This * method gets current branch for the project. * * @param project the project * @param root the git root * @param remoteCombobox the combobox to update * @param fetchUrl if true, the fetch url is shown instead of push url */ public static void setupRemotes( final Project project, final VirtualFile root, final JComboBox remoteCombobox, final boolean fetchUrl) { final GitRepository repo = GitRepositoryManager.getInstance(project).getRepositoryForRoot(root); assert repo != null : "GitRepository can't be null for root " + root; GitBranch gitBranch = repo.getCurrentBranch(); final String branch = gitBranch != null ? gitBranch.getName() : null; setupRemotes(project, root, branch, remoteCombobox, fetchUrl); }
@NotNull @Override public List<GitCommit> history(@NotNull GitRepository repository, @NotNull String range) { try { return GitHistoryUtils.history(repository.getProject(), repository.getRoot(), range); } catch (VcsException e) { // this is critical, because we need to show the list of unmerged commits, and it shouldn't // happen => inform user and developer throw new GitExecutionException( "Couldn't get [git log " + range + "] on repository [" + repository.getRoot() + "]", e); } }