VCS: lazy file history
authorirengrig <Irina.Chernushina@jetbrains.com>
Tue, 23 Feb 2010 18:59:16 +0000 (21:59 +0300)
committerirengrig <Irina.Chernushina@jetbrains.com>
Tue, 23 Feb 2010 18:59:16 +0000 (21:59 +0300)
18 files changed:
platform/platform-api/src/com/intellij/ui/dualView/DualView.java
platform/platform-api/src/com/intellij/ui/table/TableView.java
platform/util/src/com/intellij/util/BufferedListConsumer.java
platform/vcs-api/src/com/intellij/openapi/vcs/history/VcsAbstractHistorySession.java [new file with mode: 0644]
platform/vcs-api/src/com/intellij/openapi/vcs/history/VcsAppendableHistorySessionPartner.java [moved from platform/vcs-api/src/com/intellij/openapi/vcs/history/FileHistoryPanel.java with 64% similarity]
platform/vcs-api/src/com/intellij/openapi/vcs/history/VcsHistoryProvider.java
platform/vcs-api/src/com/intellij/openapi/vcs/history/VcsHistorySession.java
platform/vcs-impl/src/com/intellij/openapi/vcs/actions/CompareWithSelectedRevisionAction.java
platform/vcs-impl/src/com/intellij/openapi/vcs/actions/SelectedBlockHistoryAction.java
platform/vcs-impl/src/com/intellij/openapi/vcs/changes/committed/CommittedChangesPanel.java
platform/vcs-impl/src/com/intellij/openapi/vcs/history/FileHistoryPanelImpl.java
platform/vcs-impl/src/com/intellij/openapi/vcs/history/VcsHistoryProviderBackgroundableProxy.java
platform/vcs-impl/src/com/intellij/openapi/vcs/impl/AbstractVcsHelperImpl.java
plugins/cvs/cvs-plugin/src/com/intellij/cvsSupport2/history/CvsHistoryProvider.java
plugins/git4idea/src/git4idea/commands/GitLineHandler.java
plugins/git4idea/src/git4idea/history/GitHistoryProvider.java
plugins/git4idea/src/git4idea/history/GitHistoryUtils.java
plugins/svn4idea/src/org/jetbrains/idea/svn/history/SvnHistoryProvider.java

index 73c3bceb3ed3a6980eeb2f71671c4540dde1ec6e..4be8493975f5e451df9915d2aef1f34293c8886d 100644 (file)
@@ -24,17 +24,17 @@ import com.intellij.openapi.project.Project;
 import com.intellij.openapi.util.IconLoader;
 import com.intellij.ui.ScrollPaneFactory;
 import com.intellij.ui.UIBundle;
-import com.intellij.ui.treeStructure.Tree;
 import com.intellij.ui.table.BaseTableView;
 import com.intellij.ui.table.SelectionProvider;
 import com.intellij.ui.table.TableView;
+import com.intellij.ui.treeStructure.Tree;
+import com.intellij.ui.treeStructure.treetable.ListTreeTableModelOnColumns;
+import com.intellij.ui.treeStructure.treetable.TreeTableModel;
 import com.intellij.util.EditSourceOnDoubleClickHandler;
 import com.intellij.util.config.Storage;
 import com.intellij.util.ui.ColumnInfo;
 import com.intellij.util.ui.ListTableModel;
 import com.intellij.util.ui.Table;
-import com.intellij.ui.treeStructure.treetable.ListTreeTableModelOnColumns;
-import com.intellij.ui.treeStructure.treetable.TreeTableModel;
 import org.jetbrains.annotations.NonNls;
 
 import javax.swing.*;
@@ -392,8 +392,24 @@ public class DualView extends JPanel {
     BaseTableView.store(myTreeStorage, myTreeView);
   }
 
