[log] Load refs and commits synchronously, speed-up git log startup
authorKirill Likhodedov <Kirill.Likhodedov@jetbrains.com>
Mon, 8 Sep 2014 11:19:29 +0000 (15:19 +0400)
committerKirill Likhodedov <Kirill.Likhodedov@jetbrains.com>
Tue, 9 Sep 2014 11:04:19 +0000 (15:04 +0400)
=> IDEA-126380 avoid diverging of log & refs if they are queried by
   separate commands, and the repository changes between these requests.
=> IDEA-117553 initial startup loading becomes faster.

* Tell plugins to return refs, users and commits altogether, they
  should worry about synchronousness by themselves.
* For Git:
  + call log --decorate=full on refresh, thus getting all
    references which are attached to requested commits.
  + Add all branches there by reading them from branch files.
  + Ignore other tags on startup.
  + On refresh check new tags ('git tag' is fast), and load more commits
    if needed.
  + Ignore rare case if some old tag pointers moved.
  + on startup don't use --decorate for speed-up, instead just read
    branches manually. Some validation will follow.
* Provide trivial implementation for hg.

18 files changed:
platform/vcs-log/api/src/com/intellij/vcs/log/VcsLogProvider.java
platform/vcs-log/api/src/com/intellij/vcs/log/VcsLogProviderRequirementsEx.java
platform/vcs-log/impl/src/com/intellij/vcs/log/data/DataPack.java
platform/vcs-log/impl/src/com/intellij/vcs/log/data/EmptyDataPack.java
platform/vcs-log/impl/src/com/intellij/vcs/log/data/RefsModel.java
platform/vcs-log/impl/src/com/intellij/vcs/log/data/VcsLogRefresherImpl.java
platform/vcs-log/impl/src/com/intellij/vcs/log/data/VcsUserRegistryImpl.java
platform/vcs-log/impl/src/com/intellij/vcs/log/impl/LogDataImpl.java [new file with mode: 0644]
platform/vcs-log/impl/src/com/intellij/vcs/log/impl/RequirementsImpl.java
platform/vcs-log/impl/test/com/intellij/vcs/log/impl/TestVcsLogProvider.java
plugins/git4idea/src/git4idea/branch/GitBranchUtil.java
plugins/git4idea/src/git4idea/history/GitHistoryUtils.java
plugins/git4idea/src/git4idea/log/GitLogProvider.java
plugins/git4idea/src/git4idea/log/GitRefManager.java
plugins/git4idea/tests/git4idea/log/GitLogProviderTest.java
plugins/git4idea/tests/git4idea/log/RefParser.java [moved from plugins/git4idea/src/git4idea/log/RefParser.java with 72% similarity]
plugins/hg4idea/src/org/zmlx/hg4idea/log/HgHistoryUtil.java
plugins/hg4idea/src/org/zmlx/hg4idea/log/HgLogProvider.java

index 89805947869c3efc001b6a1c688a21c298a2019e..d9c72314edbcb5cee82fbb042743dd646cb95693 100644 (file)
@@ -10,6 +10,7 @@ import org.jetbrains.annotations.Nullable;
 
 import java.util.Collection;
 import java.util.List;
+import java.util.Set;
 
 /**
  * Provides the information needed to build the VCS log, such as the list of most recent commits with their parents.
@@ -17,20 +18,26 @@ import java.util.List;
 public interface VcsLogProvider {
 
   /**
-   * Reads the most recent correctly ordered commits from the log. <br/>
-   * Commits should be at least topologically ordered, better considering commit time as well. <br/>
-   * Commits will be shown in the log in this order.
-   * @param requirements some limitations on commit data that should be returned.
+   * Reads the most recent commits from the log together with all repository references.<br/>
+   * Commits should be at least topologically ordered, better considering commit time as well: they will be shown in the log in this order.
+   * <p/>
+   * This method is called both on the startup and on refresh.
+   *
+   * @param requirements some limitations on commit data that should be returned, e.g. the number of commits.
+   * @return given amount of ordered commits and <b>all</b> references in the repository.
    */
   @NotNull
-  List<? extends VcsCommitMetadata> readFirstBlock(@NotNull VirtualFile root, @NotNull Requirements requirements) throws VcsException;
+  DetailedLogData readFirstBlock(@NotNull VirtualFile root, @NotNull Requirements requirements) throws VcsException;
 
   /**
-   * <p>Reads the whole history, but only hashes & parents.</p>
-   * <p>Also reports authors/committers of this repository to the given user registry.</p>
+   * Reads the whole history.
+   * <p/>
+   * Reports commits to the consumer to avoid creation & even temporary storage of a too large commits collection.
+   *
+   * @return all references and all authors in the repository.
    */
-  void readAllHashes(@NotNull VirtualFile root, @NotNull Consumer<VcsUser> userRegistry,
-                     @NotNull Consumer<TimedVcsCommit> commitConsumer) throws VcsException;
+  @NotNull
+  LogData readAllHashes(@NotNull VirtualFile root, @NotNull Consumer<TimedVcsCommit> commitConsumer) throws VcsException;
 
   /**
    * Reads those details of the given commits, which are necessary to be shown in the log table.
@@ -44,12 +51,6 @@ public interface VcsLogProvider {
   @NotNull
   List<? extends VcsFullCommitDetails> readFullDetails(@NotNull VirtualFile root, @NotNull List<String> hashes) throws VcsException;
 
-  /**
-   * Read all references (branches, tags, etc.) for the given roots.
-   */
-  @NotNull
-  Collection<VcsRef> readAllRefs(@NotNull VirtualFile root) throws VcsException;
-
   /**
    * <p>Returns the VCS which is supported by this provider.</p>
    * <p>If there will be several VcsLogProviders which support the same VCS, only one will be chosen. It is undefined, which one.</p>
@@ -106,4 +107,20 @@ public interface VcsLogProvider {
 
   }
 
+  /**
+   * Container for references and users.
+   */
+  interface LogData {
+    @NotNull Set<VcsRef> getRefs();
+    @NotNull Set<VcsUser> getUsers();
+  }
+
+  /**
+   * Container for the ordered list of commits together with their details, and references.
+   */
+  interface DetailedLogData {
+    @NotNull List<VcsCommitMetadata> getCommits();
+    @NotNull Set<VcsRef> getRefs();
+  }
+
 }
index d69579ac00b1fefa52a8d09f9613b2b959dc7e8d..68623a6852f9334482db76d8d4aa07c82848dc2a 100644 (file)
@@ -15,7 +15,6 @@
  */
 package com.intellij.vcs.log;
 
-import com.intellij.openapi.vfs.VirtualFile;
 import org.jetbrains.annotations.NotNull;
 
 import java.util.Set;
