From 4b73e06c20d1af4245aae4f637f85c413dc56565 Mon Sep 17 00:00:00 2001
From: Kirill Likhodedov
Date: Mon, 6 Feb 2012 16:25:06 +0400
Subject: [PATCH 01/16] remove commented code
---
.../ui/branch/GitBranchPopupActions.java | 18 ------------------
1 file changed, 18 deletions(-)
diff --git a/plugins/git4idea/src/git4idea/ui/branch/GitBranchPopupActions.java b/plugins/git4idea/src/git4idea/ui/branch/GitBranchPopupActions.java
index efdd34f94929..4ae9edab67a2 100644
--- a/plugins/git4idea/src/git4idea/ui/branch/GitBranchPopupActions.java
+++ b/plugins/git4idea/src/git4idea/ui/branch/GitBranchPopupActions.java
@@ -53,7 +53,6 @@ class GitBranchPopupActions {
ActionGroup createActions(@Nullable DefaultActionGroup toInsert) {
DefaultActionGroup popupGroup = new DefaultActionGroup(null, false);
- //popupGroup.addAction(new CurrentBranchAction(GitBranchUiUtil.getDisplayableBranchText(myRepository), "in root " + GitUIUtil.getShortRepositoryName(myRepository)));
popupGroup.addAction(new NewBranchAction(myProject, Collections.singletonList(myRepository), myRepository));
popupGroup.addAction(new CheckoutRevisionActions(myProject, myRepository));
@@ -80,23 +79,6 @@ class GitBranchPopupActions {
return popupGroup;
}
- ///**
- // * "Current branch:" item which is disabled and is just a label to display the current branch.
- // */
- //static class CurrentBranchAction extends DumbAwareAction {
- // CurrentBranchAction(@NotNull String currentBranchName, @Nullable String rootName) {
- // super("", String.format("Current branch is %s %s", currentBranchName, rootName), null);
- // getTemplatePresentation().setText("Current Branch: " + currentBranchName, false); // no mnemonics
- // }
- //
- // @Override public void actionPerformed(AnActionEvent e) {
- // }
- //
- // @Override public void update(AnActionEvent e) {
- // e.getPresentation().setEnabled(false); // this action works as a label
- // }
- //}
-
static class NewBranchAction extends DumbAwareAction {
private final Project myProject;
private final List myRepositories;
From e4448bd74e3d3f31ccdbc54c494b01f3beabff94 Mon Sep 17 00:00:00 2001
From: Kirill Likhodedov
Date: Mon, 6 Feb 2012 16:30:13 +0400
Subject: [PATCH 02/16] Git branch delete: remove extra repository refresh, it
is done in the GitBranchOperation
---
.../src/git4idea/branch/GitBranchOperationsProcessor.java | 6 +-----
1 file changed, 1 insertion(+), 5 deletions(-)
diff --git a/plugins/git4idea/src/git4idea/branch/GitBranchOperationsProcessor.java b/plugins/git4idea/src/git4idea/branch/GitBranchOperationsProcessor.java
index df1989fdc3de..271d18a93d68 100644
--- a/plugins/git4idea/src/git4idea/branch/GitBranchOperationsProcessor.java
+++ b/plugins/git4idea/src/git4idea/branch/GitBranchOperationsProcessor.java
@@ -172,11 +172,7 @@ public final class GitBranchOperationsProcessor {
}
private void doDelete(final String branchName, ProgressIndicator indicator) {
- GitDeleteBranchOperation operation = new GitDeleteBranchOperation(myProject, myRepositories, branchName, getCurrentBranch(), indicator);
- operation.execute();
- for (GitRepository repository : myRepositories) {
- repository.update(GitRepository.TrackedTopic.BRANCHES, GitRepository.TrackedTopic.CONFIG);
- }
+ new GitDeleteBranchOperation(myProject, myRepositories, branchName, getCurrentBranch(), indicator).execute();
}
/**
From 74777152571d82fe4f9f7a27bed4608eb5637fae Mon Sep 17 00:00:00 2001
From: Kirill Likhodedov
Date: Mon, 6 Feb 2012 16:33:59 +0400
Subject: [PATCH 03/16] Git branches: Remove public where not needed
---
.../src/git4idea/branch/GitCheckoutNewBranchOperation.java | 4 ++--
.../git4idea/src/git4idea/branch/GitCheckoutOperation.java | 4 ++--
.../src/git4idea/branch/GitDeleteBranchOperation.java | 2 +-
3 files changed, 5 insertions(+), 5 deletions(-)
diff --git a/plugins/git4idea/src/git4idea/branch/GitCheckoutNewBranchOperation.java b/plugins/git4idea/src/git4idea/branch/GitCheckoutNewBranchOperation.java
index ec9f340839e0..70529ec6cae0 100644
--- a/plugins/git4idea/src/git4idea/branch/GitCheckoutNewBranchOperation.java
+++ b/plugins/git4idea/src/git4idea/branch/GitCheckoutNewBranchOperation.java
@@ -37,13 +37,13 @@ import static git4idea.util.GitUIUtil.code;
*
* @author Kirill Likhodedov
*/
-public class GitCheckoutNewBranchOperation extends GitBranchOperation {
+class GitCheckoutNewBranchOperation extends GitBranchOperation {
@NotNull private final Project myProject;
@NotNull private final String myNewBranchName;
@NotNull private final String myPreviousBranch;
- public GitCheckoutNewBranchOperation(@NotNull Project project, @NotNull Collection repositories,
+ GitCheckoutNewBranchOperation(@NotNull Project project, @NotNull Collection repositories,
@NotNull String newBranchName, @NotNull String previousBranch,
@NotNull ProgressIndicator indicator) {
super(project, repositories, indicator);
diff --git a/plugins/git4idea/src/git4idea/branch/GitCheckoutOperation.java b/plugins/git4idea/src/git4idea/branch/GitCheckoutOperation.java
index 06f93d1c9233..5b2638c045ea 100644
--- a/plugins/git4idea/src/git4idea/branch/GitCheckoutOperation.java
+++ b/plugins/git4idea/src/git4idea/branch/GitCheckoutOperation.java
@@ -68,7 +68,7 @@ import static git4idea.util.GitUIUtil.code;
*
* @author Kirill Likhodedov
*/
-public class GitCheckoutOperation extends GitBranchOperation {
+class GitCheckoutOperation extends GitBranchOperation {
private static final Logger LOG = Logger.getInstance(GitCheckoutOperation.class);
@@ -76,7 +76,7 @@ public class GitCheckoutOperation extends GitBranchOperation {
@Nullable private final String myNewBranch;
@NotNull private final String myPreviousBranch;
- public GitCheckoutOperation(@NotNull Project project, @NotNull Collection repositories,
+ GitCheckoutOperation(@NotNull Project project, @NotNull Collection repositories,
@NotNull String startPointReference, @Nullable String newBranch, @NotNull String previousBranch,
@NotNull ProgressIndicator indicator) {
super(project, repositories, indicator);
diff --git a/plugins/git4idea/src/git4idea/branch/GitDeleteBranchOperation.java b/plugins/git4idea/src/git4idea/branch/GitDeleteBranchOperation.java
index dc2b5ef1fcdc..e7231d774814 100644
--- a/plugins/git4idea/src/git4idea/branch/GitDeleteBranchOperation.java
+++ b/plugins/git4idea/src/git4idea/branch/GitDeleteBranchOperation.java
@@ -52,7 +52,7 @@ class GitDeleteBranchOperation extends GitBranchOperation {
private final String myBranchName;
private final String myCurrentBranch;
- public GitDeleteBranchOperation(@NotNull Project project, @NotNull Collection repositories,
+ GitDeleteBranchOperation(@NotNull Project project, @NotNull Collection repositories,
@NotNull String branchName, @NotNull String currentBranch, @NotNull ProgressIndicator indicator) {
super(project, repositories, indicator);
myBranchName = branchName;
From 4744780fd81858620f82e494505c363799808d2a Mon Sep 17 00:00:00 2001
From: Kirill Likhodedov
Date: Mon, 6 Feb 2012 16:47:08 +0400
Subject: [PATCH 04/16] Move refreshRoot and fatalUntrackedFilesError from
GitCheckoutOperation up to GitBranchOperation - will be reused in merge.
---
.../git4idea/branch/GitBranchOperation.java | 76 ++++++++++++++++++-
.../git4idea/branch/GitCheckoutOperation.java | 55 --------------
2 files changed, 72 insertions(+), 59 deletions(-)
diff --git a/plugins/git4idea/src/git4idea/branch/GitBranchOperation.java b/plugins/git4idea/src/git4idea/branch/GitBranchOperation.java
index 2c2d519fe3f1..fd1aefc70cae 100644
--- a/plugins/git4idea/src/git4idea/branch/GitBranchOperation.java
+++ b/plugins/git4idea/src/git4idea/branch/GitBranchOperation.java
@@ -21,23 +21,28 @@ import com.intellij.notification.NotificationType;
import com.intellij.openapi.progress.ProgressIndicator;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.ui.Messages;
+import com.intellij.openapi.ui.VerticalFlowLayout;
import com.intellij.openapi.util.text.StringUtil;
+import com.intellij.openapi.vcs.changes.ui.SelectFilesDialog;
+import com.intellij.openapi.vfs.VirtualFile;
+import com.intellij.ui.components.JBLabel;
import com.intellij.util.Function;
import com.intellij.util.ui.UIUtil;
-import git4idea.GitUtil;
-import git4idea.GitVcs;
-import git4idea.MessageManager;
-import git4idea.NotificationManager;
+import git4idea.*;
import git4idea.merge.GitConflictResolver;
import git4idea.repo.GitRepository;
+import git4idea.util.UntrackedFilesNotifier;
import org.jetbrains.annotations.NotNull;
+import javax.swing.*;
import javax.swing.event.HyperlinkEvent;
import java.util.ArrayList;
import java.util.Collection;
+import java.util.List;
import java.util.concurrent.atomic.AtomicBoolean;
import static com.intellij.openapi.util.text.StringUtil.pluralize;
+import static com.intellij.openapi.util.text.StringUtil.stripHtml;
/**
* Common class for Git operations with branches aware of multi-root configuration,
@@ -227,4 +232,67 @@ abstract class GitBranchOperation {
});
}
+ /**
+ * Asynchronously refreshes the VFS root directory of the given repository.
+ */
+ protected static void refreshRoot(@NotNull GitRepository repository) {
+ repository.getRoot().refresh(true, true);
+ }
+
+ /**
+ * Shows the error "The following untracked working tree files would be overwritten by checkout/merge".
+ * If there were no repositories that succeeded the operation, shows a notification with a link to the list of these untracked files.
+ * If some repositories succeeded, shows a dialog with the list of these files and a proposal to rollback the operation of those
+ * repositories.
+ */
+ protected void fatalUntrackedFilesError(@NotNull Collection untrackedFiles) {
+ if (wereSuccessful()) {
+ showUntrackedFilesDialogWithRollback(untrackedFiles);
+ }
+ else {
+ showUntrackedFilesNotification(untrackedFiles);
+ }
+ }
+
+ private void showUntrackedFilesNotification(@NotNull Collection untrackedFiles) {
+ UntrackedFilesNotifier.notifyUntrackedFilesOverwrittenBy(myProject, untrackedFiles, "checkout");
+ }
+
+ private void showUntrackedFilesDialogWithRollback(@NotNull Collection untrackedFiles) {
+ String title = "Couldn't checkout";
+ String description = UntrackedFilesNotifier.createUntrackedFilesOverwrittenDescription("checkout", true);
+
+ final SelectFilesDialog dialog = new UntrackedFilesDialog(myProject, new ArrayList(untrackedFiles),
+ stripHtml(description, true));
+ dialog.setTitle(title);
+ UIUtil.invokeAndWaitIfNeeded(new Runnable() {
+ @Override
+ public void run() {
+ DialogManager.getInstance(myProject).showDialog(dialog);
+ }
+ });
+
+ if (dialog.isOK()) {
+ rollback();
+ }
+ }
+
+ private class UntrackedFilesDialog extends SelectFilesDialog {
+
+ public UntrackedFilesDialog(@NotNull Project project, @NotNull List originalFiles, @NotNull String prompt) {
+ super(project, originalFiles, prompt, null, false, false);
+ setOKButtonText("Rollback");
+ setCancelButtonText("Don't rollback");
+ }
+
+ @Override
+ protected JComponent createSouthPanel() {
+ JComponent buttons = super.createSouthPanel();
+ JPanel panel = new JPanel(new VerticalFlowLayout());
+ panel.add(new JBLabel("" + getRollbackProposal() + ""));
+ panel.add(buttons);
+ return panel;
+ }
+ }
+
}
diff --git a/plugins/git4idea/src/git4idea/branch/GitCheckoutOperation.java b/plugins/git4idea/src/git4idea/branch/GitCheckoutOperation.java
index 5b2638c045ea..a8ee67c2e539 100644
--- a/plugins/git4idea/src/git4idea/branch/GitCheckoutOperation.java
+++ b/plugins/git4idea/src/git4idea/branch/GitCheckoutOperation.java
@@ -189,56 +189,6 @@ class GitCheckoutOperation extends GitBranchOperation {
return collectChangesConflictingWithCheckout(remainingRepositories);
}
- private void fatalUntrackedFilesError(@NotNull Collection untrackedFiles) {
- if (wereSuccessful()) {
- showUntrackedFilesDialogWithRollback(untrackedFiles);
- }
- else {
- showUntrackedFilesNotification(untrackedFiles);
- }
- }
-
- private void showUntrackedFilesNotification(@NotNull Collection untrackedFiles) {
- UntrackedFilesNotifier.notifyUntrackedFilesOverwrittenBy(myProject, untrackedFiles, "checkout");
- }
-
- private void showUntrackedFilesDialogWithRollback(@NotNull Collection untrackedFiles) {
- String title = "Couldn't checkout";
- String description = UntrackedFilesNotifier.createUntrackedFilesOverwrittenDescription("checkout", true);
-
- final SelectFilesDialog dialog = new UntrackedFilesDialog(myProject, new ArrayList(untrackedFiles),
- stripHtml(description, true));
- dialog.setTitle(title);
- UIUtil.invokeAndWaitIfNeeded(new Runnable() {
- @Override
- public void run() {
- DialogManager.getInstance(myProject).showDialog(dialog);
- }
- });
-
- if (dialog.isOK()) {
- rollback();
- }
- }
-
- private class UntrackedFilesDialog extends SelectFilesDialog {
-
- public UntrackedFilesDialog(@NotNull Project project, @NotNull List originalFiles, @NotNull String prompt) {
- super(project, originalFiles, prompt, null, false, false);
- setOKButtonText("Rollback");
- setCancelButtonText("Don't rollback");
- }
-
- @Override
- protected JComponent createSouthPanel() {
- JComponent buttons = super.createSouthPanel();
- JPanel panel = new JPanel(new VerticalFlowLayout());
- panel.add(new JBLabel("" + getRollbackProposal() + ""));
- panel.add(buttons);
- return panel;
- }
- }
-
@NotNull
@Override
protected String getRollbackProposal() {
@@ -437,9 +387,4 @@ class GitCheckoutOperation extends GitBranchOperation {
// repository state will be auto-updated with this VFS refresh => no need to call GitRepository#update().
}
}
-
- private static void refreshRoot(GitRepository repository) {
- repository.getRoot().refresh(true, true);
- }
-
}
From 0ff9c62ff371ba8867efdbcb723e28f712c776d6 Mon Sep 17 00:00:00 2001
From: Kirill Likhodedov
Date: Mon, 6 Feb 2012 18:59:23 +0400
Subject: [PATCH 05/16] Generalize messages in GitBranchOperation by
introducing getOperationName(). Fix the test.
---
.../git4idea/branch/GitBranchOperation.java | 56 ++++++++++++++-----
.../branch/GitCheckoutNewBranchOperation.java | 6 ++
.../git4idea/branch/GitCheckoutOperation.java | 15 +++--
.../branch/GitDeleteBranchOperation.java | 6 ++
.../branch/GitBranchOperationsTest.java | 44 ++++++++++-----
5 files changed, 90 insertions(+), 37 deletions(-)
diff --git a/plugins/git4idea/src/git4idea/branch/GitBranchOperation.java b/plugins/git4idea/src/git4idea/branch/GitBranchOperation.java
index fd1aefc70cae..8c5ece93054c 100644
--- a/plugins/git4idea/src/git4idea/branch/GitBranchOperation.java
+++ b/plugins/git4idea/src/git4idea/branch/GitBranchOperation.java
@@ -52,11 +52,6 @@ import static com.intellij.openapi.util.text.StringUtil.stripHtml;
*/
abstract class GitBranchOperation {
- static final String UNMERGED_FILES_ERROR_TITLE = "Can't checkout because of unmerged files";
- static final String UNMERGED_FILES_ERROR_NOTIFICATION_DESCRIPTION =
- "You have to resolve all merge conflicts before checkout.
" +
- "After resolving conflicts you also probably would want to commit your files to the current branch.";
-
@NotNull protected final Project myProject;
@NotNull private final Collection myRepositories;
@NotNull private final ProgressIndicator myIndicator;
@@ -83,6 +78,14 @@ abstract class GitBranchOperation {
@NotNull
protected abstract String getRollbackProposal();
+ /**
+ * Returns a short downcased name of the operation.
+ * It is used by some dialogs or notifications which are common to several operations.
+ * Some operations (like checkout new branch) can be not mentioned in these dialogs, so their operation names would be not used.
+ */
+ @NotNull
+ protected abstract String getOperationName();
+
/**
* @return next repository that wasn't handled (e.g. checked out) yet.
*/
@@ -171,6 +174,27 @@ abstract class GitBranchOperation {
}
}
+ @NotNull
+ private String unmergedFilesErrorTitle() {
+ return unmergedFilesErrorTitle(getOperationName());
+ }
+
+ @NotNull
+ private static String unmergedFilesErrorTitle(String operationName) {
+ return "Can't " + operationName + " because of unmerged files";
+ }
+
+ @NotNull
+ private String unmergedFilesErrorNotificationDescription() {
+ return unmergedFilesErrorNotificationDescription(getOperationName());
+ }
+
+ @NotNull
+ private static String unmergedFilesErrorNotificationDescription(String operationName) {
+ return "You have to resolve all merge conflicts before " + operationName + ".
" +
+ "After resolving conflicts you also probably would want to commit your files to the current branch.";
+ }
+
protected void showFatalNotification(@NotNull String title, @NotNull String message) {
notifyError(title, message);
}
@@ -206,10 +230,12 @@ abstract class GitBranchOperation {
final AtomicBoolean ok = new AtomicBoolean();
UIUtil.invokeAndWaitIfNeeded(new Runnable() {
@Override public void run() {
- String description = "You have to resolve all merge conflicts before checkout.
" + getRollbackProposal() + "";
+ String description = "You have to resolve all merge conflicts before " + getOperationName() + ".
" +
+ getRollbackProposal() + "";
// suppressing: this message looks ugly if capitalized by words
//noinspection DialogTitleCapitalization
- ok.set(Messages.OK == MessageManager.showYesNoDialog(myProject, description, UNMERGED_FILES_ERROR_TITLE, "Rollback", "Don't rollback", Messages.getErrorIcon()));
+ ok.set(Messages.OK == MessageManager.showYesNoDialog(myProject, description, unmergedFilesErrorTitle(),
+ "Rollback", "Don't rollback", Messages.getErrorIcon()));
}
});
if (ok.get()) {
@@ -218,13 +244,15 @@ abstract class GitBranchOperation {
}
private void showUnmergedFilesNotification() {
- String title = UNMERGED_FILES_ERROR_TITLE;
- String description = UNMERGED_FILES_ERROR_NOTIFICATION_DESCRIPTION;
- NotificationManager.getInstance(myProject).notify(GitVcs.IMPORTANT_ERROR_NOTIFICATION, title, description, NotificationType.ERROR, new NotificationListener() {
+ String title = unmergedFilesErrorTitle();
+ String description = unmergedFilesErrorNotificationDescription();
+ NotificationManager.getInstance(myProject).notify(GitVcs.IMPORTANT_ERROR_NOTIFICATION, title, description, NotificationType.ERROR,
+ new NotificationListener() {
@Override public void hyperlinkUpdate(@NotNull Notification notification, @NotNull HyperlinkEvent event) {
if (event.getEventType() == HyperlinkEvent.EventType.ACTIVATED && event.getDescription().equals("resolve")) {
GitConflictResolver.Params params = new GitConflictResolver.Params().
- setMergeDescription("The following files have unresolved conflicts. You need to resolve them before checking out.").
+ setMergeDescription("The following files have unresolved conflicts. You need to resolve them before " +
+ getOperationName() + ".").
setErrorNotificationTitle("Unresolved files remain.");
new GitConflictResolver(myProject, GitUtil.getRoots(getRepositories()), params).merge();
}
@@ -255,12 +283,12 @@ abstract class GitBranchOperation {
}
private void showUntrackedFilesNotification(@NotNull Collection untrackedFiles) {
- UntrackedFilesNotifier.notifyUntrackedFilesOverwrittenBy(myProject, untrackedFiles, "checkout");
+ UntrackedFilesNotifier.notifyUntrackedFilesOverwrittenBy(myProject, untrackedFiles, getOperationName());
}
private void showUntrackedFilesDialogWithRollback(@NotNull Collection untrackedFiles) {
- String title = "Couldn't checkout";
- String description = UntrackedFilesNotifier.createUntrackedFilesOverwrittenDescription("checkout", true);
+ String title = "Couldn't " + getOperationName();
+ String description = UntrackedFilesNotifier.createUntrackedFilesOverwrittenDescription(getOperationName(), true);
final SelectFilesDialog dialog = new UntrackedFilesDialog(myProject, new ArrayList(untrackedFiles),
stripHtml(description, true));
diff --git a/plugins/git4idea/src/git4idea/branch/GitCheckoutNewBranchOperation.java b/plugins/git4idea/src/git4idea/branch/GitCheckoutNewBranchOperation.java
index 70529ec6cae0..e6e7fb39a1a3 100644
--- a/plugins/git4idea/src/git4idea/branch/GitCheckoutNewBranchOperation.java
+++ b/plugins/git4idea/src/git4idea/branch/GitCheckoutNewBranchOperation.java
@@ -98,6 +98,12 @@ class GitCheckoutNewBranchOperation extends GitBranchOperation {
"
You may rollback (checkout back to " + myPreviousBranch + " and delete " + myNewBranchName + ") not to let branches diverge.";
}
+ @NotNull
+ @Override
+ protected String getOperationName() {
+ return "checkout";
+ }
+
@Override
protected void rollback() {
GitCompoundResult checkoutResult = new GitCompoundResult(myProject);
diff --git a/plugins/git4idea/src/git4idea/branch/GitCheckoutOperation.java b/plugins/git4idea/src/git4idea/branch/GitCheckoutOperation.java
index a8ee67c2e539..8b14bdab75bd 100644
--- a/plugins/git4idea/src/git4idea/branch/GitCheckoutOperation.java
+++ b/plugins/git4idea/src/git4idea/branch/GitCheckoutOperation.java
@@ -19,25 +19,20 @@ import com.intellij.notification.NotificationType;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.progress.ProgressIndicator;
import com.intellij.openapi.project.Project;
-import com.intellij.openapi.ui.VerticalFlowLayout;
import com.intellij.openapi.util.Clock;
import com.intellij.openapi.util.Condition;
import com.intellij.openapi.util.io.FileUtil;
import com.intellij.openapi.vcs.VcsException;
import com.intellij.openapi.vcs.changes.Change;
import com.intellij.openapi.vcs.changes.ChangeListManager;
-import com.intellij.openapi.vcs.changes.ui.SelectFilesDialog;
import com.intellij.openapi.vcs.history.VcsRevisionNumber;
import com.intellij.openapi.vcs.merge.MergeDialogCustomizer;
import com.intellij.openapi.vfs.VirtualFile;
-import com.intellij.ui.components.JBLabel;
import com.intellij.util.ArrayUtil;
import com.intellij.util.containers.ContainerUtil;
import com.intellij.util.continuation.ContinuationContext;
import com.intellij.util.text.DateFormatUtil;
-import com.intellij.util.ui.UIUtil;
import com.intellij.vcsUtil.VcsUtil;
-import git4idea.DialogManager;
import git4idea.GitUtil;
import git4idea.GitVcs;
import git4idea.commands.*;
@@ -46,15 +41,13 @@ import git4idea.repo.GitRepository;
import git4idea.stash.GitChangesSaver;
import git4idea.update.GitComplexProcess;
import git4idea.util.GitUIUtil;
-import git4idea.util.UntrackedFilesNotifier;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
-import javax.swing.*;
import java.util.*;
import java.util.concurrent.atomic.AtomicBoolean;
-import static com.intellij.openapi.util.text.StringUtil.*;
+import static com.intellij.openapi.util.text.StringUtil.join;
import static git4idea.commands.GitMessageWithFilesDetector.Event.LOCAL_CHANGES_OVERWRITTEN_BY_CHECKOUT;
import static git4idea.commands.GitMessageWithFilesDetector.Event.UNTRACKED_FILES_OVERWRITTEN_BY;
import static git4idea.util.GitUIUtil.code;
@@ -197,6 +190,12 @@ class GitCheckoutOperation extends GitBranchOperation {
"
You may rollback (checkout back to " + myPreviousBranch + ") not to let branches diverge.";
}
+ @NotNull
+ @Override
+ protected String getOperationName() {
+ return "checkout";
+ }
+
@Override
protected void rollback() {
GitCompoundResult checkoutResult = new GitCompoundResult(myProject);
diff --git a/plugins/git4idea/src/git4idea/branch/GitDeleteBranchOperation.java b/plugins/git4idea/src/git4idea/branch/GitDeleteBranchOperation.java
index e7231d774814..0c4c89fa3f69 100644
--- a/plugins/git4idea/src/git4idea/branch/GitDeleteBranchOperation.java
+++ b/plugins/git4idea/src/git4idea/branch/GitDeleteBranchOperation.java
@@ -142,6 +142,12 @@ class GitDeleteBranchOperation extends GitBranchOperation {
"
You may rollback (recreate " + myBranchName + " in these roots) not to let branches diverge.";
}
+ @NotNull
+ @Override
+ protected String getOperationName() {
+ return "branch deletion";
+ }
+
@NotNull
private GitCompoundResult forceDelete(@NotNull String branchName, @NotNull Collection possibleFailedRepositories) {
GitCompoundResult compoundResult = new GitCompoundResult(myProject);
diff --git a/plugins/git4idea/tests/git4idea/branch/GitBranchOperationsTest.java b/plugins/git4idea/tests/git4idea/branch/GitBranchOperationsTest.java
index 2209fdc834d9..656b18655d13 100644
--- a/plugins/git4idea/tests/git4idea/branch/GitBranchOperationsTest.java
+++ b/plugins/git4idea/tests/git4idea/branch/GitBranchOperationsTest.java
@@ -141,14 +141,14 @@ public class GitBranchOperationsTest extends AbstractVcsTestCase {
public void create_new_branch_with_unmerged_files_in_first_repo_should_show_notification() throws Exception {
GitTestScenarioGenerator.prepareUnmergedFiles(myUltimate);
doCheckoutNewBranch();
- assertNotify(NotificationType.ERROR, GitBranchOperation.UNMERGED_FILES_ERROR_NOTIFICATION_DESCRIPTION);
+ assertNotify(NotificationType.ERROR, unmergedFilesErrorNotificationDescription("checkout"));
}
-
+
@Test
public void create_new_branch_with_unmerged_files_in_second_repo_should_propose_to_rollback() throws Exception {
GitTestScenarioGenerator.prepareUnmergedFiles(myCommunity, myContrib);
doCheckoutNewBranch();
- assertMessage(GitBranchOperation.UNMERGED_FILES_ERROR_TITLE);
+ assertMessage(unmergedFilesErrorTitle("checkout"));
}
@Test
@@ -156,7 +156,7 @@ public class GitBranchOperationsTest extends AbstractVcsTestCase {
GitTestScenarioGenerator.prepareUnmergedFiles(myCommunity, myContrib);
myMessageManager.nextAnswer(Messages.OK);
doCheckoutNewBranch();
- assertMessage(GitBranchOperation.UNMERGED_FILES_ERROR_TITLE);
+ assertMessage(unmergedFilesErrorTitle("checkout"));
assertBranch("master");
assertTrue(!branch(myUltimate).contains(NEW_BRANCH));
}
@@ -166,7 +166,7 @@ public class GitBranchOperationsTest extends AbstractVcsTestCase {
GitTestScenarioGenerator.prepareUnmergedFiles(myCommunity, myContrib);
myMessageManager.nextAnswer(Messages.CANCEL);
doCheckoutNewBranch();
- assertMessage(GitBranchOperation.UNMERGED_FILES_ERROR_TITLE);
+ assertMessage(unmergedFilesErrorTitle("checkout"));
assertBranch(myUltimate, NEW_BRANCH);
assertBranch(myCommunity, MASTER);
@@ -185,7 +185,7 @@ public class GitBranchOperationsTest extends AbstractVcsTestCase {
prepareBranchForSimpleCheckout();
GitTestScenarioGenerator.prepareUnmergedFiles(myUltimate);
doCheckout("feature", null);
- assertNotify(NotificationType.ERROR, GitBranchOperation.UNMERGED_FILES_ERROR_NOTIFICATION_DESCRIPTION);
+ assertNotify(NotificationType.ERROR, unmergedFilesErrorNotificationDescription("checkout"));
}
@Test
@@ -193,7 +193,7 @@ public class GitBranchOperationsTest extends AbstractVcsTestCase {
prepareBranchForSimpleCheckout();
GitTestScenarioGenerator.prepareUnmergedFiles(myCommunity);
doCheckout("feature", null);
- assertMessage(GitBranchOperation.UNMERGED_FILES_ERROR_TITLE);
+ assertMessage(unmergedFilesErrorTitle("checkout"));
}
@Test
@@ -202,7 +202,7 @@ public class GitBranchOperationsTest extends AbstractVcsTestCase {
GitTestScenarioGenerator.prepareUnmergedFiles(myCommunity);
myMessageManager.nextAnswer(Messages.OK);
doCheckout("feature", null);
- assertMessage(GitBranchOperation.UNMERGED_FILES_ERROR_TITLE);
+ assertMessage(unmergedFilesErrorTitle("checkout"));
assertBranch("master");
}
@@ -212,7 +212,7 @@ public class GitBranchOperationsTest extends AbstractVcsTestCase {
GitTestScenarioGenerator.prepareUnmergedFiles(myCommunity);
myMessageManager.nextAnswer(Messages.CANCEL);
doCheckout("feature", null);
- assertMessage(GitBranchOperation.UNMERGED_FILES_ERROR_TITLE);
+ assertMessage(unmergedFilesErrorTitle("checkout"));
assertBranch(myUltimate, "feature");
assertBranch(myCommunity, "master");
assertBranch(myContrib, "master");
@@ -234,7 +234,7 @@ public class GitBranchOperationsTest extends AbstractVcsTestCase {
branch(myUltimate, "feature");
branch(myContrib, "feature");
- Class gitCheckoutOperationClass = Class.forName("git4idea.branch.GitCheckoutOperation");
+ Class gitCheckoutOperationClass = Class.forName("git4idea.branch.GitBranchOperation");
Class[] classes = gitCheckoutOperationClass.getDeclaredClasses();
Class untrackedFilesDialogClass = null;
for (Class aClass : classes) {
@@ -335,7 +335,7 @@ public class GitBranchOperationsTest extends AbstractVcsTestCase {
doCheckout("feature", null);
assertMessage("Couldn't checkout feature",
"Local changes would be overwritten by checkout.
Stash or commit them before checking out a branch.
" +
- "However checkout has succeeded for the following repositories:
" +
+ "However checkout has succeeded for the following repository:
" +
myUltimate.getPresentableUrl() +
"
You may rollback (checkout back to master) not to let branches diverge.",
"Rollback", "Don't rollback");
@@ -347,7 +347,7 @@ public class GitBranchOperationsTest extends AbstractVcsTestCase {
GitTestScenarioGenerator.prepareUnmergedFiles(myCommunity);
myMessageManager.nextAnswer(Messages.OK);
doCheckout("feature", "newBranch");
- assertMessage(GitBranchOperation.UNMERGED_FILES_ERROR_TITLE);
+ assertMessage(unmergedFilesErrorTitle("checkout"));
assertBranch("master");
for (GitRepository repository : myRepositories) {
assertFalse(branch(repository).contains("newBranch"), "Branch newBranch wasn't deleted from repository " + getShortRepositoryName(
@@ -423,7 +423,7 @@ public class GitBranchOperationsTest extends AbstractVcsTestCase {
registerNotFullyMergedDialog(DialogWrapper.CANCEL_EXIT_CODE);
doDeleteBranch("unmerged_branch");
- assertNotify(NotificationType.ERROR, "Branch unmerged_branch wasn't deleted", "This branch is not fully merged to master");
+ assertNotify(NotificationType.ERROR, "Branch unmerged_branch wasn't deleted", "This branch is not fully merged to master.");
}
@Test
@@ -488,6 +488,20 @@ public class GitBranchOperationsTest extends AbstractVcsTestCase {
callPrivateBranchOperationsProcessorMethod("doCheckoutNewBranch", NEW_BRANCH);
}
+ private static String unmergedFilesErrorNotificationDescription(String operation)
+ throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
+ Method method = GitBranchOperation.class.getDeclaredMethod("unmergedFilesErrorNotificationDescription", String.class);
+ method.setAccessible(true);
+ return (String) method.invoke(null, operation);
+ }
+
+ private static String unmergedFilesErrorTitle(String operation)
+ throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
+ Method method = GitBranchOperation.class.getDeclaredMethod("unmergedFilesErrorTitle", String.class);
+ method.setAccessible(true);
+ return (String) method.invoke(null, operation);
+ }
+
private void callPrivateBranchOperationsProcessorMethod(String methodName, String branchName) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException {
// call private doCheckoutNewBranch instead of public checkoutNewBranch to avoid dealing with background process creation
// same for other branch operations
@@ -547,10 +561,10 @@ public class GitBranchOperationsTest extends AbstractVcsTestCase {
TestMessageManager.Message message = myMessageManager.getLastMessage();
assertNotNull(message);
if (title != null) {
- assertEquals(message.getTitle(), title);
+ assertEquals(stripHtmlAndBreaks(message.getTitle()), stripHtmlAndBreaks(title));
}
if (description != null) {
- assertEquals(message.getDescription(), description);
+ assertEquals(stripHtmlAndBreaks(message.getDescription()), stripHtmlAndBreaks(description));
}
if (yesButton != null) {
assertEquals(message.getYesText(), yesButton);
From 7c88bd0cd51e667a0f94f2dfee2917c5227dfa44 Mon Sep 17 00:00:00 2001
From: Kirill Likhodedov
Date: Mon, 6 Feb 2012 21:22:43 +0400
Subject: [PATCH 06/16] Change GitVcs.IMPORTANT_ERROR_NOTIFICATION
NotificationGroup id, because it was changed to non-sticky balloon some time
ago, and changing to sticky-balloon in the code has no effect on those users.
---
plugins/git4idea/src/git4idea/GitVcs.java | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/plugins/git4idea/src/git4idea/GitVcs.java b/plugins/git4idea/src/git4idea/GitVcs.java
index 960ae602b6b9..e4f48069e5e3 100644
--- a/plugins/git4idea/src/git4idea/GitVcs.java
+++ b/plugins/git4idea/src/git4idea/GitVcs.java
@@ -91,7 +91,7 @@ import java.util.concurrent.locks.ReentrantReadWriteLock;
*/
public class GitVcs extends AbstractVcs {
public static final NotificationGroup NOTIFICATION_GROUP_ID = NotificationGroup.toolWindowGroup("Git Messages", ChangesViewContentManager.TOOLWINDOW_ID, true);
- public static final NotificationGroup IMPORTANT_ERROR_NOTIFICATION = new NotificationGroup("Git Important Errors", NotificationDisplayType.STICKY_BALLOON, true);
+ public static final NotificationGroup IMPORTANT_ERROR_NOTIFICATION = new NotificationGroup("Git Important Messages", NotificationDisplayType.STICKY_BALLOON, true);
static {
NotificationsConfigurationImpl.remove("Git");
From b83ebb1f9570dd74cc3b74112de8db0b48bc1823 Mon Sep 17 00:00:00 2001
From: Kirill Likhodedov
Date: Tue, 7 Feb 2012 15:58:33 +0400
Subject: [PATCH 07/16] GitCheckoutOperation: simplify get remaining repos
except the given one
---
.../git4idea/src/git4idea/branch/GitBranchOperation.java | 7 +++++++
.../src/git4idea/branch/GitCheckoutOperation.java | 9 +--------
2 files changed, 8 insertions(+), 8 deletions(-)
diff --git a/plugins/git4idea/src/git4idea/branch/GitBranchOperation.java b/plugins/git4idea/src/git4idea/branch/GitBranchOperation.java
index 8c5ece93054c..d6218c96c3d9 100644
--- a/plugins/git4idea/src/git4idea/branch/GitBranchOperation.java
+++ b/plugins/git4idea/src/git4idea/branch/GitBranchOperation.java
@@ -143,6 +143,13 @@ abstract class GitBranchOperation {
return myRemainingRepositories;
}
+ @NotNull
+ protected List getRemainingRepositoriesExceptGiven(@NotNull final GitRepository currentRepository) {
+ List repositories = new ArrayList(myRemainingRepositories);
+ repositories.remove(currentRepository);
+ return repositories;
+ }
+
protected void notifySuccess() {
NotificationManager.getInstance(myProject).notify(GitVcs.NOTIFICATION_GROUP_ID, "", getSuccessMessage(), NotificationType.INFORMATION);
}
diff --git a/plugins/git4idea/src/git4idea/branch/GitCheckoutOperation.java b/plugins/git4idea/src/git4idea/branch/GitCheckoutOperation.java
index 8b14bdab75bd..9c1843f529fb 100644
--- a/plugins/git4idea/src/git4idea/branch/GitCheckoutOperation.java
+++ b/plugins/git4idea/src/git4idea/branch/GitCheckoutOperation.java
@@ -20,7 +20,6 @@ import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.progress.ProgressIndicator;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.Clock;
-import com.intellij.openapi.util.Condition;
import com.intellij.openapi.util.io.FileUtil;
import com.intellij.openapi.vcs.VcsException;
import com.intellij.openapi.vcs.changes.Change;
@@ -29,7 +28,6 @@ import com.intellij.openapi.vcs.history.VcsRevisionNumber;
import com.intellij.openapi.vcs.merge.MergeDialogCustomizer;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.util.ArrayUtil;
-import com.intellij.util.containers.ContainerUtil;
import com.intellij.util.continuation.ContinuationContext;
import com.intellij.util.text.DateFormatUtil;
import com.intellij.vcsUtil.VcsUtil;
@@ -173,12 +171,7 @@ class GitCheckoutOperation extends GitBranchOperation {
@NotNull
private Map> collectLocalChangesOnAllOtherRepositories(@NotNull final GitRepository currentRepository) {
// get changes in all other repositories (except those which already have succeeded) to avoid multiple dialogs proposing smart checkout
- List remainingRepositories = ContainerUtil.filter(getRepositories(), new Condition() {
- @Override
- public boolean value(GitRepository repo) {
- return !repo.equals(currentRepository) && !getSuccessfulRepositories().contains(repo);
- }
- });
+ List remainingRepositories = getRemainingRepositoriesExceptGiven(currentRepository);
return collectChangesConflictingWithCheckout(remainingRepositories);
}
From 6c33525e2b01c93c41494741e4d3c54c8933fe32 Mon Sep 17 00:00:00 2001
From: Kirill Likhodedov
Date: Tue, 7 Feb 2012 17:24:26 +0400
Subject: [PATCH 08/16] GitFreezingProcess as a simple more linear alternative
to GitComplexProcess
---
.../git4idea/update/GitComplexProcess.java | 15 +--
.../src/git4idea/util/GitFreezingProcess.java | 118 ++++++++++++++++++
2 files changed, 121 insertions(+), 12 deletions(-)
create mode 100644 plugins/git4idea/src/git4idea/util/GitFreezingProcess.java
diff --git a/plugins/git4idea/src/git4idea/update/GitComplexProcess.java b/plugins/git4idea/src/git4idea/update/GitComplexProcess.java
index 612133003765..607f6a422370 100644
--- a/plugins/git4idea/src/git4idea/update/GitComplexProcess.java
+++ b/plugins/git4idea/src/git4idea/update/GitComplexProcess.java
@@ -15,14 +15,12 @@
*/
package git4idea.update;
-import com.intellij.ide.SaveAndSyncHandler;
-import com.intellij.openapi.fileEditor.FileDocumentManager;
import com.intellij.openapi.project.Project;
-import com.intellij.openapi.project.ex.ProjectManagerEx;
import com.intellij.openapi.vcs.changes.ChangeListManager;
import com.intellij.util.continuation.*;
import git4idea.repo.GitRepository;
import git4idea.repo.GitRepositoryManager;
+import git4idea.util.GitFreezingProcess;
/**
* This class executes a Git task, surrounding it with a couple of preparation and completion tasks, such as:
@@ -50,16 +48,12 @@ public class GitComplexProcess {
private final Operation myOperation;
private final String myFreezeReason;
- private final ProjectManagerEx myProjectManager;
private final GitRepositoryManager myRepositoryManager;
private final ChangeListManager myChangeListManager;
private final TaskDescriptor BLOCK = new TaskDescriptor("", Where.AWT) {
@Override public void run(ContinuationContext context) {
- myProjectManager.blockReloadingProjectOnExternalChanges();
- FileDocumentManager.getInstance().saveAllDocuments();
- SaveAndSyncHandler.getInstance().blockSaveOnFrameDeactivation();
- SaveAndSyncHandler.getInstance().blockSyncOnFrameActivation();
+ GitFreezingProcess.saveAndBlock();
}
};
@@ -77,9 +71,7 @@ public class GitComplexProcess {
private final TaskDescriptor UNBLOCK = new TaskDescriptor("", Where.AWT) {
@Override public void run(ContinuationContext context) {
- myProjectManager.unblockReloadingProjectOnExternalChanges();
- SaveAndSyncHandler.getInstance().unblockSaveOnFrameDeactivation();
- SaveAndSyncHandler.getInstance().unblockSyncOnFrameActivation();
+ GitFreezingProcess.unblock();
}
@Override public boolean isHaveMagicCure() {
@@ -99,7 +91,6 @@ public class GitComplexProcess {
myOperation = operation;
myFreezeReason = "Local changes are not available until Git " + myTitle + " is finished.";
- myProjectManager = ProjectManagerEx.getInstanceEx();
myRepositoryManager = GitRepositoryManager.getInstance(project);
myChangeListManager = ChangeListManager.getInstance(myProject);
diff --git a/plugins/git4idea/src/git4idea/util/GitFreezingProcess.java b/plugins/git4idea/src/git4idea/util/GitFreezingProcess.java
new file mode 100644
index 000000000000..1d3ee9f838a2
--- /dev/null
+++ b/plugins/git4idea/src/git4idea/util/GitFreezingProcess.java
@@ -0,0 +1,118 @@
+/*
+ * Copyright 2000-2012 JetBrains s.r.o.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package git4idea.util;
+
+import com.intellij.ide.SaveAndSyncHandler;
+import com.intellij.openapi.fileEditor.FileDocumentManager;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.project.ex.ProjectManagerEx;
+import com.intellij.openapi.vcs.changes.ChangeListManager;
+import com.intellij.util.continuation.GatheringContinuationContext;
+import com.intellij.util.ui.UIUtil;
+import org.jetbrains.annotations.NotNull;
+
+import java.util.concurrent.atomic.AtomicReference;
+
+/**
+ * Executes an action surrounding it with freezing-unfreezing operations.
+ * It is a simple linear alternative to {@link git4idea.update.GitComplexProcess} performing tasks in a single method instead of
+ * using continuations logic.
+ *
+ * @author Kirill Likhodedov
+ */
+public class GitFreezingProcess {
+
+ @NotNull private final String myOperationTitle;
+ @NotNull private final Runnable myRunnable;
+ @NotNull private final ChangeListManager myChangeListManager;
+
+ public GitFreezingProcess(@NotNull Project project, @NotNull String operationTitle, @NotNull Runnable runnable) {
+ myOperationTitle = operationTitle;
+ myRunnable = runnable;
+ myChangeListManager = ChangeListManager.getInstance(project);
+ }
+
+ public void execute() {
+ try {
+ saveAndBlockInAwt();
+ freeze();
+ try {
+ myRunnable.run();
+ }
+ finally {
+ unfreeze();
+ }
+ }
+ finally {
+ unblockInAwt();
+ }
+ }
+
+ public static void saveAndBlock() {
+ ProjectManagerEx.getInstanceEx().blockReloadingProjectOnExternalChanges();
+ FileDocumentManager.getInstance().saveAllDocuments();
+ SaveAndSyncHandler.getInstance().blockSaveOnFrameDeactivation();
+ SaveAndSyncHandler.getInstance().blockSyncOnFrameActivation();
+ }
+
+ private static void saveAndBlockInAwt() {
+ final AtomicReference exception = new AtomicReference();
+ // if an error happens, let it be thrown there (in awt) + throw it here to call unblock if anything has already been blocked
+ UIUtil.invokeAndWaitIfNeeded(new Runnable() {
+ @Override
+ public void run() {
+ try {
+ saveAndBlock();
+ }
+ catch (Throwable t) {
+ RuntimeException re = new RuntimeException(t);
+ exception.set(re);
+ throw re;
+ }
+ }
+ });
+
+ RuntimeException re = exception.get();
+ if (re != null) {
+ throw re;
+ }
+ }
+
+ private static void unblockInAwt() {
+ UIUtil.invokeAndWaitIfNeeded(new Runnable() {
+ @Override
+ public void run() {
+ unblock();
+ }
+ });
+ }
+
+ public static void unblock() {
+ ProjectManagerEx.getInstanceEx().unblockReloadingProjectOnExternalChanges();
+ SaveAndSyncHandler.getInstance().unblockSaveOnFrameDeactivation();
+ SaveAndSyncHandler.getInstance().unblockSyncOnFrameActivation();
+ }
+
+ private void freeze() {
+ myChangeListManager.freeze(new GatheringContinuationContext(),
+ "Local changes are not available until Git " + myOperationTitle + " is finished.");
+ }
+
+ private void unfreeze() {
+ myChangeListManager.letGo();
+ }
+
+}
From a3046728933dfcb36ed9c34b775585f57ba9cdc1 Mon Sep 17 00:00:00 2001
From: Kirill Likhodedov
Date: Tue, 7 Feb 2012 19:24:55 +0400
Subject: [PATCH 09/16] GitPreservingProcess: stash -> perform operation ->
unstash
Move the stuff from GitCheckoutOperation there.
(Preparing to GitMergeOperation).
---
.../src/git4idea/NotificationManager.java | 4 +
.../git4idea/branch/GitCheckoutOperation.java | 80 +--------
.../git4idea/stash/GitStashChangesSaver.java | 21 ++-
.../git4idea/util/GitPreservingProcess.java | 161 ++++++++++++++++++
4 files changed, 184 insertions(+), 82 deletions(-)
create mode 100644 plugins/git4idea/src/git4idea/util/GitPreservingProcess.java
diff --git a/plugins/git4idea/src/git4idea/NotificationManager.java b/plugins/git4idea/src/git4idea/NotificationManager.java
index 93ad28e5b8cd..7977bca38c30 100644
--- a/plugins/git4idea/src/git4idea/NotificationManager.java
+++ b/plugins/git4idea/src/git4idea/NotificationManager.java
@@ -48,6 +48,10 @@ public class NotificationManager {
notify(notificationGroup, title, message, type, null);
}
+ public void notifyError(String title, String message) {
+ notify(GitVcs.IMPORTANT_ERROR_NOTIFICATION, title, message, NotificationType.ERROR, null);
+ }
+
protected static Notification createNotification(NotificationGroup notificationGroup, String title, String message,
NotificationType type, NotificationListener listener) {
return notificationGroup.createNotification(title, message, type, listener);
diff --git a/plugins/git4idea/src/git4idea/branch/GitCheckoutOperation.java b/plugins/git4idea/src/git4idea/branch/GitCheckoutOperation.java
index 9c1843f529fb..f1161b0e35c3 100644
--- a/plugins/git4idea/src/git4idea/branch/GitCheckoutOperation.java
+++ b/plugins/git4idea/src/git4idea/branch/GitCheckoutOperation.java
@@ -19,25 +19,18 @@ import com.intellij.notification.NotificationType;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.progress.ProgressIndicator;
import com.intellij.openapi.project.Project;
-import com.intellij.openapi.util.Clock;
import com.intellij.openapi.util.io.FileUtil;
import com.intellij.openapi.vcs.VcsException;
import com.intellij.openapi.vcs.changes.Change;
import com.intellij.openapi.vcs.changes.ChangeListManager;
-import com.intellij.openapi.vcs.history.VcsRevisionNumber;
-import com.intellij.openapi.vcs.merge.MergeDialogCustomizer;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.util.ArrayUtil;
-import com.intellij.util.continuation.ContinuationContext;
-import com.intellij.util.text.DateFormatUtil;
import com.intellij.vcsUtil.VcsUtil;
import git4idea.GitUtil;
import git4idea.GitVcs;
import git4idea.commands.*;
-import git4idea.merge.GitConflictResolver;
import git4idea.repo.GitRepository;
-import git4idea.stash.GitChangesSaver;
-import git4idea.update.GitComplexProcess;
+import git4idea.util.GitPreservingProcess;
import git4idea.util.GitUIUtil;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
@@ -45,7 +38,6 @@ import org.jetbrains.annotations.Nullable;
import java.util.*;
import java.util.concurrent.atomic.AtomicBoolean;
-import static com.intellij.openapi.util.text.StringUtil.join;
import static git4idea.commands.GitMessageWithFilesDetector.Event.LOCAL_CHANGES_OVERWRITTEN_BY_CHECKOUT;
import static git4idea.commands.GitMessageWithFilesDetector.Event.UNTRACKED_FILES_OVERWRITTEN_BY;
import static git4idea.util.GitUIUtil.code;
@@ -257,76 +249,18 @@ class GitCheckoutOperation extends GitBranchOperation {
// stash - checkout - unstash
private boolean smartCheckout(@NotNull final List repositories, @NotNull final String reference, @Nullable final String newBranch, @NotNull ProgressIndicator indicator) {
- final GitChangesSaver saver = configureSaver(reference, indicator);
final AtomicBoolean result = new AtomicBoolean();
- GitComplexProcess.Operation checkoutOperation = new GitComplexProcess.Operation() {
- @Override public void run(ContinuationContext context) {
- boolean savedSuccessfully = save(repositories, saver);
- if (savedSuccessfully) {
- try {
- result.set(checkoutOrNotify(repositories, reference, newBranch, false));
- } finally {
- saver.restoreLocalChanges(context);
- }
- }
+ GitPreservingProcess preservingProcess = new GitPreservingProcess(myProject, repositories, "checkout", reference, indicator, new Runnable() {
+ @Override
+ public void run() {
+ result.set(checkoutOrNotify(repositories, reference, newBranch, false));
}
- };
- GitComplexProcess.execute(myProject, "checkout", checkoutOperation);
+ });
+ preservingProcess.execute();
return result.get();
}
- /**
- * Configures the saver, actually notifications and texts in the GitConflictResolver used inside.
- */
- private GitChangesSaver configureSaver(final String reference, ProgressIndicator indicator) {
- GitChangesSaver saver = GitChangesSaver.getSaver(myProject, indicator, String.format("Checkout %s at %s",
- reference,
- DateFormatUtil.formatDateTime(Clock.getTime())));
- MergeDialogCustomizer mergeDialogCustomizer = new MergeDialogCustomizer() {
- @Override
- public String getMultipleFileMergeDescription(Collection files) {
- return String.format(
- "Uncommitted changes that were saved before checkout have conflicts with files from %s",
- reference);
- }
-
- @Override
- public String getLeftPanelTitle(VirtualFile file) {
- return "Uncommitted changes";
- }
-
- @Override
- public String getRightPanelTitle(VirtualFile file, VcsRevisionNumber lastRevisionNumber) {
- return String.format("Changes from %s", reference);
- }
- };
-
- GitConflictResolver.Params params = new GitConflictResolver.Params().
- setReverse(true).
- setMergeDialogCustomizer(mergeDialogCustomizer).
- setErrorNotificationTitle("Local changes were not restored");
-
- saver.setConflictResolverParams(params);
- return saver;
- }
-
- /**
- * Saves local changes. In case of error shows a notification and returns false.
- */
- private boolean save(@NotNull Collection repositories, @NotNull GitChangesSaver saver) {
- try {
- saver.saveLocalChanges(GitUtil.getRoots(repositories));
- return true;
- } catch (VcsException e) {
- LOG.info("Couldn't save local changes", e);
- notifyError("Couldn't save uncommitted changes.",
- String.format("Tried to save uncommitted changes in %s before checkout, but failed with an error.
%s",
- saver.getSaverName(), join(e.getMessages())));
- return false;
- }
- }
-
/**
* Checks out or shows an error message.
*/
diff --git a/plugins/git4idea/src/git4idea/stash/GitStashChangesSaver.java b/plugins/git4idea/src/git4idea/stash/GitStashChangesSaver.java
index b369b4455717..9ce2fe859bde 100644
--- a/plugins/git4idea/src/git4idea/stash/GitStashChangesSaver.java
+++ b/plugins/git4idea/src/git4idea/stash/GitStashChangesSaver.java
@@ -67,21 +67,24 @@ public class GitStashChangesSaver extends GitChangesSaver {
}
@Override
- protected void load(ContinuationContext context) {
+ protected void load(@NotNull ContinuationContext context) {
+ try {
+ load();
+ }
+ catch (VcsException e) {
+ context.handleException(e);
+ }
+ }
+
+ public void load() throws VcsException {
for (VirtualFile root : myStashedRoots) {
- try {
- loadRoot(root);
- }
- catch (VcsException e) {
- context.handleException(e);
- return;
- }
+ loadRoot(root);
}
// we'll refresh more but this way we needn't compute what files under roots etc
LocalFileSystem.getInstance().refreshIoFiles(myChangeManager.getAffectedPaths());
}
- @Override
+ @Override
protected boolean wereChangesSaved() {
return !myStashedRoots.isEmpty();
}
diff --git a/plugins/git4idea/src/git4idea/util/GitPreservingProcess.java b/plugins/git4idea/src/git4idea/util/GitPreservingProcess.java
new file mode 100644
index 000000000000..8a4acd1832d1
--- /dev/null
+++ b/plugins/git4idea/src/git4idea/util/GitPreservingProcess.java
@@ -0,0 +1,161 @@
+/*
+ * Copyright 2000-2012 JetBrains s.r.o.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package git4idea.util;
+
+import com.intellij.openapi.diagnostic.Logger;
+import com.intellij.openapi.progress.ProgressIndicator;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.util.Clock;
+import com.intellij.openapi.util.Computable;
+import com.intellij.openapi.util.text.StringUtil;
+import com.intellij.openapi.vcs.VcsException;
+import com.intellij.openapi.vcs.history.VcsRevisionNumber;
+import com.intellij.openapi.vcs.merge.MergeDialogCustomizer;
+import com.intellij.openapi.vfs.VirtualFile;
+import com.intellij.util.text.DateFormatUtil;
+import git4idea.GitUtil;
+import git4idea.NotificationManager;
+import git4idea.merge.GitConflictResolver;
+import git4idea.repo.GitRepository;
+import git4idea.stash.GitStashChangesSaver;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+import java.util.Collection;
+
+import static com.intellij.openapi.util.text.StringUtil.join;
+
+/**
+ * Executes a Git operation on a number of repositories surrounding it by stash-unstash procedure.
+ * I.e. stashes changes, executes the operation and then unstashes it.
+ *
+ * @author Kirill Likhodedov
+ */
+public class GitPreservingProcess {
+
+ private static final Logger LOG = Logger.getInstance(GitPreservingProcess.class);
+
+ @NotNull private final Project myProject;
+ @NotNull private final Collection myRepositories;
+ @NotNull private final String myOperationTitle;
+ @NotNull private final String myDestinationName;
+ @NotNull private final ProgressIndicator myProgressIndicator;
+ @NotNull private final Runnable myOperation;
+ @NotNull private final String myStashMessage;
+
+ private GitStashChangesSaver mySaver;
+
+ public GitPreservingProcess(@NotNull Project project, @NotNull Collection repositories,
+ @NotNull String operationTitle, @NotNull String destinationName,
+ @NotNull ProgressIndicator indicator, @NotNull Runnable operation) {
+ myProject = project;
+ myRepositories = repositories;
+ myOperationTitle = operationTitle;
+ myDestinationName = destinationName;
+ myProgressIndicator = indicator;
+ myOperation = operation;
+ myStashMessage = String.format("%s %s at %s", StringUtil.capitalize(myOperationTitle), myDestinationName,
+ DateFormatUtil.formatDateTime(Clock.getTime()));
+ }
+
+ public void execute() {
+ execute(null);
+ }
+
+ public void execute(@Nullable final Computable autoLoadDecision) {
+ Runnable operation = new Runnable() {
+ @Override
+ public void run() {
+ mySaver = configureSaver();
+ boolean savedSuccessfully = save();
+ if (savedSuccessfully) {
+ try {
+ myOperation.run();
+ }
+ finally {
+ if (autoLoadDecision == null || autoLoadDecision.compute()) {
+ load();
+ }
+ }
+ }
+ }
+ };
+
+ new GitFreezingProcess(myProject, myOperationTitle, operation).execute();
+ }
+
+ /**
+ * Configures the saver, actually notifications and texts in the GitConflictResolver used inside.
+ */
+ private GitStashChangesSaver configureSaver() {
+ GitStashChangesSaver saver = new GitStashChangesSaver(myProject, myProgressIndicator, myStashMessage);
+ MergeDialogCustomizer mergeDialogCustomizer = new MergeDialogCustomizer() {
+ @Override
+ public String getMultipleFileMergeDescription(Collection files) {
+ return String.format(
+ "Uncommitted changes that were saved before %s have conflicts with files from %s",
+ myOperationTitle, myDestinationName);
+ }
+
+ @Override
+ public String getLeftPanelTitle(VirtualFile file) {
+ return "Uncommitted changes from stash";
+ }
+
+ @Override
+ public String getRightPanelTitle(VirtualFile file, VcsRevisionNumber lastRevisionNumber) {
+ return String.format("Changes from %s", myDestinationName);
+ }
+ };
+
+ GitConflictResolver.Params params = new GitConflictResolver.Params().
+ setReverse(true).
+ setMergeDialogCustomizer(mergeDialogCustomizer).
+ setErrorNotificationTitle("Local changes were not restored");
+
+ saver.setConflictResolverParams(params);
+ return saver;
+ }
+
+ /**
+ * Saves local changes. In case of error shows a notification and returns false.
+ */
+ private boolean save() {
+ try {
+ mySaver.saveLocalChanges(GitUtil.getRoots(myRepositories));
+ return true;
+ } catch (VcsException e) {
+ LOG.info("Couldn't save local changes", e);
+ NotificationManager.getInstance(myProject).notifyError(
+ "Couldn't save uncommitted changes.",
+ String.format("Tried to save uncommitted changes in stash before %s, but failed with an error.
%s",
+ myOperationTitle, join(e.getMessages())));
+ return false;
+ }
+ }
+
+ private void load() {
+ try {
+ mySaver.load();
+ }
+ catch (VcsException e) {
+ LOG.info("Couldn't load local changes", e);
+ NotificationManager.getInstance(myProject).notifyError("Couldn't restore uncommitted changes",
+ String.format("Tried to unstash uncommitted changes, but failed with error.
%s",join(e.getMessages())));
+ }
+ }
+
+}
From 8126f0ac1866cb762e444c7d89a617d8bfcdd2a4 Mon Sep 17 00:00:00 2001
From: Kirill Likhodedov
Date: Tue, 7 Feb 2012 19:45:26 +0400
Subject: [PATCH 10/16] Extract some methods from GitCheckoutOperation to make
them available for merge.
---
plugins/git4idea/src/git4idea/GitUtil.java | 33 +++++++++++
.../git4idea/branch/GitBranchOperation.java | 37 +++++++++++-
.../git4idea/branch/GitCheckoutOperation.java | 58 +------------------
3 files changed, 69 insertions(+), 59 deletions(-)
diff --git a/plugins/git4idea/src/git4idea/GitUtil.java b/plugins/git4idea/src/git4idea/GitUtil.java
index 6fa5f09f2b45..c49f36644ff4 100644
--- a/plugins/git4idea/src/git4idea/GitUtil.java
+++ b/plugins/git4idea/src/git4idea/GitUtil.java
@@ -18,10 +18,13 @@ package git4idea;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.Pair;
+import com.intellij.openapi.util.io.FileUtil;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.openapi.vcs.FilePath;
import com.intellij.openapi.vcs.ProjectLevelVcsManager;
import com.intellij.openapi.vcs.VcsException;
+import com.intellij.openapi.vcs.changes.Change;
+import com.intellij.openapi.vcs.changes.ChangeListManager;
import com.intellij.openapi.vcs.changes.FilePathsHelper;
import com.intellij.openapi.vcs.vfs.AbstractVcsVirtualFile;
import com.intellij.openapi.vfs.LocalFileSystem;
@@ -673,4 +676,34 @@ public class GitUtil {
}
return remoteChanges;
}
+
+ /**
+ * Given the list of paths converts them to the list of {@link Change Changes} found in the {@link ChangeListManager},
+ * i.e. this works only for local changes.
+ * Paths can be absolute or relative to the repository.
+ * If a path is not in the local changes, it is ignored.
+ */
+ @NotNull
+ public static List convertPathsToChanges(@NotNull GitRepository repository,
+ @NotNull Collection affectedPaths, boolean relativePaths) {
+ ChangeListManager changeListManager = ChangeListManager.getInstance(repository.getProject());
+ List affectedChanges = new ArrayList();
+ for (String path : affectedPaths) {
+ VirtualFile file;
+ if (relativePaths) {
+ file = repository.getRoot().findFileByRelativePath(FileUtil.toSystemIndependentName(path));
+ }
+ else {
+ file = VcsUtil.getVirtualFile(path);
+ }
+
+ if (file != null) {
+ Change change = changeListManager.getChange(file);
+ if (change != null) {
+ affectedChanges.add(change);
+ }
+ }
+ }
+ return affectedChanges;
+ }
}
diff --git a/plugins/git4idea/src/git4idea/branch/GitBranchOperation.java b/plugins/git4idea/src/git4idea/branch/GitBranchOperation.java
index d6218c96c3d9..c311c30dd5fb 100644
--- a/plugins/git4idea/src/git4idea/branch/GitBranchOperation.java
+++ b/plugins/git4idea/src/git4idea/branch/GitBranchOperation.java
@@ -18,11 +18,14 @@ package git4idea.branch;
import com.intellij.notification.Notification;
import com.intellij.notification.NotificationListener;
import com.intellij.notification.NotificationType;
+import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.progress.ProgressIndicator;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.ui.Messages;
import com.intellij.openapi.ui.VerticalFlowLayout;
import com.intellij.openapi.util.text.StringUtil;
+import com.intellij.openapi.vcs.VcsException;
+import com.intellij.openapi.vcs.changes.Change;
import com.intellij.openapi.vcs.changes.ui.SelectFilesDialog;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.ui.components.JBLabel;
@@ -36,9 +39,7 @@ import org.jetbrains.annotations.NotNull;
import javax.swing.*;
import javax.swing.event.HyperlinkEvent;
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.List;
+import java.util.*;
import java.util.concurrent.atomic.AtomicBoolean;
import static com.intellij.openapi.util.text.StringUtil.pluralize;
@@ -52,6 +53,8 @@ import static com.intellij.openapi.util.text.StringUtil.stripHtml;
*/
abstract class GitBranchOperation {
+ private static final Logger LOG = Logger.getInstance(GitBranchOperation.class);
+
@NotNull protected final Project myProject;
@NotNull private final Collection myRepositories;
@NotNull private final ProgressIndicator myIndicator;
@@ -312,6 +315,34 @@ abstract class GitBranchOperation {
}
}
+ /**
+ * 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
+ static Map> collectLocalChangesConflictingWithBranch(@NotNull Project project,
+ @NotNull Collection repositories,
+ @NotNull String currentBranch,
+ @NotNull String otherBranch) {
+ Map> changes = new HashMap>();
+ for (GitRepository repository : repositories) {
+ try {
+ Collection diff = GitUtil.getPathsDiffBetweenRefs(currentBranch, otherBranch, project, repository.getRoot());
+ List changesInRepo = GitUtil.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;
+ }
+
private class UntrackedFilesDialog extends SelectFilesDialog {
public UntrackedFilesDialog(@NotNull Project project, @NotNull List originalFiles, @NotNull String prompt) {
diff --git a/plugins/git4idea/src/git4idea/branch/GitCheckoutOperation.java b/plugins/git4idea/src/git4idea/branch/GitCheckoutOperation.java
index f1161b0e35c3..ca1974c9c31f 100644
--- a/plugins/git4idea/src/git4idea/branch/GitCheckoutOperation.java
+++ b/plugins/git4idea/src/git4idea/branch/GitCheckoutOperation.java
@@ -19,13 +19,9 @@ import com.intellij.notification.NotificationType;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.progress.ProgressIndicator;
import com.intellij.openapi.project.Project;
-import com.intellij.openapi.util.io.FileUtil;
-import com.intellij.openapi.vcs.VcsException;
import com.intellij.openapi.vcs.changes.Change;
-import com.intellij.openapi.vcs.changes.ChangeListManager;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.util.ArrayUtil;
-import com.intellij.vcsUtil.VcsUtil;
import git4idea.GitUtil;
import git4idea.GitVcs;
import git4idea.commands.*;
@@ -113,7 +109,7 @@ class GitCheckoutOperation extends GitBranchOperation {
private boolean smartCheckoutOrNotify(@NotNull GitRepository repository,
@NotNull GitMessageWithFilesDetector localChangesOverwrittenByCheckout) {
// get changes overwritten by checkout from the error message captured from Git
- List affectedChanges = getChangesAffectedByCheckout(repository, localChangesOverwrittenByCheckout.getRelativeFilePaths(), true);
+ List affectedChanges = GitUtil.convertPathsToChanges(repository, localChangesOverwrittenByCheckout.getRelativeFilePaths(), true);
// get all other conflicting changes
Map> conflictingChangesInRepositories = collectLocalChangesOnAllOtherRepositories(repository);
Set otherProblematicRepositories = conflictingChangesInRepositories.keySet();
@@ -164,7 +160,7 @@ class GitCheckoutOperation extends GitBranchOperation {
private Map> collectLocalChangesOnAllOtherRepositories(@NotNull final GitRepository currentRepository) {
// get changes in all other repositories (except those which already have succeeded) to avoid multiple dialogs proposing smart checkout
List remainingRepositories = getRemainingRepositoriesExceptGiven(currentRepository);
- return collectChangesConflictingWithCheckout(remainingRepositories);
+ return collectLocalChangesConflictingWithBranch(myProject, remainingRepositories, myPreviousBranch, myStartPointReference);
}
@NotNull
@@ -218,26 +214,6 @@ class GitCheckoutOperation extends GitBranchOperation {
return "Couldn't checkout " + myStartPointReference;
}
- @NotNull
- private Map> collectChangesConflictingWithCheckout(@NotNull Collection repositories) {
- Map> changes = new HashMap>();
- for (GitRepository repository : repositories) {
- try {
- Collection diff = GitUtil.getPathsDiffBetweenRefs(myPreviousBranch, myStartPointReference, myProject, repository.getRoot());
- List changesInRepo = getChangesAffectedByCheckout(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", myPreviousBranch, myStartPointReference, repository.getRoot()), e);
- }
- }
- return changes;
- }
-
@NotNull
@Override
public String getSuccessMessage() {
@@ -277,36 +253,6 @@ class GitCheckoutOperation extends GitBranchOperation {
return false;
}
- /**
- * Forms the list of the changes, that would be overwritten by checkout.
- *
- * @param repository
- * @param affectedPaths paths returned by Git.
- * @param relativePaths Are the paths specified relative or absolute.
- * @return List of Changes is these paths.
- */
- private List getChangesAffectedByCheckout(@NotNull GitRepository repository, @NotNull Collection affectedPaths, boolean relativePaths) {
- ChangeListManager changeListManager = ChangeListManager.getInstance(myProject);
- List affectedChanges = new ArrayList();
- for (String path : affectedPaths) {
- VirtualFile file;
- if (relativePaths) {
- file = repository.getRoot().findFileByRelativePath(FileUtil.toSystemIndependentName(path));
- }
- else {
- file = VcsUtil.getVirtualFile(path);
- }
-
- if (file != null) {
- Change change = changeListManager.getChange(file);
- if (change != null) {
- affectedChanges.add(change);
- }
- }
- }
- return affectedChanges;
- }
-
private static void refresh(GitRepository... repositories) {
for (GitRepository repository : repositories) {
refreshRoot(repository);
From b6b074e3a25d24323b2baafc0a20dddadb3041b9 Mon Sep 17 00:00:00 2001
From: Kirill Likhodedov
Date: Tue, 7 Feb 2012 20:22:37 +0400
Subject: [PATCH 11/16] Inline
---
.../src/git4idea/branch/GitCheckoutOperation.java | 12 ++++--------
1 file changed, 4 insertions(+), 8 deletions(-)
diff --git a/plugins/git4idea/src/git4idea/branch/GitCheckoutOperation.java b/plugins/git4idea/src/git4idea/branch/GitCheckoutOperation.java
index ca1974c9c31f..64077d856333 100644
--- a/plugins/git4idea/src/git4idea/branch/GitCheckoutOperation.java
+++ b/plugins/git4idea/src/git4idea/branch/GitCheckoutOperation.java
@@ -111,7 +111,10 @@ class GitCheckoutOperation extends GitBranchOperation {
// get changes overwritten by checkout from the error message captured from Git
List affectedChanges = GitUtil.convertPathsToChanges(repository, localChangesOverwrittenByCheckout.getRelativeFilePaths(), true);
// get all other conflicting changes
- Map> conflictingChangesInRepositories = collectLocalChangesOnAllOtherRepositories(repository);
+ // get changes in all other repositories (except those which already have succeeded) to avoid multiple dialogs proposing smart checkout
+ Map> conflictingChangesInRepositories =
+ collectLocalChangesConflictingWithBranch(myProject, getRemainingRepositoriesExceptGiven(repository), myPreviousBranch, myStartPointReference);
+
Set otherProblematicRepositories = conflictingChangesInRepositories.keySet();
List allConflictingRepositories = new ArrayList(otherProblematicRepositories);
allConflictingRepositories.add(repository);
@@ -156,13 +159,6 @@ class GitCheckoutOperation extends GitBranchOperation {
}
}
- @NotNull
- private Map> collectLocalChangesOnAllOtherRepositories(@NotNull final GitRepository currentRepository) {
- // get changes in all other repositories (except those which already have succeeded) to avoid multiple dialogs proposing smart checkout
- List remainingRepositories = getRemainingRepositoriesExceptGiven(currentRepository);
- return collectLocalChangesConflictingWithBranch(myProject, remainingRepositories, myPreviousBranch, myStartPointReference);
- }
-
@NotNull
@Override
protected String getRollbackProposal() {
From ff2905d592bda972df7741d54c07e14b9c9fbfa5 Mon Sep 17 00:00:00 2001
From: Kirill Likhodedov
Date: Wed, 8 Feb 2012 17:11:55 +0400
Subject: [PATCH 12/16] GitMergeOperation
Add "Merge" operation to the GitBranchPopup.
Extract more methods on "local changes overwritten by" error from GitCheckoutOperation, and reuse them in GitMergeOperation.
Add some tests on GitMergeOperation; DRY GitBranchOperationsTest a bit.
Rename GitWouldBeOverwrittenByCheckoutDialog to GitSmartOperationDialog. Make "force" button optional - it has no sense for merge.
Extract GitMergeCommittingConflictResolver from GitUpdateProcess to reuse in GitMergeOperation. Refresh the root after commit if needed (to understand that the merge has been committed; this is not needed in case of update, because the ChangeListManager is frozen and then refreshed).
---
.../git4idea/branch/GitBranchOperation.java | 52 ++-
.../branch/GitBranchOperationsProcessor.java | 18 ++
.../branch/GitCheckoutNewBranchOperation.java | 2 +-
.../git4idea/branch/GitCheckoutOperation.java | 54 +---
.../git4idea/branch/GitMergeOperation.java | 306 ++++++++++++++++++
...alog.java => GitSmartOperationDialog.java} | 44 ++-
.../git4idea/src/git4idea/commands/Git.java | 20 ++
.../commands/GitMessageWithFilesDetector.java | 3 +
.../commands/GitSimpleEventDetector.java | 7 +-
.../git4idea/merge/GitConflictResolver.java | 6 +-
.../GitMergeCommittingConflictResolver.java | 51 +++
.../ui/branch/GitBranchPopupActions.java | 32 +-
.../src/git4idea/update/GitUpdateProcess.java | 23 +-
.../git4idea/util/GitPreservingProcess.java | 2 +-
.../branch/GitBranchOperationsTest.java | 181 ++++++++---
.../git4idea/tests/git4idea/test/GitExec.java | 4 +
.../test/GitTestScenarioGenerator.java | 14 +-
17 files changed, 679 insertions(+), 140 deletions(-)
create mode 100644 plugins/git4idea/src/git4idea/branch/GitMergeOperation.java
rename plugins/git4idea/src/git4idea/branch/{GitWouldBeOverwrittenByCheckoutDialog.java => GitSmartOperationDialog.java} (66%)
create mode 100644 plugins/git4idea/src/git4idea/merge/GitMergeCommittingConflictResolver.java
diff --git a/plugins/git4idea/src/git4idea/branch/GitBranchOperation.java b/plugins/git4idea/src/git4idea/branch/GitBranchOperation.java
index c311c30dd5fb..d9676498e5e1 100644
--- a/plugins/git4idea/src/git4idea/branch/GitBranchOperation.java
+++ b/plugins/git4idea/src/git4idea/branch/GitBranchOperation.java
@@ -23,6 +23,7 @@ import com.intellij.openapi.progress.ProgressIndicator;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.ui.Messages;
import com.intellij.openapi.ui.VerticalFlowLayout;
+import com.intellij.openapi.util.Pair;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.openapi.vcs.VcsException;
import com.intellij.openapi.vcs.changes.Change;
@@ -32,6 +33,7 @@ import com.intellij.ui.components.JBLabel;
import com.intellij.util.Function;
import com.intellij.util.ui.UIUtil;
import git4idea.*;
+import git4idea.commands.GitMessageWithFilesDetector;
import git4idea.merge.GitConflictResolver;
import git4idea.repo.GitRepository;
import git4idea.util.UntrackedFilesNotifier;
@@ -153,8 +155,12 @@ abstract class GitBranchOperation {
return repositories;
}
+ protected void notifySuccess(@NotNull String message) {
+ NotificationManager.getInstance(myProject).notify(GitVcs.NOTIFICATION_GROUP_ID, "", message, NotificationType.INFORMATION);
+ }
+
protected void notifySuccess() {
- NotificationManager.getInstance(myProject).notify(GitVcs.NOTIFICATION_GROUP_ID, "", getSuccessMessage(), NotificationType.INFORMATION);
+ notifySuccess(getSuccessMessage());
}
/**
@@ -277,6 +283,17 @@ abstract class GitBranchOperation {
repository.getRoot().refresh(true, true);
}
+ protected void fatalLocalChangesError(@NotNull String reference) {
+ String title = String.format("Couldn't %s %s", getOperationName(), reference);
+ String message = String.format("Local changes would be overwritten by %s.
You should stash or commit them.
", getOperationName());
+ if (wereSuccessful()) {
+ showFatalErrorDialogWithRollback(title, message);
+ }
+ else {
+ showFatalNotification(title, message);
+ }
+ }
+
/**
* Shows the error "The following untracked working tree files would be overwritten by checkout/merge".
* If there were no repositories that succeeded the operation, shows a notification with a link to the list of these untracked files.
@@ -361,4 +378,37 @@ abstract class GitBranchOperation {
}
}
+ /**
+ * When checkout or merge operation on a repository fails with the error "local changes would be overwritten by...",
+ * affected local files are captured by the {@link git4idea.commands.GitMessageWithFilesDetector detector}.
+ * Then all remaining (non successful repositories) are searched if they are about to fail with the same problem.
+ * All collected local changes which prevent the operation, together with these repositories, are returned.
+ * @param currentRepository The first repository which failed the operation.
+ * @param localChangesOverwrittenBy The detector of local changes would be overwritten by merge/checkout.
+ * @param currentBranch Current branch.
+ * @param nextBranch Branch to compare with (the branch to be checked out, or the branch to be merged).
+ * @return Repositories that have failed or would fail with the "local changes" error, together with these local changes.
+ */
+ @NotNull
+ protected Pair, List> getConflictingRepositoriesAndAffectedChanges(
+ @NotNull GitRepository currentRepository, @NotNull GitMessageWithFilesDetector localChangesOverwrittenBy,
+ String currentBranch, String nextBranch) {
+
+ // get changes overwritten by checkout from the error message captured from Git
+ List affectedChanges = GitUtil.convertPathsToChanges(currentRepository, localChangesOverwrittenBy.getRelativeFilePaths(), true);
+ // get all other conflicting changes
+ // get changes in all other repositories (except those which already have succeeded) to avoid multiple dialogs proposing smart checkout
+ Map> conflictingChangesInRepositories =
+ collectLocalChangesConflictingWithBranch(myProject, getRemainingRepositoriesExceptGiven(currentRepository), currentBranch, nextBranch);
+
+ Set otherProblematicRepositories = conflictingChangesInRepositories.keySet();
+ List allConflictingRepositories = new ArrayList(otherProblematicRepositories);
+ allConflictingRepositories.add(currentRepository);
+ for (List changes : conflictingChangesInRepositories.values()) {
+ affectedChanges.addAll(changes);
+ }
+
+ return Pair.create(allConflictingRepositories, affectedChanges);
+ }
+
}
diff --git a/plugins/git4idea/src/git4idea/branch/GitBranchOperationsProcessor.java b/plugins/git4idea/src/git4idea/branch/GitBranchOperationsProcessor.java
index 271d18a93d68..e066aa825424 100644
--- a/plugins/git4idea/src/git4idea/branch/GitBranchOperationsProcessor.java
+++ b/plugins/git4idea/src/git4idea/branch/GitBranchOperationsProcessor.java
@@ -39,7 +39,9 @@ import org.jetbrains.annotations.Nullable;
import javax.swing.*;
import java.util.Collections;
+import java.util.HashMap;
import java.util.List;
+import java.util.Map;
/**
* Executor of Git branching operations.
@@ -232,6 +234,22 @@ public final class GitBranchOperationsProcessor {
}
}
+ public void merge(@NotNull final String branchName) {
+ new CommonBackgroundTask(myProject, "Merging " + branchName, myCallInAwtAfterExecution) {
+ @Override public void execute(@NotNull ProgressIndicator indicator) {
+ doMerge(branchName, indicator);
+ }
+ }.runInBackground();
+ }
+
+ private void doMerge(@NotNull String branchName, @NotNull ProgressIndicator indicator) {
+ Map revisions = new HashMap();
+ for (GitRepository repository : myRepositories) {
+ revisions.put(repository, repository.getCurrentRevision());
+ }
+ new GitMergeOperation(myProject, myRepositories, branchName, getCurrentBranch(), revisions, indicator).execute();
+ }
+
/**
* Executes common operations before/after executing the actual branch operation.
*/
diff --git a/plugins/git4idea/src/git4idea/branch/GitCheckoutNewBranchOperation.java b/plugins/git4idea/src/git4idea/branch/GitCheckoutNewBranchOperation.java
index e6e7fb39a1a3..27f135bac9a3 100644
--- a/plugins/git4idea/src/git4idea/branch/GitCheckoutNewBranchOperation.java
+++ b/plugins/git4idea/src/git4idea/branch/GitCheckoutNewBranchOperation.java
@@ -58,7 +58,7 @@ class GitCheckoutNewBranchOperation extends GitBranchOperation {
while (hasMoreRepositories() && !fatalErrorHappened) {
final GitRepository repository = next();
- GitSimpleEventDetector unmergedDetector = new GitSimpleEventDetector(GitSimpleEventDetector.Event.UNMERGED);
+ GitSimpleEventDetector unmergedDetector = new GitSimpleEventDetector(GitSimpleEventDetector.Event.UNMERGED_PREVENTING_CHECKOUT);
GitCommandResult result = Git.checkoutNewBranch(repository, myNewBranchName, unmergedDetector);
if (result.success()) {
diff --git a/plugins/git4idea/src/git4idea/branch/GitCheckoutOperation.java b/plugins/git4idea/src/git4idea/branch/GitCheckoutOperation.java
index 64077d856333..1340ba4e582f 100644
--- a/plugins/git4idea/src/git4idea/branch/GitCheckoutOperation.java
+++ b/plugins/git4idea/src/git4idea/branch/GitCheckoutOperation.java
@@ -19,10 +19,9 @@ import com.intellij.notification.NotificationType;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.progress.ProgressIndicator;
import com.intellij.openapi.project.Project;
+import com.intellij.openapi.util.Pair;
import com.intellij.openapi.vcs.changes.Change;
import com.intellij.openapi.vfs.VirtualFile;
-import com.intellij.util.ArrayUtil;
-import git4idea.GitUtil;
import git4idea.GitVcs;
import git4idea.commands.*;
import git4idea.repo.GitRepository;
@@ -31,7 +30,8 @@ import git4idea.util.GitUIUtil;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
-import java.util.*;
+import java.util.Collection;
+import java.util.List;
import java.util.concurrent.atomic.AtomicBoolean;
import static git4idea.commands.GitMessageWithFilesDetector.Event.LOCAL_CHANGES_OVERWRITTEN_BY_CHECKOUT;
@@ -72,7 +72,7 @@ class GitCheckoutOperation extends GitBranchOperation {
VirtualFile root = repository.getRoot();
GitMessageWithFilesDetector localChangesOverwrittenByCheckout = new GitMessageWithFilesDetector(LOCAL_CHANGES_OVERWRITTEN_BY_CHECKOUT, root);
- GitSimpleEventDetector unmergedFiles = new GitSimpleEventDetector(GitSimpleEventDetector.Event.UNMERGED);
+ GitSimpleEventDetector unmergedFiles = new GitSimpleEventDetector(GitSimpleEventDetector.Event.UNMERGED_PREVENTING_CHECKOUT);
GitMessageWithFilesDetector untrackedOverwrittenByCheckout = new GitMessageWithFilesDetector(UNTRACKED_FILES_OVERWRITTEN_BY, root);
GitCommandResult result = Git.checkout(repository, myStartPointReference, myNewBranch, false,
@@ -108,30 +108,19 @@ class GitCheckoutOperation extends GitBranchOperation {
private boolean smartCheckoutOrNotify(@NotNull GitRepository repository,
@NotNull GitMessageWithFilesDetector localChangesOverwrittenByCheckout) {
- // get changes overwritten by checkout from the error message captured from Git
- List affectedChanges = GitUtil.convertPathsToChanges(repository, localChangesOverwrittenByCheckout.getRelativeFilePaths(), true);
- // get all other conflicting changes
- // get changes in all other repositories (except those which already have succeeded) to avoid multiple dialogs proposing smart checkout
- Map> conflictingChangesInRepositories =
- collectLocalChangesConflictingWithBranch(myProject, getRemainingRepositoriesExceptGiven(repository), myPreviousBranch, myStartPointReference);
+ Pair, List> conflictingRepositoriesAndAffectedChanges =
+ getConflictingRepositoriesAndAffectedChanges(repository, localChangesOverwrittenByCheckout, myPreviousBranch, myStartPointReference);
+ List allConflictingRepositories = conflictingRepositoriesAndAffectedChanges.getFirst();
+ List affectedChanges = conflictingRepositoriesAndAffectedChanges.getSecond();
- Set otherProblematicRepositories = conflictingChangesInRepositories.keySet();
- List allConflictingRepositories = new ArrayList(otherProblematicRepositories);
- allConflictingRepositories.add(repository);
- for (List changes : conflictingChangesInRepositories.values()) {
- affectedChanges.addAll(changes);
- }
-
- int smartCheckoutDecision = GitWouldBeOverwrittenByCheckoutDialog.showAndGetAnswer(myProject, affectedChanges);
- if (smartCheckoutDecision == GitWouldBeOverwrittenByCheckoutDialog.SMART_CHECKOUT) {
+ int smartCheckoutDecision = GitSmartOperationDialog.showAndGetAnswer(myProject, affectedChanges, "checkout", true);
+ if (smartCheckoutDecision == GitSmartOperationDialog.SMART_EXIT_CODE) {
boolean smartCheckedOutSuccessfully = smartCheckout(allConflictingRepositories, myStartPointReference, myNewBranch, getIndicator());
if (smartCheckedOutSuccessfully) {
- GitRepository[] otherRepositories = ArrayUtil.toObjectArray(otherProblematicRepositories, GitRepository.class);
-
- markSuccessful(repository);
- markSuccessful(otherRepositories);
- refresh(repository);
- refresh(otherRepositories);
+ for (GitRepository conflictingRepository : allConflictingRepositories) {
+ markSuccessful(conflictingRepository);
+ refresh(conflictingRepository);
+ }
return true;
}
else {
@@ -139,26 +128,15 @@ class GitCheckoutOperation extends GitBranchOperation {
return false;
}
}
- else if (smartCheckoutDecision == GitWouldBeOverwrittenByCheckoutDialog.FORCE_CHECKOUT_EXIT_CODE) {
+ else if (smartCheckoutDecision == GitSmartOperationDialog.FORCE_EXIT_CODE) {
return checkoutOrNotify(allConflictingRepositories, myStartPointReference, myNewBranch, true);
}
else {
- fatalLocalChangesError();
+ fatalLocalChangesError(myStartPointReference);
return false;
}
}
- private void fatalLocalChangesError() {
- String title = "Couldn't checkout " + myStartPointReference;
- String message = "Local changes would be overwritten by checkout.
Stash or commit them before checking out a branch.
";
- if (wereSuccessful()) {
- showFatalErrorDialogWithRollback(title, message);
- }
- else {
- showFatalNotification(title, message);
- }
- }
-
@NotNull
@Override
protected String getRollbackProposal() {
diff --git a/plugins/git4idea/src/git4idea/branch/GitMergeOperation.java b/plugins/git4idea/src/git4idea/branch/GitMergeOperation.java
new file mode 100644
index 000000000000..b323bb2913e8
--- /dev/null
+++ b/plugins/git4idea/src/git4idea/branch/GitMergeOperation.java
@@ -0,0 +1,306 @@
+/*
+ * Copyright 2000-2012 JetBrains s.r.o.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package git4idea.branch;
+
+import com.intellij.notification.NotificationType;
+import com.intellij.openapi.diagnostic.Logger;
+import com.intellij.openapi.progress.ProgressIndicator;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.util.Computable;
+import com.intellij.openapi.util.Pair;
+import com.intellij.openapi.vcs.changes.Change;
+import com.intellij.openapi.vcs.changes.ChangeListManager;
+import com.intellij.openapi.vfs.VirtualFile;
+import git4idea.GitUtil;
+import git4idea.GitVcs;
+import git4idea.NotificationManager;
+import git4idea.commands.*;
+import git4idea.merge.GitConflictResolver;
+import git4idea.merge.GitMergeCommittingConflictResolver;
+import git4idea.merge.GitMerger;
+import git4idea.repo.GitRepository;
+import git4idea.util.GitPreservingProcess;
+import org.jetbrains.annotations.NotNull;
+
+import java.util.*;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+import static git4idea.commands.GitMessageWithFilesDetector.Event.LOCAL_CHANGES_OVERWRITTEN_BY_MERGE;
+import static git4idea.commands.GitMessageWithFilesDetector.Event.UNTRACKED_FILES_OVERWRITTEN_BY;
+
+/**
+ * @author Kirill Likhodedov
+ */
+class GitMergeOperation extends GitBranchOperation {
+
+ private static final Logger LOG = Logger.getInstance(GitMergeOperation.class);
+
+ @NotNull private final ChangeListManager myChangeListManager;
+ @NotNull private final String myBranchToMerge;
+ @NotNull private final String myCurrentBranch;
+ @NotNull private final Map myCurrentRevisionsBeforeMerge;
+
+ // true in value if unstash will be needed after resolving conflicts.
+ @NotNull private final Map myConflictedRepositories = new HashMap();
+ private GitPreservingProcess myPreservingProcess;
+
+ protected GitMergeOperation(@NotNull Project project, @NotNull Collection repositories,
+ @NotNull String branchToMerge,
+ @NotNull String currentBranch,
+ @NotNull Map currentRevisionsBeforeMerge,
+ @NotNull ProgressIndicator indicator) {
+ super(project, repositories, indicator);
+ myBranchToMerge = branchToMerge;
+ myCurrentBranch = currentBranch;
+ myCurrentRevisionsBeforeMerge = currentRevisionsBeforeMerge;
+ myChangeListManager = ChangeListManager.getInstance(myProject);
+ }
+
+ @Override
+ protected void execute() {
+ boolean fatalErrorHappened = false;
+ boolean notAlreadyUpToDate = false;
+ while (hasMoreRepositories() && !fatalErrorHappened) {
+ final GitRepository repository = next();
+
+ VirtualFile root = repository.getRoot();
+ GitMessageWithFilesDetector localChangesOverwrittenByMerge = new GitMessageWithFilesDetector(LOCAL_CHANGES_OVERWRITTEN_BY_MERGE, root);
+ GitSimpleEventDetector unmergedFiles = new GitSimpleEventDetector(GitSimpleEventDetector.Event.UNMERGED_PREVENTING_MERGE);
+ GitMessageWithFilesDetector untrackedOverwrittenByMerge = new GitMessageWithFilesDetector(UNTRACKED_FILES_OVERWRITTEN_BY, root);
+ GitSimpleEventDetector mergeConflict = new GitSimpleEventDetector(GitSimpleEventDetector.Event.MERGE_CONFLICT);
+ GitSimpleEventDetector alreadyUpToDateDetector = new GitSimpleEventDetector(GitSimpleEventDetector.Event.ALREADY_UP_TO_DATE);
+
+ GitCommandResult result = Git.merge(repository, myBranchToMerge,
+ localChangesOverwrittenByMerge, unmergedFiles, untrackedOverwrittenByMerge, mergeConflict,
+ alreadyUpToDateDetector);
+ if (result.success()) {
+ refresh(repository);
+ markSuccessful(repository);
+ if (!alreadyUpToDateDetector.hasHappened()) {
+ notAlreadyUpToDate = true;
+ }
+ }
+ else if (unmergedFiles.hasHappened()) {
+ fatalUnmergedFilesError();
+ fatalErrorHappened = true;
+ }
+ else if (localChangesOverwrittenByMerge.wasMessageDetected()) {
+ boolean smartMergeSucceeded = proposeSmartMergePerformAndNotify(repository, localChangesOverwrittenByMerge);
+ if (!smartMergeSucceeded) {
+ fatalErrorHappened = true;
+ }
+ }
+ else if (mergeConflict.hasHappened()) {
+ myConflictedRepositories.put(repository, Boolean.FALSE);
+ refresh(repository);
+ markSuccessful(repository);
+ }
+ else if (untrackedOverwrittenByMerge.wasMessageDetected()) {
+ fatalUntrackedFilesError(untrackedOverwrittenByMerge.getFiles());
+ fatalErrorHappened = true;
+ }
+ else {
+ fatalError(getCommonErrorTitle(), result.getErrorOutputAsJoinedString());
+ fatalErrorHappened = true;
+ }
+ }
+
+ boolean allConflictsResolved = resolveConflicts();
+
+ if (!fatalErrorHappened && allConflictsResolved) {
+ if (notAlreadyUpToDate) {
+ notifySuccess();
+ }
+ else {
+ notifySuccess("Already up-to-date.");
+ }
+ }
+
+ restoreLocalChanges();
+ }
+
+ private boolean resolveConflicts() {
+ if (!myConflictedRepositories.isEmpty()) {
+ GitMerger merger = new GitMerger(myProject);
+ GitConflictResolver.Params params = new GitConflictResolver.Params();
+ GitMergeCommittingConflictResolver conflictResolver =
+ new GitMergeCommittingConflictResolver(myProject, merger, GitUtil.getRoots(myConflictedRepositories.keySet()), params, true) {
+ @Override
+ protected void notifyUnresolvedRemain() {
+ NotificationManager.getInstance(myProject).notify(
+ GitVcs.IMPORTANT_ERROR_NOTIFICATION, "Merged branch " + myBranchToMerge + " with conflicts",
+ "Unresolved conflicts remain in the project. Resolve now.", NotificationType.WARNING,
+ getResolveLinkListener());
+ }
+ };
+ return conflictResolver.merge();
+ }
+ return true;
+ }
+
+ private boolean proposeSmartMergePerformAndNotify(@NotNull GitRepository repository,
+ @NotNull GitMessageWithFilesDetector localChangesOverwrittenByMerge) {
+ Pair, List> conflictingRepositoriesAndAffectedChanges =
+ getConflictingRepositoriesAndAffectedChanges(repository, localChangesOverwrittenByMerge, myCurrentBranch, myBranchToMerge);
+ List allConflictingRepositories = conflictingRepositoriesAndAffectedChanges.getFirst();
+ List affectedChanges = conflictingRepositoriesAndAffectedChanges.getSecond();
+
+ int smartCheckoutDecision = GitSmartOperationDialog.showAndGetAnswer(myProject, affectedChanges, "merge", false);
+ if (smartCheckoutDecision == GitSmartOperationDialog.SMART_EXIT_CODE) {
+ return doSmartMerge(allConflictingRepositories);
+ }
+ else {
+ fatalLocalChangesError(myBranchToMerge);
+ return false;
+ }
+ }
+
+ private void restoreLocalChanges() {
+ if (myPreservingProcess != null) {
+ myPreservingProcess.load();
+ }
+ }
+
+ private boolean doSmartMerge(@NotNull final Collection repositories) {
+ final AtomicBoolean success = new AtomicBoolean();
+ myPreservingProcess = new GitPreservingProcess(myProject, repositories, "merge", myBranchToMerge, getIndicator(),
+ new Runnable() {
+ @Override
+ public void run() {
+ success.set(doMerge(repositories));
+ }
+ });
+ myPreservingProcess.execute(new Computable() {
+ @Override
+ public Boolean compute() {
+ return myConflictedRepositories.isEmpty();
+ }
+ });
+ return success.get();
+ }
+
+ /**
+ * Performs merge in the given repositories.
+ * Handle only merge conflict situation: all other cases should have been handled before and are treated as errors.
+ * Conflict is treated as a success: the repository with conflict is remembered and will be handled later along with all other conflicts.
+ * If an error happens in one repository, the method doesn't go further in others, and shows a notification.
+ *
+ * @return true if merge has succeeded without errors (but possibly with conflicts) in all repositories;
+ * false if it failed at least in one of them.
+ */
+ private boolean doMerge(@NotNull Collection repositories) {
+ for (GitRepository repository : repositories) {
+ GitSimpleEventDetector mergeConflict = new GitSimpleEventDetector(GitSimpleEventDetector.Event.MERGE_CONFLICT);
+ GitCommandResult result = Git.merge(repository, myBranchToMerge, mergeConflict);
+ if (!result.success()) {
+ if (mergeConflict.hasHappened()) {
+ myConflictedRepositories.put(repository, Boolean.TRUE);
+ refresh(repository);
+ markSuccessful(repository);
+ }
+ else {
+ fatalError(getCommonErrorTitle(), result.getErrorOutputAsJoinedString());
+ return false;
+ }
+ }
+ else {
+ refresh(repository);
+ markSuccessful(repository);
+ }
+ }
+ return true;
+ }
+
+ @NotNull
+ private String getCommonErrorTitle() {
+ return "Couldn't merge " + myBranchToMerge;
+ }
+
+ @Override
+ protected void rollback() {
+ Collection repositoriesForSmartRollback = new ArrayList();
+ Collection repositoriesForSimpleRollback = new ArrayList();
+ for (GitRepository repository : getSuccessfulRepositories()) {
+ if (thereAreLocalChangesIn(repository)) {
+ repositoriesForSmartRollback.add(repository);
+ }
+ else {
+ repositoriesForSimpleRollback.add(repository);
+ }
+ }
+
+ GitCompoundResult result = smartRollback(repositoriesForSmartRollback);
+ for (GitRepository repository : repositoriesForSimpleRollback) {
+ result.append(repository, rollback(repository));
+ }
+
+ if (!result.totalSuccess()) {
+ NotificationManager.getInstance(myProject).notifyError("Error during rollback", result.getErrorOutputWithReposIndication());
+ }
+ }
+
+ @NotNull
+ private GitCompoundResult smartRollback(@NotNull final Collection repositories) {
+ final GitCompoundResult result = new GitCompoundResult(myProject);
+ myPreservingProcess = new GitPreservingProcess(myProject, repositories, "merge", myBranchToMerge, getIndicator(),
+ new Runnable() {
+ @Override public void run() {
+ for (GitRepository repository : repositories) {
+ result.append(repository, rollback(repository));
+ }
+ }
+ });
+ myPreservingProcess.execute();
+ return result;
+ }
+
+ @NotNull
+ private GitCommandResult rollback(@NotNull GitRepository repository) {
+ return Git.hardReset(repository, myCurrentRevisionsBeforeMerge.get(repository));
+ }
+
+ private boolean thereAreLocalChangesIn(@NotNull GitRepository repository) {
+ return !myChangeListManager.getChangesIn(repository.getRoot()).isEmpty();
+ }
+
+ @NotNull
+ @Override
+ public String getSuccessMessage() {
+ return String.format("Merged %s to %s", myBranchToMerge, myCurrentBranch);
+ }
+
+ @NotNull
+ @Override
+ protected String getRollbackProposal() {
+ return "However merge has succeeded for the following " + repositories() + ":
" +
+ successfulRepositoriesJoined() +
+ "
You may rollback (reset to the commit before merging) not to let branches diverge.";
+ }
+
+ @NotNull
+ @Override
+ protected String getOperationName() {
+ return "merge";
+ }
+
+ private static void refresh(GitRepository... repositories) {
+ for (GitRepository repository : repositories) {
+ refreshRoot(repository);
+ repository.update(GitRepository.TrackedTopic.ALL_CURRENT);
+ }
+ }
+
+}
diff --git a/plugins/git4idea/src/git4idea/branch/GitWouldBeOverwrittenByCheckoutDialog.java b/plugins/git4idea/src/git4idea/branch/GitSmartOperationDialog.java
similarity index 66%
rename from plugins/git4idea/src/git4idea/branch/GitWouldBeOverwrittenByCheckoutDialog.java
rename to plugins/git4idea/src/git4idea/branch/GitSmartOperationDialog.java
index ad60e17fcc44..af3e986ea62f 100644
--- a/plugins/git4idea/src/git4idea/branch/GitWouldBeOverwrittenByCheckoutDialog.java
+++ b/plugins/git4idea/src/git4idea/branch/GitSmartOperationDialog.java
@@ -31,29 +31,33 @@ import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;
/**
- * The dialog that is shown when the error "The following files would be overwritten by checkout" happens.
- * Displays the list of these files and proposes to make a "smart" checkout.
+ * The dialog that is shown when the error
+ * "Your local changes to the following files would be overwritten by merge/checkout"
+ * happens.
+ * Displays the list of these files and proposes to make a "smart" merge or checkout.
*
* @author Kirill Likhodedov
*/
-// TODO "don't ask again" option
-class GitWouldBeOverwrittenByCheckoutDialog extends DialogWrapper {
+class GitSmartOperationDialog extends DialogWrapper {
- public static final int SMART_CHECKOUT = OK_EXIT_CODE;
- public static final int FORCE_CHECKOUT_EXIT_CODE = NEXT_USER_EXIT_CODE;
+ public static final int SMART_EXIT_CODE = OK_EXIT_CODE;
+ public static final int FORCE_EXIT_CODE = NEXT_USER_EXIT_CODE;
private final Project myProject;
private final List myChanges;
+ @NotNull private final String myOperationTitle;
+ private final boolean myForceButton;
/**
- * @return true if smart checkout has to be performed, false if user doesn't want to checkout.
+ * 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 List changes) {
+ static int showAndGetAnswer(@NotNull final Project project, @NotNull final List changes, @NotNull final String operationTitle,
+ final boolean forceButton) {
final AtomicInteger exitCode = new AtomicInteger();
UIUtil.invokeAndWaitIfNeeded(new Runnable() {
@Override
public void run() {
- GitWouldBeOverwrittenByCheckoutDialog dialog = new GitWouldBeOverwrittenByCheckoutDialog(project, changes);
+ GitSmartOperationDialog dialog = new GitSmartOperationDialog(project, changes, operationTitle, forceButton);
DialogManager.getInstance(project).showDialog(dialog);
exitCode.set(dialog.getExitCode());
}
@@ -61,19 +65,25 @@ class GitWouldBeOverwrittenByCheckoutDialog extends DialogWrapper {
return exitCode.get();
}
- private GitWouldBeOverwrittenByCheckoutDialog(@NotNull Project project, @NotNull List changes) {
+ private GitSmartOperationDialog(@NotNull Project project, @NotNull List changes, @NotNull String operationTitle,
+ boolean forceButton) {
super(project);
myProject = project;
myChanges = changes;
- setOKButtonText("Smart checkout");
- setCancelButtonText("Don't checkout");
+ myOperationTitle = operationTitle;
+ myForceButton = forceButton;
+ setOKButtonText("Smart " + operationTitle);
+ setCancelButtonText("Don't " + operationTitle);
getCancelAction().putValue(FOCUSED_ACTION, Boolean.TRUE);
init();
}
@Override
protected Action[] createLeftSideActions() {
- return new Action[] {new ForceCheckoutAction() };
+ if (myForceButton) {
+ return new Action[] {new ForceCheckoutAction(myOperationTitle) };
+ }
+ return new Action[0];
}
@Override
@@ -94,19 +104,19 @@ class GitWouldBeOverwrittenByCheckoutDialog extends DialogWrapper {
@Override
protected String getDimensionServiceKey() {
- return GitWouldBeOverwrittenByCheckoutDialog.class.getName();
+ return GitSmartOperationDialog.class.getName();
}
private class ForceCheckoutAction extends AbstractAction {
- ForceCheckoutAction() {
- super("Force checkout");
+ ForceCheckoutAction(@NotNull String operationTitle) {
+ super("Force " + operationTitle);
}
@Override
public void actionPerformed(ActionEvent e) {
- close(FORCE_CHECKOUT_EXIT_CODE);
+ close(FORCE_EXIT_CODE);
}
}
diff --git a/plugins/git4idea/src/git4idea/commands/Git.java b/plugins/git4idea/src/git4idea/commands/Git.java
index c52978d918dd..27f94bf775b9 100644
--- a/plugins/git4idea/src/git4idea/commands/Git.java
+++ b/plugins/git4idea/src/git4idea/commands/Git.java
@@ -131,6 +131,19 @@ public class Git {
return run(handler, true);
}
+ @NotNull
+ public static GitCommandResult merge(@NotNull GitRepository repository, @NotNull String branchToMerge,
+ @NotNull GitLineHandlerListener... listeners) {
+ final GitLineHandler mergeHandler = new GitLineHandler(repository.getProject(), repository.getRoot(), GitCommand.MERGE);
+ mergeHandler.setSilent(false);
+ mergeHandler.addParameters(branchToMerge);
+ for (GitLineHandlerListener listener : listeners) {
+ mergeHandler.addLineListener(listener);
+ }
+ return run(mergeHandler);
+ }
+
+
/**
* {@code git checkout <reference>}
* {@code git checkout -b <newBranch> <reference>}
@@ -225,6 +238,13 @@ public class Git {
return run(h);
}
+ @NotNull
+ public static GitCommandResult hardReset(@NotNull GitRepository repository, @NotNull String revision) {
+ final GitLineHandler handler = new GitLineHandler(repository.getProject(), repository.getRoot(), GitCommand.RESET);
+ handler.addParameters("--hard", revision);
+ return run(handler);
+ }
+
/**
* Returns the last (tip) commit on the given branch.
* {@code git rev-list -1 }
diff --git a/plugins/git4idea/src/git4idea/commands/GitMessageWithFilesDetector.java b/plugins/git4idea/src/git4idea/commands/GitMessageWithFilesDetector.java
index 4b597df64bd7..a3c8910ab94b 100644
--- a/plugins/git4idea/src/git4idea/commands/GitMessageWithFilesDetector.java
+++ b/plugins/git4idea/src/git4idea/commands/GitMessageWithFilesDetector.java
@@ -40,6 +40,9 @@ public class GitMessageWithFilesDetector implements GitLineHandlerListener {
private final VirtualFile myRoot;
public enum Event {
+ LOCAL_CHANGES_OVERWRITTEN_BY_MERGE(
+ "Your local changes to the following files would be overwritten by merge",
+ "commit your changes or stash them before"),
LOCAL_CHANGES_OVERWRITTEN_BY_CHECKOUT(
"Your local changes to the following files would be overwritten by checkout",
"commit your changes or stash them before"),
diff --git a/plugins/git4idea/src/git4idea/commands/GitSimpleEventDetector.java b/plugins/git4idea/src/git4idea/commands/GitSimpleEventDetector.java
index 34c612e3b466..216db2ad3aa8 100644
--- a/plugins/git4idea/src/git4idea/commands/GitSimpleEventDetector.java
+++ b/plugins/git4idea/src/git4idea/commands/GitSimpleEventDetector.java
@@ -26,8 +26,11 @@ public class GitSimpleEventDetector implements GitLineHandlerListener {
private boolean myHappened;
public enum Event {
- UNMERGED("you need to resolve your current index first"),
- BRANCH_NOT_FULLY_MERGED("is not fully merged");
+ UNMERGED_PREVENTING_CHECKOUT("you need to resolve your current index first"),
+ UNMERGED_PREVENTING_MERGE("is not possible because you have unmerged files"),
+ BRANCH_NOT_FULLY_MERGED("is not fully merged"),
+ MERGE_CONFLICT("Automatic merge failed; fix conflicts and then commit the result"),
+ ALREADY_UP_TO_DATE("Already up-to-date");
private final String myDetectionString;
diff --git a/plugins/git4idea/src/git4idea/merge/GitConflictResolver.java b/plugins/git4idea/src/git4idea/merge/GitConflictResolver.java
index c1987199ce7d..351bb674b5cf 100644
--- a/plugins/git4idea/src/git4idea/merge/GitConflictResolver.java
+++ b/plugins/git4idea/src/git4idea/merge/GitConflictResolver.java
@@ -118,7 +118,6 @@ public class GitConflictResolver {
* the method shows a notification and returns {@code false}.
*
*
- * @param roots Git repositories to look for unmerged files.
* @return {@code true} if there is nothing to merge anymore, {@code false} if unmerged files remain or in the case of error.
*/
public final boolean merge() {
@@ -221,6 +220,11 @@ public class GitConflictResolver {
}
+ @NotNull
+ protected NotificationListener getResolveLinkListener() {
+ return new ResolveNotificationListener();
+ }
+
private class ResolveNotificationListener implements NotificationListener {
@Override public void hyperlinkUpdate(@NotNull final Notification notification, @NotNull HyperlinkEvent event) {
if (event.getEventType() == HyperlinkEvent.EventType.ACTIVATED && event.getDescription().equals("resolve")) {
diff --git a/plugins/git4idea/src/git4idea/merge/GitMergeCommittingConflictResolver.java b/plugins/git4idea/src/git4idea/merge/GitMergeCommittingConflictResolver.java
new file mode 100644
index 000000000000..4c48ab6dd0dd
--- /dev/null
+++ b/plugins/git4idea/src/git4idea/merge/GitMergeCommittingConflictResolver.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright 2000-2012 JetBrains s.r.o.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package git4idea.merge;
+
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.vcs.VcsException;
+import com.intellij.openapi.vfs.VirtualFile;
+
+import java.util.Collection;
+
+/**
+ * Conflict resolver that makes a merge commit after all conflicts are resolved.
+ *
+ * @author Kirill Likhodedov
+ */
+public class GitMergeCommittingConflictResolver extends GitConflictResolver {
+ private final Collection myMergingRoots;
+ private final boolean myRefreshAfterCommit;
+ private final GitMerger myMerger;
+
+ public GitMergeCommittingConflictResolver(Project project, GitMerger merger, Collection mergingRoots, Params params,
+ boolean refreshAfterCommit) {
+ super(project, mergingRoots, params);
+ myMerger = merger;
+ myMergingRoots = mergingRoots;
+ myRefreshAfterCommit = refreshAfterCommit;
+ }
+
+ @Override protected boolean proceedAfterAllMerged() throws VcsException {
+ myMerger.mergeCommit(myMergingRoots);
+ if (myRefreshAfterCommit) {
+ for (VirtualFile root : myMergingRoots) {
+ root.refresh(true, true);
+ }
+ }
+ return true;
+ }
+}
diff --git a/plugins/git4idea/src/git4idea/ui/branch/GitBranchPopupActions.java b/plugins/git4idea/src/git4idea/ui/branch/GitBranchPopupActions.java
index 4ae9edab67a2..ba4d118967ea 100644
--- a/plugins/git4idea/src/git4idea/ui/branch/GitBranchPopupActions.java
+++ b/plugins/git4idea/src/git4idea/ui/branch/GitBranchPopupActions.java
@@ -187,6 +187,7 @@ class GitBranchPopupActions {
new CheckoutAction(myProject, myRepositories, myBranchName, mySelectedRepository),
new CheckoutAsNewBranch(myProject, myRepositories, myBranchName, mySelectedRepository),
new CompareAction(myProject, myRepositories, myBranchName, mySelectedRepository),
+ new MergeAction(myProject, myRepositories, myBranchName, mySelectedRepository),
new DeleteAction(myProject, myRepositories, myBranchName, mySelectedRepository)
};
}
@@ -290,6 +291,7 @@ class GitBranchPopupActions {
return new AnAction[] {
new CheckoutRemoteBranchAction(myProject, myRepositories, myBranchName, mySelectedRepository),
new CompareAction(myProject, myRepositories, myBranchName, mySelectedRepository),
+ new MergeAction(myProject, myRepositories, myBranchName, mySelectedRepository),
};
}
@@ -324,7 +326,6 @@ class GitBranchPopupActions {
return myRemoteBranchName.substring(slashPosition+1);
}
}
-
}
private static class CompareAction extends DumbAwareAction {
@@ -334,10 +335,8 @@ class GitBranchPopupActions {
private final String myBranchName;
private final GitRepository mySelectedRepository;
- public CompareAction(@NotNull Project project,
- @NotNull List repositories,
- @NotNull String branchName,
- GitRepository selectedRepository) {
+ public CompareAction(@NotNull Project project, @NotNull List repositories, @NotNull String branchName,
+ @NotNull GitRepository selectedRepository) {
super("Compare");
myProject = project;
myRepositories = repositories;
@@ -351,4 +350,27 @@ class GitBranchPopupActions {
}
}
+
+ private static class MergeAction extends DumbAwareAction {
+
+ private final Project myProject;
+ private final List myRepositories;
+ private final String myBranchName;
+ private final GitRepository mySelectedRepository;
+
+ public MergeAction(@NotNull Project project, @NotNull List repositories, @NotNull String branchName,
+ @NotNull GitRepository selectedRepository) {
+ super("Merge");
+ myProject = project;
+ myRepositories = repositories;
+ myBranchName = branchName;
+ mySelectedRepository = selectedRepository;
+ }
+
+ @Override
+ public void actionPerformed(AnActionEvent e) {
+ new GitBranchOperationsProcessor(myProject, myRepositories, mySelectedRepository).merge(myBranchName);
+ }
+
+ }
}
diff --git a/plugins/git4idea/src/git4idea/update/GitUpdateProcess.java b/plugins/git4idea/src/git4idea/update/GitUpdateProcess.java
index 6f3b1fff17c2..6f4c81c8aeb6 100644
--- a/plugins/git4idea/src/git4idea/update/GitUpdateProcess.java
+++ b/plugins/git4idea/src/git4idea/update/GitUpdateProcess.java
@@ -31,6 +31,7 @@ import git4idea.GitBranch;
import git4idea.GitVcs;
import git4idea.branch.GitBranchPair;
import git4idea.merge.GitConflictResolver;
+import git4idea.merge.GitMergeCommittingConflictResolver;
import git4idea.merge.GitMerger;
import git4idea.rebase.GitRebaser;
import git4idea.stash.GitChangesSaver;
@@ -297,7 +298,7 @@ public class GitUpdateProcess {
GitConflictResolver.Params params = new GitConflictResolver.Params();
params.setErrorNotificationTitle("Can't update");
params.setMergeDescription("You have unfinished merge. These conflicts must be resolved before update.");
- return !new MergeCommittingConflictResolver(myProject, myMerger, mergingRoots, params).merge();
+ return !new GitMergeCommittingConflictResolver(myProject, myMerger, mergingRoots, params, false).merge();
}
/**
@@ -308,7 +309,7 @@ public class GitUpdateProcess {
GitConflictResolver.Params params = new GitConflictResolver.Params();
params.setErrorNotificationTitle("Can't update");
params.setMergeDescription("Unmerged files detected. These conflicts must be resolved before update.");
- return !new MergeCommittingConflictResolver(myProject, myMerger, myRoots, params).merge();
+ return !new GitMergeCommittingConflictResolver(myProject, myMerger, myRoots, params, false).merge();
}
/**
@@ -338,22 +339,4 @@ public class GitUpdateProcess {
}
}.merge();
}
-
- // conflict resolver that makes a merge commit after all conflicts are resolved
- private static class MergeCommittingConflictResolver extends GitConflictResolver {
- private final Collection myMergingRoots;
- private final GitMerger myMerger;
-
- public MergeCommittingConflictResolver(Project project, GitMerger merger, Collection mergingRoots, Params params) {
- super(project, mergingRoots, params);
- myMerger = merger;
- myMergingRoots = mergingRoots;
- }
-
- @Override protected boolean proceedAfterAllMerged() throws VcsException {
- myMerger.mergeCommit(myMergingRoots);
- return true;
- }
- }
-
}
diff --git a/plugins/git4idea/src/git4idea/util/GitPreservingProcess.java b/plugins/git4idea/src/git4idea/util/GitPreservingProcess.java
index 8a4acd1832d1..f778e761c140 100644
--- a/plugins/git4idea/src/git4idea/util/GitPreservingProcess.java
+++ b/plugins/git4idea/src/git4idea/util/GitPreservingProcess.java
@@ -147,7 +147,7 @@ public class GitPreservingProcess {
}
}
- private void load() {
+ public void load() {
try {
mySaver.load();
}
diff --git a/plugins/git4idea/tests/git4idea/branch/GitBranchOperationsTest.java b/plugins/git4idea/tests/git4idea/branch/GitBranchOperationsTest.java
index 656b18655d13..659b07d4e708 100644
--- a/plugins/git4idea/tests/git4idea/branch/GitBranchOperationsTest.java
+++ b/plugins/git4idea/tests/git4idea/branch/GitBranchOperationsTest.java
@@ -172,25 +172,25 @@ public class GitBranchOperationsTest extends AbstractVcsTestCase {
assertBranch(myCommunity, MASTER);
assertBranch(myContrib, MASTER);
}
-
+
@Test
public void checkout_without_problems() throws Exception {
- prepareBranchForSimpleCheckout();
+ prepareBranchWithCommit("feature");
doCheckout("feature", null);
assertNotify(NotificationType.INFORMATION, "Checked out feature");
}
-
+
@Test
public void checkout_with_unmerged_files_in_first_repo_should_show_notification() throws Exception {
- prepareBranchForSimpleCheckout();
+ prepareBranchWithCommit("feature");
GitTestScenarioGenerator.prepareUnmergedFiles(myUltimate);
doCheckout("feature", null);
assertNotify(NotificationType.ERROR, unmergedFilesErrorNotificationDescription("checkout"));
}
-
+
@Test
public void checkout_with_unmerged_file_in_second_repo_should_propose_to_rollback() throws Exception {
- prepareBranchForSimpleCheckout();
+ prepareBranchWithCommit("feature");
GitTestScenarioGenerator.prepareUnmergedFiles(myCommunity);
doCheckout("feature", null);
assertMessage(unmergedFilesErrorTitle("checkout"));
@@ -198,7 +198,7 @@ public class GitBranchOperationsTest extends AbstractVcsTestCase {
@Test
public void rollback_checkout_should_return_to_previous_branch() throws Exception {
- prepareBranchForSimpleCheckout();
+ prepareBranchWithCommit("feature");
GitTestScenarioGenerator.prepareUnmergedFiles(myCommunity);
myMessageManager.nextAnswer(Messages.OK);
doCheckout("feature", null);
@@ -208,7 +208,7 @@ public class GitBranchOperationsTest extends AbstractVcsTestCase {
@Test
public void deny_rollback_checkout_should_do_nothing() throws Exception {
- prepareBranchForSimpleCheckout();
+ prepareBranchWithCommit("feature");
GitTestScenarioGenerator.prepareUnmergedFiles(myCommunity);
myMessageManager.nextAnswer(Messages.CANCEL);
doCheckout("feature", null);
@@ -243,7 +243,7 @@ public class GitBranchOperationsTest extends AbstractVcsTestCase {
}
}
assertNotNull(untrackedFilesDialogClass);
-
+
final AtomicBoolean dialogShown = new AtomicBoolean();
final Class finalUntrackedFilesDialogClass = untrackedFilesDialogClass;
myDialogManager.registerDialogHandler(untrackedFilesDialogClass, new TestDialogHandler() {
@@ -267,9 +267,9 @@ public class GitBranchOperationsTest extends AbstractVcsTestCase {
branch(myContrib, "feature");
final AtomicBoolean dialogShown = new AtomicBoolean();
- myDialogManager.registerDialogHandler(GitWouldBeOverwrittenByCheckoutDialog.class, new TestDialogHandler() {
+ myDialogManager.registerDialogHandler(GitSmartOperationDialog.class, new TestDialogHandler() {
@Override
- public int handleDialog(GitWouldBeOverwrittenByCheckoutDialog dialog) {
+ public int handleDialog(GitSmartOperationDialog dialog) {
dialogShown.set(true);
return DialogWrapper.CANCEL_EXIT_CODE;
}
@@ -284,10 +284,10 @@ public class GitBranchOperationsTest extends AbstractVcsTestCase {
prepareLocalChangesAndBranchWithSameModifiedFilesWithoutConflicts(myUltimate);
prepareLocalChangesAndBranchWithSameModifiedFilesWithoutConflicts(myCommunity);
prepareLocalChangesAndBranchWithSameModifiedFilesWithoutConflicts(myContrib);
- myDialogManager.registerDialogHandler(GitWouldBeOverwrittenByCheckoutDialog.class,
- new TestDialogHandler() {
+ myDialogManager.registerDialogHandler(GitSmartOperationDialog.class,
+ new TestDialogHandler() {
@Override
- public int handleDialog(GitWouldBeOverwrittenByCheckoutDialog dialog) {
+ public int handleDialog(GitSmartOperationDialog dialog) {
return DialogWrapper.OK_EXIT_CODE;
}
});
@@ -307,16 +307,16 @@ public class GitBranchOperationsTest extends AbstractVcsTestCase {
branch(myCommunity, "feature");
branch(myContrib, "feature");
- myDialogManager.registerDialogHandler(GitWouldBeOverwrittenByCheckoutDialog.class, new TestDialogHandler() {
+ myDialogManager.registerDialogHandler(GitSmartOperationDialog.class, new TestDialogHandler() {
@Override
- public int handleDialog(GitWouldBeOverwrittenByCheckoutDialog dialog) {
+ public int handleDialog(GitSmartOperationDialog dialog) {
return DialogWrapper.CANCEL_EXIT_CODE;
}
});
doCheckout("feature", null);
assertNotify(NotificationType.ERROR, "Couldn't checkout feature", stripHtmlAndBreaks("Local changes would be overwritten by checkout." +
- "Stash or commit them before checking out a branch."));
+ "You should stash or commit them."));
assertBranch("master");
}
@@ -325,25 +325,25 @@ public class GitBranchOperationsTest extends AbstractVcsTestCase {
prepareLocalChangesAndBranchWithSameModifiedFilesWithoutConflicts(myCommunity);
branch(myUltimate, "feature");
branch(myContrib, "feature");
- myDialogManager.registerDialogHandler(GitWouldBeOverwrittenByCheckoutDialog.class, new TestDialogHandler() {
+ myDialogManager.registerDialogHandler(GitSmartOperationDialog.class, new TestDialogHandler() {
@Override
- public int handleDialog(GitWouldBeOverwrittenByCheckoutDialog dialog) {
+ public int handleDialog(GitSmartOperationDialog dialog) {
return DialogWrapper.CANCEL_EXIT_CODE;
}
});
doCheckout("feature", null);
- assertMessage("Couldn't checkout feature",
- "Local changes would be overwritten by checkout.
Stash or commit them before checking out a branch.
" +
+ assertMessage("Couldn't checkout feature",
+ "Local changes would be overwritten by checkout.
You should stash or commit them.
" +
"However checkout has succeeded for the following repository:
" +
- myUltimate.getPresentableUrl() +
- "
You may rollback (checkout back to master) not to let branches diverge.",
+ myUltimate.getPresentableUrl() +
+ "
You may rollback (checkout back to master) not to let branches diverge.",
"Rollback", "Don't rollback");
}
-
+
@Test
public void rollback_checkout_branch_as_new_branch_should_delete_branches() throws Exception {
- prepareBranchForSimpleCheckout();
+ prepareBranchWithCommit("feature");
GitTestScenarioGenerator.prepareUnmergedFiles(myCommunity);
myMessageManager.nextAnswer(Messages.OK);
doCheckout("feature", "newBranch");
@@ -372,14 +372,6 @@ public class GitBranchOperationsTest extends AbstractVcsTestCase {
create(repository, "untracked.txt");
}
- private void prepareBranchForSimpleCheckout() throws IOException {
- for (GitRepository repository : myRepositories) {
- checkout(repository, "-b", "feature");
- createAddCommit(repository, "feature_file.txt");
- checkout(repository, "master");
- }
- }
-
@Test
public void delete_branch_without_problems() throws Exception {
for (GitRepository repository : myRepositories) {
@@ -389,10 +381,10 @@ public class GitBranchOperationsTest extends AbstractVcsTestCase {
doDeleteBranch("master1");
assertNotify(NotificationType.INFORMATION, "Deleted branch master1");
}
-
+
@Test
public void delete_unmerged_branch_should_show_dialog() throws Exception {
- prepareUnmergedBranch(myUltimate, myCommunity, myContrib);
+ prepareBranchWithCommit("unmerged_branch", myUltimate, myCommunity, myContrib);
final AtomicBoolean dialogShown = new AtomicBoolean();
myDialogManager.registerDialogHandler(GitBranchIsNotFullyMergedDialog.class, new TestDialogHandler() {
@@ -405,41 +397,41 @@ public class GitBranchOperationsTest extends AbstractVcsTestCase {
doDeleteBranch("unmerged_branch");
assertTrue(dialogShown.get());
}
-
+
@Test
public void ok_in_unmerged_branch_dialog_should_force_delete_branch() throws Exception {
- prepareUnmergedBranch(myUltimate, myCommunity, myContrib);
+ prepareBranchWithCommit("unmerged_branch", myUltimate, myCommunity, myContrib);
registerNotFullyMergedDialog(DialogWrapper.OK_EXIT_CODE);
doDeleteBranch("unmerged_branch");
for (GitRepository repository : myRepositories) {
assertTrue(!branch(repository).contains("unmerged_branch"));
}
}
-
+
@Test
public void cancel_in_unmerged_branch_dialog_in_first_repository_should_show_notification() throws Exception {
- prepareUnmergedBranch(myUltimate, myContrib);
+ prepareBranchWithCommit("unmerged_branch", myUltimate, myContrib);
branch(myCommunity, "unmerged_branch");
registerNotFullyMergedDialog(DialogWrapper.CANCEL_EXIT_CODE);
doDeleteBranch("unmerged_branch");
assertNotify(NotificationType.ERROR, "Branch unmerged_branch wasn't deleted", "This branch is not fully merged to master.");
}
-
+
@Test
public void cancel_in_unmerged_branch_dialog_in_not_first_repository_should_show_rollback_proposal() throws Exception {
branch(myUltimate, "unmerged_branch");
- prepareUnmergedBranch(myCommunity, myContrib);
+ prepareBranchWithCommit("unmerged_branch", myCommunity, myContrib);
registerNotFullyMergedDialog(DialogWrapper.CANCEL_EXIT_CODE);
doDeleteBranch("unmerged_branch");
assertMessage(String.format("Branch %s wasn't deleted", "unmerged_branch"));
}
-
+
@Test
public void rollback_delete_branch_should_recreate_branches() throws Exception {
branch(myUltimate, "unmerged_branch");
- prepareUnmergedBranch(myCommunity);
+ prepareBranchWithCommit("unmerged_branch", myCommunity);
branch(myContrib, "unmerged_branch");
registerNotFullyMergedDialog(DialogWrapper.CANCEL_EXIT_CODE);
@@ -450,13 +442,13 @@ public class GitBranchOperationsTest extends AbstractVcsTestCase {
assertTrue(branch(repository).contains("unmerged_branch"));
}
}
-
+
@Test
public void deny_rollback_delete_branch_should_do_nothing() throws Exception {
branch(myUltimate, "unmerged_branch");
- prepareUnmergedBranch(myCommunity);
+ prepareBranchWithCommit("unmerged_branch", myCommunity);
branch(myContrib, "unmerged_branch");
-
+
registerNotFullyMergedDialog(DialogWrapper.CANCEL_EXIT_CODE);
myMessageManager.nextAnswer(Messages.CANCEL);
doDeleteBranch("unmerged_branch");
@@ -466,6 +458,88 @@ public class GitBranchOperationsTest extends AbstractVcsTestCase {
assertTrue(!branch(myUltimate).contains("unmerged_branch"));
}
+ @Test
+ public void merge_simple_without_problems() throws Exception {
+ prepareBranchWithCommit("feature", myUltimate, myCommunity, myContrib);
+ doMerge("feature");
+ assertNotify(NotificationType.INFORMATION, "Merged feature to master");
+
+ assertFile(myUltimate, "unmerged_branch_file", "content");
+ assertFile(myCommunity, "unmerged_branch_file", "content");
+ assertFile(myContrib, "unmerged_branch_file", "content");
+ }
+
+ private static void assertFile(GitRepository repository, String path, String content) throws IOException {
+ VirtualFile branchFile = repository.getRoot().findChild(path);
+ assertNotNull(branchFile);
+ assertTrue(branchFile.exists());
+ assertEquals(new String(branchFile.contentsToByteArray()), content);
+ }
+
+ @Test
+ public void merge_up_to_date_branch() throws Exception {
+ branch(myUltimate, "master2");
+ branch(myCommunity, "master2");
+ branch(myContrib, "master2");
+
+ doMerge("master2");
+
+ assertNotify(NotificationType.INFORMATION, "Already up-to-date.");
+ }
+
+ @Test
+ public void merge_one_simple_and_other_up_to_date() throws Exception {
+ branch(myUltimate, "master2");
+ branch(myContrib, "master2");
+ prepareBranchWithCommit("master2", myCommunity);
+
+ doMerge("master2");
+
+ assertNotify(NotificationType.INFORMATION, "Merged master2 to master");
+ assertFile(myCommunity, "unmerged_branch_file", "content");
+ assertNull(myUltimate.getRoot().findChild("unmerged_branch_file"));
+ }
+
+ @Test
+ public void merge_with_unmerged_files_in_first_repo_should_show_notification() throws Exception {
+ prepareBranchWithCommit("feature");
+ GitTestScenarioGenerator.prepareUnmergedFiles(myUltimate);
+ doMerge("feature");
+ assertNotify(NotificationType.ERROR, unmergedFilesErrorNotificationDescription("merge"));
+ }
+
+ @Test
+ public void merge_with_unmerged_files_in_second_repo_should_propose_to_rollback() throws Exception {
+ prepareBranchWithCommit("feature");
+ GitTestScenarioGenerator.prepareUnmergedFiles(myCommunity);
+ doMerge("feature");
+ assertMessage(unmergedFilesErrorTitle("merge"));
+ }
+
+ @Test
+ public void rollback_merge_should_reset_merge() throws Exception {
+ prepareBranchWithCommit("feature");
+ String ultimateTip = tip(myUltimate);
+ GitTestScenarioGenerator.prepareUnmergedFiles(myCommunity);
+ myMessageManager.nextAnswer(Messages.OK);
+ doMerge("feature");
+ assertMessage(unmergedFilesErrorTitle("merge"));
+ assertBranch("master");
+ assertEquals(tip(myUltimate), ultimateTip);
+ }
+
+ @Test
+ public void deny_rollback_merge_should_leave_as_is() throws Exception {
+ prepareBranchWithCommit("feature");
+ String ultimateTip = tip(myUltimate);
+ GitTestScenarioGenerator.prepareUnmergedFiles(myCommunity);
+ myMessageManager.nextAnswer(Messages.CANCEL);
+ doMerge("feature");
+ assertMessage(unmergedFilesErrorTitle("merge"));
+ assertBranch("master");
+ assertFalse(tip(myUltimate).equals(ultimateTip));
+ }
+
private void registerNotFullyMergedDialog(final int answer) {
myDialogManager.registerDialogHandler(GitBranchIsNotFullyMergedDialog.class, new TestDialogHandler() {
@Override
@@ -475,9 +549,12 @@ public class GitBranchOperationsTest extends AbstractVcsTestCase {
});
}
- private static void prepareUnmergedBranch(GitRepository... repositories) throws IOException {
+ private void prepareBranchWithCommit(String branch, GitRepository... repositories) throws IOException {
+ if (repositories.length == 0) {
+ repositories = new GitRepository[] { myUltimate, myCommunity, myContrib };
+ }
for (GitRepository repository : repositories) {
- checkout(repository, "-b", "unmerged_branch");
+ checkout(repository, "-b", branch);
createAddCommit(repository, "unmerged_branch_file");
checkout(repository, "master");
refresh(repository);
@@ -488,6 +565,14 @@ public class GitBranchOperationsTest extends AbstractVcsTestCase {
callPrivateBranchOperationsProcessorMethod("doCheckoutNewBranch", NEW_BRANCH);
}
+ private void doMerge(String branch) throws InvocationTargetException, NoSuchMethodException, IllegalAccessException {
+ callPrivateBranchOperationsProcessorMethod("doMerge", branch);
+ // sync refresh is needed, because the refresh inside GitMergeOperation is asynchronous.
+ for (GitRepository repository : myRepositories) {
+ repository.getRoot().refresh(false, true);
+ }
+ }
+
private static String unmergedFilesErrorNotificationDescription(String operation)
throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
Method method = GitBranchOperation.class.getDeclaredMethod("unmergedFilesErrorNotificationDescription", String.class);
diff --git a/plugins/git4idea/tests/git4idea/test/GitExec.java b/plugins/git4idea/tests/git4idea/test/GitExec.java
index e7c709f79fe2..6cabf8c9cb3d 100644
--- a/plugins/git4idea/tests/git4idea/test/GitExec.java
+++ b/plugins/git4idea/tests/git4idea/test/GitExec.java
@@ -121,6 +121,10 @@ public class GitExec {
run(repository, "merge", branch);
}
+ public static String tip(@NotNull GitRepository repository) throws IOException {
+ return run(repository, "rev-list", "-1", "HEAD");
+ }
+
@NotNull
private static String run(@NotNull GitRepository repository, @NotNull String command, String... params) throws IOException {
return new GitTestRunEnv(new File(repository.getRoot().getPath())).run(command, params);
diff --git a/plugins/git4idea/tests/git4idea/test/GitTestScenarioGenerator.java b/plugins/git4idea/tests/git4idea/test/GitTestScenarioGenerator.java
index 021535ebad9e..10cb1fa5e5e2 100644
--- a/plugins/git4idea/tests/git4idea/test/GitTestScenarioGenerator.java
+++ b/plugins/git4idea/tests/git4idea/test/GitTestScenarioGenerator.java
@@ -27,13 +27,15 @@ import static git4idea.test.GitExec.*;
*/
public class GitTestScenarioGenerator {
+ private static final String BRANCH_FOR_UNMERGED_CONFLICTS = "unmerged_files_branch_" + Math.random();
+
public static void prepareUnmergedFiles(@NotNull GitRepository... repositories) throws IOException {
for (GitRepository repository : repositories) {
String unmergedFile = "unmerged";
create(repository, unmergedFile, "master content");
addCommit(repository, unmergedFile);
- checkoutFeatureBranch(repository);
+ checkoutBranchForUnmergedConflicts(repository);
edit(repository, unmergedFile, "feature content");
addCommit(repository, unmergedFile);
@@ -41,17 +43,17 @@ public class GitTestScenarioGenerator {
edit(repository, unmergedFile, "master feature");
addCommit(repository, unmergedFile);
- merge(repository, "feature");
+ merge(repository, BRANCH_FOR_UNMERGED_CONFLICTS);
refresh(repository);
}
}
- private static void checkoutFeatureBranch(GitRepository repository) throws IOException {
+ private static void checkoutBranchForUnmergedConflicts(GitRepository repository) throws IOException {
String branches = branch(repository);
- if (!branches.contains("feature")) {
- checkout(repository, "-b", "feature");
+ if (!branches.contains(BRANCH_FOR_UNMERGED_CONFLICTS)) {
+ checkout(repository, "-b", BRANCH_FOR_UNMERGED_CONFLICTS);
} else {
- checkout(repository, "feature");
+ checkout(repository, BRANCH_FOR_UNMERGED_CONFLICTS);
}
}
}
From 47087d9a6fae8ee9d58bf3076a66cadc8a9237d6 Mon Sep 17 00:00:00 2001
From: Kirill Likhodedov
Date: Wed, 8 Feb 2012 19:36:50 +0400
Subject: [PATCH 13/16] More tests on GitMergeOperation
---
.../git4idea/branch/GitCheckoutOperation.java | 3 +-
.../git4idea/branch/GitMergeOperation.java | 3 +-
.../branch/GitBranchOperationsTest.java | 110 +++++++++++++++---
3 files changed, 95 insertions(+), 21 deletions(-)
diff --git a/plugins/git4idea/src/git4idea/branch/GitCheckoutOperation.java b/plugins/git4idea/src/git4idea/branch/GitCheckoutOperation.java
index 1340ba4e582f..6585ce74fd73 100644
--- a/plugins/git4idea/src/git4idea/branch/GitCheckoutOperation.java
+++ b/plugins/git4idea/src/git4idea/branch/GitCheckoutOperation.java
@@ -50,6 +50,7 @@ import static git4idea.util.GitUIUtil.code;
class GitCheckoutOperation extends GitBranchOperation {
private static final Logger LOG = Logger.getInstance(GitCheckoutOperation.class);
+ public static final String ROLLBACK_PROPOSAL_FORMAT = "You may rollback (checkout back to %s) not to let branches diverge.";
@NotNull private final String myStartPointReference;
@Nullable private final String myNewBranch;
@@ -142,7 +143,7 @@ class GitCheckoutOperation extends GitBranchOperation {
protected String getRollbackProposal() {
return "However checkout has succeeded for the following " + repositories() + ":
" +
successfulRepositoriesJoined() +
- "
You may rollback (checkout back to " + myPreviousBranch + ") not to let branches diverge.";
+ "
" + String.format(ROLLBACK_PROPOSAL_FORMAT, myPreviousBranch);
}
@NotNull
diff --git a/plugins/git4idea/src/git4idea/branch/GitMergeOperation.java b/plugins/git4idea/src/git4idea/branch/GitMergeOperation.java
index b323bb2913e8..1a83aff9e779 100644
--- a/plugins/git4idea/src/git4idea/branch/GitMergeOperation.java
+++ b/plugins/git4idea/src/git4idea/branch/GitMergeOperation.java
@@ -47,6 +47,7 @@ import static git4idea.commands.GitMessageWithFilesDetector.Event.UNTRACKED_FILE
class GitMergeOperation extends GitBranchOperation {
private static final Logger LOG = Logger.getInstance(GitMergeOperation.class);
+ public static final String ROLLBACK_PROPOSAL = "You may rollback (reset to the commit before merging) not to let branches diverge.";
@NotNull private final ChangeListManager myChangeListManager;
@NotNull private final String myBranchToMerge;
@@ -287,7 +288,7 @@ class GitMergeOperation extends GitBranchOperation {
protected String getRollbackProposal() {
return "However merge has succeeded for the following " + repositories() + ":
" +
successfulRepositoriesJoined() +
- "
You may rollback (reset to the commit before merging) not to let branches diverge.";
+ "
" + ROLLBACK_PROPOSAL;
}
@NotNull
diff --git a/plugins/git4idea/tests/git4idea/branch/GitBranchOperationsTest.java b/plugins/git4idea/tests/git4idea/branch/GitBranchOperationsTest.java
index 659b07d4e708..958213bbd41f 100644
--- a/plugins/git4idea/tests/git4idea/branch/GitBranchOperationsTest.java
+++ b/plugins/git4idea/tests/git4idea/branch/GitBranchOperationsTest.java
@@ -220,16 +220,25 @@ public class GitBranchOperationsTest extends AbstractVcsTestCase {
@Test
public void checkout_with_untracked_files_overwritten_by_checkout_in_first_repo_should_show_notification() throws Exception {
+ test_untracked_files_overwritten_by_in_first_repo(true);
+ }
+
+ private void test_untracked_files_overwritten_by_in_first_repo(boolean checkout) throws Exception {
prepareUntrackedFilesAndBranchWithSameTrackedFiles(myUltimate);
branch(myCommunity, "feature");
branch(myContrib, "feature");
- doCheckout("feature", null);
- assertNotify(NotificationType.ERROR, UntrackedFilesNotifier.createUntrackedFilesOverwrittenDescription("checkout", false));
+ doCheckoutOrMerge(checkout, "feature");
+ String operation = checkout ? "checkout" : "merge";
+ assertNotify(NotificationType.ERROR, UntrackedFilesNotifier.createUntrackedFilesOverwrittenDescription(operation, false));
}
@Test
public void checkout_with_untracked_files_overwritten_by_checkout_in_second_repo_should_show_rollback_proposal_with_file_list() throws Exception {
+ test_checkout_with_untracked_files_overwritten_by_in_second_repo(true);
+ }
+
+ public void test_checkout_with_untracked_files_overwritten_by_in_second_repo(boolean checkout) throws Exception {
prepareUntrackedFilesAndBranchWithSameTrackedFiles(myCommunity);
branch(myUltimate, "feature");
branch(myContrib, "feature");
@@ -256,12 +265,16 @@ public class GitBranchOperationsTest extends AbstractVcsTestCase {
}
});
- doCheckout("feature", null);
+ doCheckoutOrMerge(checkout, "feature");
assertTrue(dialogShown.get());
}
@Test
public void checkout_with_local_changes_overwritten_by_checkout_should_show_smart_checkout_dialog() throws Exception {
+ test_operation_with_local_changes_overwritten_by_should_show_smart_checkout_dialog(true);
+ }
+
+ public void test_operation_with_local_changes_overwritten_by_should_show_smart_checkout_dialog(boolean checkout) throws Exception {
prepareLocalChangesAndBranchWithSameModifiedFilesWithoutConflicts(myUltimate);
branch(myCommunity, "feature");
branch(myContrib, "feature");
@@ -275,12 +288,22 @@ public class GitBranchOperationsTest extends AbstractVcsTestCase {
}
});
- doCheckout("feature", null);
+ doCheckoutOrMerge(checkout, "feature");
assertTrue(dialogShown.get());
}
@Test
public void agree_to_smart_checkout_should_smart_checkout() throws Exception {
+ prepare_agree_to_smart_operation(true);
+ assertBranch("feature");
+ for (GitRepository repository : myRepositories) {
+ refresh(repository);
+ assertBranch(repository, "feature");
+ assertEquals(read(repository, "local.txt"), "master\ninitial content\nfeature content\n");
+ }
+ }
+
+ public void prepare_agree_to_smart_operation(boolean checkout) throws Exception {
prepareLocalChangesAndBranchWithSameModifiedFilesWithoutConflicts(myUltimate);
prepareLocalChangesAndBranchWithSameModifiedFilesWithoutConflicts(myCommunity);
prepareLocalChangesAndBranchWithSameModifiedFilesWithoutConflicts(myContrib);
@@ -292,17 +315,15 @@ public class GitBranchOperationsTest extends AbstractVcsTestCase {
}
});
- doCheckout("feature", null);
- assertBranch("feature");
- for (GitRepository repository : myRepositories) {
- refresh(repository);
- assertBranch(repository, "feature");
- assertEquals(read(repository, "local.txt"), "master\ninitial content\nfeature content\n");
- }
+ doCheckoutOrMerge(checkout, "feature");
}
@Test
public void deny_to_smart_checkout_in_first_repo_should_show_notification() throws Exception {
+ test_deny_to_smart_operation_in_first_repo_should_show_notification(true);
+ }
+
+ public void test_deny_to_smart_operation_in_first_repo_should_show_notification(boolean checkout) throws Exception {
prepareLocalChangesAndBranchWithSameModifiedFilesWithoutConflicts(myUltimate);
branch(myCommunity, "feature");
branch(myContrib, "feature");
@@ -314,14 +335,19 @@ public class GitBranchOperationsTest extends AbstractVcsTestCase {
}
});
- doCheckout("feature", null);
- assertNotify(NotificationType.ERROR, "Couldn't checkout feature", stripHtmlAndBreaks("Local changes would be overwritten by checkout." +
- "You should stash or commit them."));
+ doCheckoutOrMerge(checkout, "feature");
+ String operation = checkout ? "checkout" : "merge";
+ assertNotify(NotificationType.ERROR, "Couldn't " + operation + " feature",
+ stripHtmlAndBreaks("Local changes would be overwritten by " + operation + "." + "You should stash or commit them."));
assertBranch("master");
}
@Test
public void deny_to_smart_checkout_in_second_repo_should_show_rollback_proposal() throws Exception {
+ test_deny_to_smart_operation_in_second_repo_should_show_rollback_proposal(true);
+ }
+
+ public void test_deny_to_smart_operation_in_second_repo_should_show_rollback_proposal(boolean checkout) throws Exception {
prepareLocalChangesAndBranchWithSameModifiedFilesWithoutConflicts(myCommunity);
branch(myUltimate, "feature");
branch(myContrib, "feature");
@@ -332,12 +358,16 @@ public class GitBranchOperationsTest extends AbstractVcsTestCase {
}
});
- doCheckout("feature", null);
- assertMessage("Couldn't checkout feature",
- "Local changes would be overwritten by checkout.
You should stash or commit them.
" +
- "However checkout has succeeded for the following repository:
" +
+ doCheckoutOrMerge(checkout, "feature");
+ String operationName = checkout ? "checkout" : "merge";
+ String rollbackProposal = checkout ?
+ String.format(GitCheckoutOperation.ROLLBACK_PROPOSAL_FORMAT, "master") :
+ GitMergeOperation.ROLLBACK_PROPOSAL;
+ assertMessage("Couldn't " + operationName + " feature",
+ "Local changes would be overwritten by " + operationName + ".
You should stash or commit them.
" +
+ "However " + operationName + " has succeeded for the following repository:
" +
myUltimate.getPresentableUrl() +
- "
You may rollback (checkout back to master) not to let branches diverge.",
+ "
" + rollbackProposal,
"Rollback", "Don't rollback");
}
@@ -540,6 +570,39 @@ public class GitBranchOperationsTest extends AbstractVcsTestCase {
assertFalse(tip(myUltimate).equals(ultimateTip));
}
+ @Test
+ public void merge_with_untracked_files_overwritten_by_merge_in_first_repo_should_show_notification() throws Exception {
+ test_untracked_files_overwritten_by_in_first_repo(false);
+ }
+
+ @Test
+ public void merge_with_untracked_files_overwritten_by_merge_in_second_repo_should_show_rollback_proposal_with_file_list() throws Exception {
+ test_checkout_with_untracked_files_overwritten_by_in_second_repo(false);
+ }
+
+ @Test
+ public void merge_with_local_changes_overwritten_by_merge_should_show_smart_checkout_dialog() throws Exception {
+ test_operation_with_local_changes_overwritten_by_should_show_smart_checkout_dialog(false);
+ }
+
+ @Test
+ public void agree_to_smart_merge_should_smart_merge() throws Exception {
+ prepare_agree_to_smart_operation(false);
+ for (GitRepository repository : myRepositories) {
+ assertEquals(read(repository, "local.txt"), "master\ninitial content\nfeature content\n");
+ }
+ }
+
+ @Test
+ public void deny_to_smart_merge_in_first_repo_should_show_notification() throws Exception {
+ test_deny_to_smart_operation_in_first_repo_should_show_notification(false);
+ }
+
+ @Test
+ public void deny_to_smart_merge_in_second_repo_should_show_rollback_proposal() throws Exception {
+ test_deny_to_smart_operation_in_second_repo_should_show_rollback_proposal(false);
+ }
+
private void registerNotFullyMergedDialog(final int answer) {
myDialogManager.registerDialogHandler(GitBranchIsNotFullyMergedDialog.class, new TestDialogHandler() {
@Override
@@ -573,6 +636,15 @@ public class GitBranchOperationsTest extends AbstractVcsTestCase {
}
}
+ private void doCheckoutOrMerge(boolean checkout, String branch) throws InvocationTargetException, NoSuchMethodException, IllegalAccessException {
+ if (checkout) {
+ doCheckout(branch, null);
+ }
+ else {
+ doMerge(branch);
+ }
+ }
+
private static String unmergedFilesErrorNotificationDescription(String operation)
throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
Method method = GitBranchOperation.class.getDeclaredMethod("unmergedFilesErrorNotificationDescription", String.class);
From 054f70493b319b2b659278d8c1ac2ac983291054 Mon Sep 17 00:00:00 2001
From: Kirill Likhodedov
Date: Wed, 8 Feb 2012 19:37:41 +0400
Subject: [PATCH 14/16] GitPreservingProcess: Don't load the changes if they
already have been loaded.
---
.../git4idea/util/GitPreservingProcess.java | 22 +++++++++++++------
1 file changed, 15 insertions(+), 7 deletions(-)
diff --git a/plugins/git4idea/src/git4idea/util/GitPreservingProcess.java b/plugins/git4idea/src/git4idea/util/GitPreservingProcess.java
index f778e761c140..4162719c18f5 100644
--- a/plugins/git4idea/src/git4idea/util/GitPreservingProcess.java
+++ b/plugins/git4idea/src/git4idea/util/GitPreservingProcess.java
@@ -57,6 +57,8 @@ public class GitPreservingProcess {
@NotNull private final String myStashMessage;
private GitStashChangesSaver mySaver;
+ private boolean myLoaded;
+ private final Object LOAD_LOCK = new Object();
public GitPreservingProcess(@NotNull Project project, @NotNull Collection repositories,
@NotNull String operationTitle, @NotNull String destinationName,
@@ -148,13 +150,19 @@ public class GitPreservingProcess {
}
public void load() {
- try {
- mySaver.load();
- }
- catch (VcsException e) {
- LOG.info("Couldn't load local changes", e);
- NotificationManager.getInstance(myProject).notifyError("Couldn't restore uncommitted changes",
- String.format("Tried to unstash uncommitted changes, but failed with error.
%s",join(e.getMessages())));
+ synchronized (LOAD_LOCK) {
+ if (myLoaded) {
+ return;
+ }
+ try {
+ mySaver.load();
+ myLoaded = true;
+ }
+ catch (VcsException e) {
+ LOG.info("Couldn't load local changes", e);
+ NotificationManager.getInstance(myProject).notifyError("Couldn't restore uncommitted changes",
+ String.format("Tried to unstash uncommitted changes, but failed with error.
%s",join(e.getMessages())));
+ }
}
}
From 2c51f02d36290e97b7d7ff209dba15c8094b61b6 Mon Sep 17 00:00:00 2001
From: Kirill Likhodedov
Date: Wed, 8 Feb 2012 19:56:14 +0400
Subject: [PATCH 15/16] GitMergeOperation: fix "already up-to-date" situation
detection
---
.../git4idea/src/git4idea/branch/GitMergeOperation.java | 8 ++++----
1 file changed, 4 insertions(+), 4 deletions(-)
diff --git a/plugins/git4idea/src/git4idea/branch/GitMergeOperation.java b/plugins/git4idea/src/git4idea/branch/GitMergeOperation.java
index 1a83aff9e779..fce48e4c0643 100644
--- a/plugins/git4idea/src/git4idea/branch/GitMergeOperation.java
+++ b/plugins/git4idea/src/git4idea/branch/GitMergeOperation.java
@@ -73,7 +73,7 @@ class GitMergeOperation extends GitBranchOperation {
@Override
protected void execute() {
boolean fatalErrorHappened = false;
- boolean notAlreadyUpToDate = false;
+ int alreadyUpToDateRepositories = 0;
while (hasMoreRepositories() && !fatalErrorHappened) {
final GitRepository repository = next();
@@ -90,8 +90,8 @@ class GitMergeOperation extends GitBranchOperation {
if (result.success()) {
refresh(repository);
markSuccessful(repository);
- if (!alreadyUpToDateDetector.hasHappened()) {
- notAlreadyUpToDate = true;
+ if (alreadyUpToDateDetector.hasHappened()) {
+ alreadyUpToDateRepositories += 1;
}
}
else if (unmergedFiles.hasHappened()) {
@@ -122,7 +122,7 @@ class GitMergeOperation extends GitBranchOperation {
boolean allConflictsResolved = resolveConflicts();
if (!fatalErrorHappened && allConflictsResolved) {
- if (notAlreadyUpToDate) {
+ if (alreadyUpToDateRepositories < getRepositories().size()) {
notifySuccess();
}
else {
From 26ef832e9b185ccfbfbb29a9034a3fa0f403ac12 Mon Sep 17 00:00:00 2001
From: Kirill Likhodedov
Date: Wed, 8 Feb 2012 21:18:39 +0400
Subject: [PATCH 16/16] capitalize text on buttons
---
.../src/git4idea/branch/GitSmartOperationDialog.java | 11 ++++++-----
1 file changed, 6 insertions(+), 5 deletions(-)
diff --git a/plugins/git4idea/src/git4idea/branch/GitSmartOperationDialog.java b/plugins/git4idea/src/git4idea/branch/GitSmartOperationDialog.java
index af3e986ea62f..0d8a187664e4 100644
--- a/plugins/git4idea/src/git4idea/branch/GitSmartOperationDialog.java
+++ b/plugins/git4idea/src/git4idea/branch/GitSmartOperationDialog.java
@@ -17,6 +17,7 @@ package git4idea.branch;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.ui.DialogWrapper;
+import com.intellij.openapi.util.text.StringUtil;
import com.intellij.openapi.vcs.changes.Change;
import com.intellij.openapi.vcs.changes.ui.ChangesBrowser;
import com.intellij.ui.IdeBorderFactory;
@@ -45,7 +46,7 @@ class GitSmartOperationDialog extends DialogWrapper {
private final Project myProject;
private final List myChanges;
- @NotNull private final String myOperationTitle;
+ @NotNull private final String myCapitalizedOperationTitle;
private final boolean myForceButton;
/**
@@ -70,10 +71,10 @@ class GitSmartOperationDialog extends DialogWrapper {
super(project);
myProject = project;
myChanges = changes;
- myOperationTitle = operationTitle;
+ myCapitalizedOperationTitle = StringUtil.capitalize(operationTitle);
myForceButton = forceButton;
- setOKButtonText("Smart " + operationTitle);
- setCancelButtonText("Don't " + operationTitle);
+ setOKButtonText("Smart " + myCapitalizedOperationTitle);
+ setCancelButtonText("Don't " + myCapitalizedOperationTitle);
getCancelAction().putValue(FOCUSED_ACTION, Boolean.TRUE);
init();
}
@@ -81,7 +82,7 @@ class GitSmartOperationDialog extends DialogWrapper {
@Override
protected Action[] createLeftSideActions() {
if (myForceButton) {
- return new Action[] {new ForceCheckoutAction(myOperationTitle) };
+ return new Action[] {new ForceCheckoutAction(myCapitalizedOperationTitle) };
}
return new Action[0];
}