package org.tmatesoft.svn.core.internal.wc2.ng;
import de.regnis.q.sequence.line.diff.QDiffGenerator;
import de.regnis.q.sequence.line.diff.QDiffGeneratorFactory;
import de.regnis.q.sequence.line.diff.QDiffManager;
import de.regnis.q.sequence.line.diff.QDiffUniGenerator;
import org.tmatesoft.svn.core.*;
import org.tmatesoft.svn.core.internal.util.SVNHashMap;
import org.tmatesoft.svn.core.internal.util.SVNMergeInfoUtil;
import org.tmatesoft.svn.core.internal.util.SVNPathUtil;
import org.tmatesoft.svn.core.internal.wc.DefaultSVNOptions;
import org.tmatesoft.svn.core.internal.wc.ISVNReturnValueCallback;
import org.tmatesoft.svn.core.internal.wc.SVNErrorManager;
import org.tmatesoft.svn.core.internal.wc.SVNFileUtil;
import org.tmatesoft.svn.core.wc.ISVNOptions;
import org.tmatesoft.svn.core.wc.SVNDiffOptions;
import org.tmatesoft.svn.core.wc2.SvnTarget;
import org.tmatesoft.svn.util.SVNLogType;
import java.io.*;
import java.util.*;
public class SvnDiffGenerator implements ISvnDiffGenerator {
protected static final String WC_REVISION_LABEL = "(working copy)";
protected static final String PROPERTIES_SEPARATOR = "___________________________________________________________________";
protected static final String HEADER_SEPARATOR = "===================================================================";
protected static final String HEADER_ENCODING = "UTF-8";
private SvnTarget originalTarget1;
private SvnTarget originalTarget2;
private SvnTarget baseTarget;
private SvnTarget relativeToTarget;
private SvnTarget repositoryRoot;
private String encoding;
private byte[] eol;
private boolean useGitFormat;
private boolean forcedBinaryDiff;
private boolean diffDeleted;
private boolean diffAdded;
private List<String> rawDiffOptions;
private boolean forceEmpty;
private Set<String> visitedPaths;
private String externalDiffCommand;
private SVNDiffOptions diffOptions;
private boolean fallbackToAbsolutePath;
private ISVNOptions options;
private boolean propertiesOnly;
private boolean ignoreProperties;
private String getDisplayPath(SvnTarget target) {
String relativePath;
if (baseTarget == null) {
relativePath = null;
} else {
String targetString = target.getPathOrUrlDecodedString();
String baseTargetString = baseTarget.getPathOrUrlDecodedString();
relativePath = getRelativePath(targetString, baseTargetString);
}
return relativePath != null ? relativePath : target.getPathOrUrlString();
}
private String getRelativeToRootPath(SvnTarget target, SvnTarget originalTarget) {
String relativePath;
if (repositoryRoot == null) {
relativePath = null;
} else {
if (repositoryRoot.isFile() == target.isFile()) {
String targetString = target.getPathOrUrlDecodedString();
String baseTargetString = repositoryRoot.getPathOrUrlDecodedString();
relativePath = getRelativePath(targetString, baseTargetString);
} else {
String targetString = target.getPathOrUrlDecodedString();
String baseTargetString = new File("").getAbsolutePath();
relativePath = getRelativePath(targetString, baseTargetString);
}
}
return relativePath != null ? relativePath : target.getPathOrUrlString();
}
private String getRelativePath(String targetString, String baseTargetString) {
if (targetString != null) {
targetString = targetString.replace(File.separatorChar, '/');
}
if (baseTargetString != null) {
baseTargetString = baseTargetString.replace(File.separatorChar, '/');
}
final String pathAsChild = SVNPathUtil.getPathAsChild(baseTargetString, targetString);
if (pathAsChild != null) {
return pathAsChild;
}
if (targetString.equals(baseTargetString)) {
return "";
}
return null;
}
private String getChildPath(String path, String relativeToPath) {
if (relativeToTarget == null) {
return null;
}
String relativePath = getRelativePath(path, relativeToPath);
if (relativePath == null) {
return path;
}
if (relativePath.length() > 0) {
return relativePath;
}
if (relativeToPath.equals(path)) {
return ".";
}
return null;
}
public SvnDiffGenerator() {
this.originalTarget1 = null;
this.originalTarget2 = null;
this.visitedPaths = new HashSet<String>();
this.diffDeleted = true;
this.diffAdded = true;
}
public void setBaseTarget(SvnTarget baseTarget) {
this.baseTarget = baseTarget;
}
public void setUseGitFormat(boolean useGitFormat) {
this.useGitFormat = useGitFormat;
}
public void setOriginalTargets(SvnTarget originalTarget1, SvnTarget originalTarget2) {
this.originalTarget1 = originalTarget1;
this.originalTarget2 = originalTarget2;
}
public void setRelativeToTarget(SvnTarget relativeToTarget) {
this.relativeToTarget = relativeToTarget;
}
public void setAnchors(SvnTarget originalTarget1, SvnTarget originalTarget2) {
//anchors are not used
}
public void setRepositoryRoot(SvnTarget repositoryRoot) {
this.repositoryRoot = repositoryRoot;
}
public void setForceEmpty(boolean forceEmpty) {
this.forceEmpty = forceEmpty;
}
public void setEncoding(String encoding) {
this.encoding = encoding;
}
public String getEncoding() {
return encoding;
}
public String getGlobalEncoding() {
ISVNOptions options = getOptions();
if (options != null && options instanceof DefaultSVNOptions) {
DefaultSVNOptions defaultOptions = (DefaultSVNOptions) options;
return defaultOptions.getGlobalCharset();
}
return null;
}
public void setEOL(byte[] eol) {
this.eol = eol;
}
public byte[] getEOL() {
return eol;
}
public boolean isForcedBinaryDiff() {
return forcedBinaryDiff;
}
public void setForcedBinaryDiff(boolean forcedBinaryDiff) {
this.forcedBinaryDiff = forcedBinaryDiff;
}
public boolean isPropertiesOnly() {
return propertiesOnly;
}
public void setPropertiesOnly(boolean propertiesOnly) {
this.propertiesOnly = propertiesOnly;
}
public boolean isIgnoreProperties() {
return ignoreProperties;
}
public void setIgnoreProperties(boolean ignoreProperties) {
this.ignoreProperties = ignoreProperties;
}
public void displayDeletedDirectory(SvnTarget target, String revision1, String revision2, OutputStream outputStream) throws SVNException {
}
public void displayAddedDirectory(SvnTarget target, String revision1, String revision2, OutputStream outputStream) throws SVNException {
}
public void displayPropsChanged(SvnTarget target, String revision1, String revision2, boolean dirWasAdded, SVNProperties originalProps, SVNProperties propChanges, OutputStream outputStream) throws SVNException {
if (isIgnoreProperties()) {
return;
}
if (dirWasAdded && !isDiffAdded()) {
return;
}
ensureEncodingAndEOLSet();
String displayPath = getDisplayPath(target);
String targetString1 = originalTarget1.getPathOrUrlDecodedString();
String targetString2 = originalTarget2.getPathOrUrlDecodedString();
if (displayPath == null || displayPath.length() == 0) {
displayPath = ".";
}
if (useGitFormat) {
targetString1 = adjustRelativeToReposRoot(targetString1);
targetString2 = adjustRelativeToReposRoot(targetString2);
}
String newTargetString = displayPath;
String newTargetString1 = targetString1;
String newTargetString2 = targetString2;
String commonAncestor = SVNPathUtil.getCommonPathAncestor(newTargetString1, newTargetString2);
int commonLength = commonAncestor == null ? 0 : commonAncestor.length();
newTargetString1 = newTargetString1.substring(commonLength);
newTargetString2 = newTargetString2.substring(commonLength);
newTargetString1 = computeLabel(newTargetString, newTargetString1);
newTargetString2 = computeLabel(newTargetString, newTargetString2);
if (relativeToTarget != null) {
String relativeToPath = relativeToTarget.getPathOrUrlDecodedString();
String absolutePath = target.getPathOrUrlDecodedString();
String childPath = getChildPath(absolutePath, relativeToPath);
if (childPath == null) {
throwBadRelativePathException(absolutePath, relativeToPath);
}
String childPath1 = getChildPath(newTargetString1, relativeToPath);
if (childPath1 == null) {
throwBadRelativePathException(newTargetString1, relativeToPath);
}
String childPath2 = getChildPath(newTargetString2, relativeToPath);
if (childPath2 == null) {
throwBadRelativePathException(newTargetString2, relativeToPath);
}
displayPath = childPath;
newTargetString1 = childPath1;
newTargetString2 = childPath2;
}
boolean showDiffHeader = !visitedPaths.contains(displayPath);
if (showDiffHeader) {
String label1 = getLabel(newTargetString1, revision1);
String label2 = getLabel(newTargetString2, revision2);
boolean shouldStopDisplaying = displayHeader(outputStream, displayPath, false, fallbackToAbsolutePath, SvnDiffCallback.OperationKind.Modified);
visitedPaths.add(displayPath);
if (useGitFormat) {
displayGitDiffHeader(outputStream, SvnDiffCallback.OperationKind.Modified,
getRelativeToRootPath(target, originalTarget1),
getRelativeToRootPath(target, originalTarget2),
null);
}
if (shouldStopDisplaying) {
return;
}
// if (useGitFormat) {
// String copyFromPath = null;
// SvnDiffCallback.OperationKind operationKind = SvnDiffCallback.OperationKind.Modified;
// label1 = getGitDiffLabel1(operationKind, targetString1, targetString2, copyFromPath, revision1);
// label2 = getGitDiffLabel2(operationKind, targetString1, targetString2, copyFromPath, revision2);
// displayGitDiffHeader(outputStream, operationKind,
// getRelativeToRootPath(target, originalTarget1),
// getRelativeToRootPath(target, originalTarget2),
// copyFromPath);
// }
if (useGitFormat) {
displayGitHeaderFields(outputStream, target, revision1, revision2, SvnDiffCallback.OperationKind.Modified, null);
} else {
displayHeaderFields(outputStream, label1, label2);
}
}
displayPropertyChangesOn(useGitFormat ? getRelativeToRootPath(target, originalTarget1) : displayPath, outputStream);
displayPropDiffValues(outputStream, propChanges, originalProps);
}
private void throwBadRelativePathException(String displayPath, String relativeToPath) throws SVNException {
SVNErrorMessage errorMessage = SVNErrorMessage.create(SVNErrorCode.BAD_RELATIVE_PATH, "Path ''{0}'' must be an immediate child of the directory ''{0}''",
displayPath, relativeToPath);
SVNErrorManager.error(errorMessage, SVNLogType.CLIENT);
}
private void displayGitHeaderFields(OutputStream outputStream, SvnTarget target, String revision1, String revision2, SvnDiffCallback.OperationKind operation, String copyFromPath) throws SVNException {
String path1 = copyFromPath != null ? copyFromPath : getRelativeToRootPath(target, originalTarget1);
String path2 = getRelativeToRootPath(target, originalTarget2);
try {
displayString(outputStream, "--- ");
displayFirstGitLabelPath(outputStream, path1, revision1, operation);
displayEOL(outputStream);
displayString(outputStream, "+++ ");
displaySecondGitLabelPath(outputStream, path2, revision2, operation);
displayEOL(outputStream);
} catch (IOException e) {
wrapException(e);
}
}
private String adjustRelativeToReposRoot(String targetString) {
if (repositoryRoot != null) {
String repositoryRootString = repositoryRoot.getPathOrUrlDecodedString();
String relativePath = getRelativePath(targetString, repositoryRootString);
return relativePath == null ? "" : relativePath;
}
return targetString;
}
private String computeLabel(String targetString, String originalTargetString) {
if (originalTargetString.length() == 0) {
return targetString;
} else if (originalTargetString.charAt(0) == '/') {
return targetString + "\t(..." + originalTargetString + ")";
} else {
return targetString + "\t(.../" + originalTargetString + ")";
}
}
public void displayContentChanged(SvnTarget target, File leftFile, File rightFile, String revision1, String revision2, String mimeType1, String mimeType2, SvnDiffCallback.OperationKind operation, File copyFromPath, SVNProperties originalProperties, SVNProperties propChanges, OutputStream outputStream) throws SVNException {
if (isPropertiesOnly()) {
return;
}
ensureEncodingAndEOLSet();
String displayPath = getDisplayPath(target);
String targetString1 = originalTarget1.getPathOrUrlDecodedString();
String targetString2 = originalTarget2.getPathOrUrlDecodedString();
if (useGitFormat) {
targetString1 = adjustRelativeToReposRoot(targetString1);
targetString2 = adjustRelativeToReposRoot(targetString2);
}
String newTargetString = displayPath;
String newTargetString1 = targetString1;
String newTargetString2 = targetString2;
String commonAncestor = SVNPathUtil.getCommonPathAncestor(newTargetString1, newTargetString2);
int commonLength = commonAncestor == null ? 0 : commonAncestor.length();
newTargetString1 = newTargetString1.substring(commonLength);
newTargetString2 = newTargetString2.substring(commonLength);
newTargetString1 = computeLabel(newTargetString, newTargetString1);
newTargetString2 = computeLabel(newTargetString, newTargetString2);
if (relativeToTarget != null) {
String relativeToPath = relativeToTarget.getPathOrUrlDecodedString();
String absolutePath = target.getPathOrUrlDecodedString();
String childPath = getChildPath(absolutePath, relativeToPath);
if (childPath == null) {
throwBadRelativePathException(absolutePath, relativeToPath);
}
String childPath1 = getChildPath(newTargetString1, relativeToPath);
if (childPath1 == null) {
throwBadRelativePathException(newTargetString1, relativeToPath);
}
String childPath2 = getChildPath(newTargetString2, relativeToPath);
if (childPath2 == null) {
throwBadRelativePathException(newTargetString2, relativeToPath);
}
displayPath = childPath;
newTargetString1 = childPath1;
newTargetString2 = childPath2;
}
String label1 = getLabel(newTargetString1, revision1);
String label2 = getLabel(newTargetString2, revision2);
boolean leftIsBinary = false;
boolean rightIsBinary = false;
if (mimeType1 != null) {
leftIsBinary = SVNProperty.isBinaryMimeType(mimeType1);
}
if (mimeType2 != null) {
rightIsBinary = SVNProperty.isBinaryMimeType(mimeType2);
}
if (!forcedBinaryDiff && (leftIsBinary || rightIsBinary)) {
boolean shouldStopDisplaying = displayHeader(outputStream, displayPath, rightFile == null, leftFile == null, operation);
if (useGitFormat) {
displayGitDiffHeader(outputStream, operation,
getRelativeToRootPath(target, originalTarget1),
getRelativeToRootPath(target, originalTarget2),
null);
}
visitedPaths.add(displayPath);
if (shouldStopDisplaying) {
return;
}
displayBinary(mimeType1, mimeType2, outputStream, leftIsBinary, rightIsBinary);
return;
}
final String diffCommand = getExternalDiffCommand();
if (diffCommand != null) {
boolean shouldStopDisplaying = displayHeader(outputStream, displayPath, rightFile == null, leftFile == null, operation);
if (useGitFormat) {
displayGitDiffHeader(outputStream, operation,
getRelativeToRootPath(target, originalTarget1),
getRelativeToRootPath(target, originalTarget2),
null);
}
visitedPaths.add(displayPath);
if (shouldStopDisplaying) {
return;
}
runExternalDiffCommand(outputStream, diffCommand, leftFile, rightFile, label1, label2);
} else {
internalDiff(target, outputStream, displayPath, leftFile, rightFile, label1, label2, operation, copyFromPath == null ? null : copyFromPath.getPath(), revision1, revision2);
}
}
private void displayBinary(String mimeType1, String mimeType2, OutputStream outputStream, boolean leftIsBinary, boolean rightIsBinary) throws SVNException {
displayCannotDisplayFileMarkedBinary(outputStream);
if (leftIsBinary && !rightIsBinary) {
displayMimeType(outputStream, mimeType1);
} else if (!leftIsBinary && rightIsBinary) {
displayMimeType(outputStream, mimeType2);
} else if (leftIsBinary && rightIsBinary) {
if (mimeType1.equals(mimeType2)) {
displayMimeType(outputStream, mimeType1);
} else {
displayMimeTypes(outputStream, mimeType1, mimeType2);
}
}
}
private void internalDiff(SvnTarget target, OutputStream outputStream, String displayPath, File file1, File file2, String label1, String label2, SvnDiffCallback.OperationKind operation, String copyFromPath, String revision1, String revision2) throws SVNException {
String header = getHeaderString(target, displayPath, file2 == null, file1 == null, operation, copyFromPath);
if (file2 == null && !isDiffDeleted()) {
try {
displayString(outputStream, header);
} catch (IOException e) {
wrapException(e);
}
visitedPaths.add(displayPath);
return;
}
if (file1 == null && !isDiffAdded()) {
try {
displayString(outputStream, header);
} catch (IOException e) {
wrapException(e);
}
visitedPaths.add(displayPath);
return;
}
String headerFields = getHeaderFieldsString(target, displayPath, label1, label2, revision1, revision2, operation, copyFromPath);
RandomAccessFile is1 = null;
RandomAccessFile is2 = null;
try {
is1 = file1 == null ? null : SVNFileUtil.openRAFileForReading(file1);
is2 = file2 == null ? null : SVNFileUtil.openRAFileForReading(file2);
QDiffUniGenerator.setup();
Map properties = new SVNHashMap();
properties.put(QDiffGeneratorFactory.IGNORE_EOL_PROPERTY, Boolean.valueOf(getDiffOptions().isIgnoreEOLStyle()));
properties.put(QDiffGeneratorFactory.EOL_PROPERTY, new String(getEOL()));
if (getDiffOptions().isIgnoreAllWhitespace()) {
properties.put(QDiffGeneratorFactory.IGNORE_SPACE_PROPERTY, QDiffGeneratorFactory.IGNORE_ALL_SPACE);
} else if (getDiffOptions().isIgnoreAmountOfWhitespace()) {
properties.put(QDiffGeneratorFactory.IGNORE_SPACE_PROPERTY, QDiffGeneratorFactory.IGNORE_SPACE_CHANGE);
}
final String diffHeader;
if (forceEmpty || useGitFormat) {
displayString(outputStream, header);
diffHeader = headerFields;
visitedPaths.add(displayPath);
} else {
diffHeader = header + headerFields;
}
QDiffGenerator generator = new QDiffUniGenerator(properties, diffHeader);
EmptyDetectionOutputStream emptyDetectionOutputStream = new EmptyDetectionOutputStream(outputStream);
QDiffManager.generateTextDiff(is1, is2, emptyDetectionOutputStream, generator);
if (emptyDetectionOutputStream.isSomethingWritten()) {
visitedPaths.add(displayPath);
}
emptyDetectionOutputStream.flush();
} catch (IOException e) {
SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.IO_ERROR, e.getMessage());
SVNErrorManager.error(err, e, SVNLogType.DEFAULT);
} finally {
SVNFileUtil.closeFile(is1);
SVNFileUtil.closeFile(is2);
}
}
private String getHeaderFieldsString(SvnTarget target, String displayPath, String label1, String label2, String revision1, String revision2, SvnDiffCallback.OperationKind operation, String copyFromPath) throws SVNException {
final ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
try {
if (useGitFormat) {
displayGitHeaderFields(byteArrayOutputStream, target, revision1, revision2, operation, copyFromPath);
} else {
displayHeaderFields(byteArrayOutputStream, label1, label2);
}
} catch (SVNException e) {
SVNFileUtil.closeFile(byteArrayOutputStream);
try {
byteArrayOutputStream.writeTo(byteArrayOutputStream);
} catch (IOException e1) {
}
throw e;
}
try {
byteArrayOutputStream.close();
return byteArrayOutputStream.toString(HEADER_ENCODING);
} catch (IOException e) {
return "";
}
}
private String getHeaderString(SvnTarget target, String displayPath, boolean deleted, boolean added, SvnDiffCallback.OperationKind operation, String copyFromPath) throws SVNException {
final ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
try {
boolean stopDisplaying = displayHeader(byteArrayOutputStream, displayPath, deleted, added, operation);
if (useGitFormat) {
displayGitDiffHeader(byteArrayOutputStream, operation,
getRelativeToRootPath(target, originalTarget1),
getRelativeToRootPath(target, originalTarget2),
copyFromPath);
}
} catch (SVNException e) {
SVNFileUtil.closeFile(byteArrayOutputStream);
try {
byteArrayOutputStream.writeTo(byteArrayOutputStream);
} catch (IOException e1) {
}
throw e;
}
try {
byteArrayOutputStream.close();
return byteArrayOutputStream.toString(HEADER_ENCODING);
} catch (IOException e) {
return "";
}
}
private void runExternalDiffCommand(OutputStream outputStream, final String diffCommand, File file1, File file2, String label1, String label2) throws SVNException {
final List<String> args = new ArrayList<String>();
args.add(diffCommand);
if (rawDiffOptions != null) {
args.addAll(rawDiffOptions);
} else {
Collection svnDiffOptionsCollection = getDiffOptions().toOptionsCollection();
args.addAll(svnDiffOptionsCollection);
args.add("-u");
}
if (label1 != null) {
args.add("-L");
args.add(label1);
}
if (label2 != null) {
args.add("-L");
args.add(label2);
}
boolean tmpFile1 = false;
boolean tmpFile2 = false;
if (file1 == null) {
file1 = SVNFileUtil.createTempFile("svn.", ".tmp");
tmpFile1 = true;
}
if (file2 == null) {
file2 = SVNFileUtil.createTempFile("svn.", ".tmp");
tmpFile2 = true;
}
String file1Path = file1.getAbsolutePath().replace(File.separatorChar, '/');
String file2Path = file2.getAbsolutePath().replace(File.separatorChar, '/');
args.add(file1Path);
args.add(file2Path);
try {
final Writer writer = new OutputStreamWriter(outputStream, getEncoding());
SVNFileUtil.execCommand(args.toArray(new String[args.size()]), true,
new ISVNReturnValueCallback() {
public void handleReturnValue(int returnValue) throws SVNException {
if (returnValue != 0 && returnValue != 1) {
SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.EXTERNAL_PROGRAM,
"''{0}'' returned {1}", new Object[] { diffCommand, String.valueOf(returnValue) });
SVNErrorManager.error(err, SVNLogType.DEFAULT);
}
}
public void handleChar(char ch) throws SVNException {
try {
writer.write(ch);
} catch (IOException ioe) {
SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.IO_ERROR, ioe.getMessage());
SVNErrorManager.error(err, ioe, SVNLogType.DEFAULT);
}
}
public boolean isHandleProgramOutput() {
return true;
}
});
writer.flush();
} catch (IOException ioe) {
SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.IO_ERROR, ioe.getMessage());
SVNErrorManager.error(err, ioe, SVNLogType.DEFAULT);
} finally {
try {
if (tmpFile1) {
SVNFileUtil.deleteFile(file1);
}
if (tmpFile2) {
SVNFileUtil.deleteFile(file2);
}
} catch (SVNException e) {
// skip
}
}
}
private String getExternalDiffCommand() {
return externalDiffCommand;
}
private void displayMimeType(OutputStream outputStream, String mimeType) throws SVNException {
try {
displayString(outputStream, SVNProperty.MIME_TYPE);
displayString(outputStream, " = ");
displayString(outputStream, mimeType);
displayEOL(outputStream);
} catch (IOException e) {
wrapException(e);
}
}
private void displayMimeTypes(OutputStream outputStream, String mimeType1, String mimeType2) throws SVNException {
try {
displayString(outputStream, SVNProperty.MIME_TYPE);
displayString(outputStream, " = (");
displayString(outputStream, mimeType1);
displayString(outputStream, ", ");
displayString(outputStream, mimeType2);
displayString(outputStream, ")");
displayEOL(outputStream);
} catch (IOException e) {
wrapException(e);
}
}
private void displayCannotDisplayFileMarkedBinary(OutputStream outputStream) throws SVNException {
try {
displayString(outputStream, "Cannot display: file marked as a binary type.");
displayEOL(outputStream);
} catch (IOException e) {
wrapException(e);
}
}
private void ensureEncodingAndEOLSet() {
if (getEOL() == null) {
setEOL(SVNProperty.EOL_LF_BYTES);
}
if (getEncoding() == null) {
final ISVNOptions options = getOptions();
if (options != null && options.getNativeCharset() != null) {
setEncoding(options.getNativeCharset());
} else {
setEncoding("UTF-8");
}
}
}
private void displayPropDiffValues(OutputStream outputStream, SVNProperties diff, SVNProperties baseProps) throws SVNException {
for (Iterator changedPropNames = diff.nameSet().iterator(); changedPropNames.hasNext();) {
String name = (String) changedPropNames.next();
SVNPropertyValue originalValue = baseProps != null ? baseProps.getSVNPropertyValue(name) : null;
SVNPropertyValue newValue = diff.getSVNPropertyValue(name);
String headerFormat = null;
if (originalValue == null) {
headerFormat = "Added: ";
} else if (newValue == null) {
headerFormat = "Deleted: ";
} else {
headerFormat = "Modified: ";
}
try {
displayString(outputStream, (headerFormat + name));
displayEOL(outputStream);
if (SVNProperty.MERGE_INFO.equals(name)) {
displayMergeInfoDiff(outputStream, originalValue == null ? null : originalValue.getString(), newValue == null ? null : newValue.getString());
continue;
}
byte[] originalValueBytes = getPropertyAsBytes(originalValue, getEncoding());
byte[] newValueBytes = getPropertyAsBytes(newValue, getEncoding());
if (originalValueBytes == null) {
originalValueBytes = new byte[0];
} else {
originalValueBytes = maybeAppendEOL(originalValueBytes);
}
boolean newValueHadEol = newValueBytes != null && newValueBytes.length > 0 &&
(newValueBytes[newValueBytes.length - 1] == SVNProperty.EOL_CR_BYTES[0] ||
newValueBytes[newValueBytes.length - 1] == SVNProperty.EOL_LF_BYTES[0]);
if (newValueBytes == null) {
newValueBytes = new byte[0];
} else {
newValueBytes = maybeAppendEOL(newValueBytes);
}
QDiffUniGenerator.setup();
Map properties = new SVNHashMap();
properties.put(QDiffGeneratorFactory.IGNORE_EOL_PROPERTY, Boolean.valueOf(getDiffOptions().isIgnoreEOLStyle()));
properties.put(QDiffGeneratorFactory.EOL_PROPERTY, new String(getEOL()));
properties.put(QDiffGeneratorFactory.HUNK_DELIMITER, "##");
if (getDiffOptions().isIgnoreAllWhitespace()) {
properties.put(QDiffGeneratorFactory.IGNORE_SPACE_PROPERTY, QDiffGeneratorFactory.IGNORE_ALL_SPACE);
} else if (getDiffOptions().isIgnoreAmountOfWhitespace()) {
properties.put(QDiffGeneratorFactory.IGNORE_SPACE_PROPERTY, QDiffGeneratorFactory.IGNORE_SPACE_CHANGE);
}
QDiffGenerator generator = new QDiffUniGenerator(properties, "");
Writer writer = new OutputStreamWriter(outputStream, getEncoding());
QDiffManager.generateTextDiff(new ByteArrayInputStream(originalValueBytes), new ByteArrayInputStream(newValueBytes),
null, writer, generator);
writer.flush();
if (!newValueHadEol) {
displayString(outputStream, "\\ No newline at end of property");
displayEOL(outputStream);
}
} catch (IOException e) {
wrapException(e);
}
}
}
private byte[] maybeAppendEOL(byte[] buffer) {
if (buffer.length == 0) {
return buffer;
}
byte lastByte = buffer[buffer.length - 1];
if (lastByte == SVNProperty.EOL_CR_BYTES[0]) {
return buffer;
} else if (lastByte != SVNProperty.EOL_LF_BYTES[0]) {
final byte[] newBuffer = new byte[buffer.length + getEOL().length];
System.arraycopy(buffer, 0, newBuffer, 0, buffer.length);
System.arraycopy(getEOL(), 0, newBuffer, buffer.length, getEOL().length);
return newBuffer;
} else {
return buffer;
}
}
private String getGitDiffLabel1(SvnDiffCallback.OperationKind operationKind, String path1, String path2, String copyFromPath, String revision) {
if (operationKind == SvnDiffCallback.OperationKind.Deleted) {
return getLabel("a/" + path1, revision);
} else if (operationKind == SvnDiffCallback.OperationKind.Copied) {
return getLabel("a/" + copyFromPath, revision);
} else if (operationKind == SvnDiffCallback.OperationKind.Added) {
return getLabel("/dev/null", revision);
} else if (operationKind == SvnDiffCallback.OperationKind.Modified) {
return getLabel("a/" + path1, revision);
} else if (operationKind == SvnDiffCallback.OperationKind.Moved) {
return getLabel("a/" + copyFromPath, revision);
}
throw new IllegalArgumentException("Unsupported operation: " + operationKind);
}
private String getGitDiffLabel2(SvnDiffCallback.OperationKind operationKind, String path1, String path2, String copyFromPath, String revision) {
if (operationKind == SvnDiffCallback.OperationKind.Deleted) {
return getLabel("/dev/null", revision);
} else if (operationKind == SvnDiffCallback.OperationKind.Copied) {
return getLabel("b/" + path2, revision);
} else if (operationKind == SvnDiffCallback.OperationKind.Added) {
return getLabel("b/" + path2, revision);
} else if (operationKind == SvnDiffCallback.OperationKind.Modified) {
return getLabel("b/" + path2, revision);
} else if (operationKind == SvnDiffCallback.OperationKind.Moved) {
return getLabel("b/" + path2, revision);
}
throw new IllegalArgumentException("Unsupported operation: " + operationKind);
}
private void displayGitDiffHeader(OutputStream outputStream, SvnDiffCallback.OperationKind operationKind, String path1, String path2, String copyFromPath) throws SVNException {
if (operationKind == SvnDiffCallback.OperationKind.Deleted) {
displayGitDiffHeaderDeleted(outputStream, path1, path2, copyFromPath);
} else if (operationKind == SvnDiffCallback.OperationKind.Copied) {
displayGitDiffHeaderCopied(outputStream, path1, path2, copyFromPath);
} else if (operationKind == SvnDiffCallback.OperationKind.Added) {
displayGitDiffHeaderAdded(outputStream, path1, path2, copyFromPath);
} else if (operationKind == SvnDiffCallback.OperationKind.Modified) {
displayGitDiffHeaderModified(outputStream, path1, path2, copyFromPath);
} else if (operationKind == SvnDiffCallback.OperationKind.Moved) {
displayGitDiffHeaderRenamed(outputStream, path1, path2, copyFromPath);
}
}
private void displayGitDiffHeaderAdded(OutputStream outputStream, String path1, String path2, String copyFromPath) throws SVNException {
try {
displayString(outputStream, "diff --git ");
displayFirstGitPath(outputStream, path1);
displayString(outputStream, " ");
displaySecondGitPath(outputStream, path2);
displayEOL(outputStream);
displayString(outputStream, "new file mode 10644");
displayEOL(outputStream);
} catch (IOException e) {
wrapException(e);
}
}
private void displayGitDiffHeaderDeleted(OutputStream outputStream, String path1, String path2, String copyFromPath) throws SVNException {
try {
displayString(outputStream, "diff --git ");
displayFirstGitPath(outputStream, path1);
displayString(outputStream, " ");
displaySecondGitPath(outputStream, path2);
displayEOL(outputStream);
displayString(outputStream, "deleted file mode 10644");
displayEOL(outputStream);
} catch (IOException e) {
wrapException(e);
}
}
private void displayGitDiffHeaderCopied(OutputStream outputStream, String path1, String path2, String copyFromPath) throws SVNException {
try {
displayString(outputStream, "diff --git ");
displayFirstGitPath(outputStream, copyFromPath);
displayString(outputStream, " ");
displaySecondGitPath(outputStream, path2);
displayEOL(outputStream);
displayString(outputStream, "copy from ");
displayString(outputStream, copyFromPath);
displayEOL(outputStream);
displayString(outputStream, "copy to ");
displayString(outputStream, path2);
displayEOL(outputStream);
} catch (IOException e) {
wrapException(e);
}
}
private void displayGitDiffHeaderRenamed(OutputStream outputStream, String path1, String path2, String copyFromPath) throws SVNException {
try {
displayString(outputStream, "diff --git ");
displayFirstGitPath(outputStream, copyFromPath);
displayString(outputStream, " ");
displaySecondGitPath(outputStream, path2);
displayEOL(outputStream);
displayString(outputStream, "rename from ");
displayString(outputStream, copyFromPath);
displayEOL(outputStream);
displayString(outputStream, "rename to ");
displayString(outputStream, path2);
displayEOL(outputStream);
} catch (IOException e) {
wrapException(e);
}
}
private void displayGitDiffHeaderModified(OutputStream outputStream, String path1, String path2, String copyFromPath) throws SVNException {
try {
displayString(outputStream, "diff --git ");
displayFirstGitPath(outputStream, path1);
displayString(outputStream, " ");
displaySecondGitPath(outputStream, path2);
displayEOL(outputStream);
} catch (IOException e) {
wrapException(e);
}
}
private void displayFirstGitPath(OutputStream outputStream, String path1) throws IOException {
displayGitPath(outputStream, path1, "a/", false);
}
private void displaySecondGitPath(OutputStream outputStream, String path2) throws IOException {
displayGitPath(outputStream, path2, "b/", false);
}
private void displayFirstGitLabelPath(OutputStream outputStream, String path1, String revision1, SvnDiffCallback.OperationKind operation) throws IOException {
String pathPrefix = "a/";
if (operation == SvnDiffCallback.OperationKind.Added) {
path1 = "/dev/null";
pathPrefix = "";
}
displayGitPath(outputStream, getLabel(path1, revision1), pathPrefix, true);
}
private void displaySecondGitLabelPath(OutputStream outputStream, String path2, String revision2, SvnDiffCallback.OperationKind operation) throws IOException {
String pathPrefix = "b/";
if (operation == SvnDiffCallback.OperationKind.Deleted) {
path2 = "/dev/null";
pathPrefix = "";
}
displayGitPath(outputStream, getLabel(path2, revision2), pathPrefix, true);
}
private void displayGitPath(OutputStream outputStream, String path1, String pathPrefix, boolean label) throws IOException {
// if (!label && path1.length() == 0) {
// displayString(outputStream, ".");
// } else {
displayString(outputStream, pathPrefix);
displayString(outputStream, path1);
// }
}
private String getAdjustedPathWithLabel(String displayPath, String path, String revision, String commonAncestor) {
String adjustedPath = getAdjustedPath(displayPath, path, commonAncestor);
return getLabel(adjustedPath, revision);
}
private String getAdjustedPath(String displayPath, String path1, String commonAncestor) {
String adjustedPath = getRelativePath(path1, commonAncestor);
if (adjustedPath == null || adjustedPath.length() == 0) {
adjustedPath = displayPath;
} else if (adjustedPath.charAt(0) == '/') {
adjustedPath = displayPath + "\t(..." + adjustedPath + ")";
} else {
adjustedPath = displayPath + "\t(.../" + adjustedPath + ")";
}
return adjustedPath;
//TODO: respect relativeToDir
}
protected String getLabel(String path, String revToken) {
revToken = revToken == null ? WC_REVISION_LABEL : revToken;
return path + "\t" + revToken;
}
protected boolean displayHeader(OutputStream os, String path, boolean deleted, boolean added, SvnDiffCallback.OperationKind operation) throws SVNException {
try {
if (deleted && !isDiffDeleted()) {
displayString(os, "Index: ");
displayString(os, path);
displayString(os, " (deleted)");
displayEOL(os);
displayString(os, HEADER_SEPARATOR);
displayEOL(os);
return true;
}
if (added && !isDiffAdded()) {
displayString(os, "Index: ");
displayString(os, path);
displayString(os, " (added)");
displayEOL(os);
displayString(os, HEADER_SEPARATOR);
displayEOL(os);
return true;
}
displayString(os, "Index: ");
displayString(os, path);
displayEOL(os);
displayString(os, HEADER_SEPARATOR);
displayEOL(os);
return false;
} catch (IOException e) {
wrapException(e);
}
return false;
}
protected void displayHeaderFields(OutputStream os, String label1, String label2) throws SVNException {
try {
displayString(os, "--- ");
displayString(os, label1);
displayEOL(os);
displayString(os, "+++ ");
displayString(os, label2);
displayEOL(os);
} catch (IOException e) {
wrapException(e);
}
}
private void displayPropertyChangesOn(String path, OutputStream outputStream) throws SVNException {
try {
displayEOL(outputStream);
displayString(outputStream, ("Property changes on: " + (useLocalFileSeparatorChar() ? path.replace('/', File.separatorChar) : path)));
displayEOL(outputStream);
displayString(outputStream, PROPERTIES_SEPARATOR);
displayEOL(outputStream);
} catch (IOException e) {
wrapException(e);
}
}
private byte[] getPropertyAsBytes(SVNPropertyValue value, String encoding){
if (value == null){
return null;
}
if (value.isString()){
try {
return value.getString().getBytes(encoding);
} catch (UnsupportedEncodingException e) {
return value.getString().getBytes();
}
}
return value.getBytes();
}
private void displayMergeInfoDiff(OutputStream outputStream, String oldValue, String newValue) throws SVNException, IOException {
Map oldMergeInfo = null;
Map newMergeInfo = null;
if (oldValue != null) {
oldMergeInfo = SVNMergeInfoUtil.parseMergeInfo(new StringBuffer(oldValue), null);
}
if (newValue != null) {
newMergeInfo = SVNMergeInfoUtil.parseMergeInfo(new StringBuffer(newValue), null);
}
Map deleted = new TreeMap();
Map added = new TreeMap();
SVNMergeInfoUtil.diffMergeInfo(deleted, added, oldMergeInfo, newMergeInfo, true);
for (Iterator paths = deleted.keySet().iterator(); paths.hasNext();) {
String path = (String) paths.next();
SVNMergeRangeList rangeList = (SVNMergeRangeList) deleted.get(path);
displayString(outputStream, (" Reverse-merged " + path + ":r"));
displayString(outputStream, rangeList.toString());
displayEOL(outputStream);
}
for (Iterator paths = added.keySet().iterator(); paths.hasNext();) {
String path = (String) paths.next();
SVNMergeRangeList rangeList = (SVNMergeRangeList) added.get(path);
displayString(outputStream, (" Merged " + path + ":r"));
displayString(outputStream, rangeList.toString());
displayEOL(outputStream);
}
}
private boolean useLocalFileSeparatorChar() {
return true;
}
public boolean isDiffDeleted() {
return diffDeleted;
}
public boolean isDiffAdded() {
return diffAdded;
}
private void wrapException(IOException e) throws SVNException {
SVNErrorMessage errorMessage = SVNErrorMessage.create(SVNErrorCode.UNKNOWN, e);
SVNErrorManager.error(errorMessage, e, SVNLogType.WC);
}
private void displayString(OutputStream outputStream, String s) throws IOException {
outputStream.write(s.getBytes(HEADER_ENCODING));
}
private void displayEOL(OutputStream os) throws IOException {
os.write(getEOL());
}
public SVNDiffOptions getDiffOptions() {
if (diffOptions == null) {
diffOptions = new SVNDiffOptions();
}
return diffOptions;
}
public void setExternalDiffCommand(String externalDiffCommand) {
this.externalDiffCommand = externalDiffCommand;
}
public void setRawDiffOptions(List<String> rawDiffOptions) {
this.rawDiffOptions = rawDiffOptions;
}
public void setDiffOptions(SVNDiffOptions diffOptions) {
this.diffOptions = diffOptions;
}
public void setDiffDeleted(boolean diffDeleted) {
this.diffDeleted = diffDeleted;
}
public void setDiffAdded(boolean diffAdded) {
this.diffAdded = diffAdded;
}
public void setBasePath(File absoluteFile) {
setBaseTarget(SvnTarget.fromFile(absoluteFile));
}
public void setFallbackToAbsolutePath(boolean fallbackToAbsolutePath) {
this.fallbackToAbsolutePath = fallbackToAbsolutePath;
}
public void setOptions(ISVNOptions options) {
this.options = options;
}
public ISVNOptions getOptions() {
return options;
}
private class EmptyDetectionOutputStream extends OutputStream {
private final OutputStream outputStream;
private boolean somethingWritten;
public EmptyDetectionOutputStream(OutputStream outputStream) {
this.outputStream = outputStream;
this.somethingWritten = false;
}
public boolean isSomethingWritten() {
return somethingWritten;
}
@Override
public void write(int c) throws IOException {
somethingWritten = true;
outputStream.write(c);
}
@Override
public void write(byte[] bytes) throws IOException {
somethingWritten = bytes.length > 0;
outputStream.write(bytes);
}
@Override
public void write(byte[] bytes, int offset, int length) throws IOException {
somethingWritten = length > 0;
outputStream.write(bytes, offset, length);
}
@Override
public void flush() throws IOException {
outputStream.flush();
}
@Override
public void close() throws IOException {
outputStream.close();
}
}
}