vcs: use custom MergeTool for patch conflicts without base revision

This commit is contained in:
Aleksey Pivovarov
2015-08-31 12:50:31 +03:00
parent 597507f2d7
commit 0458527b1c
7 changed files with 326 additions and 51 deletions
@@ -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);
@@ -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) {
@@ -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"/>
@@ -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);
}
@@ -22,7 +22,10 @@
*/
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() {
final Ref<Boolean> successRef = new Ref<Boolean>();
Consumer<MergeResult> callback = new Consumer<MergeResult>() {
@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() {
@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>");
}
}
}
@@ -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);
}
}
@@ -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>");
}
}
}