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.ISVNUpdateEditor; import org.tmatesoft.svn.core.internal.wc.SVNCancellableEditor; import org.tmatesoft.svn.core.internal.wc.SVNErrorManager; import org.tmatesoft.svn.core.internal.wc.SVNFileUtil; 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.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 { doDiffWCWC(target1, revision1, target2, revision2); } } } private void doDiffReposRepos(SvnTarget target1, SVNRevision revision1, SVNRevision pegRevision, SvnTarget target2, SVNRevision revision2) throws SVNException { SVNURL url1 = getRepositoryAccess().getTargetURL(target1); SVNURL url2 = getRepositoryAccess().getTargetURL(target2); File basePath = null; if (target1.isFile()) { basePath = target1.getFile(); } else if (target2.isFile()) { basePath = target2.getFile(); } SVNRepository repository = getRepositoryAccess().createRepository(url2, null, true); if (pegRevision != SVNRevision.UNDEFINED) { try { Structure<SvnRepositoryAccess.LocationsInfo> locations = getRepositoryAccess().getLocations(repository, target2, pegRevision, revision1, revision2); url1 = locations.get(SvnRepositoryAccess.LocationsInfo.startUrl); url2 = locations.get(SvnRepositoryAccess.LocationsInfo.endUrl); } catch (SVNException e) { if (e.getErrorMessage().getErrorCode() != SVNErrorCode.CLIENT_UNRELATED_RESOURCES) { throw e; } //otherwise we ignore the exception } repository.setLocation(url2, false); } final long revisionNumber2 = getRepositoryAccess().getRevisionNumber(repository, target2, revision2, null).lng(SvnRepositoryAccess.RevisionsPair.revNumber); SVNNodeKind kind2 = repository.checkPath("", revisionNumber2); repository.setLocation(url1, false); final long revisionNumber1 = getRepositoryAccess().getRevisionNumber(repository, target1, revision1, null).lng(SvnRepositoryAccess.RevisionsPair.revNumber); SVNNodeKind kind1 = repository.checkPath("", revisionNumber1); if (kind1 == SVNNodeKind.NONE && kind2 == SVNNodeKind.NONE) { if (url1.equals(url2)) { SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.FS_NOT_FOUND, "Diff target ''{0}'' was not found in the " + "repository at revisions ''{1}'' and ''{2}''", new Object[]{ url1, new Long(revisionNumber1), new Long(revisionNumber2) }); SVNErrorManager.error(err, SVNLogType.WC); } else { SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.FS_NOT_FOUND, "Diff targets ''{0}'' and ''{1}'' were not found " + "in the repository at revisions ''{2}'' and " + "''{3}''", new Object[]{ url1, url2, new Long(revisionNumber1), new Long(revisionNumber2) }); SVNErrorManager.error(err, SVNLogType.WC); } } else if (kind1 == SVNNodeKind.NONE) { checkDiffTargetExists(url1, revisionNumber2, revisionNumber1, repository); } else if (kind2 == SVNNodeKind.NONE) { checkDiffTargetExists(url2, revisionNumber1, revisionNumber2, repository); } SVNNodeKind kind; SVNURL anchor1 = url1; SVNURL anchor2 = url2; String targetString1 = ""; String targetString2 = ""; SVNURL repositoryRoot = null; SVNNodeKind oldKind1 = kind1; SVNNodeKind oldKind2 = kind2; if (kind1 == SVNNodeKind.NONE || kind2 == SVNNodeKind.NONE) { repositoryRoot = repository.getRepositoryRoot(true); SVNURL newAnchor = kind1 == SVNNodeKind.NONE ? anchor1 : anchor2; SVNRevision revision = kind1 == SVNNodeKind.NONE ? revision1 : revision2; do { if (!newAnchor.equals(repositoryRoot)) { newAnchor = SVNURL.parseURIEncoded(SVNPathUtil.removeTail(newAnchor.toString())); if (basePath != null) { basePath = SVNFileUtil.getParentFile(basePath); } } repository.setLocation(newAnchor, false); kind = repository.checkPath("", revision.getNumber()); //TODO: no such method: SVNRepository#checkPath(String, SVNRevision) } while (kind != SVNNodeKind.DIR); anchor1 = anchor2 = newAnchor; targetString1 = SVNPathUtil.getRelativePath(newAnchor.toDecodedString(), url1.toDecodedString()); targetString2 = SVNPathUtil.getRelativePath(newAnchor.toDecodedString(), url2.toDecodedString()); assert target1 != null && target2 != null; if (kind1 == SVNNodeKind.NONE) { kind1 = SVNNodeKind.DIR; } else { kind2 = SVNNodeKind.DIR; } } else if (kind1 == SVNNodeKind.FILE || kind2 == SVNNodeKind.FILE) { targetString1 = SVNPathUtil.tail(url1.toDecodedString()); targetString2 = SVNPathUtil.tail(url2.toDecodedString()); anchor1 = SVNURL.parseURIEncoded(SVNPathUtil.removeTail(url1.toString())); anchor2 = SVNURL.parseURIEncoded(SVNPathUtil.removeTail(url2.toString())); if (basePath != null) { basePath = SVNFileUtil.getParentFile(basePath); } repository.setLocation(anchor1, false); } ISvnDiffGenerator generator = getDiffGenerator(); generator.setOriginalTargets(SvnTarget.fromURL(url1), SvnTarget.fromURL(url2)); generator.setAnchors(SvnTarget.fromURL(anchor1), SvnTarget.fromURL(anchor2)); if (getOperation().isUseGitDiffFormat()) { if (repositoryRoot == null) { repositoryRoot = repository.getRepositoryRoot(true); } generator.setRepositoryRoot(SvnTarget.fromURL(repositoryRoot)); } SvnDiffCallback callback = createDiffCallback(generator, false, revisionNumber1, revisionNumber2); SVNRepository extraRepository = getRepositoryAccess().createRepository(anchor1, null, false); try { boolean pureRemoteDiff = (basePath == null); SvnNgRemoteDiffEditor remoteDiffEditor = SvnNgRemoteDiffEditor.createEditor(getWcContext(), pureRemoteDiff ? new File("") : basePath, getOperation().getDepth(), extraRepository, revisionNumber1, true, false, pureRemoteDiff, callback, this); ISVNEditor editor; editor = remoteDiffEditor; editor = SVNCancellableEditor.newInstance(editor, this, SVNDebugLog.getDefaultLog()); if (oldKind1 != SVNNodeKind.NONE && oldKind2 != SVNNodeKind.NONE) { ISVNReporterBaton reporter = new ISVNReporterBaton() { public void report(ISVNReporter reporter) throws SVNException { reporter.setPath("", null, revisionNumber1, SVNDepth.INFINITY, false); reporter.finishReport(); } }; try { repository.diff(url2, revisionNumber2, revisionNumber1, targetString1, getOperation().isIgnoreAncestry(), getOperation().getDepth(), true, reporter, editor); } finally { remoteDiffEditor.cleanup(); } } else { //oldKind1 == NONE or oldKind2 == NONE repository.setLocation(anchor1, false); ISVNReporterBaton reporter = new ISVNReporterBaton() { public void report(ISVNReporter reporter) throws SVNException { reporter.setPath("", null, revisionNumber1, SVNDepth.INFINITY, false); reporter.finishReport(); } }; try { repository.diff(anchor2.appendPath(SVNPathUtil.head(targetString2), false), revisionNumber2, revisionNumber1, SVNPathUtil.head(targetString2), getOperation().isIgnoreAncestry(), getOperation().getDepth(), true, reporter, editor); } finally { remoteDiffEditor.cleanup(); } } } finally { extraRepository.closeSession(); } } 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()); if (!target1.getFile().equals(target2.getFile()) || !(revision1 == SVNRevision.BASE && revision2 == SVNRevision.WORKING)) { SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.INCORRECT_PARAMS, "Only diffs between a path's text-base and its working files are supported at this time"); SVNErrorManager.error(err, SVNLogType.DEFAULT); } 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; } } 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); doDiffWC(target1.getFile(), callback); } 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 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 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().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; } }