/* * 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 org.jetbrains.idea.svn.history; import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.progress.ProcessCanceledException; import com.intellij.openapi.project.Project; import com.intellij.openapi.util.Pair; import com.intellij.openapi.vcs.FilePath; import com.intellij.openapi.vcs.VcsException; import com.intellij.openapi.vcs.changes.Change; import com.intellij.openapi.vcs.changes.ContentRevision; import com.intellij.openapi.vcs.history.VcsRevisionNumber; import com.intellij.openapi.vfs.VirtualFile; import com.intellij.vcsUtil.VcsUtil; import org.jetbrains.annotations.NotNull; import org.jetbrains.idea.svn.RootUrlInfo; import org.jetbrains.idea.svn.SvnRevisionNumber; import org.jetbrains.idea.svn.SvnUtil; import org.jetbrains.idea.svn.SvnVcs; import org.jetbrains.idea.svn.auth.SvnAuthenticationNotifier; import org.jetbrains.idea.svn.commandLine.SvnBindException; import org.tmatesoft.svn.core.SVNErrorCode; import org.tmatesoft.svn.core.SVNURL; import org.tmatesoft.svn.core.wc.SVNRevision; import org.tmatesoft.svn.core.wc2.SvnTarget; import static com.intellij.openapi.vfs.VfsUtilCore.virtualToIoFile; /** * @author Konstantin Kolosovsky. */ public class SingleCommittedListProvider { private static final Logger LOG = Logger.getInstance(SingleCommittedListProvider.class); @NotNull private final SvnVcs myVcs; @NotNull private final Project myProject; @NotNull private final VirtualFile file; @NotNull private final VcsRevisionNumber number; private SvnChangeList[] changeList; private SVNRevision revisionBefore; private SVNURL repositoryUrl; private SVNURL svnRootUrl; private SvnRepositoryLocation svnRootLocation; private String repositoryRelativeUrl; private FilePath filePath; public SingleCommittedListProvider(@NotNull SvnVcs vcs, @NotNull VirtualFile file, @NotNull VcsRevisionNumber number) { myVcs = vcs; myProject = vcs.getProject(); this.file = file; this.number = number; } public Pair<SvnChangeList, FilePath> run() throws VcsException { Pair<SvnChangeList, FilePath> result = null; if (setup()) { calculate(); result = Pair.create(changeList[0], filePath); } return result; } private boolean setup() { boolean result = false; RootUrlInfo rootUrlInfo = myVcs.getSvnFileUrlMapping().getWcRootForFilePath(virtualToIoFile(file)); if (rootUrlInfo != null) { changeList = new SvnChangeList[1]; revisionBefore = ((SvnRevisionNumber)number).getRevision(); repositoryUrl = rootUrlInfo.getRepositoryUrlUrl(); svnRootUrl = rootUrlInfo.getAbsoluteUrlAsUrl(); svnRootLocation = new SvnRepositoryLocation(rootUrlInfo.getAbsoluteUrl()); repositoryRelativeUrl = SvnUtil.ensureStartSlash(SvnUtil.join( SvnUtil.getRelativeUrl(repositoryUrl.toDecodedString(), svnRootUrl.toDecodedString()), SvnUtil.getRelativePath(rootUrlInfo.getPath(), file.getPath()))); filePath = VcsUtil.getFilePath(file); result = true; } return result; } private void calculate() throws VcsException { // TODO: Seems that filePath detection could be replaced with "svn info -r <revisionBefore>" - and not call // TODO: "svn log -r HEAD:<revisionBefore>" and track copies manually (which also is not correct for all cases). if (!searchForUrl(svnRootUrl) && !(hasAccess(repositoryUrl) && searchForUrl(repositoryUrl))) { filePath = searchFromHead(svnRootUrl); } else { if (changeList[0].getChanges().size() == 1) { final ContentRevision afterRevision = changeList[0].getChanges().iterator().next().getAfterRevision(); filePath = afterRevision != null ? afterRevision.getFile() : filePath; } else { final Change targetChange = changeList[0].getByPath(repositoryRelativeUrl); filePath = targetChange == null ? searchFromHead(svnRootUrl) : filePath; } } } private boolean hasAccess(@NotNull SVNURL url) { return SvnAuthenticationNotifier.passiveValidation(myVcs, url); } // return changed path, if any private FilePath searchFromHead(@NotNull SVNURL url) throws VcsException { final SvnCopyPathTracker pathTracker = new SvnCopyPathTracker(repositoryUrl.toDecodedString(), repositoryRelativeUrl); SvnTarget target = SvnTarget.fromURL(url); myVcs.getFactory(target).createHistoryClient() .doLog(target, SVNRevision.HEAD, revisionBefore, false, true, false, 0, null, logEntry -> { checkDisposed(); // date could be null for lists where there are paths that user has no rights to observe if (logEntry.getDate() != null) { pathTracker.accept(logEntry); if (logEntry.getRevision() == revisionBefore.getNumber()) { changeList[0] = createChangeList(logEntry); } } } ); FilePath path = pathTracker.getFilePath(myVcs); return path == null ? filePath : path; } @NotNull private SvnChangeList createChangeList(@NotNull LogEntry logEntry) { return new SvnChangeList(myVcs, svnRootLocation, logEntry, repositoryUrl.toDecodedString()); } private void checkDisposed() { if (myProject.isDisposed()) { throw new ProcessCanceledException(); } } private boolean searchForUrl(@NotNull SVNURL url) throws VcsException { LogEntryConsumer handler = logEntry -> { checkDisposed(); // date could be null for lists where there are paths that user has no rights to observe if (logEntry.getDate() != null) { changeList[0] = createChangeList(logEntry); } }; SvnTarget target = SvnTarget.fromURL(url); try { myVcs.getFactory(target).createHistoryClient().doLog(target, revisionBefore, revisionBefore, false, true, false, 1, null, handler); } catch (SvnBindException e) { LOG.info(e); if (!e.containsCategory(SVNErrorCode.FS_CATEGORY)) { throw e; } } return changeList[0] != null; } }