vcs: use custom MergeTool for patch conflicts without base revision
authorAleksey Pivovarov <AMPivovarov@gmail.com>
Tue, 25 Aug 2015 15:55:58 +0000 (18:55 +0300)
committerAleksey Pivovarov <AMPivovarov@gmail.com>
Mon, 31 Aug 2015 09:50:31 +0000 (12:50 +0300)
platform/diff-api/src/com/intellij/diff/DiffContentFactory.java
platform/diff-impl/src/com/intellij/diff/DiffContentFactoryImpl.java
platform/platform-resources/src/META-INF/VcsExtensions.xml
platform/testRunner/src/com/intellij/execution/testframework/actions/TestDiffRequestProcessor.java
platform/vcs-impl/src/com/intellij/openapi/vcs/changes/patch/ApplyPatchAction.java
platform/vcs-impl/src/com/intellij/openapi/vcs/changes/patch/ApplyPatchMergeRequest.java [new file with mode: 0644]
platform/vcs-impl/src/com/intellij/openapi/vcs/changes/patch/ApplyPatchMergeTool.java [new file with mode: 0644]

index bd41446c1c1f6ecab5123a7e1839926a21e20b37..8dd004c11532530efef6ff110a701bd656a63d6a 100644 (file)
@@ -50,6 +50,9 @@ public abstract class DiffContentFactory {
   @NotNull
   public abstract DocumentContent create(@NotNull String text, @Nullable FileType type, boolean respectLineSeparators);
 
+  @NotNull
+  public abstract DocumentContent create(@NotNull String text, @Nullable VirtualFile highlightFile);
+
   @NotNull
   public abstract DocumentContent create(@Nullable Project project, @NotNull Document document);
 
index 653017dcf5689ad0121b8a988b47bf109e6a1588..d6f207e2ad93bdb70a671f926754da3792482df3 100644 (file)
@@ -58,7 +58,7 @@ public class DiffContentFactoryImpl extends DiffContentFactory {
   @Override
   @NotNull
   public DocumentContent create(@NotNull String text) {
-    return create(text, null);
+    return create(text, (FileType)null);
   }
 
   @Override
@@ -73,6 +73,11 @@ public class DiffContentFactoryImpl extends DiffContentFactory {
     return createImpl(text, type, null, null, respectLineSeparators, true);
   }
 
+  @NotNull
+  public DocumentContent create(@NotNull String text, @Nullable VirtualFile highlightFile) {
+    return createImpl(text, highlightFile != null ? highlightFile.getFileType() : null, highlightFile, null, true, true);
+  }
+
   @Override
   @NotNull
   public DocumentContent create(@Nullable Project project, @NotNull Document document) {
index a76f7f43f371d22e285e92d3aa2176c20cf24814..d07e6da2c25211073bc71da0810b234bdf0ef9bb 100644 (file)
@@ -26,6 +26,8 @@
     <projectService serviceInterface="com.intellij.openapi.vcs.CodeSmellDetector"
                     serviceImplementation="com.intellij.openapi.vcs.impl.CodeSmellDetectorImpl"/>
 
+    <diff.merge.MergeTool implementation="com.intellij.openapi.vcs.changes.patch.ApplyPatchMergeTool"/>
+
     <selectInTarget implementation="com.intellij.openapi.vcs.changes.SelectInChangesViewTarget"/>
 
     <search.topHitProvider implementation="com.intellij.openapi.vcs.configurable.VcsOptionsTopHitProvider"/>
index aff16950c712e85670c35eb5820741941428b83a..9e8aeb14e34e7078616c1ff459669cea76e28825 100644 (file)
@@ -86,7 +86,7 @@ public class TestDiffRequestProcessor extends DiffRequestProcessor {
     }
     else {
       title = ExecutionBundle.message(titleKey);
-      content = DiffContentFactory.getInstance().create(contentString, null);
+      content = DiffContentFactory.getInstance().create(contentString);
     }
     return Pair.create(title, content);
   }
index 3a2a8ad5aefb8e7e4f6a716f482fd6a6791bd409..8c77f259577e03dd7fa447e6bc622cdfb5023584 100644 (file)
  */
 package com.intellij.openapi.vcs.changes.patch;
 
-import com.intellij.diff.*;
+import com.intellij.diff.DiffContentFactory;
+import com.intellij.diff.DiffManager;
+import com.intellij.diff.DiffRequestFactory;
+import com.intellij.diff.InvalidDiffRequestException;
 import com.intellij.diff.chains.DiffRequestProducerException;
 import com.intellij.diff.contents.DiffContent;
 import com.intellij.diff.contents.DocumentContent;
@@ -47,8 +50,6 @@ import com.intellij.openapi.fileEditor.FileDocumentManager;
 import com.intellij.openapi.fileTypes.StdFileTypes;
 import com.intellij.openapi.project.DumbAwareAction;
 import com.intellij.openapi.project.Project;
-import com.intellij.openapi.ui.WindowWrapper;
-import com.intellij.openapi.util.BooleanGetter;
 import com.intellij.openapi.util.Computable;
 import com.intellij.openapi.util.Getter;
 import com.intellij.openapi.util.Ref;
@@ -59,7 +60,6 @@ import com.intellij.openapi.vcs.changes.ChangeListManager;
 import com.intellij.openapi.vcs.changes.CommitContext;
 import com.intellij.openapi.vfs.LocalFileSystem;
 import com.intellij.openapi.vfs.VirtualFile;
-import com.intellij.ui.EditorNotificationPanel;
 import com.intellij.util.Consumer;
 import com.intellij.util.ObjectUtils;
 import com.intellij.util.containers.ContainerUtil;
@@ -254,52 +254,24 @@ public class ApplyPatchAction extends DumbAwareAction {
     final Document document = FileDocumentManager.getInstance().getDocument(file);
     if (texts.getLocal() == null || document == null) return ApplyPatchStatus.FAILURE;
 
-    final CharSequence oldContent = document.getImmutableCharSequence();
-    ApplicationManager.getApplication().runWriteAction(new Runnable() {
-      @Override
-      public void run() {
-        document.setText(texts.getPatched());
-      }
-    });
-
     final String fullPath = file.getParent() == null ? file.getPath() : file.getParent().getPath();
     final String windowTitle = "Result Of Patch Apply To " + file.getName() + " (" + fullPath + ")";
-    final List<String> titles = ContainerUtil.list(VcsBundle.message("diff.title.local"), "Patched (with problems)");
-
-    final DiffContentFactory contentFactory = DiffContentFactory.getInstance();
-    final DocumentContent originalContent = contentFactory.create(texts.getLocal().toString(), file.getFileType());
-    final DiffContent mergedContent = contentFactory.create(project, document);
-
-    final List<DiffContent> contents = ContainerUtil.list(originalContent, mergedContent);
 
-    final DiffRequest request = new SimpleDiffRequest(windowTitle, contents, titles);
-    DiffUtil.addNotification(new DiffIsApproximateNotification(), request);
-
-    final DiffDialogHints dialogHints = new DiffDialogHints(WindowWrapper.Mode.MODAL);
-    dialogHints.setCancelAction(new BooleanGetter() {
-      @Override
-      public boolean get() {
-        ApplicationManager.getApplication().runWriteAction(new Runnable() {
-          @Override
-          public void run() {
-            document.setText(oldContent);
-            FileDocumentManager.getInstance().saveDocument(document);
-          }
-        });
-        return true;
-      }
-    });
-    dialogHints.setOkAction(new BooleanGetter() {
+    final Ref<Boolean> successRef = new Ref<Boolean>();
+    Consumer<MergeResult> callback = new Consumer<MergeResult>() {
       @Override
-      public boolean get() {
+      public void consume(MergeResult result) {
         FileDocumentManager.getInstance().saveDocument(document);
-        return true;
+        successRef.set(true);
       }
-    });
+    };
 
-    DiffManager.getInstance().showDiff(project, request, dialogHints);
+    MergeRequest request = new ApplyPatchMergeRequest(project, document, file, texts.getLocal().toString(), texts.getPatched(), windowTitle,
+                                                      VcsBundle.message("diff.title.local"), "Patched (with problems)", callback);
 
-    return ApplyPatchStatus.SUCCESS;
+    DiffManager.getInstance().showMerge(project, request);
+
+    return successRef.get() == Boolean.TRUE ? ApplyPatchStatus.SUCCESS : ApplyPatchStatus.FAILURE;
   }
 
   @NotNull
@@ -322,14 +294,8 @@ public class ApplyPatchAction extends DumbAwareAction {
     final List<DiffContent> contents = ContainerUtil.list(localContent, mergedContent);
 
     final DiffRequest request = new SimpleDiffRequest(windowTitle, contents, titles);
-    DiffUtil.addNotification(new DiffIsApproximateNotification(), request);
+    DiffUtil.addNotification(new ApplyPatchMergeTool.DiffIsApproximateNotification(), request);
 
     return request;
   }
-
-  private static class DiffIsApproximateNotification extends EditorNotificationPanel {
-    public DiffIsApproximateNotification() {
-      myLabel.setText("<html>Couldn't find context for patch. Some fragments were applied at the best possible place. <b>Please check carefully.</b></html>");
-    }
-  }
 }
diff --git a/platform/vcs-impl/src/com/intellij/openapi/vcs/changes/patch/ApplyPatchMergeRequest.java b/platform/vcs-impl/src/com/intellij/openapi/vcs/changes/patch/ApplyPatchMergeRequest.java
new file mode 100644 (file)
index 0000000..63cb798
--- /dev/null
@@ -0,0 +1,138 @@
+/*
+ * Copyright 2000-2015 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 com.intellij.openapi.vcs.changes.patch;
+
+import com.intellij.diff.merge.MergeRequest;
+import com.intellij.diff.merge.MergeResult;
+import com.intellij.openapi.application.ApplicationManager;
+import com.intellij.openapi.command.WriteCommandAction;
+import com.intellij.openapi.editor.Document;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.util.Computable;
+import com.intellij.openapi.vfs.VirtualFile;
+import com.intellij.util.Consumer;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+public class ApplyPatchMergeRequest extends MergeRequest {
+  @Nullable private final Project myProject;
+  @NotNull private final Document myDocument;
+
+  @NotNull private final CharSequence myOriginalContent;
+  @NotNull private final String myLocalContent;
+  @NotNull private final String myPatchedContent;
+
+  @NotNull private final String myWindowTitle;
+  @NotNull private final String myLocalTitle;
+  @NotNull private final String myPatchedTitle;
+
+  @Nullable private final Consumer<MergeResult> myCallback;
+
+  public ApplyPatchMergeRequest(@Nullable Project project,
+                                @NotNull Document document,
+                                @NotNull String localContent,
+                                @NotNull String patchedContent,
+                                @NotNull String windowTitle,
+                                @NotNull String localTitle,
+                                @NotNull String patchedTitle,
+                                @Nullable Consumer<MergeResult> callback) {
+    myProject = project;
+    myDocument = document;
+
+    myOriginalContent = ApplicationManager.getApplication().runReadAction(new Computable<CharSequence>() {
+      @Override
+      public CharSequence compute() {
+        return myDocument.getImmutableCharSequence();
+      }
+    });
+    myLocalContent = localContent;
+    myPatchedContent = patchedContent;
+
+    myWindowTitle = windowTitle;
+    myLocalTitle = localTitle;
+    myPatchedTitle = patchedTitle;
+
+    myCallback = callback;
+  }
+
+  @Nullable
+  public Project getProject() {
+    return myProject;
+  }
+
+  @NotNull
+  public Document getDocument() {
+    return myDocument;
+  }
+
+  @NotNull
+  public String getLocalContent() {
+    return myLocalContent;
+  }
+
+  @NotNull
+  public String getPatchedContent() {
+    return myPatchedContent;
+  }
+
+  @Nullable
+  @Override
+  public String getTitle() {
+    return myWindowTitle;
+  }
+
+  @NotNull
+  public String getLocalTitle() {
+    return myLocalTitle;
+  }
+
+  @NotNull
+  public String getPatchedTitle() {
+    return myPatchedTitle;
+  }
+
+  @Override
+  public void applyResult(@NotNull MergeResult result) {
+    final CharSequence applyContent;
+    switch (result) {
+      case CANCEL:
+        applyContent = myOriginalContent;
+        break;
+      case LEFT:
+        applyContent = myLocalContent;
+        break;
+      case RIGHT:
+        applyContent = myPatchedContent;
+        break;
+      case RESOLVED:
+        applyContent = null;
+        break;
+      default:
+        throw new IllegalArgumentException(result.name());
+    }
+
+    if (applyContent != null) {
+      new WriteCommandAction.Simple(myProject) {
+        @Override
+        protected void run() throws Throwable {
+          myDocument.setText(applyContent);
+        }
+      }.execute();
+    }
+
+    if (myCallback != null) myCallback.consume(result);
+  }
+}
diff --git a/platform/vcs-impl/src/com/intellij/openapi/vcs/changes/patch/ApplyPatchMergeTool.java b/platform/vcs-impl/src/com/intellij/openapi/vcs/changes/patch/ApplyPatchMergeTool.java
new file mode 100644 (file)
index 0000000..2e4e0ce
--- /dev/null
@@ -0,0 +1,161 @@
+/*
+ * Copyright 2000-2015 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 com.intellij.openapi.vcs.changes.patch;
+
+import com.intellij.diff.DiffContentFactory;
+import com.intellij.diff.FrameDiffTool;
+import com.intellij.diff.contents.DocumentContent;
+import com.intellij.diff.merge.*;
+import com.intellij.diff.requests.SimpleDiffRequest;
+import com.intellij.diff.tools.simple.SimpleDiffViewer;
+import com.intellij.diff.util.DiffUtil;
+import com.intellij.openapi.command.undo.DocumentReference;
+import com.intellij.openapi.command.undo.DocumentReferenceManager;
+import com.intellij.openapi.command.undo.UndoManager;
+import com.intellij.openapi.diff.DiffBundle;
+import com.intellij.openapi.editor.Document;
+import com.intellij.openapi.fileEditor.FileDocumentManager;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.ui.Messages;
+import com.intellij.openapi.util.BooleanGetter;
+import com.intellij.openapi.util.Disposer;
+import com.intellij.openapi.vfs.VirtualFile;
+import com.intellij.ui.EditorNotificationPanel;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+import javax.swing.*;
+import java.awt.event.ActionEvent;
+
+public class ApplyPatchMergeTool implements MergeTool {
+  @NotNull
+  @Override
+  public MergeViewer createComponent(@NotNull MergeContext context, @NotNull MergeRequest request) {
+    return new MyViewer(context, (ApplyPatchMergeRequest)request);
+  }
+
+  @Override
+  public boolean canShow(@NotNull MergeContext context, @NotNull MergeRequest request) {
+    return request instanceof ApplyPatchMergeRequest;
+  }
+
+  private static class MyViewer implements MergeViewer {
+    @NotNull private final MergeContext myMergeContext;
+    @NotNull private final ApplyPatchMergeRequest myMergeRequest;
+
+    @NotNull private final SimpleDiffViewer myViewer;
+
+    public MyViewer(@NotNull MergeContext context, @NotNull ApplyPatchMergeRequest request) {
+      myMergeContext = context;
+      myMergeRequest = request;
+
+      MergeUtil.ProxyDiffContext diffContext = new MergeUtil.ProxyDiffContext(myMergeContext);
+
+      VirtualFile file = FileDocumentManager.getInstance().getFile(myMergeRequest.getDocument());
+      final DiffContentFactory contentFactory = DiffContentFactory.getInstance();
+      final DocumentContent localContent = contentFactory.create(myMergeRequest.getLocalContent(), file);
+      final DocumentContent mergedContent = contentFactory.create(myMergeRequest.getProject(), myMergeRequest.getDocument());
+
+      SimpleDiffRequest diffRequest = new SimpleDiffRequest(myMergeRequest.getTitle(), localContent, mergedContent,
+                                                            myMergeRequest.getLocalTitle(), myMergeRequest.getPatchedTitle());
+      DiffUtil.addNotification(new DiffIsApproximateNotification(), diffRequest);
+
+      myViewer = new SimpleDiffViewer(diffContext, diffRequest);
+    }
+
+    @NotNull
+    @Override
+    public JComponent getComponent() {
+      return myViewer.getComponent();
+    }
+
+    @Nullable
+    @Override
+    public JComponent getPreferredFocusedComponent() {
+      return myViewer.getPreferredFocusedComponent();
+    }
+
+    @Override
+    public ToolbarComponents init() {
+      final Project project = myMergeContext.getProject();
+      final Document document = myMergeRequest.getDocument();
+
+      DiffUtil.executeWriteCommand(document, project, "Init merge content", new Runnable() {
+        @Override
+        public void run() {
+          document.setText(myMergeRequest.getPatchedContent());
+
+          UndoManager undoManager = project != null ? UndoManager.getInstance(project) : UndoManager.getGlobalInstance();
+          if (undoManager != null) {
+            DocumentReference ref = DocumentReferenceManager.getInstance().create(document);
+            undoManager.nonundoableActionPerformed(ref, false);
+          }
+        }
+      });
+
+      ToolbarComponents components = new ToolbarComponents();
+
+      FrameDiffTool.ToolbarComponents init = myViewer.init();
+      components.statusPanel = init.statusPanel;
+      components.toolbarActions = init.toolbarActions;
+
+      components.closeHandler = new BooleanGetter() {
+        @Override
+        public boolean get() {
+          return Messages.showYesNoDialog(getComponent().getRootPane(),
+                                          DiffBundle.message("merge.dialog.exit.without.applying.changes.confirmation.message"),
+                                          DiffBundle.message("cancel.visual.merge.dialog.title"), Messages.getQuestionIcon()) ==
+                 Messages.YES;
+        }
+      };
+      return components;
+    }
+
+    @Nullable
+    @Override
+    public Action getResolveAction(@NotNull final MergeResult result) {
+      if (result == MergeResult.LEFT || result == MergeResult.RIGHT) return null;
+
+      String caption = MergeUtil.getResolveActionTitle(result, myMergeRequest, myMergeContext);
+      return new AbstractAction(caption) {
+        @Override
+        public void actionPerformed(ActionEvent e) {
+          if (result == MergeResult.CANCEL) {
+            if (Messages.showYesNoDialog(getComponent().getRootPane(),
+                                         DiffBundle.message("merge.dialog.exit.without.applying.changes.confirmation.message"),
+                                         DiffBundle.message("cancel.visual.merge.dialog.title"), Messages.getQuestionIcon()) !=
+                Messages.YES) {
+              return;
+            }
+          }
+          myMergeContext.finishMerge(result);
+        }
+      };
+    }
+
+    @Override
+    public void dispose() {
+      Disposer.dispose(myViewer);
+    }
+  }
+
+  public static class DiffIsApproximateNotification extends EditorNotificationPanel {
+    public DiffIsApproximateNotification() {
+      myLabel.setText("<html>Couldn't find context for patch. Some fragments were applied at the best possible place. " +
+                      "<b>Please check carefully.</b></html>");
+    }
+  }
+}