blob: f7fbe4c9980d644e4cd553ba5a1bf78167ece782 [file] [log] [blame]
/*
* 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.
*/
/*
* Created by IntelliJ IDEA.
* User: yole
* Date: 17.11.2006
* Time: 17:08:11
*/
package com.intellij.openapi.vcs.changes.patch;
import com.intellij.openapi.actionSystem.ActionPlaces;
import com.intellij.openapi.actionSystem.AnActionEvent;
import com.intellij.openapi.actionSystem.CommonDataKeys;
import com.intellij.openapi.actionSystem.PlatformDataKeys;
import com.intellij.openapi.application.Application;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.diff.*;
import com.intellij.openapi.diff.impl.patch.*;
import com.intellij.openapi.diff.impl.patch.apply.ApplyFilePatch;
import com.intellij.openapi.diff.impl.patch.apply.ApplyFilePatchBase;
import com.intellij.openapi.editor.Document;
import com.intellij.openapi.editor.impl.DocumentImpl;
import com.intellij.openapi.extensions.Extensions;
import com.intellij.openapi.fileChooser.FileChooser;
import com.intellij.openapi.fileChooser.FileChooserDescriptor;
import com.intellij.openapi.fileEditor.FileDocumentManager;
import com.intellij.openapi.fileEditor.impl.LoadTextUtil;
import com.intellij.openapi.fileTypes.StdFileTypes;
import com.intellij.openapi.project.DumbAwareAction;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.ui.DialogWrapper;
import com.intellij.openapi.ui.Messages;
import com.intellij.openapi.util.Computable;
import com.intellij.openapi.util.Getter;
import com.intellij.openapi.util.io.FileUtil;
import com.intellij.openapi.vcs.FilePathImpl;
import com.intellij.openapi.vcs.VcsApplicationSettings;
import com.intellij.openapi.vcs.VcsBundle;
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.VfsUtil;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.util.Consumer;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import javax.swing.*;
import java.io.File;
import java.io.IOException;
import java.util.Collections;
import java.util.List;
public class ApplyPatchAction extends DumbAwareAction {
private static final Logger LOG = Logger.getInstance("#com.intellij.openapi.vcs.changes.patch.ApplyPatchAction");
public void actionPerformed(AnActionEvent e) {
final Project project = e.getData(CommonDataKeys.PROJECT);
if (project == null) return;
if (ChangeListManager.getInstance(project).isFreezedWithNotification("Can not apply patch now")) return;
if (e.getPlace().equals(ActionPlaces.PROJECT_VIEW_POPUP)) {
VirtualFile vFile = e.getData(CommonDataKeys.VIRTUAL_FILE);
if (vFile != null && vFile.getFileType() == StdFileTypes.PATCH) {
showApplyPatch(project, vFile);
return;
}
}
showApplyPatch(project, null);
}
public static void showApplyPatch(final Project project, final VirtualFile file) {
FileDocumentManager.getInstance().saveAllDocuments();
if (file != null) {
final ApplyPatchDifferentiatedDialog dialog = new ApplyPatchDifferentiatedDialog(project, new ApplyPatchDefaultExecutor(project),
Collections.<ApplyPatchExecutor>singletonList(new ImportToShelfExecutor(project)), ApplyPatchMode.APPLY, file);
dialog.show();
}
else {
final FileChooserDescriptor descriptor = ApplyPatchDifferentiatedDialog.createSelectPatchDescriptor();
final VcsApplicationSettings settings = VcsApplicationSettings.getInstance();
final VirtualFile toSelect = settings.PATCH_STORAGE_LOCATION == null ? null :
LocalFileSystem.getInstance().refreshAndFindFileByIoFile(new File(settings.PATCH_STORAGE_LOCATION));
FileChooser.chooseFiles(descriptor, project, toSelect, new Consumer<List<VirtualFile>>() {
@Override
public void consume(List<VirtualFile> files) {
if (files.size() != 1) return;
final VirtualFile parent = files.get(0).getParent();
if (parent != null) {
settings.PATCH_STORAGE_LOCATION = FileUtil.toSystemDependentName(parent.getPath());
}
new ApplyPatchDifferentiatedDialog(
project, new ApplyPatchDefaultExecutor(project),
Collections.<ApplyPatchExecutor>singletonList(new ImportToShelfExecutor(project)), ApplyPatchMode.APPLY, files.get(0)
).show();
}
});
}
}
public static void applySkipDirs(final List<FilePatch> patches, final int skipDirs) {
if (skipDirs < 1) {
return;
}
for (FilePatch patch : patches) {
patch.setBeforeName(skipN(patch.getBeforeName(), skipDirs));
patch.setAfterName(skipN(patch.getAfterName(), skipDirs));
}
}
private static String skipN(final String path, final int num) {
final String[] pieces = path.split("/");
final StringBuilder sb = new StringBuilder();
for (int i = num; i < pieces.length; i++) {
final String piece = pieces[i];
sb.append('/').append(piece);
}
return sb.toString();
}
public static<T extends FilePatch> ApplyPatchStatus applyOnly(final Project project,
final ApplyFilePatchBase<T> patch,
final ApplyPatchContext context,
final VirtualFile file,
final CommitContext commitContext,
boolean reverse,
@Nullable String leftPanelTitle, @Nullable String rightPanelTitle) {
final T patchBase = patch.getPatch();
final Application application = ApplicationManager.getApplication();
final ApplyFilePatch.Result result = application.runWriteAction(new Computable<ApplyFilePatch.Result>() {
@Override
public ApplyFilePatch.Result compute() {
try {
return patch.apply(file, context, project, new FilePathImpl(file), new Getter<CharSequence>() {
@Override
public CharSequence get() {
return getBaseContents((TextFilePatch)patchBase, commitContext, project);
}
}, commitContext);
}
catch (IOException e) {
LOG.error(e);
return ApplyFilePatch.Result.createThrow(e);
}
}
});
final ApplyPatchStatus status;
try {
status = result.getStatus();
}
catch (IOException e) {
showIOException(project, patchBase.getBeforeName(), e);
return ApplyPatchStatus.FAILURE;
}
if (ApplyPatchStatus.ALREADY_APPLIED.equals(status) || ApplyPatchStatus.SUCCESS.equals(status)) {
return status;
}
final ApplyPatchForBaseRevisionTexts mergeData = result.getMergeData();
if (mergeData != null) {
if (mergeData.getBase() != null) {
return showMergeDialog(project, file, mergeData.getBase(), mergeData.getPatched(), ApplyPatchMergeRequestFactory.INSTANCE,
reverse, leftPanelTitle, rightPanelTitle);
} else {
try {
return showBadDiffDialog(project, file, mergeData, false);
}
catch (final IOException e) {
SwingUtilities.invokeLater(new Runnable() {
@Override
public void run() {
showIOException(project, patchBase.getBeforeName(), e);
}
});
}
return ApplyPatchStatus.FAILURE;
}
}
return status;
}
private static <T extends FilePatch> void showIOException(Project project, String name, IOException e) {
Messages.showErrorDialog(project, VcsBundle.message("patch.apply.error", name, e.getMessage()),
VcsBundle.message("patch.apply.dialog.title"));
}
@Nullable
private static CharSequence getBaseContents(final TextFilePatch patchBase, final CommitContext commitContext, final Project project) {
final BaseRevisionTextPatchEP baseRevisionTextPatchEP = Extensions.findExtension(PatchEP.EP_NAME, project, BaseRevisionTextPatchEP.class);
if (baseRevisionTextPatchEP != null) {
final String path = patchBase.getBeforeName() == null ? patchBase.getAfterName() : patchBase.getBeforeName();
return baseRevisionTextPatchEP.provideContent(path, commitContext);
}
return null;
}
public static ApplyPatchStatus showBadDiffDialog(final Project project, final VirtualFile file, final ApplyPatchForBaseRevisionTexts texts, final boolean readonly)
throws IOException {
if (texts.getLocal() == null) {
return ApplyPatchStatus.FAILURE;
}
final DiffTool tool = DiffManager.getInstance().getDiffTool();
final SimpleDiffRequest simpleRequest = createBadDiffRequest(project, file, texts, readonly);
tool.show(simpleRequest);
return ApplyPatchStatus.SUCCESS;
}
public static SimpleDiffRequest createBadDiffRequest(final Project project,
final VirtualFile file,
ApplyPatchForBaseRevisionTexts texts,
boolean readonly) {
final SimpleDiffRequest simpleRequest =
new SimpleDiffRequest(project, "Result Of Patch Apply To " + file.getName() + " (" +
(file.getParent() == null ? file.getPath() : file.getParent().getPath()) +
")");
final DocumentImpl patched = new DocumentImpl(texts.getPatched());
patched.setReadOnly(false);
final DocumentContent mergedDocument = new DocumentContent(project, patched, file.getFileType());
mergedDocument.getDocument().setReadOnly(readonly);
simpleRequest.setContents(new SimpleContent(texts.getLocal().toString(), file.getFileType()),
mergedDocument);
simpleRequest.setContentTitles(VcsBundle.message("diff.title.local"), "Patched (with problems)");
simpleRequest.addHint(DiffTool.HINT_SHOW_MODAL_DIALOG);
simpleRequest.addHint(DiffTool.HINT_DIFF_IS_APPROXIMATE);
if (! readonly) {
simpleRequest.setOnOkRunnable(new Runnable() {
@Override
public void run() {
ApplicationManager.getApplication().runWriteAction(new Runnable() {
@Override
public void run() {
final String resultText = mergedDocument.getDocument().getText();
final FileDocumentManager fileDocumentManager = FileDocumentManager.getInstance();
final Document document = fileDocumentManager.getDocument(file);
if (document == null) {
try {
VfsUtil.saveText(file, resultText);
}
catch (IOException e) {
showIOException(project, file.getName(), e); // todo bad: we had already returned success by now
}
} else {
document.setText(resultText);
fileDocumentManager.saveDocument(document);
}
}
});
}
});
}
return simpleRequest;
}
private static ApplyPatchStatus showMergeDialog(Project project,
VirtualFile file,
CharSequence content,
final String patchedContent,
final PatchMergeRequestFactory mergeRequestFactory,
boolean reverse,
@Nullable String leftPanelTitle, @Nullable String rightPanelTitle) {
final FileDocumentManager fileDocumentManager = FileDocumentManager.getInstance();
Document document = fileDocumentManager.getDocument(file);
if (document != null) {
fileDocumentManager.saveDocument(document);
}
CharSequence fileContent = LoadTextUtil.loadText(file);
if (fileContent == null || content == null) {
return ApplyPatchStatus.FAILURE;
}
final MergeRequest request = mergeRequestFactory.createMergeRequest(fileContent.toString(), patchedContent, content.toString(), file,
project, reverse, leftPanelTitle, rightPanelTitle);
DiffManager.getInstance().getDiffTool().show(request);
if (request.getResult() == DialogWrapper.OK_EXIT_CODE) {
return ApplyPatchStatus.SUCCESS;
}
request.restoreOriginalContent();
return ApplyPatchStatus.FAILURE;
}
@Override
public void update(AnActionEvent e) {
Project project = e.getData(CommonDataKeys.PROJECT);
if (e.getPlace().equals(ActionPlaces.PROJECT_VIEW_POPUP)) {
VirtualFile vFile = e.getData(CommonDataKeys.VIRTUAL_FILE);
e.getPresentation().setVisible(project != null && vFile != null && vFile.getFileType() == StdFileTypes.PATCH);
}
else {
e.getPresentation().setEnabled(project != null);
}
}
public static class ApplyPatchMergeRequestFactory implements PatchMergeRequestFactory {
private final boolean myReadOnly;
public static final ApplyPatchMergeRequestFactory INSTANCE = new ApplyPatchMergeRequestFactory(false);
public static final ApplyPatchMergeRequestFactory INSTANCE_READ_ONLY = new ApplyPatchMergeRequestFactory(true);
public ApplyPatchMergeRequestFactory(final boolean readOnly) {
myReadOnly = readOnly;
}
public MergeRequest createMergeRequest(final String leftText, final String rightText, final String originalContent,
@NotNull final VirtualFile file, final Project project, boolean reverse,
@Nullable String leftPanelTitle, @Nullable String rightPanelTitle) {
MergeRequest request;
if (myReadOnly) {
request = DiffRequestFactory.getInstance()
.create3WayDiffRequest(leftText, rightText, originalContent, file.getFileType(), project, null, null);
} else {
request = DiffRequestFactory.getInstance().createMergeRequest(reverse ? rightText : leftText,
reverse ? leftText : rightText, originalContent,
file, project, ActionButtonPresentation.APPLY,
ActionButtonPresentation.CANCEL_WITH_PROMPT);
}
request.setVersionTitles(new String[] {
leftPanelTitle == null ? VcsBundle.message("patch.apply.conflict.local.version") : leftPanelTitle,
rightPanelTitle == null ? VcsBundle.message("patch.apply.conflict.merged.version") : rightPanelTitle,
VcsBundle.message("patch.apply.conflict.patched.version")
});
request.setWindowTitle(VcsBundle.message("patch.apply.conflict.title", file.getPresentableUrl()));
return request;
}
}
}