blob: 7c3c5b4e51dc6a36177124d9aa4aed703db5c799 [file] [log] [blame]
/*
* Copyright 2000-2010 JetBrains s.r.o.
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package git4idea.history.wholeTree;
import com.intellij.openapi.progress.ProcessCanceledException;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.Getter;
import com.intellij.openapi.vcs.ObjectsConvertor;
import com.intellij.openapi.vcs.VcsException;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.util.AsynchConsumer;
import com.intellij.util.BufferedListConsumer;
import com.intellij.util.Consumer;
import com.intellij.util.Ticket;
import com.intellij.util.containers.Convertor;
import git4idea.history.browser.ChangesFilter;
import git4idea.history.browser.GitHeavyCommit;
import git4idea.history.browser.LowLevelAccessImpl;
import git4idea.history.browser.SymbolicRefsI;
import org.jetbrains.annotations.NotNull;
import java.util.*;
/**
* @author irengrig
*/
public class LoaderAndRefresherImpl implements LoaderAndRefresher<CommitHashPlusParents> {
private final static int ourFirstLoadCount = 15;
private final static int ourPreload = (! parameterCheck(Integer.getInteger("git.log.preload.size"))) ? 100 : Integer.getInteger("git.log.preload.size");
private final Collection<String> myStartingPoints;
private final Ticket myTicket;
private final Collection<ChangesFilter.Filter> myFilters;
private final Mediator myMediator;
private final DetailsCache myDetailsCache;
private final Project myProject;
private final Getter<Boolean> myProgressAnalog;
private BufferedListConsumer<CommitHashPlusParents> myBufferConsumer;
private Consumer<List<CommitHashPlusParents>> myRealConsumer;
private final MyRootHolder myRootHolder;
private final UsersIndex myUsersIndex;
private final boolean myLoadParents;
private RepeatingLoadConsumer<CommitHashPlusParents> myRepeatingLoadConsumer;
private LowLevelAccessImpl myLowLevelAccess;
private SymbolicRefsI mySymbolicRefs;
private final LoadGrowthController.ID myId;
private final boolean myHaveStructureFilter;
// state
@NotNull
private volatile StepType myStepType;
private final boolean myTopoOrder;
private static boolean parameterCheck(final Integer i) {
return i != null && i > 0;
}
public LoaderAndRefresherImpl(final Ticket ticket,
Collection<ChangesFilter.Filter> filters,
Mediator mediator,
Collection<String> startingPoints,
DetailsCache detailsCache,
Project project,
MyRootHolder rootHolder,
final UsersIndex usersIndex,
final LoadGrowthController.ID id, boolean haveStructureFilter, boolean topoOrder, boolean haveDisordering) {
myRootHolder = rootHolder;
myUsersIndex = usersIndex;
myId = id;
myHaveStructureFilter = haveStructureFilter;
myTopoOrder = topoOrder;
myLoadParents = ! haveDisordering;
myTicket = ticket;
myFilters = filters;
myMediator = mediator;
myStartingPoints = startingPoints;
myDetailsCache = detailsCache;
myStepType = StepType.CONTINUE;
myProject = project;
myProgressAnalog = new Getter<Boolean>() {
@Override
public Boolean get() {
return isInterrupted();
}
};
myLowLevelAccess = new LowLevelAccessImpl(myProject, myRootHolder.getRoot());
myRealConsumer = new Consumer<List<CommitHashPlusParents>>() {
@Override
public void consume(final List<CommitHashPlusParents> list) {
if (isInterrupted()) return;
final List<CommitI> buffer = new ArrayList<CommitI>();
final List<List<AbstractHash>> parents = myLoadParents ? new ArrayList<List<AbstractHash>>() : null;
for (CommitHashPlusParents commitHashPlusParents : list) {
CommitI commit = new Commit(commitHashPlusParents.getHash(), commitHashPlusParents.getTime(),
myUsersIndex.put(commitHashPlusParents.getAuthorName()));
commit = myRootHolder.decorateByRoot(commit);
buffer.add(commit);
if (myLoadParents) {
parents.add(commitHashPlusParents.getParents());
}
}
StepType stepType = myMediator.appendResult(myTicket, buffer, parents, myRootHolder.getRoot(), true);
if (! StepType.FINISHED.equals(myStepType)) {
myStepType = stepType;
}
}
};
myBufferConsumer = new BufferedListConsumer<CommitHashPlusParents>(15, myRealConsumer, 400);
myRepeatingLoadConsumer = new RepeatingLoadConsumer<CommitHashPlusParents>(myProject, myBufferConsumer.asConsumer());
}
public void interrupt() {
myStepType = StepType.STOP;
}
public boolean isInterrupted() {
return StepType.STOP.equals(myStepType);
}
@Override
public VirtualFile getRoot() {
return myRootHolder.getRoot();
}
@Override
public StepType flushIntoUI() {
myBufferConsumer.flush();
if (StepType.FINISHED.equals(myStepType)) {
myMediator.oneFinished();
}
return myStepType;
}
@Override
public LoadAlgorithm.Result<CommitHashPlusParents> load(final LoadAlgorithm.LoadType loadType, long continuation) {
if (isInterrupted()) return new LoadAlgorithm.Result<CommitHashPlusParents>(true, 0, myRepeatingLoadConsumer.getLast());
/*if (! myStartingPoints.isEmpty()) {
if (! checkStartingPoints()) return new LoadAlgorithm.Result<CommitHashPlusParents>(true, 0, myRepeatingLoadConsumer.getLast());
}*/
myRepeatingLoadConsumer.reset();
int count = 340;
boolean shouldFull = ! myHaveStructureFilter;
if (LoadAlgorithm.LoadType.TEST.equals(loadType)) {
count = ourFirstLoadCount;
} else if (LoadAlgorithm.LoadType.SHORT.equals(loadType) || LoadAlgorithm.LoadType.SHORT_START.equals(loadType)) {
shouldFull = false;
} else if (LoadAlgorithm.LoadType.FULL_PREVIEW.equals(loadType)) {
count = ourPreload;
}
long start;
boolean isOver = false;
while (true) {
start = System.currentTimeMillis();
step(count, shouldFull, continuation);
if (isInterrupted()) return new LoadAlgorithm.Result<CommitHashPlusParents>(true, 0, myRepeatingLoadConsumer.getLast());
final List<AbstractHash> lastParents = myRepeatingLoadConsumer.getLast() == null ? null : myRepeatingLoadConsumer.getLast().getParents();
// at least 1 record would be found, the latest
isOver = lastParents == null || lastParents.isEmpty() || (myRepeatingLoadConsumer.getTotalRecordsInPack() < count);
if (isOver) {
myId.finished();
myStepType = StepType.FINISHED;
break;
}
if (myRepeatingLoadConsumer.sincePoint() == 0) {
count *= 2;
myRepeatingLoadConsumer.reset();
} else {
break;
}
}
final long end = System.currentTimeMillis();
if (! isOver && (myRepeatingLoadConsumer.getLast() != null)) {
myId.registerTime(myRepeatingLoadConsumer.getLast().getTime());
}
return new LoadAlgorithm.Result<CommitHashPlusParents>(isOver, end - start, myRepeatingLoadConsumer.getLast());
}
private void step(final int count, final boolean shouldFull, final long continuation) {
if (shouldFull) {
loadFull(count, continuation);
} else {
loadShort(continuation, count);
}
}
/*private boolean checkStartingPoints() {
for (String point : myStartingPoints) {
if (point.startsWith(GitBranch.REFS_REMOTES_PREFIX)) {
if (mySymbolicRefs.getRemoteBranches().contains(point.substring(GitBranch.REFS_REMOTES_PREFIX.length()))) {
return true;
}
} else {
point = point.startsWith(GitBranch.REFS_HEADS_PREFIX) ? point.substring(GitBranch.REFS_HEADS_PREFIX.length()) : point;
if (mySymbolicRefs.getLocalBranches().contains(point) || mySymbolicRefs.getTags().contains(point)) {
return true;
}
}
}
return false;
}*/
/*private void initSymbRefs() {
if (mySymbolicRefs == null) {
try {
mySymbolicRefs = myLowLevelAccess.getRefs();
myMediator.reportSymbolicRefs(myTicket, myRootHolder.getRoot(), mySymbolicRefs);
}
catch (VcsException e) {
myMediator.acceptException(e);
}
}
}*/
public void setSymbolicRefs(SymbolicRefsI symbolicRefs) {
mySymbolicRefs = symbolicRefs;
}
// true - load is complete //if (gitCommit.getParentsHashes().isEmpty())
private void loadFull(final int count, final long continuation) {
try {
final Collection<ChangesFilter.Filter> filters = addContinuation(continuation);
myLowLevelAccess.loadCommits(myStartingPoints, Collections.<String>emptyList(), filters, new AsynchConsumer<GitHeavyCommit>() {
@Override
public void consume(GitHeavyCommit gitCommit) {
if (gitCommit.getParentsHashes().size() <= 1) {
myDetailsCache.acceptAnswer(Collections.singleton(gitCommit), myRootHolder.getRoot());
}
myRepeatingLoadConsumer.consume(GitCommitToCommitConvertor.getInstance().convert(gitCommit));
}
@Override
public void finished() {
}
}, count, myProgressAnalog, mySymbolicRefs, myTopoOrder);
}
catch (VcsException e) {
myMediator.acceptException(e);
}
}
private Collection<ChangesFilter.Filter> addContinuation(long continuation) {
Collection<ChangesFilter.Filter> filters;
if (continuation > 0) {
filters = new ArrayList<ChangesFilter.Filter>(myFilters);
filters.add(new ChangesFilter.BeforeDate(new Date(continuation)));
} else {
filters = myFilters;
}
return filters;
}
private void appendCommits(List<CommitI> result, List<List<AbstractHash>> parents, List<GitHeavyCommit> commits) {
for (GitHeavyCommit commit : commits) {
final Commit commitObj =
new Commit(commit.getShortHash().getString(), commit.getDate().getTime(), myUsersIndex.put(commit.getAuthor()));
if (parents != null) {
final Set<String> parentsHashes = commit.getParentsHashes();
parents.add(ObjectsConvertor.convert(parentsHashes, new Convertor<String, AbstractHash>() {
@Override
public AbstractHash convert(String o) {
return AbstractHash.create(o);
}
}));
}
result.add(myRootHolder.decorateByRoot(commitObj));
}
}
private void loadShort(final long continuation, int maxCount) {
final Collection<ChangesFilter.Filter> filters = addContinuation(continuation);
try {
myLowLevelAccess.loadHashesWithParents(myStartingPoints, filters, myRepeatingLoadConsumer, myProgressAnalog, maxCount, myTopoOrder);
}
catch (VcsException e) {
myMediator.acceptException(e);
}
}
interface MyRootHolder {
VirtualFile getRoot();
CommitI decorateByRoot(final CommitI commitI);
}
static class OneRootHolder implements MyRootHolder {
private final VirtualFile myVirtualFile;
OneRootHolder(VirtualFile virtualFile) {
myVirtualFile = virtualFile;
}
@Override
public CommitI decorateByRoot(CommitI commitI) {
return commitI;
}
@Override
public VirtualFile getRoot() {
return myVirtualFile;
}
}
static class ManyCaseHolder implements MyRootHolder {
private final RootsHolder myRootsHolder;
private final int myNum;
ManyCaseHolder(int num, RootsHolder rootsHolder) {
myNum = num;
myRootsHolder = rootsHolder;
}
@Override
public CommitI decorateByRoot(CommitI commitI) {
return new MultipleRepositoryCommitDecorator(commitI, myNum);
}
@Override
public VirtualFile getRoot() {
return myRootsHolder.get(myNum);
}
}
private static class RepeatingLoadConsumer<T> implements AsynchConsumer<T> {
private final Project myProject;
private final Consumer<T> myConsumer;
private int myCnt;
private T myLastT;
private int mySavedPoint;
private int myTotalRecordsInPack;
private boolean myPointMeet;
private RepeatingLoadConsumer(final Project project, Consumer<T> consumer) {
myProject = project;
myConsumer = consumer;
mySavedPoint = 0;
myCnt = 0;
myLastT = null;
myPointMeet = false;
}
public int reset() {
mySavedPoint = myCnt;
myTotalRecordsInPack = 0;
myPointMeet = false;
return mySavedPoint;
}
public int getTotalRecordsInPack() {
return myTotalRecordsInPack;
}
public int sincePoint() {
return myCnt - mySavedPoint;
}
public T getLast() {
return myLastT;
}
@Override
public void consume(T t) {
if (! myProject.isDefault() && ! myProject.isOpen() || myProject.isDisposed()) throw new ProcessCanceledException();
++ myTotalRecordsInPack;
if (myLastT == null) {
myPointMeet = true;
}
if (! myPointMeet) {
myPointMeet = t.equals(myLastT);
} else {
++ myCnt;
myConsumer.consume(t);
myLastT = t;
}
}
@Override
public void finished() {
}
}
}