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