"revert" action for committed changelist (IDEADEV-13292); refactoring to introduce...
[fedora-idea.git] / vcs-impl / src / com / intellij / openapi / vcs / changes / patch / ApplyPatchAction.java
blobd658df6ae57da2de5e7a2747ce82d425d6cbd653
1 /*
2 * Copyright (c) 2000-2006 JetBrains s.r.o. All Rights Reserved.
3 */
5 /*
6 * Created by IntelliJ IDEA.
7 * User: yole
8 * Date: 17.11.2006
9 * Time: 17:08:11
11 package com.intellij.openapi.vcs.changes.patch;
13 import com.intellij.openapi.actionSystem.ActionPlaces;
14 import com.intellij.openapi.actionSystem.AnAction;
15 import com.intellij.openapi.actionSystem.AnActionEvent;
16 import com.intellij.openapi.actionSystem.PlatformDataKeys;
17 import com.intellij.openapi.application.ApplicationManager;
18 import com.intellij.openapi.command.CommandProcessor;
19 import com.intellij.openapi.diagnostic.Logger;
20 import com.intellij.openapi.diff.ActionButtonPresentation;
21 import com.intellij.openapi.diff.DiffManager;
22 import com.intellij.openapi.diff.DiffRequestFactory;
23 import com.intellij.openapi.diff.MergeRequest;
24 import com.intellij.openapi.diff.impl.patch.*;
25 import com.intellij.openapi.fileEditor.impl.LoadTextUtil;
26 import com.intellij.openapi.fileTypes.FileType;
27 import com.intellij.openapi.fileTypes.FileTypeManager;
28 import com.intellij.openapi.fileTypes.FileTypes;
29 import com.intellij.openapi.fileTypes.StdFileTypes;
30 import com.intellij.openapi.fileTypes.ex.FileTypeChooser;
31 import com.intellij.openapi.project.Project;
32 import com.intellij.openapi.ui.DialogWrapper;
33 import com.intellij.openapi.ui.Messages;
34 import com.intellij.openapi.util.Ref;
35 import com.intellij.openapi.vcs.FilePath;
36 import com.intellij.openapi.vcs.ProjectLevelVcsManager;
37 import com.intellij.openapi.vcs.VcsBundle;
38 import com.intellij.openapi.vcs.VcsException;
39 import com.intellij.openapi.vcs.changes.Change;
40 import com.intellij.openapi.vcs.changes.ChangeListManager;
41 import com.intellij.openapi.vcs.changes.LocalChangeList;
42 import com.intellij.openapi.vcs.impl.ExcludedFileIndex;
43 import com.intellij.openapi.vfs.ReadonlyStatusHandler;
44 import com.intellij.openapi.vfs.VirtualFile;
45 import com.intellij.util.Processor;
47 import java.io.IOException;
48 import java.util.ArrayList;
49 import java.util.List;
51 public class ApplyPatchAction extends AnAction {
52 private static final Logger LOG = Logger.getInstance("#com.intellij.openapi.vcs.changes.patch.ApplyPatchAction");
54 public void actionPerformed(AnActionEvent e) {
55 final Project project = e.getData(PlatformDataKeys.PROJECT);
56 final ApplyPatchDialog dialog = new ApplyPatchDialog(project);
57 final VirtualFile vFile = e.getData(PlatformDataKeys.VIRTUAL_FILE);
58 if (vFile != null && vFile.getFileType() == StdFileTypes.PATCH) {
59 dialog.setFileName(vFile.getPresentableUrl());
61 dialog.show();
62 if (dialog.getExitCode() != DialogWrapper.OK_EXIT_CODE) {
63 return;
65 applyPatch(project, dialog.getPatches(), dialog.getApplyPatchContext(), dialog.getSelectedChangeList());
68 public static void applyPatch(final Project project, final List<? extends FilePatch> patches,
69 final ApplyPatchContext context, final LocalChangeList targetChangeList) {
70 List<VirtualFile> filesToMakeWritable = new ArrayList<VirtualFile>();
71 if (!prepareFiles(project, patches, context, filesToMakeWritable)) {
72 return;
74 final VirtualFile[] fileArray = filesToMakeWritable.toArray(new VirtualFile[filesToMakeWritable.size()]);
75 final ReadonlyStatusHandler.OperationStatus readonlyStatus = ReadonlyStatusHandler.getInstance(project).ensureFilesWritable(fileArray);
76 if (readonlyStatus.hasReadonlyFiles()) {
77 return;
79 applyFilePatches(project, patches, context);
80 moveChangesToList(project, context.getAffectedFiles(), targetChangeList);
83 public static ApplyPatchStatus applyFilePatches(final Project project, final List<? extends FilePatch> patches,
84 final ApplyPatchContext context) {
85 final Ref<ApplyPatchStatus> statusRef = new Ref<ApplyPatchStatus>();
86 ApplicationManager.getApplication().runWriteAction(new Runnable() {
87 public void run() {
88 CommandProcessor.getInstance().executeCommand(project, new Runnable() {
89 public void run() {
90 ApplyPatchStatus status = null;
91 for(FilePatch patch: patches) {
92 final ApplyPatchStatus patchStatus = applySinglePatch(project, patch, context);
93 status = ApplyPatchStatus.and(status, patchStatus);
95 try {
96 context.applyPendingRenames();
98 catch (IOException e) {
99 Messages.showErrorDialog(project, "Error renaming directories: " + e.getMessage(),
100 VcsBundle.message("patch.apply.dialog.title"));
102 if (status == ApplyPatchStatus.ALREADY_APPLIED) {
103 Messages.showInfoMessage(project, VcsBundle.message("patch.apply.already.applied"),
104 VcsBundle.message("patch.apply.dialog.title"));
106 else if (status == ApplyPatchStatus.PARTIAL) {
107 Messages.showInfoMessage(project, VcsBundle.message("patch.apply.partially.applied"),
108 VcsBundle.message("patch.apply.dialog.title"));
110 statusRef.set(status);
112 }, VcsBundle.message("patch.apply.command"), null);
115 return statusRef.get();
118 public static boolean prepareFiles(final Project project, final List<? extends FilePatch> patches,
119 final ApplyPatchContext context,
120 final List<VirtualFile> filesToMakeWritable) {
121 for(FilePatch patch: patches) {
122 VirtualFile fileToPatch;
123 try {
124 fileToPatch = patch.findFileToPatch(context.getPrepareContext());
126 catch (IOException e) {
127 Messages.showErrorDialog(project, "Error when searching for file to patch: " + patch.getBeforeName() + ": " + e.getMessage(),
128 VcsBundle.message("patch.apply.dialog.title"));
129 return false;
131 // security check to avoid overwriting system files with a patch
132 if (fileToPatch != null && !ExcludedFileIndex.getInstance(project).isInContent(fileToPatch) &&
133 ProjectLevelVcsManager.getInstance(project).getVcsRootFor(fileToPatch) == null) {
134 Messages.showErrorDialog(project, "File to patch found outside content root: " + patch.getBeforeName(),
135 VcsBundle.message("patch.apply.dialog.title"));
136 return false;
138 if (fileToPatch != null && !fileToPatch.isDirectory()) {
139 filesToMakeWritable.add(fileToPatch);
140 FileType fileType = fileToPatch.getFileType();
141 if (fileType == FileTypes.UNKNOWN && patch instanceof TextFilePatch) {
142 fileType = FileTypeChooser.associateFileType(fileToPatch.getPresentableName());
143 if (fileType == null) {
144 return false;
148 else if (patch.isNewFile()) {
149 FileType fileType = FileTypeManager.getInstance().getFileTypeByFileName(patch.getBeforeFileName());
150 if (fileType == FileTypes.UNKNOWN && patch instanceof TextFilePatch) {
151 fileType = FileTypeChooser.associateFileType(patch.getBeforeFileName());
152 if (fileType == null) {
153 return false;
158 return true;
161 private static ApplyPatchStatus applySinglePatch(final Project project, final FilePatch patch,
162 final ApplyPatchContext context) {
163 VirtualFile file;
164 try {
165 file = patch.findFileToPatch(context);
167 catch (IOException e) {
168 Messages.showErrorDialog(project, "Error when searching for file to patch: " + patch.getBeforeName() + ": " + e.getMessage(),
169 VcsBundle.message("patch.apply.dialog.title"));
170 return ApplyPatchStatus.FAILURE;
172 if (file == null) {
173 Messages.showErrorDialog(project, "Cannot find file to patch: " + patch.getBeforeName(),
174 VcsBundle.message("patch.apply.dialog.title"));
175 return ApplyPatchStatus.FAILURE;
177 if (file.isDirectory() && !patch.isNewFile() && !patch.isDeletedFile()) {
178 Messages.showErrorDialog(project, "Expected to find a file but found a directory: " + patch.getBeforeName(),
179 VcsBundle.message("patch.apply.dialog.title"));
180 return ApplyPatchStatus.FAILURE;
183 try {
184 return patch.apply(file, context);
186 catch(ApplyPatchException ex) {
187 if (!patch.isNewFile() && !patch.isDeletedFile()) {
188 FilePath pathBeforeRename = context.getPathBeforeRename(file);
189 final DefaultPatchBaseVersionProvider provider = new DefaultPatchBaseVersionProvider(project);
190 if (provider.canProvideContent(file, patch.getBeforeVersionId()) && patch instanceof TextFilePatch) {
191 final StringBuilder newText = new StringBuilder();
192 final Ref<CharSequence> contentRef = new Ref<CharSequence>();
193 final Ref<ApplyPatchStatus> statusRef = new Ref<ApplyPatchStatus>();
194 try {
195 provider.getBaseVersionContent(file, pathBeforeRename, patch.getBeforeVersionId(), new Processor<CharSequence>() {
196 public boolean process(final CharSequence text) {
197 newText.setLength(0);
198 try {
199 statusRef.set(((TextFilePatch) patch).applyModifications(text, newText));
201 catch(ApplyPatchException ex) {
202 return true; // continue to older versions
204 contentRef.set(text);
205 return false;
209 catch (VcsException vcsEx) {
210 Messages.showErrorDialog(project, VcsBundle.message("patch.load.base.revision.error", patch.getBeforeName(), vcsEx.getMessage()),
211 VcsBundle.message("patch.apply.dialog.title"));
212 return ApplyPatchStatus.FAILURE;
214 ApplyPatchStatus status = statusRef.get();
215 if (status != null) {
216 if (status != ApplyPatchStatus.ALREADY_APPLIED) {
217 return showMergeDialog(project, file, contentRef.get(), newText.toString());
219 else {
220 return status;
225 Messages.showErrorDialog(project, VcsBundle.message("patch.apply.error", patch.getBeforeName(), ex.getMessage()),
226 VcsBundle.message("patch.apply.dialog.title"));
228 catch (Exception ex) {
229 LOG.error(ex);
231 return ApplyPatchStatus.FAILURE;
234 private static ApplyPatchStatus showMergeDialog(Project project, VirtualFile file, CharSequence content, final String patchedContent) {
235 final DiffRequestFactory diffRequestFactory = DiffRequestFactory.getInstance();
236 CharSequence fileContent = LoadTextUtil.loadText(file);
237 final MergeRequest request = diffRequestFactory.createMergeRequest(fileContent.toString(), patchedContent, content.toString(), file,
238 project, ActionButtonPresentation.createApplyButton());
239 request.setVersionTitles(new String[] {
240 VcsBundle.message("patch.apply.conflict.local.version"),
241 VcsBundle.message("patch.apply.conflict.merged.version"),
242 VcsBundle.message("patch.apply.conflict.patched.version")
244 request.setWindowTitle(VcsBundle.message("patch.apply.conflict.title", file.getPresentableUrl()));
245 DiffManager.getInstance().getDiffTool().show(request);
246 if (request.getResult() == DialogWrapper.OK_EXIT_CODE) {
247 return ApplyPatchStatus.SUCCESS;
249 return ApplyPatchStatus.FAILURE;
252 @Override
253 public void update(AnActionEvent e) {
254 Project project = e.getData(PlatformDataKeys.PROJECT);
255 if (e.getPlace() == ActionPlaces.PROJECT_VIEW_POPUP) {
256 VirtualFile vFile = e.getData(PlatformDataKeys.VIRTUAL_FILE);
257 e.getPresentation().setVisible(project != null && vFile != null && vFile.getFileType() == StdFileTypes.PATCH);
259 else {
260 e.getPresentation().setEnabled(project != null);
264 public static void moveChangesToList(final Project project, final List<FilePath> files, final LocalChangeList targetChangeList) {
265 ChangeListManager changeListManager = ChangeListManager.getInstance(project);
266 if (targetChangeList != changeListManager.getDefaultChangeList()) {
267 changeListManager.ensureUpToDate(false);
268 List<Change> changes = new ArrayList<Change>();
269 for(FilePath file: files) {
270 final Change change = changeListManager.getChange(file);
271 if (change != null) {
272 changes.add(change);
275 changeListManager.moveChangesTo(targetChangeList, changes.toArray(new Change[changes.size()]));