package org.tmatesoft.svn.core.internal.wc2.ng; import org.tmatesoft.svn.core.*; import org.tmatesoft.svn.core.internal.util.SVNPathUtil; import org.tmatesoft.svn.core.internal.wc.*; import org.tmatesoft.svn.core.internal.wc17.SVNWCContext; import org.tmatesoft.svn.core.internal.wc17.db.Structure; import org.tmatesoft.svn.core.internal.wc2.SvnRepositoryAccess; import org.tmatesoft.svn.core.internal.wc2.SvnWcGeneration; import org.tmatesoft.svn.core.internal.wc2.compat.SvnCodec; import org.tmatesoft.svn.core.io.*; import org.tmatesoft.svn.core.wc.*; import org.tmatesoft.svn.core.wc2.ISvnObjectReceiver; import org.tmatesoft.svn.core.wc2.SvnDiffStatus; import org.tmatesoft.svn.core.wc2.SvnDiffSummarize; import org.tmatesoft.svn.core.wc2.SvnTarget; import org.tmatesoft.svn.util.ISVNDebugLog; import org.tmatesoft.svn.util.SVNDebugLog; import org.tmatesoft.svn.util.SVNLogType; import java.io.File; public class SvnNgDiffSummarize extends SvnNgOperationRunner<SvnDiffStatus, SvnDiffSummarize> { private SvnNgRepositoryAccess repositoryAccess; @Override public boolean isApplicable(SvnDiffSummarize operation, SvnWcGeneration wcGeneration) throws SVNException { if (operation.getSource() != null) { if (operation.getSource().isFile() && wcGeneration != SvnWcGeneration.V17) { return false; } return true; } else { if (operation.getFirstSource().isFile() && wcGeneration != SvnWcGeneration.V17) { return false; } if (operation.getSecondSource().isFile() && wcGeneration != SvnWcGeneration.V17) { return false; } return true; } } @Override protected SvnDiffStatus run(SVNWCContext context) throws SVNException { final SvnTarget source = getOperation().getSource(); final SvnTarget firstSource = getOperation().getFirstSource(); final SvnTarget secondSource = getOperation().getSecondSource(); final ISVNDiffStatusHandler handler = createHandlerForReceiver(getOperation()); final SVNDepth depth = getOperation().getDepth(); final boolean useAncestry = !getOperation().isIgnoreAncestry(); if (source != null) { doDiff(source, getOperation().getStartRevision(), source, getOperation().getEndRevision(), source.getPegRevision(), depth, useAncestry, handler); } else { doDiff(firstSource, firstSource.getResolvedPegRevision(), secondSource, secondSource.getResolvedPegRevision(), SVNRevision.UNDEFINED, depth, useAncestry, handler); } return null; } private void doDiff(SvnTarget target1, SVNRevision revision1, SvnTarget target2, SVNRevision revision2, SVNRevision pegRevision, SVNDepth depth, boolean useAncestry, ISVNDiffStatusHandler handler) throws SVNException { if ((revision1 == SVNRevision.UNDEFINED) || (revision2 == SVNRevision.UNDEFINED)) { SVNErrorMessage errorMessage = SVNErrorMessage.create(SVNErrorCode.CLIENT_BAD_REVISION, "Not all required revisions are specified"); SVNErrorManager.error(errorMessage, SVNLogType.WC); } boolean isLocalRev1 = (revision1 == SVNRevision.BASE) || (revision1 == SVNRevision.WORKING); boolean isLocalRev2 = (revision2 == SVNRevision.BASE) || (revision2 == SVNRevision.WORKING); if (pegRevision != SVNRevision.UNDEFINED && isLocalRev1 && isLocalRev2) { SVNErrorMessage errorMessage = SVNErrorMessage.create(SVNErrorCode.CLIENT_BAD_REVISION, "At least one revision must be something other than BASE or WORKING when diffing a URL"); SVNErrorManager.error(errorMessage, SVNLogType.WC); } boolean isRepos1 = !isLocalRev1 || target1.isURL(); boolean isRepos2 = !isLocalRev2 || target2.isURL(); if (isRepos1) { if (isRepos2) { doDiffReposRepos(target1.getURL(), target1.getFile(), revision1, target2.getURL(), target2.getFile(), revision2, pegRevision, depth, useAncestry, handler); } else { doDiffReposWC(target1, revision1, target2, revision2, pegRevision, false, depth, useAncestry, handler); } } else { if (isRepos2) { doDiffReposWC(target2, revision2, target1, revision1, pegRevision, true, depth, useAncestry, handler); } else { if (revision1 == SVNRevision.WORKING && revision2 == SVNRevision.WORKING) { File path1 = target1.getFile(); File path2 = target2.getFile(); SVNNodeKind kind = SVNFileType.getNodeKind(SVNFileType.getType(path1)); String target; SvnTarget baseTarget; if (kind == SVNNodeKind.DIR) { target = ""; baseTarget = target1; } else { target = SVNFileUtil.getFileName(path1); baseTarget = SvnTarget.fromFile(SVNFileUtil.getParentFile(target1.getFile()), target1.getPegRevision()); } SvnNgDiffUtil.doArbitraryNodesDiff(target1, target2, depth, getWcContext(), new SvnDiffSummarizeCallback(kind == SVNNodeKind.DIR ? path1 : SVNFileUtil.getParentFile(path1), false, getRepositoryAccess().getTargetURL(baseTarget), baseTarget.getFile(), handler), getOperation().getEventHandler()); } else { doDiffWCWC(target1, revision1, target2, revision2, depth, useAncestry, handler); } } } } private void doDiffURL(SVNURL url, File path, SVNRevision startRevision, SVNRevision endRevision, SVNRevision pegRevision, SVNDepth depth, boolean useAncestry, ISVNDiffStatusHandler handler) throws SVNException { if (handler == null) { return; } doDiffReposRepos(url, path, startRevision, url, path, endRevision, pegRevision, depth, useAncestry, handler); } private void doDiffReposRepos(SVNURL url1, File path1, SVNRevision revision1, SVNURL url2, File path2, SVNRevision revision2, SVNRevision pegRevision, SVNDepth depth, boolean useAncestry, ISVNDiffStatusHandler handler) throws SVNException { if (revision1 == SVNRevision.UNDEFINED || revision2 == SVNRevision.UNDEFINED) { SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.CLIENT_BAD_REVISION, "Not all revisions are specified"); SVNErrorManager.error(err, SVNLogType.WC); } boolean isLocalRev1 = revision1 == SVNRevision.BASE || revision1 == SVNRevision.WORKING; boolean isLocalRev2 = revision2 == SVNRevision.BASE || revision2 == SVNRevision.WORKING; boolean isRepos1; boolean isRepos2; if (pegRevision != SVNRevision.UNDEFINED) { if (isLocalRev1 && isLocalRev2) { SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.CLIENT_BAD_REVISION, "At least one revision must be non-local for a pegged diff"); SVNErrorManager.error(err, SVNLogType.WC); } isRepos1 = !isLocalRev1; isRepos2 = !isLocalRev2; } else { isRepos1 = !isLocalRev1 || url1 != null; isRepos2 = !isLocalRev2 || url2 != null; } if (!isRepos1 || !isRepos2) { SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.UNSUPPORTED_FEATURE, "Summarizing diff can only compare repository to repository"); SVNErrorManager.error(err, SVNLogType.WC); } File basePath = null; if (path1 != null) { basePath = path1; } if (path2 != null) { basePath = path2; } if (pegRevision.isValid()) { url2 = resolvePeggedDiffTargetUrl(url2, path2, pegRevision, revision2); url1 = resolvePeggedDiffTargetUrl(url1, path1, pegRevision, revision1); if (url2 != null && url1 == null) { url1 = url2; } if (url1 != null && url2 == null) { url2 = url1; } } else { url1 = url1 == null ? getURL(path1) : url1; url2 = url2 == null ? getURL(path2) : url2; } SVNRepository repository1 = createRepository(url1, null, true); SVNRepository repository2 = createRepository(url2, null, false); long rev1 = getRevisionNumber(revision1, repository1, url1); long rev2 = -1; SVNNodeKind kind1 = null; SVNNodeKind kind2 = null; SVNURL anchor1 = url1; SVNURL anchor2 = url2; String target1 = ""; String target2 = ""; try { rev2 = getRevisionNumber(revision2, repository2, url2); kind1 = repository1.checkPath("", rev1); kind2 = repository2.checkPath("", rev2); if (kind1 == SVNNodeKind.NONE && kind2 == SVNNodeKind.NONE) { if (url1.equals(url2)) { SVNErrorMessage errorMessage = SVNErrorMessage.create(SVNErrorCode.FS_NOT_FOUND, "Diff target ''{0}'' was not found in the repository at revisions ''{1}'' and ''{2}''", url1, rev1, rev2); SVNErrorManager.error(errorMessage, SVNLogType.WC); } else { SVNErrorMessage errorMessage = SVNErrorMessage.create(SVNErrorCode.FS_NOT_FOUND, "Diff targets ''{0}'' and ''{1}'' were not found in the repository at revisions ''{2}'' and ''{3}''"); SVNErrorManager.error(errorMessage, SVNLogType.WC); } } else if (kind1 == SVNNodeKind.NONE) { checkDiffTargetExists(url1, rev2, rev1, repository1); } else if (kind2 == SVNNodeKind.NONE) { checkDiffTargetExists(url2, rev1, rev2, repository2); } SVNURL repositoryRoot = repository1.getRepositoryRoot(true); if (!url1.equals(repositoryRoot) && !url2.equals(repositoryRoot)) { anchor1 = url1.removePathTail(); anchor2 = url2.removePathTail(); target1 = SVNPathUtil.tail(url1.toDecodedString()); target2 = SVNPathUtil.tail(url2.toDecodedString()); } } finally { repository2.closeSession(); } boolean nonDir = kind1 != SVNNodeKind.DIR || kind2 != SVNNodeKind.DIR; if (basePath == null) { basePath = nonDir ? new File(SVNPathUtil.tail(url1.toDecodedString())).getAbsoluteFile() : new File("").getAbsoluteFile(); } ISvnDiffCallback oldCallback = new SvnDiffSummarizeCallback(path1 != null ? SVNFileUtil.createFilePath(path1.getParentFile(), target1) : SVNFileUtil.createFilePath(new File("").getAbsolutePath(), target1), false, anchor1, nonDir ? basePath.getParentFile() : basePath, handler); ISvnDiffCallback2 callback = new SvnDiffCallbackWrapper(oldCallback, true, nonDir ? basePath.getParentFile() : basePath); if (kind2 == SVNNodeKind.NONE) { SVNURL tmpUrl; tmpUrl = url1; url1 = url2; url2 = tmpUrl; long tmpRev; tmpRev = rev1; rev1 = rev2; rev2 = tmpRev; tmpUrl = anchor1; anchor1 = anchor2; anchor2 = tmpUrl; String tmpTarget; tmpTarget = target1; target1 = target2; target2 = tmpTarget; callback = new SvnReverseOrderDiffCallback(callback, null); } repository1.setLocation(anchor1, true); repository2.setLocation(anchor1, true); SvnNgRemoteDiffEditor2 editor = null; try { editor = new SvnNgRemoteDiffEditor2(rev1, false, repository2, callback); final long finalRev1 = rev1; ISVNReporterBaton reporter = new ISVNReporterBaton() { public void report(ISVNReporter reporter) throws SVNException { reporter.setPath("", null, finalRev1, SVNDepth.INFINITY, false); reporter.finishReport(); } }; repository1.diff(url2, rev2, rev1, target1, !useAncestry, depth, false, reporter, SVNCancellableEditor.newInstance(editor, this, getDebugLog())); } finally { repository2.closeSession(); if (editor != null) { editor.cleanup(); } } } private void doDiffReposWC(SvnTarget target1, SVNRevision revision1, SvnTarget target2, SVNRevision revision2, SVNRevision pegRevision, boolean reverse, SVNDepth depth, boolean useAncestry, ISVNDiffStatusHandler handler) throws SVNException {//TODO: changelists? SvnNgDiffUtil.doDiffSummarizeReposWC(target1, revision1, pegRevision, target2, revision2, reverse, getRepositoryAccess(), getWcContext(), false, depth, useAncestry, getOperation().getApplicableChangelists(), false, null, handler, this); } private void doDiffWCWC(SvnTarget target1, SVNRevision revision1, SvnTarget target2, SVNRevision revision2, SVNDepth depth, boolean useAncestry, ISVNDiffStatusHandler handler) throws SVNException { assert !target1.isURL(); assert !target2.isURL(); File path1 = target1.getFile(); File path2 = target2.getFile(); if (!path1.equals(path2) || (!(revision1 == SVNRevision.BASE && revision2 == SVNRevision.WORKING))) { SVNErrorMessage errorMessage = SVNErrorMessage.create(SVNErrorCode.INCORRECT_PARAMS, "Summarized diffs are only supported between a path's text-base and its working files at this time"); SVNErrorManager.error(errorMessage, SVNLogType.WC); } SVNNodeKind kind = SVNFileType.getNodeKind(SVNFileType.getType(path1)); String targetString1 = (kind == SVNNodeKind.DIR) ? "" : SVNFileUtil.getFileName(path1); File basePath = (kind == SVNNodeKind.DIR) ? path1 : SVNFileUtil.getParentFile(path1); SVNURL baseUrl = kind == SVNNodeKind.DIR ? getRepositoryAccess().getTargetURL(target1) : getRepositoryAccess().getTargetURL(SvnTarget.fromFile(SVNFileUtil.getParentFile(target1.getFile()))); ISvnDiffCallback callback = new SvnDiffSummarizeCallback(path1, false, baseUrl, basePath, handler); SvnNgDiffUtil.doDiffWCWC(path1, getRepositoryAccess(), getWcContext(), depth, useAncestry, getOperation().getApplicableChangelists(), false, false, null, callback, getOperation().getEventHandler()); } private SVNURL resolvePeggedDiffTargetUrl(SVNURL url, File path, SVNRevision pegRevision, SVNRevision revision) throws SVNException { try { final Structure<SvnRepositoryAccess.LocationsInfo> locationsInfo = getRepositoryAccess().getLocations(null, url == null ? SvnTarget.fromFile(path) : SvnTarget.fromURL(url), pegRevision, revision, SVNRevision.UNDEFINED); return locationsInfo.get(SvnRepositoryAccess.LocationsInfo.startUrl); } catch (SVNException e) { if (e.getErrorMessage().getErrorCode() == SVNErrorCode.CLIENT_UNRELATED_RESOURCES || e.getErrorMessage().getErrorCode() == SVNErrorCode.FS_NOT_FOUND) { return null; } throw e; } } private void checkDiffTargetExists(SVNURL url, long revision, long otherRevision, SVNRepository repository) throws SVNException { SVNURL sessionUrl = repository.getLocation(); boolean equal = sessionUrl.equals(url); if (!equal) { repository.setLocation(url, true); } SVNNodeKind kind = repository.checkPath("", revision); if (kind == SVNNodeKind.NONE) { if (revision == otherRevision) { SVNErrorMessage errorMessage = SVNErrorMessage.create(SVNErrorCode.FS_NOT_FOUND, "Diff target ''{0}'' was not found in the repository at revision ''{1}''", url, revision); SVNErrorManager.error(errorMessage, SVNLogType.WC); } else { SVNErrorMessage errorMessage = SVNErrorMessage.create(SVNErrorCode.FS_NOT_FOUND, "Diff target ''{0}'' was not found in the repository at revision ''{1}'' or ''{2}''", url, revision, otherRevision); SVNErrorManager.error(errorMessage, SVNLogType.WC); } } if (!equal) { repository.setLocation(url, true); } } private ISVNDebugLog getDebugLog() { return SVNDebugLog.getDefaultLog(); } private long getRevisionNumber(SVNRevision revision1, SVNRepository repository1, SVNURL url1) throws SVNException { final Structure<SvnRepositoryAccess.RevisionsPair> revisionNumber = getRepositoryAccess().getRevisionNumber(repository1, SvnTarget.fromURL(url1, revision1), revision1, null); return revisionNumber.lng(SvnRepositoryAccess.RevisionsPair.revNumber); } private SVNURL getURL(File path1) throws SVNException { return getRepositoryAccess().getURLFromPath(SvnTarget.fromFile(path1), SVNRevision.UNDEFINED, null).<SVNURL>get(SvnRepositoryAccess.UrlInfo.url); } protected SVNRepository createRepository(SVNURL url, File path, boolean mayReuse) throws SVNException { return getRepositoryAccess().createRepository(url, null, mayReuse); } private static ISVNDiffStatusHandler createHandlerForReceiver(final ISvnObjectReceiver<SvnDiffStatus> receiver) { return new ISVNDiffStatusHandler() { public void handleDiffStatus(SVNDiffStatus diffStatus) throws SVNException { if (receiver != null) { final SvnTarget target = diffStatus.getURL() != null ? SvnTarget.fromURL(diffStatus.getURL()) : SvnTarget.fromFile(diffStatus.getFile()); receiver.receive(target, SvnCodec.diffStatus(diffStatus)); } } }; } protected SvnNgRepositoryAccess getRepositoryAccess() throws SVNException { if (repositoryAccess == null) { repositoryAccess = new SvnNgRepositoryAccess(getOperation(), getWcContext()); } return repositoryAccess; } }