-  public void setRoot(TreeNode node) {
+  public void setRoot(final TreeNode node, final Object selection) {
+    ListTableModel model = myFlatView.getListTableModel();
+    final int column = model.getSortedColumnIndex();
+    final int sortingType = model.getSortingType();
+    final Object obj = myFlatView.getSelectedObject() != null ? myFlatView.getSelectedObject() : selection;
+
     myTreeView.getTreeViewModel().setRoot(node);
+
+    if (column != -1) {
+      model.sortByColumn(column, sortingType);
+    }
+    if (obj != null) {
+      final List items = myFlatView.getItems();
+      if (items.contains(obj)) {
+        final int idx = items.indexOf(obj);
+        setSelectionInterval(idx, idx);
+      }
+    }
   }
 
   public void rebuild() {
index 8dc9f1d61ac90d93acdc57256ca852f63a78a1b8..c708a592a83604594da6c493213cac9e31ad7328 100644 (file)
@@ -73,7 +73,7 @@ public class TableView<Item> extends BaseTableView implements ItemsProvider, Sel
     super.tableChanged(e);
   }
 
-  private void setSelection(Collection<Item> selection) {
+  public void setSelection(Collection<Item> selection) {
     clearSelection();
     for (Iterator iterator = selection.iterator(); iterator.hasNext();) {
       addSelection(iterator.next());
index 78c05897c39ce1b5d98a4d5393b54b0a93bde21f..2cb12a45a5f24c8ca589d065d715cacbcf057ea0 100644 (file)
@@ -20,14 +20,18 @@ import java.util.Collections;
 import java.util.List;
 
 public class BufferedListConsumer<T> implements Consumer<List<T>> {
+  private final int myInterval;
+  private long myTs;
   private final int mySize;
   private final List<T> myBuffer;
   private final Consumer<List<T>> myConsumer;
 
-  public BufferedListConsumer(int size, Consumer<List<T>> consumer) {
+  public BufferedListConsumer(int size, Consumer<List<T>> consumer, int interval) {
     mySize = size;
     myBuffer = new ArrayList<T>();
     myConsumer = consumer;
+    myInterval = interval;
+    myTs = System.currentTimeMillis();
   }
 
   public void consumeOne(final T t) {
@@ -36,10 +40,12 @@ public class BufferedListConsumer<T> implements Consumer<List<T>> {
 
   public void consume(List<T> list) {
     myBuffer.addAll(list);
-    if (mySize <= myBuffer.size()) {
+    final long ts = System.currentTimeMillis();
+    if ((mySize <= myBuffer.size()) || (myInterval > 0) && ((ts - myInterval) > myTs)) {
       myConsumer.consume(new ArrayList<T>(myBuffer));
       myBuffer.clear();
     }
+    myTs = ts;
   }
 
   public void flush() {
diff --git a/platform/vcs-api/src/com/intellij/openapi/vcs/history/VcsAbstractHistorySession.java b/platform/vcs-api/src/com/intellij/openapi/vcs/history/VcsAbstractHistorySession.java
new file mode 100644 (file)
index 0000000..a708f67
--- /dev/null
@@ -0,0 +1,88 @@
+/*
+ * Copyright 2000-2010 JetBrains s.r.o.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.intellij.openapi.vcs.history;
+
+import com.intellij.openapi.util.Comparing;
+import org.jetbrains.annotations.Nullable;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public abstract class VcsAbstractHistorySession implements VcsHistorySession {
+  private final List<VcsFileRevision> myRevisions;
+  private final Object myLock;
+  private VcsRevisionNumber myCachedRevisionNumber;
+
+  protected VcsRevisionNumber getCachedRevision() {
+    synchronized (myLock) {
+      return myCachedRevisionNumber;
+    }
+  }
+
+  protected void setCachedRevision(final VcsRevisionNumber number) {
+    synchronized (myLock) {
+      myCachedRevisionNumber = number;
+    }
+  }
+
+  public VcsAbstractHistorySession(List<VcsFileRevision> revisions) {
+    myLock = new Object();
+    myRevisions = new ArrayList<VcsFileRevision>(revisions);
+    myCachedRevisionNumber = calcCurrentRevisionNumber();
+  }
+
+  protected VcsAbstractHistorySession(List<VcsFileRevision> revisions, VcsRevisionNumber currentRevisionNumber) {
+    myLock = new Object();
+    myRevisions = new ArrayList<VcsFileRevision>(revisions);
+    myCachedRevisionNumber = currentRevisionNumber;
+  }
+
+  public List<VcsFileRevision> getRevisionList() {
+    return myRevisions;
+  }
+
+  /**
+   * This method should return actual value for current revision (it can be changed after submit for example)
+   * @return current file revision, null if file does not exist anymore
+   */
+
+  @Nullable
+  protected abstract VcsRevisionNumber calcCurrentRevisionNumber();
+
+  public final VcsRevisionNumber getCurrentRevisionNumber() {
+    return getCachedRevision();
+  }
+
+  public boolean isCurrentRevision(VcsRevisionNumber rev) {
+    VcsRevisionNumber revNumber = getCurrentRevisionNumber();
+    return revNumber != null && revNumber.compareTo(rev) == 0;
+  }
+
+  public synchronized boolean shouldBeRefreshed() {
+    final VcsRevisionNumber oldValue = getCachedRevision();
+    final VcsRevisionNumber newNumber = calcCurrentRevisionNumber();
+    setCachedRevision(newNumber);
+    return !Comparing.equal(oldValue, newNumber);
+  }
+
+  public boolean allowAsyncRefresh() {
+    return false;
+  }
+
+  public boolean isContentAvailable(VcsFileRevision revision) {
+    return true;
+  }
+}
similarity index 64%
rename from platform/vcs-api/src/com/intellij/openapi/vcs/history/FileHistoryPanel.java
rename to platform/vcs-api/src/com/intellij/openapi/vcs/history/VcsAppendableHistorySessionPartner.java
index 6b7db30b461ca3004fc8492d4790171d746fb09b..c81941e96684bc394f7bc7d6224d6977ae0f8335 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright 2000-2009 JetBrains s.r.o.
+ * Copyright 2000-2010 JetBrains s.r.o.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
  */
 package com.intellij.openapi.vcs.history;
 
-public interface FileHistoryPanel {
-  void refresh();
+import com.intellij.openapi.vcs.VcsException;
+
+public interface VcsAppendableHistorySessionPartner {
+  void reportCreatedEmptySession(VcsAbstractHistorySession session);
+  void acceptRevision(final VcsFileRevision revision);
+  void reportException(final VcsException exception);
+  void finished();
 }
index 8655c2e0b40b72db55a5d3e2bf1ceccf7193615b..5022363b310d95dc14be6d9d0693588fcb46a323 100644 (file)
@@ -29,7 +29,7 @@ public interface VcsHistoryProvider extends VcsProviderMarker {
 
   VcsDependentHistoryComponents getUICustomization(final VcsHistorySession session, final JComponent forShortcutRegistration);
 
-  AnAction[] getAdditionalActions(final FileHistoryPanel panel);
+  AnAction[] getAdditionalActions(final Runnable refresher);
 
   /**
    * Returns whether the history provider submits the custom-formatted date
@@ -53,9 +53,7 @@ public interface VcsHistoryProvider extends VcsProviderMarker {
   @CalledInBackground
   VcsHistorySession createSessionFor(FilePath filePath) throws VcsException;
 
-  //return null if your revisions cannot be tree
-  @Nullable
-  HistoryAsTreeProvider getTreeHistoryProvider();
+  void reportAppendableHistory(final FilePath path, final VcsAppendableHistorySessionPartner partner) throws VcsException;
 
   boolean supportsHistoryForDirectories();
 }
index abae1d52599d6530d29bfb8bcafc1c5f4cbdd9fe..40129efd47f0bb2d6a26f4b35be574b2dc8fbf44 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright 2000-2009 JetBrains s.r.o.
+ * Copyright 2000-2010 JetBrains s.r.o.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
  */
 package com.intellij.openapi.vcs.history;
 
-import com.intellij.openapi.util.Comparing;
 import org.jetbrains.annotations.Nullable;
 
 import java.util.List;
 
-public abstract class VcsHistorySession {
-  private final List<VcsFileRevision> myRevisions;
-  private final Object myLock;
-  private VcsRevisionNumber myCachedRevisionNumber;
-
-  protected VcsRevisionNumber getCachedRevision() {
-    synchronized (myLock) {
-      return myCachedRevisionNumber;
-    }
-  }
-
-  protected void setCachedRevision(final VcsRevisionNumber number) {
-    synchronized (myLock) {
-      myCachedRevisionNumber = number;
-    }
-  }
-
-  public VcsHistorySession(List<VcsFileRevision> revisions) {
-    myLock = new Object();
-    myRevisions = revisions;
-    myCachedRevisionNumber = calcCurrentRevisionNumber();
-  }
-
-  protected VcsHistorySession(List<VcsFileRevision> revisions, VcsRevisionNumber currentRevisionNumber) {
-    myLock = new Object();
-    myRevisions = revisions;
-    myCachedRevisionNumber = currentRevisionNumber;
-  }
-
-  public List<VcsFileRevision> getRevisionList() {
-    return myRevisions;
-  }
-
-  /**
-   * This method should return actual value for current revision (it can be changed after submit for example)
-   * @return current file revision, null if file does not exist anymore
-   */
-
+public interface VcsHistorySession {
+  List<VcsFileRevision> getRevisionList();
+  VcsRevisionNumber getCurrentRevisionNumber();
+  boolean isCurrentRevision(VcsRevisionNumber rev);
+  boolean shouldBeRefreshed();
+  boolean allowAsyncRefresh();
+  boolean isContentAvailable(VcsFileRevision revision);
   @Nullable
-  protected abstract VcsRevisionNumber calcCurrentRevisionNumber();
-
-  public final VcsRevisionNumber getCurrentRevisionNumber() {
-    return getCachedRevision();
-  }
-
-  public boolean isCurrentRevision(VcsRevisionNumber rev) {
-    VcsRevisionNumber revNumber = getCurrentRevisionNumber();
-    return revNumber != null && revNumber.compareTo(rev) == 0;
-  }
-
-  public synchronized boolean refresh() {
-    final VcsRevisionNumber oldValue = getCachedRevision();
-    final VcsRevisionNumber newNumber = calcCurrentRevisionNumber();
-    setCachedRevision(newNumber);
-    return !Comparing.equal(oldValue, newNumber);
-  }
-
-  public boolean allowAsyncRefresh() {
-    return false;
-  }
-
-  public boolean isContentAvailable(VcsFileRevision revision) {
-    return true;
-  }
+  HistoryAsTreeProvider getHistoryAsTreeProvider();
 }
index bb1e74660d1de2e3c1b3987855331dfa5819cac6..bedd65ad47b59387689b5a7219e56e4051e2e55b 100644 (file)
@@ -20,7 +20,10 @@ import com.intellij.openapi.actionSystem.Presentation;
 import com.intellij.openapi.project.Project;
 import com.intellij.openapi.ui.popup.JBPopup;
 import com.intellij.openapi.ui.popup.PopupChooserBuilder;
-import com.intellij.openapi.vcs.*;
+import com.intellij.openapi.vcs.AbstractVcs;
+import com.intellij.openapi.vcs.FilePathImpl;
+import com.intellij.openapi.vcs.ProjectLevelVcsManager;
+import com.intellij.openapi.vcs.VcsBundle;
 import com.intellij.openapi.vcs.diff.DiffProvider;
 import com.intellij.openapi.vcs.history.*;
 import com.intellij.openapi.vcs.impl.VcsBackgroundableActions;
@@ -115,7 +118,7 @@ public class CompareWithSelectedRevisionAction extends AbstractVcsAction {
           public void consume(VcsHistorySession session) {
             if (session == null) return;
             final List<VcsFileRevision> revisions = session.getRevisionList();
-            final HistoryAsTreeProvider treeHistoryProvider = vcsHistoryProvider.getTreeHistoryProvider();
+            final HistoryAsTreeProvider treeHistoryProvider = session.getHistoryAsTreeProvider();
             if (treeHistoryProvider != null) {
               showTreePopup(treeHistoryProvider.createTreeOn(revisions), file, project, vcs.getDiffProvider());
             }
index 79d025a4d5ed91b365a257441eed4b3977c38389..788fc455c0bb13f0096df88973da331a0b41d5c2 100644 (file)
@@ -20,18 +20,21 @@ import com.intellij.openapi.actionSystem.Presentation;
 import com.intellij.openapi.fileEditor.FileDocumentManager;
 import com.intellij.openapi.project.Project;
 import com.intellij.openapi.ui.Messages;
-import com.intellij.openapi.vcs.*;
-import com.intellij.openapi.vcs.impl.ProjectLevelVcsManagerImpl;
-import com.intellij.openapi.vcs.impl.BackgroundableActionEnabledHandler;
-import com.intellij.openapi.vcs.impl.VcsBackgroundableActions;
+import com.intellij.openapi.vcs.AbstractVcs;
+import com.intellij.openapi.vcs.FilePathImpl;
+import com.intellij.openapi.vcs.ProjectLevelVcsManager;
+import com.intellij.openapi.vcs.VcsBundle;
 import com.intellij.openapi.vcs.history.VcsHistoryProvider;
-import com.intellij.openapi.vcs.history.VcsHistorySession;
 import com.intellij.openapi.vcs.history.VcsHistoryProviderBackgroundableProxy;
+import com.intellij.openapi.vcs.history.VcsHistorySession;
 import com.intellij.openapi.vcs.history.impl.VcsBlockHistoryDialog;
+import com.intellij.openapi.vcs.impl.BackgroundableActionEnabledHandler;
+import com.intellij.openapi.vcs.impl.ProjectLevelVcsManagerImpl;
+import com.intellij.openapi.vcs.impl.VcsBackgroundableActions;
 import com.intellij.openapi.vfs.VirtualFile;
+import com.intellij.util.Consumer;
 import com.intellij.vcsUtil.VcsSelection;
 import com.intellij.vcsUtil.VcsSelectionUtil;
-import com.intellij.util.Consumer;
 
 public class SelectedBlockHistoryAction extends AbstractVcsAction {
 
index 57f3aa19d79cc069c4d1ec0ef546ee1800605042..9dbf77a5f28fa2f9220cbbd5af1360bd2bb020f3 100644 (file)
@@ -152,7 +152,7 @@ public class CommittedChangesPanel extends JPanel implements TypeSafeDataProvide
               }.callMe();
             }
           };
-          final BufferedListConsumer<CommittedChangeList> bufferedListConsumer = new BufferedListConsumer<CommittedChangeList>(30, appender);
+          final BufferedListConsumer<CommittedChangeList> bufferedListConsumer = new BufferedListConsumer<CommittedChangeList>(30, appender,-1);
 
           myProvider.loadCommittedChanges(mySettings, myLocation, myMaxCount, new AsynchConsumer<CommittedChangeList>() {
             public void finished() {
index 89801783d78225fb4eeea5dbb994b73143c2462b..61354f08818e6494bb708ad57db7810e701efb40 100644 (file)
@@ -18,8 +18,8 @@ package com.intellij.openapi.vcs.history;
 import com.intellij.history.LocalHistory;
 import com.intellij.history.LocalHistoryAction;
 import com.intellij.openapi.actionSystem.*;
-import com.intellij.openapi.application.Application;
 import com.intellij.openapi.application.ApplicationManager;
+import com.intellij.openapi.application.ModalityState;
 import com.intellij.openapi.command.CommandProcessor;
 import com.intellij.openapi.diagnostic.Logger;
 import com.intellij.openapi.diff.*;
@@ -45,6 +45,7 @@ import com.intellij.openapi.vcs.annotate.AnnotationProvider;
 import com.intellij.openapi.vcs.annotate.FileAnnotation;
 import com.intellij.openapi.vcs.changes.*;
 import com.intellij.openapi.vcs.changes.actions.CreatePatchFromChangesAction;
+import com.intellij.openapi.vcs.changes.committed.AbstractCalledLater;
 import com.intellij.openapi.vcs.changes.issueLinks.IssueLinkHtmlRenderer;
 import com.intellij.openapi.vcs.changes.issueLinks.IssueLinkRenderer;
 import com.intellij.openapi.vcs.changes.issueLinks.TableLinkMouseListener;
@@ -67,10 +68,7 @@ import com.intellij.ui.dualView.CellWrapper;
 import com.intellij.ui.dualView.DualTreeElement;
 import com.intellij.ui.dualView.DualView;
 import com.intellij.ui.dualView.DualViewColumnInfo;
-import com.intellij.util.Alarm;
-import com.intellij.util.Consumer;
-import com.intellij.util.Icons;
-import com.intellij.util.TreeItem;
+import com.intellij.util.*;
 import com.intellij.util.ui.ColumnInfo;
 import com.intellij.util.ui.SortableColumnModel;
 import com.intellij.util.ui.TableViewModel;
@@ -106,7 +104,7 @@ import java.util.List;
 /**
  * author: lesya
  */
-public class FileHistoryPanelImpl<S extends CommittedChangeList, U extends ChangeBrowserSettings> extends PanelWithActionsAndCloseButton implements FileHistoryPanel {
+public class FileHistoryPanelImpl<S extends CommittedChangeList, U extends ChangeBrowserSettings> extends PanelWithActionsAndCloseButton {
   private static final Logger LOG = Logger.getInstance("#com.intellij.cvsSupport2.ui.FileHistoryDialog");
 
   private final JEditorPane myComments;
@@ -119,14 +117,18 @@ public class FileHistoryPanelImpl<S extends CommittedChangeList, U extends Chang
   private final VcsHistoryProvider myProvider;
   private final AnnotationProvider myAnnotationProvider;
   private VcsHistorySession myHistorySession;
-  private final CommittedChangesProvider<S, U> myCommittedChangesProvider;
   private final FilePath myFilePath;
+  private final Runnable myRefresher;
   private final DualView myDualView;
 
   private final Alarm myUpdateAlarm;
 
   private final String myRepositoryPath;
 
+  private boolean myInRefresh;
+  private Object myTargetSelection;
+  private final AsynchConsumer<VcsHistorySession> myHistoryPanelRefresh;
+
   private static final String COMMIT_MESSAGE_TITLE = VcsBundle.message("label.selected.revision.commit.message");
   @NonNls private static final String VCS_HISTORY_ACTIONS_GROUP = "VcsHistoryActionsGroup";
   
@@ -174,6 +176,8 @@ public class FileHistoryPanelImpl<S extends CommittedChangeList, U extends Chang
       return "author_author";
     }
   };
+  private JLabel myLoadingLabel;
+  private Splitter mySplitter;
 
 
   private static class MessageRenderer extends ColoredTableCellRenderer {
@@ -255,14 +259,14 @@ public class FileHistoryPanelImpl<S extends CommittedChangeList, U extends Chang
                               FilePath filePath, final String repositoryPath, VcsHistorySession session,
                               VcsHistoryProvider provider,
                               AnnotationProvider annotationProvider,
-                              ContentManager contentManager, final CommittedChangesProvider<S, U> committedChangesProvider) {
+                              ContentManager contentManager, final Runnable refresher) {
     super(contentManager, provider.getHelpId() != null ? provider.getHelpId() : "reference.versionControl.toolwindow.history");
     myProvider = provider;
     myAnnotationProvider = annotationProvider;
-    myCommittedChangesProvider = committedChangesProvider;
     myRepositoryPath = repositoryPath;
     myProject = project;
-    myHistorySession = session;
+    myRefresher = refresher;
+    myHistorySession = session;         
     myFilePath = filePath;
 
     COLUMNS = createColumnList(project, provider, session);
@@ -277,15 +281,15 @@ public class FileHistoryPanelImpl<S extends CommittedChangeList, U extends Chang
 
     myUpdateAlarm = new Alarm(session.allowAsyncRefresh() ? Alarm.ThreadToUse.SHARED_THREAD : Alarm.ThreadToUse.SWING_THREAD);
 
-    HistoryAsTreeProvider treeHistoryProvider = provider.getTreeHistoryProvider();
+    final HistoryAsTreeProvider treeHistoryProvider = myHistorySession.getHistoryAsTreeProvider();
 
     @NonNls String storageKey = "FileHistory." + provider.getClass().getName();
     if (treeHistoryProvider != null) {
-      myDualView = new DualView(new TreeNodeOnVcsRevision(null, treeHistoryProvider.createTreeOn(myHistorySession.getRevisionList())),
+      myDualView = new DualView(new TreeNodeOnVcsRevision(null, treeHistoryProvider.createTreeOn(new ArrayList<VcsFileRevision>(myHistorySession.getRevisionList()))),
                                 COLUMNS, storageKey, project);
     }
     else {
-      myDualView = new DualView(new TreeNodeOnVcsRevision(null, wrapWithTreeElements(myHistorySession.getRevisionList())), COLUMNS,
+      myDualView = new DualView(new TreeNodeOnVcsRevision(null, wrapWithTreeElements(new ArrayList<VcsFileRevision>(myHistorySession.getRevisionList()))), COLUMNS,
                                 storageKey, project);
       myDualView.switchToTheFlatMode();
     }
@@ -297,33 +301,31 @@ public class FileHistoryPanelImpl<S extends CommittedChangeList, U extends Chang
 
     myPopupActions = createPopupActions();
 
+    myHistoryPanelRefresh = new AsynchConsumer<VcsHistorySession>() {
+      public void finished() {
+        myInRefresh = false;
+        myTargetSelection = null;
+
+        myLoadingLabel.setVisible(false);
+        mySplitter.revalidate();
+        mySplitter.repaint();
+      }
+      public void consume(VcsHistorySession vcsHistorySession) {
+        FileHistoryPanelImpl.this.refresh(vcsHistorySession);
+      }
+    };
+    
     myUpdateAlarm.addRequest(new Runnable() {
       public void run() {
         if (myProject.isDisposed()) {
           return;
         }
-        final boolean refresh = myHistorySession.refresh();
+        final boolean refresh = (! myInRefresh) && myHistorySession.shouldBeRefreshed();
         myUpdateAlarm.cancelAllRequests();
         myUpdateAlarm.addRequest(this, 10000);
 
         if (refresh) {
-          createSession(new Consumer<VcsHistorySession>() {
-            public void consume(final VcsHistorySession session) {
-              if (session != null) {
-                if (session.allowAsyncRefresh()) {
-                  SwingUtilities.invokeLater(new Runnable() {
-                    public void run() {
-                      refresh(session);
-                    }
-                  });
-                }
-                else {
-                  refresh(session);
-                }
-              }
-            }
-          });
-
+          refreshImpl();
         }
       }
     }, 10000);
@@ -333,20 +335,6 @@ public class FileHistoryPanelImpl<S extends CommittedChangeList, U extends Chang
     chooseView();
   }
 
-  private void createSession(final Consumer<VcsHistorySession> consumer) {
-    final Runnable runnable = new Runnable() {
-      public void run() {
-        new VcsHistoryProviderBackgroundableProxy(myProject, getHistoryProvider()).createSessionFor(myFilePath, consumer, null, true);
-      }
-    };
-    final Application application = ApplicationManager.getApplication();
-    if (application.isDispatchThread()) {
-      runnable.run();
-    } else {
-      application.invokeLater(runnable);
-    }
-  }
-
   private void replaceTransferable() {
     final TransferHandler originalTransferHandler = myComments.getTransferHandler();
 
@@ -437,18 +425,19 @@ public class FileHistoryPanelImpl<S extends CommittedChangeList, U extends Chang
     return result;
   }
 
-  private void refresh(VcsHistorySession session) {
+  private void refresh(final VcsHistorySession session) {
     myHistorySession = session;
-    HistoryAsTreeProvider treeHistoryProvider = getHistoryProvider().getTreeHistoryProvider();
+    HistoryAsTreeProvider treeHistoryProvider = session.getHistoryAsTreeProvider();
 
     if (treeHistoryProvider != null) {
-      myDualView.setRoot(new TreeNodeOnVcsRevision(null, treeHistoryProvider.createTreeOn(myHistorySession.getRevisionList())));
+      myDualView.setRoot(new TreeNodeOnVcsRevision(null,
+        treeHistoryProvider.createTreeOn(new ArrayList<VcsFileRevision>(myHistorySession.getRevisionList()))), myTargetSelection);
     }
     else {
-      myDualView.setRoot(new TreeNodeOnVcsRevision(null, wrapWithTreeElements(myHistorySession.getRevisionList())));
+      myDualView.setRoot(new TreeNodeOnVcsRevision(null,
+        wrapWithTreeElements(new ArrayList<VcsFileRevision>(myHistorySession.getRevisionList()))), myTargetSelection);
     }
 
-    myDualView.rebuild();
     myDualView.expandAll();
     myDualView.repaint();
   }
@@ -626,11 +615,11 @@ public class FileHistoryPanelImpl<S extends CommittedChangeList, U extends Chang
 
 
   protected JComponent createCenterPanel() {
-    Splitter splitter = new Splitter(true, getSplitterProportion());
-    splitter.setDividerWidth(4);
+    mySplitter = new Splitter(true, getSplitterProportion());
+    mySplitter.setDividerWidth(4);
     //splitter.getDivider().setBackground(UIUtil.getBgFillColor(splitter.getDivider()).brighter());
 
-    splitter.addPropertyChangeListener(new PropertyChangeListener() {
+    mySplitter.addPropertyChangeListener(new PropertyChangeListener() {
       public void propertyChange(PropertyChangeEvent evt) {
         if (Splitter.PROP_PROPORTION.equals(evt.getPropertyName())) {
           setSplitterProportionTo((Float)evt.getNewValue());
@@ -651,9 +640,16 @@ public class FileHistoryPanelImpl<S extends CommittedChangeList, U extends Chang
     detailsSplitter.setFirstComponent(commentGroup);
     detailsSplitter.setSecondComponent(myAdditionalDetails);
 
-    splitter.setFirstComponent(myDualView);
-    splitter.setSecondComponent(detailsSplitter);
-    return splitter;
+    final JPanel wrapper = new JPanel(new BorderLayout());
+    myLoadingLabel = new JLabel("Loading...");
+    //myLoadingLabel.setVisible(false);
+    myLoadingLabel.setBackground(UIUtil.getToolTipBackground());
+    wrapper.add(myLoadingLabel, BorderLayout.NORTH);
+    wrapper.add(myDualView, BorderLayout.CENTER);
+
+    mySplitter.setFirstComponent(wrapper);
+    mySplitter.setSecondComponent(detailsSplitter);
+    return mySplitter;
   }
 
   private void chooseView() {
@@ -722,7 +718,11 @@ public class FileHistoryPanelImpl<S extends CommittedChangeList, U extends Chang
     });
     result.add(new MyGetVersionAction());
     result.add(new MyAnnotateAction());
-    AnAction[] additionalActions = getHistoryProvider().getAdditionalActions(this);
+    AnAction[] additionalActions = myProvider.getAdditionalActions(new Runnable() {
+      public void run() {
+        refreshImpl();
+      }
+    });
     if (additionalActions != null) {
       for (AnAction additionalAction : additionalActions) {
         result.add(additionalAction);
@@ -737,21 +737,28 @@ public class FileHistoryPanelImpl<S extends CommittedChangeList, U extends Chang
     return result;
   }
 
-  public void refresh() {
-    createSession(new Consumer<VcsHistorySession>() {
-      public void consume(VcsHistorySession session) {
-        if (session == null) return;
-        refresh(session);
+  private void refreshImpl() {
+    new AbstractCalledLater(myProject, ModalityState.NON_MODAL) {
+      public void run() {
+        if (myInRefresh) return;
+        myInRefresh = true;
+        myTargetSelection = myDualView.getFlatView().getSelectedObject();
+
+        myLoadingLabel.setVisible(true);
+        mySplitter.revalidate();
+        mySplitter.repaint();
+
+        myRefresher.run();
       }
-    });
+    }.callMe();
   }
 
-  private boolean supportsTree() {
-    return getHistoryProvider().getTreeHistoryProvider() != null;
+  public AsynchConsumer<VcsHistorySession> getHistoryPanelRefresh() {
+    return myHistoryPanelRefresh;
   }
 
-  private VcsHistoryProvider getHistoryProvider() {
-    return myProvider;
+  private boolean supportsTree() {
+    return myHistorySession != null && myHistorySession.getHistoryAsTreeProvider() != null;
   }
 
   private class MyShowAsTreeAction extends ToggleAction implements DumbAware {
@@ -1274,6 +1281,22 @@ public class FileHistoryPanelImpl<S extends CommittedChangeList, U extends Chang
       return myRevision != VcsFileRevision.NULL;
     }
 
+    @Override
+    public boolean equals(Object o) {
+      if (this == o) return true;
+      if (o == null || getClass() != o.getClass()) return false;
+
+      TreeNodeOnVcsRevision that = (TreeNodeOnVcsRevision)o;
+
+      if (myRevision != null ? !myRevision.getRevisionNumber().equals(that.myRevision.getRevisionNumber()) : that.myRevision != null) return false;
+
+      return true;
+    }
+
+    @Override
+    public int hashCode() {
+      return myRevision != null ? myRevision.getRevisionNumber().hashCode() : 0;
+    }
   }
 
   protected void dispose() {
@@ -1515,8 +1538,14 @@ public class FileHistoryPanelImpl<S extends CommittedChangeList, U extends Chang
     }
 
     public void actionPerformed(AnActionEvent e) {
-      refresh();
+      if (myInRefresh) return;
+      refreshImpl();
+    }
 
+    @Override
+    public void update(AnActionEvent e) {
+      super.update(e);
+      e.getPresentation().setEnabled(! myInRefresh);
     }
   }
 }
index 67136c8063e6ef0670d9bf56ad660a09a07e4987..df0e3a0fa9ca678e083bc99fc3b1ea6ebd53dca3 100644 (file)
  */
 package com.intellij.openapi.vcs.history;
 
+import com.intellij.openapi.application.ApplicationManager;
+import com.intellij.openapi.application.ModalityState;
+import com.intellij.openapi.progress.ProgressIndicator;
+import com.intellij.openapi.progress.ProgressManager;
+import com.intellij.openapi.progress.Task;
 import com.intellij.openapi.project.Project;
 import com.intellij.openapi.util.ThrowableComputable;
 import com.intellij.openapi.vcs.FilePath;
+import com.intellij.openapi.vcs.ProjectLevelVcsManager;
 import com.intellij.openapi.vcs.VcsBundle;
 import com.intellij.openapi.vcs.VcsException;
-import com.intellij.openapi.vcs.impl.VcsBackgroundableComputable;
+import com.intellij.openapi.vcs.changes.BackgroundFromStartOption;
+import com.intellij.openapi.vcs.impl.BackgroundableActionEnabledHandler;
+import com.intellij.openapi.vcs.impl.ProjectLevelVcsManagerImpl;
 import com.intellij.openapi.vcs.impl.VcsBackgroundableActions;
+import com.intellij.openapi.vcs.impl.VcsBackgroundableComputable;
 import com.intellij.util.Consumer;
+import org.jetbrains.annotations.NotNull;
 import org.jetbrains.annotations.Nullable;
 
 public class VcsHistoryProviderBackgroundableProxy {
@@ -53,4 +63,38 @@ public class VcsHistoryProviderBackgroundableProxy {
       VcsBundle.message("message.title.could.not.load.file.history"), throwableComputable, continuation, null);
     }
   }
+
+  public void executeAppendableSession(final FilePath filePath, final VcsAppendableHistorySessionPartner partner, 
+                                       @Nullable VcsBackgroundableActions actionKey, final boolean silent) {
+    final ProjectLevelVcsManagerImpl vcsManager = (ProjectLevelVcsManagerImpl) ProjectLevelVcsManager.getInstance(myProject);
+    final VcsBackgroundableActions resultingActionKey = actionKey == null ? VcsBackgroundableActions.CREATE_HISTORY_SESSION : actionKey;
+
+    final BackgroundableActionEnabledHandler handler;
+    handler = vcsManager.getBackgroundableActionHandler(resultingActionKey);
+    // fo not start same action twice
+    if (handler.isInProgress(resultingActionKey)) return;
+
+    handler.register(resultingActionKey);
+
+    ProgressManager.getInstance().run(new Task.Backgroundable(myProject, VcsBundle.message("loading.file.history.progress"),
+                                                              true, BackgroundFromStartOption.getInstance()) {
+      @Override
+      public void run(@NotNull ProgressIndicator indicator) {
+        try {
+          myDelegate.reportAppendableHistory(filePath, partner);
+        }
+        catch (VcsException e) {
+          partner.reportException(e);
+        }
+        finally {
+          partner.finished();
+          ApplicationManager.getApplication().invokeLater(new Runnable() {
+            public void run() {
+              handler.completed(resultingActionKey);
+            }
+          }, ModalityState.NON_MODAL);
+        }
+      }
+    });
+  }
 }
index 979c75e0a61abc6d466b1d1791d2c52ad6797e60..c13512833287b2f384422c130760d9825eb15fa3 100644 (file)
@@ -35,6 +35,7 @@ import com.intellij.openapi.progress.ProgressManager;
 import com.intellij.openapi.progress.Task;
 import com.intellij.openapi.project.Project;
 import com.intellij.openapi.ui.DialogWrapper;
+import com.intellij.openapi.ui.MessageType;
 import com.intellij.openapi.ui.Messages;
 import com.intellij.openapi.util.Comparing;
 import com.intellij.openapi.util.Disposer;
@@ -117,34 +118,111 @@ public class AbstractVcsHelperImpl extends AbstractVcsHelper {
     showFileHistory(vcsHistoryProvider, null, path, repositoryPath, vcs);
   }
 
-  public void showFileHistory(final VcsHistoryProvider vcsHistoryProvider, final AnnotationProvider annotationProvider, final FilePath path,
-                              final String repositoryPath, final AbstractVcs vcs) {
-    try {
-      new VcsHistoryProviderBackgroundableProxy(myProject, vcsHistoryProvider).createSessionFor(path, new Consumer<VcsHistorySession>() {
-        public void consume(VcsHistorySession session) {
-          if (session == null) return;
-          List<VcsFileRevision> revisionsList = session.getRevisionList();
-          if (revisionsList.isEmpty()) return;
-
-          String actionName = VcsBundle.message("action.name.file.history", path.getName());
+  private static class MyVcsAppendableHistorySessionPartner implements VcsAppendableHistorySessionPartner {
+    private FileHistoryPanelImpl myFileHistoryPanel;
+    private final VcsHistoryProvider myVcsHistoryProvider;
+    private final AnnotationProvider myAnnotationProvider;
+    private final FilePath myPath;
+    private final String myRepositoryPath;
+    private final AbstractVcs myVcs;
+    private final Runnable myRefresher;
+    private VcsAbstractHistorySession mySession;
+    private BufferedListConsumer<VcsFileRevision> myBuffer;
+
+    private MyVcsAppendableHistorySessionPartner(final VcsHistoryProvider vcsHistoryProvider, final AnnotationProvider annotationProvider,
+                                                 final FilePath path,
+                                                 final String repositoryPath,
+                                                 final AbstractVcs vcs,
+                                                 final Runnable refresher) {
+      myVcsHistoryProvider = vcsHistoryProvider;
+      myAnnotationProvider = annotationProvider;
+      myPath = path;
+      myRepositoryPath = repositoryPath;
+      myVcs = vcs;
+      myRefresher = refresher;
+      myBuffer = new BufferedListConsumer<VcsFileRevision>(5, new Consumer<List<VcsFileRevision>>() {
+        public void consume(List<VcsFileRevision> vcsFileRevisions) {
+          mySession.getRevisionList().addAll(vcsFileRevisions);
+          ApplicationManager.getApplication().invokeLater(new Runnable() {
+            public void run() {
+              myFileHistoryPanel.getHistoryPanelRefresh().consume(mySession);
+            }
+          });
+        }
+      }, 1000);
+    }
 
-          ContentManager contentManager = ProjectLevelVcsManagerEx.getInstanceEx(myProject).getContentManager();
+    public void acceptRevision(VcsFileRevision revision) {
+      myBuffer.consumeOne(revision);
+    }
 
-          FileHistoryPanelImpl fileHistoryPanel =
-            new FileHistoryPanelImpl(myProject, path, repositoryPath, session, vcsHistoryProvider, annotationProvider, contentManager,
-                                     vcs.getCommittedChangesProvider());
-          Content content = ContentFactory.SERVICE.getInstance().createContent(fileHistoryPanel, actionName, true);
+    public void reportCreatedEmptySession(final VcsAbstractHistorySession session) {
+      mySession = session;
+      if (myFileHistoryPanel != null) {
+        ApplicationManager.getApplication().invokeLater(new Runnable() {
+          public void run() {
+            myFileHistoryPanel.getHistoryPanelRefresh().consume(mySession);
+          }
+        });
+        return;
+      }
+      ApplicationManager.getApplication().invokeLater(new Runnable() {
+        public void run() {
+          String actionName = VcsBundle.message("action.name.file.history", myPath.getName());
+          ContentManager contentManager = ProjectLevelVcsManagerEx.getInstanceEx(myVcs.getProject()).getContentManager();
+
+          myFileHistoryPanel = new FileHistoryPanelImpl(myVcs.getProject(), myPath, myRepositoryPath, session, myVcsHistoryProvider,
+                                                        myAnnotationProvider, contentManager, myRefresher);
+          Content content = ContentFactory.SERVICE.getInstance().createContent(myFileHistoryPanel, actionName, true);
           ContentsUtil.addOrReplaceContent(contentManager, content, true);
 
-          ToolWindow toolWindow = ToolWindowManager.getInstance(myProject).getToolWindow(ToolWindowId.VCS);
+          ToolWindow toolWindow = ToolWindowManager.getInstance(myVcs.getProject()).getToolWindow(ToolWindowId.VCS);
           toolWindow.activate(null);
         }
-      }, null, false);
+      });
     }
-    catch (Exception exception) {
-      reportError(exception);
+
+    public void reportException(VcsException exception) {
+      ChangesViewBalloonProblemNotifier.showMe(myVcs.getProject(), VcsBundle.message("message.title.could.not.load.file.history") + ": " +
+                                                                   exception.getMessage(), MessageType.ERROR);
     }
 
+    public void finished() {
+      myBuffer.flush();
+      ApplicationManager.getApplication().invokeLater(new Runnable() {
+        public void run() {
+          if (myFileHistoryPanel != null) {
+            myFileHistoryPanel.getHistoryPanelRefresh().finished();
+          }
+        }
+      });
+    }
+  }
+
+  private static class MyRefresher implements Runnable {
+    private MyVcsAppendableHistorySessionPartner mySessionPartner;
+    private final VcsHistoryProvider myVcsHistoryProvider;
+    private final FilePath myPath;
+    private final AbstractVcs myVcs;
+
+    private MyRefresher(final VcsHistoryProvider vcsHistoryProvider, final AnnotationProvider annotationProvider, final FilePath path,
+                                                 final String repositoryPath, final AbstractVcs vcs) {
+      myVcsHistoryProvider = vcsHistoryProvider;
+      myPath = path;
+      myVcs = vcs;
+      mySessionPartner = new MyVcsAppendableHistorySessionPartner(vcsHistoryProvider, annotationProvider, path, repositoryPath, vcs, this);
+    }
+
+    public void run() {
+      final VcsHistoryProviderBackgroundableProxy proxy = new VcsHistoryProviderBackgroundableProxy(myVcs.getProject(), myVcsHistoryProvider);
+      proxy.executeAppendableSession(myPath, mySessionPartner, null, false);
+    }
+  }
+
+  public void showFileHistory(final VcsHistoryProvider vcsHistoryProvider, final AnnotationProvider annotationProvider, final FilePath path,
+                              final String repositoryPath, final AbstractVcs vcs) {
+    final MyRefresher refresher = new MyRefresher(vcsHistoryProvider, annotationProvider, path, repositoryPath, vcs);
+    refresher.run();
   }
 
   public void showRollbackChangesDialog(List<Change> changes) {
@@ -499,7 +577,6 @@ public class AbstractVcsHelperImpl extends AbstractVcsHelper {
         }
       }
       else {
-        // todo
         openCommittedChangesTab(provider, location, settings, 0, title);
       }
     }
@@ -626,7 +703,7 @@ public class AbstractVcsHelperImpl extends AbstractVcsHelper {
     @Override
     public void run(@NotNull final ProgressIndicator indicator) {
       final AsynchConsumer<List<CommittedChangeList>> appender = myDlg.getAppender();
-      final BufferedListConsumer<CommittedChangeList> bufferedListConsumer = new BufferedListConsumer<CommittedChangeList>(10, appender);
+      final BufferedListConsumer<CommittedChangeList> bufferedListConsumer = new BufferedListConsumer<CommittedChangeList>(10, appender, -1);
 
       final Application application = ApplicationManager.getApplication();
       try {
index cc45826a6062e5591a732fd413087c203c51b8ec..4334d852f032fdb8ed4dc3578c3ced46950be685 100644 (file)
@@ -17,9 +17,8 @@ package com.intellij.cvsSupport2.history;
 
 import com.intellij.CvsBundle;
 import com.intellij.cvsSupport2.CvsUtil;
-import com.intellij.cvsSupport2.util.CvsVfsUtil;
-import com.intellij.cvsSupport2.changeBrowser.CvsChangeList;
 import com.intellij.cvsSupport2.application.CvsEntriesManager;
+import com.intellij.cvsSupport2.changeBrowser.CvsChangeList;
 import com.intellij.cvsSupport2.connections.CvsConnectionSettings;
 import com.intellij.cvsSupport2.connections.CvsEnvironment;
 import com.intellij.cvsSupport2.cvsExecution.CvsOperationExecutor;
@@ -28,9 +27,11 @@ import com.intellij.cvsSupport2.cvsExecution.ModalityContext;
 import com.intellij.cvsSupport2.cvshandlers.CommandCvsHandler;
 import com.intellij.cvsSupport2.cvsoperations.cvsLog.LocalPathIndifferentLogOperation;
 import com.intellij.cvsSupport2.cvsoperations.cvsTagOrBranch.ui.TagsPanel;
+import com.intellij.cvsSupport2.util.CvsVfsUtil;
 import com.intellij.openapi.actionSystem.AnAction;
 import com.intellij.openapi.project.Project;
 import com.intellij.openapi.vcs.FilePath;
+import com.intellij.openapi.vcs.VcsException;
 import com.intellij.openapi.vcs.history.*;
 import com.intellij.openapi.vfs.VirtualFile;
 import com.intellij.util.TreeItem;
@@ -157,19 +158,19 @@ public class CvsHistoryProvider implements VcsHistoryProvider {
   public VcsHistorySession createSessionFor(final FilePath filePath) {
     final List<VcsFileRevision> fileRevisionList = createRevisions(filePath);
     if (fileRevisionList == null) return null;
-    return new VcsHistorySession(fileRevisionList) {
+    return new VcsAbstractHistorySession(fileRevisionList) {
       @Nullable
       public VcsRevisionNumber calcCurrentRevisionNumber() {
         return getCurrentRevision(filePath);
       }
 
       @Override
-      public synchronized boolean refresh() {
+      public synchronized boolean shouldBeRefreshed() {
         //noinspection SimplifiableIfStatement
         if (!CvsEntriesManager.getInstance().isActive()) {
           return false;
         }
-        return super.refresh();
+        return super.shouldBeRefreshed();
       }
 
       public boolean isContentAvailable(final VcsFileRevision revision) {
@@ -179,9 +180,19 @@ public class CvsHistoryProvider implements VcsHistoryProvider {
         }
         return super.isContentAvailable(revision);
       }
+
+      public HistoryAsTreeProvider getHistoryAsTreeProvider() {
+        return MyHistoryAsTreeProvider.getInstance();
+      }
     };
   }
 
+  public void reportAppendableHistory(FilePath path, VcsAppendableHistorySessionPartner partner) throws VcsException {
+    // todo some time after ... this could be done
+    final VcsHistorySession session = createSessionFor(path);
+    partner.reportCreatedEmptySession((VcsAbstractHistorySession) session);
+  }
+
   private static VcsRevisionNumber getCurrentRevision(FilePath filePath) {
     Entry entryFor = CvsEntriesManager.getInstance().getEntryFor(filePath.getVirtualFileParent(),
                                                                  filePath.getName());
@@ -238,19 +249,21 @@ public class CvsHistoryProvider implements VcsHistoryProvider {
     return result;
   }
 
-  public AnAction[] getAdditionalActions(final FileHistoryPanel panel) {
+  public AnAction[] getAdditionalActions(final Runnable refresher) {
     return AnAction.EMPTY_ARRAY;
   }
 
-  public HistoryAsTreeProvider getTreeHistoryProvider() {
-    return new MyHistoryAsTreeProvider();
-  }
-
   public boolean supportsHistoryForDirectories() {
     return false;
   }
 
   private static class MyHistoryAsTreeProvider implements HistoryAsTreeProvider {
+    private static MyHistoryAsTreeProvider ourInstance = new MyHistoryAsTreeProvider();
+
+    public static MyHistoryAsTreeProvider getInstance() {
+      return ourInstance;
+    }
+
     public List<TreeItem<VcsFileRevision>> createTreeOn(List<VcsFileRevision> allRevisions) {
       List<VcsFileRevision> sortedRevisions = sortRevisions(allRevisions);
 
index f6b11f2be466a1bba0673fad6274a2e716674ea2..5c670e27daf98463e8411a16dcac998d20ed7180 100644 (file)
@@ -112,6 +112,7 @@ public class GitLineHandler extends GitHandler {
    * @param lineBuilder a line builder
    */
   private void notifyLines(final Key outputType, final Iterator<String> lines, final StringBuilder lineBuilder) {
+    if (! lines.hasNext()) return;
     if (lineBuilder.length() > 0) {
       lineBuilder.append(lines.next());
       if (lines.hasNext()) {
index 7e3a2ae519e9e9fd41af0ee8f095f7330c58baa7..71659451f06fa33f686433bcb25917dc27c97fe5 100644 (file)
@@ -21,12 +21,15 @@ import com.intellij.openapi.project.Project;
 import com.intellij.openapi.vcs.FilePath;
 import com.intellij.openapi.vcs.VcsException;
 import com.intellij.openapi.vcs.history.*;
+import com.intellij.util.Consumer;
 import com.intellij.util.ui.ColumnInfo;
+import git4idea.GitFileRevision;
 import git4idea.actions.GitShowAllSubmittedFilesAction;
 import org.jetbrains.annotations.NotNull;
 import org.jetbrains.annotations.Nullable;
 
 import javax.swing.*;
+import java.util.Collections;
 import java.util.List;
 
 /**
@@ -60,8 +63,9 @@ public class GitHistoryProvider implements VcsHistoryProvider {
 
   /**
    * {@inheritDoc}
+   * @param refresher
    */
-  public AnAction[] getAdditionalActions(FileHistoryPanel panel) {
+  public AnAction[] getAdditionalActions(Runnable refresher) {
     return new AnAction[]{new GitShowAllSubmittedFilesAction(), new GitCopyHistoryRevisionNumberAction()};
   }
 
@@ -89,7 +93,7 @@ public class GitHistoryProvider implements VcsHistoryProvider {
       return null;
     }
     List<VcsFileRevision> revisions = GitHistoryUtils.history(project, filePath);
-    return new VcsHistorySession(revisions) {
+    return new VcsAbstractHistorySession(revisions) {
       @Nullable
       protected VcsRevisionNumber calcCurrentRevisionNumber() {
         try {
@@ -103,15 +107,43 @@ public class GitHistoryProvider implements VcsHistoryProvider {
           return null;
         }
       }
+
+      public HistoryAsTreeProvider getHistoryAsTreeProvider() {
+        return null;
+      }
     };
   }
 
-  /**
-   * {@inheritDoc}
-   */
-  @Nullable
-  public HistoryAsTreeProvider getTreeHistoryProvider() {
-    return null;
+  public void reportAppendableHistory(final FilePath path, final VcsAppendableHistorySessionPartner partner) throws VcsException {
+    final VcsAbstractHistorySession emptySession = new VcsAbstractHistorySession(Collections.<VcsFileRevision>emptyList()) {
+      @Nullable
+      protected VcsRevisionNumber calcCurrentRevisionNumber() {
+        try {
+          return GitHistoryUtils.getCurrentRevision(project, GitHistoryUtils.getLastCommitName(project, path));
+        }
+        catch (VcsException e) {
+          // likely the file is not under VCS anymore.
+          if (log.isDebugEnabled()) {
+            log.debug("Unable to retrieve the current revision number", e);
+          }
+          return null;
+        }
+      }
+
+      public HistoryAsTreeProvider getHistoryAsTreeProvider() {
+        return null;
+      }
+    };
+    partner.reportCreatedEmptySession(emptySession);
+    GitHistoryUtils.history(project, path, new Consumer<GitFileRevision>() {
+      public void consume(GitFileRevision gitFileRevision) {
+        partner.acceptRevision(gitFileRevision);
+      }
+    }, new Consumer<VcsException>() {
+      public void consume(VcsException e) {
+        partner.reportException(e);
+      }
+    });
   }
 
   /**
index ab229b64f4596863e460c624f04147d70a362c39..ca6449eaf18f084a78e151d4ae52dcbe3c4d093d 100644 (file)
@@ -17,6 +17,7 @@ package git4idea.history;
 
 import com.intellij.openapi.diagnostic.Logger;
 import com.intellij.openapi.project.Project;
+import com.intellij.openapi.util.Key;
 import com.intellij.openapi.vcs.FilePath;
 import com.intellij.openapi.vcs.VcsException;
 import com.intellij.openapi.vcs.changes.Change;
@@ -25,10 +26,14 @@ import com.intellij.openapi.vcs.diff.ItemLatestState;
 import com.intellij.openapi.vcs.history.VcsFileRevision;
 import com.intellij.openapi.vcs.history.VcsRevisionNumber;
 import com.intellij.openapi.vfs.VirtualFile;
+import com.intellij.util.Consumer;
+import com.intellij.util.concurrency.Semaphore;
 import com.intellij.util.text.StringTokenizer;
 import com.intellij.vcsUtil.VcsUtil;
 import git4idea.*;
 import git4idea.commands.GitCommand;
+import git4idea.commands.GitLineHandler;
+import git4idea.commands.GitLineHandlerAdapter;
 import git4idea.commands.GitSimpleHandler;
 import org.jetbrains.annotations.Nullable;
 
@@ -115,6 +120,103 @@ public class GitHistoryUtils {
     return new ItemLatestState(new GitRevisionNumber(hash, commitDate), lines[2].charAt(0) != 'D', false);
   }
 
+  public static void history(final Project project, FilePath path, final Consumer<GitFileRevision> consumer,
+                             final Consumer<VcsException> exceptionConsumer) throws VcsException {
+    // adjust path using change manager
+    path = getLastCommitName(project, path);
+    final VirtualFile root = GitUtil.getGitRoot(path);
+    GitLineHandler h = new GitLineHandler(project, root, GitCommand.LOG);
+    h.setNoSSH(true);
+    h.setStdoutSuppressed(true);
+    h.addParameters("-M", "--follow", "--name-only",
+                    "--pretty=format:%H%x00%ct%x00%an%x20%x3C%ae%x3E%x00%cn%x20%x3C%ce%x3E%x00%s%n%n%b%x00", "--encoding=UTF-8");
+    h.endOptions();
+    h.addRelativePaths(path);
+
+    final String prefix = root.getPath() + "/";
+    final MyTokenAccomulator accomulator = new MyTokenAccomulator(6);
+
+    final Consumer<List<String>> resultAdapter = new Consumer<List<String>>() {
+      public void consume(List<String> result) {
+        final GitRevisionNumber revision = new GitRevisionNumber(result.get(0), GitUtil.parseTimestamp(result.get(1)));
+        final String author = GitUtil.adjustAuthorName(result.get(2), result.get(3));
+        final String message = result.get(4).trim();
+
+        String path = "";
+        try {
+          path = GitUtil.unescapePath(result.get(5));
+        }
+        catch (VcsException e) {
+          exceptionConsumer.consume(e);
+        }
+        final FilePath revisionPath = VcsUtil.getFilePathForDeletedFile(prefix + path, false);
+        consumer.consume(new GitFileRevision(project, revisionPath, revision, author, message, null));
+      }
+    };
+
+    final Semaphore semaphore = new Semaphore();
+    h.addLineListener(new GitLineHandlerAdapter() {
+      @Override
+      public void onLineAvailable(String line, Key outputType) {
+        final List<String> result = accomulator.acceptLine(line);
+        if (result != null) {
+          resultAdapter.consume(result);
+        }
+      }
+      @Override
+      public void startFailed(Throwable exception) {
+        exceptionConsumer.consume(new VcsException(exception));
+      }
+
+      @Override
+      public void processTerminated(int exitCode) {
+        super.processTerminated(exitCode);
+        semaphore.up();
+      }
+    });
+    semaphore.down();
+    h.start();
+    semaphore.waitFor();
+  }
+
+  private static class MyTokenAccomulator {
+    private int myCnt;
+    private List<String> mySb;
+    private final int myMax;
+
+    private MyTokenAccomulator(final int max) {
+      myMax = max;
+      mySb = new ArrayList<String>(6);
+      myCnt = 0;
+    }
+
+    @Nullable
+    public List<String> acceptLine(final String s) {
+      StringTokenizer tk = new StringTokenizer(s.trim(), "\u0000", false);
+      List<String> result = null;
+      while (tk.hasMoreElements()) {
+        final String token = tk.nextToken();
+        final List<String> curResult = acceptPieces(token);
+        if (curResult != null) {
+          result = curResult;
+        }
+      }
+      return result;
+    }
+
+    @Nullable
+    private List<String> acceptPieces(final String s) {
+      mySb.add(s);
+      ++ myCnt;
+      if (myMax == myCnt) {
+        myCnt = 0;
+        final List<String> result = mySb;
+        mySb = new ArrayList<String>(6);
+        return result;
+      }
+      return null;
+    }
+  }
 
   /**
    * Get history for the file
index 73430fe5e331bd5502dba0c9127cc4bdb551f222..e82d726bc96117b6e60d19adaaa19dca7930601c 100644 (file)
@@ -33,6 +33,7 @@ import com.intellij.openapi.vfs.VirtualFile;
 import com.intellij.ui.ColoredTableCellRenderer;
 import com.intellij.ui.ScrollPaneFactory;
 import com.intellij.ui.SimpleTextAttributes;
+import com.intellij.util.CollectConsumer;
 import com.intellij.util.Consumer;
 import com.intellij.util.ui.ColumnInfo;
 import com.intellij.util.ui.UIUtil;
@@ -75,10 +76,6 @@ public class SvnHistoryProvider implements VcsHistoryProvider {
     myDirectory = isDirectory;
   }
 
-  public HistoryAsTreeProvider getTreeHistoryProvider() {
-    return null;
-  }
-
   public boolean supportsHistoryForDirectories() {
     return true;
   }
@@ -133,7 +130,7 @@ public class SvnHistoryProvider implements VcsHistoryProvider {
     return new VcsDependentHistoryComponents(columns, listener, addComp);
   }
 
-  private class MyHistorySession extends VcsHistorySession {
+  private class MyHistorySession extends VcsAbstractHistorySession {
     private final FilePath myCommittedPath;
     private final Map<Long, SvnChangeList> myListsMap;
     private final boolean mySupports15;
@@ -143,13 +140,17 @@ public class SvnHistoryProvider implements VcsHistoryProvider {
       myCommittedPath = committedPath;
       mySupports15 = supports15;
       myListsMap = new HashMap<Long, SvnChangeList>();
-      refresh();
+      shouldBeRefreshed();
     }
 
     public Map<Long, SvnChangeList> getListsMap() {
       return myListsMap;
     }
 
+    public HistoryAsTreeProvider getHistoryAsTreeProvider() {
+      return null;
+    }
+
     @Nullable
     public VcsRevisionNumber calcCurrentRevisionNumber() {
       if (myCommittedPath == null) {
@@ -176,17 +177,35 @@ public class SvnHistoryProvider implements VcsHistoryProvider {
   public VcsHistorySession createSessionFor(final FilePath filePath) throws VcsException {
     final FilePath committedPath = ChangesUtil.getCommittedPath(myVcs.getProject(), filePath);
     final Ref<Boolean> supports15Ref = new Ref<Boolean>();
-    final List<VcsFileRevision> revisions = getRevisionsList(committedPath, supports15Ref);
-    if (revisions == null) {
-      return null;
-    }
+    final List<VcsFileRevision> revisions = new ArrayList<VcsFileRevision>();
+    getRevisionsList(committedPath, supports15Ref, new CollectConsumer<VcsFileRevision>(revisions));
     return new MyHistorySession(revisions, committedPath, Boolean.TRUE.equals(supports15Ref.get()));
   }
 
+  public void reportAppendableHistory(FilePath path, final VcsAppendableHistorySessionPartner partner) throws VcsException {
+    final FilePath committedPath = ChangesUtil.getCommittedPath(myVcs.getProject(), path);
+    final Ref<Boolean> supports15Ref = new Ref<Boolean>();
+
+    final MyHistorySession historySession =
+      new MyHistorySession(Collections.<VcsFileRevision>emptyList(), committedPath, Boolean.TRUE.equals(supports15Ref.get()));
+
+    final Ref<Boolean> sessionReported = new Ref<Boolean>();
+
+    getRevisionsList(committedPath, supports15Ref, new Consumer<VcsFileRevision>() {
+      public void consume(VcsFileRevision vcsFileRevision) {
+        if (! Boolean.TRUE.equals(sessionReported.get())) {
+          partner.reportCreatedEmptySession(historySession);
+          sessionReported.set(true);
+        }
+        partner.acceptRevision(vcsFileRevision);
+      }
+    });
+  }
+
   @Nullable
-  private List<VcsFileRevision> getRevisionsList(final FilePath file, final Ref<Boolean> supports15Ref) throws VcsException {
+  private void getRevisionsList(final FilePath file, final Ref<Boolean> supports15Ref,
+                                                 final Consumer<VcsFileRevision> consumer) throws VcsException {
     final SVNException[] exception = new SVNException[1];
-    final ArrayList<VcsFileRevision> result = new ArrayList<VcsFileRevision>();
 
     Runnable command = new Runnable() {
       public void run() {
@@ -196,9 +215,9 @@ public class SvnHistoryProvider implements VcsHistoryProvider {
         }
         try {
           if (myURL == null) {
-            collectLogEntries(indicator, file, exception, result, supports15Ref);
+            collectLogEntries(indicator, file, exception, consumer, supports15Ref);
           } else {
-            collectLogEntriesForRepository(indicator, result, supports15Ref);
+            collectLogEntriesForRepository(indicator, consumer, supports15Ref);
           }
         }
         catch(SVNCancelException ex) {
@@ -215,11 +234,10 @@ public class SvnHistoryProvider implements VcsHistoryProvider {
     if (exception[0] != null) {
       throw new VcsException(exception[0]);
     }
-    return result;
   }
 
   private void collectLogEntries(final ProgressIndicator indicator, FilePath file, SVNException[] exception,
-                                 final ArrayList<VcsFileRevision> result, final Ref<Boolean> supports15Ref) throws SVNException {
+                                 final Consumer<VcsFileRevision> result, final Ref<Boolean> supports15Ref) throws SVNException {
     SVNWCClient wcClient = myVcs.createWCClient();
     SVNInfo info = wcClient.doInfo(new File(file.getIOFile().getAbsolutePath()), SVNRevision.WORKING);
     wcClient.setEventHandler(new ISVNEventHandler() {
@@ -255,7 +273,7 @@ public class SvnHistoryProvider implements VcsHistoryProvider {
                  new MyLogEntryHandler(url, pegRevision, relativeUrl, result));
   }
 
-  private void collectLogEntriesForRepository(final ProgressIndicator indicator, final ArrayList<VcsFileRevision> result,
+  private void collectLogEntriesForRepository(final ProgressIndicator indicator, final Consumer<VcsFileRevision> result,
                                               final Ref<Boolean> supports15Ref) throws SVNException {
     if (indicator != null) {
       indicator.setText2(SvnBundle.message("progress.text2.changes.establishing.connection", myURL.toString()));
@@ -299,7 +317,7 @@ public class SvnHistoryProvider implements VcsHistoryProvider {
     }
   }
 
-  public AnAction[] getAdditionalActions(final FileHistoryPanel panel) {
+  public AnAction[] getAdditionalActions(final Runnable refresher) {
     return new AnAction[]{new ShowAllSubmittedFilesAction(), new MergeSourceDetailsAction()};
   }
 
@@ -310,12 +328,13 @@ public class SvnHistoryProvider implements VcsHistoryProvider {
   private class MyLogEntryHandler implements ISVNLogEntryHandler {
     private final ProgressIndicator myIndicator;
     private String myLastPath;
-    protected final ArrayList<VcsFileRevision> myResult;
+    protected final Consumer<VcsFileRevision> myResult;
+    private VcsFileRevision myPrevious;
     private final SVNRevision myPegRevision;
     private final String myUrl;
     private int myMergeLevel;
 
-    public MyLogEntryHandler(final String url, final SVNRevision pegRevision, String lastPath, final ArrayList<VcsFileRevision> result) {
+    public MyLogEntryHandler(final String url, final SVNRevision pegRevision, String lastPath, final Consumer<VcsFileRevision> result) {
       myLastPath = lastPath;
       myIndicator = ProgressManager.getInstance().getProgressIndicator();
       myResult = result;
@@ -361,9 +380,10 @@ public class SvnHistoryProvider implements VcsHistoryProvider {
       }
       final SvnFileRevision revision = createRevision(logEntry, copyPath);
       if (myMergeLevel >= 0) {
-        addToListByLevel((SvnFileRevision) myResult.get(myResult.size() - 1), revision, myMergeLevel);
+        addToListByLevel((SvnFileRevision) myPrevious, revision, myMergeLevel);
       } else {
-        myResult.add(revision);
+        myResult.consume(revision);
+        myPrevious = revision;
       }
       if (logEntry.hasChildren()) {
         ++ myMergeLevel;
@@ -394,7 +414,7 @@ public class SvnHistoryProvider implements VcsHistoryProvider {
   }
 
   private class RepositoryLogEntryHandler extends MyLogEntryHandler {
-    public RepositoryLogEntryHandler(final String url, final SVNRevision pegRevision, String lastPath, final ArrayList<VcsFileRevision> result) {
+    public RepositoryLogEntryHandler(final String url, final SVNRevision pegRevision, String lastPath, final Consumer<VcsFileRevision> result) {
       super(url, pegRevision, lastPath, result);
     }