VCS: support renames in show diff for annotation - for SVN only for now

This commit is contained in:
irengrig
2010-08-11 21:05:02 +04:00
parent 06225646b7
commit 777824a3e5
7 changed files with 114 additions and 28 deletions
@@ -16,6 +16,7 @@
package com.intellij.openapi.vcs;
import com.intellij.openapi.util.Pair;
import com.intellij.openapi.vcs.changes.committed.DecoratorManager;
import com.intellij.openapi.vcs.changes.committed.VcsCommittedListsZipper;
import com.intellij.openapi.vcs.changes.committed.VcsCommittedViewAuxiliary;
@@ -58,6 +59,9 @@ public interface CommittedChangesProvider<T extends CommittedChangeList, U exten
*/
int getUnlimitedCountValue();
/**
* @return required list and path of the target file in that revision (changes when move/rename)
*/
@Nullable
T getOneList(final VirtualFile file, final VcsRevisionNumber number) throws VcsException;
Pair<T, FilePath> getOneList(final VirtualFile file, final VcsRevisionNumber number) throws VcsException;
}
@@ -38,6 +38,7 @@ import com.intellij.openapi.project.Project;
import com.intellij.openapi.ui.MessageType;
import com.intellij.openapi.util.IconLoader;
import com.intellij.openapi.util.Key;
import com.intellij.openapi.util.Pair;
import com.intellij.openapi.util.Ref;
import com.intellij.openapi.util.registry.Registry;
import com.intellij.openapi.util.text.StringUtil;
@@ -526,13 +527,16 @@ public class AnnotateToggleAction extends ToggleAction implements DumbAware {
if (revisionNumber != null) {
final VcsException[] exc = new VcsException[1];
final List<Change> changes = new LinkedList<Change>();
final FilePath[] targetPath = new FilePath[1];
ProgressManager.getInstance().run(new Task.Backgroundable(myVcs.getProject(),
"Loading revision " + revisionNumber.asString() + " contents", true, BackgroundFromStartOption.getInstance()) {
@Override
public void run(@NotNull ProgressIndicator indicator) {
final CommittedChangesProvider provider = myVcs.getCommittedChangesProvider();
try {
final CommittedChangeList cl = provider.getOneList(myFile, revisionNumber);
final Pair<CommittedChangeList, FilePath> pair = provider.getOneList(myFile, revisionNumber);
targetPath[0] = pair.getSecond() == null ? new FilePathImpl(myFile) : pair.getSecond();
final CommittedChangeList cl = pair.getFirst();
if (cl == null) {
ChangesViewBalloonProblemNotifier.showMe(myVcs.getProject(), "Can not load data for show diff", MessageType.ERROR);
return;
@@ -550,7 +554,7 @@ public class AnnotateToggleAction extends ToggleAction implements DumbAware {
if (exc[0] != null) {
ChangesViewBalloonProblemNotifier.showMe(myVcs.getProject(), "Can not show diff: " + exc[0].getMessage(), MessageType.ERROR);
} else if (! changes.isEmpty()) {
int idx = findSelfInList(changes);
int idx = findSelfInList(changes, targetPath[0]);
final ShowDiffUIContext context = new ShowDiffUIContext(true);
context.setDiffNavigationContext(createDiffNavigationContext(actualNumber));
ShowDiffAction.showDiffForChange(changes.toArray(new Change[changes.size()]), idx, myVcs.getProject(), context);
@@ -560,9 +564,9 @@ public class AnnotateToggleAction extends ToggleAction implements DumbAware {
}
}
private int findSelfInList(List<Change> changes) {
private int findSelfInList(List<Change> changes, final FilePath filePath) {
int idx = -1;
final File ioFile = new File(myFile.getPath());
final File ioFile = filePath.getIOFile();
for (int i = 0; i < changes.size(); i++) {
final Change change = changes.get(i);
if ((change.getAfterRevision() != null) && (change.getAfterRevision().getFile().getIOFile().equals(ioFile))) {
@@ -18,6 +18,7 @@ package com.intellij.openapi.vcs.changes.committed;
import com.intellij.openapi.actionSystem.AnAction;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.Pair;
import com.intellij.openapi.vcs.*;
import com.intellij.openapi.vcs.history.VcsRevisionNumber;
import com.intellij.openapi.vcs.versionBrowser.ChangeBrowserSettings;
@@ -154,7 +155,7 @@ public class CompositeCommittedChangesProvider implements CommittedChangesProvid
}
@Override
public CommittedChangeList getOneList(VirtualFile file, VcsRevisionNumber number) {
public Pair<CommittedChangeList, FilePath> getOneList(VirtualFile file, VcsRevisionNumber number) {
throw new UnsupportedOperationException();
}
@@ -30,6 +30,7 @@ import com.intellij.openapi.progress.ProcessCanceledException;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.Comparing;
import com.intellij.openapi.util.Computable;
import com.intellij.openapi.util.Pair;
import com.intellij.openapi.vcs.*;
import com.intellij.openapi.vcs.changes.ChangesUtil;
import com.intellij.openapi.vcs.changes.committed.*;
@@ -132,8 +133,10 @@ public class CvsCommittedChangesProvider implements CachingCommittedChangesProvi
@Nullable
@Override
public CvsChangeList getOneList(VirtualFile file, final VcsRevisionNumber number) throws VcsException {
CvsRepositoryLocation cvsLocation = (CvsRepositoryLocation) getLocationFor(new FilePathImpl(file));
public Pair<CvsChangeList, FilePath> getOneList(VirtualFile file, final VcsRevisionNumber number) throws VcsException {
// todo implement in proper way
final FilePathImpl filePath = new FilePathImpl(file);
CvsRepositoryLocation cvsLocation = (CvsRepositoryLocation) getLocationFor(filePath);
final String module = cvsLocation.getModuleName();
final CvsEnvironment connectionSettings = cvsLocation.getEnvironment();
if (connectionSettings.isOffline()) {
@@ -162,7 +165,7 @@ public class CvsCommittedChangesProvider implements CachingCommittedChangesProvi
else if (! executionResult.hasNoErrors()) {
throw executionResult.composeError();
}
return result[0];
return new Pair<CvsChangeList, FilePath>(result[0], filePath);
}
public List<CvsChangeList> getCommittedChanges(ChangeBrowserSettings settings, RepositoryLocation location, final int maxCount) throws VcsException {
@@ -17,6 +17,7 @@ package git4idea.changes;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.Pair;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.openapi.vcs.*;
import com.intellij.openapi.vcs.changes.committed.DecoratorManager;
@@ -225,8 +226,10 @@ public class GitCommittedChangeListProvider implements CommittedChangesProvider<
}
@Override
public CommittedChangeList getOneList(VirtualFile file, final VcsRevisionNumber number) throws VcsException {
final GitRepositoryLocation l = (GitRepositoryLocation) getLocationFor(new FilePathImpl(file));
public Pair<CommittedChangeList, FilePath> getOneList(VirtualFile file, final VcsRevisionNumber number) throws VcsException {
// todo implement in proper way
final FilePathImpl filePath = new FilePathImpl(file);
final GitRepositoryLocation l = (GitRepositoryLocation) getLocationFor(filePath);
VirtualFile root = LocalFileSystem.getInstance().findFileByIoFile(l.getRoot());
if (root == null) {
throw new VcsException("The repository does not exists anymore: " + l.getRoot());
@@ -244,7 +247,7 @@ public class GitCommittedChangeListProvider implements CommittedChangesProvider<
result[0] = committedChangeList;
}
}, false);
return result[0];
return new Pair<CommittedChangeList, FilePath>(result[0], filePath);
}
public int getFormatVersion() {
@@ -13,6 +13,7 @@
package org.zmlx.hg4idea.provider;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.Pair;
import com.intellij.openapi.vcs.*;
import com.intellij.openapi.vcs.actions.VcsContextFactory;
import com.intellij.openapi.vcs.changes.Change;
@@ -271,16 +272,17 @@ public class HgCachingCommitedChangesProvider
}
@Override
public CommittedChangeList getOneList(VirtualFile file, VcsRevisionNumber number) throws VcsException {
public Pair<CommittedChangeList, FilePath> getOneList(VirtualFile file, VcsRevisionNumber number) throws VcsException {
final ChangeBrowserSettings settings = createDefaultSettings();
settings.USE_CHANGE_AFTER_FILTER = true;
settings.USE_CHANGE_BEFORE_FILTER = true;
settings.CHANGE_AFTER = number.asString();
settings.CHANGE_BEFORE = number.asString();
final List<CommittedChangeList> list = getCommittedChanges(settings, getLocationFor(
VcsContextFactory.SERVICE.getInstance().createFilePathOn(file)), 1);
// todo implement in proper way
final FilePath filePath = VcsContextFactory.SERVICE.getInstance().createFilePathOn(file);
final List<CommittedChangeList> list = getCommittedChanges(settings, getLocationFor(filePath), 1);
if (list.size() == 1) {
return list.get(0);
return new Pair<CommittedChangeList, FilePath>(list.get(0), filePath);
}
return null;
}
@@ -26,6 +26,8 @@ import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.IconLoader;
import com.intellij.openapi.util.Pair;
import com.intellij.openapi.vcs.*;
import com.intellij.openapi.vcs.changes.Change;
import com.intellij.openapi.vcs.changes.ContentRevision;
import com.intellij.openapi.vcs.changes.committed.*;
import com.intellij.openapi.vcs.history.VcsRevisionNumber;
import com.intellij.openapi.vcs.versionBrowser.ChangeBrowserSettings;
@@ -39,12 +41,10 @@ import com.intellij.util.ThrowableConsumer;
import com.intellij.util.containers.MultiMap;
import com.intellij.util.messages.MessageBusConnection;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.idea.svn.SvnAuthenticationNotifier;
import org.jetbrains.idea.svn.SvnBundle;
import org.jetbrains.idea.svn.SvnUtil;
import org.jetbrains.idea.svn.SvnVcs;
import org.jetbrains.idea.svn.*;
import org.jetbrains.idea.svn.actions.ConfigureBranchesAction;
import org.tmatesoft.svn.core.*;
import org.tmatesoft.svn.core.internal.util.SVNPathUtil;
import org.tmatesoft.svn.core.io.SVNRepository;
import org.tmatesoft.svn.core.wc.SVNInfo;
import org.tmatesoft.svn.core.wc.SVNLogClient;
@@ -53,6 +53,7 @@ import org.tmatesoft.svn.core.wc.SVNWCClient;
import java.io.DataInput;
import java.io.DataOutput;
import java.io.File;
import java.io.IOException;
import java.util.*;
@@ -491,7 +492,7 @@ public class SvnCommittedChangesProvider implements CachingCommittedChangesProvi
}
@Override
public SvnChangeList getOneList(final VirtualFile file, VcsRevisionNumber number) throws VcsException {
public Pair<SvnChangeList, FilePath> getOneList(final VirtualFile file, VcsRevisionNumber number) throws VcsException {
final VirtualFile root = ProjectLevelVcsManager.getInstance(myProject).getVcsRootFor(file);
final SvnRepositoryLocation svnRootLocation = (SvnRepositoryLocation)getLocationFor(new FilePathImpl(root));
if (svnRootLocation == null) return null;
@@ -508,13 +509,15 @@ public class SvnCommittedChangesProvider implements CachingCommittedChangesProvi
final SVNRevision revisionBefore;
final SVNURL repositoryUrl;
final SVNURL svnurl;
final SVNInfo targetInfo;
try {
logger = myVcs.createLogClient();
revisionBefore = SVNRevision.create(revision);
svnurl = SVNURL.parseURIEncoded(url);
final SVNWCClient client = myVcs.createWCClient();
SVNInfo info = client.doInfo(svnurl, SVNRevision.UNDEFINED, SVNRevision.HEAD);
final SVNInfo info = client.doInfo(svnurl, SVNRevision.UNDEFINED, SVNRevision.HEAD);
targetInfo = client.doInfo(new File(file.getPath()), SVNRevision.WORKING);
if (info == null) {
throw new VcsException("Can not get repository URL");
}
@@ -528,10 +531,71 @@ public class SvnCommittedChangesProvider implements CachingCommittedChangesProvi
if (result[0] == null) {
tryByRoot(result, logger, revisionBefore, repositoryUrl);
if (result[0] == null) {
tryStepByStep(svnRootLocation, result, logger, revisionBefore, repositoryUrl, svnurl);
FilePath path = tryStepByStep(svnRootLocation, result, logger, revisionBefore, targetInfo, svnurl);
path = path == null ? new FilePathImpl(file) : path;
// and pass & take rename context there
return new Pair<SvnChangeList, FilePath>(result[0], path);
}
}
return result[0];
if (result[0].getChanges().size() == 1) {
final Collection<Change> changes = result[0].getChanges();
final Change change = changes.iterator().next();
final ContentRevision afterRevision = change.getAfterRevision();
if (afterRevision != null) {
return new Pair<SvnChangeList, FilePath>(result[0], afterRevision.getFile());
} else {
return new Pair<SvnChangeList, FilePath>(result[0], new FilePathImpl(file));
}
}
String relativePath = SVNPathUtil.getRelativePath(targetInfo.getRepositoryRootURL().toString(), targetInfo.getURL().toString());
relativePath = relativePath.startsWith("/") ? relativePath : "/" + relativePath;
final Change targetChange = result[0].getByPath(relativePath);
if (targetChange == null) {
FilePath path = tryStepByStep(svnRootLocation, result, logger, revisionBefore, targetInfo, svnurl);
path = path == null ? new FilePathImpl(file) : path;
// and pass & take rename context there
return new Pair<SvnChangeList, FilePath>(result[0], path);
}
return new Pair<SvnChangeList, FilePath>(result[0], new FilePathImpl(file));
}
private static class RenameContext {
private String myCurrentPath;
private String myRepositoryRoot;
private boolean myHadChanged;
private RenameContext(final SVNInfo info) {
myRepositoryRoot = info.getRepositoryRootURL().toString();
myCurrentPath = SVNPathUtil.getRelativePath(myRepositoryRoot, info.getURL().toString());
myCurrentPath = myCurrentPath.startsWith("/") ? myCurrentPath : ("/" + myCurrentPath);
}
public void accept(final SVNLogEntry entry) {
final Map changedPaths = entry.getChangedPaths();
for (Object o : changedPaths.values()) {
final SVNLogEntryPath entryPath = (SVNLogEntryPath) o;
if ('A' == entryPath.getType()) {
if (myCurrentPath.equals(entryPath.getPath())) {
myHadChanged = true;
myCurrentPath = entryPath.getCopyPath();
}
}
}
}
@Nullable
public FilePath getFilePath(final SvnVcs vcs) {
if (! myHadChanged) return null;
final SvnFileUrlMapping svnFileUrlMapping = vcs.getSvnFileUrlMapping();
final String absolutePath = SVNPathUtil.append(myRepositoryRoot, myCurrentPath);
final String localPath = svnFileUrlMapping.getLocalPath(absolutePath);
if (localPath == null) {
LOG.info("Cannot find local path for url: " + absolutePath);
return null;
}
return new FilePathImpl(new File(localPath), false);
}
}
private void tryByRoot(SvnChangeList[] result, SVNLogClient logger, SVNRevision revisionBefore, SVNURL repositoryUrl) throws VcsException {
@@ -540,13 +604,16 @@ public class SvnCommittedChangesProvider implements CachingCommittedChangesProvi
tryExactHit(new SvnRepositoryLocation(repositoryUrl.toString()), result, logger, revisionBefore, repositoryUrl, repositoryUrl);
}
private void tryStepByStep(final SvnRepositoryLocation svnRepositoryLocation,
// return changed path, if any
private FilePath tryStepByStep(final SvnRepositoryLocation svnRepositoryLocation,
final SvnChangeList[] result,
SVNLogClient logger,
final SVNRevision revisionBefore, final SVNURL repositoryUrl, SVNURL svnurl) throws VcsException {
final SVNRevision revisionBefore, final SVNInfo info, SVNURL svnurl) throws VcsException {
final String repositoryRoot = info.getRepositoryRootURL().toString();
try {
final RenameContext renameContext = new RenameContext(info);
logger.doLog(svnurl, null, SVNRevision.UNDEFINED, SVNRevision.HEAD, revisionBefore,
false, true, true, 0, null,
false, true, false, 0, null,
new ISVNLogEntryHandler() {
public void handleLogEntry(SVNLogEntry logEntry) {
if (myProject.isDisposed()) throw new ProcessCanceledException();
@@ -554,11 +621,13 @@ public class SvnCommittedChangesProvider implements CachingCommittedChangesProvi
// do not add lists without info - this situation is possible for lists where there are paths that user has no rights to observe
return;
}
renameContext.accept(logEntry);
if (logEntry.getRevision() == revisionBefore.getNumber()) {
result[0] = new SvnChangeList(myVcs, svnRepositoryLocation, logEntry, repositoryUrl.toString());
result[0] = new SvnChangeList(myVcs, svnRepositoryLocation, logEntry, repositoryRoot);
}
}
});
return renameContext.getFilePath(myVcs);
}
catch (SVNException e) {
throw new VcsException(e);