diff --git a/platform/vcs-impl/src/com/intellij/openapi/vcs/changes/ui/ChangeListViewerDialog.java b/platform/vcs-impl/src/com/intellij/openapi/vcs/changes/ui/ChangeListViewerDialog.java index 7e4a12be6d3e..c40f1ed7950e 100644 --- a/platform/vcs-impl/src/com/intellij/openapi/vcs/changes/ui/ChangeListViewerDialog.java +++ b/platform/vcs-impl/src/com/intellij/openapi/vcs/changes/ui/ChangeListViewerDialog.java @@ -41,6 +41,7 @@ import com.intellij.ui.SeparatorFactory; import com.intellij.util.NotNullFunction; import com.intellij.util.ui.UIUtil; import org.jetbrains.annotations.NonNls; +import org.jetbrains.annotations.Nullable; import javax.swing.*; import java.awt.*; @@ -157,6 +158,13 @@ public class ChangeListViewerDialog extends DialogWrapper implements DataProvide } mainPanel.add(splitter, BorderLayout.CENTER); + final String description = getDescription(); + if (description != null) { + JPanel descPanel = new JPanel(); + descPanel.add(new JLabel("" + description + "")); + descPanel.setBorder(BorderFactory.createEtchedBorder()); + mainPanel.add(descPanel, BorderLayout.NORTH); + } return mainPanel; } @@ -175,4 +183,11 @@ public class ChangeListViewerDialog extends DialogWrapper implements DataProvide public JComponent getPreferredFocusedComponent() { return myChangesBrowser; } + + /** + * @return description that is added to the top of this dialog. May be null - then no description is shown. + */ + protected @Nullable String getDescription() { + return null; + } } diff --git a/plugins/git4idea/src/git4idea/ui/GitUIUtil.java b/plugins/git4idea/src/git4idea/ui/GitUIUtil.java index 5cca2c3b96f6..b9494a38f0e9 100644 --- a/plugins/git4idea/src/git4idea/ui/GitUIUtil.java +++ b/plugins/git4idea/src/git4idea/ui/GitUIUtil.java @@ -37,9 +37,7 @@ import org.jetbrains.annotations.Nullable; import javax.swing.*; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; -import java.util.Collection; -import java.util.Collections; -import java.util.List; +import java.util.*; /** * Utilities for git plugin user interface @@ -55,15 +53,28 @@ public class GitUIUtil { */ private GitUIUtil() { } - public static void notifyMessage(Project project, String title, String description, NotificationType type, boolean important, @Nullable Collection errors) { + public static void notifyMessages(Project project, String title, String description, NotificationType type, boolean important, @Nullable Collection messages) { String desc = description.replace("\n", "
"); - if (errors != null && !errors.isEmpty()) { - desc += "
" + stringifyErrors(errors); + if (messages != null && !messages.isEmpty()) { + desc += "
" + StringUtil.join(messages, "
"); } String id = important ? GitVcs.IMPORTANT_ERROR_NOTIFICATION : GitVcs.NOTIFICATION_GROUP_ID; Notifications.Bus.notify(new Notification(id, title, desc, type), project); } + public static void notifyMessage(Project project, String title, String description, NotificationType type, boolean important, @Nullable Collection errors) { + Collection errorMessages; + if (errors == null) { + errorMessages = null; + } else { + errorMessages = new HashSet(errors.size()); + for (VcsException error : errors) { + errorMessages.addAll(Arrays.asList(error.getMessages())); + } + } + notifyMessages(project, title, description, type, important, errorMessages); + } + public static void notifyError(Project project, String title, String description, boolean important, @Nullable VcsException error) { notifyMessage(project, title, description, NotificationType.ERROR, important, Collections.singleton(error)); } diff --git a/plugins/git4idea/src/git4idea/update/GitMergeUpdater.java b/plugins/git4idea/src/git4idea/update/GitMergeUpdater.java index ad456f7c0606..55ae323dfa06 100644 --- a/plugins/git4idea/src/git4idea/update/GitMergeUpdater.java +++ b/plugins/git4idea/src/git4idea/update/GitMergeUpdater.java @@ -21,9 +21,16 @@ import com.intellij.openapi.project.Project; import com.intellij.openapi.util.Key; import com.intellij.openapi.util.text.StringUtil; import com.intellij.openapi.vcs.FilePath; +import com.intellij.openapi.vcs.FilePathImpl; 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.ContentRevision; +import com.intellij.openapi.vcs.changes.LocalChangeList; +import com.intellij.openapi.vcs.changes.ui.ChangeListViewerDialog; import com.intellij.openapi.vcs.update.UpdatedFiles; import com.intellij.openapi.vfs.VirtualFile; +import com.intellij.util.ui.UIUtil; import git4idea.GitUtil; import git4idea.branch.GitBranchPair; import git4idea.commands.*; @@ -32,9 +39,8 @@ import git4idea.merge.GitMerger; import git4idea.ui.GitUIUtil; import org.jetbrains.annotations.NotNull; -import java.util.Collection; -import java.util.Collections; -import java.util.HashSet; +import java.io.File; +import java.util.*; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicReference; @@ -45,12 +51,7 @@ public class GitMergeUpdater extends GitUpdater { private static final Logger LOG = Logger.getInstance(GitMergeUpdater.class); private final GitUpdateProcess myUpdateProcess; - - private enum MergeError { - CONFLICT, - LOCAL_CHANGES, - OTHER - } + private final ChangeListManager myChangeListManager; public GitMergeUpdater(Project project, VirtualFile root, @@ -59,23 +60,18 @@ public class GitMergeUpdater extends GitUpdater { UpdatedFiles updatedFiles) { super(project, root, progressIndicator, updatedFiles); myUpdateProcess = gitUpdateProcess; + myChangeListManager = ChangeListManager.getInstance(myProject); } @Override protected GitUpdateResult doUpdate() { final GitMerger merger = new GitMerger(myProject); - final GitLineHandler mergeHandler = makeMergeHandler(myRoot); - final AtomicReference mergeError = new AtomicReference(MergeError.OTHER); - mergeHandler.addLineListener(new GitLineHandlerAdapter() { - @Override - public void onLineAvailable(String line, Key outputType) { - if (line.contains("Automatic merge failed; fix conflicts and then commit the result")) { - mergeError.set(MergeError.CONFLICT); - } else if (line.contains("Please, commit your changes or stash them before you can merge")) { - mergeError.set(MergeError.LOCAL_CHANGES); - } - } - }); + final GitLineHandler mergeHandler = new GitLineHandler(myProject, myRoot, GitCommand.MERGE); + mergeHandler.addParameters("--no-stat", "-v"); + mergeHandler.addParameters(myUpdateProcess.getTrackedBranches().get(myRoot).getTracked().getName()); + + final MergeLineListener mergeLineListener = new MergeLineListener(); + mergeHandler.addLineListener(mergeLineListener); final GitTask mergeTask = new GitTask(myProject, mergeHandler, "git merge"); mergeTask.setExecuteResultInAwt(false); @@ -92,7 +88,8 @@ public class GitMergeUpdater extends GitUpdater { } @Override protected void onFailure() { - final MergeError error = mergeError.get(); + final MergeError error = mergeLineListener.getMergeError(); + if (error == MergeError.CONFLICT) { final boolean allMerged = new GitMergeConflictResolver(myProject, true, "Merge conflicts detected. Resolve them before continuing update.", @@ -108,6 +105,22 @@ public class GitMergeUpdater extends GitUpdater { } }.mergeFiles(Collections.singleton(myRoot)); updateResult.set(allMerged ? GitUpdateResult.SUCCESS : GitUpdateResult.INCOMPLETE); + } + else if (error == MergeError.LOCAL_CHANGES) { + final List paths = getFilesOverwrittenByMerge(mergeLineListener.getOutput()); + final Collection changes = getLocalChangesFilteredByFiles(paths); + final ChangeListViewerDialog dialog = new ChangeListViewerDialog(myProject, changes, false) { + @Override protected String getDescription() { + return "Your local changes to the following files would be overwritten by merge.
" + + "Please, commit your changes or stash them before you can merge."; + } + }; + UIUtil.invokeAndWaitIfNeeded(new Runnable() { + @Override public void run() { + dialog.show(); + } + }); + updateResult.set(GitUpdateResult.ERROR); } else { GitUIUtil.notifyImportantError(myProject, "Error merging", GitUIUtil.stringifyErrors(mergeHandler.errors())); updateResult.set(GitUpdateResult.ERROR); @@ -203,11 +216,73 @@ public class GitMergeUpdater extends GitUpdater { } } - private GitLineHandler makeMergeHandler(VirtualFile root) { - GitLineHandler h = new GitLineHandler(myProject, root, GitCommand.PULL); - h.addParameters("--no-rebase"); - h.addParameters("--no-stat"); - h.addParameters("-v"); - return h; + private List getFilesOverwrittenByMerge(List mergeOutput) { + final List paths = new ArrayList(); + for (String line : mergeOutput) { + if (StringUtil.isEmptyOrSpaces(line)) { + continue; + } + if (line.contains("Please, commit your changes or stash them before you can merge")) { + break; + } + line = line.trim(); + + final String path; + try { + path = myRoot.getPath() + "/" + GitUtil.unescapePath(line); + final File file = new File(path); + if (file.exists()) { + paths.add(new FilePathImpl(file, false)); + } + } catch (VcsException e) { // just continue + } + } + return paths; + } + + private Collection getLocalChangesFilteredByFiles(List paths) { + final Collection changes = new HashSet(); + for(LocalChangeList list : myChangeListManager.getChangeLists()) { + for (Change change : list.getChanges()) { + final ContentRevision afterRevision = change.getAfterRevision(); + final ContentRevision beforeRevision = change.getBeforeRevision(); + if ((afterRevision != null && paths.contains(afterRevision.getFile())) || (beforeRevision != null && paths.contains(beforeRevision.getFile()))) { + changes.add(change); + } + } + } + return changes; + } + + private enum MergeError { + CONFLICT, + LOCAL_CHANGES, + OTHER + } + + private static class MergeLineListener extends GitLineHandlerAdapter { + private MergeError myMergeError; + private List myOutput = new ArrayList(); + private boolean myLocalChangesError = false; + + @Override + public void onLineAvailable(String line, Key outputType) { + if (myLocalChangesError) { + myOutput.add(line); + } else if (line.contains("Automatic merge failed; fix conflicts and then commit the result")) { + myMergeError = MergeError.CONFLICT; + } else if (line.contains("Your local changes to the following files would be overwritten by merge")) { + myMergeError = MergeError.LOCAL_CHANGES; + myLocalChangesError = true; + } + } + + public MergeError getMergeError() { + return myMergeError; + } + + public List getOutput() { + return myOutput; + } } }