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.
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
) {
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
) {
80 }), newDia
.getSelectedChangeList(), null);
81 appliers
.add(patchApplier
);
83 PatchApplier
.executePatchGroup(appliers
);
86 final ApplyPatchDifferentiatedDialog dialog
= new ApplyPatchDifferentiatedDialog(project
, callback
);
90 public static void applySkipDirs(final List
<FilePatch
> patches
, final int skipDirs
) {
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
) {
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) {
123 Messages
.showErrorDialog(project
, VcsBundle
.message("patch.apply.error", patch
.getBeforeName(), ex
.getMessage()),
124 VcsBundle
.message("patch.apply.dialog.title"));
126 catch (Exception ex
) {
129 return ApplyPatchStatus
.FAILURE
;
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
);
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) {
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
>();
153 provider
.getBaseVersionContent(pathBeforeRename
, new Processor
<CharSequence
>() {
154 public boolean process(final CharSequence text
) {
155 newText
.setLength(0);
157 statusRef
.set(patch
.applyModifications(text
, newText
));
159 catch(ApplyPatchException ex
) {
160 return true; // continue to older versions
162 contentRef
.set(text
);
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
);
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
,
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
;
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
);
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
;
225 request
= DiffRequestFactory
.getInstance().create3WayDiffRequest(leftText
, rightText
, originalContent
, project
, null);
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()));