git log: good speed step by step load. as is. not ready for use
authorirengrig <Irina.Chernushina@jetbrains.com>
Tue, 14 Sep 2010 09:20:05 +0000 (13:20 +0400)
committerKirill Likhodedov <kirill.likhodedov@jetbrains.com>
Wed, 15 Sep 2010 11:53:06 +0000 (15:53 +0400)
15 files changed:
platform/platform-api/src/com/intellij/ui/TableScrollingUtil.java
platform/util/src/com/intellij/util/BufferedListConsumer.java
platform/util/src/com/intellij/util/containers/SLRUMap.java
plugins/git4idea/src/git4idea/history/GitHistoryUtils.java
plugins/git4idea/src/git4idea/history/browser/LowLevelAccess.java
plugins/git4idea/src/git4idea/history/browser/LowLevelAccessImpl.java
plugins/git4idea/src/git4idea/history/wholeTree/CommitIdsHolder.java [new file with mode: 0644]
plugins/git4idea/src/git4idea/history/wholeTree/GitLogLongPanel.java
plugins/git4idea/src/git4idea/history/wholeTree/LinesProxy.java
plugins/git4idea/src/git4idea/history/wholeTree/LoaderImpl.java
plugins/git4idea/src/git4idea/history/wholeTree/SkeletonBuilder.java
plugins/git4idea/src/git4idea/history/wholeTree/Speedometer.java [new file with mode: 0644]
plugins/git4idea/src/git4idea/history/wholeTree/TreeComposite.java
plugins/git4idea/src/git4idea/history/wholeTree/TreeSkeletonImpl.java
plugins/git4idea/tests/git4idea/tests/CommitIdsTest.java [new file with mode: 0644]

index ec6567a56abf67e15c9a108390c2a48e36109ad6..6f382fdc3667a7a4dd65ef1857000eca009fc305 100644 (file)
@@ -19,11 +19,12 @@ import com.intellij.ide.ui.UISettings;
 import com.intellij.openapi.actionSystem.AnAction;
 import com.intellij.openapi.actionSystem.AnActionEvent;
 import com.intellij.openapi.actionSystem.CustomShortcutSet;
+import com.intellij.openapi.util.Pair;
 
 import javax.swing.*;
 import java.awt.*;
-import java.awt.event.InputEvent;
 import java.awt.event.ActionEvent;