@@ -24,7 +23,7 @@ import static com.intellij.vcs.log.VcsLogProvider.Requirements;
 
 /**
  * Extension of the standard {@link Requirements} which contains data used by some VCSs. <br/>
- * An object of this object is actually passed to {@link VcsLogProvider#readFirstBlock(VirtualFile, Requirements)}, but VcsLogProviders
+ * An object of this object is actually passed to {@link #readFirstBlock(com.intellij.util.Consumer}, but VcsLogProviders
  * which need this additional information must check for instanceof before casting & be able to fallback.
  */
 public interface VcsLogProviderRequirementsEx extends Requirements {
@@ -41,10 +40,4 @@ public interface VcsLogProviderRequirementsEx extends Requirements {
   @NotNull
   Set<VcsRef> getPreviousRefs();
 
-  /**
-   * Returns current refs.
-   */
-  @NotNull
-  Set<VcsRef> getCurrentRefs();
-
 }
index 48b25be01ab96e5a6bd4b12dcce8084e769913b4..975808611ed46635ce3a2865e7f1f34b20b67a78 100644 (file)
@@ -26,16 +26,14 @@ public class DataPack implements VcsLogDataPack {
   private boolean myFull;
 
   @NotNull
-  static DataPack build(@NotNull List<? extends GraphCommit<Integer>> commits, @NotNull RefsModel refsModel,
-                        @NotNull NotNullFunction<Hash, Integer> indexGetter, @NotNull NotNullFunction<Integer, Hash> hashGetter,
-                        @NotNull Map<VirtualFile, VcsLogProvider> providers, boolean full) {
-    return build(buildPermanentGraph(commits, refsModel, indexGetter, hashGetter, providers), providers, refsModel, full);
-  }
-
-  @NotNull
-  static DataPack build(@NotNull PermanentGraph<Integer> permanentGraph, @NotNull Map<VirtualFile, VcsLogProvider> providers,
-                        @NotNull RefsModel refsModel, boolean full) {
-    return new DataPack(refsModel, permanentGraph, createGraphFacade(permanentGraph), providers, full);
+  static DataPack build(@NotNull List<? extends GraphCommit<Integer>> commits,
+                        @NotNull Map<VirtualFile, Set<VcsRef>> refs,
+                        @NotNull Map<VirtualFile, VcsLogProvider> providers,
+                        @NotNull VcsLogHashMap hashMap,
+                        boolean full) {
+    RefsModel refsModel = new RefsModel(refs, hashMap.asIndexGetter());
+    PermanentGraph<Integer> graph = buildPermanentGraph(commits, refsModel, hashMap.asIndexGetter(), hashMap.asHashGetter(), providers);
+    return new DataPack(refsModel, graph, createGraphFacade(graph), providers, full);
   }
 
   @NotNull
@@ -57,11 +55,11 @@ public class DataPack implements VcsLogDataPack {
   }
 
   @NotNull
-  static PermanentGraph<Integer> buildPermanentGraph(@NotNull List<? extends GraphCommit<Integer>> commits,
-                                                     @NotNull RefsModel refsModel,
-                                                     @NotNull NotNullFunction<Hash, Integer> indexGetter,
-                                                     @NotNull NotNullFunction<Integer, Hash> hashGetter,
-                                                     @NotNull Map<VirtualFile, VcsLogProvider> providers) {
+  private static PermanentGraph<Integer> buildPermanentGraph(@NotNull List<? extends GraphCommit<Integer>> commits,
+                                                             @NotNull RefsModel refsModel,
+                                                             @NotNull NotNullFunction<Hash, Integer> indexGetter,
+                                                             @NotNull NotNullFunction<Integer, Hash> hashGetter,
+                                                             @NotNull Map<VirtualFile, VcsLogProvider> providers) {
     if (commits.isEmpty()) {
       return EmptyPermanentGraph.getInstance();
     }
index 6262d767d0711461002fcbb12cbc34533d8e43bf..54140afb394039bebd36bcc4f679bda68f830309 100644 (file)
@@ -22,14 +22,14 @@ import com.intellij.vcs.log.VcsLogProvider;
 import com.intellij.vcs.log.VcsRef;
 import org.jetbrains.annotations.NotNull;
 
-import java.util.Collection;
 import java.util.Collections;
+import java.util.Set;
 
 public class EmptyDataPack {
 
   @NotNull
   public static DataPack getInstance() {
-    RefsModel emptyModel = new RefsModel(Collections.<VirtualFile, Collection<VcsRef>>emptyMap(), new ConstantFunction<Hash, Integer>(0));
+    RefsModel emptyModel = new RefsModel(Collections.<VirtualFile, Set<VcsRef>>emptyMap(), new ConstantFunction<Hash, Integer>(0));
     return new DataPack(emptyModel, EmptyPermanentGraph.getInstance(), new EmptyGraphFacade(), Collections.<VirtualFile, VcsLogProvider>emptyMap(), false);
   }
 
index 46504b338970ccfaae5ea24a91930388d2cd6ab1..c38e4c315ff033b24ec34b0925e5af683ba693fc 100644 (file)
@@ -16,14 +16,14 @@ import java.util.*;
 
 public class RefsModel implements VcsLogRefs {
 
-  @NotNull private final Map<VirtualFile, Collection<VcsRef>> myRefs;
+  @NotNull private final Map<VirtualFile, Set<VcsRef>> myRefs;
   @NotNull private final NotNullFunction<Hash, Integer> myIndexGetter;
 
   @NotNull private final Collection<VcsRef> myBranches;
   @NotNull private final MultiMap<Hash, VcsRef> myRefsToHashes;
   @NotNull private final TIntObjectHashMap<SmartList<VcsRef>> myRefsToIndices;
 
-  public RefsModel(@NotNull Map<VirtualFile, Collection<VcsRef>> refsByRoot, @NotNull NotNullFunction<Hash, Integer> indexGetter) {
+  public RefsModel(@NotNull Map<VirtualFile, Set<VcsRef>> refsByRoot, @NotNull NotNullFunction<Hash, Integer> indexGetter) {
     myRefs = refsByRoot;
     myIndexGetter = indexGetter;
 
@@ -85,7 +85,7 @@ public class RefsModel implements VcsLogRefs {
   }
 
   @NotNull
-  public Map<VirtualFile, Collection<VcsRef>> getAllRefsByRoot() {
+  public Map<VirtualFile, Set<VcsRef>> getAllRefsByRoot() {
     return myRefs;
   }
 
index c7ab5e324b3d5efff272387fd63ab6d28c04a866..09de51c722bf5a4eb488ffa8c44335952ab0c74d 100644 (file)
@@ -27,6 +27,7 @@ import com.intellij.openapi.vcs.VcsException;
 import com.intellij.openapi.vfs.VirtualFile;
 import com.intellij.util.Consumer;
 import com.intellij.util.Function;
+import com.intellij.util.NotNullFunction;
 import com.intellij.util.containers.ContainerUtil;
 import com.intellij.util.ui.UIUtil;
 import com.intellij.vcs.log.*;
@@ -62,7 +63,8 @@ public class VcsLogRefresherImpl implements VcsLogRefresher {
                              @NotNull final VcsUserRegistryImpl userRegistry,
                              @NotNull Map<Hash, VcsCommitMetadata> topCommitsDetailsCache,
                              @NotNull final Consumer<DataPack> dataPackUpdateHandler,
-                             @NotNull Consumer<Exception> exceptionHandler, int recentCommitsCount) {
+                             @NotNull Consumer<Exception> exceptionHandler,
+                             int recentCommitsCount) {
     myProject = project;
     myHashMap = hashMap;
     myProviders = providers;
@@ -95,16 +97,11 @@ public class VcsLogRefresherImpl implements VcsLogRefresher {
   @Override
   public DataPack readFirstBlock() {
     try {
-      Map<VirtualFile, Collection<VcsRef>> refs = loadRefsFromVcs(myProviders);
-      Set<VirtualFile> roots = myProviders.keySet();
-      Map<VirtualFile, VcsLogProvider.Requirements> requirements = prepareSimpleRequirements(roots, myRecentCommitCount);
-      Map<VirtualFile, List<? extends GraphCommit<Integer>>> commits = loadRecentCommitsFromVcs(myProviders, requirements,
-                                                                                                myUserRegistry, myTopCommitsDetailsCache,
-                                                                                                myHashMap);
-      List<? extends GraphCommit<Integer>> compoundLog = compound(commits.values());
-      DataPack dataPack = DataPack.build(compoundLog, new RefsModel(refs, myHashMap.asIndexGetter()),
-                                         myHashMap.asIndexGetter(), myHashMap.asHashGetter(), myProviders, false);
-      mySingleTaskController.request(RefreshRequest.RELOAD_ALL); // build/rebuild the full log in bg
+      LogInfo data = loadRecentData(new CommitCountRequirements(myRecentCommitCount).asMap(myProviders.keySet()));
+      Collection<List<GraphCommit<Integer>>> commits = data.getCommits();
+      Map<VirtualFile, Set<VcsRef>> refs = data.getRefs();
+      DataPack dataPack = DataPack.build(multiRepoJoin(commits), refs, myProviders, myHashMap, false);
+      mySingleTaskController.request(RefreshRequest.RELOAD_ALL); // build/rebuild the full log in background
       return dataPack;
     }
     catch (VcsException e) {
@@ -113,109 +110,77 @@ public class VcsLogRefresherImpl implements VcsLogRefresher {
     }
   }
 
-  @Override
-  public void refresh(@NotNull Collection<VirtualFile> rootsToRefresh) {
-    if (!rootsToRefresh.isEmpty()) {
-      mySingleTaskController.request(new RefreshRequest(rootsToRefresh));
-    }
-  }
-
   @NotNull
-  private static Map<VirtualFile, VcsLogProvider.Requirements> prepareSimpleRequirements(@NotNull Collection<VirtualFile> roots,
-                                                                                         final int commitCount) {
-    final VcsLogProvider.Requirements requirements = new VcsLogProvider.Requirements() {
-      @Override
-      public int getCommitCount() {
-        return commitCount;
-      }
-    };
-    return ContainerUtil.map2Map(roots, new Function<VirtualFile, Pair<VirtualFile, VcsLogProvider.Requirements>>() {
-      @Override
-      public Pair<VirtualFile, VcsLogProvider.Requirements> fun(VirtualFile file) {
-        return Pair.create(file, requirements);
-      }
-    });
-  }
-
-  @NotNull
-  private static Map<VirtualFile, Collection<VcsRef>> loadRefsFromVcs(@NotNull Map<VirtualFile, VcsLogProvider> providers)
-    throws VcsException {
-    final StopWatch sw = StopWatch.start("loading refs");
-    final Map<VirtualFile, Collection<VcsRef>> refs = ContainerUtil.newHashMap();
+  private LogInfo loadRecentData(@NotNull final Map<VirtualFile, VcsLogProvider.Requirements> requirements) throws VcsException {
+    final StopWatch sw = StopWatch.start("loading commits");
+    final LogInfo logInfo = new LogInfo();
     new ProviderIterator() {
       @Override
-      void each(@NotNull VirtualFile root, @NotNull VcsLogProvider provider) throws VcsException {
-        refs.put(root, provider.readAllRefs(root));
+      public void each(@NotNull VirtualFile root, @NotNull VcsLogProvider provider) throws VcsException {
+        VcsLogProvider.DetailedLogData data = provider.readFirstBlock(root, requirements.get(root));
+        storeUsersAndDetails(data.getCommits());
+        logInfo.put(root, compactCommits(data.getCommits()));
+        logInfo.put(root, data.getRefs());
         sw.rootCompleted(root);
       }
-    }.iterate(providers);
+    }.iterate(getProvidersForRoots(requirements.keySet()));
+    myUserRegistry.flush();
     sw.report();
-    return refs;
+    return logInfo;
   }
 
   @NotNull
-  private static Map<VirtualFile, List<? extends GraphCommit<Integer>>> loadRecentCommitsFromVcs(
-    @NotNull Map<VirtualFile, VcsLogProvider> providers,
-    @NotNull final Map<VirtualFile, VcsLogProvider.Requirements> requirements,
-    @NotNull final VcsUserRegistryImpl userRegistry,
-    @NotNull final Map<Hash, VcsCommitMetadata> topCommitsDetailsCache,
-    @NotNull final VcsLogHashMap hashMap) throws VcsException
-  {
-    final StopWatch sw = StopWatch.start("loading commits");
-    final Map<VirtualFile, List<? extends GraphCommit<Integer>>> commits = ContainerUtil.newHashMap();
-    new ProviderIterator() {
-      @Override
-      public void each(@NotNull VirtualFile root, @NotNull VcsLogProvider provider) throws VcsException {
-        List<? extends VcsCommitMetadata> metadatas = provider.readFirstBlock(root, requirements.get(root));
-        storeUsersAndDetails(metadatas, userRegistry, topCommitsDetailsCache);
-        commits.put(root, compactCommits(metadatas, hashMap));
-        sw.rootCompleted(root);
-      }
-    }.iterate(providers);
-    userRegistry.flush();
-    sw.report();
-    return commits;
+  private Map<VirtualFile, VcsLogProvider> getProvidersForRoots(@NotNull Set<VirtualFile> roots) {
+    return ContainerUtil.map2Map(roots,
+                                 new Function<VirtualFile, Pair<VirtualFile, VcsLogProvider>>() {
+                                   @Override
+                                   public Pair<VirtualFile, VcsLogProvider> fun(VirtualFile root) {
+                                     return Pair.create(root, myProviders.get(root));
+                                   }
+                                 });
+  }
+
+  @Override
+  public void refresh(@NotNull Collection<VirtualFile> rootsToRefresh) {
+    if (!rootsToRefresh.isEmpty()) {
+      mySingleTaskController.request(new RefreshRequest(rootsToRefresh));
+    }
   }
 
-  /**
-   * Compounds logs from different repositories into a single multi-repository log.
-   */
   @NotNull
-  private static List<? extends GraphCommit<Integer>> compound(@NotNull Collection<List<? extends GraphCommit<Integer>>> commits) {
+  private static <T extends GraphCommit<Integer>> List<T> multiRepoJoin(@NotNull Collection<List<T>> commits) {
     StopWatch sw = StopWatch.start("multi-repo join");
-    List<? extends GraphCommit<Integer>> joined = new VcsLogMultiRepoJoiner<Integer>().join(commits);
+    List<T> joined = new VcsLogMultiRepoJoiner<Integer, T>().join(commits);
     sw.report();
     return joined;
   }
 
   @NotNull
-  private static List<GraphCommit<Integer>> compactCommits(@NotNull List<? extends TimedVcsCommit> commits,
-                                                           @NotNull final VcsLogHashMap hashMap) {
+  private List<GraphCommit<Integer>> compactCommits(@NotNull List<? extends TimedVcsCommit> commits) {
     StopWatch sw = StopWatch.start("compacting commits");
     List<GraphCommit<Integer>> map = ContainerUtil.map(commits, new Function<TimedVcsCommit, GraphCommit<Integer>>() {
       @NotNull
       @Override
       public GraphCommit<Integer> fun(@NotNull TimedVcsCommit commit) {
-        return compactCommit(commit, hashMap);
+        return compactCommit(commit);
       }
     });
-    hashMap.flush();
+    myHashMap.flush();
     sw.report();
     return map;
   }
 
   @NotNull
-  private static GraphCommitImpl<Integer> compactCommit(@NotNull TimedVcsCommit commit, @NotNull VcsLogHashMap hashMap) {
-    return new GraphCommitImpl<Integer>(hashMap.getCommitIndex(commit.getId()),
-                                        ContainerUtil.map(commit.getParents(), hashMap.asIndexGetter()), commit.getTimestamp());
+  private GraphCommitImpl<Integer> compactCommit(@NotNull TimedVcsCommit commit) {
+    return new GraphCommitImpl<Integer>(myHashMap.getCommitIndex(commit.getId()),
+                                        ContainerUtil.map(commit.getParents(), myHashMap.asIndexGetter()), commit.getTimestamp());
   }
 
-  private static void storeUsersAndDetails(@NotNull List<? extends VcsCommitMetadata> metadatas, @NotNull VcsUserRegistryImpl userRegistry,
-                                           @NotNull Map<Hash, VcsCommitMetadata> topCommitsDetailsCache) {
+  private void storeUsersAndDetails(@NotNull Collection<? extends VcsCommitMetadata> metadatas) {
     for (VcsCommitMetadata detail : metadatas) {
-      userRegistry.addUser(detail.getAuthor());
-      userRegistry.addUser(detail.getCommitter());
-      topCommitsDetailsCache.put(detail.getId(), detail);
+      myUserRegistry.addUser(detail.getAuthor());
+      myUserRegistry.addUser(detail.getCommitter());
+      myTopCommitsDetailsCache.put(detail.getId(), detail);
     }
   }
 
@@ -223,17 +188,7 @@ public class VcsLogRefresherImpl implements VcsLogRefresher {
 
     @NotNull private DataPack myCurrentDataPack;
 
-    // collects loaded info from different roots, which refresh was requested consecutively within a single task
-    private final Map<VirtualFile, LogAndRefs> myLoadedInfos = ContainerUtil.newHashMap();
-
-    private class LogAndRefs {
-      List<? extends GraphCommit<Integer>> log;
-      Collection<VcsRef> refs;
-      LogAndRefs(Collection<VcsRef> refs, List<? extends GraphCommit<Integer>> commits) {
-        this.refs = refs;
-        this.log = commits;
-      }
-    }
+    @NotNull private final LogInfo myLoadedInfo = new LogInfo();
 
     MyRefreshTask(@NotNull DataPack currentDataPack) {
       super(VcsLogRefresherImpl.this.myProject, "Refreshing history...", false);
@@ -271,30 +226,28 @@ public class VcsLogRefresherImpl implements VcsLogRefresher {
     private DataPack doRefresh(@NotNull Collection<VirtualFile> roots) {
       StopWatch sw = StopWatch.start("refresh");
       PermanentGraph<Integer> permanentGraph = myCurrentDataPack.isFull() ? myCurrentDataPack.getPermanentGraph() : null;
-      Map<VirtualFile, Collection<VcsRef>> currentRefs = myCurrentDataPack.getRefsModel().getAllRefsByRoot();
+      Map<VirtualFile, Set<VcsRef>> currentRefs = myCurrentDataPack.getRefsModel().getAllRefsByRoot();
       try {
         if (permanentGraph != null) {
           int commitCount = myRecentCommitCount;
           for (int attempt = 0; attempt <= 1; attempt++) {
             loadLogAndRefs(roots, currentRefs, commitCount);
-            List<? extends GraphCommit<Integer>> compoundLog = compoundLoadedLogs(myLoadedInfos.values());
-            Map<VirtualFile, Collection<VcsRef>> allNewRefs = getAllNewRefs(myLoadedInfos, currentRefs);
+            List<? extends GraphCommit<Integer>> compoundLog = multiRepoJoin(myLoadedInfo.getCommits());
+            Map<VirtualFile, Set<VcsRef>> allNewRefs = getAllNewRefs(myLoadedInfo, currentRefs);
             List<GraphCommit<Integer>> joinedFullLog = join(compoundLog, permanentGraph.getAllCommits(), currentRefs, allNewRefs);
             if (joinedFullLog == null) {
               commitCount *= 5;
             }
             else {
-              return DataPack.build(joinedFullLog, new RefsModel(allNewRefs, myHashMap.asIndexGetter()),
-                                    myHashMap.asIndexGetter(), myHashMap.asHashGetter(), myProviders, true);
+              return DataPack.build(joinedFullLog, allNewRefs, myProviders, myHashMap, true);
             }
           }
           // couldn't join => need to reload everything; if 5000 commits is still not enough, it's worth reporting:
           LOG.error("Couldn't join " + commitCount + " recent commits to the log (" + permanentGraph.getAllCommits().size() + " commits)",
-                    new Attachment("recent_commits", toLogString(myLoadedInfos)));
+                    new Attachment("recent_commits", myLoadedInfo.toLogString(myHashMap.asIndexGetter())));
         }
 
-        Pair<PermanentGraph<Integer>, Map<VirtualFile, Collection<VcsRef>>> fullLogAndRefs = loadFullLog();
-        return DataPack.build(fullLogAndRefs.first, myProviders, new RefsModel(fullLogAndRefs.second, myHashMap.asIndexGetter()), true);
+        return loadFullLog();
       }
       catch (Exception e) {
         myExceptionHandler.consume(e);
@@ -305,95 +258,45 @@ public class VcsLogRefresherImpl implements VcsLogRefresher {
       }
     }
 
-    private String toLogString(Map<VirtualFile, LogAndRefs> infos) {
-      StringBuilder sb = new StringBuilder();
-      for (Map.Entry<VirtualFile, LogAndRefs> entry : infos.entrySet()) {
-        sb.append(entry.getKey().getName());
-        sb.append(" LOG:\n");
-        sb.append(StringUtil.join(entry.getValue().log, new Function<GraphCommit<Integer>, String>() {
-          @Override
-          public String fun(GraphCommit<Integer> commit) {
-            return commit.getId() + "<-" + StringUtil.join(commit.getParents(), ",");
-          }
-        }, "\n"));
-        sb.append("\nREFS:\n");
-        sb.append(StringUtil.join(entry.getValue().refs, new Function<VcsRef, String>() {
-          @Override
-          public String fun(VcsRef ref) {
-            return ref.getName() + "(" + myHashMap.getCommitIndex(ref.getCommitHash()) + ")";
-          }
-        }, ","));
-      }
-      return sb.toString();
-    }
-
     @NotNull
-    private List<? extends GraphCommit<Integer>> compoundLoadedLogs(@NotNull Collection<LogAndRefs> logsAndRefs) {
-      return compound(ContainerUtil.map(logsAndRefs, new Function<LogAndRefs, List<? extends GraphCommit<Integer>>>() {
-        @Override
-        public List<? extends GraphCommit<Integer>> fun(LogAndRefs refs) {
-          return refs.log;
-        }
-      }));
-    }
-
-    @NotNull
-    private Map<VirtualFile, Collection<VcsRef>> getAllNewRefs(@NotNull Map<VirtualFile, LogAndRefs> newInfo,
-                                                               @NotNull Map<VirtualFile, Collection<VcsRef>> previousRefs) {
-      Map<VirtualFile, Collection<VcsRef>> result = ContainerUtil.newHashMap();
+    private Map<VirtualFile, Set<VcsRef>> getAllNewRefs(@NotNull LogInfo newInfo,
+                                                        @NotNull Map<VirtualFile, Set<VcsRef>> previousRefs) {
+      Map<VirtualFile, Set<VcsRef>> result = ContainerUtil.newHashMap();
       for (VirtualFile root : previousRefs.keySet()) {
-        result.put(root, newInfo.containsKey(root) ? newInfo.get(root).refs : previousRefs.get(root));
+        Set<VcsRef> newInfoRefs = newInfo.getRefs(root);
+        result.put(root, newInfoRefs != null ? newInfoRefs : previousRefs.get(root));
       }
       return result;
     }
 
-    private void loadLogAndRefs(@NotNull Collection<VirtualFile> roots, @NotNull Map<VirtualFile, Collection<VcsRef>> prevRefs,
+    private void loadLogAndRefs(@NotNull Collection<VirtualFile> roots,
+                                @NotNull Map<VirtualFile, Set<VcsRef>> prevRefs,
                                 int commitCount) throws VcsException {
-      Map<VirtualFile, VcsLogProvider> providers = getProviders(roots);
-      Map<VirtualFile, Collection<VcsRef>> refs = loadRefsFromVcs(providers);
-      Map<VirtualFile, VcsLogProvider.Requirements> requirements = prepareRequirements(roots, commitCount, prevRefs, refs);
-      Map<VirtualFile, List<? extends GraphCommit<Integer>>> commits = loadRecentCommitsFromVcs(providers, requirements,
-                                                                                                myUserRegistry, myTopCommitsDetailsCache,
-                                                                                                myHashMap);
+      LogInfo logInfo = loadRecentData(prepareRequirements(roots, commitCount, prevRefs));
       for (VirtualFile root : roots) {
-        myLoadedInfos.put(root, new LogAndRefs(refs.get(root), commits.get(root)));
+        myLoadedInfo.put(root, logInfo.getCommits(root));
+        myLoadedInfo.put(root, logInfo.getRefs(root));
       }
     }
 
     @NotNull
     private Map<VirtualFile, VcsLogProvider.Requirements> prepareRequirements(@NotNull Collection<VirtualFile> roots,
                                                                               int commitCount,
-                                                                              @NotNull Map<VirtualFile, Collection<VcsRef>> prevRefs,
-                                                                              @NotNull Map<VirtualFile, Collection<VcsRef>> newRefs) {
+                                                                              @NotNull Map<VirtualFile, Set<VcsRef>> prevRefs) {
       Map<VirtualFile, VcsLogProvider.Requirements> requirements = ContainerUtil.newHashMap();
       for (VirtualFile root : roots) {
-        requirements.put(root, new RequirementsImpl(commitCount, true, getRefsForRoot(prevRefs, root), getRefsForRoot(newRefs, root)));
+        requirements.put(root, new RequirementsImpl(commitCount, true, ContainerUtil.notNullize(prevRefs.get(root))));
       }
       return requirements;
     }
 
-    @NotNull
-    private Set<VcsRef> getRefsForRoot(@NotNull Map<VirtualFile, Collection<VcsRef>> map, @NotNull VirtualFile root) {
-      Collection<VcsRef> refs = map.get(root);
-      return refs == null ? Collections.<VcsRef>emptySet() : new HashSet<VcsRef>(refs);
-    }
-
-    @NotNull
-    private Map<VirtualFile, VcsLogProvider> getProviders(@NotNull Collection<VirtualFile> roots) {
-      Map<VirtualFile, VcsLogProvider> providers = ContainerUtil.newHashMap();
-      for (VirtualFile root : roots) {
-        providers.put(root, myProviders.get(root));
-      }
-      return providers;
-    }
-
     @Nullable
-    private List<GraphCommit<Integer>> join(@NotNull List<? extends GraphCommit<Integer>> recentCommits, @NotNull List<GraphCommit<Integer>> fullLog,
-                                            @NotNull Map<VirtualFile, Collection<VcsRef>> previousRefs,
-                                            @NotNull Map<VirtualFile, Collection<VcsRef>> newRefs) {
+    private List<GraphCommit<Integer>> join(@NotNull List<? extends GraphCommit<Integer>> recentCommits,
+                                            @NotNull List<GraphCommit<Integer>> fullLog,
+                                            @NotNull Map<VirtualFile, Set<VcsRef>> previousRefs,
+                                            @NotNull Map<VirtualFile, Set<VcsRef>> newRefs) {
       StopWatch sw = StopWatch.start("joining new commits");
       Function<VcsRef, Integer> ref2Int = new Function<VcsRef, Integer>() {
-        @NotNull
         @Override
         public Integer fun(@NotNull VcsRef ref) {
           return myHashMap.getCommitIndex(ref.getCommitHash());
@@ -418,50 +321,44 @@ public class VcsLogRefresherImpl implements VcsLogRefresher {
     }
 
     @NotNull
-    private Pair<PermanentGraph<Integer>, Map<VirtualFile, Collection<VcsRef>>> loadFullLog() throws VcsException {
+    private DataPack loadFullLog() throws VcsException {
       StopWatch sw = StopWatch.start("full log reload");
-      Collection<List<? extends GraphCommit<Integer>>> commits = readFullLogFromVcs();
-      List<? extends GraphCommit<Integer>> graphCommits = compound(commits);
-      Map<VirtualFile, Collection<VcsRef>> refMap = loadRefsFromVcs(myProviders);
-      PermanentGraph<Integer> permanentGraph = DataPack.buildPermanentGraph(graphCommits, new RefsModel(refMap, myHashMap.asIndexGetter()),
-                                                                            myHashMap.asIndexGetter(),
-                                                                            myHashMap.asHashGetter(), myProviders);
+      LogInfo logInfo = readFullLogFromVcs();
+      List<? extends GraphCommit<Integer>> graphCommits = multiRepoJoin(logInfo.getCommits());
+      DataPack dataPack = DataPack.build(graphCommits, logInfo.getRefs(), myProviders, myHashMap, true);
       sw.report();
-      return Pair.create(permanentGraph, refMap);
+      return dataPack;
     }
 
     @NotNull
-    private Collection<List<? extends GraphCommit<Integer>>> readFullLogFromVcs() throws VcsException {
+    private LogInfo readFullLogFromVcs() throws VcsException {
       final StopWatch sw = StopWatch.start("read full log from VCS");
-      final Collection<List<? extends GraphCommit<Integer>>> logs = ContainerUtil.newArrayList();
+      final LogInfo logInfo = new LogInfo();
       new ProviderIterator() {
         @Override
         void each(@NotNull VirtualFile root, @NotNull VcsLogProvider provider) throws VcsException {
           final List<GraphCommit<Integer>> graphCommits = ContainerUtil.newArrayList();
-          provider.readAllHashes(root, new Consumer<VcsUser>() {
-            @Override
-            public void consume(@NotNull VcsUser user) {
-              myUserRegistry.addUser(user);
-            }
-          }, new Consumer<TimedVcsCommit>() {
+          VcsLogProvider.LogData data = provider.readAllHashes(root, new Consumer<TimedVcsCommit>() {
             @Override
-            public void consume(TimedVcsCommit commit) {
-              graphCommits.add(compactCommit(commit, myHashMap));
+            public void consume(@NotNull TimedVcsCommit commit) {
+              graphCommits.add(compactCommit(commit));
             }
           });
-          logs.add(graphCommits);
+          logInfo.put(root, graphCommits);
+          logInfo.put(root, data.getRefs());
+          myUserRegistry.addUsers(data.getUsers());
           sw.rootCompleted(root);
         }
       }.iterate(myProviders);
       myUserRegistry.flush();
       sw.report();
-      return logs;
+      return logInfo;
     }
   }
 
   private static class RefreshRequest {
-    private static RefreshRequest RELOAD_ALL = new RefreshRequest(Collections.<VirtualFile>emptyList());
-    @NotNull private final Collection<VirtualFile> rootsToRefresh;
+    private static final RefreshRequest RELOAD_ALL = new RefreshRequest(Collections.<VirtualFile>emptyList());
+    private final Collection<VirtualFile> rootsToRefresh;
 
     RefreshRequest(@NotNull Collection<VirtualFile> rootsToRefresh) {
       this.rootsToRefresh = rootsToRefresh;
@@ -477,4 +374,85 @@ public class VcsLogRefresherImpl implements VcsLogRefresher {
       }
     }
   }
+
+  private static class CommitCountRequirements implements VcsLogProvider.Requirements {
+    private final int myCommitCount;
+
+    public CommitCountRequirements(int commitCount) {
+      myCommitCount = commitCount;
+    }
+
+    @Override
+    public int getCommitCount() {
+      return myCommitCount;
+    }
+
+    @NotNull
+    Map<VirtualFile, VcsLogProvider.Requirements> asMap(@NotNull Collection<VirtualFile> roots) {
+      return ContainerUtil.map2Map(roots, new Function<VirtualFile, Pair<VirtualFile, VcsLogProvider.Requirements>>() {
+        @Override
+        public Pair<VirtualFile, VcsLogProvider.Requirements> fun(VirtualFile root) {
+          return Pair.<VirtualFile, VcsLogProvider.Requirements>create(root, CommitCountRequirements.this);
+        }
+      });
+    }
+  }
+
+  private static class LogInfo {
+    private final Map<VirtualFile, Set<VcsRef>> myRefs = ContainerUtil.newHashMap();
+    private final Map<VirtualFile, List<GraphCommit<Integer>>> myCommits = ContainerUtil.newHashMap();
+
+    void put(@NotNull VirtualFile root, @NotNull List<GraphCommit<Integer>> commits) {
+      myCommits.put(root, commits);
+    }
+
+    void put(@NotNull VirtualFile root, @NotNull Set<VcsRef> refs) {
+      myRefs.put(root, refs);
+    }
+
+    @NotNull
+    Collection<List<GraphCommit<Integer>>> getCommits() {
+      return myCommits.values();
+    }
+
+    List<GraphCommit<Integer>> getCommits(@NotNull VirtualFile root) {
+      return myCommits.get(root);
+    }
+
+    @NotNull
+    Map<VirtualFile, Set<VcsRef>> getRefs() {
+      return myRefs;
+    }
+
+    public Set<VcsRef> getRefs(@NotNull VirtualFile root) {
+      return myRefs.get(root);
+    }
+
+    @SuppressWarnings("StringConcatenationInsideStringBufferAppend")
+    @NotNull
+    public String toLogString(@NotNull final NotNullFunction<Hash, Integer> indexGetter) {
+      StringBuilder sb = new StringBuilder();
+      sb.append(" LOG:\n");
+      for (Map.Entry<VirtualFile, List<GraphCommit<Integer>>> entry : myCommits.entrySet()) {
+        sb.append(entry.getKey().getName() + "\n");
+        sb.append(StringUtil.join(entry.getValue(), new Function<GraphCommit<Integer>, String>() {
+          @Override
+          public String fun(@NotNull GraphCommit<Integer> commit) {
+            return commit.getId() + "<-" + StringUtil.join(commit.getParents(), ",");
+          }
+        }, "\n"));
+      }
+        sb.append("\nREFS:\n");
+      for (Map.Entry<VirtualFile, Set<VcsRef>> entry : myRefs.entrySet()) {
+        sb.append(entry.getKey().getName() + "\n");
+        sb.append(StringUtil.join(entry.getValue(), new Function<VcsRef, String>() {
+          @Override
+          public String fun(@NotNull VcsRef ref) {
+            return ref.getName() + "(" + indexGetter.fun(ref.getCommitHash()) + ")";
+          }
+        }, ","));
+      }
+      return sb.toString();
+    }
+  }
 }
index a5447d20f115fbd12f4d155c8d8328f99358799d..8621f379ce6519922b9a0e599e7692274e7df455 100644 (file)
@@ -86,6 +86,12 @@ public class VcsUserRegistryImpl implements Disposable, VcsUserRegistry {
     }
   }
 
+  public void addUsers(@NotNull Collection<VcsUser> users) {
+    for (VcsUser user : users) {
+      addUser(user);
+    }
+  }
+
   @Override
   @NotNull
   public Set<VcsUser> getUsers() {
diff --git a/platform/vcs-log/impl/src/com/intellij/vcs/log/impl/LogDataImpl.java b/platform/vcs-log/impl/src/com/intellij/vcs/log/impl/LogDataImpl.java
new file mode 100644 (file)
index 0000000..82eb4e1
--- /dev/null
@@ -0,0 +1,74 @@
+/*
+ * Copyright 2000-2014 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.vcs.log.impl;
+
+import com.intellij.vcs.log.VcsCommitMetadata;
+import com.intellij.vcs.log.VcsLogProvider;
+import com.intellij.vcs.log.VcsRef;
+import com.intellij.vcs.log.VcsUser;
+import org.jetbrains.annotations.NotNull;
+
+import java.util.Collections;
+import java.util.List;
+import java.util.Set;
+
+public class LogDataImpl implements VcsLogProvider.DetailedLogData, VcsLogProvider.LogData {
+
+  private static final LogDataImpl EMPTY = new LogDataImpl(Collections.<VcsRef>emptySet(),
+                                                           Collections.<VcsUser>emptySet(),
+                                                           Collections.<VcsCommitMetadata>emptyList());
+
+  @NotNull private final List<VcsCommitMetadata> myCommits;
+  @NotNull private final Set<VcsRef> myRefs;
+  @NotNull private final Set<VcsUser> myUsers;
+
+  @NotNull
+  public static LogDataImpl empty() {
+    return EMPTY;
+  }
+
+  public LogDataImpl(@NotNull Set<VcsRef> refs, @NotNull Set<VcsUser> users) {
+    this(refs, users, Collections.<VcsCommitMetadata>emptyList());
+  }
+
+  public LogDataImpl(@NotNull Set<VcsRef> refs, @NotNull List<VcsCommitMetadata> metadatas) {
+    this(refs, Collections.<VcsUser>emptySet(), metadatas);
+  }
+
+  private LogDataImpl(@NotNull Set<VcsRef> refs, @NotNull Set<VcsUser> users, @NotNull List<VcsCommitMetadata> commits) {
+    myRefs = refs;
+    myUsers = users;
+    myCommits = commits;
+  }
+
+  @NotNull
+  @Override
+  public List<VcsCommitMetadata> getCommits() {
+    return myCommits;
+  }
+
+  @Override
+  @NotNull
+  public Set<VcsRef> getRefs() {
+    return myRefs;
+  }
+
+  @NotNull
+  @Override
+  public Set<VcsUser> getUsers() {
+    return myUsers;
+  }
+}
index f742e277283f5f3cce82eafcf7d6e0e0b147335d..9a1e54ed3886a39fb4a9abd4a0e3c3ed71c70c42 100644 (file)
@@ -26,13 +26,11 @@ public class RequirementsImpl implements VcsLogProviderRequirementsEx {
   private final int myCommitCount;
   private final boolean myRefresh;
   @NotNull private final Set<VcsRef> myPreviousRefs;
-  @NotNull private final Set<VcsRef> myCurrentRefs;
 
-  public RequirementsImpl(int count, boolean refresh, @NotNull Set<VcsRef> previousRefs, @NotNull Set<VcsRef> currentRefs) {
+  public RequirementsImpl(int count, boolean refresh, @NotNull Set<VcsRef> previousRefs) {
     myCommitCount = count;
     myRefresh = refresh;
     myPreviousRefs = previousRefs;
-    myCurrentRefs = currentRefs;
   }
 
   @Override
@@ -50,10 +48,4 @@ public class RequirementsImpl implements VcsLogProviderRequirementsEx {
   public Set<VcsRef> getPreviousRefs() {
     return myPreviousRefs;
   }
-
-  @NotNull
-  @Override
-  public Set<VcsRef> getCurrentRefs() {
-    return myCurrentRefs;
-  }
 }
index 850478b048dc84e00ec89fc41c5b3daf24e7cb72..05659538da20ffaeb2026ba8b237c4c97d777773 100644 (file)
@@ -26,10 +26,8 @@ import org.jetbrains.annotations.NotNull;
 import org.jetbrains.annotations.Nullable;
 
 import java.awt.*;
-import java.util.Collection;
-import java.util.Comparator;
+import java.util.*;
 import java.util.List;
-import java.util.Set;
 import java.util.concurrent.Semaphore;
 
 import static org.junit.Assert.assertEquals;
@@ -59,13 +57,14 @@ public class TestVcsLogProvider implements VcsLogProvider {
   @NotNull private final ReducibleSemaphore myRefreshSemaphore;
   private int myReadFirstBlockCounter;
 
-  private final Function<TimedVcsCommit, VcsCommitMetadata> myCommitToMetadataConvertor = new Function<TimedVcsCommit, VcsCommitMetadata>(){
-    @Override
-    public VcsCommitMetadata fun(TimedVcsCommit commit) {
-      return new VcsCommitMetadataImpl(commit.getId(), commit.getParents(), commit.getTimestamp(), myRoot, SAMPLE_SUBJECT, STUB_USER,
-                                       SAMPLE_SUBJECT, STUB_USER, commit.getTimestamp());
-    }
-  };
+  private final Function<TimedVcsCommit, VcsCommitMetadata> myCommitToMetadataConvertor =
+    new Function<TimedVcsCommit, VcsCommitMetadata>() {
+      @Override
+      public VcsCommitMetadata fun(TimedVcsCommit commit) {
+        return new VcsCommitMetadataImpl(commit.getId(), commit.getParents(), commit.getTimestamp(), myRoot,
+                                                               SAMPLE_SUBJECT, STUB_USER, SAMPLE_SUBJECT, STUB_USER, commit.getTimestamp());
+      }
+    };
 
   public TestVcsLogProvider(@NotNull VirtualFile root) {
     myRoot = root;
@@ -78,7 +77,8 @@ public class TestVcsLogProvider implements VcsLogProvider {
 
   @NotNull
   @Override
-  public List<? extends VcsCommitMetadata> readFirstBlock(@NotNull final VirtualFile root, @NotNull Requirements requirements)
+  public DetailedLogData readFirstBlock(@NotNull final VirtualFile root,
+                                                   @NotNull Requirements requirements)
     throws VcsException {
     if (requirements instanceof VcsLogProviderRequirementsEx && ((VcsLogProviderRequirementsEx)requirements).isRefresh()) {
       try {
@@ -90,12 +90,14 @@ public class TestVcsLogProvider implements VcsLogProvider {
     }
     myReadFirstBlockCounter++;
     assertRoot(root);
-    return ContainerUtil.map(myCommits.subList(0, requirements.getCommitCount()), myCommitToMetadataConvertor);
+    List<VcsCommitMetadata> metadatas = ContainerUtil.map(myCommits.subList(0, requirements.getCommitCount()),
+                                                          myCommitToMetadataConvertor);
+    return new LogDataImpl(Collections.<VcsRef>emptySet(), metadatas);
   }
 
+  @NotNull
   @Override
-  public void readAllHashes(@NotNull VirtualFile root, @NotNull Consumer<VcsUser> userRegistry,
-                            @NotNull Consumer<TimedVcsCommit> commitConsumer) throws VcsException {
+  public LogData readAllHashes(@NotNull VirtualFile root, @NotNull Consumer<TimedVcsCommit> commitConsumer) throws VcsException {
     try {
       myFullLogSemaphore.acquire();
     }
@@ -106,6 +108,7 @@ public class TestVcsLogProvider implements VcsLogProvider {
     for (TimedVcsCommit commit : myCommits) {
       commitConsumer.consume(commit);
     }
+    return new LogDataImpl(myRefs, Collections.<VcsUser>emptySet());
   }
 
   private void assertRoot(@NotNull VirtualFile root) {
@@ -125,12 +128,6 @@ public class TestVcsLogProvider implements VcsLogProvider {
     throw new UnsupportedOperationException();
   }
 
-  @NotNull
-  @Override
-  public Collection<VcsRef> readAllRefs(@NotNull VirtualFile root) throws VcsException {
-    return myRefs;
-  }
-
   @NotNull
   @Override
   public VcsKey getSupportedVcs() {
index 6e178b9cbc0f50e4f7b821c898fb66e4737c2979..2504c345b5596651f5b83b185769143559d98ed1 100644 (file)
@@ -314,6 +314,9 @@ public class GitBranchUtil {
     else if (branchName.startsWith(GitBranch.REFS_REMOTES_PREFIX)) {
       return branchName.substring(GitBranch.REFS_REMOTES_PREFIX.length());
     }
+    else if (branchName.startsWith(GitTag.REFS_TAGS_PREFIX)) {
+      return branchName.substring(GitTag.REFS_TAGS_PREFIX.length());
+    }
     return branchName;
   }
 
index e44241289c068502bd042b61826bbfbc8e41c149..a0030c8158a5789395c1a1525fafaec0e5316b41 100644 (file)
@@ -39,6 +39,7 @@ import com.intellij.util.*;
 import com.intellij.util.concurrency.Semaphore;
 import com.intellij.util.containers.ContainerUtil;
 import com.intellij.vcs.log.*;
+import com.intellij.vcs.log.impl.LogDataImpl;
 import com.intellij.vcs.log.impl.HashImpl;
 import com.intellij.vcs.log.util.StopWatch;
 import git4idea.*;
@@ -53,6 +54,7 @@ import git4idea.history.browser.SymbolicRefsI;
 import git4idea.history.wholeTree.AbstractHash;
 import git4idea.history.wholeTree.CommitHashPlusParents;
 import git4idea.history.wholeTree.GitCommitsSequentialIndex;
+import git4idea.log.GitRefManager;
 import org.jetbrains.annotations.NotNull;
 import org.jetbrains.annotations.Nullable;
 
@@ -550,21 +552,12 @@ public class GitHistoryUtils {
     });
   }
 
-  @NotNull
-  public static List<TimedVcsCommit> readCommits(@NotNull final Project project,
-                                                 @NotNull VirtualFile root,
-                                                 @NotNull final Consumer<VcsUser> userRegistry,
-                                                 @NotNull List<String> parameters) throws VcsException {
-    List<TimedVcsCommit> collector = ContainerUtil.newArrayList();
-    readCommits(project, root, userRegistry, parameters, new CollectConsumer<TimedVcsCommit>(collector));
-    return collector;
-  }
-
   public static void readCommits(@NotNull final Project project,
-                                                 @NotNull VirtualFile root,
-                                                 @NotNull final Consumer<VcsUser> userRegistry,
-                                                 @NotNull List<String> parameters,
-                                                 @NotNull final Consumer<TimedVcsCommit> commitConsumer) throws VcsException {
+                                 @NotNull final VirtualFile root,
+                                 @NotNull List<String> parameters,
+                                 @NotNull final Consumer<VcsUser> userConsumer,
+                                 @NotNull final Consumer<VcsRef> refConsumer,
+                                 @NotNull final Consumer<TimedVcsCommit> commitConsumer) throws VcsException {
     final VcsLogObjectsFactory factory = getObjectsFactoryWithDisposeCheck(project);
     if (factory == null) {
       return;
@@ -573,11 +566,12 @@ public class GitHistoryUtils {
     final int COMMIT_BUFFER = 1000;
     GitLineHandler h = new GitLineHandler(project, root, GitCommand.LOG);
     final GitLogParser parser = new GitLogParser(project, GitLogParser.NameStatus.NONE, HASH, PARENTS, COMMIT_TIME,
-                                                 AUTHOR_NAME, AUTHOR_EMAIL);
+                                                 AUTHOR_NAME, AUTHOR_EMAIL, REF_NAMES);
     h.setStdoutSuppressed(true);
     h.addParameters(parser.getPretty(), "--encoding=UTF-8");
     h.addParameters("--full-history");
     h.addParameters("--date-order");
+    h.addParameters("--decorate=full");
     h.addParameters(parameters);
     h.endOptions();
 
@@ -603,7 +597,7 @@ public class GitHistoryUtils {
             afterParseRemainder = line.substring(recordEnd + 1);
           }
           if (afterParseRemainder != null && records.incrementAndGet() > COMMIT_BUFFER) { // null means can't parse now
-            List<TimedVcsCommit> commits = parseCommit(parser, record, userRegistry, factory);
+            List<TimedVcsCommit> commits = parseCommit(parser, record, userConsumer, refConsumer, factory, root);
             for (TimedVcsCommit commit : commits) {
               commitConsumer.consume(commit);
             }
@@ -619,7 +613,7 @@ public class GitHistoryUtils {
       @Override
       public void processTerminated(int exitCode) {
         try {
-          List<TimedVcsCommit> commits = parseCommit(parser, record, userRegistry, factory);
+          List<TimedVcsCommit> commits = parseCommit(parser, record, userConsumer, refConsumer, factory, root);
           for (TimedVcsCommit commit : commits) {
             commitConsumer.consume(commit);
           }
@@ -641,9 +635,12 @@ public class GitHistoryUtils {
   }
 
   @NotNull
-  private static List<TimedVcsCommit> parseCommit(@NotNull GitLogParser parser, @NotNull StringBuilder record,
+  private static List<TimedVcsCommit> parseCommit(@NotNull GitLogParser parser,
+                                                  @NotNull StringBuilder record,
                                                   @NotNull final Consumer<VcsUser> userRegistry,
-                                                  @NotNull final VcsLogObjectsFactory factory) {
+                                                  @NotNull final Consumer<VcsRef> refConsumer,
+                                                  @NotNull final VcsLogObjectsFactory factory,
+                                                  @NotNull final VirtualFile root) {
     List<GitLogRecord> rec = parser.parse(record.toString());
     return ContainerUtil.mapNotNull(rec, new Function<GitLogRecord, TimedVcsCommit>() {
       @Override
@@ -651,7 +648,11 @@ public class GitHistoryUtils {
         if (record == null) {
           return null;
         }
-        TimedVcsCommit commit = convert(record, factory);
+        Pair<TimedVcsCommit, Collection<VcsRef>> pair = convert(record, factory, root);
+        TimedVcsCommit commit = pair.first;
+        for (VcsRef ref : pair.second) {
+          refConsumer.consume(ref);
+        }
         userRegistry.consume(factory.createUser(record.getAuthorName(), record.getAuthorEmail()));
         return commit;
       }
@@ -659,9 +660,28 @@ public class GitHistoryUtils {
   }
 
   @NotNull
-  private static TimedVcsCommit convert(@NotNull GitLogRecord rec, @NotNull VcsLogObjectsFactory factory) {
+  private static Pair<TimedVcsCommit, Collection<VcsRef>> convert(@NotNull GitLogRecord rec,
+                                                                  @NotNull VcsLogObjectsFactory factory,
+                                                                  @NotNull VirtualFile root) {
+    Hash hash = HashImpl.build(rec.getHash());
     List<Hash> parents = getParentHashes(factory, rec);
-    return factory.createTimedCommit(HashImpl.build(rec.getHash()), parents, rec.getCommitTime());
+    TimedVcsCommit commit = factory.createTimedCommit(hash, parents, rec.getCommitTime());
+    return Pair.create(commit, parseRefs(rec.getRefs(), hash, factory, root));
+  }
+
+  @NotNull
+  private static Collection<VcsRef> parseRefs(@NotNull Collection<String> refs,
+                                              @NotNull final Hash hash,
+                                              @NotNull final VcsLogObjectsFactory factory,
+                                              @NotNull final VirtualFile root) {
+    return ContainerUtil.mapNotNull(refs, new Function<String, VcsRef>() {
+      @Override
+      public VcsRef fun(String refName) {
+        VcsRefType type = GitRefManager.getRefType(refName);
+        refName = GitBranchUtil.stripRefsPrefix(refName);
+        return factory.createRef(hash, refName, type, root);
+      }
+    });
   }
 
   @Nullable
@@ -784,23 +804,28 @@ public class GitHistoryUtils {
   }
 
   @NotNull
-  public static List<VcsCommitMetadata> loadMetadata(@NotNull Project project, @NotNull final VirtualFile root,
-                                                               @NotNull String... parameters) throws VcsException {
-
+  public static VcsLogProvider.DetailedLogData loadMetadata(@NotNull final Project project,
+                                                            @NotNull final VirtualFile root,
+                                                            final boolean withRefs,
+                                                            String... params) throws VcsException {
     final VcsLogObjectsFactory factory = getObjectsFactoryWithDisposeCheck(project);
     if (factory == null) {
-      return Collections.emptyList();
+      return LogDataImpl.empty();
     }
-    return loadDetails(project, root, false, new NullableFunction<GitLogRecord, VcsCommitMetadata>() {
-      @Nullable
-      @Override
-      public VcsCommitMetadata fun(GitLogRecord record) {
-        return factory.createCommitMetadata(factory.createHash(record.getHash()), getParentHashes(factory, record), record.getCommitTime(), 
-                                            root, record.getSubject(), record.getAuthorName(), record.getAuthorEmail(),
-                                            record.getFullMessage(), record.getCommitterName(), record.getCommitterEmail(),
-                                            record.getAuthorTimeStamp());
-      }
-    }, parameters);
+    final Set<VcsRef> refs = ContainerUtil.newHashSet();
+    final List<VcsCommitMetadata> commits =
+      loadDetails(project, root, withRefs, false, new NullableFunction<GitLogRecord, VcsCommitMetadata>() {
+        @Nullable
+        @Override
+        public VcsCommitMetadata fun(GitLogRecord record) {
+          GitCommit commit = createCommit(project, root, record, factory);
+          if (withRefs) {
+            refs.addAll(parseRefs(record.getRefs(), commit.getId(), factory, root));
+          }
+          return commit;
+        }
+      }, params);
+    return new LogDataImpl(refs, commits);
   }
 
   /**
@@ -816,33 +841,37 @@ public class GitHistoryUtils {
     if (factory == null) {
       return Collections.emptyList();
     }
-    return loadDetails(project, root, true, new NullableFunction<GitLogRecord, GitCommit>() {
+    return loadDetails(project, root, true, true, new NullableFunction<GitLogRecord, GitCommit>() {
       @Override
       @Nullable
       public GitCommit fun(GitLogRecord record) {
-        try {
-          return createCommit(project, root, record, factory);
-        }
-        catch (VcsException e) {
-          LOG.error(e);
-          return null;
-        }
+        return createCommit(project, root, record, factory);
       }
     }, parameters);
   }
 
   @NotNull
-  public static <T> List<T> loadDetails(@NotNull final Project project, @NotNull final VirtualFile root, boolean withChanges,
-                                        @NotNull NullableFunction<GitLogRecord, T> converter, String... parameters)
+  public static <T> List<T> loadDetails(@NotNull final Project project,
+                                        @NotNull final VirtualFile root,
+                                        boolean withRefs,
+                                        boolean withChanges,
+                                        @NotNull NullableFunction<GitLogRecord, T> converter,
+                                        String... parameters)
                                         throws VcsException {
     GitSimpleHandler h = new GitSimpleHandler(project, root, GitCommand.LOG);
     GitLogParser.NameStatus status = withChanges ? GitLogParser.NameStatus.STATUS : GitLogParser.NameStatus.NONE;
-    GitLogParser parser = new GitLogParser(project, status, HASH, COMMIT_TIME, AUTHOR_NAME, AUTHOR_TIME,
-                                           AUTHOR_EMAIL, COMMITTER_NAME, COMMITTER_EMAIL, PARENTS, SUBJECT, BODY, RAW_BODY);
+    GitLogParser.GitLogOption[] options = { HASH, COMMIT_TIME, AUTHOR_NAME, AUTHOR_TIME, AUTHOR_EMAIL, COMMITTER_NAME, COMMITTER_EMAIL,
+      PARENTS, SUBJECT, BODY, RAW_BODY };
+    if (withRefs) {
+      options = ArrayUtil.append(options, REF_NAMES);
+    }
+    GitLogParser parser = new GitLogParser(project, status, options);
     h.setStdoutSuppressed(true);
     h.addParameters(parameters);
     h.addParameters(parser.getPretty(), "--encoding=UTF-8");
-    h.addParameters("--full-history", "--sparse");
+    if (withRefs) {
+      h.addParameters("--decorate=full");
+    }
     if (withChanges) {
       h.addParameters("-M", "--name-status");
     }
@@ -863,7 +892,7 @@ public class GitHistoryUtils {
   }
 
   private static GitCommit createCommit(@NotNull Project project, @NotNull VirtualFile root, @NotNull GitLogRecord record,
-                                        @NotNull VcsLogObjectsFactory factory) throws VcsException {
+                                        @NotNull VcsLogObjectsFactory factory) {
     List<Hash> parents = getParentHashes(factory, record);
     return new GitCommit(project, HashImpl.build(record.getHash()), parents, record.getCommitTime(), root, record.getSubject(),
                          factory.createUser(record.getAuthorName(), record.getAuthorEmail()), record.getFullMessage(),
index b5096a94f03b1191c9b1099d8d3c5e6b082599ff..3cc80e920942d5abcf031d65770f69e4e4dd92fd 100644 (file)
  */
 package git4idea.log;
 
-import com.intellij.openapi.diagnostic.Attachment;
 import com.intellij.openapi.diagnostic.Logger;
 import com.intellij.openapi.project.Project;
-import com.intellij.openapi.util.Condition;
 import com.intellij.openapi.util.text.StringUtil;
 import com.intellij.openapi.vcs.VcsException;
 import com.intellij.openapi.vcs.VcsKey;
 import com.intellij.openapi.vfs.VirtualFile;
-import com.intellij.util.ArrayUtil;
-import com.intellij.util.Consumer;
-import com.intellij.util.ExceptionUtil;
-import com.intellij.util.Function;
+import com.intellij.util.*;
 import com.intellij.util.containers.ContainerUtil;
+import com.intellij.util.containers.OpenTHashSet;
 import com.intellij.vcs.log.*;
 import com.intellij.vcs.log.data.VcsLogSorter;
+import com.intellij.vcs.log.impl.LogDataImpl;
 import com.intellij.vcs.log.impl.HashImpl;
-import git4idea.GitLocalBranch;
-import git4idea.GitRemoteBranch;
-import git4idea.GitUserRegistry;
-import git4idea.GitVcs;
+import git4idea.*;
 import git4idea.branch.GitBranchUtil;
-import git4idea.commands.GitCommand;
-import git4idea.commands.GitSimpleHandler;
 import git4idea.history.GitHistoryUtils;
-import git4idea.history.GitLogParser;
 import git4idea.repo.GitRepository;
 import git4idea.repo.GitRepositoryChangeListener;
 import git4idea.repo.GitRepositoryManager;
+import gnu.trove.TObjectHashingStrategy;
 import org.jetbrains.annotations.NotNull;
 import org.jetbrains.annotations.Nullable;
 
@@ -51,6 +43,23 @@ import java.util.*;
 public class GitLogProvider implements VcsLogProvider {
 
   private static final Logger LOG = Logger.getInstance(GitLogProvider.class);
+  public static final Function<VcsRef, String> GET_TAG_NAME = new Function<VcsRef, String>() {
+    @Override
+    public String fun(VcsRef ref) {
+      return ref.getType() == GitRefManager.TAG ? ref.getName() : null;
+    }
+  };
+  private static final TObjectHashingStrategy<VcsRef> REF_ONLY_NAME_STRATEGY = new TObjectHashingStrategy<VcsRef>() {
+    @Override
+    public int computeHashCode(@NotNull VcsRef ref) {
+      return ref.getName().hashCode();
+    }
+
+    @Override
+    public boolean equals(@NotNull VcsRef ref1, @NotNull VcsRef ref2) {
+      return ref1.getName().equals(ref2.getName());
+    }
+  };
 
   @NotNull private final Project myProject;
   @NotNull private final GitRepositoryManager myRepositoryManager;
@@ -69,108 +78,122 @@ public class GitLogProvider implements VcsLogProvider {
 
   @NotNull
   @Override
-  public List<? extends VcsCommitMetadata> readFirstBlock(@NotNull VirtualFile root, @NotNull Requirements requirements) throws VcsException {
+  public DetailedLogData readFirstBlock(@NotNull VirtualFile root, @NotNull Requirements requirements) throws VcsException {
     if (!isRepositoryReady(root)) {
-      return Collections.emptyList();
+      return LogDataImpl.empty();
     }
+    GitRepository repository = ObjectUtils.assertNotNull(myRepositoryManager.getRepositoryForRoot(root));
 
     // need to query more to sort them manually; this doesn't affect performance: it is equal for -1000 and -2000
     int commitCount = requirements.getCommitCount() * 2;
 
     String[] params = new String[]{"HEAD", "--branches", "--remotes", "--max-count=" + commitCount};
     // NB: not specifying --tags, because it introduces great slowdown if there are many tags,
-    // but makes sense only if there are heads without branch or HEAD labels (rare case). Such cases are partially handles below.
-    List<VcsCommitMetadata> firstBlock = GitHistoryUtils.loadMetadata(myProject, root, params);
+    // but makes sense only if there are heads without branch or HEAD labels (rare case). Such cases are partially handled below.
 
-    if (requirements instanceof VcsLogProviderRequirementsEx) {
-      VcsLogProviderRequirementsEx rex = (VcsLogProviderRequirementsEx)requirements;
+    boolean refresh = requirements instanceof VcsLogProviderRequirementsEx && ((VcsLogProviderRequirementsEx)requirements).isRefresh();
+
+    DetailedLogData data = GitHistoryUtils.loadMetadata(myProject, root, refresh, params);
+
+    Set<VcsRef> safeRefs = data.getRefs();
+    Set<VcsRef> allRefs = new OpenTHashSet<VcsRef>(safeRefs, REF_ONLY_NAME_STRATEGY);
+    addNewElements(allRefs, readBranches(repository));
+
+    Collection<VcsCommitMetadata> allDetails;
+    if (!refresh) {
+      allDetails = data.getCommits();
+    }
+    else {
       // on refresh: get new tags, which point to commits not from the first block; then get history, walking down just from these tags
       // on init: just ignore such tagged-only branches. The price for speed-up.
-      if (rex.isRefresh()) {
-        Collection<VcsRef> newTags = getNewTags(rex.getCurrentRefs(), rex.getPreviousRefs());
-        if (!newTags.isEmpty()) {
-          final Set<Hash> firstBlockHashes = ContainerUtil.map2Set(firstBlock, new Function<VcsCommitMetadata, Hash>() {
-            @Override
-            public Hash fun(VcsCommitMetadata metadata) {
-              return metadata.getId();
-            }
-          });
-          List<VcsRef> unmatchedHeads = getUnmatchedHeads(firstBlockHashes, newTags);
-          if (!unmatchedHeads.isEmpty()) {
-            List<VcsCommitMetadata> detailsFromTaggedBranches = loadSomeCommitsOnTaggedBranches(root, commitCount, unmatchedHeads);
-            Collection<VcsCommitMetadata> unmatchedCommits = getUnmatchedCommits(firstBlockHashes, detailsFromTaggedBranches);
-            firstBlock.addAll(unmatchedCommits);
-          }
-        }
+      VcsLogProviderRequirementsEx rex = (VcsLogProviderRequirementsEx)requirements;
+
+      Set<String> currentTags = readCurrentTagNames(root);
+      addOldStillExistingTags(allRefs, currentTags, rex.getPreviousRefs());
+
+      allDetails = ContainerUtil.newHashSet(data.getCommits());
+
+      Set<String> previousTags = new HashSet<String>(ContainerUtil.mapNotNull(rex.getPreviousRefs(), GET_TAG_NAME));
+      Set<String> safeTags = new HashSet<String>(ContainerUtil.mapNotNull(safeRefs, GET_TAG_NAME));
+      Set<String> newUnmatchedTags = remove(currentTags, previousTags, safeTags);
+
+      if (!newUnmatchedTags.isEmpty()) {
+        DetailedLogData commitsFromTags = loadSomeCommitsOnTaggedBranches(root, commitCount, newUnmatchedTags);
+        addNewElements(allDetails, commitsFromTags.getCommits());
+        addNewElements(allRefs, commitsFromTags.getRefs());
       }
     }
 
-    firstBlock = VcsLogSorter.sortByDateTopoOrder(firstBlock);
-    firstBlock = new ArrayList<VcsCommitMetadata>(firstBlock.subList(0, Math.min(firstBlock.size(), requirements.getCommitCount())));
-    return firstBlock;
+    List<VcsCommitMetadata> sortedCommits = VcsLogSorter.sortByDateTopoOrder(allDetails);
+    sortedCommits = sortedCommits.subList(0, Math.min(sortedCommits.size(), requirements.getCommitCount()));
+
+    return new LogDataImpl(allRefs, sortedCommits);
   }
 
-  @NotNull
-  private static Collection<VcsRef> getNewTags(@NotNull Set<VcsRef> currentRefs, @NotNull final Set<VcsRef> previousRefs) {
-    return ContainerUtil.filter(currentRefs, new Condition<VcsRef>() {
-      @Override
-      public boolean value(VcsRef ref) {
-        return !ref.getType().isBranch() && !previousRefs.contains(ref);
+  private static void addOldStillExistingTags(@NotNull Set<VcsRef> allRefs,
+                                              @NotNull Set<String> currentTags,
+                                              @NotNull Set<VcsRef> previousRefs) {
+    for (VcsRef ref : previousRefs) {
+      if (!allRefs.contains(ref) && currentTags.contains(ref.getName())) {
+        allRefs.add(ref);
       }
-    });
+    }
   }
 
   @NotNull
-  private List<VcsCommitMetadata> loadSomeCommitsOnTaggedBranches(@NotNull VirtualFile root, int commitCount,
-                                                                  @NotNull List<VcsRef> unmatchedHeads) throws VcsException {
-    List<String> params = new ArrayList<String>(ContainerUtil.map(unmatchedHeads, new Function<VcsRef, String>() {
-      @Override
-      public String fun(VcsRef ref) {
-        return ref.getCommitHash().asString();
-      }
-    }));
-    params.add("--max-count=" + commitCount);
-    return GitHistoryUtils.loadMetadata(myProject, root, ArrayUtil.toStringArray(params));
+  private Set<String> readCurrentTagNames(@NotNull VirtualFile root) throws VcsException {
+    Set<String> tags = ContainerUtil.newHashSet();
+    GitTag.listAsStrings(myProject, root, tags, null);
+    return tags;
   }
 
   @NotNull
-  private static List<VcsRef> getUnmatchedHeads(@NotNull final Set<Hash> firstBlockHashes, @NotNull Collection<VcsRef> refs) {
-    return ContainerUtil.filter(refs, new Condition<VcsRef>() {
-      @Override
-      public boolean value(VcsRef ref) {
-        return !firstBlockHashes.contains(ref.getCommitHash());
+  private static <T> Set<T> remove(@NotNull Set<T> original, @NotNull Set<T>... toRemove) {
+    Set<T> result = ContainerUtil.newHashSet(original);
+    for (Set<T> set : toRemove) {
+      result.removeAll(set);
+    }
+    return result;
+  }
+
+  private static <T> void addNewElements(@NotNull Collection<T> original, @NotNull Collection<T> toAdd) {
+    for (T item : toAdd) {
+      if (!original.contains(item)) {
+        original.add(item);
       }
-    });
+    }
   }
 
   @NotNull
-  private static Collection<VcsCommitMetadata> getUnmatchedCommits(@NotNull final Set<Hash> firstBlockHashes,
-                                                                   @NotNull List<VcsCommitMetadata> detailsFromTaggedBranches) {
-    return ContainerUtil.filter(detailsFromTaggedBranches, new Condition<VcsCommitMetadata>() {
-      @Override
-      public boolean value(VcsCommitMetadata metadata) {
-        return !firstBlockHashes.contains(metadata.getId());
-      }
-    });
+  private DetailedLogData loadSomeCommitsOnTaggedBranches(@NotNull VirtualFile root, int commitCount,
+                                                          @NotNull Collection<String> unmatchedTags) throws VcsException {
+    List<String> params = new ArrayList<String>();
+    params.add("--max-count=" + commitCount);
+    params.addAll(unmatchedTags);
+    return GitHistoryUtils.loadMetadata(myProject, root, true, ArrayUtil.toStringArray(params));
   }
 
   @Override
-  public void readAllHashes(@NotNull VirtualFile root, @NotNull Consumer<VcsUser> userRegistry,
-                            @NotNull final Consumer<TimedVcsCommit> commitConsumer) throws VcsException {
+  @NotNull
+  public LogData readAllHashes(@NotNull VirtualFile root, @NotNull final Consumer<TimedVcsCommit> commitConsumer) throws VcsException {
     if (!isRepositoryReady(root)) {
-      return;
+      return LogDataImpl.empty();
     }
 
     List<String> parameters = new ArrayList<String>(GitHistoryUtils.LOG_ALL);
     parameters.add("--sparse");
 
     final GitBekParentFixer parentFixer = GitBekParentFixer.prepare(root, this);
-    GitHistoryUtils.readCommits(myProject, root, userRegistry, parameters, new Consumer<TimedVcsCommit>() {
+    Set<VcsUser> userRegistry = ContainerUtil.newHashSet();
+    Set<VcsRef> refs = ContainerUtil.newHashSet();
+    GitHistoryUtils.readCommits(myProject, root, parameters, new CollectConsumer<VcsUser>(userRegistry),
+                                new CollectConsumer<VcsRef>(refs), new Consumer<TimedVcsCommit>() {
       @Override
       public void consume(TimedVcsCommit commit) {
         commitConsumer.consume(parentFixer.fixCommit(commit));
       }
     });
+    return new LogDataImpl(refs, userRegistry);
   }
 
   @NotNull
@@ -187,17 +210,11 @@ public class GitLogProvider implements VcsLogProvider {
   }
 
   @NotNull
-  @Override
-  public Collection<VcsRef> readAllRefs(@NotNull VirtualFile root) throws VcsException {
-    if (!isRepositoryReady(root)) {
-      return Collections.emptyList();
-    }
-
-    GitRepository repository = getRepository(root);
-    repository.update();
+  private Set<VcsRef> readBranches(@NotNull GitRepository repository) {
+    VirtualFile root = repository.getRoot();
     Collection<GitLocalBranch> localBranches = repository.getBranches().getLocalBranches();
     Collection<GitRemoteBranch> remoteBranches = repository.getBranches().getRemoteBranches();
-    Collection<VcsRef> refs = new ArrayList<VcsRef>(localBranches.size() + remoteBranches.size());
+    Set<VcsRef> refs = new HashSet<VcsRef>(localBranches.size() + remoteBranches.size());
     for (GitLocalBranch localBranch : localBranches) {
       refs.add(
         myVcsObjectsFactory.createRef(HashImpl.build(localBranch.getHash()), localBranch.getName(), GitRefManager.LOCAL_BRANCH, root));
@@ -210,29 +227,6 @@ public class GitLogProvider implements VcsLogProvider {
     if (currentRevision != null) { // null => fresh repository
       refs.add(myVcsObjectsFactory.createRef(HashImpl.build(currentRevision), "HEAD", GitRefManager.HEAD, root));
     }
-
-    refs.addAll(readTags(root));
-    return refs;
-  }
-
-  // TODO this is to be removed when tags will be supported by the GitRepositoryReader
-  private Collection<? extends VcsRef> readTags(@NotNull VirtualFile root) throws VcsException {
-    GitSimpleHandler tagHandler = new GitSimpleHandler(myProject, root, GitCommand.LOG);
-    tagHandler.setSilent(true);
-    tagHandler.addParameters("--tags", "--no-walk", "--format=%H%d" + GitLogParser.RECORD_START_GIT, "--decorate=full");
-    String out = tagHandler.run();
-    Collection<VcsRef> refs = new ArrayList<VcsRef>();
-    try {
-      for (String record : out.split(GitLogParser.RECORD_START)) {
-        if (!StringUtil.isEmptyOrSpaces(record)) {
-          refs.addAll(new RefParser(myVcsObjectsFactory).parseCommitRefs(record.trim(), root));
-        }
-      }
-    }
-    catch (Exception e) {
-      LOG.error("Error during tags parsing", new Attachment("stack_trace.txt", ExceptionUtil.getThrowableText(e)),
-                new Attachment("git_output.txt", out));
-    }
     return refs;
   }
 
@@ -327,7 +321,10 @@ public class GitLogProvider implements VcsLogProvider {
       }
     }
 
-    return GitHistoryUtils.readCommits(myProject, root, Consumer.EMPTY_CONSUMER, filterParameters);
+    List<TimedVcsCommit> commits = ContainerUtil.newArrayList();
+    GitHistoryUtils.readCommits(myProject, root, filterParameters, Consumer.EMPTY_CONSUMER, Consumer.EMPTY_CONSUMER,
+                                new CollectConsumer<TimedVcsCommit>(commits));
+    return commits;
   }
 
   @Nullable
index 2cedc15b05ca78e3943077a092677920d27708af..6469a1fef1bdf71f039062e7e6ef78047e1a1eab 100644 (file)
@@ -15,6 +15,7 @@ import com.intellij.vcs.log.VcsRefType;
 import com.intellij.vcs.log.impl.SingletonRefGroup;
 import git4idea.GitBranch;
 import git4idea.GitRemoteBranch;
+import git4idea.GitTag;
 import git4idea.repo.GitBranchTrackInfo;
 import git4idea.repo.GitRemote;
 import git4idea.repo.GitRepository;
@@ -38,9 +39,10 @@ public class GitRefManager implements VcsLogRefManager {
   public static final VcsRefType LOCAL_BRANCH = new SimpleRefType(true, LOCAL_BRANCH_COLOR);
   public static final VcsRefType REMOTE_BRANCH = new SimpleRefType(true, REMOTE_BRANCH_COLOR);
   public static final VcsRefType TAG = new SimpleRefType(false, TAG_COLOR);
+  public static final VcsRefType OTHER = new SimpleRefType(false, TAG_COLOR);
 
   // first has the highest priority
-  private static final List<VcsRefType> REF_TYPE_PRIORITIES = Arrays.asList(HEAD, LOCAL_BRANCH, REMOTE_BRANCH, TAG);
+  private static final List<VcsRefType> REF_TYPE_PRIORITIES = Arrays.asList(HEAD, LOCAL_BRANCH, REMOTE_BRANCH, TAG, OTHER);
 
   // -1 => higher priority
   public static final Comparator<VcsRefType> REF_TYPE_COMPARATOR = new Comparator<VcsRefType>() {
@@ -243,6 +245,23 @@ public class GitRefManager implements VcsLogRefManager {
     return grouped;
   }
 
+  @NotNull
+  public static VcsRefType getRefType(@NotNull String refName) {
+    if (refName.startsWith(GitBranch.REFS_HEADS_PREFIX)) {
+      return LOCAL_BRANCH;
+    }
+    if (refName.startsWith(GitBranch.REFS_REMOTES_PREFIX)) {
+      return REMOTE_BRANCH;
+    }
+    if (refName.startsWith(GitTag.REFS_TAGS_PREFIX)) {
+      return TAG;
+    }
+    if (refName.startsWith("HEAD")) {
+      return HEAD;
+    }
+    return OTHER;
+  }
+
   private static class SimpleRefType implements VcsRefType {
     private final boolean myIsBranch;
     @NotNull private final Color myColor;
index 0bb67671622b6c229e6a16d70698a66ed40aedc4..313852f118b0cd285642bb64edf10781a6a900c3 100644 (file)
  */
 package git4idea.log;
 
+import com.intellij.openapi.components.ServiceManager;
 import com.intellij.openapi.extensions.Extensions;
 import com.intellij.openapi.util.Condition;
 import com.intellij.openapi.util.text.StringUtil;
 import com.intellij.openapi.vcs.VcsException;
 import com.intellij.util.ArrayUtil;
 import com.intellij.util.CollectConsumer;
-import com.intellij.util.Consumer;
 import com.intellij.util.Function;
 import com.intellij.util.containers.ContainerUtil;
 import com.intellij.vcs.log.*;
@@ -40,7 +40,8 @@ import static git4idea.test.GitExecutor.*;
 
 public class GitLogProviderTest extends GitSingleRepoTest {
 
-  @NotNull private GitLogProvider myLogProvider;
+  private GitLogProvider myLogProvider;
+  private VcsLogObjectsFactory myObjectsFactory;
 
   public void setUp() throws Exception {
     super.setUp();
@@ -53,6 +54,7 @@ public class GitLogProviderTest extends GitSingleRepoTest {
       });
     assertEquals("Incorrect number of GitLogProviders", 1, providers.size());
     myLogProvider = (GitLogProvider)providers.get(0);
+    myObjectsFactory = ServiceManager.getService(myProject, VcsLogObjectsFactory.class);
   }
 
   public void tearDown() throws Exception {
@@ -64,21 +66,51 @@ public class GitLogProviderTest extends GitSingleRepoTest {
     List<VcsCommitMetadata> expectedLogWithoutTaggedBranch = log();
     createTaggedBranch();
 
-    Set<VcsRef> noRefs = Collections.emptySet();
-    List<? extends VcsCommitMetadata> block = myLogProvider.readFirstBlock(myProjectRoot, new RequirementsImpl(1000, false, noRefs, noRefs));
-    assertOrderedEquals(block, expectedLogWithoutTaggedBranch);
+    VcsLogProvider.DetailedLogData block = myLogProvider.readFirstBlock(myProjectRoot,new RequirementsImpl(1000, false, Collections.<VcsRef>emptySet()));
+    assertOrderedEquals(block.getCommits(), expectedLogWithoutTaggedBranch);
   }
 
   public void test_refresh_with_new_tagged_branch() throws VcsException {
     prepareSomeHistory();
-    Set<VcsRef> prevRefs = ContainerUtil.newHashSet(myLogProvider.readAllRefs(myProjectRoot));
+    Set<VcsRef> prevRefs = readAllRefs();
     createTaggedBranch();
-    Set<VcsRef> newRefs = ContainerUtil.newHashSet(myLogProvider.readAllRefs(myProjectRoot));
 
     List<VcsCommitMetadata> expectedLog = log();
-    List<? extends VcsCommitMetadata> block = myLogProvider.readFirstBlock(myProjectRoot,
-                                                                           new RequirementsImpl(1000, true, prevRefs, newRefs));
-    assertSameElements(block, expectedLog);
+    VcsLogProvider.DetailedLogData block = myLogProvider.readFirstBlock(myProjectRoot, new RequirementsImpl(1000, true, prevRefs));
+    assertSameElements(block.getCommits(), expectedLog);
+  }
+
+  public void test_refresh_when_new_tag_moved() throws VcsException {
+    prepareSomeHistory();
+    Set<VcsRef> prevRefs = readAllRefs();
+    git("tag -f ATAG");
+
+    List<VcsCommitMetadata> expectedLog = log();
+    Set<VcsRef> refs = readAllRefs();
+    VcsLogProvider.DetailedLogData block = myLogProvider.readFirstBlock(myProjectRoot, new RequirementsImpl(1000, true, prevRefs));
+    assertSameElements(block.getCommits(), expectedLog);
+    assertSameElements(block.getRefs(), refs);
+  }
+
+  public void test_new_tag_on_old_commit() throws VcsException {
+    prepareSomeHistory();
+    Set<VcsRef> prevRefs = readAllRefs();
+    List<VcsCommitMetadata> log = log();
+    String firstCommit = log.get(log.size() - 1).getId().asString();
+    git("tag NEW_TAG " + firstCommit);
+
+    Set<VcsRef> refs = readAllRefs();
+    VcsLogProvider.DetailedLogData block = myLogProvider.readFirstBlock(myProjectRoot, new RequirementsImpl(1000, true, prevRefs));
+    assertSameElements(block.getRefs(), refs);
+  }
+
+  private Set<VcsRef> readAllRefs() {
+    String[] refs = StringUtil.splitByLines(git("log --branches --tags --no-walk --format=%H%d --decorate=full"));
+    Set<VcsRef> result = ContainerUtil.newHashSet();
+    for (String ref : refs) {
+      result.addAll(new RefParser(myObjectsFactory).parseCommitRefs(ref, myProjectRoot));
+    }
+    return result;
   }
 
   public void test_all_log_with_tagged_branch() throws VcsException {
@@ -87,7 +119,7 @@ public class GitLogProviderTest extends GitSingleRepoTest {
     List<VcsCommitMetadata> expectedLog = log();
     List<TimedVcsCommit> collector = ContainerUtil.newArrayList();
     //noinspection unchecked
-    myLogProvider.readAllHashes(myProjectRoot, Consumer.EMPTY_CONSUMER, new CollectConsumer<TimedVcsCommit>(collector));
+    myLogProvider.readAllHashes(myProjectRoot, new CollectConsumer<TimedVcsCommit>(collector));
     assertOrderedEquals(expectedLog, collector);
   }
 
similarity index 72%
rename from plugins/git4idea/src/git4idea/log/RefParser.java
rename to plugins/git4idea/tests/git4idea/log/RefParser.java
index 619cfcd5d4dad75b044ab30f33a07aaabf4a54c9..9a4cc962c6e7d6f7ef4522177d356e9357930844 100644 (file)
@@ -4,7 +4,9 @@ import com.intellij.openapi.vfs.VirtualFile;
 import com.intellij.vcs.log.Hash;
 import com.intellij.vcs.log.VcsLogObjectsFactory;
 import com.intellij.vcs.log.VcsRef;
+import com.intellij.vcs.log.VcsRefType;
 import com.intellij.vcs.log.impl.HashImpl;
+import git4idea.branch.GitBranchUtil;
 import org.jetbrains.annotations.NotNull;
 import org.jetbrains.annotations.Nullable;
 
@@ -12,17 +14,11 @@ import java.util.ArrayList;
 import java.util.Collections;
 import java.util.List;
 
-
-/**
- * TODO: remove when tags are supported by the {@link git4idea.repo.GitRepositoryReader}.
- *
- * @author erokhins
- */
 class RefParser {
 
   private final VcsLogObjectsFactory myFactory;
 
-  public RefParser(VcsLogObjectsFactory factory) {
+  public RefParser(@NotNull VcsLogObjectsFactory factory) {
     myFactory = factory;
   }
 
@@ -46,28 +42,22 @@ class RefParser {
     return refs;
   }
 
-  @Nullable
-  private static String getRefName(@NotNull String longRefPath, @NotNull String startPatch) {
+  @NotNull
+  private static String getRefName(@NotNull String longRefPath) {
     String tagPrefix = "tag: ";
     if (longRefPath.startsWith(tagPrefix)) {
       longRefPath = longRefPath.substring(tagPrefix.length());
     }
-    if (longRefPath.startsWith(startPatch)) {
-      return longRefPath.substring(startPatch.length());
-    }
-    else {
-      return null;
-    }
+    return longRefPath;
   }
 
   // example input: fb29c80 refs/tags/92.29
   @Nullable
   private VcsRef createRef(@NotNull Hash hash, @NotNull String longRefPath, @NotNull VirtualFile root) {
-    String name = getRefName(longRefPath, "refs/tags/");
-    if (name != null) {
-      return myFactory.createRef(hash, name, GitRefManager.TAG, root);
-    }
-
-    return null;
+    String name = getRefName(longRefPath);
+    VcsRefType type = GitRefManager.getRefType(name);
+    assert type != null;
+    return myFactory.createRef(hash, GitBranchUtil.stripRefsPrefix(name), type, root);
   }
+
 }
index de565484e2d5fa1610ff769754cf30fca8687b07..2d3a7bce191b519fa0c559c2cfbfbdd8761f2cfb 100644 (file)
@@ -54,9 +54,9 @@ public class HgHistoryUtil {
   }
 
   @NotNull
-  public static List<? extends VcsCommitMetadata> loadMetadata(@NotNull final Project project,
-                                                               @NotNull final VirtualFile root, int limit,
-                                                               @NotNull List<String> parameters) throws VcsException {
+  public static List<VcsCommitMetadata> loadMetadata(@NotNull final Project project,
+                                                     @NotNull final VirtualFile root, int limit,
+                                                     @NotNull List<String> parameters) throws VcsException {
 
     final VcsLogObjectsFactory factory = getObjectsFactoryWithDisposeCheck(project);
     if (factory == null) {
index 3b989e448b6131f6e467c8eb95cb8b09b9852ee3..109e83899e5b3891ceb4c272336ea8517cb7b620 100644 (file)
@@ -22,9 +22,11 @@ import com.intellij.openapi.util.Couple;
 import com.intellij.openapi.vcs.VcsException;
 import com.intellij.openapi.vcs.VcsKey;
 import com.intellij.openapi.vfs.VirtualFile;
+import com.intellij.util.CollectConsumer;
 import com.intellij.util.Consumer;
 import com.intellij.util.containers.ContainerUtil;
 import com.intellij.vcs.log.*;
+import com.intellij.vcs.log.impl.LogDataImpl;
 import org.jetbrains.annotations.NotNull;
 import org.jetbrains.annotations.Nullable;
 import org.zmlx.hg4idea.HgNameWithHashInfo;
@@ -59,18 +61,23 @@ public class HgLogProvider implements VcsLogProvider {
 
   @NotNull
   @Override
-  public List<? extends VcsCommitMetadata> readFirstBlock(@NotNull VirtualFile root,
-                                                          @NotNull Requirements requirements) throws VcsException {
-    return HgHistoryUtil.loadMetadata(myProject, root, requirements.getCommitCount(), Collections.<String>emptyList());
+  public DetailedLogData readFirstBlock(@NotNull VirtualFile root,
+                                                   @NotNull Requirements requirements) throws VcsException {
+    List<VcsCommitMetadata> commits = HgHistoryUtil.loadMetadata(myProject, root, requirements.getCommitCount(),
+                                                                           Collections.<String>emptyList());
+    return new LogDataImpl(readAllRefs(root), commits);
   }
 
   @Override
-  public void readAllHashes(@NotNull VirtualFile root, @NotNull Consumer<VcsUser> userRegistry,
-                            @NotNull Consumer<TimedVcsCommit> commitConsumer) throws VcsException {
-    List<TimedVcsCommit> commits = HgHistoryUtil.readAllHashes(myProject, root, userRegistry, Collections.<String>emptyList());
+  @NotNull
+  public LogData readAllHashes(@NotNull VirtualFile root, @NotNull final Consumer<TimedVcsCommit> commitConsumer) throws VcsException {
+    Set<VcsUser> userRegistry = ContainerUtil.newHashSet();
+    List<TimedVcsCommit> commits = HgHistoryUtil.readAllHashes(myProject, root, new CollectConsumer<VcsUser>(userRegistry),
+                                                               Collections.<String>emptyList());
     for (TimedVcsCommit commit : commits) {
       commitConsumer.consume(commit);
     }
+    return new LogDataImpl(readAllRefs(root), userRegistry);
   }
 
   @NotNull
@@ -87,16 +94,15 @@ public class HgLogProvider implements VcsLogProvider {
   }
 
   @NotNull
-  @Override
-  public Collection<VcsRef> readAllRefs(@NotNull VirtualFile root) throws VcsException {
+  private Set<VcsRef> readAllRefs(@NotNull VirtualFile root) throws VcsException {
     myRepositoryManager.waitUntilInitialized();
     if (myProject.isDisposed()) {
-      return Collections.emptyList();
+      return Collections.emptySet();
     }
     HgRepository repository = myRepositoryManager.getRepositoryForRoot(root);
     if (repository == null) {
       LOG.error("Repository not found for root " + root);
-      return Collections.emptyList();
+      return Collections.emptySet();
     }
 
     repository.update();
@@ -106,7 +112,7 @@ public class HgLogProvider implements VcsLogProvider {
     Collection<HgNameWithHashInfo> tags = repository.getTags();
     Collection<HgNameWithHashInfo> localTags = repository.getLocalTags();
 
-    Collection<VcsRef> refs = new ArrayList<VcsRef>(branches.size() + bookmarks.size());
+    Set<VcsRef> refs = new HashSet<VcsRef>(branches.size() + bookmarks.size());
 
     for (Map.Entry<String, Set<Hash>> entry : branches.entrySet()) {
       String branchName = entry.getKey();