IDEA-53894 (IDEA is unable to show diff for shelved new files)
[idea/community.git] / platform / vcs-impl / src / com / intellij / openapi / vcs / changes / shelf / DiffShelvedChangesAction.java
1 /*
2  * Copyright 2000-2009 JetBrains s.r.o.
3  *
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
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
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.
15  */
16 package com.intellij.openapi.vcs.changes.shelf;
17
18 import com.intellij.openapi.actionSystem.*;
19 import com.intellij.openapi.diff.DiffRequestFactory;
20 import com.intellij.openapi.diff.MergeRequest;
21 import com.intellij.openapi.diff.impl.patch.*;
22 import com.intellij.openapi.diff.impl.patch.apply.ApplyTextFilePatch;
23 import com.intellij.openapi.project.DumbAware;
24 import com.intellij.openapi.project.Project;
25 import com.intellij.openapi.ui.MessageType;
26 import com.intellij.openapi.util.text.StringUtil;
27 import com.intellij.openapi.vcs.FilePath;
28 import com.intellij.openapi.vcs.FileStatus;
29 import com.intellij.openapi.vcs.VcsException;
30 import com.intellij.openapi.vcs.changes.Change;
31 import com.intellij.openapi.vcs.changes.ContentRevision;
32 import com.intellij.openapi.vcs.changes.actions.ChangeDiffRequestPresentable;
33 import com.intellij.openapi.vcs.changes.actions.DiffRequestPresentable;
34 import com.intellij.openapi.vcs.changes.actions.DiffRequestPresentableProxy;
35 import com.intellij.openapi.vcs.changes.actions.ShowDiffAction;
36 import com.intellij.openapi.vcs.changes.patch.ApplyPatchForBaseRevisionTexts;
37 import com.intellij.openapi.vcs.changes.patch.MergedDiffRequestPresentable;
38 import com.intellij.openapi.vcs.changes.patch.PatchMergeRequestFactory;
39 import com.intellij.openapi.vcs.changes.ui.ChangesComparator;
40 import com.intellij.openapi.vcs.changes.ui.ChangesViewBalloonProblemNotifier;
41 import com.intellij.openapi.vfs.VirtualFile;
42 import org.jetbrains.annotations.NotNull;
43 import org.jetbrains.annotations.Nullable;
44
45 import java.io.IOException;
46 import java.util.*;
47
48 /**
49  * @author yole
50  */
51 public class DiffShelvedChangesAction extends AnAction implements DumbAware {
52   public void actionPerformed(final AnActionEvent e) {
53     showShelvedChangesDiff(e.getDataContext());
54   }
55
56   public static void showShelvedChangesDiff(final DataContext dc) {
57     final Project project = PlatformDataKeys.PROJECT.getData(dc);
58     ShelvedChangeList[] changeLists = ShelvedChangesViewManager.SHELVED_CHANGELIST_KEY.getData(dc);
59     if (changeLists == null) {
60       changeLists = ShelvedChangesViewManager.SHELVED_RECYCLED_CHANGELIST_KEY.getData(dc);
61     }
62
63     // selected changes inside lists
64     List<ShelvedChange> shelvedChanges = ShelvedChangesViewManager.SHELVED_CHANGE_KEY.getData(dc);
65
66     if (changeLists == null) return;
67
68     final List<ShelvedChange> changesFromFirstList = changeLists[0].getChanges();
69
70     Collections.sort(changesFromFirstList, new MyComparator(project));
71
72     int toSelectIdx = 0;
73     final ArrayList<DiffRequestPresentable> diffRequestPresentables = new ArrayList<DiffRequestPresentable>();
74     final ApplyPatchContext context = new ApplyPatchContext(project.getBaseDir(), 0, false, false);
75     final PatchesPreloader preloader = new PatchesPreloader();
76
77     final List<String> missing = new LinkedList<String>();
78     for (final ShelvedChange shelvedChange : changesFromFirstList) {
79       final Change change = shelvedChange.getChange(project);
80       final String beforePath = shelvedChange.getBeforePath();
81       try {
82         final VirtualFile f = ApplyTextFilePatch.findPatchTarget(context, beforePath, shelvedChange.getAfterPath(), FileStatus.ADDED.equals(shelvedChange.getFileStatus()));
83         if ((f == null) || (! f.exists())) {
84           if (beforePath != null) {
85             missing.add(beforePath);
86           }
87           continue;
88         }
89
90         diffRequestPresentables.add(new DiffRequestPresentableProxy() {
91           @Override
92           protected DiffRequestPresentable init() {
93             if (isConflictingChange(change)) {
94               TextFilePatch patch = preloader.getPatch(shelvedChange);
95               if (patch == null) return null;
96
97               final FilePath pathBeforeRename = context.getPathBeforeRename(f);
98
99               final ApplyPatchForBaseRevisionTexts threeTexts = ApplyPatchForBaseRevisionTexts.create(project, f, pathBeforeRename, patch);
100               if ((threeTexts == null) || (threeTexts.getStatus() == null) || (ApplyPatchStatus.FAILURE.equals(threeTexts.getStatus()))) {
101                 return null;
102               }
103
104               return new MergedDiffRequestPresentable(project, threeTexts, f, "Shelved Version");
105             }
106             else {
107               return new ChangeDiffRequestPresentable(project, change);
108             }
109           }
110         });
111       }
112       catch (IOException e) {
113         continue;
114       }
115       if ((shelvedChanges != null) && shelvedChanges.contains(shelvedChange)) {
116         // just added
117         toSelectIdx = diffRequestPresentables.size() - 1;
118       }
119     }
120     if (! missing.isEmpty()) {
121       // 7-8
122       ChangesViewBalloonProblemNotifier.showMe(project, "Show Diff: Cannot find base for: " + StringUtil.join(missing, ",\n"), MessageType.WARNING);
123     }
124
125     ShowDiffAction.showDiffImpl(project, diffRequestPresentables, toSelectIdx, ShowDiffAction.DiffExtendUIFactory.NONE, false);
126   }
127
128   private static class PatchesPreloader {
129     private final Map<String, List<TextFilePatch>> myFilePatchesMap;
130
131     private PatchesPreloader() {
132       myFilePatchesMap = new HashMap<String, List<TextFilePatch>>();
133     }
134
135     @Nullable
136     public TextFilePatch getPatch(final ShelvedChange shelvedChange) {
137       List<TextFilePatch> textFilePatches = myFilePatchesMap.get(shelvedChange.getPatchPath());
138       if (textFilePatches == null) {
139         try {
140           textFilePatches = ShelveChangesManager.loadPatches(shelvedChange.getPatchPath());
141         }
142         catch (IOException e) {
143           return null;
144         }
145         catch (PatchSyntaxException e) {
146           return null;
147         }
148         myFilePatchesMap.put(shelvedChange.getPatchPath(), textFilePatches);
149       }
150       for (TextFilePatch textFilePatch : textFilePatches) {
151         if (shelvedChange.getBeforePath().equals(textFilePatch.getBeforeName())) {
152           return textFilePatch;
153         }
154       }
155       return null;
156     }
157   }
158
159   private final static class MyComparator implements Comparator<ShelvedChange> {
160     private final Project myProject;
161
162     public MyComparator(Project project) {
163       myProject = project;
164     }
165
166     public int compare(final ShelvedChange o1, final ShelvedChange o2) {
167       return ChangesComparator.getInstance().compare(o1.getChange(myProject), o2.getChange(myProject));
168     }
169   }
170
171   private static boolean isConflictingChange(final Change change) {
172     ContentRevision afterRevision = change.getAfterRevision();
173     if (afterRevision == null) return false;
174     try {
175       afterRevision.getContent();
176     }
177     catch(VcsException e) {
178       if (e.getCause() instanceof ApplyPatchException) {
179         return true;
180       }
181     }
182     return false;
183   }
184
185   public void update(final AnActionEvent e) {
186     ActionManager.getInstance().getAction("ChangesView.Diff").update(e);
187   }
188
189   private static class ShelvedChangeDiffRequestFactory implements PatchMergeRequestFactory {
190     public static final ShelvedChangeDiffRequestFactory INSTANCE = new ShelvedChangeDiffRequestFactory();
191
192     public MergeRequest createMergeRequest(final String leftText, final String rightText, final String originalContent, @NotNull final VirtualFile file,
193                                            final Project project) {
194       MergeRequest request = DiffRequestFactory.getInstance().create3WayDiffRequest(leftText, rightText, originalContent,
195                                                                                     project,
196                                                                                     null);
197       request.setVersionTitles(new String[] {
198         "Current Version",
199         "Base Version",
200         "Shelved Version"
201       });
202       request.setWindowTitle("Shelved Change Conflict for" + file.getPresentableUrl());
203       return request;
204     }
205   }
206 }