package org.tmatesoft.svn.core.internal.wc2.ng; import java.io.File; import org.tmatesoft.svn.core.SVNDepth; import org.tmatesoft.svn.core.SVNErrorCode; import org.tmatesoft.svn.core.SVNErrorMessage; import org.tmatesoft.svn.core.SVNException; import org.tmatesoft.svn.core.SVNNodeKind; import org.tmatesoft.svn.core.SVNURL; import org.tmatesoft.svn.core.internal.util.SVNPathUtil; import org.tmatesoft.svn.core.internal.wc.*; import org.tmatesoft.svn.core.internal.wc17.SVNAmbientDepthFilterEditor17; import org.tmatesoft.svn.core.internal.wc17.SVNReporter17; import org.tmatesoft.svn.core.internal.wc17.SVNStatusEditor17; import org.tmatesoft.svn.core.internal.wc17.SVNWCContext; import org.tmatesoft.svn.core.internal.wc17.db.Structure; import org.tmatesoft.svn.core.internal.wc17.db.StructureFields; import org.tmatesoft.svn.core.internal.wc2.SvnRepositoryAccess; import org.tmatesoft.svn.core.internal.wc2.SvnWcGeneration; import org.tmatesoft.svn.core.io.ISVNEditor; import org.tmatesoft.svn.core.io.ISVNReporter; import org.tmatesoft.svn.core.io.ISVNReporterBaton; import org.tmatesoft.svn.core.io.SVNCapability; import org.tmatesoft.svn.core.io.SVNRepository; import org.tmatesoft.svn.core.wc.SVNRevision; import org.tmatesoft.svn.core.wc2.SvnDiff; import org.tmatesoft.svn.core.wc2.SvnTarget; import org.tmatesoft.svn.util.SVNDebugLog; import org.tmatesoft.svn.util.SVNLogType; public class SvnNgDiff extends SvnNgOperationRunner<Void, SvnDiff> { @Override public SvnWcGeneration getWcGeneration() { return SvnWcGeneration.NOT_DETECTED; } @Override public boolean isApplicable(SvnDiff 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 Void run(SVNWCContext context) throws SVNException { if (isPeggedDiff()) { SvnTarget target = getOperation().getSource(); SVNRevision revision1 = getOperation().getStartRevision(); SVNRevision revision2 = getOperation().getEndRevision(); SVNRevision pegRevision = target.getPegRevision(); doDiff(target, revision1, pegRevision, target, revision2); } else { SvnTarget target1 = getOperation().getFirstSource(); SvnTarget target2 = getOperation().getSecondSource(); SVNRevision revision1 = target1.getPegRevision(); SVNRevision revision2 = target2.getPegRevision(); SVNRevision pegRevision = SVNRevision.UNDEFINED; doDiff(target1, revision1, pegRevision, target2, revision2); } return null; } private void doDiff(SvnTarget target1, SVNRevision revision1, SVNRevision pegRevision, SvnTarget target2, SVNRevision revision2) throws SVNException { if (revision1 == SVNRevision.UNDEFINED || revision2 == SVNRevision.UNDEFINED) { SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.CLIENT_BAD_REVISION, "Not all required revisions are specified"); SVNErrorManager.error(err, SVNLogType.DEFAULT); } final boolean isLocalRev1 = revision1 == SVNRevision.BASE || revision1 == SVNRevision.WORKING; final 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.DEFAULT); } isRepos1 = !isLocalRev1; isRepos2 = !isLocalRev2; } else { isRepos1 = !isLocalRev1 || target1.isURL(); isRepos2 = !isLocalRev2 || target2.isURL(); } if (isRepos1) { if (isRepos2) { doDiffReposRepos(target1, revision1, pegRevision, target2, revision2); } else { doDiffReposWC(target1, revision1, pegRevision, target2, revision2, false); } } else { if (isRepos2) { doDiffReposWC(target2, revision2, pegRevision, target1, revision1, true); } else { if (revision1 == SVNRevision.WORKING && revision2 == SVNRevision.WORKING) { ISvnDiffGenerator generator = getDiffGenerator(); generator.setOriginalTargets(target1, target2); generator.setAnchors(target1, target2); SvnNgDiffUtil.doArbitraryNodesDiff(target1, target2, getOperation().getDepth(), getWcContext(), createDiffCallback(generator, false, -1, -1), getOperation().getEventHandler()); } else { doDiffWCWC(target1, revision1, target2, revision2); } } } } private void doDiffReposRepos(SvnTarget svnTarget1, SVNRevision revision1, SVNRevision pegRevision, SvnTarget svnTarget2, SVNRevision revision2) throws SVNException { SVNURL url1 = svnTarget1.getURL(); SVNURL url2 = svnTarget2.getURL(); File path1 = svnTarget1.getFile(); File path2 = svnTarget2.getFile(); 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 = getRepositoryAccess().createRepository(url1, null, true); SVNRepository repository2 = getRepositoryAccess().createRepository(url2, null, false); long rev1 = getRevisionNumber(revision1, repository1, svnTarget1); long rev2 = -1; SVNNodeKind kind1 = null; SVNNodeKind kind2 = null; SVNURL anchor1 = url1; SVNURL anchor2 = url2; String target1 = ""; String target2 = ""; try { rev2 = getRevisionNumber(revision2, repository2, svnTarget2); 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; SVNURL repositoryRoot = null; ISvnDiffGenerator generator = getDiffGenerator(); generator.setOriginalTargets(SvnTarget.fromURL(url1), SvnTarget.fromURL(url2)); generator.setAnchors(SvnTarget.fromURL(kind1 == SVNNodeKind.FILE ? anchor1 : url1), SvnTarget.fromURL(kind2 == SVNNodeKind.FILE ? anchor2 : url2)); if (getOperation().isUseGitDiffFormat()) { if (repositoryRoot == null) { repositoryRoot = repository1.getRepositoryRoot(true); } generator.setRepositoryRoot(SvnTarget.fromURL(repositoryRoot)); } SvnDiffCallback oldCallback = createDiffCallback(generator, false, rev1, rev2); ISvnDiffCallback2 callback = new SvnDiffCallbackWrapper(oldCallback, true, basePath != null ? (nonDir ? basePath.getParentFile() : basePath) : new File("").getAbsoluteFile()); 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); } if (kind1 != SVNNodeKind.FILE && kind2 != SVNNodeKind.FILE && target1.length() > 0) { callback = new SvnFilterDiffCallback(SVNFileUtil.createFilePath(target1), callback); } repository1.setLocation(anchor1, true); repository2.setLocation(anchor1, true); SvnNgRemoteDiffEditor2 editor = null; try { editor = new SvnNgRemoteDiffEditor2(rev1, true, 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, getOperation().isIgnoreAncestry(), getOperation().getDepth(), true, reporter, SVNCancellableEditor.newInstance(editor, this, SVNDebugLog.getDefaultLog())); } finally { repository2.closeSession(); if (editor != null) { editor.cleanup(); } } } private void doDiffReposWC(SvnTarget target1, SVNRevision revision1, SVNRevision pegRevision, SvnTarget target2, SVNRevision revision2, boolean reverse) throws SVNException { assert !target2.isURL(); SVNURL url1 = getRepositoryAccess().getTargetURL(target1); String target = getWcContext().getActualTarget(target2.getFile()); File anchor; if (target == null || target.length() == 0) { anchor = target2.getFile(); } else { anchor = SVNFileUtil.getParentFile(target2.getFile()); } SVNURL anchorUrl = getWcContext().getNodeUrl(anchor); if (anchorUrl == null) { SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.ENTRY_MISSING_URL, "Directory ''{0}'' has no URL", anchor); SVNErrorManager.error(err, SVNLogType.DEFAULT); } final ISvnDiffGenerator generator = getDiffGenerator(); if (pegRevision != SVNRevision.UNDEFINED) { url1 = getRepositoryAccess().getLocations(null, target1, pegRevision, revision1, SVNRevision.UNDEFINED).get(SvnRepositoryAccess.LocationsInfo.startUrl); if (!reverse) { generator.setOriginalTargets(SvnTarget.fromURL(url1), SvnTarget.fromURL(anchorUrl.appendPath(target, false))); generator.setAnchors(SvnTarget.fromURL(url1), SvnTarget.fromURL(anchorUrl.appendPath(target, false))); } else { generator.setOriginalTargets(SvnTarget.fromURL(anchorUrl.appendPath(target, false)), SvnTarget.fromURL(url1)); generator.setAnchors(SvnTarget.fromURL(anchorUrl.appendPath(target, false)), SvnTarget.fromURL(url1)); } } else { if (!reverse) { generator.setOriginalTargets(target1, target2); generator.setAnchors(target1, target2); } else { generator.setOriginalTargets(target2, target1); generator.setAnchors(target2, target1); } } SVNRepository repository2 = getRepositoryAccess().createRepository(anchorUrl, null, true); if (getOperation().isUseGitDiffFormat()) { File wcRoot = getWcContext().getDb().getWCRoot(anchor); generator.setRepositoryRoot(SvnTarget.fromFile(wcRoot)); } boolean serverSupportsDepth = repository2.hasCapability(SVNCapability.DEPTH); long revisionNumber1 = getRepositoryAccess().getRevisionNumber(repository2, url1.equals(target1.getURL()) ? null : target1, revision1, null).lng(SvnRepositoryAccess.RevisionsPair.revNumber); SvnDiffCallback callback = createDiffCallback(generator, reverse, revisionNumber1, -1); SVNReporter17 reporter = new SVNReporter17(target2.getFile(), getWcContext(), false, !serverSupportsDepth, getOperation().getDepth(), false, false, true, false, SVNDebugLog.getDefaultLog()); boolean revisionIsBase = isRevisionBase(revision2); SvnDiffEditor svnDiffEditor = new SvnDiffEditor(anchor, target, callback, getOperation().getDepth(), getWcContext(), reverse, revisionIsBase, getOperation().isShowCopiesAsAdds(), getOperation().isIgnoreAncestry(), getOperation().getApplicableChangelists(), getOperation().isUseGitDiffFormat(), this); ISVNUpdateEditor updateEditor = svnDiffEditor; if (!serverSupportsDepth && getOperation().getDepth() == SVNDepth.UNKNOWN) { updateEditor = new SVNAmbientDepthFilterEditor17(updateEditor, getWcContext(), anchor, target, revisionIsBase); } ISVNEditor editor = SVNCancellableEditor.newInstance(updateEditor, this, SVNDebugLog.getDefaultLog()); try{ repository2.diff(url1, revisionNumber1, revisionNumber1, target, getOperation().isIgnoreAncestry(), getDiffDepth(getOperation().getDepth()), true, reporter, editor); } finally { svnDiffEditor.cleanup(); } } private void doDiffWCWC(SvnTarget target1, SVNRevision revision1, SvnTarget target2, SVNRevision revision2) 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); } long revisionNumber1; try { revisionNumber1 = getRepositoryAccess().getRevisionNumber(null, target1, revision1, null).lng(SvnRepositoryAccess.RevisionsPair.revNumber); } catch (SVNException e) { if (e.getErrorMessage().getErrorCode() == SVNErrorCode.CLIENT_BAD_REVISION) { revisionNumber1 = 0; } else { throw e; } } SVNNodeKind kind = SVNFileType.getNodeKind(SVNFileType.getType(path1)); String targetString1 = (kind == SVNNodeKind.DIR) ? "" : SVNFileUtil.getFileName(path1); final ISvnDiffGenerator generator = getDiffGenerator(); generator.setOriginalTargets(target1, target2); generator.setAnchors(target1, target2); if (getOperation().isUseGitDiffFormat()) { generator.setRepositoryRoot(SvnTarget.fromFile(getWcContext().getDb().getWCRoot(target1.getFile()))); } final SvnDiffCallback callback = createDiffCallback(generator, false, revisionNumber1, -1); SvnNgDiffUtil.doDiffWCWC(path1, getRepositoryAccess(), getWcContext(), getOperation().getDepth(), !getOperation().isIgnoreAncestry(), getOperation().getApplicableChangelists(), getOperation().isShowCopiesAsAdds(), getOperation().isUseGitDiffFormat(), generator, callback, getOperation().getEventHandler()); } private void doDiffWC(File localAbspath, ISvnDiffCallback callback) throws SVNException { final boolean getAll; //noinspection RedundantIfStatement if (getOperation().isShowCopiesAsAdds() || getOperation().isUseGitDiffFormat()) { getAll = true; } else { getAll = false; } final boolean diffIgnored = false; final SvnDiffStatusReceiver statusHandler = new SvnDiffStatusReceiver(getWcContext(), localAbspath, getWcContext().getDb(), callback, getOperation().isIgnoreAncestry(), getOperation().isShowCopiesAsAdds(), getOperation().isUseGitDiffFormat(), getOperation().getApplicableChangelists()); final SVNStatusEditor17 statusEditor = new SVNStatusEditor17( localAbspath, getWcContext(), getOperation().getOptions(), !diffIgnored, getAll, getOperation().getDepth(), statusHandler); statusEditor.walkStatus(localAbspath, getOperation().getDepth(), getAll, !diffIgnored, false, getOperation().getApplicableChangelists()); } 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 { repository.setLocation(url, false); SVNNodeKind kind = repository.checkPath("", revision); if (kind == SVNNodeKind.NONE) { if (revision == otherRevision) { SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.FS_NOT_FOUND, "Diff target ''{0}'' was not found in the " + "repository at revision ''{1}''", new Object[]{ url, new Long(revision) }); SVNErrorManager.error(err, SVNLogType.WC); } else { SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.FS_NOT_FOUND, "Diff target ''{0}'' was not found in the " + "repository at revisions ''{1}'' and ''{2}''", new Object[]{ url, new Long(revision), new Long(otherRevision) }); SVNErrorManager.error(err, SVNLogType.WC); } } } private SVNURL getURL(File path1) throws SVNException { return getRepositoryAccess().getURLFromPath(SvnTarget.fromFile(path1), SVNRevision.UNDEFINED, null).<SVNURL>get(SvnRepositoryAccess.UrlInfo.url); } private long getRevisionNumber(SVNRevision revision, SVNRepository repository, SvnTarget target) throws SVNException { final Structure<SvnRepositoryAccess.RevisionsPair> revisionNumber = getRepositoryAccess().getRevisionNumber(repository, target, revision, null); return revisionNumber.lng(SvnRepositoryAccess.RevisionsPair.revNumber); } private boolean isPeggedDiff() { return getOperation().getSource() != null; } private boolean isRevisionBase(SVNRevision revision2) { return revision2 == SVNRevision.BASE; } private SVNDepth getDiffDepth(SVNDepth depth) { return depth != SVNDepth.INFINITY ? depth : SVNDepth.UNKNOWN; } private SvnDiffCallback createDiffCallback(ISvnDiffGenerator generator, boolean reverse, long revisionNumber1, long revisionNumber2) { return new SvnDiffCallback(generator, reverse ? revisionNumber2 : revisionNumber1, reverse ? revisionNumber1 : revisionNumber2, getOperation().isShowCopiesAsAdds(), false, getOperation().getOutput()); } private ISvnDiffGenerator getDiffGenerator() { ISvnDiffGenerator diffGenerator = getOperation().getDiffGenerator(); if (diffGenerator == null) { diffGenerator = new SvnDiffGenerator(); } diffGenerator.setUseGitFormat(getOperation().isUseGitDiffFormat()); if (getOperation().getRelativeToDirectory() != null) { if (diffGenerator instanceof SvnDiffGenerator) { ((SvnDiffGenerator)diffGenerator).setRelativeToTarget(SvnTarget.fromFile(getOperation().getRelativeToDirectory())); } else { diffGenerator.setBaseTarget(SvnTarget.fromFile(getOperation().getRelativeToDirectory())); } } return diffGenerator; } }