diff options
Diffstat (limited to 'plugins/git4idea/src/git4idea/branch')
8 files changed, 101 insertions, 35 deletions
diff --git a/plugins/git4idea/src/git4idea/branch/GitBranchOperation.java b/plugins/git4idea/src/git4idea/branch/GitBranchOperation.java index 7c2f77eeebd2..9147054ba7b9 100644 --- a/plugins/git4idea/src/git4idea/branch/GitBranchOperation.java +++ b/plugins/git4idea/src/git4idea/branch/GitBranchOperation.java @@ -15,6 +15,7 @@ */ package git4idea.branch; +import com.intellij.dvcs.repo.RepositoryUtil; import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.progress.ProgressIndicator; import com.intellij.openapi.project.Project; @@ -25,6 +26,9 @@ import com.intellij.openapi.vcs.VcsNotifier; import com.intellij.openapi.vcs.changes.Change; import com.intellij.openapi.vfs.VirtualFile; import com.intellij.util.Function; +import com.intellij.util.containers.ContainerUtil; +import com.intellij.util.containers.MultiMap; +import git4idea.GitLocalBranch; import git4idea.GitPlatformFacade; import git4idea.GitUtil; import git4idea.commands.Git; @@ -32,6 +36,7 @@ import git4idea.commands.GitMessageWithFilesDetector; import git4idea.config.GitVcsSettings; import git4idea.repo.GitRepository; import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; import java.util.*; @@ -50,7 +55,7 @@ abstract class GitBranchOperation { @NotNull protected final Git myGit; @NotNull protected final GitBranchUiHandler myUiHandler; @NotNull private final Collection<GitRepository> myRepositories; - @NotNull protected final String myCurrentBranchOrRev; + @NotNull protected final Map<GitRepository, String> myCurrentHeads; private final GitVcsSettings mySettings; @NotNull private final Collection<GitRepository> mySuccessfulRepositories; @@ -63,7 +68,13 @@ abstract class GitBranchOperation { myGit = git; myUiHandler = uiHandler; myRepositories = repositories; - myCurrentBranchOrRev = GitBranchUtil.getCurrentBranchOrRev(repositories); + myCurrentHeads = ContainerUtil.map2Map(repositories, new Function<GitRepository, Pair<GitRepository, String>>() { + @Override + public Pair<GitRepository, String> fun(GitRepository repository) { + GitLocalBranch currentBranch = repository.getCurrentBranch(); + return Pair.create(repository, currentBranch == null ? repository.getCurrentRevision() : currentBranch.getName()); + } + }); mySuccessfulRepositories = new ArrayList<GitRepository>(); myRemainingRepositories = new ArrayList<GitRepository>(myRepositories); mySettings = myFacade.getSettings(myProject); @@ -220,11 +231,28 @@ abstract class GitBranchOperation { protected void updateRecentBranch() { if (getRepositories().size() == 1) { GitRepository repository = myRepositories.iterator().next(); - mySettings.setRecentBranchOfRepository(repository.getRoot().getPath(), myCurrentBranchOrRev); + mySettings.setRecentBranchOfRepository(repository.getRoot().getPath(), myCurrentHeads.get(repository)); } else { - mySettings.setRecentCommonBranch(myCurrentBranchOrRev); + String recentCommonBranch = getRecentCommonBranch(); + if (recentCommonBranch != null) { + mySettings.setRecentCommonBranch(recentCommonBranch); + } + } + } + + @Nullable + private String getRecentCommonBranch() { + String recentCommonBranch = null; + for (String branch : myCurrentHeads.values()) { + if (recentCommonBranch == null) { + recentCommonBranch = branch; + } + else if (!recentCommonBranch.equals(branch)) { + return null; + } } + return recentCommonBranch; } private void showUnmergedFilesDialogWithRollback() { @@ -340,4 +368,35 @@ abstract class GitBranchOperation { return Pair.create(allConflictingRepositories, affectedChanges); } + + @NotNull + protected static String stringifyBranchesByRepos(@NotNull Map<GitRepository, String> heads) { + MultiMap<String, VirtualFile> grouped = groupByBranches(heads); + if (grouped.size() == 1) { + return grouped.keySet().iterator().next(); + } + return StringUtil.join(grouped.entrySet(), new Function<Map.Entry<String, Collection<VirtualFile>>, String>() { + @Override + public String fun(Map.Entry<String, Collection<VirtualFile>> entry) { + String roots = StringUtil.join(entry.getValue(), new Function<VirtualFile, String>() { + @Override + public String fun(VirtualFile file) { + return file.getName(); + } + }, ", "); + return entry.getKey() + " (in " + roots + ")"; + } + }, "<br/>"); + } + + @NotNull + private static MultiMap<String, VirtualFile> groupByBranches(@NotNull Map<GitRepository, String> heads) { + MultiMap<String, VirtualFile> result = MultiMap.createLinked(); + List<GitRepository> sortedRepos = RepositoryUtil.sortRepositories(heads.keySet()); + for (GitRepository repo : sortedRepos) { + result.putValue(heads.get(repo), repo.getRoot()); + } + return result; + } + } diff --git a/plugins/git4idea/src/git4idea/branch/GitBranchUiHandler.java b/plugins/git4idea/src/git4idea/branch/GitBranchUiHandler.java index 3b694c455c7b..95546f0547e8 100644 --- a/plugins/git4idea/src/git4idea/branch/GitBranchUiHandler.java +++ b/plugins/git4idea/src/git4idea/branch/GitBranchUiHandler.java @@ -22,6 +22,7 @@ import com.intellij.openapi.vfs.VirtualFile; import git4idea.GitCommit; import git4idea.repo.GitRepository; import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; import java.util.Collection; import java.util.List; @@ -68,14 +69,15 @@ public interface GitBranchUiHandler { * Shows the dialog proposing to execute the operation (checkout or merge) smartly, i.e. stash-execute-unstash. * * @param project - * @param changes local changes that would be overwritten by checkout or merge. - * @param paths paths reported by Git (in most cases this is covered by {@code changes}. - * @param operation operation name: checkout or merge - * @param isForcePossible can the operation be executed force (force checkout is possible, force merge - not). + * @param changes local changes that would be overwritten by checkout or merge. + * @param paths paths reported by Git (in most cases this is covered by {@code changes}. + * @param operation operation name: checkout or merge + * @param forceButtonTitle if the operation can be executed force (force checkout is possible), + * specify the title of the force button; otherwise (force merge is not possible) pass null. * @return the code of the decision. */ int showSmartOperationDialog(@NotNull Project project, @NotNull List<Change> changes, @NotNull Collection<String> paths, - @NotNull String operation, boolean isForcePossible); + @NotNull String operation, @Nullable String forceButtonTitle); boolean showBranchIsNotFullyMergedDialog(@NotNull Project project, @NotNull Map<GitRepository, List<GitCommit>> history, @NotNull String unmergedBranch, @NotNull List<String> mergedToBranches, diff --git a/plugins/git4idea/src/git4idea/branch/GitBranchUiHandlerImpl.java b/plugins/git4idea/src/git4idea/branch/GitBranchUiHandlerImpl.java index cea2b55596e0..20e207d14ba1 100644 --- a/plugins/git4idea/src/git4idea/branch/GitBranchUiHandlerImpl.java +++ b/plugins/git4idea/src/git4idea/branch/GitBranchUiHandlerImpl.java @@ -172,7 +172,7 @@ public class GitBranchUiHandlerImpl implements GitBranchUiHandler { @Override public int showSmartOperationDialog(@NotNull Project project, @NotNull List<Change> changes, @NotNull Collection<String> paths, - @NotNull String operation, boolean isForcePossible) { + @NotNull String operation, @Nullable String forceButtonTitle) { JComponent fileBrowser; if (!changes.isEmpty()) { fileBrowser = new ChangesBrowserWithRollback(project, changes); @@ -180,7 +180,7 @@ public class GitBranchUiHandlerImpl implements GitBranchUiHandler { else { fileBrowser = new GitSimplePathsBrowser(project, paths); } - return GitSmartOperationDialog.showAndGetAnswer(myProject, fileBrowser, operation, isForcePossible); + return GitSmartOperationDialog.showAndGetAnswer(myProject, fileBrowser, operation, forceButtonTitle); } @Override diff --git a/plugins/git4idea/src/git4idea/branch/GitCheckoutNewBranchOperation.java b/plugins/git4idea/src/git4idea/branch/GitCheckoutNewBranchOperation.java index 22e486e6eab0..cdc4f7711801 100644 --- a/plugins/git4idea/src/git4idea/branch/GitCheckoutNewBranchOperation.java +++ b/plugins/git4idea/src/git4idea/branch/GitCheckoutNewBranchOperation.java @@ -89,7 +89,7 @@ class GitCheckoutNewBranchOperation extends GitBranchOperation { protected String getRollbackProposal() { return "However checkout has succeeded for the following " + repositories() + ":<br/>" + successfulRepositoriesJoined() + - "<br/>You may rollback (checkout back to " + myCurrentBranchOrRev + " and delete " + myNewBranchName + ") not to let branches diverge."; + "<br/>You may rollback (checkout previous branch back, and delete " + myNewBranchName + ") not to let branches diverge."; } @NotNull @@ -104,7 +104,7 @@ class GitCheckoutNewBranchOperation extends GitBranchOperation { GitCompoundResult deleteResult = new GitCompoundResult(myProject); Collection<GitRepository> repositories = getSuccessfulRepositories(); for (GitRepository repository : repositories) { - GitCommandResult result = myGit.checkout(repository, myCurrentBranchOrRev, null, true); + GitCommandResult result = myGit.checkout(repository, myCurrentHeads.get(repository), null, true); checkoutResult.append(repository, result); if (result.success()) { deleteResult.append(repository, myGit.branchDelete(repository, myNewBranchName, false)); @@ -113,7 +113,7 @@ class GitCheckoutNewBranchOperation extends GitBranchOperation { } if (checkoutResult.totalSuccess() && deleteResult.totalSuccess()) { VcsNotifier.getInstance(myProject).notifySuccess("Rollback successful", String - .format("Checked out %s and deleted %s on %s %s", code(myCurrentBranchOrRev), code(myNewBranchName), + .format("Checked out %s and deleted %s on %s %s", stringifyBranchesByRepos(myCurrentHeads), code(myNewBranchName), StringUtil.pluralize("root", repositories.size()), successfulRepositoriesJoined())); } else { diff --git a/plugins/git4idea/src/git4idea/branch/GitCheckoutOperation.java b/plugins/git4idea/src/git4idea/branch/GitCheckoutOperation.java index 2441bf7d46cf..b772434959e8 100644 --- a/plugins/git4idea/src/git4idea/branch/GitCheckoutOperation.java +++ b/plugins/git4idea/src/git4idea/branch/GitCheckoutOperation.java @@ -47,7 +47,7 @@ import static git4idea.util.GitUIUtil.code; */ class GitCheckoutOperation extends GitBranchOperation { - public static final String ROLLBACK_PROPOSAL_FORMAT = "You may rollback (checkout back to %s) not to let branches diverge."; + public static final String ROLLBACK_PROPOSAL_FORMAT = "You may rollback (checkout back to previous branch) not to let branches diverge."; @NotNull private final String myStartPointReference; @Nullable private final String myNewBranch; @@ -115,12 +115,14 @@ class GitCheckoutOperation extends GitBranchOperation { private boolean smartCheckoutOrNotify(@NotNull GitRepository repository, @NotNull GitMessageWithFilesDetector localChangesOverwrittenByCheckout) { Pair<List<GitRepository>, List<Change>> conflictingRepositoriesAndAffectedChanges = - getConflictingRepositoriesAndAffectedChanges(repository, localChangesOverwrittenByCheckout, myCurrentBranchOrRev, myStartPointReference); + getConflictingRepositoriesAndAffectedChanges(repository, localChangesOverwrittenByCheckout, myCurrentHeads.get(repository), + myStartPointReference); List<GitRepository> allConflictingRepositories = conflictingRepositoriesAndAffectedChanges.getFirst(); List<Change> affectedChanges = conflictingRepositoriesAndAffectedChanges.getSecond(); Collection<String> absolutePaths = GitUtil.toAbsolute(repository.getRoot(), localChangesOverwrittenByCheckout.getRelativeFilePaths()); - int smartCheckoutDecision = myUiHandler.showSmartOperationDialog(myProject, affectedChanges, absolutePaths, "checkout", true); + int smartCheckoutDecision = myUiHandler.showSmartOperationDialog(myProject, affectedChanges, absolutePaths, "checkout", + "&Force Checkout"); if (smartCheckoutDecision == GitSmartOperationDialog.SMART_EXIT_CODE) { boolean smartCheckedOutSuccessfully = smartCheckout(allConflictingRepositories, myStartPointReference, myNewBranch, getIndicator()); if (smartCheckedOutSuccessfully) { @@ -153,8 +155,7 @@ class GitCheckoutOperation extends GitBranchOperation { @Override protected String getRollbackProposal() { return "However checkout has succeeded for the following " + repositories() + ":<br/>" + - successfulRepositoriesJoined() + - "<br/>" + String.format(ROLLBACK_PROPOSAL_FORMAT, myCurrentBranchOrRev); + successfulRepositoriesJoined() + "<br/>" + ROLLBACK_PROPOSAL_FORMAT; } @NotNull @@ -168,7 +169,7 @@ class GitCheckoutOperation extends GitBranchOperation { GitCompoundResult checkoutResult = new GitCompoundResult(myProject); GitCompoundResult deleteResult = new GitCompoundResult(myProject); for (GitRepository repository : getSuccessfulRepositories()) { - GitCommandResult result = myGit.checkout(repository, myCurrentBranchOrRev, null, true); + GitCommandResult result = myGit.checkout(repository, myCurrentHeads.get(repository), null, true); checkoutResult.append(repository, result); if (result.success() && myNewBranch != null) { /* @@ -183,7 +184,7 @@ class GitCheckoutOperation extends GitBranchOperation { if (!checkoutResult.totalSuccess() || !deleteResult.totalSuccess()) { StringBuilder message = new StringBuilder(); if (!checkoutResult.totalSuccess()) { - message.append("Errors during checking out ").append(myCurrentBranchOrRev).append(": "); + message.append("Errors during checkout: "); message.append(checkoutResult.getErrorOutputWithReposIndication()); } if (!deleteResult.totalSuccess()) { diff --git a/plugins/git4idea/src/git4idea/branch/GitDeleteBranchOperation.java b/plugins/git4idea/src/git4idea/branch/GitDeleteBranchOperation.java index d3501caf7089..1dba1e07f6ac 100644 --- a/plugins/git4idea/src/git4idea/branch/GitDeleteBranchOperation.java +++ b/plugins/git4idea/src/git4idea/branch/GitDeleteBranchOperation.java @@ -68,7 +68,7 @@ class GitDeleteBranchOperation extends GitBranchOperation { else if (notFullyMergedDetector.hasHappened()) { String baseBranch = notMergedToUpstreamDetector.getBaseBranch(); if (baseBranch == null) { // GitBranchNotMergedToUpstreamDetector didn't happen - baseBranch = myCurrentBranchOrRev; + baseBranch = myCurrentHeads.get(repository); } Collection<GitRepository> remainingRepositories = getRemainingRepositories(); diff --git a/plugins/git4idea/src/git4idea/branch/GitMergeOperation.java b/plugins/git4idea/src/git4idea/branch/GitMergeOperation.java index 89a50cfd6054..1b8f382d1dd8 100644 --- a/plugins/git4idea/src/git4idea/branch/GitMergeOperation.java +++ b/plugins/git4idea/src/git4idea/branch/GitMergeOperation.java @@ -34,6 +34,7 @@ import git4idea.commands.*; import git4idea.merge.GitMergeCommittingConflictResolver; import git4idea.merge.GitMerger; import git4idea.repo.GitRepository; +import git4idea.reset.GitResetMode; import git4idea.util.GitPreservingProcess; import org.jetbrains.annotations.NotNull; @@ -189,12 +190,13 @@ class GitMergeOperation extends GitBranchOperation { private boolean proposeSmartMergePerformAndNotify(@NotNull GitRepository repository, @NotNull GitMessageWithFilesDetector localChangesOverwrittenByMerge) { Pair<List<GitRepository>, List<Change>> conflictingRepositoriesAndAffectedChanges = - getConflictingRepositoriesAndAffectedChanges(repository, localChangesOverwrittenByMerge, myCurrentBranchOrRev, myBranchToMerge); + getConflictingRepositoriesAndAffectedChanges(repository, localChangesOverwrittenByMerge, myCurrentHeads.get(repository), + myBranchToMerge); List<GitRepository> allConflictingRepositories = conflictingRepositoriesAndAffectedChanges.getFirst(); List<Change> affectedChanges = conflictingRepositoriesAndAffectedChanges.getSecond(); Collection<String> absolutePaths = GitUtil.toAbsolute(repository.getRoot(), localChangesOverwrittenByMerge.getRelativeFilePaths()); - int smartCheckoutDecision = myUiHandler.showSmartOperationDialog(myProject, affectedChanges, absolutePaths, "merge", false); + int smartCheckoutDecision = myUiHandler.showSmartOperationDialog(myProject, affectedChanges, absolutePaths, "merge", null); if (smartCheckoutDecision == GitSmartOperationDialog.SMART_EXIT_CODE) { return doSmartMerge(allConflictingRepositories); } @@ -322,7 +324,7 @@ class GitMergeOperation extends GitBranchOperation { @NotNull private GitCommandResult rollback(@NotNull GitRepository repository) { - return myGit.resetHard(repository, myCurrentRevisionsBeforeMerge.get(repository)); + return myGit.reset(repository, GitResetMode.HARD, myCurrentRevisionsBeforeMerge.get(repository)); } @NotNull @@ -339,7 +341,8 @@ class GitMergeOperation extends GitBranchOperation { @NotNull @Override public String getSuccessMessage() { - return String.format("Merged <b><code>%s</code></b> to <b><code>%s</code></b>", myBranchToMerge, myCurrentBranchOrRev); + return String.format("Merged <b><code>%s</code></b> to <b><code>%s</code></b>", + myBranchToMerge, stringifyBranchesByRepos(myCurrentHeads)); } @NotNull diff --git a/plugins/git4idea/src/git4idea/branch/GitSmartOperationDialog.java b/plugins/git4idea/src/git4idea/branch/GitSmartOperationDialog.java index 9c2ef4a8d107..4f5073027624 100644 --- a/plugins/git4idea/src/git4idea/branch/GitSmartOperationDialog.java +++ b/plugins/git4idea/src/git4idea/branch/GitSmartOperationDialog.java @@ -24,6 +24,7 @@ import com.intellij.ui.components.JBLabel; import com.intellij.util.ui.UIUtil; import git4idea.GitPlatformFacade; import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; import javax.swing.*; import java.awt.event.ActionEvent; @@ -44,18 +45,18 @@ public class GitSmartOperationDialog extends DialogWrapper { @NotNull private final JComponent myFileBrowser; @NotNull private final String myOperationTitle; - private final boolean myShowForceButton; + @Nullable private final String myForceButton; /** * Shows the dialog with the list of local changes preventing merge/checkout and returns the dialog exit code. */ static int showAndGetAnswer(@NotNull final Project project, @NotNull final JComponent fileBrowser, - @NotNull final String operationTitle, final boolean showForceButton) { + @NotNull final String operationTitle, @Nullable final String forceButtonTitle) { final AtomicInteger exitCode = new AtomicInteger(); UIUtil.invokeAndWaitIfNeeded(new Runnable() { @Override public void run() { - GitSmartOperationDialog dialog = new GitSmartOperationDialog(project, fileBrowser, operationTitle, showForceButton); + GitSmartOperationDialog dialog = new GitSmartOperationDialog(project, fileBrowser, operationTitle, forceButtonTitle); ServiceManager.getService(project, GitPlatformFacade.class).showDialog(dialog); exitCode.set(dialog.getExitCode()); } @@ -64,11 +65,11 @@ public class GitSmartOperationDialog extends DialogWrapper { } private GitSmartOperationDialog(@NotNull Project project, @NotNull JComponent fileBrowser, @NotNull String operationTitle, - boolean showForceButton) { + @Nullable String forceButton) { super(project); myFileBrowser = fileBrowser; myOperationTitle = operationTitle; - myShowForceButton = showForceButton; + myForceButton = forceButton; String capitalizedOperation = capitalize(myOperationTitle); setTitle("Git " + capitalizedOperation + " Problem"); @@ -82,8 +83,8 @@ public class GitSmartOperationDialog extends DialogWrapper { @NotNull @Override protected Action[] createLeftSideActions() { - if (myShowForceButton) { - return new Action[] {new ForceCheckoutAction(myOperationTitle) }; + if (myForceButton != null) { + return new Action[]{new ForceCheckoutAction(myForceButton, myOperationTitle)}; } return new Action[0]; } @@ -110,8 +111,8 @@ public class GitSmartOperationDialog extends DialogWrapper { private class ForceCheckoutAction extends AbstractAction { - ForceCheckoutAction(@NotNull String operationTitle) { - super("&Force " + capitalize(operationTitle)); + ForceCheckoutAction(@NotNull String buttonTitle, @NotNull String operationTitle) { + super(buttonTitle); putValue(Action.SHORT_DESCRIPTION, capitalize(operationTitle) + " and overwrite local changes"); } |