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.
16 package org.jetbrains.idea.svn.actions;
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.*;
33 import org.jetbrains.annotations.NotNull;
34 import org.jetbrains.annotations.Nullable;
35 import org.jetbrains.idea.svn.SvnBundle;
36 import org.jetbrains.idea.svn.SvnRevisionNumber;
37 import org.jetbrains.idea.svn.SvnVcs;
38 import org.jetbrains.idea.svn.api.Depth;
39 import org.jetbrains.idea.svn.difftool.properties.SvnPropertiesDiffRequest;
40 import org.jetbrains.idea.svn.difftool.properties.SvnPropertiesDiffRequest.PropertyContent;
41 import org.jetbrains.idea.svn.history.SvnRepositoryContentRevision;
42 import org.jetbrains.idea.svn.properties.PropertyConsumer;
43 import org.jetbrains.idea.svn.properties.PropertyData;
44 import org.jetbrains.idea.svn.properties.PropertyValue;
45 import org.tmatesoft.svn.core.SVNErrorCode;
46 import org.tmatesoft.svn.core.SVNErrorMessage;
47 import org.tmatesoft.svn.core.SVNException;
48 import org.tmatesoft.svn.core.SVNURL;
49 import org.tmatesoft.svn.core.wc.SVNRevision;
50 import org.tmatesoft.svn.core.wc2.SvnTarget;
53 import java.util.ArrayList;
54 import java.util.Collections;
55 import java.util.Comparator;
56 import java.util.List;
58 import static com.intellij.util.ObjectUtils.notNull;
59 import static com.intellij.util.containers.ContainerUtil.exists;
61 public class ShowPropertiesDiffAction extends AnAction implements DumbAware {
64 public void update(@NotNull AnActionEvent e) {
65 Change[] changes = e.getData(VcsDataKeys.CHANGES);
66 boolean showAction = checkThatChangesAreUnderSvn(changes);
68 e.getPresentation().setVisible(changes != null && showAction);
69 e.getPresentation().setEnabled(showAction);
72 private static boolean checkThatChangesAreUnderSvn(@Nullable Change[] changes) {
73 return changes != null && exists(changes, change -> isUnderSvn(change.getBeforeRevision()) || isUnderSvn(change.getAfterRevision()));
76 private static boolean isUnderSvn(@Nullable ContentRevision revision) {
77 return revision instanceof MarkerVcsContentRevision && SvnVcs.getKey().equals(((MarkerVcsContentRevision)revision).getVcsKey());
81 public void actionPerformed(@NotNull AnActionEvent e) {
82 Change[] changes = e.getData(VcsDataKeys.CHANGE_LEAD_SELECTION);
84 if (checkThatChangesAreUnderSvn(changes)) {
85 new CalculateAndShow(e.getProject(), changes[0], e.getPresentation().getText()).queue();
89 private static class CalculateAndShow extends Task.Backgroundable {
90 private final Change myChange;
91 private List<PropertyData> myBeforeContent;
92 private List<PropertyData> myAfterContent;
93 private SVNRevision myBeforeRevisionValue;
94 private SVNRevision myAfterRevision;
95 private Exception myException;
96 private final String myErrorTitle;
98 private CalculateAndShow(@Nullable final Project project, final Change change, final String errorTitle) {
99 super(project, SvnBundle.message("fetching.properties.contents.progress.title"), true, PerformInBackgroundOption.DEAF);
101 myErrorTitle = errorTitle;
104 public void run(@NotNull final ProgressIndicator indicator) {
105 final SvnVcs vcs = SvnVcs.getInstance(myProject);
108 myBeforeRevisionValue = getBeforeRevisionValue(myChange);
109 myAfterRevision = getAfterRevisionValue(myChange);
111 myBeforeContent = getPropertyList(vcs, myChange.getBeforeRevision(), myBeforeRevisionValue);
112 indicator.checkCanceled();
113 // gets exactly WORKING revision property
114 myAfterContent = getPropertyList(vcs, myChange.getAfterRevision(), myAfterRevision);
116 catch (SVNException exc) {
119 catch (VcsException exc) {
125 public void onSuccess() {
126 if (myException != null) {
127 Messages.showErrorDialog(myException.getMessage(), myErrorTitle);
130 if (myBeforeContent != null && myAfterContent != null && myBeforeRevisionValue != null && myAfterRevision != null) {
131 SvnPropertiesDiffRequest diffRequest;
132 if (compareRevisions(myBeforeRevisionValue, myAfterRevision) > 0) {
133 diffRequest = new SvnPropertiesDiffRequest(getDiffWindowTitle(myChange),
134 new PropertyContent(myAfterContent), new PropertyContent(myBeforeContent),
135 revisionToString(myAfterRevision), revisionToString(myBeforeRevisionValue));
138 diffRequest = new SvnPropertiesDiffRequest(getDiffWindowTitle(myChange),
139 new PropertyContent(myBeforeContent), new PropertyContent(myAfterContent),
140 revisionToString(myBeforeRevisionValue), revisionToString(myAfterRevision));
142 DiffManager.getInstance().showDiff(myProject, diffRequest);
148 private static SVNRevision getBeforeRevisionValue(@NotNull Change change) throws SVNException {
149 ContentRevision beforeRevision = change.getBeforeRevision();
150 if (beforeRevision != null) {
151 return ((SvnRevisionNumber)beforeRevision.getRevisionNumber()).getRevision();
154 return SVNRevision.create(((SvnRevisionNumber)notNull(change.getAfterRevision()).getRevisionNumber()).getRevision().getNumber() - 1);
159 private static SVNRevision getAfterRevisionValue(@NotNull Change change) throws SVNException {
160 ContentRevision afterRevision = change.getAfterRevision();
161 if (afterRevision != null) {
162 // CurrentContentRevision will be here, for instance, if invoked from changes dialog for "Compare with Branch" action
163 return afterRevision instanceof CurrentContentRevision
164 ? SVNRevision.WORKING
165 : ((SvnRevisionNumber)afterRevision.getRevisionNumber()).getRevision();
168 return SVNRevision.create(((SvnRevisionNumber)notNull(change.getBeforeRevision()).getRevisionNumber()).getRevision().getNumber() + 1);
173 private static String getDiffWindowTitle(@NotNull Change change) {
174 if (change.isMoved() || change.isRenamed()) {
175 final FilePath beforeFilePath = ChangesUtil.getBeforePath(change);
176 final FilePath afterFilePath = ChangesUtil.getAfterPath(change);
178 final String beforePath = beforeFilePath == null ? "" : beforeFilePath.getPath();
179 final String afterPath = afterFilePath == null ? "" : afterFilePath.getPath();
180 return SvnBundle.message("action.Subversion.properties.difference.diff.for.move.title", beforePath, afterPath);
182 return SvnBundle.message("action.Subversion.properties.difference.diff.title", ChangesUtil.getFilePath(change).getPath());
186 private static int compareRevisions(@NotNull SVNRevision revision1, @NotNull SVNRevision revision2) {
187 if (revision1.equals(revision2)) {
190 // working(local) ahead of head
191 if (SVNRevision.WORKING.equals(revision1)) {
194 if (SVNRevision.WORKING.equals(revision2)) {
197 if (SVNRevision.HEAD.equals(revision1)) {
200 if (SVNRevision.HEAD.equals(revision2)) {
203 return revision1.getNumber() > revision2.getNumber() ? 1 : -1;
207 private static String revisionToString(@Nullable SVNRevision revision) {
208 return revision == null ? "not exists" : revision.toString();
211 private final static String ourPropertiesDelimiter = "\n";
214 private static List<PropertyData> getPropertyList(@NotNull SvnVcs vcs,
215 @Nullable final ContentRevision contentRevision,
216 @Nullable final SVNRevision revision)
217 throws SVNException, VcsException {
218 if (contentRevision == null) {
219 return Collections.emptyList();
223 if (contentRevision instanceof SvnRepositoryContentRevision) {
224 final SvnRepositoryContentRevision svnRevision = (SvnRepositoryContentRevision)contentRevision;
225 target = SvnTarget.fromURL(SVNURL.parseURIEncoded(svnRevision.getFullPath()), revision);
227 final File ioFile = contentRevision.getFile().getIOFile();
228 target = SvnTarget.fromFile(ioFile, revision);
231 return getPropertyList(vcs, target, revision);
235 public static List<PropertyData> getPropertyList(@NotNull SvnVcs vcs, @NotNull final SVNURL url, @Nullable final SVNRevision revision)
236 throws VcsException {
237 return getPropertyList(vcs, SvnTarget.fromURL(url, revision), revision);
241 public static List<PropertyData> getPropertyList(@NotNull SvnVcs vcs, @NotNull final File ioFile, @Nullable final SVNRevision revision)
242 throws SVNException {
244 return getPropertyList(vcs, SvnTarget.fromFile(ioFile, revision), revision);
246 catch (VcsException e) {
247 throw new SVNException(SVNErrorMessage.create(SVNErrorCode.FS_GENERAL, e), e);
252 private static List<PropertyData> getPropertyList(@NotNull SvnVcs vcs, @NotNull SvnTarget target, @Nullable SVNRevision revision)
253 throws VcsException {
254 final List<PropertyData> lines = new ArrayList<>();
255 final PropertyConsumer propertyHandler = createHandler(revision, lines);
257 vcs.getFactory(target).createPropertyClient().list(target, revision, Depth.EMPTY, propertyHandler);
263 private static PropertyConsumer createHandler(SVNRevision revision, @NotNull final List<PropertyData> lines) {
264 final ProgressIndicator indicator = ProgressManager.getInstance().getProgressIndicator();
265 if (indicator != null) {
266 indicator.checkCanceled();
267 indicator.setText(SvnBundle.message("show.properties.diff.progress.text.revision.information", revision.toString()));
270 return new PropertyConsumer() {
271 public void handleProperty(final File path, final PropertyData property) throws SVNException {
272 registerProperty(property);
275 public void handleProperty(final SVNURL url, final PropertyData property) throws SVNException {
276 registerProperty(property);
279 public void handleProperty(final long revision, final PropertyData property) throws SVNException {
280 // revision properties here
283 private void registerProperty(@NotNull PropertyData property) {
284 if (indicator != null) {
285 indicator.checkCanceled();
286 indicator.setText2(SvnBundle.message("show.properties.diff.progress.text2.property.information", property.getName()));
294 public static String toSortedStringPresentation(@NotNull List<PropertyData> lines) {
295 StringBuilder sb = new StringBuilder();
297 Collections.sort(lines, Comparator.comparing(PropertyData::getName));
299 for (PropertyData line : lines) {
300 addPropertyPresentation(line, sb);
303 return sb.toString();
306 private static void addPropertyPresentation(@NotNull PropertyData property, @NotNull StringBuilder sb) {
307 if (sb.length() != 0) {
308 sb.append(ourPropertiesDelimiter);
310 sb.append(property.getName()).append("=").append(StringUtil.notNullize(PropertyValue.toString(property.getValue())));