2 * Copyright (c) 2000-2006 JetBrains s.r.o. All Rights Reserved.
6 * Created by IntelliJ IDEA.
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());
62 if (dialog
.getExitCode() != DialogWrapper
.OK_EXIT_CODE
) {
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
)) {
74 final VirtualFile
[] fileArray
= filesToMakeWritable
.toArray(new VirtualFile
[filesToMakeWritable
.size()]);
75 final ReadonlyStatusHandler
.OperationStatus readonlyStatus
= ReadonlyStatusHandler
.getInstance(project
).ensureFilesWritable(fileArray
);
76 if (readonlyStatus
.hasReadonlyFiles()) {
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() {
88 CommandProcessor
.getInstance().executeCommand(project
, new Runnable() {
90 ApplyPatchStatus status
= null;
91 for(FilePatch patch
: patches
) {
92 final ApplyPatchStatus patchStatus
= applySinglePatch(project
, patch
, context
);
93 status
= ApplyPatchStatus
.and(status
, patchStatus
);
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
;
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"));
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"));
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) {
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) {
161 private static ApplyPatchStatus
applySinglePatch(final Project project
, final FilePatch patch
,
162 final ApplyPatchContext context
) {
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
;
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
;
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
>();
195 provider
.getBaseVersionContent(file
, pathBeforeRename
, patch
.getBeforeVersionId(), new Processor
<CharSequence
>() {
196 public boolean process(final CharSequence text
) {
197 newText
.setLength(0);
199 statusRef
.set(((TextFilePatch
) patch
).applyModifications(text
, newText
));
201 catch(ApplyPatchException ex
) {
202 return true; // continue to older versions
204 contentRef
.set(text
);
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());
225 Messages
.showErrorDialog(project
, VcsBundle
.message("patch.apply.error", patch
.getBeforeName(), ex
.getMessage()),
226 VcsBundle
.message("patch.apply.dialog.title"));
228 catch (Exception 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
;
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
);
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) {
275 changeListManager
.moveChangesTo(targetChangeList
, changes
.toArray(new Change
[changes
.size()]));