blob: 57084bff43fcb4e10024ba2a6ef44a2dec39f0ed [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 git4idea.history.wholeTree;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.Comparing;
import com.intellij.openapi.util.Couple;
import com.intellij.openapi.util.Pair;
import com.intellij.openapi.util.Ref;
import com.intellij.openapi.vcs.CalledInAwt;
import com.intellij.openapi.vcs.CalledInBackground;
import com.intellij.openapi.vcs.ObjectsConvertor;
import com.intellij.openapi.vcs.VcsException;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.util.Processor;
import com.intellij.util.Ticket;
import com.intellij.util.containers.Convertor;
import com.intellij.util.ui.UIUtil;
import git4idea.history.browser.CachedRefs;
import org.jetbrains.annotations.NonNls;
import org.jetbrains.annotations.Nullable;
import javax.swing.*;
import java.util.*;
/**
* @author irengrig
*/
public class MediatorImpl implements Mediator {
private boolean myHaveRestrictingFilters;
private final Ticket myTicket;
private final Project myProject;
private final GitCommitsSequentially myGitCommitsSequentially;
private TableWrapper myTableWrapper;
private UIRefresh myUIRefresh;
private Loader myLoader;
private final LoadGrowthController myController;
private Map<VirtualFile, SequenceSupportBuffer> mySequenceBuffers;
private DetailsLoader myDetailsLoader;
public MediatorImpl(final Project project, GitCommitsSequentially gitCommitsSequentially) {
myProject = project;
myGitCommitsSequentially = gitCommitsSequentially;
myTicket = new Ticket();
myController = new LoadGrowthController();
mySequenceBuffers = new HashMap<VirtualFile, SequenceSupportBuffer>();
myHaveRestrictingFilters = false;
}
@CalledInBackground
@Override
public StepType appendResult(final Ticket ticket,
final List<CommitI> result,
final @Nullable List<List<AbstractHash>> parents,
VirtualFile root, boolean checkForSequential) {
if (! myTicket.equals(ticket)) {
return StepType.STOP;
}
if (! result.isEmpty()) {
/*if (mySequenceBuffers != null && checkForSequential) {
try {
mySequenceBuffers.get(root).appendResult(ticket, result, parents);
}
catch (VcsException e) {
// todo
myUIRefresh.acceptException(e);
myTableWrapper.forceStop();
return StepType.STOP;
}
} else {*/
myTableWrapper.appendResult(ticket, result, parents);
//}
}
if (myTableWrapper.isSuspend()) {
return StepType.PAUSE;
}
return StepType.CONTINUE;
}
@CalledInBackground
@Override
public void reportSymbolicRefs(final Ticket ticket, final VirtualFile root, final CachedRefs symbolicRefs) {
Runnable runnable = new Runnable() {
@Override
public void run() {
if (!myTicket.equals(ticket)) return;
myDetailsLoader.reportRefs(root, symbolicRefs);
myUIRefresh.reportSymbolicRefs(root, symbolicRefs);
}
};
if (ApplicationManager.getApplication().isDispatchThread()) {
runnable.run();
} else {
SwingUtilities.invokeLater(runnable);
}
}
@CalledInAwt
@Override
public void continueLoading() {
myTableWrapper.clearSuspend();
myLoader.resume();
}
@Override
public void forceStop() {
myTableWrapper.forceStop();
}
@Override
public void acceptException(VcsException e) {
myUIRefresh.acceptException(e);
}
@Override
public void acceptStashHead(Ticket ticket, VirtualFile root, Couple<AbstractHash> hash) {
if (! myTicket.equals(ticket)) return;
myUIRefresh.reportStash(root, hash);
}
@Override
public void oneFinished() {
if (myController.isEmpty()) {
UIUtil.invokeLaterIfNeeded(new Runnable() {
@Override
public void run() {
myUIRefresh.finished();
myTableWrapper.finished();
}
});
}
}
@CalledInAwt
@Override
public void reloadSetFixed(final Map<AbstractHash, Long> starred, final RootsHolder rootsHolder) {
myTicket.increment();
final List<AbstractHash> hash = new ArrayList<AbstractHash>(starred.keySet());
Collections.sort(hash, new Comparator<AbstractHash>() {
@Override
public int compare(AbstractHash o1, AbstractHash o2) {
return Comparing.compare(starred.get(o2), starred.get(o1));
}
});
final boolean multipleRoots = rootsHolder.multipleRoots();
final List<CommitI> filtered = new ArrayList<CommitI>();
final List<AbstractHash> missing = new ArrayList<AbstractHash>();
int cInHashes = 0;
final int count = myTableWrapper.myTableModel.getRowCount();
for (int i = 0; i < count && hash.size() > cInHashes; i++) {
final CommitI at = myTableWrapper.myTableModel.getCommitAt(i);
if (at.holdsDecoration()) continue;
final AbstractHash obj = hash.get(cInHashes);
if (at.getHash().equals(obj)) {
// commit + multiple repo
final Commit commit = new Commit(at.getHash().getString(), at.getTime(), at.getAuthorIdx());
if (multipleRoots) {
filtered.add(new MultipleRepositoryCommitDecorator(commit, at.selectRepository(SelectorList.getInstance())));
} else {
filtered.add(commit);
}
++ cInHashes;
} else if (starred.get(obj) > at.getTime()) {
missing.add(obj);
++ cInHashes;
}
}
for (; cInHashes < hash.size(); ++ cInHashes) {
missing.add(hash.get(cInHashes));
}
myTableWrapper.reset(false, true);
myController.reset();
myHaveRestrictingFilters = true;
if (! filtered.isEmpty()) {
myTableWrapper.appendResult(myTicket.copy(), filtered, Collections.<List<AbstractHash>>emptyList());
}
if (! missing.isEmpty()) {
final GitLogFilters filters =
new GitLogFilters(null, null, null, null, ObjectsConvertor.convert(missing, new Convertor<AbstractHash, String>() {
@Override
public String convert(AbstractHash o) {
return o.getString();
}
}));
filters.setUseOnlyHashes(true);
myLoader.loadSkeleton(myTicket.copy(), rootsHolder, Collections.<String>emptyList(), filters, myController, true);
} else {
myUIRefresh.finished();
myTableWrapper.finished();
}
}
@CalledInAwt
@Override
public void reload(final RootsHolder rootsHolder,
final Collection<String> startingPoints,
Collection<String> endPoints, final GitLogFilters filters, final boolean topoOrder) {
myTicket.increment();
myTableWrapper.reset(! filters.haveDisordering(), startingPoints.isEmpty());
myController.reset();
myHaveRestrictingFilters = filters.haveCommitterOrCommentFilters();
/*if (filters.isEmpty()) {
mySequenceBuffers.clear();
for (VirtualFile root : rootsHolder.getRoots()) {
mySequenceBuffers.put(root, new SequenceSupportBuffer(myTableWrapper, myGitCommitsSequentially, root));
}
} else {
mySequenceBuffers = null;
}*/
myLoader.loadSkeleton(myTicket.copy(), rootsHolder, startingPoints, filters, myController, topoOrder);
}
public void setLoader(Loader loader) {
myLoader = loader;
}
public void setTableModel(BigTableTableModel tableWrapper) {
myTableWrapper = new TableWrapper(tableWrapper);
}
public void setUIRefresh(UIRefresh UIRefresh) {
myUIRefresh = UIRefresh;
}
public void setDetailsLoader(DetailsLoader loader) {
myDetailsLoader = loader;
}
private static boolean pageSizeOk(final Integer size) {
return size != null && size > 0;
}
@NonNls public static final String GIT_LOG_PAGE_SIZE = "git.log.page.size";
public static final int ourDefaultPageSize = 1000;
public final static int ourManyLoadedStep = (! pageSizeOk(Integer.getInteger(GIT_LOG_PAGE_SIZE))) ? ourDefaultPageSize : Integer.getInteger(
GIT_LOG_PAGE_SIZE);
private static class SequenceSupportBuffer {
private CommitI myTail;
private List<CommitI> myCommits;
private List<List<AbstractHash>> myParents;
private final TableWrapper myTableWrapper;
private final GitCommitsSequentially myCommitsSequentially;
private final VirtualFile myRoot;
private SequenceSupportBuffer(TableWrapper tableWrapper, GitCommitsSequentially commitsSequentially, final VirtualFile root) {
myTableWrapper = tableWrapper;
myCommitsSequentially = commitsSequentially;
myRoot = root;
myCommits = Collections.emptyList();
myParents = Collections.emptyList();
}
public void appendResult(final Ticket ticket, final List<CommitI> result,
final @Nullable List<List<AbstractHash>> parents) throws VcsException {
assert result.size() == parents.size();
result.addAll(myCommits);
parents.addAll(myParents);
final SortListsByFirst sortListsByFirst = new SortListsByFirst(result, CommitIComparator.getInstance());
sortListsByFirst.sortAnyOther(parents);
final long commitTime = myTail == null ? result.get(0).getTime() : myTail.getTime();
final Ref<Integer> cnt = new Ref<Integer>(myTail == null ? 0 : -1);
myCommitsSequentially.iterateDescending(myRoot, commitTime, new Processor<Pair<AbstractHash, Long>>() {
boolean startFound = false;
@Override
public boolean process(Pair<AbstractHash, Long> pair) {
if (cnt.get() == -1) {
if (! pair.getFirst().getString().startsWith(myTail.getHash().getString())) {
System.out.println("!!!!!!!!! (1) pair: " + pair.getFirst().getString() + " commit (" + cnt.get() + "): " + myTail.getHash().getString());
return (! startFound) ? pair.getSecond() == commitTime : false;
} else {
cnt.set(0);
startFound = true;
return true;
}
}
if (! pair.getFirst().getString().startsWith(result.get(cnt.get()).getHash().getString())) {
System.out.println("(2) pair: " + pair.getFirst().getString() + " commit (" + cnt.get() + "): " + result.get(cnt.get()).getHash().getString());
return (! startFound) ? pair.getSecond() == commitTime : false;
}
cnt.set(cnt.get() + 1);
startFound = true;
return cnt.get() < result.size();
}
});
myCommits = new ArrayList<CommitI>(result.subList(cnt.get(), result.size()));
myParents = new ArrayList<List<AbstractHash>>(parents.subList(cnt.get(), parents.size()));
if (cnt.get() > 0) {
myTableWrapper.appendResult(ticket, result.subList(0, cnt.get()), parents.subList(0, cnt.get()));
myTail = result.get(cnt.get() - 1);
}
}
}
// in order to don't forget resets etc
// in fact, data is linked to table state...
private class TableWrapper {
private int myRecentCut;
// queried from background
private volatile boolean mySuspend;
private boolean myForcedStop;
private final BigTableTableModel myTableModel;
public TableWrapper(final BigTableTableModel tableModel) {
myTableModel = tableModel;
myRecentCut = 0;
mySuspend = false;
myForcedStop = false;
}
@CalledInBackground
public boolean isSuspend() {
return mySuspend;
}
@CalledInBackground
public void appendResult(final Ticket ticket, final List<CommitI> result,
final @Nullable List<List<AbstractHash>> parents) {
final Runnable runnable = new Runnable() {
@Override
public void run() {
if (! myTicket.equals(ticket)) return;
// todo check for continuation right here
myTableModel.appendData(result, parents);
if (myController.isEmpty()) {
myTableModel.restore();
mySuspend = false;
} else if (! myForcedStop) {
int nextCut = nextCut();
if ((nextCut + 1) < myTableModel.getTrueCount()) {
if (nextCut > myRecentCut) {
mySuspend = true;
myRecentCut = nextCut;
myTableModel.cutAt(myRecentCut);
}
}
}
myUIRefresh.linesReloaded(mySuspend);
if (myController.isEmpty()) {
myUIRefresh.finished();
}
}
};
if (ApplicationManager.getApplication().isDispatchThread()) {
runnable.run();
} else {
SwingUtilities.invokeLater(runnable);
}
}
private int nextCut() {
int nextCut = myRecentCut;
while (true) {
final CommitI commitAt = myTableModel.getCommitAt(nextCut + getLoadSize());
if (commitAt != null && myController.isEverybodyLoadedMoreThan(commitAt.getTime())) {
nextCut += getLoadSize();
continue;
}
break;
}
return nextCut;
}
public void finished() {
mySuspend = false;
myForcedStop = false;
myTableModel.restore();
}
@CalledInAwt
public void clearSuspend() {
mySuspend = false;
myForcedStop = false;
// myTableModel.restore();
}
@CalledInAwt
public void reset(boolean noFilters, boolean noStartingPoints) {
mySuspend = false;
myForcedStop = false;
myRecentCut = 0;
myTableModel.clear(noFilters, noStartingPoints);
}
@CalledInAwt
public void forceStop() {
mySuspend = true;
myForcedStop = true;
}
}
private int getLoadSize() {
return myHaveRestrictingFilters ? ourManyLoadedStep/2 : ourManyLoadedStep;
}
}