blob: ca69ba7a58eccb9faf622c81316c0d0e8a57f779 [file] [log] [blame]
/*
* Copyright 2000-2009 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 org.jetbrains.idea.svn.history;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.progress.ProcessCanceledException;
import com.intellij.openapi.progress.ProgressManager;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.Ref;
import com.intellij.openapi.vcs.RepositoryLocation;
import com.intellij.openapi.vcs.VcsException;
import com.intellij.openapi.vcs.changes.committed.ChangesBunch;
import com.intellij.openapi.vcs.changes.committed.CommittedChangesCache;
import com.intellij.openapi.vcs.changes.committed.CommittedChangesNavigation;
import com.intellij.openapi.vcs.versionBrowser.CommittedChangeList;
import com.intellij.openapi.vfs.VirtualFile;
import org.jetbrains.idea.svn.SvnVcs;
import org.jetbrains.idea.svn.commandLine.SvnBindException;
import org.jetbrains.idea.svn.info.Info;
import org.tmatesoft.svn.core.wc.SVNRevision;
import java.util.*;
public class SvnRevisionsNavigationMediator implements CommittedChangesNavigation {
private static final Logger LOG = Logger.getInstance("#org.jetbrains.idea.svn.history.SvnRevisionsNavigationMediator");
public final static int CHUNK_SIZE = 50;
private final InternallyCachedProvider myInternallyCached;
private final VisuallyCachedProvider myVisuallyCached;
private final List<List<Fragment>> myChunks;
private boolean myCanNotGoBack;
private int myCurrentIdx;
private final BunchFactory myChunkFactory;
private final Project myProject;
public SvnRevisionsNavigationMediator(final SvnRepositoryLocation location, final Project project, final VirtualFile vcsRoot) throws
VcsException {
myProject = project;
final SvnVcs vcs = SvnVcs.getInstance(project);
myChunks = new LinkedList<List<Fragment>>();
final VcsException[] exception = new VcsException[1];
final Ref<Info> infoRef = new Ref<Info>();
Runnable process = new Runnable() {
@Override
public void run() {
try {
infoRef.set(vcs.getInfo(location.toSvnUrl(), SVNRevision.HEAD));
}
catch (SvnBindException e) {
exception[0] = e;
}
}
};
underProgress(exception, process);
Info info = infoRef.get();
if (info == null || info.getRevision() == null || info.getRepositoryRootURL() == null) {
throw new VcsException("Could not get head info for " + location);
}
final Iterator<ChangesBunch> visualIterator = project.isDefault() ? null :
CommittedChangesCache.getInstance(project).getBackBunchedIterator(vcs, vcsRoot, location, CHUNK_SIZE);
final Iterator<ChangesBunch> internalIterator = project.isDefault() ? null : LoadedRevisionsCache.getInstance(project).iterator(location.getURL());
myInternallyCached = (internalIterator == null) ? null : new InternallyCachedProvider(internalIterator, myProject);
myVisuallyCached = (visualIterator == null) ? null : new VisuallyCachedProvider(visualIterator, myProject, location);
myChunkFactory = new BunchFactory(myInternallyCached, myVisuallyCached, new LiveProvider(vcs, location, info.getRevision().getNumber(),
new SvnLogUtil(myProject, vcs, location,
info.getRepositoryRootURL()),
info.getRepositoryRootURL()));
myCurrentIdx = -1;
underProgress(exception, new Runnable() {
@Override
public void run() {
// init first screen
try {
goBack();
}
catch (VcsException e) {
exception[0] = e;
}
}
});
}
private void underProgress(final VcsException[] exception, final Runnable process) throws VcsException {
final boolean succeeded = ProgressManager.getInstance().runProcessWithProgressSynchronously(
process, "Getting latest repository revision", true, myProject);
if (exception[0] != null) {
throw exception[0];
}
if (! succeeded) {
throw new ProcessCanceledException();
}
}
public boolean canGoBack() {
return ((myCurrentIdx + 1) < myChunks.size()) || (! myCanNotGoBack);
}
public boolean canGoForward() {
return myCurrentIdx > 0;
}
public void goBack() throws VcsException {
if ((myCurrentIdx + 1) < myChunks.size()) {
++ myCurrentIdx;
return;
}
final Ref<Boolean> canNotGoBackRef = new Ref<Boolean>();
final List<Fragment> fragments = myChunkFactory.goBack(CHUNK_SIZE, canNotGoBackRef);
myCanNotGoBack = canNotGoBackRef.get().booleanValue();
if (! fragments.isEmpty()) {
// load
++ myCurrentIdx;
myChunks.add(fragments);
}
}
public void goForward() {
-- myCurrentIdx;
}
public List<CommittedChangeList> getCurrent() {
debugPrinting();
return (List<CommittedChangeList>) ((myChunks.isEmpty()) ? Collections.<List<CommittedChangeList>>emptyList() :
fragmentsToLists(myChunks.get(myCurrentIdx)));
}
private void debugPrinting() {
LOG.debug("== showing screen (" + myCurrentIdx + "): ==");
if (! myChunks.isEmpty()) {
for (Fragment fragment : myChunks.get(myCurrentIdx)) {
LOG.debug(fragment.getOrigin().toString() + " from: " + fragment.getList().get(0).getNumber() +
" to: " + fragment.getList().get(fragment.getList().size() - 1).getNumber());
}
}
LOG.debug("== end of screen ==");
}
private List<CommittedChangeList> fragmentsToLists(final List<Fragment> fragments) {
final List<CommittedChangeList> result = new ArrayList<CommittedChangeList>();
for (Fragment fragment : fragments) {
result.addAll(fragment.getList());
}
return result;
}
public void onBeforeClose() {
if ((myVisuallyCached != null) && (myVisuallyCached.hadBeenSuccessfullyAccessed())) {
myVisuallyCached.doCacheUpdate(myChunks);
// keep also in internal cache
InternallyCachedProvider.initCache(myChunks, myProject);
} else if (myInternallyCached != null) {
myInternallyCached.doCacheUpdate(myChunks);
} else {
InternallyCachedProvider.initCache(myChunks, myProject);
}
}
private static class VisuallyCachedProvider extends CachedProvider {
private final Project myProject;
private final RepositoryLocation myLocation;
private VisuallyCachedProvider(final Iterator<ChangesBunch> iterator, final Project project, final RepositoryLocation location) {
super(iterator, Origin.VISUAL);
myProject = project;
myLocation = location;
}
public void doCacheUpdate(final List<List<Fragment>> fragmentsListList) {
final List<CommittedChangeList> lists = getAllBeforeVisuallyCached(fragmentsListList);
CommittedChangesCache.getInstance(myProject).submitExternallyLoaded(myLocation, myAlreadyReaded.getList().get(0).getNumber(), lists);
}
}
private static class InternallyCachedProvider extends CachedProvider {
private final Project myProject;
private boolean myHolesDetected;
private InternallyCachedProvider(final Iterator<ChangesBunch> iterator, final Project project) {
super(iterator, Origin.INTERNAL);
myProject = project;
}
@Override
protected void addToLoaded(final ChangesBunch loaded) {
myHolesDetected |= (! loaded.isConsistentWithPrevious());
super.addToLoaded(loaded);
}
public static void initCache(final List<List<Fragment>> fragmentListList, final Project project) {
final List<CommittedChangeList> lists = getAllBeforeVisuallyCached(fragmentListList);
if (! lists.isEmpty()) {
LoadedRevisionsCache.getInstance(project).put(lists, false, null);
}
}
public void doCacheUpdate(final List<List<Fragment>> fragmentsListList) {
final List<CommittedChangeList> lists = new ArrayList<CommittedChangeList>();
LoadedRevisionsCache.Bunch bindAddress = null;
boolean consistent = false;
if (myHolesDetected) {
boolean liveMet = false;
for (int i = 0; i < fragmentsListList.size(); i++) {
final List<Fragment> fragmentList = fragmentsListList.get(i);
for (int j = 0; j < fragmentList.size(); j++) {
final Fragment fragment = fragmentList.get(j);
liveMet |= Origin.LIVE.equals(fragment.getOrigin());
if (Origin.INTERNAL.equals(fragment.getOrigin())) {
bindAddress = ((LoadedRevisionsCache.Bunch) fragment.getOriginBunch()).getNext();
// latest element
if ((i == (fragmentsListList.size() - 1)) && (j == (fragmentList.size() - 1))) {
lists.addAll(fragment.getOriginBunch().getList());
consistent = fragment.getOriginBunch().isConsistentWithPrevious();
break;
}
}
lists.addAll(fragment.getList());
}
}
if (! liveMet) {
return;
}
} else {
// until _first_internally
for (List<Fragment> fragmentList : fragmentsListList) {
for (Fragment fragment : fragmentList) {
if (Origin.INTERNAL.equals(fragment.getOrigin())) {
bindAddress = (LoadedRevisionsCache.Bunch) fragment.getOriginBunch();
consistent = true;
break;
}
lists.addAll(fragment.getList());
}
}
}
if (! lists.isEmpty()) {
LoadedRevisionsCache.getInstance(myProject).put(lists, consistent, bindAddress);
}
}
}
}