2 * Copyright 2000-2013 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 com.intellij.openapi.vcs.changes.actions.diff;
18 import com.intellij.diff.DiffContentFactory;
19 import com.intellij.diff.DiffContentFactoryEx;
20 import com.intellij.diff.DiffRequestFactory;
21 import com.intellij.diff.DiffRequestFactoryImpl;
22 import com.intellij.diff.chains.DiffRequestProducer;
23 import com.intellij.diff.chains.DiffRequestProducerException;
24 import com.intellij.diff.contents.DiffContent;
25 import com.intellij.diff.impl.DiffViewerWrapper;
26 import com.intellij.diff.merge.MergeUtil;
27 import com.intellij.diff.requests.DiffRequest;
28 import com.intellij.diff.requests.ErrorDiffRequest;
29 import com.intellij.diff.requests.SimpleDiffRequest;
30 import com.intellij.diff.util.DiffUserDataKeys;
31 import com.intellij.diff.util.DiffUserDataKeysEx;
32 import com.intellij.diff.util.DiffUtil;
33 import com.intellij.diff.util.Side;
34 import com.intellij.openapi.diagnostic.Logger;
35 import com.intellij.openapi.progress.ProcessCanceledException;
36 import com.intellij.openapi.progress.ProgressIndicator;
37 import com.intellij.openapi.project.Project;
38 import com.intellij.openapi.util.Comparing;
39 import com.intellij.openapi.util.Key;
40 import com.intellij.openapi.util.Ref;
41 import com.intellij.openapi.util.UserDataHolder;
42 import com.intellij.openapi.vcs.AbstractVcs;
43 import com.intellij.openapi.vcs.FilePath;
44 import com.intellij.openapi.vcs.VcsDataKeys;
45 import com.intellij.openapi.vcs.VcsException;
46 import com.intellij.openapi.vcs.changes.*;
47 import com.intellij.openapi.vcs.merge.MergeData;
48 import com.intellij.openapi.vfs.LocalFileSystem;
49 import com.intellij.openapi.vfs.VirtualFile;
50 import com.intellij.util.ThreeState;
51 import com.intellij.util.containers.ContainerUtil;
52 import com.intellij.util.ui.UIUtil;
53 import org.jetbrains.annotations.NotNull;
54 import org.jetbrains.annotations.Nullable;
56 import java.io.IOException;
57 import java.util.Collections;
58 import java.util.List;
61 public class ChangeDiffRequestProducer implements DiffRequestProducer {
62 private static final Logger LOG = Logger.getInstance(ChangeDiffRequestProducer.class);
64 public static Key<Change> CHANGE_KEY = Key.create("DiffRequestPresentable.Change");
66 @Nullable private final Project myProject;
67 @NotNull private final Change myChange;
68 @NotNull private final Map<Key, Object> myChangeContext;
70 private ChangeDiffRequestProducer(@Nullable Project project, @NotNull Change change, @NotNull Map<Key, Object> changeContext) {
73 myChangeContext = changeContext;
77 public Change getChange() {
82 public Project getProject() {
88 public String getName() {
89 return ChangesUtil.getFilePath(myChange).getPath();
92 public static boolean isEquals(@NotNull Change change1, @NotNull Change change2) {
93 for (ChangeDiffViewerWrapperProvider provider : ChangeDiffViewerWrapperProvider.EP_NAME.getExtensions()) {
94 ThreeState equals = provider.isEquals(change1, change2);
95 if (equals == ThreeState.NO) return false;
97 for (ChangeDiffRequestProvider provider : ChangeDiffRequestProvider.EP_NAME.getExtensions()) {
98 ThreeState equals = provider.isEquals(change1, change2);
99 if (equals == ThreeState.YES) return true;
100 if (equals == ThreeState.NO) return false;
103 if (!Comparing.equal(change1.getClass(), change2.getClass())) return false;
104 if (!isEquals(change1.getBeforeRevision(), change2.getBeforeRevision())) return false;
105 if (!isEquals(change1.getAfterRevision(), change2.getAfterRevision())) return false;
110 private static boolean isEquals(@Nullable ContentRevision revision1, @Nullable ContentRevision revision2) {
111 if (Comparing.equal(revision1, revision2)) return true;
112 if (revision1 instanceof CurrentContentRevision && revision2 instanceof CurrentContentRevision) {
113 VirtualFile vFile1 = ((CurrentContentRevision)revision1).getVirtualFile();
114 VirtualFile vFile2 = ((CurrentContentRevision)revision2).getVirtualFile();
115 return Comparing.equal(vFile1, vFile2);
121 public static ChangeDiffRequestProducer create(@Nullable Project project, @NotNull Change change) {
122 return create(project, change, Collections.<Key, Object>emptyMap());
126 public static ChangeDiffRequestProducer create(@Nullable Project project,
127 @NotNull Change change,
128 @NotNull Map<Key, Object> changeContext) {
129 if (!canCreate(project, change)) return null;
130 return new ChangeDiffRequestProducer(project, change, changeContext);
133 public static boolean canCreate(@Nullable Project project, @NotNull Change change) {
134 for (ChangeDiffViewerWrapperProvider provider : ChangeDiffViewerWrapperProvider.EP_NAME.getExtensions()) {
135 if (provider.canCreate(project, change)) return true;
137 for (ChangeDiffRequestProvider provider : ChangeDiffRequestProvider.EP_NAME.getExtensions()) {
138 if (provider.canCreate(project, change)) return true;
141 ContentRevision bRev = change.getBeforeRevision();
142 ContentRevision aRev = change.getAfterRevision();
144 if (bRev == null && aRev == null) return false;
145 if (bRev != null && bRev.getFile().isDirectory()) return false;
146 if (aRev != null && aRev.getFile().isDirectory()) return false;
153 public DiffRequest process(@NotNull UserDataHolder context,
154 @NotNull ProgressIndicator indicator) throws DiffRequestProducerException, ProcessCanceledException {
156 return loadCurrentContents(context, indicator);
158 catch (ProcessCanceledException e) {
161 catch (DiffRequestProducerException e) {
164 catch (Exception e) {
166 throw new DiffRequestProducerException(e.getMessage());
171 protected DiffRequest loadCurrentContents(@NotNull UserDataHolder context,
172 @NotNull ProgressIndicator indicator) throws DiffRequestProducerException {
173 DiffRequestProducerException wrapperException = null;
174 DiffRequestProducerException requestException = null;
176 DiffViewerWrapper wrapper = null;
178 for (ChangeDiffViewerWrapperProvider provider : ChangeDiffViewerWrapperProvider.EP_NAME.getExtensions()) {
179 if (provider.canCreate(myProject, myChange)) {
180 wrapper = provider.process(this, context, indicator);
185 catch (DiffRequestProducerException e) {
186 wrapperException = e;
189 DiffRequest request = null;
191 for (ChangeDiffRequestProvider provider : ChangeDiffRequestProvider.EP_NAME.getExtensions()) {
192 if (provider.canCreate(myProject, myChange)) {
193 request = provider.process(this, context, indicator);
197 if (request == null) request = createRequest(myProject, myChange, context, indicator);
199 catch (DiffRequestProducerException e) {
200 requestException = e;
203 if (requestException != null && wrapperException != null) {
204 String message = requestException.getMessage() + "\n\n" + wrapperException.getMessage();
205 throw new DiffRequestProducerException(message);
207 if (requestException != null) {
208 request = new ErrorDiffRequest(getRequestTitle(myChange), requestException);
209 LOG.info("Request: " + requestException.getMessage());
211 if (wrapperException != null) {
212 LOG.info("Wrapper: " + wrapperException.getMessage());
215 request.putUserData(CHANGE_KEY, myChange);
216 request.putUserData(DiffViewerWrapper.KEY, wrapper);
218 for (Map.Entry<Key, Object> entry : myChangeContext.entrySet()) {
219 request.putUserData(entry.getKey(), entry.getValue());
222 DiffUtil.putDataKey(request, VcsDataKeys.CURRENT_CHANGE, myChange);
228 private DiffRequest createRequest(@Nullable Project project,
229 @NotNull Change change,
230 @NotNull UserDataHolder context,
231 @NotNull ProgressIndicator indicator) throws DiffRequestProducerException {
232 if (ChangesUtil.isTextConflictingChange(change)) { // three side diff
233 // FIXME: This part is ugly as a VCS merge subsystem itself.
235 FilePath path = ChangesUtil.getFilePath(change);
236 VirtualFile file = path.getVirtualFile();
238 file = LocalFileSystem.getInstance().refreshAndFindFileByPath(path.getPath());
240 if (file == null) throw new DiffRequestProducerException("Can't show merge conflict - file not found");
242 if (project == null) {
243 throw new DiffRequestProducerException("Can't show merge conflict - project is unknown");
245 final AbstractVcs vcs = ChangesUtil.getVcsForChange(change, project);
246 if (vcs == null || vcs.getMergeProvider() == null) {
247 throw new DiffRequestProducerException("Can't show merge conflict - operation nos supported");
250 // FIXME: loadRevisions() can call runProcessWithProgressSynchronously() inside
251 final Ref<Throwable> exceptionRef = new Ref<>();
252 final Ref<MergeData> mergeDataRef = new Ref<>();
253 final VirtualFile finalFile = file;
254 UIUtil.invokeAndWaitIfNeeded(new Runnable() {
258 mergeDataRef.set(vcs.getMergeProvider().loadRevisions(finalFile));
260 catch (VcsException e) {
265 if (!exceptionRef.isNull()) {
266 Throwable e = exceptionRef.get();
267 if (e instanceof VcsException) throw (VcsException)e;
268 if (e instanceof Error) throw (Error)e;
269 if (e instanceof RuntimeException) throw (RuntimeException)e;
270 throw new RuntimeException(e);
272 MergeData mergeData = mergeDataRef.get();
274 ContentRevision bRev = change.getBeforeRevision();
275 ContentRevision aRev = change.getAfterRevision();
276 String beforeRevisionTitle = getRevisionTitle(bRev, "Your version");
277 String afterRevisionTitle = getRevisionTitle(aRev, "Server version");
279 String title = DiffRequestFactory.getInstance().getTitle(file);
280 List<String> titles = ContainerUtil.list(beforeRevisionTitle, "Base Version", afterRevisionTitle);
282 DiffContentFactory contentFactory = DiffContentFactory.getInstance();
283 List<DiffContent> contents = ContainerUtil.list(
284 contentFactory.createFromBytes(project, mergeData.CURRENT, file),
285 contentFactory.createFromBytes(project, mergeData.ORIGINAL, file),
286 contentFactory.createFromBytes(project, mergeData.LAST, file)
289 SimpleDiffRequest request = new SimpleDiffRequest(title, contents, titles);
290 MergeUtil.putRevisionInfos(request, mergeData);
294 catch (VcsException e) {
296 throw new DiffRequestProducerException(e);
298 catch (IOException e) {
300 throw new DiffRequestProducerException(e);
304 ContentRevision bRev = change.getBeforeRevision();
305 ContentRevision aRev = change.getAfterRevision();
307 if (bRev == null && aRev == null) {
308 LOG.warn("Both revision contents are empty");
309 throw new DiffRequestProducerException("Bad revisions contents");
311 if (bRev != null) checkContentRevision(project, bRev, context, indicator);
312 if (aRev != null) checkContentRevision(project, aRev, context, indicator);
314 String title = getRequestTitle(change);
316 indicator.setIndeterminate(true);
317 DiffContent content1 = createContent(project, bRev, context, indicator);
318 DiffContent content2 = createContent(project, aRev, context, indicator);
320 final String userLeftRevisionTitle = (String)myChangeContext.get(DiffUserDataKeysEx.VCS_DIFF_LEFT_CONTENT_TITLE);
321 String beforeRevisionTitle = userLeftRevisionTitle != null ? userLeftRevisionTitle : getRevisionTitle(bRev, "Base version");
322 final String userRightRevisionTitle = (String)myChangeContext.get(DiffUserDataKeysEx.VCS_DIFF_RIGHT_CONTENT_TITLE);
323 String afterRevisionTitle = userRightRevisionTitle != null ? userRightRevisionTitle : getRevisionTitle(aRev, "Your version");
325 SimpleDiffRequest request = new SimpleDiffRequest(title, content1, content2, beforeRevisionTitle, afterRevisionTitle);
327 boolean bRevCurrent = bRev instanceof CurrentContentRevision;
328 boolean aRevCurrent = aRev instanceof CurrentContentRevision;
329 if (bRevCurrent && !aRevCurrent) request.putUserData(DiffUserDataKeys.MASTER_SIDE, Side.LEFT);
330 if (!bRevCurrent && aRevCurrent) request.putUserData(DiffUserDataKeys.MASTER_SIDE, Side.RIGHT);
337 public static String getRequestTitle(@NotNull Change change) {
338 ContentRevision bRev = change.getBeforeRevision();
339 ContentRevision aRev = change.getAfterRevision();
341 assert bRev != null || aRev != null;
342 if (bRev != null && aRev != null) {
343 FilePath bPath = bRev.getFile();
344 FilePath aPath = aRev.getFile();
345 if (bPath.equals(aPath)) {
346 return DiffRequestFactoryImpl.getContentTitle(bPath);
349 return DiffRequestFactoryImpl.getTitle(bPath, aPath, " -> ");
352 else if (bRev != null) {
353 return DiffRequestFactoryImpl.getContentTitle(bRev.getFile());
356 return DiffRequestFactoryImpl.getContentTitle(aRev.getFile());
361 public static String getRevisionTitle(@Nullable ContentRevision revision, @NotNull String defaultValue) {
362 if (revision == null) return defaultValue;
363 String title = revision.getRevisionNumber().asString();
364 if (title == null || title.isEmpty()) return defaultValue;
369 public static DiffContent createContent(@Nullable Project project,
370 @Nullable ContentRevision revision,
371 @NotNull UserDataHolder context,
372 @NotNull ProgressIndicator indicator) throws DiffRequestProducerException {
374 indicator.checkCanceled();
376 if (revision == null) return DiffContentFactory.getInstance().createEmpty();
377 FilePath filePath = revision.getFile();
378 DiffContentFactoryEx contentFactory = DiffContentFactoryEx.getInstanceEx();
380 if (revision instanceof CurrentContentRevision) {
381 VirtualFile vFile = ((CurrentContentRevision)revision).getVirtualFile();
382 if (vFile == null) throw new DiffRequestProducerException("Can't get current revision content");
383 return contentFactory.create(project, vFile);
386 if (revision instanceof BinaryContentRevision) {
387 byte[] content = ((BinaryContentRevision)revision).getBinaryContent();
388 if (content == null) {
389 throw new DiffRequestProducerException("Can't get binary revision content");
391 return contentFactory.createBinary(project, content, filePath.getFileType(), filePath.getName());
394 if (revision instanceof ByteBackedContentRevision) {
395 byte[] revisionContent = ((ByteBackedContentRevision)revision).getContentAsBytes();
396 if (revisionContent == null) throw new DiffRequestProducerException("Can't get revision content");
397 return contentFactory.createFromBytes(project, revisionContent, filePath);
400 String revisionContent = revision.getContent();
401 if (revisionContent == null) throw new DiffRequestProducerException("Can't get revision content");
402 return contentFactory.create(project, revisionContent, filePath);
405 catch (IOException e) {
407 throw new DiffRequestProducerException(e);
409 catch (VcsException e) {
411 throw new DiffRequestProducerException(e);
415 public static void checkContentRevision(@Nullable Project project,
416 @NotNull ContentRevision rev,
417 @NotNull UserDataHolder context,
418 @NotNull ProgressIndicator indicator) throws DiffRequestProducerException {
419 if (rev.getFile().isDirectory()) {
420 throw new DiffRequestProducerException("Can't show diff for directory");
425 public boolean equals(Object o) {
426 if (this == o) return true;
427 if (o == null || getClass() != o.getClass()) return false;
429 ChangeDiffRequestProducer that = (ChangeDiffRequestProducer)o;
431 return myChange.equals(that.myChange);
435 public int hashCode() {
436 return myChange.hashCode();