+import java.awt.event.InputEvent;
 import java.awt.event.KeyEvent;
 
 /**
@@ -70,6 +71,11 @@ public class TableScrollingUtil {
     return getTrailingRow(list, visibleRect) - getLeadingRow(list, visibleRect) + 1;
   }
 
+  public static Pair<Integer, Integer> getVisibleRows(JTable list) {
+    Rectangle visibleRect = list.getVisibleRect();
+    return new Pair<Integer, Integer>(getLeadingRow(list, visibleRect) + 1, getTrailingRow(list, visibleRect));
+  }
+
   private static int getLeadingRow(JTable table,Rectangle visibleRect) {
       Point leadingPoint;
 
index 2a3b990d20c87ae1d7b31e6036bb33cd39e5ece9..cfb0e9eb8aae6592685129c08f4ffe4538b25559 100644 (file)
@@ -53,11 +53,15 @@ public class BufferedListConsumer<T> implements Consumer<List<T>> {
     myTs = ts;
   }
 
-  public void flush() {
+  public void flushPart() {
     if (! myBuffer.isEmpty()) {
       myConsumer.consume(new ArrayList<T>(myBuffer));
       myBuffer.clear();
     }
+  }
+
+  public void flush() {
+    flushPart();
     if (myFlushListener != null) {
       myFlushListener.run();
     }
index 8fdf7351edc4ac0d25d1645879c1131b9c8e7343..d16873d7a3f505122b9069f067902d3bc1934bb0 100644 (file)
@@ -26,8 +26,8 @@ import java.util.Map;
 import java.util.Set;
 
 public class SLRUMap<K,V> {
-  private final Map<K,V> myProtectedQueue;
-  private final Map<K,V> myProbationalQueue;
+  protected final Map<K,V> myProtectedQueue;
+  protected final Map<K,V> myProbationalQueue;
 
   private final int myProtectedQueueSize;
   private final int myProbationalQueueSize;
index 59898c1cde009c7d80023b832745b001a6405aa2..f97ce7fd0a2f95cbecfba815d74d929acd2086b8 100644 (file)
@@ -341,6 +341,29 @@ public class GitHistoryUtils {
     h.endOptions();
     h.addRelativePaths(path);
     String output = h.run();
+    return parseCommitsLoadOutput(allBranchesSet, root, output);
+  }
+
+  public static List<GitCommit> commitsDetails(Project project,
+                                                 FilePath path, Set<String> allBranchesSet,
+                                                 final Collection<String> commitsIds) throws VcsException {
+    // adjust path using change manager
+    path = getLastCommitName(project, path);
+    final VirtualFile root = GitUtil.getGitRoot(path);
+    GitSimpleHandler h = new GitSimpleHandler(project, root, GitCommand.SHOW);
+    h.setNoSSH(true);
+    h.setStdoutSuppressed(true);
+    h.addParameters("--name-only",
+                    "--pretty=format:%x03%h%x00%H%x00%ct%x00%an%x00%ae%x00%cn%x00%ce%x00[%p]%x00[%d]%x00%s%n%n%b%x00", "--encoding=UTF-8");
+    h.addParameters(new ArrayList<String>(commitsIds));
+
+    h.endOptions();
+    h.addRelativePaths(path);
+    String output = h.run();
+    return parseCommitsLoadOutput(allBranchesSet, root, output);
+  }
+
+  private static List<GitCommit> parseCommitsLoadOutput(Set<String> allBranchesSet, VirtualFile root, String output) throws VcsException {
     final List<GitCommit> rc = new ArrayList<GitCommit>();
     StringTokenizer tk = new StringTokenizer(output, "\u0003", false);
     final String prefix = root.getPath() + "/";
index 7427f9878b3c7dbb5641a5ac66de5146bee3cbfa..bf34da53a9acd87a1a4b56511b906ccd532ef9fe 100644 (file)
@@ -49,4 +49,5 @@ public interface LowLevelAccess {
   void cherryPick(SHAHash hash) throws VcsException;
   void loadHashesWithParents(final @NotNull Collection<String> startingPoints, @NotNull final Collection<ChangesFilter.Filter> filters,
                                     final Consumer<CommitHashPlusParents> consumer) throws VcsException;
+  List<GitCommit> getCommitDetails(final Collection<String> commitIds) throws VcsException;
 }
index 8af3b358b2008c5e6418ddbda6e2adb2e8b5fc8a..f6d143f8406af2f55bbc3a88f468824e06215e4b 100644 (file)
@@ -92,6 +92,12 @@ public class LowLevelAccessImpl implements LowLevelAccess {
     GitHistoryUtils.hashesWithParents(myProject, new FilePathImpl(myRoot), consumer, parameters.toArray(new String[parameters.size()]));
   }
 
+  @Override
+  public List<GitCommit> getCommitDetails(final Collection<String> commitIds) throws VcsException {
+    // todo branches
+    return GitHistoryUtils.commitsDetails(myProject, new FilePathImpl(myRoot), Collections.<String>emptySet(), commitIds);
+  }
+
   public void loadCommits(final Collection<String> startingPoints, final Date beforePoint, final Date afterPoint,
                              final Collection<ChangesFilter.Filter> filtersIn, final Consumer<GitCommit> consumer,
                              int maxCnt, List<String> branches) throws VcsException {
diff --git a/plugins/git4idea/src/git4idea/history/wholeTree/CommitIdsHolder.java b/plugins/git4idea/src/git4idea/history/wholeTree/CommitIdsHolder.java
new file mode 100644 (file)
index 0000000..4cf6f8b
--- /dev/null
@@ -0,0 +1,137 @@
+/*
+ * 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 git4idea.history.wholeTree;
+
+import com.intellij.util.containers.SLRUMap;
+
+import java.util.*;
+
+/**
+ * @author irengrig
+ */
+public class CommitIdsHolder<Item> {
+  private final MyLruCache<Item, Item> myRequests;
+  private final Object myLock;
+
+  public CommitIdsHolder() {
+    myRequests = new MyLruCache<Item, Item>(100, 50);
+    myLock = new Object();
+  }
+
+  public void add(final Collection<Item> s) {
+    synchronized (myLock) {
+      for (Item hash : s) {
+        myRequests.put(hash, hash);
+      }
+    }
+  }
+
+  public boolean haveData() {
+    synchronized (myLock) {
+      return ! myRequests.isEmpty();
+    }
+  }
+
+  public Collection<Item> get(final int size) {
+    final Set<Item> result = new HashSet<Item>();
+    synchronized (myLock) {
+      final Iterator<Item> iterator = myRequests.iterator();
+      int cnt = 0;
+      for (; iterator.hasNext() && (cnt < size);) {
+        final Item hash = iterator.next();
+        iterator.remove();
+        result.add(hash);
+        ++ cnt;
+      }
+    }
+    return result;
+  }
+
+  private static class MyLruCache<Key, Val> extends SLRUMap<Key, Val> {
+    private MyLruCache(int protectedQueueSize, int probationalQueueSize) {
+      super(protectedQueueSize, probationalQueueSize);
+    }
+
+    public boolean isEmpty() {
+      return myProtectedQueue.keySet().isEmpty() && myProbationalQueue.keySet().isEmpty();
+    }
+
+    public Iterator<Key> iterator() {
+      final List<Iterator<Key>> iterators = new ArrayList<Iterator<Key>>(2);
+      iterators.add(myProtectedQueue.keySet().iterator());
+      iterators.add(myProbationalQueue.keySet().iterator());
+      return new CompositeIterator<Key>(iterators);
+    }
+
+    private static class CompositeIterator<Key> implements Iterator<Key> {
+      private int myPreviousIdx;
+      private int myIdx;
+      private final List<Iterator<Key>> myIterators;
+
+      private CompositeIterator(final List<Iterator<Key>> iterators) {
+        myIterators = iterators;
+        myIdx = -1;
+        myPreviousIdx = -1;
+        for (int i = 0; i < myIterators.size(); i++) {
+          final Iterator<Key> iterator = myIterators.get(i);
+          if (iterator.hasNext()) {
+            myIdx = i;
+            break;
+          }
+        }
+      }
+
+      @Override
+      public boolean hasNext() {
+        return (myIdx >= 0) && myIterators.get(myIdx).hasNext();
+      }
+
+      @Override
+      public Key next() {
+        final Key result = myIterators.get(myIdx).next();
+        recalculateCurrent();
+        return result;
+      }
+
+      private void recalculateCurrent() {
+        if (myIdx == -1) return;
+        if (! myIterators.get(myIdx).hasNext()) {
+          myPreviousIdx = myIdx;
+          myIdx = -1;
+          for (int i = myPreviousIdx; i < myIterators.size(); i++) {
+            final Iterator<Key> iterator = myIterators.get(i);
+            if (iterator.hasNext()) {
+              myIdx = i;
+              break;
+            }
+          }
+        }
+      }
+
+      @Override
+      public void remove() {
+        if ((myPreviousIdx != -1) && (myPreviousIdx != myIdx)) {
+          // last element
+          final Iterator<Key> keyIterator = myIterators.get(myPreviousIdx);
+          keyIterator.remove(); // already on last position
+        } else {
+          myIterators.get(myIdx).remove();
+        }
+        recalculateCurrent();
+      }
+    }
+  }
+}
index 46f9489199e0a38de16af213abdc6fd3091a7d96..2f3ef7bbbde76bcc44133954eac3d55d446d95e6 100644 (file)
@@ -19,6 +19,7 @@ import com.intellij.openapi.application.ModalityState;
 import com.intellij.openapi.project.Project;
 import com.intellij.openapi.ui.DialogWrapper;
 import com.intellij.openapi.util.Comparing;
