blob: cdb9f80fe1a93ac78e58c748d30e91da4f2b45ee [file] [log] [blame]
/*
* 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.data;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.progress.ProgressIndicator;
import com.intellij.openapi.progress.Task;
import com.intellij.openapi.progress.impl.ProgressManagerImpl;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.Pair;
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.containers.ContainerUtil;
import com.intellij.util.ui.UIUtil;
import com.intellij.vcs.log.*;
import com.intellij.vcs.log.graph.GraphCommit;
import com.intellij.vcs.log.graph.GraphCommitImpl;
import com.intellij.vcs.log.graph.PermanentGraph;
import com.intellij.vcs.log.impl.RequirementsImpl;
import com.intellij.vcs.log.util.StopWatch;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.*;
public class VcsLogRefresherImpl implements VcsLogRefresher {
private static final Logger LOG = Logger.getInstance(VcsLogRefresherImpl.class);
@NotNull private final Project myProject;
@NotNull private final VcsLogHashMap myHashMap;
@NotNull private final Map<VirtualFile, VcsLogProvider> myProviders;
@NotNull private final VcsUserRegistry myUserRegistry;
@NotNull private final Map<Hash, VcsCommitMetadata> myTopCommitsDetailsCache;
@NotNull private final Consumer<Exception> myExceptionHandler;
private final int myRecentCommitCount;
@NotNull private final SingleTaskController<RefreshRequest, DataPack> mySingleTaskController;
@NotNull private DataPack myDataPack = EmptyDataPack.getInstance();
public VcsLogRefresherImpl(@NotNull final Project project,
@NotNull VcsLogHashMap hashMap,
@NotNull Map<VirtualFile, VcsLogProvider> providers,
@NotNull final VcsUserRegistry userRegistry,
@NotNull Map<Hash, VcsCommitMetadata> topCommitsDetailsCache,
@NotNull final Consumer<DataPack> dataPackUpdateHandler,
@NotNull Consumer<Exception> exceptionHandler, int recentCommitsCount) {
myProject = project;
myHashMap = hashMap;
myProviders = providers;
myUserRegistry = userRegistry;
myTopCommitsDetailsCache = topCommitsDetailsCache;
myExceptionHandler = exceptionHandler;
myRecentCommitCount = recentCommitsCount;
Consumer<DataPack> dataPackUpdater = new Consumer<DataPack>() {
@Override
public void consume(@NotNull DataPack dataPack) {
myDataPack = dataPack;
dataPackUpdateHandler.consume(dataPack);
}
};
mySingleTaskController = new SingleTaskController<RefreshRequest, DataPack>(dataPackUpdater) {
@Override
protected void startNewBackgroundTask() {
UIUtil.invokeLaterIfNeeded(new Runnable() {
@Override
public void run() {
ProgressManagerImpl.runProcessWithProgressAsynchronously(new MyRefreshTask(myDataPack));
}
});
}
};
}
@NotNull
@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 TimedVcsCommit>> commits = loadRecentCommitsFromVcs(myProviders, requirements,
myUserRegistry, myTopCommitsDetailsCache);
List<? extends TimedVcsCommit> compoundLog = compound(commits.values());
List<GraphCommit<Integer>> compactedLog = compactCommits(compoundLog, myHashMap);
DataPack dataPack = DataPack.build(compactedLog, new RefsModel(refs, myHashMap.asIndexGetter()),
myHashMap.asIndexGetter(), myHashMap.asHashGetter(), myProviders, false);
mySingleTaskController.request(RefreshRequest.RELOAD_ALL); // build/rebuild the full log in bg
return dataPack;
}
catch (VcsException e) {
myExceptionHandler.consume(e);
return EmptyDataPack.getInstance();
}
}
@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();
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<VirtualFile, List<? extends TimedVcsCommit>> loadRecentCommitsFromVcs(@NotNull Map<VirtualFile, VcsLogProvider> providers,
@NotNull final Map<VirtualFile, VcsLogProvider.Requirements> requirements,
@NotNull final VcsUserRegistry userRegistry,
@NotNull final Map<Hash, VcsCommitMetadata> topCommitsDetailsCache)
throws VcsException {
final StopWatch sw = StopWatch.start("loading commits");
final Map<VirtualFile, List<? extends TimedVcsCommit>> 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, metadatas);
sw.rootCompleted(root);
}
}.iterate(providers);
sw.report();
return commits;
}
/**
* Compounds logs from different repositories into a single multi-repository log.
*/
@NotNull
private static List<? extends TimedVcsCommit> compound(@NotNull Collection<List<? extends TimedVcsCommit>> commits) {
StopWatch sw = StopWatch.start("multi-repo join");
List<? extends TimedVcsCommit> joined = new VcsLogMultiRepoJoiner().join(commits);
sw.report();
return joined;
}
@NotNull
private static List<GraphCommit<Integer>> compactCommits(@NotNull List<? extends TimedVcsCommit> commits,
@NotNull final VcsLogHashMap hashMap) {
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 new GraphCommitImpl<Integer>(hashMap.getCommitIndex(commit.getId()),
ContainerUtil.map(commit.getParents(), hashMap.asIndexGetter()), commit.getTimestamp());
}
});
hashMap.flush();
sw.report();
return map;
}
private static void storeUsersAndDetails(@NotNull List<? extends VcsCommitMetadata> metadatas, @NotNull VcsUserRegistry userRegistry,
@NotNull Map<Hash, VcsCommitMetadata> topCommitsDetailsCache) {
for (VcsCommitMetadata detail : metadatas) {
userRegistry.addUser(detail.getAuthor());
userRegistry.addUser(detail.getCommitter());
topCommitsDetailsCache.put(detail.getId(), detail);
}
}
private class MyRefreshTask extends Task.Backgroundable {
@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 TimedVcsCommit> log;
Collection<VcsRef> refs;
LogAndRefs(Collection<VcsRef> refs, List<? extends TimedVcsCommit> commits) {
this.refs = refs;
this.log = commits;
}
}
MyRefreshTask(@NotNull DataPack currentDataPack) {
super(VcsLogRefresherImpl.this.myProject, "Refreshing history...", false);
myCurrentDataPack = currentDataPack;
}
@Override
public void run(@NotNull ProgressIndicator indicator) {
DataPack dataPack = myCurrentDataPack;
while (true) {
List<RefreshRequest> requests = mySingleTaskController.popRequests();
Collection<VirtualFile> rootsToRefresh = getRootsToRefresh(requests);
if (rootsToRefresh.isEmpty()) {
mySingleTaskController.taskCompleted(dataPack);
break;
}
dataPack = doRefresh(rootsToRefresh);
}
}
@NotNull
private Collection<VirtualFile> getRootsToRefresh(@NotNull List<RefreshRequest> requests) {
Collection<VirtualFile> rootsToRefresh = ContainerUtil.newArrayList();
for (RefreshRequest request : requests) {
if (request == RefreshRequest.RELOAD_ALL) {
myCurrentDataPack = EmptyDataPack.getInstance();
return myProviders.keySet();
}
rootsToRefresh.addAll(request.rootsToRefresh);
}
return rootsToRefresh;
}
@NotNull
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();
try {
if (permanentGraph != null) {
loadLogAndRefs(roots, currentRefs, myRecentCommitCount);
List<? extends TimedVcsCommit> compoundLog = compound(ContainerUtil.map(myLoadedInfos.values(),
new Function<LogAndRefs, List<? extends TimedVcsCommit>>() {
@Override
public List<? extends TimedVcsCommit> fun(LogAndRefs refs) {
return refs.log;
}
}));
List<GraphCommit<Integer>> preparedLog = compactCommits(compoundLog, myHashMap);
Map<VirtualFile, Collection<VcsRef>> allNewRefs = getAllNewRefs(myLoadedInfos, currentRefs);
List<GraphCommit<Integer>> joinedFullLog = join(preparedLog, permanentGraph.getAllCommits(), currentRefs, allNewRefs);
if (joinedFullLog != null) {
return DataPack.build(joinedFullLog, new RefsModel(allNewRefs, myHashMap.asIndexGetter()),
myHashMap.asIndexGetter(), myHashMap.asHashGetter(), myProviders, true);
}
// couldn't join => need to reload everything; this shouldn't happen often, the error is logged in join().
}
Pair<PermanentGraph<Integer>, Map<VirtualFile, Collection<VcsRef>>> fullLogAndRefs = loadFullLog();
return DataPack.build(fullLogAndRefs.first, myProviders, new RefsModel(fullLogAndRefs.second, myHashMap.asIndexGetter()), true);
}
catch (Exception e) {
myExceptionHandler.consume(e);
return EmptyDataPack.getInstance();
}
finally {
sw.report();
}
}
@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();
for (VirtualFile root : previousRefs.keySet()) {
result.put(root, newInfo.containsKey(root) ? newInfo.get(root).refs : previousRefs.get(root));
}
return result;
}
private void loadLogAndRefs(@NotNull Collection<VirtualFile> roots, @NotNull Map<VirtualFile, Collection<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 TimedVcsCommit>> commits = loadRecentCommitsFromVcs(providers, requirements,
myUserRegistry, myTopCommitsDetailsCache);
for (VirtualFile root : roots) {
myLoadedInfos.put(root, new LogAndRefs(refs.get(root), commits.get(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) {
Map<VirtualFile, VcsLogProvider.Requirements> requirements = ContainerUtil.newHashMap();
for (VirtualFile root : roots) {
requirements.put(root, new RequirementsImpl(commitCount, true, getRefsForRoot(prevRefs, root), getRefsForRoot(newRefs, 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<GraphCommit<Integer>> recentCommits, @NotNull List<GraphCommit<Integer>> fullLog,
@NotNull Map<VirtualFile, Collection<VcsRef>> previousRefs,
@NotNull Map<VirtualFile, Collection<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());
}
};
Collection<Integer> prevRefIndices = ContainerUtil.map(ContainerUtil.concat(previousRefs.values()), ref2Int);
Collection<Integer> newRefIndices = ContainerUtil.map(ContainerUtil.concat(newRefs.values()), ref2Int);
try {
List<GraphCommit<Integer>> commits = new VcsLogJoiner<Integer, GraphCommit<Integer>>().addCommits(fullLog, prevRefIndices,
recentCommits,
newRefIndices).first;
sw.report();
return commits;
}
catch (IllegalStateException e) {
LOG.info(e);
return null;
}
}
@NotNull
private Pair<PermanentGraph<Integer>, Map<VirtualFile, Collection<VcsRef>>> loadFullLog() throws VcsException {
StopWatch sw = StopWatch.start("full log reload");
Collection<List<? extends TimedVcsCommit>> commits = readFullLogFromVcs();
List<? extends TimedVcsCommit> compoundLog = compound(commits);
List<GraphCommit<Integer>> graphCommits = compactCommits(compoundLog, myHashMap);
Map<VirtualFile, Collection<VcsRef>> refMap = loadRefsFromVcs(myProviders);
PermanentGraph<Integer> permanentGraph = DataPack.buildPermanentGraph(graphCommits, new RefsModel(refMap, myHashMap.asIndexGetter()),
myHashMap.asIndexGetter(),
myHashMap.asHashGetter(), myProviders);
sw.report();
return Pair.create(permanentGraph, refMap);
}
@NotNull
private Collection<List<? extends TimedVcsCommit>> readFullLogFromVcs() throws VcsException {
final StopWatch sw = StopWatch.start("read full log from VCS");
final Collection<List<? extends TimedVcsCommit>> commits = ContainerUtil.newArrayList();
new ProviderIterator() {
@Override
void each(@NotNull VirtualFile root, @NotNull VcsLogProvider provider) throws VcsException {
commits.add(provider.readAllHashes(root, new Consumer<VcsUser>() {
@Override
public void consume(@NotNull VcsUser user) {
myUserRegistry.addUser(user);
}
}));
sw.rootCompleted(root);
}
}.iterate(myProviders);
sw.report();
return commits;
}
}
private static class RefreshRequest {
private static RefreshRequest RELOAD_ALL = new RefreshRequest(Collections.<VirtualFile>emptyList());
@NotNull private final Collection<VirtualFile> rootsToRefresh;
RefreshRequest(@NotNull Collection<VirtualFile> rootsToRefresh) {
this.rootsToRefresh = rootsToRefresh;
}
}
private static abstract class ProviderIterator {
abstract void each(@NotNull VirtualFile root, @NotNull VcsLogProvider provider) throws VcsException;
final void iterate(@NotNull Map<VirtualFile, VcsLogProvider> providers) throws VcsException {
for (Map.Entry<VirtualFile, VcsLogProvider> entry : providers.entrySet()) {
each(entry.getKey(), entry.getValue());
}
}
}
}