package org.tmatesoft.svn.core.wc2;
import java.io.File;
import java.io.OutputStream;
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.internal.wc.SVNErrorManager;
import org.tmatesoft.svn.core.internal.wc2.ng.ISvnDiffGenerator;
import org.tmatesoft.svn.core.internal.wc2.ng.SvnNewDiffGenerator;
import org.tmatesoft.svn.core.internal.wc2.ng.SvnOldDiffGenerator;
import org.tmatesoft.svn.core.wc.DefaultSVNDiffGenerator;
import org.tmatesoft.svn.core.wc.ISVNDiffGenerator;
import org.tmatesoft.svn.core.wc.SVNDiffOptions;
import org.tmatesoft.svn.core.wc.SVNRevision;
import org.tmatesoft.svn.util.SVNLogType;
/**
* Represents diff operation.
* Produces diff output which describes the delta between <code>target</code>
* in its <code>pegRevision</code>, as it changed between <code>startRevision</code> and <code>endRevision</code>,
* or between first <code>target</code> at <code>startRevision</code> and second <code>target</code> at <code>endRevision</code>.
* Writes the output of the diff to <code>output</code> stream.
*
* <ul>
* <li>
* If it is diff between <code>startRevision</code> and <code>endRevision</code> of one <code>target</code>:
*
* <p/>
* <code>Target</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 targets, using <code>target</code>'s path for both targets.
* </li>
*
* <li>
* If it is diff between first <code>target</code> and second <code>target</code>:
*
* <p/>
* First and second <code>targets</code> can be either working copy path or URL, but cannot be both URLs.
* If so {@link UnsupportedOperationException} is thrown.
*
* <p/>
* Both <code>targets</code> must represent the same node kind -- that is, if first <code>target</code> is a directory,
* second <code>target</code> must also be, and if first <code>target</code> is a file,
* second <code>target</code> must also be.
*
* </li>
* </ul>
*
* <p/>
* If this operation object uses {@link DefaultSVNDiffGenerator} and there was
* a non-<code>null</code> {@link DefaultSVNDiffGenerator#setBasePath(File) base path} provided to
* it, the original path and modified path will have this base path stripped
* from the front of the respective paths. If the base path is not <null>null</null>
* but is not a parent path of the target, an exception with the {@link SVNErrorCode#BAD_RELATIVE_PATH} error code
* is thrown.
*
* <p/>
* If <code>noDiffDeleted</code> or old {@link ISVNDiffGenerator#isDiffDeleted()} is <code>true</span>, then no diff output will be generated on
* deleted files.
*
* <p/>
* Generated headers are encoded using {@link ISvnDiffGenerator#getEncoding()}.
*
* <p/>
* Diffs output will not be generated for binary files, unless
* {@link ISvnDiffGenerator#isForcedBinaryDiff()} is <code>true</code>, in which case diffs will be shown
* regardless of the content types.
*
* <p/>
* If this operation object uses {@link DefaultSVNDiffGenerator} then a caller
* can set {@link SVNDiffOptions} to it which will be used to pass
* additional options to the diff processes invoked to compare files.
*
* <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/>
* <code>ignoreAncestry</code> controls whether or not items being diffed will
* be checked for relatedness first. Unrelated items are typically
* transmitted to the editor as a deletion of one thing and the addition of
* another, but if this flag is <code>false</code>,
* unrelated items will be diffed as if they were related.
*
* <p/>
* <code>changeLists</code> is a collection of <code>String</code>
* changelist names, used as a restrictive filter on items whose differences
* are reported; that is, doesn't generate diffs about any item unless it's
* a member of one of those changelists. If <code>changeLists</code> is
* empty (or <code>null</code>), no changelist filtering
* occurs.
*
* <p/>
* Note: changelist filtering only applies to diffs in which at least one
* side of the diff represents working copy data.
*
* <p/>
* If both <code>startRevision</code> and <code>endRevision</code> is either {@link SVNRevision#WORKING} or
* {@link SVNRevision#BASE}, then it will be a url-against-wc; otherwise, a
* url-against-url diff.
*
* <p/>
* If <code>startRevision</code> is neither {@link SVNRevision#BASE}, nor
* {@link SVNRevision#WORKING}, nor {@link SVNRevision#COMMITTED}, and if,
* on the contrary, <code>endRevision</code> is one of the aforementioned revisions,
* then a wc-against-url diff is performed; if <code>endRevision</code> also is not
* one of those revision constants, then a url-against-url diff is
* performed. Otherwise it's a url-against-wc diff.
*
* <p/>
* {@link #run()} method throws {@link SVNException} if one of the following is true:
* <ul>
* <li>exception with {@link SVNErrorCode#CLIENT_BAD_REVISION}
* error code - if either of <code>startRevision</code> and <code>endRevision</code>
* is {@link SVNRevision#isValid() invalid}; if both <code>startRevision</code> and <code>endRevision</code> are either
* {@link SVNRevision#WORKING} or {@link SVNRevision#BASE}
* <li>exception with {@link SVNErrorCode#FS_NOT_FOUND} error code -
* <code>target</code> can not be found in either <code>startRevision</code>
* or <code>endRevision</code>
* </ul>
*
* @author TMate Software Ltd.
* @version 1.7
*/
public class SvnDiff extends SvnOperation<Void> {
private ISvnDiffGenerator diffGenerator;
private SVNDiffOptions diffOptions;
private OutputStream output;
private SvnTarget source;
private SvnTarget firstSource;
private SvnTarget secondSource;
private SVNRevision startRevision;
private SVNRevision endRevision;
private boolean ignoreAncestry;
private boolean noDiffDeleted;
private boolean showCopiesAsAdds;
private boolean ignoreContentType;
private File relativeToDirectory;
private boolean useGitDiffFormat;
protected SvnDiff(SvnOperationFactory factory) {
super(factory);
setIgnoreAncestry(true);
}
/**
* 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 void setRelativeToDirectory(File relativeToDirectory) {
this.relativeToDirectory = relativeToDirectory;
}
public File getRelativeToDirectory() {
return relativeToDirectory;
}
/**
* Returns operation's diff generator.
* If not set, {@link DefaultSVNDiffGenerator} is used.
*
* @return diff generator of the operation
*/
public ISvnDiffGenerator getDiffGenerator() {
return diffGenerator;
}
/**
* Sets operation's diff generator of type ISVNDiffGenerator.
* Used for compatibility with 1.6 version.
*
* @param diffGenerator diff generator of the operation of type ISVNDiffGenerator
*/
public void setDiffGenerator(ISVNDiffGenerator diffGenerator) {
if (diffGenerator == null) {
setDiffGenerator((ISvnDiffGenerator) null);
} else if (diffGenerator instanceof SvnNewDiffGenerator) {
setDiffGenerator(((SvnNewDiffGenerator) diffGenerator).getDelegate());
} else {
setDiffGenerator(new SvnOldDiffGenerator(diffGenerator));
}
}
/**
* Sets operation's diff generator.
*
* @param diffGenerator diff generator of the operation
*/
public void setDiffGenerator(ISvnDiffGenerator diffGenerator) {
this.diffGenerator = diffGenerator;
}
/**
* Returns the operation's diff options controlling white-spaces and eol-styles.
*
* @return diff options of the operation
*/
public SVNDiffOptions getDiffOptions() {
return diffOptions;
}
/**
* Sets the operation's diff options controlling white-spaces and eol-styles.
*
* @param diffOptions diff options of the operation
*/
public void setDiffOptions(SVNDiffOptions diffOptions) {
this.diffOptions = diffOptions;
}
/**
* Returns output stream where the differences will be written to.
*
* @return output stream of the diff's result
*/
public OutputStream getOutput() {
return output;
}
/**
* Sets output stream where the differences will be written to.
*
* @param output output stream of the diff's result
*/
public void setOutput(OutputStream output) {
this.output = output;
}
/**
* Returns the paths ancestry should not be noticed while calculating differences.
*
* @return <code>true</code> if the paths ancestry should not be noticed while calculating differences, otherwise <code>false</code>
* @see #setIgnoreAncestry(boolean)
*/
public boolean isIgnoreAncestry() {
return ignoreAncestry;
}
/**
* Sets whether or not items being diffed should
* be checked for relatedness first. Unrelated items are typically
* transmitted to the editor as a deletion of one thing and the addition of
* another, but if this flag is <code>false</code>,
* unrelated items will be diffed as if they were related.
*
* @param ignoreAncestry <code>true</code> if the paths ancestry should not be noticed while calculating differences, otherwise <code>false</code>
*/
public void setIgnoreAncestry(boolean ignoreAncestry) {
this.ignoreAncestry = ignoreAncestry;
}
/**
* Returns whether to generate differences for deleted files.
* In 1.6 version it was {@link ISVNDiffGenerator#isDiffDeleted()}.
*
* @return <code>true</code> if deleted files should not be diffed, otherwise <code>false</code>
*/
public boolean isNoDiffDeleted() {
return noDiffDeleted;
}
/**
* Sets whether to generate differences for deleted files.
* In 1.6 version it was {@link org.tmatesoft.svn.core.wc.ISVNDiffGenerator#setDiffDeleted(boolean)}.
*
* @param noDiffDeleted <code>true</code> if deleted files should not be diffed, otherwise <code>false</code>
*/
public void setNoDiffDeleted(boolean noDiffDeleted) {
this.noDiffDeleted = noDiffDeleted;
}
/**
* Returns whether to report copies and moves as it were adds.
*
* @return <code>true</code> if copies and moves should be reported as adds, otherwise <code>false</code>
* @since 1.7, SVN 1.7
*/
public boolean isShowCopiesAsAdds() {
return showCopiesAsAdds;
}
/**
* Sets whether to report copies and moves as it were adds.
*
* @param showCopiesAsAdds <code>true</code> if copies and moves should be reported as adds, otherwise <code>false</code>
* @since 1.7, SVN 1.7
*/
public void setShowCopiesAsAdds(boolean showCopiesAsAdds) {
this.showCopiesAsAdds = showCopiesAsAdds;
}
public boolean isIgnoreContentType() {
return ignoreContentType;
}
public void setIgnoreContentType(boolean ignoreContentType) {
this.ignoreContentType = ignoreContentType;
}
/**
* Returns whether to report in Git diff format.
*
* @return <code>true</code> if report should be in report in Git diff format, otherwise <code>false</code>
* @since 1.7
*/
public boolean isUseGitDiffFormat() {
return useGitDiffFormat;
}
/**
* Sets whether to report in Git diff format.
*
* @param useGitDiffFormat <code>true</code> if report should be in report in Git diff format, otherwise <code>false</code>
* @since 1.7
*/
public void setUseGitDiffFormat(boolean useGitDiffFormat) {
this.useGitDiffFormat = useGitDiffFormat;
}
@Override
protected int getMinimumTargetsCount() {
return super.getMinimumTargetsCount();
}
@Override
protected int getMaximumTargetsCount() {
return Integer.MAX_VALUE;
}
@Override
protected void ensureArgumentsAreValid() throws SVNException {
if (getRelativeToDirectory() != null && hasRemoteTargets()) {
//TODO
SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.UNKNOWN, "Relative directory cannot be specified with remote targets");
SVNErrorManager.error(err, SVNLogType.CLIENT);
}
if (getOutput() == null) {
//TODO
SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.UNKNOWN, "No output is specified.");
SVNErrorManager.error(err, SVNLogType.CLIENT);
}
}
@Override
protected File getOperationalWorkingCopy() {
if (getSource() != null && getSource().isFile()) {
return getSource().getFile();
} else {
if (getFirstSource() != null && getFirstSource().isFile()) {
return getFirstSource().getFile();
}
if (getSecondSource() != null && getSecondSource().isFile()) {
return getSecondSource().getFile();
}
}
return null;
}
/**
* 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;
}
}