package org.tmatesoft.svn.core.wc2; 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.SVNURL; import org.tmatesoft.svn.core.internal.wc.SVNErrorManager; import org.tmatesoft.svn.core.wc.SVNRevision; import org.tmatesoft.svn.util.SVNLogType; /** * Produces a diff summary which lists the changed items between * <code>source</code> in its <code>pegRevision</code>, as it changed * between <code>startRevision</code> and <code>endRevision</code>, * or diff summary between <code>firstSource</code> at its <code>pegRevision</code> * and <code>secondSource</code> at its <code>pegRevision</code>. * Changes are produced without creating text deltas. * * <ul> * <li> * If it is diff between <code>startRevision</code> and <code>endRevision</code> of one <code>source</code>: * * <p/> * <code>Source</code> can be either working copy path or URL. * * <p/> * If <code>pegRevision</code> is {@link SVNRevision#isValid() invalid}, behaves identically to * diff between two sources, using <code>source</code>'s path for both sources. * </li> * * <li> * If it is diff between first <code>source</code> and second <code>source</code>: * * <p/> * First and second <code>sources</code> can be either working copy path or URL. * * <p/> * Both <code>sources</code> must represent the same node kind -- that is, if first <code>source</code> is a directory, * second <code>sources</code> must also be, and if first <code>sources</code> is a file, * second <code>sources</code> must also be. * * </li> * </ul> * * The operation may report false positives if <code>ignoreAncestry</code> is * <code>false</code>, since a file might have been * modified between two revisions, but still have the same contents. * * <p/> * If <code>depth</code> is {@link SVNDepth#INFINITY}, diffs fully * recursively. Else if it is {@link SVNDepth#IMMEDIATES}, diffs the named * paths and their file children (if any), and diffs properties of * subdirectories, but does not descend further into the subdirectories. * Else if {@link SVNDepth#FILES}, behaves as if for * {@link SVNDepth#IMMEDIATES} except doesn't diff properties of * subdirectories. If {@link SVNDepth#EMPTY}, diffs exactly the named paths * but nothing underneath them. * * <p/> * {@link #run()} method throws {@link SVNException} in the following cases: * <ul> * <li/>exception with {@link SVNErrorCode#CLIENT_BAD_REVISION} * error code - if either <code>startRevision</code> or <code>endRevision</code> is * {@link SVNRevision#isValid() invalid} * <li/>exception with * {@link SVNErrorCode#UNSUPPORTED_FEATURE} error code - if * either of <code>startRevision</code> or </code>endRevision</code> is either * {@link SVNRevision#WORKING} or {@link SVNRevision#BASE} * </ul> * * @author TMate Software Ltd. * @version 1.7 */ public class SvnDiffSummarize extends SvnReceivingOperation<SvnDiffStatus> { private SvnTarget firstSource; private SvnTarget secondSource; private SvnTarget source; private SVNRevision startRevision; private SVNRevision endRevision; private boolean ignoreAncestry; protected SvnDiffSummarize(SvnOperationFactory factory) { super(factory); } /** * Sets the diff's <code>source</code> with start and end revisions for one-source type of operation. * * @param source source of the diff * @param start start revision of the diff * @param end end revision of the diff */ public void setSource(SvnTarget source, SVNRevision start, SVNRevision end) { this.source = source; this.startRevision = start; this.endRevision = end; if (source != null) { setSources(null, null); } } /** * Sets both diff's <code>sources</code>. * * @param source1 first source of the diff * @param source2 second source of the diff */ public void setSources(SvnTarget source1, SvnTarget source2) { this.firstSource = source1; this.secondSource = source2; if (firstSource != null) { setSource(null, null, null); } } /** * Gets the diff's <code>source</code> with start and end revisions for one-target type of operation. * * @return source of the diff */ public SvnTarget getSource() { return source; } public SVNRevision getStartRevision() { return startRevision; } public SVNRevision getEndRevision() { return endRevision; } public SvnTarget getFirstSource() { return firstSource; } public SvnTarget getSecondSource() { return secondSource; } public boolean isIgnoreAncestry() { return ignoreAncestry; } public void setIgnoreAncestry(boolean ignoreAncestry) { this.ignoreAncestry = ignoreAncestry; } @Override protected File getOperationalWorkingCopy() { if (getSource() != null && getSource().isFile()) { return getSource().getFile(); } else if (getFirstSource() != null && getFirstSource().isFile()) { return getFirstSource().getFile(); } else if (getSecondSource() != null && getSecondSource().isFile()) { return getSecondSource().getFile(); } return null; } @Override protected void ensureArgumentsAreValid() throws SVNException { if (getSource() == null || getSource().getPegRevision() == null || getSource().getPegRevision() == SVNRevision.UNDEFINED) { final SvnTarget firstSource = getFirstSource(); final SvnTarget secondSource = getSecondSource(); ensureArgumentsAreValid(firstSource.getURL(), firstSource.getFile(), firstSource.getPegRevision(), secondSource.getURL(), secondSource.getFile(), secondSource.getPegRevision(), null); } else { final SvnTarget source = getSource(); ensureArgumentsAreValid(source.getURL(), source.getFile(), getStartRevision(), source.getURL(), source.getFile(), getEndRevision(), source.getPegRevision()); } } private void ensureArgumentsAreValid(SVNURL url1, File path1, SVNRevision revision1, SVNURL url2, File path2, SVNRevision revision2, SVNRevision pegRevision) throws SVNException { if (pegRevision == null) { pegRevision = SVNRevision.UNDEFINED; } ensureRevisionIsValid(revision1); ensureRevisionIsValid(revision2); final boolean isPath1Local = startRevision == SVNRevision.WORKING || startRevision == SVNRevision.BASE; final boolean isPath2Local = endRevision == SVNRevision.WORKING || endRevision == SVNRevision.BASE; final boolean isRepos1; final boolean isRepos2; if (pegRevision != SVNRevision.UNDEFINED) { if (isPath1Local && isPath2Local) { 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 = !isPath1Local; isRepos2 = !isPath2Local; } else { isRepos1 = !isPath1Local || (url1 != null); isRepos2 = !isPath2Local || (url2 != null); } if (!isRepos1 || !isRepos2) { SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.UNSUPPORTED_FEATURE, "Summarizing diff can only compare repository to repository"); SVNErrorManager.error(err, SVNLogType.DEFAULT); } } private void ensureRevisionIsValid(SVNRevision revision) throws SVNException { final boolean revisionIsValid = revision != null && revision.isValid(); if (!revisionIsValid) { SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.CLIENT_BAD_REVISION, "Not all required revisions are specified"); SVNErrorManager.error(err, SVNLogType.DEFAULT); } } /** * Gets whether the operation changes working copy * @return <code>true</code> if the operation changes the working copy, otherwise <code>false</code> */ @Override public boolean isChangesWorkingCopy() { return false; } }