c1394c50503b953a687d1c8e71df9cc858906198
[idea/community.git] / plugins / svn4idea / src / org / jetbrains / idea / svn / actions / AbstractShowPropertiesDiffAction.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 org.jetbrains.idea.svn.actions;
17
18 import com.intellij.diff.DiffManager;
19 import com.intellij.openapi.actionSystem.*;
20 import com.intellij.openapi.application.ApplicationManager;
21 import com.intellij.openapi.progress.PerformInBackgroundOption;
22 import com.intellij.openapi.progress.ProgressIndicator;
23 import com.intellij.openapi.progress.ProgressManager;
24 import com.intellij.openapi.progress.Task;
25 import com.intellij.openapi.project.DumbAware;
26 import com.intellij.openapi.project.Project;
27 import com.intellij.openapi.ui.Messages;
28 import com.intellij.openapi.util.Condition;
29 import com.intellij.openapi.util.text.StringUtil;
30 import com.intellij.openapi.vcs.FilePath;
31 import com.intellij.openapi.vcs.VcsDataKeys;
32 import com.intellij.openapi.vcs.VcsException;
33 import com.intellij.openapi.vcs.changes.Change;
34 import com.intellij.openapi.vcs.changes.ChangesUtil;
35 import com.intellij.openapi.vcs.changes.ContentRevision;
36 import com.intellij.openapi.vcs.changes.MarkerVcsContentRevision;
37 import com.intellij.util.containers.ContainerUtil;
38 import org.jetbrains.annotations.NotNull;
39 import org.jetbrains.annotations.Nullable;
40 import org.jetbrains.idea.svn.SvnBundle;
41 import org.jetbrains.idea.svn.SvnVcs;
42 import org.jetbrains.idea.svn.api.Depth;
43 import org.jetbrains.idea.svn.difftool.properties.SvnPropertiesDiffRequest;
44 import org.jetbrains.idea.svn.difftool.properties.SvnPropertiesDiffRequest.PropertyContent;
45 import org.jetbrains.idea.svn.history.SvnRepositoryContentRevision;
46 import org.jetbrains.idea.svn.properties.PropertyConsumer;
47 import org.jetbrains.idea.svn.properties.PropertyData;
48 import org.jetbrains.idea.svn.properties.PropertyValue;
49 import org.tmatesoft.svn.core.SVNErrorCode;
50 import org.tmatesoft.svn.core.SVNErrorMessage;
51 import org.tmatesoft.svn.core.SVNException;
52 import org.tmatesoft.svn.core.SVNURL;
53 import org.tmatesoft.svn.core.wc.SVNRevision;
54 import org.tmatesoft.svn.core.wc2.SvnTarget;
55
56 import java.io.File;
57 import java.util.ArrayList;
58 import java.util.Collections;
59 import java.util.Comparator;
60 import java.util.List;
61
62 public abstract class AbstractShowPropertiesDiffAction extends AnAction implements DumbAware {
63   protected AbstractShowPropertiesDiffAction(String name) {
64     super(name);
65   }
66
67   protected abstract DataKey<Change[]> getChangesKey();
68   @Nullable
69   protected abstract SVNRevision getBeforeRevisionValue(final Change change, final SvnVcs vcs) throws SVNException;
70   @Nullable
71   protected abstract SVNRevision getAfterRevisionValue(final Change change, final SvnVcs vcs) throws SVNException;
72
73   @Override
74   public void update(final AnActionEvent e) {
75     final DataContext dataContext = e.getDataContext();
76     final Presentation presentation = e.getPresentation();
77     final Change[] data = VcsDataKeys.CHANGES.getData(dataContext);
78     boolean showAction = checkThatChangesAreUnderSvn(data);
79     presentation.setVisible(data != null && showAction);
80     presentation.setEnabled(showAction);
81   }
82
83   private static boolean checkThatChangesAreUnderSvn(@Nullable Change[] changes) {
84     boolean result = false;
85
86     if (changes != null) {
87       result = ContainerUtil.or(changes, new Condition<Change>() {
88         @Override
89         public boolean value(Change change) {
90           return isUnderSvn(change.getBeforeRevision()) || isUnderSvn(change.getAfterRevision());
91         }
92       });
93     }
94
95     return result;
96   }
97
98   private static boolean isUnderSvn(@Nullable ContentRevision revision) {
99     return revision instanceof MarkerVcsContentRevision && SvnVcs.getKey().equals(((MarkerVcsContentRevision)revision).getVcsKey());
100   }
101
102   public void actionPerformed(final AnActionEvent e) {
103     final DataContext dataContext = e.getDataContext();
104     final Project project = CommonDataKeys.PROJECT.getData(dataContext);
105     final Change[] changes = e.getData(getChangesKey());
106
107     if (! checkThatChangesAreUnderSvn(changes)) {
108       return;
109     }
110
111     final Change change = changes[0];
112
113     final CalculateAndShow worker = new CalculateAndShow(project, change, e.getPresentation().getText());
114     ProgressManager.getInstance().run(worker);
115   }
116
117   private class CalculateAndShow extends Task.Backgroundable {
118     private final Change myChange;
119     private List<PropertyData> myBeforeContent;
120     private List<PropertyData> myAfterContent;
121     private SVNRevision myBeforeRevisionValue;
122     private SVNRevision myAfterRevision;
123     private Exception myException;
124     private final String myErrorTitle;
125
126     private CalculateAndShow(@Nullable final Project project, final Change change, final String errorTitle) {
127       super(project, SvnBundle.message("fetching.properties.contents.progress.title"), true, PerformInBackgroundOption.DEAF);
128       myChange = change;
129       myErrorTitle = errorTitle;
130     }
131
132     public void run(@NotNull final ProgressIndicator indicator) {
133       final SvnVcs vcs = SvnVcs.getInstance(myProject);
134
135       try {
136         myBeforeRevisionValue = getBeforeRevisionValue(myChange, vcs);
137         myAfterRevision = getAfterRevisionValue(myChange, vcs);
138
139         myBeforeContent = getPropertyList(vcs, myChange.getBeforeRevision(), myBeforeRevisionValue);
140         indicator.checkCanceled();
141         // gets exactly WORKING revision property
142         myAfterContent = getPropertyList(vcs, myChange.getAfterRevision(), myAfterRevision);
143       }
144       catch(SVNException exc) {
145         myException = exc;
146       }
147       catch (VcsException exc) {
148         myException = exc;
149       }
150
151       ApplicationManager.getApplication().invokeLater(() -> {
152         if (myException != null) {
153           Messages.showErrorDialog(myException.getMessage(), myErrorTitle);
154           return;
155         }
156         if (myBeforeContent != null && myAfterContent != null && myBeforeRevisionValue != null && myAfterRevision != null) {
157           SvnPropertiesDiffRequest diffRequest;
158           if (compareRevisions(myBeforeRevisionValue, myAfterRevision) > 0) {
159             diffRequest = new SvnPropertiesDiffRequest(getDiffWindowTitle(myChange),
160                                                        new PropertyContent(myAfterContent), new PropertyContent(myBeforeContent),
161                                                        revisionToString(myAfterRevision), revisionToString(myBeforeRevisionValue));
162           }
163           else {
164             diffRequest = new SvnPropertiesDiffRequest(getDiffWindowTitle(myChange),
165                                                        new PropertyContent(myBeforeContent), new PropertyContent(myAfterContent),
166                                                        revisionToString(myBeforeRevisionValue), revisionToString(myAfterRevision));
167           }
168           DiffManager.getInstance().showDiff(myProject, diffRequest);
169         }
170       });
171     }
172   }
173
174   @NotNull
175   private static String getDiffWindowTitle(@NotNull Change change) {
176     if (change.isMoved() || change.isRenamed()) {
177       final FilePath beforeFilePath = ChangesUtil.getBeforePath(change);
178       final FilePath afterFilePath = ChangesUtil.getAfterPath(change);
179
180       final String beforePath = beforeFilePath == null ? "" : beforeFilePath.getPath();
181       final String afterPath = afterFilePath == null ? "" : afterFilePath.getPath();
182       return SvnBundle.message("action.Subversion.properties.difference.diff.for.move.title", beforePath, afterPath);
183     } else {
184       return SvnBundle.message("action.Subversion.properties.difference.diff.title", ChangesUtil.getFilePath(change).getPath());
185     }
186   }
187
188   private static int compareRevisions(@NotNull SVNRevision revision1, @NotNull SVNRevision revision2) {
189     if (revision1.equals(revision2)) {
190       return 0;
191     }
192     // working(local) ahead of head
193     if (SVNRevision.WORKING.equals(revision1)) {
194       return 1;
195     }
196     if (SVNRevision.WORKING.equals(revision2)) {
197       return -1;
198     }
199     if (SVNRevision.HEAD.equals(revision1)) {
200       return 1;
201     }
202     if (SVNRevision.HEAD.equals(revision2)) {
203       return -1;
204     }
205     return revision1.getNumber() > revision2.getNumber() ? 1 : -1;
206   }
207
208   @NotNull
209   private static String revisionToString(@Nullable SVNRevision revision) {
210     return revision == null ? "not exists" : revision.toString();
211   }
212
213   private final static String ourPropertiesDelimiter = "\n";
214
215   @NotNull
216   private static List<PropertyData> getPropertyList(@NotNull SvnVcs vcs,
217                                                     @Nullable final ContentRevision contentRevision,
218                                                     @Nullable final SVNRevision revision)
219   throws SVNException, VcsException {
220     if (contentRevision == null) {
221       return Collections.emptyList();
222     }
223
224     SvnTarget target;
225     if (contentRevision instanceof SvnRepositoryContentRevision) {
226       final SvnRepositoryContentRevision svnRevision = (SvnRepositoryContentRevision)contentRevision;
227       target = SvnTarget.fromURL(SVNURL.parseURIEncoded(svnRevision.getFullPath()), revision);
228     } else {
229       final File ioFile = contentRevision.getFile().getIOFile();
230       target = SvnTarget.fromFile(ioFile, revision);
231     }
232
233     return getPropertyList(vcs, target, revision);
234   }
235
236   @NotNull
237   public static List<PropertyData> getPropertyList(@NotNull SvnVcs vcs, @NotNull final SVNURL url, @Nullable final SVNRevision revision)
238     throws VcsException {
239     return getPropertyList(vcs, SvnTarget.fromURL(url, revision), revision);
240   }
241
242   @NotNull
243   public static List<PropertyData> getPropertyList(@NotNull SvnVcs vcs, @NotNull final File ioFile, @Nullable final SVNRevision revision)
244     throws SVNException {
245     try {
246       return getPropertyList(vcs, SvnTarget.fromFile(ioFile, revision), revision);
247     }
248     catch (VcsException e) {
249       throw new SVNException(SVNErrorMessage.create(SVNErrorCode.FS_GENERAL, e), e);
250     }
251   }
252
253   @NotNull
254   private static List<PropertyData> getPropertyList(@NotNull SvnVcs vcs, @NotNull SvnTarget target, @Nullable SVNRevision revision)
255     throws VcsException {
256     final List<PropertyData> lines = new ArrayList<PropertyData>();
257     final PropertyConsumer propertyHandler = createHandler(revision, lines);
258
259     vcs.getFactory(target).createPropertyClient().list(target, revision, Depth.EMPTY, propertyHandler);
260
261     return lines;
262   }
263
264   @NotNull
265   private static PropertyConsumer createHandler(SVNRevision revision, @NotNull final List<PropertyData> lines) {
266     final ProgressIndicator indicator = ProgressManager.getInstance().getProgressIndicator();
267     if (indicator != null) {
268       indicator.checkCanceled();
269       indicator.setText(SvnBundle.message("show.properties.diff.progress.text.revision.information", revision.toString()));
270     }
271
272     return new PropertyConsumer() {
273       public void handleProperty(final File path, final PropertyData property) throws SVNException {
274         registerProperty(property);
275       }
276
277       public void handleProperty(final SVNURL url, final PropertyData property) throws SVNException {
278         registerProperty(property);
279       }
280
281       public void handleProperty(final long revision, final PropertyData property) throws SVNException {
282         // revision properties here
283       }
284
285       private void registerProperty(@NotNull PropertyData property) {
286         if (indicator != null) {
287           indicator.checkCanceled();
288           indicator.setText2(SvnBundle.message("show.properties.diff.progress.text2.property.information", property.getName()));
289         }
290         lines.add(property);
291       }
292     };
293   }
294
295   @NotNull
296   public static String toSortedStringPresentation(@NotNull List<PropertyData> lines) {
297     StringBuilder sb = new StringBuilder();
298
299     Collections.sort(lines, new Comparator<PropertyData>() {
300       public int compare(final PropertyData o1, final PropertyData o2) {
301         return o1.getName().compareTo(o2.getName());
302       }
303     });
304
305     for (PropertyData line : lines) {
306       addPropertyPresentation(line, sb);
307     }
308
309     return sb.toString();
310   }
311
312   private static void addPropertyPresentation(@NotNull PropertyData property, @NotNull StringBuilder sb) {
313     if (sb.length() != 0) {
314       sb.append(ourPropertiesDelimiter);
315     }
316     sb.append(property.getName()).append("=").append(StringUtil.notNullize(PropertyValue.toString(property.getValue())));
317   }
318 }