|
|
|
@ -66,13 +66,40 @@
|
|
|
|
|
public class DiffFormatter {
|
|
|
|
|
private static final byte[] noNewLine = encodeASCII("\\ No newline at end of file\n");
|
|
|
|
|
|
|
|
|
|
private final OutputStream out;
|
|
|
|
|
|
|
|
|
|
private Repository db;
|
|
|
|
|
|
|
|
|
|
private int context;
|
|
|
|
|
|
|
|
|
|
/** Create a new formatter with a default level of context. */
|
|
|
|
|
public DiffFormatter() {
|
|
|
|
|
/**
|
|
|
|
|
* Create a new formatter with a default level of context.
|
|
|
|
|
*
|
|
|
|
|
* @param out
|
|
|
|
|
* the stream the formatter will write line data to. This stream
|
|
|
|
|
* should have buffering arranged by the caller, as many small
|
|
|
|
|
* writes are performed to it.
|
|
|
|
|
*/
|
|
|
|
|
public DiffFormatter(OutputStream out) {
|
|
|
|
|
this.out = out;
|
|
|
|
|
setContext(3);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/** @return the stream we are outputting data to. */
|
|
|
|
|
protected OutputStream getOutputStream() {
|
|
|
|
|
return out;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Set the repository the formatter can load object contents from.
|
|
|
|
|
*
|
|
|
|
|
* @param repository
|
|
|
|
|
* source repository holding referenced objects.
|
|
|
|
|
*/
|
|
|
|
|
public void setRepository(Repository repository) {
|
|
|
|
|
db = repository;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Change the number of lines of context to display.
|
|
|
|
|
*
|
|
|
|
@ -83,45 +110,61 @@ public DiffFormatter() {
|
|
|
|
|
*/
|
|
|
|
|
public void setContext(final int lineCount) {
|
|
|
|
|
if (lineCount < 0)
|
|
|
|
|
throw new IllegalArgumentException(JGitText.get().contextMustBeNonNegative);
|
|
|
|
|
throw new IllegalArgumentException(
|
|
|
|
|
JGitText.get().contextMustBeNonNegative);
|
|
|
|
|
context = lineCount;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Flush the underlying output stream of this formatter.
|
|
|
|
|
*
|
|
|
|
|
* @throws IOException
|
|
|
|
|
* the stream's own flush method threw an exception.
|
|
|
|
|
*/
|
|
|
|
|
public void flush() throws IOException {
|
|
|
|
|
out.flush();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Format a patch script from a list of difference entries.
|
|
|
|
|
*
|
|
|
|
|
* @param out
|
|
|
|
|
* stream to write the patch script out to.
|
|
|
|
|
* @param src
|
|
|
|
|
* repository the file contents can be read from.
|
|
|
|
|
* @param entries
|
|
|
|
|
* entries describing the affected files.
|
|
|
|
|
* @throws IOException
|
|
|
|
|
* a file's content cannot be read, or the output stream cannot
|
|
|
|
|
* be written to.
|
|
|
|
|
*/
|
|
|
|
|
public void format(final OutputStream out, Repository src,
|
|
|
|
|
List<? extends DiffEntry> entries) throws IOException {
|
|
|
|
|
for(DiffEntry ent : entries) {
|
|
|
|
|
if (ent instanceof FileHeader) {
|
|
|
|
|
format(
|
|
|
|
|
out,
|
|
|
|
|
(FileHeader) ent, //
|
|
|
|
|
newRawText(open(src, ent.getOldMode(), ent.getOldId())),
|
|
|
|
|
newRawText(open(src, ent.getNewMode(), ent.getNewId())));
|
|
|
|
|
} else {
|
|
|
|
|
format(out, src, ent);
|
|
|
|
|
}
|
|
|
|
|
public void format(List<? extends DiffEntry> entries) throws IOException {
|
|
|
|
|
for (DiffEntry ent : entries)
|
|
|
|
|
format(ent);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Format a patch script for one file entry.
|
|
|
|
|
*
|
|
|
|
|
* @param entry
|
|
|
|
|
* the entry to be formatted.
|
|
|
|
|
* @throws IOException
|
|
|
|
|
* a file's content cannot be read, or the output stream cannot
|
|
|
|
|
* be written to.
|
|
|
|
|
*/
|
|
|
|
|
public void format(DiffEntry entry) throws IOException {
|
|
|
|
|
if (entry instanceof FileHeader) {
|
|
|
|
|
format(
|
|
|
|
|
(FileHeader) entry, //
|
|
|
|
|
newRawText(open(entry.getOldMode(), entry.getOldId())),
|
|
|
|
|
newRawText(open(entry.getNewMode(), entry.getNewId())));
|
|
|
|
|
} else {
|
|
|
|
|
formatAndDiff(entry);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private void format(OutputStream out, Repository src, DiffEntry ent)
|
|
|
|
|
throws IOException {
|
|
|
|
|
private void formatAndDiff(DiffEntry ent) throws IOException {
|
|
|
|
|
String oldName = quotePath("a/" + ent.getOldName());
|
|
|
|
|
String newName = quotePath("b/" + ent.getNewName());
|
|
|
|
|
out.write(encode("diff --git " + oldName + " " + newName + "\n"));
|
|
|
|
|
|
|
|
|
|
switch(ent.getChangeType()) {
|
|
|
|
|
switch (ent.getChangeType()) {
|
|
|
|
|
case ADD:
|
|
|
|
|
out.write(encodeASCII("new file mode "));
|
|
|
|
|
ent.getNewMode().copyTo(out);
|
|
|
|
@ -135,7 +178,7 @@ private void format(OutputStream out, Repository src, DiffEntry ent)
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
case RENAME:
|
|
|
|
|
out.write(encode("similarity index " + ent.getScore() + "%"));
|
|
|
|
|
out.write(encodeASCII("similarity index " + ent.getScore() + "%"));
|
|
|
|
|
out.write('\n');
|
|
|
|
|
|
|
|
|
|
out.write(encode("rename from " + quotePath(ent.getOldName())));
|
|
|
|
@ -146,7 +189,7 @@ private void format(OutputStream out, Repository src, DiffEntry ent)
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
case COPY:
|
|
|
|
|
out.write(encode("similarity index " + ent.getScore() + "%"));
|
|
|
|
|
out.write(encodeASCII("similarity index " + ent.getScore() + "%"));
|
|
|
|
|
out.write('\n');
|
|
|
|
|
|
|
|
|
|
out.write(encode("copy from " + quotePath(ent.getOldName())));
|
|
|
|
@ -178,9 +221,9 @@ private void format(OutputStream out, Repository src, DiffEntry ent)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
out.write(encodeASCII("index " //
|
|
|
|
|
+ format(src, ent.getOldId()) //
|
|
|
|
|
+ format(ent.getOldId()) //
|
|
|
|
|
+ ".." //
|
|
|
|
|
+ format(src, ent.getNewId())));
|
|
|
|
|
+ format(ent.getNewId())));
|
|
|
|
|
if (ent.getOldMode().equals(ent.getNewMode())) {
|
|
|
|
|
out.write(' ');
|
|
|
|
|
ent.getNewMode().copyTo(out);
|
|
|
|
@ -189,8 +232,8 @@ private void format(OutputStream out, Repository src, DiffEntry ent)
|
|
|
|
|
out.write(encode("--- " + oldName + '\n'));
|
|
|
|
|
out.write(encode("+++ " + newName + '\n'));
|
|
|
|
|
|
|
|
|
|
byte[] aRaw = open(src, ent.getOldMode(), ent.getOldId());
|
|
|
|
|
byte[] bRaw = open(src, ent.getNewMode(), ent.getNewId());
|
|
|
|
|
byte[] aRaw = open(ent.getOldMode(), ent.getOldId());
|
|
|
|
|
byte[] bRaw = open(ent.getNewMode(), ent.getNewId());
|
|
|
|
|
|
|
|
|
|
if (RawText.isBinary(aRaw) || RawText.isBinary(bRaw)) {
|
|
|
|
|
out.write(encodeASCII("Binary files differ\n"));
|
|
|
|
@ -198,7 +241,7 @@ private void format(OutputStream out, Repository src, DiffEntry ent)
|
|
|
|
|
} else {
|
|
|
|
|
RawText a = newRawText(aRaw);
|
|
|
|
|
RawText b = newRawText(bRaw);
|
|
|
|
|
formatEdits(out, a, b, new MyersDiff(a, b).getEdits());
|
|
|
|
|
formatEdits(a, b, new MyersDiff(a, b).getEdits());
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
@ -213,8 +256,8 @@ protected RawText newRawText(byte[] content) {
|
|
|
|
|
return new RawText(content);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private String format(Repository db, AbbreviatedObjectId oldId) {
|
|
|
|
|
if (oldId.isComplete())
|
|
|
|
|
private String format(AbbreviatedObjectId oldId) {
|
|
|
|
|
if (oldId.isComplete() && db != null)
|
|
|
|
|
oldId = oldId.toObjectId().abbreviate(db, 8);
|
|
|
|
|
return oldId.name();
|
|
|
|
|
}
|
|
|
|
@ -224,7 +267,7 @@ private static String quotePath(String name) {
|
|
|
|
|
return ('"' + name + '"').equals(q) ? name : q;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private byte[] open(Repository src, FileMode mode, AbbreviatedObjectId id)
|
|
|
|
|
private byte[] open(FileMode mode, AbbreviatedObjectId id)
|
|
|
|
|
throws IOException {
|
|
|
|
|
if (mode == FileMode.MISSING)
|
|
|
|
|
return new byte[] {};
|
|
|
|
@ -232,8 +275,10 @@ private byte[] open(Repository src, FileMode mode, AbbreviatedObjectId id)
|
|
|
|
|
if (mode.getObjectType() != Constants.OBJ_BLOB)
|
|
|
|
|
return new byte[] {};
|
|
|
|
|
|
|
|
|
|
if (db == null)
|
|
|
|
|
throw new IllegalStateException(JGitText.get().repositoryIsRequired);
|
|
|
|
|
if (id.isComplete()) {
|
|
|
|
|
ObjectLoader ldr = src.openObject(id.toObjectId());
|
|
|
|
|
ObjectLoader ldr = db.openObject(id.toObjectId());
|
|
|
|
|
return ldr.getCachedBytes();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
@ -247,8 +292,6 @@ private byte[] open(Repository src, FileMode mode, AbbreviatedObjectId id)
|
|
|
|
|
* to increase or reduce the number of lines of context within the script.
|
|
|
|
|
* All header lines are reused as-is from the supplied FileHeader.
|
|
|
|
|
*
|
|
|
|
|
* @param out
|
|
|
|
|
* stream to write the patch script out to.
|
|
|
|
|
* @param head
|
|
|
|
|
* existing file header containing the header lines to copy.
|
|
|
|
|
* @param a
|
|
|
|
@ -260,8 +303,8 @@ private byte[] open(Repository src, FileMode mode, AbbreviatedObjectId id)
|
|
|
|
|
* @throws IOException
|
|
|
|
|
* writing to the supplied stream failed.
|
|
|
|
|
*/
|
|
|
|
|
public void format(final OutputStream out, final FileHeader head,
|
|
|
|
|
final RawText a, final RawText b) throws IOException {
|
|
|
|
|
public void format(final FileHeader head, final RawText a, final RawText b)
|
|
|
|
|
throws IOException {
|
|
|
|
|
// Reuse the existing FileHeader as-is by blindly copying its
|
|
|
|
|
// header lines, but avoiding its hunks. Instead we recreate
|
|
|
|
|
// the hunks from the text instances we have been supplied.
|
|
|
|
@ -272,19 +315,22 @@ public void format(final OutputStream out, final FileHeader head,
|
|
|
|
|
end = head.getHunks().get(0).getStartOffset();
|
|
|
|
|
out.write(head.getBuffer(), start, end - start);
|
|
|
|
|
|
|
|
|
|
formatEdits(out, a, b, head.toEditList());
|
|
|
|
|
formatEdits(a, b, head.toEditList());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Formats a list of edits in unified diff format
|
|
|
|
|
* @param out where the unified diff is written to
|
|
|
|
|
* @param a the text A which was compared
|
|
|
|
|
* @param b the text B which was compared
|
|
|
|
|
* @param edits some differences which have been calculated between A and B
|
|
|
|
|
*
|
|
|
|
|
* @param a
|
|
|
|
|
* the text A which was compared
|
|
|
|
|
* @param b
|
|
|
|
|
* the text B which was compared
|
|
|
|
|
* @param edits
|
|
|
|
|
* some differences which have been calculated between A and B
|
|
|
|
|
* @throws IOException
|
|
|
|
|
*/
|
|
|
|
|
public void formatEdits(final OutputStream out, final RawText a,
|
|
|
|
|
final RawText b, final EditList edits) throws IOException {
|
|
|
|
|
public void formatEdits(final RawText a, final RawText b,
|
|
|
|
|
final EditList edits) throws IOException {
|
|
|
|
|
for (int curIdx = 0; curIdx < edits.size();) {
|
|
|
|
|
Edit curEdit = edits.get(curIdx);
|
|
|
|
|
final int endIdx = findCombinedEnd(edits, curIdx);
|
|
|
|
@ -295,18 +341,24 @@ public void formatEdits(final OutputStream out, final RawText a,
|
|
|
|
|
final int aEnd = Math.min(a.size(), endEdit.getEndA() + context);
|
|
|
|
|
final int bEnd = Math.min(b.size(), endEdit.getEndB() + context);
|
|
|
|
|
|
|
|
|
|
writeHunkHeader(out, aCur, aEnd, bCur, bEnd);
|
|
|
|
|
writeHunkHeader(aCur, aEnd, bCur, bEnd);
|
|
|
|
|
|
|
|
|
|
while (aCur < aEnd || bCur < bEnd) {
|
|
|
|
|
if (aCur < curEdit.getBeginA() || endIdx + 1 < curIdx) {
|
|
|
|
|
writeContextLine(out, a, aCur, isEndOfLineMissing(a, aCur));
|
|
|
|
|
writeContextLine(a, aCur);
|
|
|
|
|
if (isEndOfLineMissing(a, aCur))
|
|
|
|
|
out.write(noNewLine);
|
|
|
|
|
aCur++;
|
|
|
|
|
bCur++;
|
|
|
|
|
} else if (aCur < curEdit.getEndA()) {
|
|
|
|
|
writeRemovedLine(out, a, aCur, isEndOfLineMissing(a, aCur));
|
|
|
|
|
writeRemovedLine(a, aCur);
|
|
|
|
|
if (isEndOfLineMissing(a, aCur))
|
|
|
|
|
out.write(noNewLine);
|
|
|
|
|
aCur++;
|
|
|
|
|
} else if (bCur < curEdit.getEndB()) {
|
|
|
|
|
writeAddedLine(out, b, bCur, isEndOfLineMissing(b, bCur));
|
|
|
|
|
writeAddedLine(b, bCur);
|
|
|
|
|
if (isEndOfLineMissing(b, bCur))
|
|
|
|
|
out.write(noNewLine);
|
|
|
|
|
bCur++;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
@ -317,21 +369,17 @@ public void formatEdits(final OutputStream out, final RawText a,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Output a line of diff context
|
|
|
|
|
* Output a line of context (unmodified line).
|
|
|
|
|
*
|
|
|
|
|
* @param out
|
|
|
|
|
* OutputStream
|
|
|
|
|
* @param text
|
|
|
|
|
* RawText for accessing raw data
|
|
|
|
|
* @param line
|
|
|
|
|
* the line number within text
|
|
|
|
|
* @param endOfLineMissing
|
|
|
|
|
* true if we should add the GNU end of line missing warning
|
|
|
|
|
* @throws IOException
|
|
|
|
|
*/
|
|
|
|
|
protected void writeContextLine(final OutputStream out, final RawText text,
|
|
|
|
|
final int line, boolean endOfLineMissing) throws IOException {
|
|
|
|
|
writeLine(out, ' ', text, line, endOfLineMissing);
|
|
|
|
|
protected void writeContextLine(final RawText text, final int line)
|
|
|
|
|
throws IOException {
|
|
|
|
|
writeLine(' ', text, line);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private boolean isEndOfLineMissing(final RawText text, final int line) {
|
|
|
|
@ -339,46 +387,36 @@ private boolean isEndOfLineMissing(final RawText text, final int line) {
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Output an added line
|
|
|
|
|
* Output an added line.
|
|
|
|
|
*
|
|
|
|
|
* @param out
|
|
|
|
|
* OutputStream
|
|
|
|
|
* @param text
|
|
|
|
|
* RawText for accessing raw data
|
|
|
|
|
* @param line
|
|
|
|
|
* the line number within text
|
|
|
|
|
* @param endOfLineMissing
|
|
|
|
|
* true if we should add the gnu end of line missing warning
|
|
|
|
|
* @throws IOException
|
|
|
|
|
*/
|
|
|
|
|
protected void writeAddedLine(final OutputStream out, final RawText text, final int line, boolean endOfLineMissing)
|
|
|
|
|
protected void writeAddedLine(final RawText text, final int line)
|
|
|
|
|
throws IOException {
|
|
|
|
|
writeLine(out, '+', text, line, endOfLineMissing);
|
|
|
|
|
writeLine('+', text, line);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Output a removed line
|
|
|
|
|
*
|
|
|
|
|
* @param out
|
|
|
|
|
* OutputStream
|
|
|
|
|
* @param text
|
|
|
|
|
* RawText for accessing raw data
|
|
|
|
|
* @param line
|
|
|
|
|
* the line number within text
|
|
|
|
|
* @param endOfLineMissing
|
|
|
|
|
* true if we should add the gnu end of line missing warning
|
|
|
|
|
* @throws IOException
|
|
|
|
|
*/
|
|
|
|
|
protected void writeRemovedLine(final OutputStream out, final RawText text,
|
|
|
|
|
final int line, boolean endOfLineMissing) throws IOException {
|
|
|
|
|
writeLine(out, '-', text, line, endOfLineMissing);
|
|
|
|
|
protected void writeRemovedLine(final RawText text, final int line)
|
|
|
|
|
throws IOException {
|
|
|
|
|
writeLine('-', text, line);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Output a hunk header
|
|
|
|
|
*
|
|
|
|
|
* @param out
|
|
|
|
|
* OutputStream
|
|
|
|
|
* @param aStartLine
|
|
|
|
|
* within first source
|
|
|
|
|
* @param aEndLine
|
|
|
|
@ -389,20 +427,20 @@ protected void writeRemovedLine(final OutputStream out, final RawText text,
|
|
|
|
|
* within second source
|
|
|
|
|
* @throws IOException
|
|
|
|
|
*/
|
|
|
|
|
protected void writeHunkHeader(final OutputStream out, int aStartLine, int aEndLine,
|
|
|
|
|
protected void writeHunkHeader(int aStartLine, int aEndLine,
|
|
|
|
|
int bStartLine, int bEndLine) throws IOException {
|
|
|
|
|
out.write('@');
|
|
|
|
|
out.write('@');
|
|
|
|
|
writeRange(out, '-', aStartLine + 1, aEndLine - aStartLine);
|
|
|
|
|
writeRange(out, '+', bStartLine + 1, bEndLine - bStartLine);
|
|
|
|
|
writeRange('-', aStartLine + 1, aEndLine - aStartLine);
|
|
|
|
|
writeRange('+', bStartLine + 1, bEndLine - bStartLine);
|
|
|
|
|
out.write(' ');
|
|
|
|
|
out.write('@');
|
|
|
|
|
out.write('@');
|
|
|
|
|
out.write('\n');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private static void writeRange(final OutputStream out, final char prefix,
|
|
|
|
|
final int begin, final int cnt) throws IOException {
|
|
|
|
|
private void writeRange(final char prefix, final int begin, final int cnt)
|
|
|
|
|
throws IOException {
|
|
|
|
|
out.write(' ');
|
|
|
|
|
out.write(prefix);
|
|
|
|
|
switch (cnt) {
|
|
|
|
@ -431,18 +469,23 @@ private static void writeRange(final OutputStream out, final char prefix,
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private static void writeLine(final OutputStream out, final char prefix,
|
|
|
|
|
final RawText text, final int cur, boolean noNewLineIndicator) throws IOException {
|
|
|
|
|
/**
|
|
|
|
|
* Write a standard patch script line.
|
|
|
|
|
*
|
|
|
|
|
* @param prefix
|
|
|
|
|
* prefix before the line, typically '-', '+', ' '.
|
|
|
|
|
* @param text
|
|
|
|
|
* the text object to obtain the line from.
|
|
|
|
|
* @param cur
|
|
|
|
|
* line number to output.
|
|
|
|
|
* @throws IOException
|
|
|
|
|
* the stream threw an exception while writing to it.
|
|
|
|
|
*/
|
|
|
|
|
protected void writeLine(final char prefix, final RawText text,
|
|
|
|
|
final int cur) throws IOException {
|
|
|
|
|
out.write(prefix);
|
|
|
|
|
text.writeLine(out, cur);
|
|
|
|
|
out.write('\n');
|
|
|
|
|
if (noNewLineIndicator)
|
|
|
|
|
writeNoNewLine(out);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private static void writeNoNewLine(final OutputStream out)
|
|
|
|
|
throws IOException {
|
|
|
|
|
out.write(noNewLine);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private int findCombinedEnd(final List<Edit> edits, final int i) {
|
|
|
|
|