new Apply Patch UI
[fedora-idea.git] / platform / vcs-impl / src / com / intellij / openapi / vcs / changes / patch / ApplyPatchAction.java
blobea5fc3071018cabdd9b4e8ba424f2b3602e7014f
1 /*
2 * Copyright 2000-2009 JetBrains s.r.o.
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
8 * http://www.apache.org/licenses/LICENSE-2.0
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
18 * Created by IntelliJ IDEA.
19 * User: yole
20 * Date: 17.11.2006
21 * Time: 17:08:11
23 package com.intellij.openapi.vcs.changes.patch;
25 import com.intellij.openapi.actionSystem.ActionPlaces;
26 import com.intellij.openapi.actionSystem.AnAction;
27 import com.intellij.openapi.actionSystem.AnActionEvent;
28 import com.intellij.openapi.actionSystem.PlatformDataKeys;
29 import com.intellij.openapi.diagnostic.Logger;
30 import com.intellij.openapi.diff.ActionButtonPresentation;
31 import com.intellij.openapi.diff.DiffManager;
32 import com.intellij.openapi.diff.DiffRequestFactory;
33 import com.intellij.openapi.diff.MergeRequest;
34 import com.intellij.openapi.diff.impl.patch.*;
35 import com.intellij.openapi.diff.impl.patch.formove.PatchApplier;
36 import com.intellij.openapi.fileEditor.impl.LoadTextUtil;
37 import com.intellij.openapi.fileTypes.StdFileTypes;
38 import com.intellij.openapi.project.Project;
39 import com.intellij.openapi.ui.DialogWrapper;
40 import com.intellij.openapi.ui.Messages;
41 import com.intellij.openapi.util.Ref;
42 import com.intellij.openapi.vcs.*;
43 import com.intellij.openapi.vfs.VirtualFile;
44 import com.intellij.util.Consumer;
45 import com.intellij.util.Processor;
46 import com.intellij.util.containers.Convertor;
47 import com.intellij.util.containers.MultiMap;
48 import org.jetbrains.annotations.NotNull;
49 import org.jetbrains.annotations.Nullable;
51 import java.util.Collection;
52 import java.util.LinkedList;
53 import java.util.List;
55 public class ApplyPatchAction extends AnAction {
56 private static final Logger LOG = Logger.getInstance("#com.intellij.openapi.vcs.changes.patch.ApplyPatchAction");
58 public void actionPerformed(AnActionEvent e) {
59 final Project project = e.getData(PlatformDataKeys.PROJECT);
61 final Consumer<ApplyPatchDifferentiatedDialog> callback = new Consumer<ApplyPatchDifferentiatedDialog>() {
62 public void consume(ApplyPatchDifferentiatedDialog newDia) {
63 if (newDia.getExitCode() != DialogWrapper.OK_EXIT_CODE) {
64 return;
67 final Collection<FilePatchInProgress> included = newDia.getIncluded();
68 final MultiMap<VirtualFile, FilePatchInProgress> patchGroups = new MultiMap<VirtualFile, FilePatchInProgress>();
69 for (FilePatchInProgress patchInProgress : included) {
70 patchGroups.putValue(patchInProgress.getBase(), patchInProgress);
73 final Collection<PatchApplier> appliers = new LinkedList<PatchApplier>();
74 for (VirtualFile base : patchGroups.keySet()) {
75 final PatchApplier patchApplier =
76 new PatchApplier(project, base, ObjectsConvertor.convert(patchGroups.get(base), new Convertor<FilePatchInProgress, FilePatch>() {
77 public FilePatch convert(FilePatchInProgress o) {
78 return o.getPatch();
80 }), newDia.getSelectedChangeList(), null);
81 appliers.add(patchApplier);
83 PatchApplier.executePatchGroup(appliers);
86 final ApplyPatchDifferentiatedDialog dialog = new ApplyPatchDifferentiatedDialog(project, callback);
87 dialog.show();
90 public static void applySkipDirs(final List<FilePatch> patches, final int skipDirs) {
91 if (skipDirs < 1) {
92 return;
94 for (FilePatch patch : patches) {
95 patch.setBeforeName(skipN(patch.getBeforeName(), skipDirs));
96 patch.setAfterName(skipN(patch.getAfterName(), skipDirs));
100 private static String skipN(final String path, final int num) {
101 final String[] pieces = path.split("/");
102 final StringBuilder sb = new StringBuilder();
103 for (int i = num; i < pieces.length; i++) {
104 final String piece = pieces[i];
105 sb.append('/').append(piece);
107 return sb.toString();
110 public static ApplyPatchStatus applyOnly(final Project project, final FilePatch patch, final ApplyPatchContext context, final VirtualFile file) {
111 try {
112 return patch.apply(file, context, project);
114 catch(ApplyPatchException ex) {
115 if (!patch.isNewFile() && !patch.isDeletedFile() && patch instanceof TextFilePatch) {
116 //final VirtualFile beforeRename = (pathBeforeRename == null) ? file : pathBeforeRename;
117 ApplyPatchStatus mergeStatus = mergeAgainstBaseVersion(project, file, new FilePathImpl(file), (TextFilePatch) patch,
118 ApplyPatchMergeRequestFactory.INSTANCE);
119 if (mergeStatus != null) {
120 return mergeStatus;
123 Messages.showErrorDialog(project, VcsBundle.message("patch.apply.error", patch.getBeforeName(), ex.getMessage()),
124 VcsBundle.message("patch.apply.dialog.title"));
126 catch (Exception ex) {
127 LOG.error(ex);
129 return ApplyPatchStatus.FAILURE;
132 @Nullable
133 public static ApplyPatchStatus mergeAgainstBaseVersion(Project project, VirtualFile file, ApplyPatchContext context,
134 final TextFilePatch patch,
135 final PatchMergeRequestFactory mergeRequestFactory) {
136 final FilePath pathBeforeRename = context.getPathBeforeRename(file);
137 return mergeAgainstBaseVersion(project, file, pathBeforeRename, patch, mergeRequestFactory);
140 @Nullable
141 public static ApplyPatchStatus mergeAgainstBaseVersion(final Project project, final VirtualFile file, final FilePath pathBeforeRename,
142 final TextFilePatch patch, final PatchMergeRequestFactory mergeRequestFactory) {
143 final String beforeVersionId = patch.getBeforeVersionId();
144 if (beforeVersionId == null) {
145 return null;
147 final DefaultPatchBaseVersionProvider provider = new DefaultPatchBaseVersionProvider(project, file, beforeVersionId);
148 if (provider.canProvideContent()) {
149 final StringBuilder newText = new StringBuilder();
150 final Ref<CharSequence> contentRef = new Ref<CharSequence>();
151 final Ref<ApplyPatchStatus> statusRef = new Ref<ApplyPatchStatus>();
152 try {
153 provider.getBaseVersionContent(pathBeforeRename, new Processor<CharSequence>() {
154 public boolean process(final CharSequence text) {
155 newText.setLength(0);
156 try {
157 statusRef.set(patch.applyModifications(text, newText));
159 catch(ApplyPatchException ex) {
160 return true; // continue to older versions
162 contentRef.set(text);
163 return false;
167 catch (VcsException vcsEx) {
168 Messages.showErrorDialog(project, VcsBundle.message("patch.load.base.revision.error", patch.getBeforeName(), vcsEx.getMessage()),
169 VcsBundle.message("patch.apply.dialog.title"));
170 return ApplyPatchStatus.FAILURE;
172 ApplyPatchStatus status = statusRef.get();
173 if (status != null) {
174 if (status != ApplyPatchStatus.ALREADY_APPLIED) {
175 return showMergeDialog(project, file, contentRef.get(), newText.toString(), mergeRequestFactory);
177 else {
178 return status;
182 return null;
185 private static ApplyPatchStatus showMergeDialog(Project project, VirtualFile file, CharSequence content, final String patchedContent,
186 final PatchMergeRequestFactory mergeRequestFactory) {
187 CharSequence fileContent = LoadTextUtil.loadText(file);
189 final MergeRequest request = mergeRequestFactory.createMergeRequest(fileContent.toString(), patchedContent, content.toString(), file,
190 project);
191 DiffManager.getInstance().getDiffTool().show(request);
192 if (request.getResult() == DialogWrapper.OK_EXIT_CODE) {
193 return ApplyPatchStatus.SUCCESS;
195 request.restoreOriginalContent();
196 return ApplyPatchStatus.FAILURE;
199 @Override
200 public void update(AnActionEvent e) {
201 Project project = e.getData(PlatformDataKeys.PROJECT);
202 if (e.getPlace().equals(ActionPlaces.PROJECT_VIEW_POPUP)) {
203 VirtualFile vFile = e.getData(PlatformDataKeys.VIRTUAL_FILE);
204 e.getPresentation().setVisible(project != null && vFile != null && vFile.getFileType() == StdFileTypes.PATCH);
206 else {
207 e.getPresentation().setEnabled(project != null);
211 public static class ApplyPatchMergeRequestFactory implements PatchMergeRequestFactory {
212 private final boolean myReadOnly;
214 public static final ApplyPatchMergeRequestFactory INSTANCE = new ApplyPatchMergeRequestFactory(false);
215 public static final ApplyPatchMergeRequestFactory INSTANCE_READ_ONLY = new ApplyPatchMergeRequestFactory(true);
217 public ApplyPatchMergeRequestFactory(final boolean readOnly) {
218 myReadOnly = readOnly;
221 public MergeRequest createMergeRequest(final String leftText, final String rightText, final String originalContent, @NotNull final VirtualFile file,
222 final Project project) {
223 MergeRequest request;
224 if (myReadOnly) {
225 request = DiffRequestFactory.getInstance().create3WayDiffRequest(leftText, rightText, originalContent, project, null);
226 } else {
227 request = DiffRequestFactory.getInstance().createMergeRequest(leftText, rightText, originalContent,
228 file, project, ActionButtonPresentation.createApplyButton());
231 request.setVersionTitles(new String[] {
232 VcsBundle.message("patch.apply.conflict.local.version"),
233 VcsBundle.message("patch.apply.conflict.merged.version"),
234 VcsBundle.message("patch.apply.conflict.patched.version")
236 request.setWindowTitle(VcsBundle.message("patch.apply.conflict.title", file.getPresentableUrl()));
237 return request;