diff --git a/platform/vcs-log/api/src/com/intellij/vcs/log/VcsLogProvider.java b/platform/vcs-log/api/src/com/intellij/vcs/log/VcsLogProvider.java index 89805947869c..d9c72314edbc 100644 --- a/platform/vcs-log/api/src/com/intellij/vcs/log/VcsLogProvider.java +++ b/platform/vcs-log/api/src/com/intellij/vcs/log/VcsLogProvider.java @@ -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.
- * Commits should be at least topologically ordered, better considering commit time as well.
- * 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.
+ * Commits should be at least topologically ordered, better considering commit time as well: they will be shown in the log in this order. + *

+ * 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 all references in the repository. */ @NotNull - List readFirstBlock(@NotNull VirtualFile root, @NotNull Requirements requirements) throws VcsException; + DetailedLogData readFirstBlock(@NotNull VirtualFile root, @NotNull Requirements requirements) throws VcsException; /** - *

Reads the whole history, but only hashes & parents.

- *

Also reports authors/committers of this repository to the given user registry.

+ * Reads the whole history. + *

+ * 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 userRegistry, - @NotNull Consumer commitConsumer) throws VcsException; + @NotNull + LogData readAllHashes(@NotNull VirtualFile root, @NotNull Consumer 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 readFullDetails(@NotNull VirtualFile root, @NotNull List hashes) throws VcsException; - /** - * Read all references (branches, tags, etc.) for the given roots. - */ - @NotNull - Collection readAllRefs(@NotNull VirtualFile root) throws VcsException; - /** *

Returns the VCS which is supported by this provider.

*

If there will be several VcsLogProviders which support the same VCS, only one will be chosen. It is undefined, which one.

@@ -106,4 +107,20 @@ public interface VcsLogProvider { } + /** + * Container for references and users. + */ + interface LogData { + @NotNull Set getRefs(); + @NotNull Set getUsers(); + } + + /** + * Container for the ordered list of commits together with their details, and references. + */ + interface DetailedLogData { + @NotNull List getCommits(); + @NotNull Set getRefs(); + } + } diff --git a/platform/vcs-log/api/src/com/intellij/vcs/log/VcsLogProviderRequirementsEx.java b/platform/vcs-log/api/src/com/intellij/vcs/log/VcsLogProviderRequirementsEx.java index d69579ac00b1..68623a6852f9 100644 --- a/platform/vcs-log/api/src/com/intellij/vcs/log/VcsLogProviderRequirementsEx.java +++ b/platform/vcs-log/api/src/com/intellij/vcs/log/VcsLogProviderRequirementsEx.java @@ -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.
- * 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 getPreviousRefs(); - /** - * Returns current refs. - */ - @NotNull - Set getCurrentRefs(); - } diff --git a/platform/vcs-log/impl/src/com/intellij/vcs/log/data/DataPack.java b/platform/vcs-log/impl/src/com/intellij/vcs/log/data/DataPack.java index 48b25be01ab9..975808611ed4 100644 --- a/platform/vcs-log/impl/src/com/intellij/vcs/log/data/DataPack.java +++ b/platform/vcs-log/impl/src/com/intellij/vcs/log/data/DataPack.java @@ -26,16 +26,14 @@ public class DataPack implements VcsLogDataPack { private boolean myFull; @NotNull - static DataPack build(@NotNull List> commits, @NotNull RefsModel refsModel, - @NotNull NotNullFunction indexGetter, @NotNull NotNullFunction hashGetter, - @NotNull Map providers, boolean full) { - return build(buildPermanentGraph(commits, refsModel, indexGetter, hashGetter, providers), providers, refsModel, full); - } - - @NotNull - static DataPack build(@NotNull PermanentGraph permanentGraph, @NotNull Map providers, - @NotNull RefsModel refsModel, boolean full) { - return new DataPack(refsModel, permanentGraph, createGraphFacade(permanentGraph), providers, full); + static DataPack build(@NotNull List> commits, + @NotNull Map> refs, + @NotNull Map providers, + @NotNull VcsLogHashMap hashMap, + boolean full) { + RefsModel refsModel = new RefsModel(refs, hashMap.asIndexGetter()); + PermanentGraph 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 buildPermanentGraph(@NotNull List> commits, - @NotNull RefsModel refsModel, - @NotNull NotNullFunction indexGetter, - @NotNull NotNullFunction hashGetter, - @NotNull Map providers) { + private static PermanentGraph buildPermanentGraph(@NotNull List> commits, + @NotNull RefsModel refsModel, + @NotNull NotNullFunction indexGetter, + @NotNull NotNullFunction hashGetter, + @NotNull Map providers) { if (commits.isEmpty()) { return EmptyPermanentGraph.getInstance(); } diff --git a/platform/vcs-log/impl/src/com/intellij/vcs/log/data/EmptyDataPack.java b/platform/vcs-log/impl/src/com/intellij/vcs/log/data/EmptyDataPack.java index 6262d767d071..54140afb3940 100644 --- a/platform/vcs-log/impl/src/com/intellij/vcs/log/data/EmptyDataPack.java +++ b/platform/vcs-log/impl/src/com/intellij/vcs/log/data/EmptyDataPack.java @@ -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.>emptyMap(), new ConstantFunction(0)); + RefsModel emptyModel = new RefsModel(Collections.>emptyMap(), new ConstantFunction(0)); return new DataPack(emptyModel, EmptyPermanentGraph.getInstance(), new EmptyGraphFacade(), Collections.emptyMap(), false); } diff --git a/platform/vcs-log/impl/src/com/intellij/vcs/log/data/RefsModel.java b/platform/vcs-log/impl/src/com/intellij/vcs/log/data/RefsModel.java index 46504b338970..c38e4c315ff0 100644 --- a/platform/vcs-log/impl/src/com/intellij/vcs/log/data/RefsModel.java +++ b/platform/vcs-log/impl/src/com/intellij/vcs/log/data/RefsModel.java @@ -16,14 +16,14 @@ import java.util.*; public class RefsModel implements VcsLogRefs { - @NotNull private final Map> myRefs; + @NotNull private final Map> myRefs; @NotNull private final NotNullFunction myIndexGetter; @NotNull private final Collection myBranches; @NotNull private final MultiMap myRefsToHashes; @NotNull private final TIntObjectHashMap> myRefsToIndices; - public RefsModel(@NotNull Map> refsByRoot, @NotNull NotNullFunction indexGetter) { + public RefsModel(@NotNull Map> refsByRoot, @NotNull NotNullFunction indexGetter) { myRefs = refsByRoot; myIndexGetter = indexGetter; @@ -85,7 +85,7 @@ public class RefsModel implements VcsLogRefs { } @NotNull - public Map> getAllRefsByRoot() { + public Map> getAllRefsByRoot() { return myRefs; } diff --git a/platform/vcs-log/impl/src/com/intellij/vcs/log/data/VcsLogRefresherImpl.java b/platform/vcs-log/impl/src/com/intellij/vcs/log/data/VcsLogRefresherImpl.java index c7ab5e324b3d..09de51c722bf 100644 --- a/platform/vcs-log/impl/src/com/intellij/vcs/log/data/VcsLogRefresherImpl.java +++ b/platform/vcs-log/impl/src/com/intellij/vcs/log/data/VcsLogRefresherImpl.java @@ -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 topCommitsDetailsCache, @NotNull final Consumer dataPackUpdateHandler, - @NotNull Consumer exceptionHandler, int recentCommitsCount) { + @NotNull Consumer exceptionHandler, + int recentCommitsCount) { myProject = project; myHashMap = hashMap; myProviders = providers; @@ -95,16 +97,11 @@ public class VcsLogRefresherImpl implements VcsLogRefresher { @Override public DataPack readFirstBlock() { try { - Map> refs = loadRefsFromVcs(myProviders); - Set roots = myProviders.keySet(); - Map requirements = prepareSimpleRequirements(roots, myRecentCommitCount); - Map>> commits = loadRecentCommitsFromVcs(myProviders, requirements, - myUserRegistry, myTopCommitsDetailsCache, - myHashMap); - List> 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>> commits = data.getCommits(); + Map> 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,6 +110,36 @@ public class VcsLogRefresherImpl implements VcsLogRefresher { } } + @NotNull + private LogInfo loadRecentData(@NotNull final Map requirements) throws VcsException { + final StopWatch sw = StopWatch.start("loading commits"); + final LogInfo logInfo = new LogInfo(); + new ProviderIterator() { + @Override + 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(getProvidersForRoots(requirements.keySet())); + myUserRegistry.flush(); + sw.report(); + return logInfo; + } + + @NotNull + private Map getProvidersForRoots(@NotNull Set roots) { + return ContainerUtil.map2Map(roots, + new Function>() { + @Override + public Pair fun(VirtualFile root) { + return Pair.create(root, myProviders.get(root)); + } + }); + } + @Override public void refresh(@NotNull Collection rootsToRefresh) { if (!rootsToRefresh.isEmpty()) { @@ -121,101 +148,39 @@ public class VcsLogRefresherImpl implements VcsLogRefresher { } @NotNull - private static Map prepareSimpleRequirements(@NotNull Collection roots, - final int commitCount) { - final VcsLogProvider.Requirements requirements = new VcsLogProvider.Requirements() { - @Override - public int getCommitCount() { - return commitCount; - } - }; - return ContainerUtil.map2Map(roots, new Function>() { - @Override - public Pair fun(VirtualFile file) { - return Pair.create(file, requirements); - } - }); - } - - @NotNull - private static Map> loadRefsFromVcs(@NotNull Map providers) - throws VcsException { - final StopWatch sw = StopWatch.start("loading refs"); - final Map> refs = ContainerUtil.newHashMap(); - new ProviderIterator() { - @Override - void each(@NotNull VirtualFile root, @NotNull VcsLogProvider provider) throws VcsException { - refs.put(root, provider.readAllRefs(root)); - sw.rootCompleted(root); - } - }.iterate(providers); - sw.report(); - return refs; - } - - @NotNull - private static Map>> loadRecentCommitsFromVcs( - @NotNull Map providers, - @NotNull final Map requirements, - @NotNull final VcsUserRegistryImpl userRegistry, - @NotNull final Map topCommitsDetailsCache, - @NotNull final VcsLogHashMap hashMap) throws VcsException - { - final StopWatch sw = StopWatch.start("loading commits"); - final Map>> commits = ContainerUtil.newHashMap(); - new ProviderIterator() { - @Override - public void each(@NotNull VirtualFile root, @NotNull VcsLogProvider provider) throws VcsException { - List 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; - } - - /** - * Compounds logs from different repositories into a single multi-repository log. - */ - @NotNull - private static List> compound(@NotNull Collection>> commits) { + private static > List multiRepoJoin(@NotNull Collection> commits) { StopWatch sw = StopWatch.start("multi-repo join"); - List> joined = new VcsLogMultiRepoJoiner().join(commits); + List joined = new VcsLogMultiRepoJoiner().join(commits); sw.report(); return joined; } @NotNull - private static List> compactCommits(@NotNull List commits, - @NotNull final VcsLogHashMap hashMap) { + private List> compactCommits(@NotNull List commits) { StopWatch sw = StopWatch.start("compacting commits"); List> map = ContainerUtil.map(commits, new Function>() { @NotNull @Override public GraphCommit fun(@NotNull TimedVcsCommit commit) { - return compactCommit(commit, hashMap); + return compactCommit(commit); } }); - hashMap.flush(); + myHashMap.flush(); sw.report(); return map; } @NotNull - private static GraphCommitImpl compactCommit(@NotNull TimedVcsCommit commit, @NotNull VcsLogHashMap hashMap) { - return new GraphCommitImpl(hashMap.getCommitIndex(commit.getId()), - ContainerUtil.map(commit.getParents(), hashMap.asIndexGetter()), commit.getTimestamp()); + private GraphCommitImpl compactCommit(@NotNull TimedVcsCommit commit) { + return new GraphCommitImpl(myHashMap.getCommitIndex(commit.getId()), + ContainerUtil.map(commit.getParents(), myHashMap.asIndexGetter()), commit.getTimestamp()); } - private static void storeUsersAndDetails(@NotNull List metadatas, @NotNull VcsUserRegistryImpl userRegistry, - @NotNull Map topCommitsDetailsCache) { + private void storeUsersAndDetails(@NotNull Collection 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 myLoadedInfos = ContainerUtil.newHashMap(); - - private class LogAndRefs { - List> log; - Collection refs; - LogAndRefs(Collection refs, List> 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 roots) { StopWatch sw = StopWatch.start("refresh"); PermanentGraph permanentGraph = myCurrentDataPack.isFull() ? myCurrentDataPack.getPermanentGraph() : null; - Map> currentRefs = myCurrentDataPack.getRefsModel().getAllRefsByRoot(); + Map> currentRefs = myCurrentDataPack.getRefsModel().getAllRefsByRoot(); try { if (permanentGraph != null) { int commitCount = myRecentCommitCount; for (int attempt = 0; attempt <= 1; attempt++) { loadLogAndRefs(roots, currentRefs, commitCount); - List> compoundLog = compoundLoadedLogs(myLoadedInfos.values()); - Map> allNewRefs = getAllNewRefs(myLoadedInfos, currentRefs); + List> compoundLog = multiRepoJoin(myLoadedInfo.getCommits()); + Map> allNewRefs = getAllNewRefs(myLoadedInfo, currentRefs); List> 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, Map>> 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 infos) { - StringBuilder sb = new StringBuilder(); - for (Map.Entry entry : infos.entrySet()) { - sb.append(entry.getKey().getName()); - sb.append(" LOG:\n"); - sb.append(StringUtil.join(entry.getValue().log, new Function, String>() { - @Override - public String fun(GraphCommit commit) { - return commit.getId() + "<-" + StringUtil.join(commit.getParents(), ","); - } - }, "\n")); - sb.append("\nREFS:\n"); - sb.append(StringUtil.join(entry.getValue().refs, new Function() { - @Override - public String fun(VcsRef ref) { - return ref.getName() + "(" + myHashMap.getCommitIndex(ref.getCommitHash()) + ")"; - } - }, ",")); - } - return sb.toString(); - } - @NotNull - private List> compoundLoadedLogs(@NotNull Collection logsAndRefs) { - return compound(ContainerUtil.map(logsAndRefs, new Function>>() { - @Override - public List> fun(LogAndRefs refs) { - return refs.log; - } - })); - } - - @NotNull - private Map> getAllNewRefs(@NotNull Map newInfo, - @NotNull Map> previousRefs) { - Map> result = ContainerUtil.newHashMap(); + private Map> getAllNewRefs(@NotNull LogInfo newInfo, + @NotNull Map> previousRefs) { + Map> result = ContainerUtil.newHashMap(); for (VirtualFile root : previousRefs.keySet()) { - result.put(root, newInfo.containsKey(root) ? newInfo.get(root).refs : previousRefs.get(root)); + Set newInfoRefs = newInfo.getRefs(root); + result.put(root, newInfoRefs != null ? newInfoRefs : previousRefs.get(root)); } return result; } - private void loadLogAndRefs(@NotNull Collection roots, @NotNull Map> prevRefs, + private void loadLogAndRefs(@NotNull Collection roots, + @NotNull Map> prevRefs, int commitCount) throws VcsException { - Map providers = getProviders(roots); - Map> refs = loadRefsFromVcs(providers); - Map requirements = prepareRequirements(roots, commitCount, prevRefs, refs); - Map>> 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 prepareRequirements(@NotNull Collection roots, int commitCount, - @NotNull Map> prevRefs, - @NotNull Map> newRefs) { + @NotNull Map> prevRefs) { Map 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 getRefsForRoot(@NotNull Map> map, @NotNull VirtualFile root) { - Collection refs = map.get(root); - return refs == null ? Collections.emptySet() : new HashSet(refs); - } - - @NotNull - private Map getProviders(@NotNull Collection roots) { - Map providers = ContainerUtil.newHashMap(); - for (VirtualFile root : roots) { - providers.put(root, myProviders.get(root)); - } - return providers; - } - @Nullable - private List> join(@NotNull List> recentCommits, @NotNull List> fullLog, - @NotNull Map> previousRefs, - @NotNull Map> newRefs) { + private List> join(@NotNull List> recentCommits, + @NotNull List> fullLog, + @NotNull Map> previousRefs, + @NotNull Map> newRefs) { StopWatch sw = StopWatch.start("joining new commits"); Function ref2Int = new Function() { - @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, Map>> loadFullLog() throws VcsException { + private DataPack loadFullLog() throws VcsException { StopWatch sw = StopWatch.start("full log reload"); - Collection>> commits = readFullLogFromVcs(); - List> graphCommits = compound(commits); - Map> refMap = loadRefsFromVcs(myProviders); - PermanentGraph permanentGraph = DataPack.buildPermanentGraph(graphCommits, new RefsModel(refMap, myHashMap.asIndexGetter()), - myHashMap.asIndexGetter(), - myHashMap.asHashGetter(), myProviders); + LogInfo logInfo = readFullLogFromVcs(); + List> 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>> readFullLogFromVcs() throws VcsException { + private LogInfo readFullLogFromVcs() throws VcsException { final StopWatch sw = StopWatch.start("read full log from VCS"); - final Collection>> logs = ContainerUtil.newArrayList(); + final LogInfo logInfo = new LogInfo(); new ProviderIterator() { @Override void each(@NotNull VirtualFile root, @NotNull VcsLogProvider provider) throws VcsException { final List> graphCommits = ContainerUtil.newArrayList(); - provider.readAllHashes(root, new Consumer() { + VcsLogProvider.LogData data = provider.readAllHashes(root, new Consumer() { @Override - public void consume(@NotNull VcsUser user) { - myUserRegistry.addUser(user); - } - }, new Consumer() { - @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.emptyList()); - @NotNull private final Collection rootsToRefresh; + private static final RefreshRequest RELOAD_ALL = new RefreshRequest(Collections.emptyList()); + private final Collection rootsToRefresh; RefreshRequest(@NotNull Collection 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 asMap(@NotNull Collection roots) { + return ContainerUtil.map2Map(roots, new Function>() { + @Override + public Pair fun(VirtualFile root) { + return Pair.create(root, CommitCountRequirements.this); + } + }); + } + } + + private static class LogInfo { + private final Map> myRefs = ContainerUtil.newHashMap(); + private final Map>> myCommits = ContainerUtil.newHashMap(); + + void put(@NotNull VirtualFile root, @NotNull List> commits) { + myCommits.put(root, commits); + } + + void put(@NotNull VirtualFile root, @NotNull Set refs) { + myRefs.put(root, refs); + } + + @NotNull + Collection>> getCommits() { + return myCommits.values(); + } + + List> getCommits(@NotNull VirtualFile root) { + return myCommits.get(root); + } + + @NotNull + Map> getRefs() { + return myRefs; + } + + public Set getRefs(@NotNull VirtualFile root) { + return myRefs.get(root); + } + + @SuppressWarnings("StringConcatenationInsideStringBufferAppend") + @NotNull + public String toLogString(@NotNull final NotNullFunction indexGetter) { + StringBuilder sb = new StringBuilder(); + sb.append(" LOG:\n"); + for (Map.Entry>> entry : myCommits.entrySet()) { + sb.append(entry.getKey().getName() + "\n"); + sb.append(StringUtil.join(entry.getValue(), new Function, String>() { + @Override + public String fun(@NotNull GraphCommit commit) { + return commit.getId() + "<-" + StringUtil.join(commit.getParents(), ","); + } + }, "\n")); + } + sb.append("\nREFS:\n"); + for (Map.Entry> entry : myRefs.entrySet()) { + sb.append(entry.getKey().getName() + "\n"); + sb.append(StringUtil.join(entry.getValue(), new Function() { + @Override + public String fun(@NotNull VcsRef ref) { + return ref.getName() + "(" + indexGetter.fun(ref.getCommitHash()) + ")"; + } + }, ",")); + } + return sb.toString(); + } + } } diff --git a/platform/vcs-log/impl/src/com/intellij/vcs/log/data/VcsUserRegistryImpl.java b/platform/vcs-log/impl/src/com/intellij/vcs/log/data/VcsUserRegistryImpl.java index a5447d20f115..8621f379ce65 100644 --- a/platform/vcs-log/impl/src/com/intellij/vcs/log/data/VcsUserRegistryImpl.java +++ b/platform/vcs-log/impl/src/com/intellij/vcs/log/data/VcsUserRegistryImpl.java @@ -86,6 +86,12 @@ public class VcsUserRegistryImpl implements Disposable, VcsUserRegistry { } } + public void addUsers(@NotNull Collection users) { + for (VcsUser user : users) { + addUser(user); + } + } + @Override @NotNull public Set 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 index 000000000000..82eb4e1a6336 --- /dev/null +++ b/platform/vcs-log/impl/src/com/intellij/vcs/log/impl/LogDataImpl.java @@ -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.emptySet(), + Collections.emptySet(), + Collections.emptyList()); + + @NotNull private final List myCommits; + @NotNull private final Set myRefs; + @NotNull private final Set myUsers; + + @NotNull + public static LogDataImpl empty() { + return EMPTY; + } + + public LogDataImpl(@NotNull Set refs, @NotNull Set users) { + this(refs, users, Collections.emptyList()); + } + + public LogDataImpl(@NotNull Set refs, @NotNull List metadatas) { + this(refs, Collections.emptySet(), metadatas); + } + + private LogDataImpl(@NotNull Set refs, @NotNull Set users, @NotNull List commits) { + myRefs = refs; + myUsers = users; + myCommits = commits; + } + + @NotNull + @Override + public List getCommits() { + return myCommits; + } + + @Override + @NotNull + public Set getRefs() { + return myRefs; + } + + @NotNull + @Override + public Set getUsers() { + return myUsers; + } +} diff --git a/platform/vcs-log/impl/src/com/intellij/vcs/log/impl/RequirementsImpl.java b/platform/vcs-log/impl/src/com/intellij/vcs/log/impl/RequirementsImpl.java index f742e277283f..9a1e54ed3886 100644 --- a/platform/vcs-log/impl/src/com/intellij/vcs/log/impl/RequirementsImpl.java +++ b/platform/vcs-log/impl/src/com/intellij/vcs/log/impl/RequirementsImpl.java @@ -26,13 +26,11 @@ public class RequirementsImpl implements VcsLogProviderRequirementsEx { private final int myCommitCount; private final boolean myRefresh; @NotNull private final Set myPreviousRefs; - @NotNull private final Set myCurrentRefs; - public RequirementsImpl(int count, boolean refresh, @NotNull Set previousRefs, @NotNull Set currentRefs) { + public RequirementsImpl(int count, boolean refresh, @NotNull Set previousRefs) { myCommitCount = count; myRefresh = refresh; myPreviousRefs = previousRefs; - myCurrentRefs = currentRefs; } @Override @@ -50,10 +48,4 @@ public class RequirementsImpl implements VcsLogProviderRequirementsEx { public Set getPreviousRefs() { return myPreviousRefs; } - - @NotNull - @Override - public Set getCurrentRefs() { - return myCurrentRefs; - } } diff --git a/platform/vcs-log/impl/test/com/intellij/vcs/log/impl/TestVcsLogProvider.java b/platform/vcs-log/impl/test/com/intellij/vcs/log/impl/TestVcsLogProvider.java index 850478b048dc..05659538da20 100644 --- a/platform/vcs-log/impl/test/com/intellij/vcs/log/impl/TestVcsLogProvider.java +++ b/platform/vcs-log/impl/test/com/intellij/vcs/log/impl/TestVcsLogProvider.java @@ -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 myCommitToMetadataConvertor = new Function(){ - @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 myCommitToMetadataConvertor = + new Function() { + @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 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 metadatas = ContainerUtil.map(myCommits.subList(0, requirements.getCommitCount()), + myCommitToMetadataConvertor); + return new LogDataImpl(Collections.emptySet(), metadatas); } + @NotNull @Override - public void readAllHashes(@NotNull VirtualFile root, @NotNull Consumer userRegistry, - @NotNull Consumer commitConsumer) throws VcsException { + public LogData readAllHashes(@NotNull VirtualFile root, @NotNull Consumer 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.emptySet()); } private void assertRoot(@NotNull VirtualFile root) { @@ -125,12 +128,6 @@ public class TestVcsLogProvider implements VcsLogProvider { throw new UnsupportedOperationException(); } - @NotNull - @Override - public Collection readAllRefs(@NotNull VirtualFile root) throws VcsException { - return myRefs; - } - @NotNull @Override public VcsKey getSupportedVcs() { diff --git a/plugins/git4idea/src/git4idea/branch/GitBranchUtil.java b/plugins/git4idea/src/git4idea/branch/GitBranchUtil.java index 6e178b9cbc0f..2504c345b559 100644 --- a/plugins/git4idea/src/git4idea/branch/GitBranchUtil.java +++ b/plugins/git4idea/src/git4idea/branch/GitBranchUtil.java @@ -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; } diff --git a/plugins/git4idea/src/git4idea/history/GitHistoryUtils.java b/plugins/git4idea/src/git4idea/history/GitHistoryUtils.java index e44241289c06..a0030c8158a5 100644 --- a/plugins/git4idea/src/git4idea/history/GitHistoryUtils.java +++ b/plugins/git4idea/src/git4idea/history/GitHistoryUtils.java @@ -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 readCommits(@NotNull final Project project, - @NotNull VirtualFile root, - @NotNull final Consumer userRegistry, - @NotNull List parameters) throws VcsException { - List collector = ContainerUtil.newArrayList(); - readCommits(project, root, userRegistry, parameters, new CollectConsumer(collector)); - return collector; - } - public static void readCommits(@NotNull final Project project, - @NotNull VirtualFile root, - @NotNull final Consumer userRegistry, - @NotNull List parameters, - @NotNull final Consumer commitConsumer) throws VcsException { + @NotNull final VirtualFile root, + @NotNull List parameters, + @NotNull final Consumer userConsumer, + @NotNull final Consumer refConsumer, + @NotNull final Consumer 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 commits = parseCommit(parser, record, userRegistry, factory); + List 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 commits = parseCommit(parser, record, userRegistry, factory); + List 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 parseCommit(@NotNull GitLogParser parser, @NotNull StringBuilder record, + private static List parseCommit(@NotNull GitLogParser parser, + @NotNull StringBuilder record, @NotNull final Consumer userRegistry, - @NotNull final VcsLogObjectsFactory factory) { + @NotNull final Consumer refConsumer, + @NotNull final VcsLogObjectsFactory factory, + @NotNull final VirtualFile root) { List rec = parser.parse(record.toString()); return ContainerUtil.mapNotNull(rec, new Function() { @Override @@ -651,7 +648,11 @@ public class GitHistoryUtils { if (record == null) { return null; } - TimedVcsCommit commit = convert(record, factory); + Pair> 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> convert(@NotNull GitLogRecord rec, + @NotNull VcsLogObjectsFactory factory, + @NotNull VirtualFile root) { + Hash hash = HashImpl.build(rec.getHash()); List 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 parseRefs(@NotNull Collection refs, + @NotNull final Hash hash, + @NotNull final VcsLogObjectsFactory factory, + @NotNull final VirtualFile root) { + return ContainerUtil.mapNotNull(refs, new Function() { + @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 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() { - @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 refs = ContainerUtil.newHashSet(); + final List commits = + loadDetails(project, root, withRefs, false, new NullableFunction() { + @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() { + return loadDetails(project, root, true, true, new NullableFunction() { @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 List loadDetails(@NotNull final Project project, @NotNull final VirtualFile root, boolean withChanges, - @NotNull NullableFunction converter, String... parameters) + public static List loadDetails(@NotNull final Project project, + @NotNull final VirtualFile root, + boolean withRefs, + boolean withChanges, + @NotNull NullableFunction 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 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(), diff --git a/plugins/git4idea/src/git4idea/log/GitLogProvider.java b/plugins/git4idea/src/git4idea/log/GitLogProvider.java index b5096a94f03b..3cc80e920942 100644 --- a/plugins/git4idea/src/git4idea/log/GitLogProvider.java +++ b/plugins/git4idea/src/git4idea/log/GitLogProvider.java @@ -15,34 +15,26 @@ */ 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 GET_TAG_NAME = new Function() { + @Override + public String fun(VcsRef ref) { + return ref.getType() == GitRefManager.TAG ? ref.getName() : null; + } + }; + private static final TObjectHashingStrategy REF_ONLY_NAME_STRATEGY = new TObjectHashingStrategy() { + @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 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 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 safeRefs = data.getRefs(); + Set allRefs = new OpenTHashSet(safeRefs, REF_ONLY_NAME_STRATEGY); + addNewElements(allRefs, readBranches(repository)); + + Collection 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 newTags = getNewTags(rex.getCurrentRefs(), rex.getPreviousRefs()); - if (!newTags.isEmpty()) { - final Set firstBlockHashes = ContainerUtil.map2Set(firstBlock, new Function() { - @Override - public Hash fun(VcsCommitMetadata metadata) { - return metadata.getId(); - } - }); - List unmatchedHeads = getUnmatchedHeads(firstBlockHashes, newTags); - if (!unmatchedHeads.isEmpty()) { - List detailsFromTaggedBranches = loadSomeCommitsOnTaggedBranches(root, commitCount, unmatchedHeads); - Collection unmatchedCommits = getUnmatchedCommits(firstBlockHashes, detailsFromTaggedBranches); - firstBlock.addAll(unmatchedCommits); - } - } + VcsLogProviderRequirementsEx rex = (VcsLogProviderRequirementsEx)requirements; + + Set currentTags = readCurrentTagNames(root); + addOldStillExistingTags(allRefs, currentTags, rex.getPreviousRefs()); + + allDetails = ContainerUtil.newHashSet(data.getCommits()); + + Set previousTags = new HashSet(ContainerUtil.mapNotNull(rex.getPreviousRefs(), GET_TAG_NAME)); + Set safeTags = new HashSet(ContainerUtil.mapNotNull(safeRefs, GET_TAG_NAME)); + Set 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(firstBlock.subList(0, Math.min(firstBlock.size(), requirements.getCommitCount()))); - return firstBlock; + List sortedCommits = VcsLogSorter.sortByDateTopoOrder(allDetails); + sortedCommits = sortedCommits.subList(0, Math.min(sortedCommits.size(), requirements.getCommitCount())); + + return new LogDataImpl(allRefs, sortedCommits); + } + + private static void addOldStillExistingTags(@NotNull Set allRefs, + @NotNull Set currentTags, + @NotNull Set previousRefs) { + for (VcsRef ref : previousRefs) { + if (!allRefs.contains(ref) && currentTags.contains(ref.getName())) { + allRefs.add(ref); + } + } } @NotNull - private static Collection getNewTags(@NotNull Set currentRefs, @NotNull final Set previousRefs) { - return ContainerUtil.filter(currentRefs, new Condition() { - @Override - public boolean value(VcsRef ref) { - return !ref.getType().isBranch() && !previousRefs.contains(ref); - } - }); + private Set readCurrentTagNames(@NotNull VirtualFile root) throws VcsException { + Set tags = ContainerUtil.newHashSet(); + GitTag.listAsStrings(myProject, root, tags, null); + return tags; } @NotNull - private List loadSomeCommitsOnTaggedBranches(@NotNull VirtualFile root, int commitCount, - @NotNull List unmatchedHeads) throws VcsException { - List params = new ArrayList(ContainerUtil.map(unmatchedHeads, new Function() { - @Override - public String fun(VcsRef ref) { - return ref.getCommitHash().asString(); + private static Set remove(@NotNull Set original, @NotNull Set... toRemove) { + Set result = ContainerUtil.newHashSet(original); + for (Set set : toRemove) { + result.removeAll(set); + } + return result; + } + + private static void addNewElements(@NotNull Collection original, @NotNull Collection toAdd) { + for (T item : toAdd) { + if (!original.contains(item)) { + original.add(item); } - })); + } + } + + @NotNull + private DetailedLogData loadSomeCommitsOnTaggedBranches(@NotNull VirtualFile root, int commitCount, + @NotNull Collection unmatchedTags) throws VcsException { + List params = new ArrayList(); params.add("--max-count=" + commitCount); - return GitHistoryUtils.loadMetadata(myProject, root, ArrayUtil.toStringArray(params)); - } - - @NotNull - private static List getUnmatchedHeads(@NotNull final Set firstBlockHashes, @NotNull Collection refs) { - return ContainerUtil.filter(refs, new Condition() { - @Override - public boolean value(VcsRef ref) { - return !firstBlockHashes.contains(ref.getCommitHash()); - } - }); - } - - @NotNull - private static Collection getUnmatchedCommits(@NotNull final Set firstBlockHashes, - @NotNull List detailsFromTaggedBranches) { - return ContainerUtil.filter(detailsFromTaggedBranches, new Condition() { - @Override - public boolean value(VcsCommitMetadata metadata) { - return !firstBlockHashes.contains(metadata.getId()); - } - }); + params.addAll(unmatchedTags); + return GitHistoryUtils.loadMetadata(myProject, root, true, ArrayUtil.toStringArray(params)); } @Override - public void readAllHashes(@NotNull VirtualFile root, @NotNull Consumer userRegistry, - @NotNull final Consumer commitConsumer) throws VcsException { + @NotNull + public LogData readAllHashes(@NotNull VirtualFile root, @NotNull final Consumer commitConsumer) throws VcsException { if (!isRepositoryReady(root)) { - return; + return LogDataImpl.empty(); } List parameters = new ArrayList(GitHistoryUtils.LOG_ALL); parameters.add("--sparse"); final GitBekParentFixer parentFixer = GitBekParentFixer.prepare(root, this); - GitHistoryUtils.readCommits(myProject, root, userRegistry, parameters, new Consumer() { + Set userRegistry = ContainerUtil.newHashSet(); + Set refs = ContainerUtil.newHashSet(); + GitHistoryUtils.readCommits(myProject, root, parameters, new CollectConsumer(userRegistry), + new CollectConsumer(refs), new Consumer() { @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 readAllRefs(@NotNull VirtualFile root) throws VcsException { - if (!isRepositoryReady(root)) { - return Collections.emptyList(); - } - - GitRepository repository = getRepository(root); - repository.update(); + private Set readBranches(@NotNull GitRepository repository) { + VirtualFile root = repository.getRoot(); Collection localBranches = repository.getBranches().getLocalBranches(); Collection remoteBranches = repository.getBranches().getRemoteBranches(); - Collection refs = new ArrayList(localBranches.size() + remoteBranches.size()); + Set refs = new HashSet(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 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 refs = new ArrayList(); - 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 commits = ContainerUtil.newArrayList(); + GitHistoryUtils.readCommits(myProject, root, filterParameters, Consumer.EMPTY_CONSUMER, Consumer.EMPTY_CONSUMER, + new CollectConsumer(commits)); + return commits; } @Nullable diff --git a/plugins/git4idea/src/git4idea/log/GitRefManager.java b/plugins/git4idea/src/git4idea/log/GitRefManager.java index 2cedc15b05ca..6469a1fef1bd 100644 --- a/plugins/git4idea/src/git4idea/log/GitRefManager.java +++ b/plugins/git4idea/src/git4idea/log/GitRefManager.java @@ -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 REF_TYPE_PRIORITIES = Arrays.asList(HEAD, LOCAL_BRANCH, REMOTE_BRANCH, TAG); + private static final List REF_TYPE_PRIORITIES = Arrays.asList(HEAD, LOCAL_BRANCH, REMOTE_BRANCH, TAG, OTHER); // -1 => higher priority public static final Comparator REF_TYPE_COMPARATOR = new Comparator() { @@ -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; diff --git a/plugins/git4idea/tests/git4idea/log/GitLogProviderTest.java b/plugins/git4idea/tests/git4idea/log/GitLogProviderTest.java index 0bb67671622b..313852f118b0 100644 --- a/plugins/git4idea/tests/git4idea/log/GitLogProviderTest.java +++ b/plugins/git4idea/tests/git4idea/log/GitLogProviderTest.java @@ -15,13 +15,13 @@ */ 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 expectedLogWithoutTaggedBranch = log(); createTaggedBranch(); - Set noRefs = Collections.emptySet(); - List block = myLogProvider.readFirstBlock(myProjectRoot, new RequirementsImpl(1000, false, noRefs, noRefs)); - assertOrderedEquals(block, expectedLogWithoutTaggedBranch); + VcsLogProvider.DetailedLogData block = myLogProvider.readFirstBlock(myProjectRoot,new RequirementsImpl(1000, false, Collections.emptySet())); + assertOrderedEquals(block.getCommits(), expectedLogWithoutTaggedBranch); } public void test_refresh_with_new_tagged_branch() throws VcsException { prepareSomeHistory(); - Set prevRefs = ContainerUtil.newHashSet(myLogProvider.readAllRefs(myProjectRoot)); + Set prevRefs = readAllRefs(); createTaggedBranch(); - Set newRefs = ContainerUtil.newHashSet(myLogProvider.readAllRefs(myProjectRoot)); List expectedLog = log(); - List 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 prevRefs = readAllRefs(); + git("tag -f ATAG"); + + List expectedLog = log(); + Set 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 prevRefs = readAllRefs(); + List log = log(); + String firstCommit = log.get(log.size() - 1).getId().asString(); + git("tag NEW_TAG " + firstCommit); + + Set refs = readAllRefs(); + VcsLogProvider.DetailedLogData block = myLogProvider.readFirstBlock(myProjectRoot, new RequirementsImpl(1000, true, prevRefs)); + assertSameElements(block.getRefs(), refs); + } + + private Set readAllRefs() { + String[] refs = StringUtil.splitByLines(git("log --branches --tags --no-walk --format=%H%d --decorate=full")); + Set 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 expectedLog = log(); List collector = ContainerUtil.newArrayList(); //noinspection unchecked - myLogProvider.readAllHashes(myProjectRoot, Consumer.EMPTY_CONSUMER, new CollectConsumer(collector)); + myLogProvider.readAllHashes(myProjectRoot, new CollectConsumer(collector)); assertOrderedEquals(expectedLog, collector); } diff --git a/plugins/git4idea/src/git4idea/log/RefParser.java b/plugins/git4idea/tests/git4idea/log/RefParser.java similarity index 72% rename from plugins/git4idea/src/git4idea/log/RefParser.java rename to plugins/git4idea/tests/git4idea/log/RefParser.java index 619cfcd5d4da..9a4cc962c6e7 100644 --- a/plugins/git4idea/src/git4idea/log/RefParser.java +++ b/plugins/git4idea/tests/git4idea/log/RefParser.java @@ -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); } + } diff --git a/plugins/hg4idea/src/org/zmlx/hg4idea/log/HgHistoryUtil.java b/plugins/hg4idea/src/org/zmlx/hg4idea/log/HgHistoryUtil.java index de565484e2d5..2d3a7bce191b 100644 --- a/plugins/hg4idea/src/org/zmlx/hg4idea/log/HgHistoryUtil.java +++ b/plugins/hg4idea/src/org/zmlx/hg4idea/log/HgHistoryUtil.java @@ -54,9 +54,9 @@ public class HgHistoryUtil { } @NotNull - public static List loadMetadata(@NotNull final Project project, - @NotNull final VirtualFile root, int limit, - @NotNull List parameters) throws VcsException { + public static List loadMetadata(@NotNull final Project project, + @NotNull final VirtualFile root, int limit, + @NotNull List parameters) throws VcsException { final VcsLogObjectsFactory factory = getObjectsFactoryWithDisposeCheck(project); if (factory == null) { diff --git a/plugins/hg4idea/src/org/zmlx/hg4idea/log/HgLogProvider.java b/plugins/hg4idea/src/org/zmlx/hg4idea/log/HgLogProvider.java index 3b989e448b61..109e83899e5b 100644 --- a/plugins/hg4idea/src/org/zmlx/hg4idea/log/HgLogProvider.java +++ b/plugins/hg4idea/src/org/zmlx/hg4idea/log/HgLogProvider.java @@ -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 readFirstBlock(@NotNull VirtualFile root, - @NotNull Requirements requirements) throws VcsException { - return HgHistoryUtil.loadMetadata(myProject, root, requirements.getCommitCount(), Collections.emptyList()); + public DetailedLogData readFirstBlock(@NotNull VirtualFile root, + @NotNull Requirements requirements) throws VcsException { + List commits = HgHistoryUtil.loadMetadata(myProject, root, requirements.getCommitCount(), + Collections.emptyList()); + return new LogDataImpl(readAllRefs(root), commits); } @Override - public void readAllHashes(@NotNull VirtualFile root, @NotNull Consumer userRegistry, - @NotNull Consumer commitConsumer) throws VcsException { - List commits = HgHistoryUtil.readAllHashes(myProject, root, userRegistry, Collections.emptyList()); + @NotNull + public LogData readAllHashes(@NotNull VirtualFile root, @NotNull final Consumer commitConsumer) throws VcsException { + Set userRegistry = ContainerUtil.newHashSet(); + List commits = HgHistoryUtil.readAllHashes(myProject, root, new CollectConsumer(userRegistry), + Collections.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 readAllRefs(@NotNull VirtualFile root) throws VcsException { + private Set 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 tags = repository.getTags(); Collection localTags = repository.getLocalTags(); - Collection refs = new ArrayList(branches.size() + bookmarks.size()); + Set refs = new HashSet(branches.size() + bookmarks.size()); for (Map.Entry> entry : branches.entrySet()) { String branchName = entry.getKey();