+import com.intellij.openapi.util.Pair;
 import com.intellij.openapi.util.text.StringUtil;
 import com.intellij.openapi.vcs.CalledInAwt;
 import com.intellij.openapi.vcs.CompoundNumber;
@@ -27,6 +28,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.ui.TableScrollingUtil;
 import com.intellij.ui.table.JBTable;
 import com.intellij.util.ui.ColumnInfo;
 import com.intellij.util.ui.UIUtil;
@@ -38,10 +40,7 @@ import javax.swing.*;
 import javax.swing.event.ChangeEvent;
 import javax.swing.event.ChangeListener;
 import java.awt.*;
-import java.awt.event.FocusAdapter;
-import java.awt.event.FocusEvent;
-import java.awt.event.KeyAdapter;
-import java.awt.event.KeyEvent;
+import java.awt.event.*;
 import java.util.Arrays;
 import java.util.Collection;
 import java.util.Collections;
@@ -63,6 +62,7 @@ public class GitLogLongPanel {
   private JTextField myFilterField;
   private String myPreviousFilter;
   private JLabel myLoading;
+  private MyChangeListener myListener;
 
   // todo for the case when hierarchy is also drawn?
   public GitLogLongPanel(final Project project, final Collection<VirtualFile> roots) {
@@ -109,10 +109,14 @@ public class GitLogLongPanel {
     };
     loaderImpl.setUIRefresh(myUIRefresh);
 
-    initPanel();
+    initPanel(loaderImpl);
   }
 
-  private void initPanel() {
+  public void stop() {
+    myListener.stop();
+  }
+
+  private void initPanel(final LoaderImpl loaderImpl) {
     final JPanel container = new JPanel(new BorderLayout());
     final JPanel menu = new JPanel();
     final BoxLayout layout = new BoxLayout(menu, BoxLayout.X_AXIS);
@@ -128,7 +132,8 @@ public class GitLogLongPanel {
     final JBTable table = new JBTable(myTableModel);
     table.setModel(myTableModel);
     final JScrollPane scrollPane = ScrollPaneFactory.createScrollPane(table);
-    scrollPane.getViewport().addChangeListener(new MyChangeListener());
+    myListener = new MyChangeListener(loaderImpl, table);
+    scrollPane.getViewport().addChangeListener(myListener);
 
     container.add(menu, BorderLayout.NORTH);
     container.add(scrollPane, BorderLayout.CENTER);
@@ -250,6 +255,18 @@ public class GitLogLongPanel {
       return myGitLogLongPanel.myPanel;
     }
 
+    @Override
+    public void doCancelAction() {
+      myGitLogLongPanel.stop();
+      super.doCancelAction();
+    }
+
+    @Override
+    protected void doOKAction() {
+      myGitLogLongPanel.stop();
+      super.doOKAction();
+    }
+
     @Override
     public JComponent getPreferredFocusedComponent() {
       myGitLogLongPanel.setModalityState(ModalityState.current());
@@ -263,9 +280,36 @@ public class GitLogLongPanel {
   }
 
   private static class MyChangeListener implements ChangeListener {
+    private final Speedometer mySpeedometer;
+    private Timer myTimer;
+    private long myRefreshMark;
+
+    private MyChangeListener(final LoaderImpl loaderImpl, final JBTable table) {
+      mySpeedometer = new Speedometer();
+      myRefreshMark = 0;
+      myTimer = new Timer(100, new ActionListener() {
+        @Override
+        public void actionPerformed(ActionEvent e) {
+          //final boolean shouldPing = (System.currentTimeMillis() - myRefreshMark) > 700;
+          final boolean shouldPing = false;
+          if (((mySpeedometer.getSpeed() < 0.1) && mySpeedometer.hasData()) || shouldPing) {
+            myRefreshMark = System.currentTimeMillis();
+            mySpeedometer.clear();
+            final Pair<Integer,Integer> visibleRows = TableScrollingUtil.getVisibleRows(table);
+            loaderImpl.loadCommitDetails(visibleRows.first == 0 ? visibleRows.first : (visibleRows.first - 1), visibleRows.second);
+          }
+        }
+      });
+      myTimer.start();
+    }
+
+    public void stop() {
+      myTimer.stop();
+    }
+
     @Override
     public void stateChanged(ChangeEvent e) {
-      System.out.println(e.toString());
+      mySpeedometer.event();
     }
   }
 }
index 025cc75f1c04821b0e0c1c7a259dacac652fc30f..2f642b0858ac736fee8db0ea35ae29b7b15025e2 100644 (file)
@@ -34,6 +34,16 @@ public class LinesProxy implements ReadonlyList<Object>, Consumer<GitCommit> {
     myCache = new SLRUMap<String, GitCommit>(ourSize, 50);
   }
 
+  public boolean shouldLoad(int idx) {
+    final VisibleLine visibleLine = myTreeComposite.get(idx);
+    if (visibleLine.isDecoration()) {
+      return false;
+    }
+    final String hash = new String(((TreeSkeletonImpl.Commit) visibleLine.getData()).getHash());
+    final GitCommit gitCommit = myCache.get(hash);
+    return gitCommit == null;
+  }
+
   @Override
   public Object get(int idx) {
     final VisibleLine visibleLine = myTreeComposite.get(idx);
index c8824e47a18f61cebe05f024195959a5f61ca248..c631d4661780c95c5c98115a0301f0a61e924538 100644 (file)
@@ -18,7 +18,11 @@ package git4idea.history.wholeTree;
 import com.intellij.openapi.application.Application;
 import com.intellij.openapi.application.ApplicationManager;
 import com.intellij.openapi.application.ModalityState;
+import com.intellij.openapi.diagnostic.Logger;
 import com.intellij.openapi.diff.impl.patch.formove.FilePathComparator;
+import com.intellij.openapi.progress.BackgroundTaskQueue;
+import com.intellij.openapi.progress.ProgressIndicator;
+import com.intellij.openapi.progress.Task;
 import com.intellij.openapi.project.Project;
 import com.intellij.openapi.util.Comparing;
 import com.intellij.openapi.util.Condition;
@@ -27,14 +31,17 @@ import com.intellij.openapi.vcs.CalledInAwt;
 import com.intellij.openapi.vcs.CompoundNumber;
 import com.intellij.openapi.vcs.StaticReadonlyList;
 import com.intellij.openapi.vcs.VcsException;
+import com.intellij.openapi.vcs.changes.BackgroundFromStartOption;
 import com.intellij.openapi.vfs.VirtualFile;
 import com.intellij.util.BufferedListConsumer;
 import com.intellij.util.Consumer;
 import com.intellij.util.containers.Convertor;
+import com.intellij.util.containers.MultiMap;
 import git4idea.history.browser.ChangesFilter;
 import git4idea.history.browser.GitCommit;
 import git4idea.history.browser.LowLevelAccess;
 import git4idea.history.browser.LowLevelAccessImpl;
+import org.jetbrains.annotations.NotNull;
 
 import java.util.*;
 
@@ -42,10 +49,12 @@ import java.util.*;
  * @author irengrig
  */
 public class LoaderImpl implements Loader {
-  private static final long ourTestTimeThreshold = 500;
+  private static final Logger LOG = Logger.getInstance("#git4idea.history.wholeTree.LoaderImpl");
+
+  private static final long ourTestTimeThreshold = 100;
   private final static int ourBigArraysSize = 10;
   private final static int ourTestCount = 5;
-  // todo Object +-
+  private final static int ourSlowPreloadCount = 50;
   private final TreeComposite<VisibleLine> myTreeComposite;
   private final Map<VirtualFile, LowLevelAccess> myAccesses;
   private final LinesProxy myLinesCache;
@@ -58,8 +67,11 @@ public class LoaderImpl implements Loader {
   private final Project myProject;
   private ModalityState myModalityState;
 
-  public LoaderImpl(final Project project,
-                    final Collection<VirtualFile> allGitRoots) {
+  private final BackgroundTaskQueue myQueue;
+  private final CommitIdsHolder<Pair<VirtualFile, String>> myCommitIdsHolder;
+  private List<VirtualFile> myRootsList;
+
+  public LoaderImpl(final Project project, final Collection<VirtualFile> allGitRoots) {
     myProject = project;
     myTreeComposite = new TreeComposite<VisibleLine>(ourBigArraysSize, WithoutDecorationComparator.getInstance());
     myLinesCache = new LinesProxy(myTreeComposite);
@@ -67,8 +79,13 @@ public class LoaderImpl implements Loader {
     for (VirtualFile gitRoot : allGitRoots) {
       myAccesses.put(gitRoot, new LowLevelAccessImpl(project, gitRoot));
     }
+    // todo refresh roots
+    myRootsList = new ArrayList<VirtualFile>(myAccesses.keySet());
+    Collections.sort(myRootsList, FilePathComparator.getInstance());
     myLock = new Object();
     myLoadId = 0;
+    myQueue = new BackgroundTaskQueue(project, "Git log");
+    myCommitIdsHolder = new CommitIdsHolder();
   }
 
   public LinesProxy getLinesProxy() {
@@ -128,9 +145,9 @@ public class LoaderImpl implements Loader {
     }
     final boolean drawHierarchy = filters.isEmpty();
 
-    application.executeOnPooledThread(new Runnable() {
+    myQueue.run(new Task.Backgroundable(myProject, "Git log refresh", false, BackgroundFromStartOption.getInstance()) {
       @Override
-      public void run() {
+      public void run(@NotNull ProgressIndicator indicator) {
         try {
           final Join join = new Join(myAccesses.size(), new MyJoin(current));
           final Runnable joinCaller = new Runnable() {
@@ -142,16 +159,14 @@ public class LoaderImpl implements Loader {
 
           myTreeComposite.clearMembers();
 
-          final List<VirtualFile> list = new ArrayList<VirtualFile>(myAccesses.keySet());
-          Collections.sort(list, FilePathComparator.getInstance());
-
-          for (VirtualFile vf : list) {
+          final List<LoaderBase> endOfTheList = new LinkedList<LoaderBase>();
+          for (VirtualFile vf : myRootsList) {
             final LowLevelAccess access = myAccesses.get(vf);
             final Consumer<CommitHashPlusParents> consumer = createCommitsHolderConsumer(drawHierarchy);
             final Consumer<List<CommitHashPlusParents>> listConsumer = new RefreshingCommitsPackConsumer(current, consumer);
 
             final BufferedListConsumer<CommitHashPlusParents> bufferedListConsumer =
-              new BufferedListConsumer<CommitHashPlusParents>(15, listConsumer, 1000);
+              new BufferedListConsumer<CommitHashPlusParents>(15, listConsumer, 400);
             bufferedListConsumer.setFlushListener(joinCaller);
 
             final long start = System.currentTimeMillis();
@@ -159,16 +174,37 @@ public class LoaderImpl implements Loader {
               FullDataLoader.load(myLinesCache, access, startingPoints, filters, bufferedListConsumer.asConsumer(), ourTestCount);
             final long end = System.currentTimeMillis();
 
+            bufferedListConsumer.flushPart();
             if (allDataAlreadyLoaded) {
               bufferedListConsumer.flush();
             } else {
               final boolean loadFull = (end - start) > ourTestTimeThreshold;
-              final LoaderBase loaderBase = new LoaderBase(access, bufferedListConsumer, filters, ourTestCount, loadFull, startingPoints, myLinesCache);
-              loaderBase.execute();
+              if (loadFull) {
+                final LoaderBase loaderBase = new LoaderBase(access, bufferedListConsumer, filters,
+                  ourTestCount, loadFull, startingPoints, myLinesCache, ourSlowPreloadCount);
+                loaderBase.execute();
+                endOfTheList.add(new LoaderBase(access, bufferedListConsumer, filters, ourSlowPreloadCount, false, startingPoints, myLinesCache, -1));
+              } else {
+                final LoaderBase loaderBase = new LoaderBase(access, bufferedListConsumer, filters, ourTestCount, loadFull, startingPoints, myLinesCache, -1);
+                loaderBase.execute();
+              }
             }
           }
+          myQueue.run(new Task.Backgroundable(myProject, "Git log refresh", false, BackgroundFromStartOption.getInstance()) {
+      @Override
+      public void run(@NotNull ProgressIndicator indicator) {
+        for (LoaderBase loaderBase : endOfTheList) {
+          try {
+            loaderBase.execute();
+          }
+          catch (VcsException e) {
+            myUIRefresh.acceptException(e);
+          }
+        }
+      }});
         } catch (VcsException e) {
           myUIRefresh.acceptException(e);
+        } catch (MyStopListenToOutputException e) {
         } finally {
           //myUIRefresh.skeletonLoadComplete();
         }
@@ -187,7 +223,7 @@ public class LoaderImpl implements Loader {
       consumer = new Consumer<CommitHashPlusParents>() {
         @Override
         public void consume(CommitHashPlusParents commitHashPlusParents) {
-          readonlyList.consume(new TreeSkeletonImpl.Commit(commitHashPlusParents.getHash().getBytes(), 0, commitHashPlusParents.getTime()));
+          readonlyList.consume(new TreeSkeletonImpl.Commit(commitHashPlusParents.getHash(), 0, commitHashPlusParents.getTime()));
         }
       };
       myTreeComposite.addMember(readonlyList);
@@ -211,6 +247,8 @@ public class LoaderImpl implements Loader {
       myRefreshRunnable = new Runnable() {
         @Override
         public void run() {
+          if (myTreeComposite.getAwaitedSize() == myTreeComposite.getSize()) return;
+          LOG.info("Items refresh: (was=" + myTreeComposite.getSize() + " will be=" + myTreeComposite.getAwaitedSize());
           myTreeComposite.repack();
           if (! mySomeDataShown) {
             // todo remove
@@ -250,13 +288,15 @@ public class LoaderImpl implements Loader {
     private final LowLevelAccess myAccess;
     private final Collection<String> myStartingPoints;
     private final Consumer<GitCommit> myLinesCache;
+    private final int myMaxCount;
     private final Collection<ChangesFilter.Filter> myFilters;
     private final int myIgnoreFirst;
 
     public LoaderBase(LowLevelAccess access,
                        BufferedListConsumer<CommitHashPlusParents> consumer,
                        Collection<ChangesFilter.Filter> filters,
-                       int ignoreFirst, boolean loadFullData, Collection<String> startingPoints, final Consumer<GitCommit> linesCache) {
+                       int ignoreFirst, boolean loadFullData, Collection<String> startingPoints, final Consumer<GitCommit> linesCache,
+                       final int maxCount) {
       myAccess = access;
       myConsumer = consumer;
       myFilters = filters;
@@ -264,14 +304,18 @@ public class LoaderImpl implements Loader {
       myLoadFullData = loadFullData;
       myStartingPoints = startingPoints;
       myLinesCache = linesCache;
+      myMaxCount = maxCount;
     }
 
     public void execute() throws VcsException {
-      final MyConsumer consumer = new MyConsumer(myConsumer, myIgnoreFirst);
+      final MyConsumer consumer = new MyConsumer(myConsumer, 0);
 
       if (myLoadFullData) {
-        FullDataLoader.load(myLinesCache, myAccess, myStartingPoints, myFilters, myConsumer.asConsumer(), -1);
+        // todo
+        LOG.info("FULL " + myMaxCount);
+        FullDataLoader.load(myLinesCache, myAccess, myStartingPoints, myFilters, myConsumer.asConsumer(), myMaxCount);
       } else {
+        LOG.info("SKELETON " + myMaxCount);
         myAccess.loadHashesWithParents(myStartingPoints, myFilters, consumer);
       }
       myConsumer.flush();
@@ -375,4 +419,61 @@ public class LoaderImpl implements Loader {
       return Comparing.compare(obj1.toString(), obj2.toString());
     }
   }
+
+  private final static int ourLoadSize = 30;
+
+  public void loadCommitDetails(final int startIdx, final int endIdx) {
+    final Set<Pair<VirtualFile, String>> newIds = new HashSet<Pair<VirtualFile, String>>();
+    for (int i = startIdx; i <= endIdx; i++) {
+      if (myLinesCache.shouldLoad(i)) {
+        final TreeSkeletonImpl.Commit commit = (TreeSkeletonImpl.Commit) myTreeComposite.get(i).getData();
+        final CompoundNumber memberData = myTreeComposite.getMemberData(i);
+        newIds.add(new Pair<VirtualFile, String>(myRootsList.get(memberData.getMemberNumber()), String.valueOf(commit.getHash())));
+      }
+    }
+
+    myCommitIdsHolder.add(newIds);
+    scheduleDetailsLoad();
+  }
+
+  private void scheduleDetailsLoad() {
+    final Collection<Pair<VirtualFile, String>> toLoad = myCommitIdsHolder.get(ourLoadSize);
+    if (toLoad.isEmpty()) return;
+    myQueue.run(new Task.Backgroundable(myProject, "Load git commits details", false, BackgroundFromStartOption.getInstance()) {
+      @Override
+      public void run(@NotNull ProgressIndicator indicator) {
+        final MultiMap<VirtualFile, String> map = new MultiMap<VirtualFile, String>();
+        for (Pair<VirtualFile, String> pair : toLoad) {
+          map.putValue(pair.getFirst(), pair.getSecond());
+        }
+        for (VirtualFile virtualFile : map.keySet()) {
+          try {
+            final Collection<String> values = map.get(virtualFile);
+            final List<GitCommit> commitDetails = myAccesses.get(virtualFile).getCommitDetails(values);
+            for (GitCommit commitDetail : commitDetails) {
+              myLinesCache.consume(commitDetail);
+            }
+            // todo another UI event
+            ApplicationManager.getApplication().invokeLater(new Runnable() {
+              @Override
+              public void run() {
+                myUIRefresh.setSomeDataReadyState();
+              }
+            }, myModalityState);
+          }
+          catch (final VcsException e) {
+            ApplicationManager.getApplication().invokeLater(new Runnable() {
+              @Override
+              public void run() {
+                myUIRefresh.acceptException(e);
+              }
+            }, myModalityState);
+          }
+        }
+        if (myCommitIdsHolder.haveData()) {
+          scheduleDetailsLoad();
+        }
+      }
+    });
+  }
 }
index 4673cd2007269d7c91fb3c5f74e19b6175b4f41a..b810ccfaed31193d617635335eb3b0a46ca3f5ac 100644 (file)
@@ -78,7 +78,8 @@ public class SkeletonBuilder implements AsynchConsumer<CommitHashPlusParents> {
           mySkeleton.addStartToEvent(commit.myIdx, rowCount);
         }
 
-        if (mySeizedWires.get(correspCommit.getWireNumber()) == commit.myIdx) {
+        final Integer seized = mySeizedWires.get(correspCommit.getWireNumber());
+        if (seized != null && seized == commit.myIdx) {
           if (wireNumber == -1) {
             // there is no other commits on the wire after parent -> use it
             wireNumber = correspCommit.getWireNumber();
diff --git a/plugins/git4idea/src/git4idea/history/wholeTree/Speedometer.java b/plugins/git4idea/src/git4idea/history/wholeTree/Speedometer.java
new file mode 100644 (file)
index 0000000..897701f
--- /dev/null
@@ -0,0 +1,72 @@
+/*
+ * 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 git4idea.history.wholeTree;
+
+import java.util.LinkedList;
+
+/**
+ * @author irengrig
+ */
+public class Speedometer {
+  private final int myHistorySize;
+  private final int myMillisInterval;
+  private final LinkedList<Long> myEvents;
+
+  public Speedometer() {
+    this(-1, -1);
+  }
+
+  public Speedometer(int historySize, int millisInterval) {
+    myHistorySize = historySize == -1 ? 20 : historySize;
+    myMillisInterval = millisInterval == -1 ? 500 : millisInterval;
+    myEvents = new LinkedList<Long>();
+  }
+
+  public void event() {
+    while (myEvents.size() >= myHistorySize) {
+      myEvents.removeLast();
+    }
+    myEvents.addFirst(System.currentTimeMillis());
+  }
+
+  // events per 100ms during last myMillisInterval OR last myHistorySize
+  public double getSpeed() {
+    if (myEvents.isEmpty()) return 0;
+
+    final long current = System.currentTimeMillis();
+    final long boundary = current - myMillisInterval;
+    int cnt = 0;
+    final long end = myEvents.getFirst();
+    long last = end;
+    for (Long event : myEvents) {
+      if (cnt > myHistorySize) break;
+      if (event < boundary) break;
+
+      ++ cnt;
+      last = event;
+    }
+    if (cnt == 0) return 0;
+    return ((double) end - last) / (cnt * 100);
+  }
+
+  public void clear() {
+    myEvents.clear();
+  }
+
+  public boolean hasData() {
+    return ! myEvents.isEmpty();
+  }
+}
index c953b6e0128d57c74b265348c00ab379198d72bc..12f10697609eca0cc979bee8537cc9012bdd4345 100644 (file)
@@ -83,6 +83,16 @@ public class TreeComposite<T> implements ReadonlyList<T> {
     }
   }
 
+  // todo debug
+  public void visualDump() {
+    System.out.println("======= dump =======");
+    final int size = myCombinedList.getSize();
+    for (int i = 0; i < size; i++) {
+      final CompoundNumber elem = myCombinedList.get(i);
+      System.out.println("i=" + i + " member=" + elem.getMemberNumber() + " hash=" + myMembers.get(elem.getMemberNumber()).get(elem.getIdx()).toString());
+    }
+  }
+
   @Override
   public T get(int idx) {
     synchronized (myLock) {
@@ -99,4 +109,12 @@ public class TreeComposite<T> implements ReadonlyList<T> {
   public int getSize() {
     return myCombinedList.getSize();
   }
+
+  public int getAwaitedSize() {
+    int result = 0;
+    for (ReadonlyList<T> member : myMembers) {
+      result += member.getSize();
+    }
+    return result;
+  }
 }
index 16875d4a7cabf80a002058f23c8d5d2474c4b4fe..b3fe3da345f0e23043a03e4c712f8d9b0a5ee938 100644 (file)
@@ -135,7 +135,7 @@ public class TreeSkeletonImpl implements TreeSkeleton {
   }
 
   public void addCommit(final int row, final String hash, final int wireNumber, final long time) {
-    myList.put(row, new Commit(hash.getBytes(), wireNumber, time));
+    myList.put(row, new Commit(hash, wireNumber, time));
   }
 
   @Nullable
@@ -210,16 +210,21 @@ public class TreeSkeletonImpl implements TreeSkeleton {
   }
 
   public static class Commit implements Comparable<Commit>, VisibleLine {
-    private final byte[] myHash;
+    private final String myHash;
     private int myWireNumber;
     private final long myTime;
 
-    public Commit(final byte[] hash, final int wireNumber, long time) {
+    public Commit(final String hash, final int wireNumber, long time) {
       myHash = hash;
       myWireNumber = wireNumber;
       myTime = time;
     }
 
+    @Override
+    public String toString() {
+      return myHash;
+    }
+
     @Override
     public Object getData() {
       return this;
@@ -230,7 +235,7 @@ public class TreeSkeletonImpl implements TreeSkeleton {
       return false;
     }
 
-    public byte[] getHash() {
+    public String getHash() {
       return myHash;
     }
 
diff --git a/plugins/git4idea/tests/git4idea/tests/CommitIdsTest.java b/plugins/git4idea/tests/git4idea/tests/CommitIdsTest.java
new file mode 100644 (file)
index 0000000..af5bbb0
--- /dev/null
@@ -0,0 +1,41 @@
+/*
+ * 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 git4idea.tests;
+
+import git4idea.history.wholeTree.CommitIdsHolder;
+import junit.framework.Assert;
+import junit.framework.TestCase;
+
+import java.util.Arrays;
+import java.util.Collection;
+
+/**
+ * @author irengrig
+ */
+public class CommitIdsTest extends TestCase {
+  public void testSimple() throws Exception {
+    final CommitIdsHolder<Integer> holder = new CommitIdsHolder<Integer>();
+
+    holder.add(Arrays.asList(1,2,3,4,5,6,7,8));
+    Assert.assertTrue(holder.haveData());
+    Collection<Integer> integers = holder.get(5);
+    Assert.assertEquals(5, integers.size());
+    Assert.assertTrue(holder.haveData());
+    integers = holder.get(20);
+    Assert.assertEquals(3, integers.size());
+    Assert.assertTrue(! holder.haveData());
+  }
+}