vcs: Combined "TreeConflictRefreshablePanel" and "AbstractRefreshablePanel"
[idea/community.git] / plugins / svn4idea / src / org / jetbrains / idea / svn / treeConflict / TreeConflictRefreshablePanel.java
1 /*
2  * Copyright 2000-2016 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.treeConflict;
17
18 import com.intellij.openapi.CompositeDisposable;
19 import com.intellij.openapi.Disposable;
20 import com.intellij.openapi.application.ApplicationManager;
21 import com.intellij.openapi.diagnostic.Logger;
22 import com.intellij.openapi.fileEditor.FileDocumentManager;
23 import com.intellij.openapi.progress.BackgroundTaskQueue;
24 import com.intellij.openapi.progress.PerformInBackgroundOption;
25 import com.intellij.openapi.progress.ProgressIndicator;
26 import com.intellij.openapi.progress.ProgressManager;
27 import com.intellij.openapi.project.Project;
28 import com.intellij.openapi.ui.MessageType;
29 import com.intellij.openapi.ui.Messages;
30 import com.intellij.openapi.util.Comparing;
31 import com.intellij.openapi.util.Disposer;
32 import com.intellij.openapi.util.io.FileUtil;
33 import com.intellij.openapi.vcs.FilePath;
34 import com.intellij.openapi.vcs.VcsException;
35 import com.intellij.openapi.vcs.changes.Change;
36 import com.intellij.openapi.vcs.changes.ChangesUtil;
37 import com.intellij.openapi.vcs.history.*;
38 import com.intellij.openapi.vcs.ui.VcsBalloonProblemNotifier;
39 import com.intellij.ui.JBColor;
40 import com.intellij.ui.components.JBLoadingPanel;
41 import com.intellij.util.BeforeAfter;
42 import com.intellij.util.containers.Convertor;
43 import com.intellij.util.continuation.ModalityIgnorantBackgroundableTask;
44 import com.intellij.util.ui.JBUI;
45 import com.intellij.util.ui.UIUtil;
46 import com.intellij.util.ui.VcsBackgroundTask;
47 import com.intellij.vcsUtil.VcsUtil;
48 import gnu.trove.TLongArrayList;
49 import org.jetbrains.annotations.CalledInAwt;
50 import org.jetbrains.annotations.CalledInBackground;
51 import org.jetbrains.annotations.NotNull;
52 import org.jetbrains.annotations.Nullable;
53 import org.jetbrains.idea.svn.ConflictedSvnChange;
54 import org.jetbrains.idea.svn.SvnRevisionNumber;
55 import org.jetbrains.idea.svn.SvnVcs;
56 import org.jetbrains.idea.svn.conflict.ConflictAction;
57 import org.jetbrains.idea.svn.conflict.ConflictReason;
58 import org.jetbrains.idea.svn.conflict.ConflictVersion;
59 import org.jetbrains.idea.svn.conflict.TreeConflictDescription;
60 import org.jetbrains.idea.svn.history.SvnHistoryProvider;
61 import org.jetbrains.idea.svn.history.SvnHistorySession;
62 import org.tmatesoft.svn.core.SVNException;
63 import org.tmatesoft.svn.core.wc.SVNRevision;
64
65 import javax.swing.*;
66 import java.awt.*;
67 import java.awt.event.ActionEvent;
68 import java.awt.event.ActionListener;
69 import java.util.Collections;
70 import java.util.List;
71
72 import static com.intellij.openapi.util.Disposer.isDisposed;
73 import static com.intellij.util.ObjectUtils.notNull;
74
75 public class TreeConflictRefreshablePanel implements Disposable {
76
77   private static final Logger LOG = Logger.getInstance(TreeConflictRefreshablePanel.class);
78
79   public static final String TITLE = "Resolve tree conflict";
80   private final ConflictedSvnChange myChange;
81   private final SvnVcs myVcs;
82   private SvnRevisionNumber myCommittedRevision;
83   private FilePath myPath;
84   private final CompositeDisposable myChildDisposables = new CompositeDisposable();
85   private final TLongArrayList myRightRevisionsList;
86   @NotNull private final String myLoadingTitle;
87   @NotNull private final JBLoadingPanel myDetailsPanel;
88   @NotNull private final BackgroundTaskQueue myQueue;
89
90   public TreeConflictRefreshablePanel(@NotNull Project project,
91                                       @NotNull String loadingTitle,
92                                       @NotNull BackgroundTaskQueue queue,
93                                       Change change) {
94     myVcs = SvnVcs.getInstance(project);
95     assert change instanceof ConflictedSvnChange;
96     myChange = (ConflictedSvnChange) change;
97     myPath = ChangesUtil.getFilePath(myChange);
98     myRightRevisionsList = new TLongArrayList();
99
100     myLoadingTitle = loadingTitle;
101     myQueue = queue;
102     myDetailsPanel = new JBLoadingPanel(new BorderLayout(), this);
103     myDetailsPanel.setLoadingText("Loading...");
104   }
105
106   public static boolean descriptionsEqual(TreeConflictDescription d1, TreeConflictDescription d2) {
107     if (d1.isPropertyConflict() != d2.isPropertyConflict()) return false;
108     if (d1.isTextConflict() != d2.isTextConflict()) return false;
109     if (d1.isTreeConflict() != d2.isTreeConflict()) return false;
110
111     if (! d1.getOperation().equals(d2.getOperation())) return false;
112     if (! d1.getConflictAction().equals(d2.getConflictAction())) return false;
113     if (! Comparing.equal(d1.getConflictReason(), d2.getConflictReason())) return false;
114     if (! Comparing.equal(d1.getPath(), d2.getPath())) return false;
115     if (! Comparing.equal(d1.getNodeKind(), d2.getNodeKind())) return false;
116     if (! compareConflictVersion(d1.getSourceLeftVersion(), d2.getSourceLeftVersion())) return false;
117     if (! compareConflictVersion(d1.getSourceRightVersion(), d2.getSourceRightVersion())) return false;
118     return true;
119   }
120
121   private static boolean compareConflictVersion(ConflictVersion v1, ConflictVersion v2) {
122     if (v1 == null && v2 == null) return true;
123     if (v1 == null || v2 == null) return false;
124     if (! v1.getKind().equals(v2.getKind())) return false;
125     if (! v1.getPath().equals(v2.getPath())) return false;
126     if (v1.getPegRevision() != v2.getPegRevision()) return false;
127     if (! Comparing.equal(v1.getRepositoryRoot(), v2.getRepositoryRoot())) return false;
128     return true;
129   }
130
131   @NotNull
132   public JPanel getPanel() {
133     return myDetailsPanel;
134   }
135
136   private BeforeAfter<ConflictSidePresentation> processDescription(TreeConflictDescription description) throws VcsException {
137     if (description == null) return null;
138     if (myChange.getBeforeRevision() != null) {
139       myCommittedRevision = (SvnRevisionNumber)SvnHistorySession.getCurrentCommittedRevision(myVcs,
140               myChange.getBeforeRevision() != null ? myChange.getBeforeRevision().getFile().getIOFile() : myPath.getIOFile());
141     }
142     boolean differentURLs = isDifferentURLs(description);
143
144     ConflictSidePresentation leftSide = null;
145     ConflictSidePresentation rightSide = null;
146     try {
147       if (differentURLs) {
148         leftSide = createSide(description.getSourceLeftVersion(), null, true);
149         rightSide = createSide(description.getSourceRightVersion(), null, false);
150         leftSide.load();
151         rightSide.load();
152       } else {
153         //only one side
154         leftSide = EmptyConflictSide.getInstance();
155         final SVNRevision pegFromLeft;
156         if (description.getSourceLeftVersion() == null) {
157           pegFromLeft = null;
158         }
159         else {
160           long committed = description.getSourceLeftVersion().getPegRevision();
161           if (myCommittedRevision != null && myCommittedRevision.getRevision().getNumber() < committed &&
162             myCommittedRevision.getRevision().isValid()) {
163             committed = myCommittedRevision.getRevision().getNumber();
164           }
165           pegFromLeft = SVNRevision.create(committed);
166         }
167         rightSide = createSide(description.getSourceRightVersion(), pegFromLeft, false);
168         rightSide.load();
169         return new BeforeAfter<>(leftSide, rightSide);
170       }
171     } catch (SVNException e) {
172       throw new VcsException(e);
173     } finally {
174       if (leftSide != null) {
175         myChildDisposables.add(leftSide);
176       }
177       if (rightSide != null) {
178         myChildDisposables.add(rightSide);
179       }
180     }
181
182     return new BeforeAfter<>(leftSide, rightSide);
183   }
184
185   private static boolean isDifferentURLs(TreeConflictDescription description) {
186     return description.getSourceLeftVersion() != null && description.getSourceRightVersion() != null &&
187                 ! Comparing.equal(description.getSourceLeftVersion().getPath(), description.getSourceRightVersion().getPath());
188   }
189
190   private ConflictSidePresentation createSide(ConflictVersion version, final SVNRevision untilThisOther, final boolean isLeft) throws VcsException {
191     if (version == null) return EmptyConflictSide.getInstance();
192     if (myChange.getBeforeRevision() != null && myCommittedRevision != null) {
193       SvnRevisionNumber number = myCommittedRevision;
194       if (isLeft && number.getRevision().isValid() && number.getRevision().getNumber() == version.getPegRevision()) {
195         return EmptyConflictSide.getInstance();
196       }
197     }
198     HistoryConflictSide side = new HistoryConflictSide(myVcs, version, untilThisOther);
199     if (untilThisOther != null && ! isLeft) {
200       side.setListToReportLoaded(myRightRevisionsList);
201     }
202     return side;
203   }
204
205   @CalledInAwt
206   public void refresh() {
207     ApplicationManager.getApplication().assertIsDispatchThread();
208
209     myDetailsPanel.startLoading();
210     myQueue.run(new Loader(myVcs.getProject(), myLoadingTitle));
211   }
212
213   @CalledInBackground
214   @NotNull
215   protected BeforeAfter<BeforeAfter<ConflictSidePresentation>> loadData() throws VcsException {
216     return new BeforeAfter<>(processDescription(myChange.getBeforeDescription()),
217                              processDescription(myChange.getAfterDescription()));
218   }
219
220   @CalledInAwt
221   protected JPanel dataToPresentation(BeforeAfter<BeforeAfter<ConflictSidePresentation>> data) {
222     final JPanel wrapper = new JPanel(new BorderLayout());
223     final JPanel main = new JPanel(new GridBagLayout());
224
225     final GridBagConstraints gb = new GridBagConstraints(0, 0, 1, 1, 1, 0, GridBagConstraints.NORTHWEST, GridBagConstraints.HORIZONTAL,
226                                                          JBUI.insets(1), 0, 0);
227     final String pathComment = myCommittedRevision == null ? "" :
228                                " (current: " +
229                                myChange.getBeforeRevision().getRevisionNumber().asString() +
230                                ", committed: " +
231                                myCommittedRevision.asString() +
232                                ")";
233     final JLabel name = new JLabel(myPath.getName() + pathComment);
234     name.setFont(name.getFont().deriveFont(Font.BOLD));
235     gb.insets.top = 5;
236     main.add(name, gb);
237     ++ gb.gridy;
238     gb.insets.top = 10;
239     appendDescription(myChange.getBeforeDescription(), main, gb, data.getBefore(), myPath.isDirectory());
240     appendDescription(myChange.getAfterDescription(), main, gb, data.getAfter(), myPath.isDirectory());
241     wrapper.add(main, BorderLayout.NORTH);
242     return wrapper;
243   }
244
245   private void appendDescription(TreeConflictDescription description,
246                                  JPanel main,
247                                  GridBagConstraints gb,
248                                  BeforeAfter<ConflictSidePresentation> ba, boolean directory) {
249     if (description == null) return;
250     JLabel descriptionLbl = new JLabel(description.toPresentableString());
251     descriptionLbl.setForeground(JBColor.RED);
252     main.add(descriptionLbl, gb);
253     ++ gb.gridy;
254     //buttons
255     gb.insets.top = 0;
256     addResolveButtons(description, main, gb);
257
258     addSide(main, gb, ba.getBefore(), description.getSourceLeftVersion(), "Left", directory);
259     addSide(main, gb, ba.getAfter(), description.getSourceRightVersion(), "Right", directory);
260   }
261
262   private void addResolveButtons(TreeConflictDescription description, JPanel main, GridBagConstraints gb) {
263     final FlowLayout flowLayout = new FlowLayout(FlowLayout.LEFT, 5, 5);
264     JPanel wrapper = new JPanel(flowLayout);
265     final JButton both = new JButton("Both");
266     final JButton merge = new JButton("Merge");
267     final JButton left = new JButton("Accept Yours");
268     final JButton right = new JButton("Accept Theirs");
269     enableAndSetListener(createBoth(description), both);
270     enableAndSetListener(createMerge(description), merge);
271     enableAndSetListener(createLeft(description), left);
272     enableAndSetListener(createRight(description), right);
273     //wrapper.add(both);
274     if (merge.isEnabled()) {
275       wrapper.add(merge);
276     }
277     wrapper.add(left);
278     wrapper.add(right);
279     gb.insets.left = -4;
280     main.add(wrapper, gb);
281     gb.insets.left = 1;
282     ++ gb.gridy;
283   }
284
285   private ActionListener createRight(final TreeConflictDescription description) {
286     return new ActionListener() {
287       @Override
288       public void actionPerformed(ActionEvent e) {
289         int ok = Messages.showOkCancelDialog(myVcs.getProject(), "Accept theirs for " + filePath(myPath) + "?",
290                                              TITLE, Messages.getQuestionIcon());
291         if (Messages.OK != ok) return;
292         FileDocumentManager.getInstance().saveAllDocuments();
293         final Paths paths = getPaths(description);
294         ProgressManager.getInstance().run(
295           new VcsBackgroundTask<TreeConflictDescription>(myVcs.getProject(), "Accepting theirs for: " + filePath(paths.myMainPath),
296                                                          PerformInBackgroundOption.ALWAYS_BACKGROUND,
297                                                          Collections.singletonList(description),
298                                                          true) {
299             @Override
300             protected void process(TreeConflictDescription d) throws VcsException {
301               new SvnTreeConflictResolver(myVcs, paths.myMainPath, paths.myAdditionalPath).resolveSelectTheirsFull();
302             }
303
304             @Override
305             public void onSuccess() {
306               super.onSuccess();
307               if (executedOk()) {
308                 VcsBalloonProblemNotifier.showOverChangesView(myProject, "Theirs accepted for " + filePath(paths.myMainPath), MessageType.INFO);
309               }
310             }
311           });
312       }
313     };
314   }
315
316   private Paths getPaths(final TreeConflictDescription description) {
317     FilePath mainPath;
318     FilePath additionalPath = null;
319     if (myChange.isMoved() || myChange.isRenamed()) {
320       if (ConflictAction.ADD.equals(description.getConflictAction())) {
321         mainPath = myChange.getAfterRevision().getFile();
322         additionalPath = myChange.getBeforeRevision().getFile();
323       } else {
324         mainPath = myChange.getBeforeRevision().getFile();
325         additionalPath = myChange.getAfterRevision().getFile();
326       }
327     } else {
328       mainPath = myChange.getBeforeRevision() != null ? myChange.getBeforeRevision().getFile() : myChange.getAfterRevision().getFile();
329     }
330     return new Paths(mainPath, additionalPath);
331   }
332
333   private static class Paths {
334     public final FilePath myMainPath;
335     public final FilePath myAdditionalPath;
336
337     private Paths(FilePath mainPath, FilePath additionalPath) {
338       myMainPath = mainPath;
339       myAdditionalPath = additionalPath;
340     }
341   }
342
343   private ActionListener createLeft(final TreeConflictDescription description) {
344     return new ActionListener() {
345       @Override
346       public void actionPerformed(ActionEvent e) {
347         int ok = Messages.showOkCancelDialog(myVcs.getProject(), "Accept yours for " + filePath(myPath) + "?",
348                                              TITLE, Messages.getQuestionIcon());
349         if (Messages.OK != ok) return;
350         FileDocumentManager.getInstance().saveAllDocuments();
351         final Paths paths = getPaths(description);
352         ProgressManager.getInstance().run(
353           new VcsBackgroundTask<TreeConflictDescription>(myVcs.getProject(), "Accepting yours for: " + filePath(paths.myMainPath),
354                                                          PerformInBackgroundOption.ALWAYS_BACKGROUND,
355                                                          Collections.singletonList(description),
356                                                          true) {
357             @Override
358             protected void process(TreeConflictDescription d) throws VcsException {
359               new SvnTreeConflictResolver(myVcs, paths.myMainPath, paths.myAdditionalPath).resolveSelectMineFull();
360             }
361
362             @Override
363             public void onSuccess() {
364               super.onSuccess();
365               if (executedOk()) {
366                 VcsBalloonProblemNotifier.showOverChangesView(myProject, "Yours accepted for " + filePath(paths.myMainPath), MessageType.INFO);
367               }
368             }
369           });
370       }
371     };
372   }
373
374   private ActionListener createMerge(final TreeConflictDescription description) {
375     if (isDifferentURLs(description)) {
376       return null;
377     }
378     // my edit, theirs move or delete
379     if (ConflictAction.EDIT.equals(description.getConflictAction()) && description.getSourceLeftVersion() != null &&
380         ConflictReason.DELETED.equals(description.getConflictReason()) && (myChange.isMoved() || myChange.isRenamed()) &&
381         myCommittedRevision != null) {
382       if (myPath.isDirectory() == description.getSourceRightVersion().isDirectory()) {
383         return createMergeTheirsForFile(description);
384       }
385     }
386     return null;
387   }
388
389   private ActionListener createMergeTheirsForFile(final TreeConflictDescription description) {
390     return new ActionListener() {
391       @Override
392       public void actionPerformed(ActionEvent e) {
393         new MergeFromTheirsResolver(myVcs, description, myChange, myCommittedRevision).execute();
394       }
395     };
396   }
397
398   @NotNull
399   public static String filePath(@NotNull FilePath newFilePath) {
400     return newFilePath.getName() + " (" + notNull(newFilePath.getParentPath()).getPath() + ")";
401   }
402
403   private static ActionListener createBoth(TreeConflictDescription description) {
404     return null;
405   }
406
407   private static void enableAndSetListener(final ActionListener al, final JButton b) {
408     if (al == null) {
409       b.setEnabled(false);
410     }
411     else {
412       b.addActionListener(al);
413     }
414   }
415
416   private void addSide(JPanel main,
417                        GridBagConstraints gb,
418                        ConflictSidePresentation before,
419                        ConflictVersion leftVersion, final String name, boolean directory) {
420     final String leftPresentation = leftVersion == null ? name + ": (" + (directory ? "directory" : "file") +
421       (myChange.getBeforeRevision() == null ? ") added" : ") unversioned") :
422                                     name + ": " + FileUtil.toSystemIndependentName(ConflictVersion.toPresentableString(leftVersion));
423     gb.insets.top = 10;
424     main.add(new JLabel(leftPresentation), gb);
425     ++ gb.gridy;
426     gb.insets.top = 0;
427
428     if (before != null) {
429       JPanel panel = before.createPanel();
430       if (panel != null) {
431         //gb.fill = GridBagConstraints.HORIZONTAL;
432         main.add(panel, gb);
433         //gb.fill = GridBagConstraints.NONE;
434         ++ gb.gridy;
435       }
436     }
437   }
438
439   @Override
440   public void dispose() {
441     Disposer.dispose(myChildDisposables);
442   }
443
444   private interface ConflictSidePresentation extends Disposable {
445     JPanel createPanel();
446     void load() throws SVNException, VcsException;
447   }
448
449   private static class EmptyConflictSide implements ConflictSidePresentation {
450     private static final EmptyConflictSide ourInstance = new EmptyConflictSide();
451
452     public static EmptyConflictSide getInstance() {
453       return ourInstance;
454     }
455
456     @Override
457     public JPanel createPanel() {
458       return null;
459     }
460
461     @Override
462     public void dispose() {
463     }
464
465     @Override
466     public void load() throws SVNException {
467     }
468   }
469
470   private abstract static class AbstractConflictSide<T> implements ConflictSidePresentation, Convertor<T, VcsRevisionNumber> {
471     protected final Project myProject;
472     protected final ConflictVersion myVersion;
473
474     private AbstractConflictSide(Project project, ConflictVersion version) {
475       myProject = project;
476       myVersion = version;
477     }
478   }
479
480   private static class HistoryConflictSide extends AbstractConflictSide<VcsFileRevision> {
481     public static final int LIMIT = 10;
482     private final VcsAppendableHistoryPartnerAdapter mySessionAdapter;
483     private final SvnHistoryProvider myProvider;
484     private final FilePath myPath;
485     private final SvnVcs myVcs;
486     private final SVNRevision myPeg;
487     private FileHistoryPanelImpl myFileHistoryPanel;
488     private TLongArrayList myListToReportLoaded;
489
490     private HistoryConflictSide(SvnVcs vcs, ConflictVersion version, final SVNRevision peg) throws VcsException {
491       super(vcs.getProject(), version);
492       myVcs = vcs;
493       myPeg = peg;
494       try {
495         myPath = VcsUtil.getFilePathOnNonLocal(
496           version.getRepositoryRoot().appendPath(FileUtil.toSystemIndependentName(version.getPath()), true).toString(),
497           version.isDirectory());
498       }
499       catch (SVNException e) {
500         throw new VcsException(e);
501       }
502
503       mySessionAdapter = new VcsAppendableHistoryPartnerAdapter();
504       /*mySessionAdapter.reportCreatedEmptySession(new SvnHistorySession(myVcs, Collections.<VcsFileRevision>emptyList(),
505         myPath, SvnUtil.checkRepositoryVersion15(myVcs, version.getPath()), null, true));*/
506       myProvider = (SvnHistoryProvider) myVcs.getVcsHistoryProvider();
507     }
508
509     public void setListToReportLoaded(TLongArrayList listToReportLoaded) {
510       myListToReportLoaded = listToReportLoaded;
511     }
512
513     @Override
514     public VcsRevisionNumber convert(VcsFileRevision o) {
515       return o.getRevisionNumber();
516     }
517
518     @Override
519     public void load() throws SVNException, VcsException {
520       SVNRevision from = SVNRevision.create(myVersion.getPegRevision());
521       myProvider.reportAppendableHistory(myPath, mySessionAdapter, from, myPeg, myPeg == null ? LIMIT : 0, myPeg, true);
522       VcsAbstractHistorySession session = mySessionAdapter.getSession();
523       if (myListToReportLoaded != null && session != null) {
524         List<VcsFileRevision> list = session.getRevisionList();
525         for (VcsFileRevision revision : list) {
526           myListToReportLoaded.add(((SvnRevisionNumber) revision.getRevisionNumber()).getRevision().getNumber());
527         }
528       }
529     }
530
531     @Override
532     public void dispose() {
533       if (myFileHistoryPanel != null) {
534         Disposer.dispose(myFileHistoryPanel);
535       }
536     }
537
538     @Override
539     public JPanel createPanel() {
540       VcsAbstractHistorySession session = mySessionAdapter.getSession();
541       if (session == null) return EmptyConflictSide.getInstance().createPanel();
542       List<VcsFileRevision> list = session.getRevisionList();
543       if (list.isEmpty()) {
544         return EmptyConflictSide.getInstance().createPanel();
545       }
546       VcsFileRevision last = null;
547       if (! list.isEmpty() && myPeg == null && list.size() == LIMIT ||
548           myPeg != null && myPeg.getNumber() > 0 &&
549           myPeg.equals(((SvnRevisionNumber) list.get(list.size() - 1).getRevisionNumber()).getRevision())) {
550         last = list.remove(list.size() - 1);
551       }
552       myFileHistoryPanel = new FileHistoryPanelImpl(myVcs, myPath, session, myProvider, null, new FileHistoryRefresherI() {
553         @Override
554         public void run(boolean isRefresh, boolean canUseCache) {
555           //we will not refresh
556         }
557
558         @Override
559         public boolean isFirstTime() {
560           return false;
561         }
562       }, true);
563       myFileHistoryPanel.setBottomRevisionForShowDiff(last);
564       myFileHistoryPanel.setBorder(BorderFactory.createLineBorder(UIUtil.getBorderColor()));
565       return myFileHistoryPanel;
566     }
567   }
568
569   private class Loader extends ModalityIgnorantBackgroundableTask {
570     private BeforeAfter<BeforeAfter<ConflictSidePresentation>> myData;
571
572     private Loader(@Nullable Project project, @NotNull String title) {
573       super(project, title, false);
574     }
575
576     @Override
577     protected void doInAwtIfFail(@NotNull Exception e) {
578       Exception cause;
579       if (e instanceof RuntimeException && e.getCause() != null) {
580         cause = (Exception)e.getCause();
581       }
582       else {
583         cause = e;
584       }
585       LOG.info(e);
586       String message = cause.getMessage() == null ? e.getMessage() : cause.getMessage();
587       message = message == null ? "Unknown error" : message;
588       VcsBalloonProblemNotifier.showOverChangesView(myProject, message, MessageType.ERROR);
589     }
590
591     @Override
592     protected void doInAwtIfCancel() {
593     }
594
595     @Override
596     protected void doInAwtIfSuccess() {
597       if (!isDisposed(TreeConflictRefreshablePanel.this)) {
598         myDetailsPanel.add(dataToPresentation(myData));
599         myDetailsPanel.stopLoading();
600       }
601     }
602
603     @Override
604     protected void runImpl(@NotNull ProgressIndicator indicator) {
605       if (!isDisposed(TreeConflictRefreshablePanel.this)) {
606         try {
607           myData = loadData();
608         }
609         catch (VcsException e) {
610           throw new RuntimeException(e);
611         }
612       }
613     }
614   }
615 }