* [JGIT PATCH 00/12] Patch API bug fixes and diff --cc suport @ 2008-12-12 22:05 Shawn O. Pearce 2008-12-12 22:05 ` [JGIT PATCH 01/12] Assert the HunkHeader.getFileHeader returns the right file Shawn O. Pearce 0 siblings, 1 reply; 14+ messages in thread From: Shawn O. Pearce @ 2008-12-12 22:05 UTC (permalink / raw) To: Robin Rosenberg; +Cc: git This series addes new unit tests for the patch API, closing some gaps in our test coverage. Some of those tests identified bugs in the implementation, and those are now fixed. The final patch in the series adds support for "diff -cc" patches, used to show an "evil merge" commit's conflict resolution. Shawn O. Pearce (12): Assert the HunkHeader.getFileHeader returns the right file Add tests to cover more methods of BinaryHunk Add a simple toString to FormatError to facilitate debugging Allow FileHeader to create its HunkHeader children Refactor the old/pre-image data in HunkHeader to support >1 ancestor Assert the ChunkHeader.OldImage.getId uses FileHeader.getOldImage Allow a stray LF at the end of a hunk Fix HunkHeader start line when parsing "@@ -1 +1 @@" style headers Add test cases for parsing "\ No newline at end of file" style patches Use FileMode.MISSING when a file is added or deleted rather than null Add a test for delta binary patch parsing and fix a bug in it Add support for parsing "diff --cc" style patches .../spearce/jgit/patch/EGitPatchHistoryTest.java | 4 +- .../tst/org/spearce/jgit/patch/FileHeaderTest.java | 4 +- .../org/spearce/jgit/patch/PatchCcErrorTest.java | 97 +++++++++ .../tst/org/spearce/jgit/patch/PatchCcTest.java | 200 ++++++++++++++++++ .../tst/org/spearce/jgit/patch/PatchTest.java | 165 +++++++++++++-- .../jgit/patch/testError_CcTruncatedOld.patch | 24 +++ .../jgit/patch/testParse_AddNoNewline.patch | 20 ++ .../jgit/patch/testParse_CcDeleteFile.patch | 12 + .../spearce/jgit/patch/testParse_CcNewFile.patch | 14 ++ .../jgit/patch/testParse_FixNoNewline.patch | 20 ++ .../jgit/patch/testParse_GitBinaryDelta.patch | 21 ++ ...nary.patch => testParse_GitBinaryLiteral.patch} | 0 .../spearce/jgit/patch/testParse_OneFileCc.patch | 27 +++ .../src/org/spearce/jgit/patch/BinaryHunk.java | 2 +- .../org/spearce/jgit/patch/CombinedFileHeader.java | 213 ++++++++++++++++++++ .../org/spearce/jgit/patch/CombinedHunkHeader.java | 191 ++++++++++++++++++ .../src/org/spearce/jgit/patch/FileHeader.java | 64 ++++-- .../src/org/spearce/jgit/patch/FormatError.java | 14 ++ .../src/org/spearce/jgit/patch/HunkHeader.java | 134 +++++++----- .../src/org/spearce/jgit/patch/Patch.java | 24 ++- 20 files changed, 1134 insertions(+), 116 deletions(-) create mode 100644 org.spearce.jgit.test/tst/org/spearce/jgit/patch/PatchCcErrorTest.java create mode 100644 org.spearce.jgit.test/tst/org/spearce/jgit/patch/PatchCcTest.java create mode 100644 org.spearce.jgit.test/tst/org/spearce/jgit/patch/testError_CcTruncatedOld.patch create mode 100644 org.spearce.jgit.test/tst/org/spearce/jgit/patch/testParse_AddNoNewline.patch create mode 100644 org.spearce.jgit.test/tst/org/spearce/jgit/patch/testParse_CcDeleteFile.patch create mode 100644 org.spearce.jgit.test/tst/org/spearce/jgit/patch/testParse_CcNewFile.patch create mode 100644 org.spearce.jgit.test/tst/org/spearce/jgit/patch/testParse_FixNoNewline.patch create mode 100644 org.spearce.jgit.test/tst/org/spearce/jgit/patch/testParse_GitBinaryDelta.patch rename org.spearce.jgit.test/tst/org/spearce/jgit/patch/{testParse_GitBinary.patch => testParse_GitBinaryLiteral.patch} (100%) create mode 100644 org.spearce.jgit.test/tst/org/spearce/jgit/patch/testParse_OneFileCc.patch create mode 100644 org.spearce.jgit/src/org/spearce/jgit/patch/CombinedFileHeader.java create mode 100644 org.spearce.jgit/src/org/spearce/jgit/patch/CombinedHunkHeader.java ^ permalink raw reply [flat|nested] 14+ messages in thread
* [JGIT PATCH 01/12] Assert the HunkHeader.getFileHeader returns the right file 2008-12-12 22:05 [JGIT PATCH 00/12] Patch API bug fixes and diff --cc suport Shawn O. Pearce @ 2008-12-12 22:05 ` Shawn O. Pearce 2008-12-12 22:05 ` [JGIT PATCH 02/12] Add tests to cover more methods of BinaryHunk Shawn O. Pearce 0 siblings, 1 reply; 14+ messages in thread From: Shawn O. Pearce @ 2008-12-12 22:05 UTC (permalink / raw) To: Robin Rosenberg; +Cc: git Signed-off-by: Shawn O. Pearce <spearce@spearce.org> --- .../tst/org/spearce/jgit/patch/PatchTest.java | 2 ++ 1 files changed, 2 insertions(+), 0 deletions(-) diff --git a/org.spearce.jgit.test/tst/org/spearce/jgit/patch/PatchTest.java b/org.spearce.jgit.test/tst/org/spearce/jgit/patch/PatchTest.java index 7c69fff..5850364 100644 --- a/org.spearce.jgit.test/tst/org/spearce/jgit/patch/PatchTest.java +++ b/org.spearce.jgit.test/tst/org/spearce/jgit/patch/PatchTest.java @@ -80,6 +80,7 @@ assertSame(FileHeader.PatchType.UNIFIED, fRepositoryConfigTest assertEquals(1, fRepositoryConfigTest.getHunks().size()); { final HunkHeader h = fRepositoryConfigTest.getHunks().get(0); + assertSame(fRepositoryConfigTest, h.getFileHeader()); assertEquals(921, h.startOffset); assertEquals(109, h.getOldStartLine()); assertEquals(4, h.getOldLineCount()); @@ -102,6 +103,7 @@ assertSame(FileHeader.PatchType.UNIFIED, fRepositoryConfig assertEquals(3, fRepositoryConfig.getHunks().size()); { final HunkHeader h = fRepositoryConfig.getHunks().get(0); + assertSame(fRepositoryConfig, h.getFileHeader()); assertEquals(1803, h.startOffset); assertEquals(236, h.getOldStartLine()); assertEquals(9, h.getOldLineCount()); -- 1.6.1.rc2.306.ge5d5e ^ permalink raw reply related [flat|nested] 14+ messages in thread
* [JGIT PATCH 02/12] Add tests to cover more methods of BinaryHunk 2008-12-12 22:05 ` [JGIT PATCH 01/12] Assert the HunkHeader.getFileHeader returns the right file Shawn O. Pearce @ 2008-12-12 22:05 ` Shawn O. Pearce 2008-12-12 22:05 ` [JGIT PATCH 03/12] Add a simple toString to FormatError to facilitate debugging Shawn O. Pearce 0 siblings, 1 reply; 14+ messages in thread From: Shawn O. Pearce @ 2008-12-12 22:05 UTC (permalink / raw) To: Robin Rosenberg; +Cc: git Signed-off-by: Shawn O. Pearce <spearce@spearce.org> --- .../tst/org/spearce/jgit/patch/PatchTest.java | 16 ++++++++++++---- 1 files changed, 12 insertions(+), 4 deletions(-) diff --git a/org.spearce.jgit.test/tst/org/spearce/jgit/patch/PatchTest.java b/org.spearce.jgit.test/tst/org/spearce/jgit/patch/PatchTest.java index 5850364..ebd23b4 100644 --- a/org.spearce.jgit.test/tst/org/spearce/jgit/patch/PatchTest.java +++ b/org.spearce.jgit.test/tst/org/spearce/jgit/patch/PatchTest.java @@ -199,10 +199,18 @@ assertTrue(fh.getNewName().startsWith( assertTrue(fh.getHunks().isEmpty()); assertTrue(fh.hasMetaDataChanges()); - assertNotNull(fh.getForwardBinaryHunk()); - assertNotNull(fh.getReverseBinaryHunk()); - assertEquals(binsizes[i], fh.getForwardBinaryHunk().getSize()); - assertEquals(0, fh.getReverseBinaryHunk().getSize()); + final BinaryHunk fwd = fh.getForwardBinaryHunk(); + final BinaryHunk rev = fh.getReverseBinaryHunk(); + assertNotNull(fwd); + assertNotNull(rev); + assertEquals(binsizes[i], fwd.getSize()); + assertEquals(0, rev.getSize()); + + assertSame(fh, fwd.getFileHeader()); + assertSame(fh, rev.getFileHeader()); + + assertSame(BinaryHunk.Type.LITERAL_DEFLATED, fwd.getType()); + assertSame(BinaryHunk.Type.LITERAL_DEFLATED, rev.getType()); } final FileHeader fh = p.getFiles().get(4); -- 1.6.1.rc2.306.ge5d5e ^ permalink raw reply related [flat|nested] 14+ messages in thread
* [JGIT PATCH 03/12] Add a simple toString to FormatError to facilitate debugging 2008-12-12 22:05 ` [JGIT PATCH 02/12] Add tests to cover more methods of BinaryHunk Shawn O. Pearce @ 2008-12-12 22:05 ` Shawn O. Pearce 2008-12-12 22:05 ` [JGIT PATCH 04/12] Allow FileHeader to create its HunkHeader children Shawn O. Pearce 0 siblings, 1 reply; 14+ messages in thread From: Shawn O. Pearce @ 2008-12-12 22:05 UTC (permalink / raw) To: Robin Rosenberg; +Cc: git Signed-off-by: Shawn O. Pearce <spearce@spearce.org> --- .../src/org/spearce/jgit/patch/FormatError.java | 14 ++++++++++++++ 1 files changed, 14 insertions(+), 0 deletions(-) diff --git a/org.spearce.jgit/src/org/spearce/jgit/patch/FormatError.java b/org.spearce.jgit/src/org/spearce/jgit/patch/FormatError.java index e6f0a03..ab75c63 100644 --- a/org.spearce.jgit/src/org/spearce/jgit/patch/FormatError.java +++ b/org.spearce.jgit/src/org/spearce/jgit/patch/FormatError.java @@ -92,4 +92,18 @@ public String getLineText() { final int eol = RawParseUtils.nextLF(buf, offset); return RawParseUtils.decode(Constants.CHARSET, buf, offset, eol); } + + @Override + public String toString() { + final StringBuilder r = new StringBuilder(); + r.append(getSeverity().name().toLowerCase()); + r.append(": at offset "); + r.append(getOffset()); + r.append(": "); + r.append(getMessage()); + r.append("\n"); + r.append(" in "); + r.append(getLineText()); + return r.toString(); + } } -- 1.6.1.rc2.306.ge5d5e ^ permalink raw reply related [flat|nested] 14+ messages in thread
* [JGIT PATCH 04/12] Allow FileHeader to create its HunkHeader children 2008-12-12 22:05 ` [JGIT PATCH 03/12] Add a simple toString to FormatError to facilitate debugging Shawn O. Pearce @ 2008-12-12 22:05 ` Shawn O. Pearce 2008-12-12 22:05 ` [JGIT PATCH 05/12] Refactor the old/pre-image data in HunkHeader to support >1 ancestor Shawn O. Pearce 0 siblings, 1 reply; 14+ messages in thread From: Shawn O. Pearce @ 2008-12-12 22:05 UTC (permalink / raw) To: Robin Rosenberg; +Cc: git By using a factory method on FileHeader we can later subclass the FileHeader class to handle "diff --cc" style patches, and let it create its own subclass of HunkHeader to handle the specialized form of the n-way diff. The getParentCount() method is hard-coded to return 1 in the 2-way diff case as there is exactly one parent. But in a "diff --cc" we need to verify the hunk header has the same number of parents as the file header in order to parse the hunk. So a subclass of the FileHeader would need to override getParentCount() to return the actual number of '@' symbols (less 1) that should appear in each hunk header line. (E.g. a 3-way diff shows "@@@ -" so the parent count should be 2.) Signed-off-by: Shawn O. Pearce <spearce@spearce.org> --- .../src/org/spearce/jgit/patch/FileHeader.java | 8 ++++++++ .../src/org/spearce/jgit/patch/Patch.java | 4 ++-- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/org.spearce.jgit/src/org/spearce/jgit/patch/FileHeader.java b/org.spearce.jgit/src/org/spearce/jgit/patch/FileHeader.java index 5fe2acf..f93129d 100644 --- a/org.spearce.jgit/src/org/spearce/jgit/patch/FileHeader.java +++ b/org.spearce.jgit/src/org/spearce/jgit/patch/FileHeader.java @@ -169,6 +169,10 @@ FileHeader(final byte[] b, final int offset) { patchType = PatchType.UNIFIED; } + int getParentCount() { + return 1; + } + /** * Get the old name associated with this file. * <p> @@ -274,6 +278,10 @@ void addHunk(final HunkHeader h) { hunks.add(h); } + HunkHeader newHunkHeader(final int offset) { + return new HunkHeader(this, offset); + } + /** @return if a {@link PatchType#GIT_BINARY}, the new-image delta/literal */ public BinaryHunk getForwardBinaryHunk() { return forwardBinaryHunk; diff --git a/org.spearce.jgit/src/org/spearce/jgit/patch/Patch.java b/org.spearce.jgit/src/org/spearce/jgit/patch/Patch.java index 77ae02f..05d034d 100644 --- a/org.spearce.jgit/src/org/spearce/jgit/patch/Patch.java +++ b/org.spearce.jgit/src/org/spearce/jgit/patch/Patch.java @@ -281,8 +281,8 @@ private int parseHunks(final FileHeader fh, int c, final int end) { if (match(buf, c, NEW_NAME) >= 0) break; - if (isHunkHdr(buf, c, end) == 1) { - final HunkHeader h = new HunkHeader(fh, c); + if (isHunkHdr(buf, c, end) == fh.getParentCount()) { + final HunkHeader h = fh.newHunkHeader(c); h.parseHeader(end); c = h.parseBody(this, end); h.endOffset = c; -- 1.6.1.rc2.306.ge5d5e ^ permalink raw reply related [flat|nested] 14+ messages in thread
* [JGIT PATCH 05/12] Refactor the old/pre-image data in HunkHeader to support >1 ancestor 2008-12-12 22:05 ` [JGIT PATCH 04/12] Allow FileHeader to create its HunkHeader children Shawn O. Pearce @ 2008-12-12 22:05 ` Shawn O. Pearce 2008-12-12 22:05 ` [JGIT PATCH 06/12] Assert the ChunkHeader.OldImage.getId uses FileHeader.getOldImage Shawn O. Pearce 0 siblings, 1 reply; 14+ messages in thread From: Shawn O. Pearce @ 2008-12-12 22:05 UTC (permalink / raw) To: Robin Rosenberg; +Cc: git The "diff --cc" format uses more than one ancestor in each hunk, so we need to expand the hunk header information in a way that allows access to the data for each ancestor. This change moves the information relative to the old/pre-image ancestor in a 2-way patch into an OldImage object. In a 2-way patch we only have one OldImage, but in a "diff --cc" patch we will have more than one of these available in each hunk. Signed-off-by: Shawn O. Pearce <spearce@spearce.org> --- .../spearce/jgit/patch/EGitPatchHistoryTest.java | 4 +- .../tst/org/spearce/jgit/patch/PatchTest.java | 36 +++--- .../src/org/spearce/jgit/patch/HunkHeader.java | 124 ++++++++++++-------- 3 files changed, 95 insertions(+), 69 deletions(-) diff --git a/org.spearce.jgit.test/exttst/org/spearce/jgit/patch/EGitPatchHistoryTest.java b/org.spearce.jgit.test/exttst/org/spearce/jgit/patch/EGitPatchHistoryTest.java index d0c2632..b170dc2 100644 --- a/org.spearce.jgit.test/exttst/org/spearce/jgit/patch/EGitPatchHistoryTest.java +++ b/org.spearce.jgit.test/exttst/org/spearce/jgit/patch/EGitPatchHistoryTest.java @@ -110,8 +110,8 @@ void onCommit(String cid, byte[] buf) { assertNotNull("No " + nid, s); int added = 0, deleted = 0; for (final HunkHeader h : fh.getHunks()) { - added += h.getLinesAdded(); - deleted += h.getLinesDeleted(); + added += h.getOldImage().getLinesAdded(); + deleted += h.getOldImage().getLinesDeleted(); } if (s.added == added) { diff --git a/org.spearce.jgit.test/tst/org/spearce/jgit/patch/PatchTest.java b/org.spearce.jgit.test/tst/org/spearce/jgit/patch/PatchTest.java index ebd23b4..4eceeb5 100644 --- a/org.spearce.jgit.test/tst/org/spearce/jgit/patch/PatchTest.java +++ b/org.spearce.jgit.test/tst/org/spearce/jgit/patch/PatchTest.java @@ -82,14 +82,14 @@ assertSame(FileHeader.PatchType.UNIFIED, fRepositoryConfigTest final HunkHeader h = fRepositoryConfigTest.getHunks().get(0); assertSame(fRepositoryConfigTest, h.getFileHeader()); assertEquals(921, h.startOffset); - assertEquals(109, h.getOldStartLine()); - assertEquals(4, h.getOldLineCount()); + assertEquals(109, h.getOldImage().getStartLine()); + assertEquals(4, h.getOldImage().getLineCount()); assertEquals(109, h.getNewStartLine()); assertEquals(11, h.getNewLineCount()); assertEquals(4, h.getLinesContext()); - assertEquals(7, h.getLinesAdded()); - assertEquals(0, h.getLinesDeleted()); + assertEquals(7, h.getOldImage().getLinesAdded()); + assertEquals(0, h.getOldImage().getLinesDeleted()); assertEquals(1490, h.endOffset); } @@ -105,42 +105,42 @@ assertSame(FileHeader.PatchType.UNIFIED, fRepositoryConfig final HunkHeader h = fRepositoryConfig.getHunks().get(0); assertSame(fRepositoryConfig, h.getFileHeader()); assertEquals(1803, h.startOffset); - assertEquals(236, h.getOldStartLine()); - assertEquals(9, h.getOldLineCount()); + assertEquals(236, h.getOldImage().getStartLine()); + assertEquals(9, h.getOldImage().getLineCount()); assertEquals(236, h.getNewStartLine()); assertEquals(9, h.getNewLineCount()); assertEquals(7, h.getLinesContext()); - assertEquals(2, h.getLinesAdded()); - assertEquals(2, h.getLinesDeleted()); + assertEquals(2, h.getOldImage().getLinesAdded()); + assertEquals(2, h.getOldImage().getLinesDeleted()); assertEquals(2434, h.endOffset); } { final HunkHeader h = fRepositoryConfig.getHunks().get(1); assertEquals(2434, h.startOffset); - assertEquals(300, h.getOldStartLine()); - assertEquals(7, h.getOldLineCount()); + assertEquals(300, h.getOldImage().getStartLine()); + assertEquals(7, h.getOldImage().getLineCount()); assertEquals(300, h.getNewStartLine()); assertEquals(7, h.getNewLineCount()); assertEquals(6, h.getLinesContext()); - assertEquals(1, h.getLinesAdded()); - assertEquals(1, h.getLinesDeleted()); + assertEquals(1, h.getOldImage().getLinesAdded()); + assertEquals(1, h.getOldImage().getLinesDeleted()); assertEquals(2816, h.endOffset); } { final HunkHeader h = fRepositoryConfig.getHunks().get(2); assertEquals(2816, h.startOffset); - assertEquals(954, h.getOldStartLine()); - assertEquals(7, h.getOldLineCount()); + assertEquals(954, h.getOldImage().getStartLine()); + assertEquals(7, h.getOldImage().getLineCount()); assertEquals(954, h.getNewStartLine()); assertEquals(7, h.getNewLineCount()); assertEquals(6, h.getLinesContext()); - assertEquals(1, h.getLinesAdded()); - assertEquals(1, h.getLinesDeleted()); + assertEquals(1, h.getOldImage().getLinesAdded()); + assertEquals(1, h.getOldImage().getLinesDeleted()); assertEquals(3035, h.endOffset); } @@ -177,7 +177,7 @@ assertTrue(fh.getNewName().startsWith( assertNull(fh.getForwardBinaryHunk()); assertNull(fh.getReverseBinaryHunk()); assertEquals(1, fh.getHunks().size()); - assertEquals(272, fh.getHunks().get(0).getOldStartLine()); + assertEquals(272, fh.getHunks().get(0).getOldImage().getStartLine()); } public void testParse_GitBinary() throws IOException { @@ -222,7 +222,7 @@ assertTrue(fh.getNewName().startsWith( assertNull(fh.getForwardBinaryHunk()); assertNull(fh.getReverseBinaryHunk()); assertEquals(1, fh.getHunks().size()); - assertEquals(272, fh.getHunks().get(0).getOldStartLine()); + assertEquals(272, fh.getHunks().get(0).getOldImage().getStartLine()); } private Patch parseTestPatchFile() throws IOException { diff --git a/org.spearce.jgit/src/org/spearce/jgit/patch/HunkHeader.java b/org.spearce.jgit/src/org/spearce/jgit/patch/HunkHeader.java index c3bd642..842519e 100644 --- a/org.spearce.jgit/src/org/spearce/jgit/patch/HunkHeader.java +++ b/org.spearce.jgit/src/org/spearce/jgit/patch/HunkHeader.java @@ -41,10 +41,49 @@ import static org.spearce.jgit.util.RawParseUtils.nextLF; import static org.spearce.jgit.util.RawParseUtils.parseBase10; +import org.spearce.jgit.lib.AbbreviatedObjectId; import org.spearce.jgit.util.MutableInteger; /** Hunk header describing the layout of a single block of lines */ public class HunkHeader { + /** Details about an old image of the file. */ + public abstract static class OldImage { + /** First line number the hunk starts on in this file. */ + int startLine; + + /** Total number of lines this hunk covers in this file. */ + int lineCount; + + /** Number of lines deleted by the post-image from this file. */ + int nDeleted; + + /** Number of lines added by the post-image not in this file. */ + int nAdded; + + /** @return first line number the hunk starts on in this file. */ + public int getStartLine() { + return startLine; + } + + /** @return total number of lines this hunk covers in this file. */ + public int getLineCount() { + return lineCount; + } + + /** @return number of lines deleted by the post-image from this file. */ + public int getLinesDeleted() { + return nDeleted; + } + + /** @return number of lines added by the post-image not in this file. */ + public int getLinesAdded() { + return nAdded; + } + + /** @return object id of the pre-image file. */ + public abstract AbbreviatedObjectId getId(); + } + private final FileHeader file; /** Offset within {@link #file}.buf to the "@@ -" line. */ @@ -53,11 +92,7 @@ /** Position 1 past the end of this hunk within {@link #file}'s buf. */ int endOffset; - /** First line number in the pre-image file where the hunk starts */ - int oldStartLine; - - /** Total number of pre-image lines this hunk covers (context + deleted) */ - int oldLineCount; + private final OldImage old; /** First line number in the post-image file where the hunk starts */ int newStartLine; @@ -68,15 +103,19 @@ /** Total number of lines of context appearing in this hunk */ int nContext; - /** Number of lines removed by this hunk */ - int nDeleted; - - /** Number of lines added by this hunk */ - int nAdded; - HunkHeader(final FileHeader fh, final int offset) { + this(fh, offset, new OldImage() { + @Override + public AbbreviatedObjectId getId() { + return fh.getOldId(); + } + }); + } + + HunkHeader(final FileHeader fh, final int offset, final OldImage oi) { file = fh; startOffset = offset; + old = oi; } /** @return header for the file this hunk applies to */ @@ -84,14 +123,9 @@ public FileHeader getFileHeader() { return file; } - /** @return first line number in the pre-image file where the hunk starts */ - public int getOldStartLine() { - return oldStartLine; - } - - /** @return total number of pre-image lines this hunk covers */ - public int getOldLineCount() { - return oldLineCount; + /** @return information about the old image mentioned in this hunk. */ + public OldImage getOldImage() { + return old; } /** @return first line number in the post-image file where the hunk starts */ @@ -109,28 +143,18 @@ public int getLinesContext() { return nContext; } - /** @return number of lines removed by this hunk */ - public int getLinesDeleted() { - return nDeleted; - } - - /** @return number of lines added by this hunk */ - public int getLinesAdded() { - return nAdded; - } - void parseHeader(final int end) { // Parse "@@ -236,9 +236,9 @@ protected boolean" // final byte[] buf = file.buf; final MutableInteger ptr = new MutableInteger(); ptr.value = nextLF(buf, startOffset, ' '); - oldStartLine = -parseBase10(buf, ptr.value, ptr); + old.startLine = -parseBase10(buf, ptr.value, ptr); if (buf[ptr.value] == ',') - oldLineCount = parseBase10(buf, ptr.value + 1, ptr); + old.lineCount = parseBase10(buf, ptr.value + 1, ptr); else { - oldLineCount = oldStartLine; - oldStartLine = 0; + old.lineCount = old.startLine; + old.startLine = 0; } newStartLine = parseBase10(buf, ptr.value + 1, ptr); @@ -146,8 +170,8 @@ int parseBody(final Patch script, final int end) { final byte[] buf = file.buf; int c = nextLF(buf, startOffset), last = c; - nDeleted = 0; - nAdded = 0; + old.nDeleted = 0; + old.nAdded = 0; SCAN: for (; c < end; last = c, c = nextLF(buf, c)) { switch (buf[c]) { @@ -157,11 +181,11 @@ int parseBody(final Patch script, final int end) { continue; case '-': - nDeleted++; + old.nDeleted++; continue; case '+': - nAdded++; + old.nAdded++; continue; case '\\': // Matches "\ No newline at end of file" @@ -172,33 +196,35 @@ int parseBody(final Patch script, final int end) { } } - if (last < end && nContext + nDeleted - 1 == oldLineCount - && nContext + nAdded == newLineCount + if (last < end && nContext + old.nDeleted - 1 == old.lineCount + && nContext + old.nAdded == newLineCount && match(buf, last, Patch.SIG_FOOTER) >= 0) { // This is an extremely common occurrence of "corruption". // Users add footers with their signatures after this mark, // and git diff adds the git executable version number. // Let it slide; the hunk otherwise looked sound. // - nDeleted--; + old.nDeleted--; return last; } - if (nContext + nDeleted < oldLineCount) { - final int missingCount = oldLineCount - (nContext + nDeleted); + if (nContext + old.nDeleted < old.lineCount) { + final int missingCount = old.lineCount - (nContext + old.nDeleted); script.error(buf, startOffset, "Truncated hunk, at least " + missingCount + " old lines is missing"); - } else if (nContext + nAdded < newLineCount) { - final int missingCount = newLineCount - (nContext + nAdded); + } else if (nContext + old.nAdded < newLineCount) { + final int missingCount = newLineCount - (nContext + old.nAdded); script.error(buf, startOffset, "Truncated hunk, at least " + missingCount + " new lines is missing"); - } else if (nContext + nDeleted > oldLineCount - || nContext + nAdded > newLineCount) { - script.warn(buf, startOffset, "Hunk header " + oldLineCount + ":" - + newLineCount + " does not match body line count of " - + (nContext + nDeleted) + ":" + (nContext + nAdded)); + } else if (nContext + old.nDeleted > old.lineCount + || nContext + old.nAdded > newLineCount) { + final String oldcnt = old.lineCount + ":" + newLineCount; + final String newcnt = (nContext + old.nDeleted) + ":" + + (nContext + old.nAdded); + script.warn(buf, startOffset, "Hunk header " + oldcnt + + " does not match body line count of " + newcnt); } return c; -- 1.6.1.rc2.306.ge5d5e ^ permalink raw reply related [flat|nested] 14+ messages in thread
* [JGIT PATCH 06/12] Assert the ChunkHeader.OldImage.getId uses FileHeader.getOldImage 2008-12-12 22:05 ` [JGIT PATCH 05/12] Refactor the old/pre-image data in HunkHeader to support >1 ancestor Shawn O. Pearce @ 2008-12-12 22:05 ` Shawn O. Pearce 2008-12-12 22:05 ` [JGIT PATCH 07/12] Allow a stray LF at the end of a hunk Shawn O. Pearce 0 siblings, 1 reply; 14+ messages in thread From: Shawn O. Pearce @ 2008-12-12 22:05 UTC (permalink / raw) To: Robin Rosenberg; +Cc: git These should always produce the same AbbreviatedObjectId. Signed-off-by: Shawn O. Pearce <spearce@spearce.org> --- .../tst/org/spearce/jgit/patch/PatchTest.java | 3 +++ 1 files changed, 3 insertions(+), 0 deletions(-) diff --git a/org.spearce.jgit.test/tst/org/spearce/jgit/patch/PatchTest.java b/org.spearce.jgit.test/tst/org/spearce/jgit/patch/PatchTest.java index 4eceeb5..c81356b 100644 --- a/org.spearce.jgit.test/tst/org/spearce/jgit/patch/PatchTest.java +++ b/org.spearce.jgit.test/tst/org/spearce/jgit/patch/PatchTest.java @@ -90,6 +90,8 @@ assertSame(FileHeader.PatchType.UNIFIED, fRepositoryConfigTest assertEquals(4, h.getLinesContext()); assertEquals(7, h.getOldImage().getLinesAdded()); assertEquals(0, h.getOldImage().getLinesDeleted()); + assertSame(fRepositoryConfigTest.getOldId(), h.getOldImage() + .getId()); assertEquals(1490, h.endOffset); } @@ -113,6 +115,7 @@ assertSame(FileHeader.PatchType.UNIFIED, fRepositoryConfig assertEquals(7, h.getLinesContext()); assertEquals(2, h.getOldImage().getLinesAdded()); assertEquals(2, h.getOldImage().getLinesDeleted()); + assertSame(fRepositoryConfig.getOldId(), h.getOldImage().getId()); assertEquals(2434, h.endOffset); } -- 1.6.1.rc2.306.ge5d5e ^ permalink raw reply related [flat|nested] 14+ messages in thread
* [JGIT PATCH 07/12] Allow a stray LF at the end of a hunk 2008-12-12 22:05 ` [JGIT PATCH 06/12] Assert the ChunkHeader.OldImage.getId uses FileHeader.getOldImage Shawn O. Pearce @ 2008-12-12 22:05 ` Shawn O. Pearce 2008-12-12 22:05 ` [JGIT PATCH 08/12] Fix HunkHeader start line when parsing "@@ -1 +1 @@" style headers Shawn O. Pearce 0 siblings, 1 reply; 14+ messages in thread From: Shawn O. Pearce @ 2008-12-12 22:05 UTC (permalink / raw) To: Robin Rosenberg; +Cc: git If a hunk ends and is followed by a stray LF its not worth creating a warning for. A single extra blank line isn't all that interesting relative to the other sorts of data we might find at the end of a patch hunk. Signed-off-by: Shawn O. Pearce <spearce@spearce.org> --- .../src/org/spearce/jgit/patch/Patch.java | 13 ++++++++++--- 1 files changed, 10 insertions(+), 3 deletions(-) diff --git a/org.spearce.jgit/src/org/spearce/jgit/patch/Patch.java b/org.spearce.jgit/src/org/spearce/jgit/patch/Patch.java index 05d034d..51f1fe5 100644 --- a/org.spearce.jgit/src/org/spearce/jgit/patch/Patch.java +++ b/org.spearce.jgit/src/org/spearce/jgit/patch/Patch.java @@ -287,9 +287,16 @@ private int parseHunks(final FileHeader fh, int c, final int end) { c = h.parseBody(this, end); h.endOffset = c; fh.addHunk(h); - if (c < end && buf[c] != '@' && buf[c] != 'd' - && match(buf, c, SIG_FOOTER) < 0) { - warn(buf, c, "Unexpected hunk trailer"); + if (c < end) { + switch (buf[c]) { + case '@': + case 'd': + case '\n': + break; + default: + if (match(buf, c, SIG_FOOTER) < 0) + warn(buf, c, "Unexpected hunk trailer"); + } } continue; } -- 1.6.1.rc2.306.ge5d5e ^ permalink raw reply related [flat|nested] 14+ messages in thread
* [JGIT PATCH 08/12] Fix HunkHeader start line when parsing "@@ -1 +1 @@" style headers 2008-12-12 22:05 ` [JGIT PATCH 07/12] Allow a stray LF at the end of a hunk Shawn O. Pearce @ 2008-12-12 22:05 ` Shawn O. Pearce 2008-12-12 22:05 ` [JGIT PATCH 09/12] Add test cases for parsing "\ No newline at end of file" style patches Shawn O. Pearce 0 siblings, 1 reply; 14+ messages in thread From: Shawn O. Pearce @ 2008-12-12 22:05 UTC (permalink / raw) To: Robin Rosenberg; +Cc: git In this case we are listing a delta of 1 line but also the position start is line 1. Signed-off-by: Shawn O. Pearce <spearce@spearce.org> --- .../src/org/spearce/jgit/patch/HunkHeader.java | 12 ++++-------- 1 files changed, 4 insertions(+), 8 deletions(-) diff --git a/org.spearce.jgit/src/org/spearce/jgit/patch/HunkHeader.java b/org.spearce.jgit/src/org/spearce/jgit/patch/HunkHeader.java index 842519e..f543aed 100644 --- a/org.spearce.jgit/src/org/spearce/jgit/patch/HunkHeader.java +++ b/org.spearce.jgit/src/org/spearce/jgit/patch/HunkHeader.java @@ -152,18 +152,14 @@ void parseHeader(final int end) { old.startLine = -parseBase10(buf, ptr.value, ptr); if (buf[ptr.value] == ',') old.lineCount = parseBase10(buf, ptr.value + 1, ptr); - else { - old.lineCount = old.startLine; - old.startLine = 0; - } + else + old.lineCount = 1; newStartLine = parseBase10(buf, ptr.value + 1, ptr); if (buf[ptr.value] == ',') newLineCount = parseBase10(buf, ptr.value + 1, ptr); - else { - newLineCount = newStartLine; - newStartLine = 0; - } + else + newLineCount = 1; } int parseBody(final Patch script, final int end) { -- 1.6.1.rc2.306.ge5d5e ^ permalink raw reply related [flat|nested] 14+ messages in thread
* [JGIT PATCH 09/12] Add test cases for parsing "\ No newline at end of file" style patches 2008-12-12 22:05 ` [JGIT PATCH 08/12] Fix HunkHeader start line when parsing "@@ -1 +1 @@" style headers Shawn O. Pearce @ 2008-12-12 22:05 ` Shawn O. Pearce 2008-12-12 22:05 ` [JGIT PATCH 10/12] Use FileMode.MISSING when a file is added or deleted rather than null Shawn O. Pearce 0 siblings, 1 reply; 14+ messages in thread From: Shawn O. Pearce @ 2008-12-12 22:05 UTC (permalink / raw) To: Robin Rosenberg; +Cc: git Signed-off-by: Shawn O. Pearce <spearce@spearce.org> --- .../tst/org/spearce/jgit/patch/PatchTest.java | 68 ++++++++++++++++++++ .../jgit/patch/testParse_AddNoNewline.patch | 20 ++++++ .../jgit/patch/testParse_FixNoNewline.patch | 20 ++++++ 3 files changed, 108 insertions(+), 0 deletions(-) create mode 100644 org.spearce.jgit.test/tst/org/spearce/jgit/patch/testParse_AddNoNewline.patch create mode 100644 org.spearce.jgit.test/tst/org/spearce/jgit/patch/testParse_FixNoNewline.patch diff --git a/org.spearce.jgit.test/tst/org/spearce/jgit/patch/PatchTest.java b/org.spearce.jgit.test/tst/org/spearce/jgit/patch/PatchTest.java index c81356b..13eab5f 100644 --- a/org.spearce.jgit.test/tst/org/spearce/jgit/patch/PatchTest.java +++ b/org.spearce.jgit.test/tst/org/spearce/jgit/patch/PatchTest.java @@ -228,6 +228,74 @@ assertTrue(fh.getNewName().startsWith( assertEquals(272, fh.getHunks().get(0).getOldImage().getStartLine()); } + public void testParse_FixNoNewline() throws IOException { + final Patch p = parseTestPatchFile(); + assertEquals(1, p.getFiles().size()); + assertTrue(p.getErrors().isEmpty()); + + final FileHeader f = p.getFiles().get(0); + + assertEquals("a", f.getNewName()); + assertEquals(252, f.startOffset); + + assertEquals("2e65efe", f.getOldId().name()); + assertEquals("f2ad6c7", f.getNewId().name()); + assertSame(FileHeader.PatchType.UNIFIED, f.getPatchType()); + assertSame(FileMode.REGULAR_FILE, f.getOldMode()); + assertSame(FileMode.REGULAR_FILE, f.getNewMode()); + assertEquals(1, f.getHunks().size()); + { + final HunkHeader h = f.getHunks().get(0); + assertSame(f, h.getFileHeader()); + assertEquals(317, h.startOffset); + assertEquals(1, h.getOldImage().getStartLine()); + assertEquals(1, h.getOldImage().getLineCount()); + assertEquals(1, h.getNewStartLine()); + assertEquals(1, h.getNewLineCount()); + + assertEquals(0, h.getLinesContext()); + assertEquals(1, h.getOldImage().getLinesAdded()); + assertEquals(1, h.getOldImage().getLinesDeleted()); + assertSame(f.getOldId(), h.getOldImage().getId()); + + assertEquals(363, h.endOffset); + } + } + + public void testParse_AddNoNewline() throws IOException { + final Patch p = parseTestPatchFile(); + assertEquals(1, p.getFiles().size()); + assertTrue(p.getErrors().isEmpty()); + + final FileHeader f = p.getFiles().get(0); + + assertEquals("a", f.getNewName()); + assertEquals(256, f.startOffset); + + assertEquals("f2ad6c7", f.getOldId().name()); + assertEquals("c59d9b6", f.getNewId().name()); + assertSame(FileHeader.PatchType.UNIFIED, f.getPatchType()); + assertSame(FileMode.REGULAR_FILE, f.getOldMode()); + assertSame(FileMode.REGULAR_FILE, f.getNewMode()); + assertEquals(1, f.getHunks().size()); + { + final HunkHeader h = f.getHunks().get(0); + assertSame(f, h.getFileHeader()); + assertEquals(321, h.startOffset); + assertEquals(1, h.getOldImage().getStartLine()); + assertEquals(1, h.getOldImage().getLineCount()); + assertEquals(1, h.getNewStartLine()); + assertEquals(1, h.getNewLineCount()); + + assertEquals(0, h.getLinesContext()); + assertEquals(1, h.getOldImage().getLinesAdded()); + assertEquals(1, h.getOldImage().getLinesDeleted()); + assertSame(f.getOldId(), h.getOldImage().getId()); + + assertEquals(367, h.endOffset); + } + } + private Patch parseTestPatchFile() throws IOException { final String patchFile = getName() + ".patch"; final InputStream in = getClass().getResourceAsStream(patchFile); diff --git a/org.spearce.jgit.test/tst/org/spearce/jgit/patch/testParse_AddNoNewline.patch b/org.spearce.jgit.test/tst/org/spearce/jgit/patch/testParse_AddNoNewline.patch new file mode 100644 index 0000000..3060952 --- /dev/null +++ b/org.spearce.jgit.test/tst/org/spearce/jgit/patch/testParse_AddNoNewline.patch @@ -0,0 +1,20 @@ +From ca4719a4b2d93a469f61d1ddfb3e39ecbabfcd69 Mon Sep 17 00:00:00 2001 +From: Shawn O. Pearce <sop@google.com> +Date: Fri, 12 Dec 2008 12:35:14 -0800 +Subject: [PATCH] introduce no lf again + +--- + a | 2 +- + 1 files changed, 1 insertions(+), 1 deletions(-) + +diff --git a/a b/a +index f2ad6c7..c59d9b6 100644 +--- a/a ++++ b/a +@@ -1 +1 @@ +-c ++d +\ No newline at end of file +-- +1.6.1.rc2.306.ge5d5e + diff --git a/org.spearce.jgit.test/tst/org/spearce/jgit/patch/testParse_FixNoNewline.patch b/org.spearce.jgit.test/tst/org/spearce/jgit/patch/testParse_FixNoNewline.patch new file mode 100644 index 0000000..e8af2e7 --- /dev/null +++ b/org.spearce.jgit.test/tst/org/spearce/jgit/patch/testParse_FixNoNewline.patch @@ -0,0 +1,20 @@ +From 1beb3ec1fe68ff18b0287396096442e12c34787a Mon Sep 17 00:00:00 2001 +From: Shawn O. Pearce <sop@google.com> +Date: Fri, 12 Dec 2008 12:29:45 -0800 +Subject: [PATCH] make c and add lf + +--- + a | 2 +- + 1 files changed, 1 insertions(+), 1 deletions(-) + +diff --git a/a b/a +index 2e65efe..f2ad6c7 100644 +--- a/a ++++ b/a +@@ -1 +1 @@ +-a +\ No newline at end of file ++c +-- +1.6.1.rc2.306.ge5d5e + -- 1.6.1.rc2.306.ge5d5e ^ permalink raw reply related [flat|nested] 14+ messages in thread
* [JGIT PATCH 10/12] Use FileMode.MISSING when a file is added or deleted rather than null 2008-12-12 22:05 ` [JGIT PATCH 09/12] Add test cases for parsing "\ No newline at end of file" style patches Shawn O. Pearce @ 2008-12-12 22:05 ` Shawn O. Pearce 2008-12-12 22:05 ` [JGIT PATCH 11/12] Add a test for delta binary patch parsing and fix a bug in it Shawn O. Pearce 0 siblings, 1 reply; 14+ messages in thread From: Shawn O. Pearce @ 2008-12-12 22:05 UTC (permalink / raw) To: Robin Rosenberg; +Cc: git Null is better used to indicate "no mode information at all" in the patch, while FileMode.MISSING is already commonly used within the TreeWalk code to mean "this path doesn't exist in this tree". In the context of a patch to create or delete a file, MISSING makes a lot more sense for the application to work with. Signed-off-by: Shawn O. Pearce <spearce@spearce.org> --- .../tst/org/spearce/jgit/patch/FileHeaderTest.java | 4 ++-- .../tst/org/spearce/jgit/patch/PatchTest.java | 1 + .../src/org/spearce/jgit/patch/FileHeader.java | 2 ++ 3 files changed, 5 insertions(+), 2 deletions(-) diff --git a/org.spearce.jgit.test/tst/org/spearce/jgit/patch/FileHeaderTest.java b/org.spearce.jgit.test/tst/org/spearce/jgit/patch/FileHeaderTest.java index 36c528e..69e06ab 100644 --- a/org.spearce.jgit.test/tst/org/spearce/jgit/patch/FileHeaderTest.java +++ b/org.spearce.jgit.test/tst/org/spearce/jgit/patch/FileHeaderTest.java @@ -153,7 +153,7 @@ public void testParseUnicodeName_NewFile() { assertSame(FileHeader.PatchType.UNIFIED, fh.getPatchType()); assertTrue(fh.hasMetaDataChanges()); - assertNull(fh.getOldMode()); + assertSame(FileMode.MISSING, fh.getOldMode()); assertSame(FileMode.REGULAR_FILE, fh.getNewMode()); assertEquals("0000000", fh.getOldId().name()); @@ -179,7 +179,7 @@ public void testParseUnicodeName_DeleteFile() { assertTrue(fh.hasMetaDataChanges()); assertSame(FileMode.REGULAR_FILE, fh.getOldMode()); - assertNull(fh.getNewMode()); + assertSame(FileMode.MISSING, fh.getNewMode()); assertEquals("7898192", fh.getOldId().name()); assertEquals("0000000", fh.getNewId().name()); diff --git a/org.spearce.jgit.test/tst/org/spearce/jgit/patch/PatchTest.java b/org.spearce.jgit.test/tst/org/spearce/jgit/patch/PatchTest.java index 13eab5f..2c617d3 100644 --- a/org.spearce.jgit.test/tst/org/spearce/jgit/patch/PatchTest.java +++ b/org.spearce.jgit.test/tst/org/spearce/jgit/patch/PatchTest.java @@ -160,6 +160,7 @@ public void testParse_NoBinary() throws IOException { assertNotNull(fh.getOldId()); assertNotNull(fh.getNewId()); assertEquals("0000000", fh.getOldId().name()); + assertSame(FileMode.MISSING, fh.getOldMode()); assertSame(FileMode.REGULAR_FILE, fh.getNewMode()); assertTrue(fh.getNewName().startsWith( "org.spearce.egit.ui/icons/toolbar/")); diff --git a/org.spearce.jgit/src/org/spearce/jgit/patch/FileHeader.java b/org.spearce.jgit/src/org/spearce/jgit/patch/FileHeader.java index f93129d..48d7623 100644 --- a/org.spearce.jgit/src/org/spearce/jgit/patch/FileHeader.java +++ b/org.spearce.jgit/src/org/spearce/jgit/patch/FileHeader.java @@ -386,9 +386,11 @@ int parseGitHeaders(int ptr, final int end) { } else if (match(buf, ptr, DELETED_FILE_MODE) >= 0) { oldMode = parseFileMode(ptr + DELETED_FILE_MODE.length, eol); + newMode = FileMode.MISSING; changeType = ChangeType.DELETE; } else if (match(buf, ptr, NEW_FILE_MODE) >= 0) { + oldMode = FileMode.MISSING; newMode = parseFileMode(ptr + NEW_FILE_MODE.length, eol); changeType = ChangeType.ADD; -- 1.6.1.rc2.306.ge5d5e ^ permalink raw reply related [flat|nested] 14+ messages in thread
* [JGIT PATCH 11/12] Add a test for delta binary patch parsing and fix a bug in it 2008-12-12 22:05 ` [JGIT PATCH 10/12] Use FileMode.MISSING when a file is added or deleted rather than null Shawn O. Pearce @ 2008-12-12 22:05 ` Shawn O. Pearce 2008-12-12 22:05 ` [JGIT PATCH 12/12] Add support for parsing "diff --cc" style patches Shawn O. Pearce 0 siblings, 1 reply; 14+ messages in thread From: Shawn O. Pearce @ 2008-12-12 22:05 UTC (permalink / raw) To: Robin Rosenberg; +Cc: git We had the wrong header code in this case, so we didn't parse the length correctly for delta style binary hunks. Without a test case for it we never noticed the problem. Signed-off-by: Shawn O. Pearce <spearce@spearce.org> --- .../tst/org/spearce/jgit/patch/PatchTest.java | 39 +++++++++++++++++++- .../jgit/patch/testParse_GitBinaryDelta.patch | 21 +++++++++++ ...nary.patch => testParse_GitBinaryLiteral.patch} | 0 .../src/org/spearce/jgit/patch/BinaryHunk.java | 2 +- 4 files changed, 60 insertions(+), 2 deletions(-) create mode 100644 org.spearce.jgit.test/tst/org/spearce/jgit/patch/testParse_GitBinaryDelta.patch rename org.spearce.jgit.test/tst/org/spearce/jgit/patch/{testParse_GitBinary.patch => testParse_GitBinaryLiteral.patch} (100%) diff --git a/org.spearce.jgit.test/tst/org/spearce/jgit/patch/PatchTest.java b/org.spearce.jgit.test/tst/org/spearce/jgit/patch/PatchTest.java index 2c617d3..8309951 100644 --- a/org.spearce.jgit.test/tst/org/spearce/jgit/patch/PatchTest.java +++ b/org.spearce.jgit.test/tst/org/spearce/jgit/patch/PatchTest.java @@ -184,7 +184,7 @@ assertTrue(fh.getNewName().startsWith( assertEquals(272, fh.getHunks().get(0).getOldImage().getStartLine()); } - public void testParse_GitBinary() throws IOException { + public void testParse_GitBinaryLiteral() throws IOException { final Patch p = parseTestPatchFile(); final int[] binsizes = { 359, 393, 372, 404 }; assertEquals(5, p.getFiles().size()); @@ -229,6 +229,43 @@ assertTrue(fh.getNewName().startsWith( assertEquals(272, fh.getHunks().get(0).getOldImage().getStartLine()); } + public void testParse_GitBinaryDelta() throws IOException { + final Patch p = parseTestPatchFile(); + assertEquals(1, p.getFiles().size()); + assertTrue(p.getErrors().isEmpty()); + + final FileHeader fh = p.getFiles().get(0); + assertTrue(fh.getNewName().startsWith("zero.bin")); + assertSame(FileHeader.ChangeType.MODIFY, fh.getChangeType()); + assertSame(FileHeader.PatchType.GIT_BINARY, fh.getPatchType()); + assertSame(FileMode.REGULAR_FILE, fh.getNewMode()); + + assertNotNull(fh.getOldId()); + assertNotNull(fh.getNewId()); + assertEquals("08e7df176454f3ee5eeda13efa0adaa54828dfd8", fh.getOldId() + .name()); + assertEquals("d70d8710b6d32ff844af0ee7c247e4b4b051867f", fh.getNewId() + .name()); + + assertTrue(fh.getHunks().isEmpty()); + assertFalse(fh.hasMetaDataChanges()); + + final BinaryHunk fwd = fh.getForwardBinaryHunk(); + final BinaryHunk rev = fh.getReverseBinaryHunk(); + assertNotNull(fwd); + assertNotNull(rev); + assertEquals(12, fwd.getSize()); + assertEquals(11, rev.getSize()); + + assertSame(fh, fwd.getFileHeader()); + assertSame(fh, rev.getFileHeader()); + + assertSame(BinaryHunk.Type.DELTA_DEFLATED, fwd.getType()); + assertSame(BinaryHunk.Type.DELTA_DEFLATED, rev.getType()); + + assertEquals(496, fh.endOffset); + } + public void testParse_FixNoNewline() throws IOException { final Patch p = parseTestPatchFile(); assertEquals(1, p.getFiles().size()); diff --git a/org.spearce.jgit.test/tst/org/spearce/jgit/patch/testParse_GitBinaryDelta.patch b/org.spearce.jgit.test/tst/org/spearce/jgit/patch/testParse_GitBinaryDelta.patch new file mode 100644 index 0000000..5b2c9c6 --- /dev/null +++ b/org.spearce.jgit.test/tst/org/spearce/jgit/patch/testParse_GitBinaryDelta.patch @@ -0,0 +1,21 @@ +From 7e49721ad0efdec3a81e20bc58e385ea5d2b87b7 Mon Sep 17 00:00:00 2001 +From: Shawn O. Pearce <sop@google.com> +Date: Fri, 12 Dec 2008 12:45:17 -0800 +Subject: [PATCH] make zero have a 3 + +--- + zero.bin | Bin 4096 -> 4096 bytes + 1 files changed, 0 insertions(+), 0 deletions(-) + +diff --git a/zero.bin b/zero.bin +index 08e7df176454f3ee5eeda13efa0adaa54828dfd8..d70d8710b6d32ff844af0ee7c247e4b4b051867f 100644 +GIT binary patch +delta 12 +TcmZorXi%6C%4ociaTPxR8IA+R + +delta 11 +ScmZorXi(Uguz-JJK>`37u>@iO + +-- +1.6.1.rc2.306.ge5d5e + diff --git a/org.spearce.jgit.test/tst/org/spearce/jgit/patch/testParse_GitBinary.patch b/org.spearce.jgit.test/tst/org/spearce/jgit/patch/testParse_GitBinaryLiteral.patch similarity index 100% rename from org.spearce.jgit.test/tst/org/spearce/jgit/patch/testParse_GitBinary.patch rename to org.spearce.jgit.test/tst/org/spearce/jgit/patch/testParse_GitBinaryLiteral.patch diff --git a/org.spearce.jgit/src/org/spearce/jgit/patch/BinaryHunk.java b/org.spearce.jgit/src/org/spearce/jgit/patch/BinaryHunk.java index 3e07ec4..92eab86 100644 --- a/org.spearce.jgit/src/org/spearce/jgit/patch/BinaryHunk.java +++ b/org.spearce.jgit/src/org/spearce/jgit/patch/BinaryHunk.java @@ -100,7 +100,7 @@ int parseHunk(int ptr, final int end) { } else if (match(buf, ptr, DELTA) >= 0) { type = Type.DELTA_DEFLATED; - length = parseBase10(buf, ptr + LITERAL.length, null); + length = parseBase10(buf, ptr + DELTA.length, null); } else { // Not a valid binary hunk. Signal to the caller that -- 1.6.1.rc2.306.ge5d5e ^ permalink raw reply related [flat|nested] 14+ messages in thread
* [JGIT PATCH 12/12] Add support for parsing "diff --cc" style patches 2008-12-12 22:05 ` [JGIT PATCH 11/12] Add a test for delta binary patch parsing and fix a bug in it Shawn O. Pearce @ 2008-12-12 22:05 ` Shawn O. Pearce 2008-12-12 23:19 ` [JGIT PATCH 12/12 v2] " Shawn O. Pearce 0 siblings, 1 reply; 14+ messages in thread From: Shawn O. Pearce @ 2008-12-12 22:05 UTC (permalink / raw) To: Robin Rosenberg; +Cc: git Even though the diff --cc format used by Git is only meant to be read by humans, JGit needs to be able to parse these to get the patch metadata so it can be shown in a user interface to facilitate code review processes. Patches are parsed into the specialized CombinedFileHeader and CombinedHunkHeader classes, where the old image information is augmented with additional fields for the arbitrary number of parents that can appear in such patches. These cost more in terms of memory, but "diff --cc" style patches tend to occur very infrequently as they only occur during a merge conflict resolution. Signed-off-by: Shawn O. Pearce <spearce@spearce.org> --- .../org/spearce/jgit/patch/PatchCcErrorTest.java | 97 +++++++++ .../tst/org/spearce/jgit/patch/PatchCcTest.java | 200 ++++++++++++++++++ .../jgit/patch/testError_CcTruncatedOld.patch | 24 +++ .../jgit/patch/testParse_CcDeleteFile.patch | 12 + .../spearce/jgit/patch/testParse_CcNewFile.patch | 14 ++ .../spearce/jgit/patch/testParse_OneFileCc.patch | 27 +++ .../org/spearce/jgit/patch/CombinedFileHeader.java | 213 ++++++++++++++++++++ .../org/spearce/jgit/patch/CombinedHunkHeader.java | 191 ++++++++++++++++++ .../src/org/spearce/jgit/patch/FileHeader.java | 56 +++--- .../src/org/spearce/jgit/patch/HunkHeader.java | 2 +- .../src/org/spearce/jgit/patch/Patch.java | 7 +- 11 files changed, 814 insertions(+), 29 deletions(-) create mode 100644 org.spearce.jgit.test/tst/org/spearce/jgit/patch/PatchCcErrorTest.java create mode 100644 org.spearce.jgit.test/tst/org/spearce/jgit/patch/PatchCcTest.java create mode 100644 org.spearce.jgit.test/tst/org/spearce/jgit/patch/testError_CcTruncatedOld.patch create mode 100644 org.spearce.jgit.test/tst/org/spearce/jgit/patch/testParse_CcDeleteFile.patch create mode 100644 org.spearce.jgit.test/tst/org/spearce/jgit/patch/testParse_CcNewFile.patch create mode 100644 org.spearce.jgit.test/tst/org/spearce/jgit/patch/testParse_OneFileCc.patch create mode 100644 org.spearce.jgit/src/org/spearce/jgit/patch/CombinedFileHeader.java create mode 100644 org.spearce.jgit/src/org/spearce/jgit/patch/CombinedHunkHeader.java diff --git a/org.spearce.jgit.test/tst/org/spearce/jgit/patch/PatchCcErrorTest.java b/org.spearce.jgit.test/tst/org/spearce/jgit/patch/PatchCcErrorTest.java new file mode 100644 index 0000000..a2f3a19 --- /dev/null +++ b/org.spearce.jgit.test/tst/org/spearce/jgit/patch/PatchCcErrorTest.java @@ -0,0 +1,97 @@ +/* + * Copyright (C) 2008, Google Inc. + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * - Neither the name of the Git Development Community nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package org.spearce.jgit.patch; + +import java.io.IOException; +import java.io.InputStream; + +import junit.framework.TestCase; + +public class PatchCcErrorTest extends TestCase { + public void testError_CcTruncatedOld() throws IOException { + final Patch p = parseTestPatchFile(); + assertEquals(1, p.getFiles().size()); + assertEquals(3, p.getErrors().size()); + { + final FormatError e = p.getErrors().get(0); + assertSame(FormatError.Severity.ERROR, e.getSeverity()); + assertEquals( + "Truncated hunk, at least 1 lines is missing for ancestor 1", + e.getMessage()); + assertEquals(346, e.getOffset()); + assertTrue(e.getLineText().startsWith( + "@@@ -55,12 -163,13 +163,15 @@@ public ")); + } + { + final FormatError e = p.getErrors().get(1); + assertSame(FormatError.Severity.ERROR, e.getSeverity()); + assertEquals( + "Truncated hunk, at least 2 lines is missing for ancestor 2", + e.getMessage()); + assertEquals(346, e.getOffset()); + assertTrue(e.getLineText().startsWith( + "@@@ -55,12 -163,13 +163,15 @@@ public ")); + } + { + final FormatError e = p.getErrors().get(2); + assertSame(FormatError.Severity.ERROR, e.getSeverity()); + assertEquals("Truncated hunk, at least 3 new lines is missing", e + .getMessage()); + assertEquals(346, e.getOffset()); + assertTrue(e.getLineText().startsWith( + "@@@ -55,12 -163,13 +163,15 @@@ public ")); + } + } + + private Patch parseTestPatchFile() throws IOException { + final String patchFile = getName() + ".patch"; + final InputStream in = getClass().getResourceAsStream(patchFile); + if (in == null) { + fail("No " + patchFile + " test vector"); + return null; // Never happens + } + try { + final Patch p = new Patch(); + p.parse(in); + return p; + } finally { + in.close(); + } + } + +} diff --git a/org.spearce.jgit.test/tst/org/spearce/jgit/patch/PatchCcTest.java b/org.spearce.jgit.test/tst/org/spearce/jgit/patch/PatchCcTest.java new file mode 100644 index 0000000..9e8650b --- /dev/null +++ b/org.spearce.jgit.test/tst/org/spearce/jgit/patch/PatchCcTest.java @@ -0,0 +1,200 @@ +/* + * Copyright (C) 2008, Google Inc. + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * - Neither the name of the Git Development Community nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package org.spearce.jgit.patch; + +import java.io.IOException; +import java.io.InputStream; + +import org.spearce.jgit.lib.FileMode; + +import junit.framework.TestCase; + +public class PatchCcTest extends TestCase { + public void testParse_OneFileCc() throws IOException { + final Patch p = parseTestPatchFile(); + assertEquals(1, p.getFiles().size()); + assertTrue(p.getErrors().isEmpty()); + + final CombinedFileHeader cfh = (CombinedFileHeader) p.getFiles().get(0); + + assertEquals("org.spearce.egit.ui/src/org/spearce/egit/ui/UIText.java", + cfh.getNewName()); + assertEquals(cfh.getNewName(), cfh.getOldName()); + + assertEquals(98, cfh.startOffset); + + assertEquals(2, cfh.getParentCount()); + assertSame(cfh.getOldId(0), cfh.getOldId()); + assertEquals("169356b", cfh.getOldId(0).name()); + assertEquals("dd8c317", cfh.getOldId(1).name()); + assertEquals("fd85931", cfh.getNewId().name()); + + assertSame(cfh.getOldMode(0), cfh.getOldMode()); + assertSame(FileMode.REGULAR_FILE, cfh.getOldMode(0)); + assertSame(FileMode.REGULAR_FILE, cfh.getOldMode(1)); + assertSame(FileMode.EXECUTABLE_FILE, cfh.getNewMode()); + assertSame(FileHeader.ChangeType.MODIFY, cfh.getChangeType()); + assertSame(FileHeader.PatchType.UNIFIED, cfh.getPatchType()); + + assertEquals(1, cfh.getHunks().size()); + { + final CombinedHunkHeader h = cfh.getHunks().get(0); + + assertSame(cfh, h.getFileHeader()); + assertEquals(346, h.startOffset); + assertEquals(764, h.endOffset); + + assertSame(h.getOldImage(0), h.getOldImage()); + assertSame(cfh.getOldId(0), h.getOldImage(0).getId()); + assertSame(cfh.getOldId(1), h.getOldImage(1).getId()); + + assertEquals(55, h.getOldImage(0).getStartLine()); + assertEquals(12, h.getOldImage(0).getLineCount()); + assertEquals(3, h.getOldImage(0).getLinesAdded()); + assertEquals(0, h.getOldImage(0).getLinesDeleted()); + + assertEquals(163, h.getOldImage(1).getStartLine()); + assertEquals(13, h.getOldImage(1).getLineCount()); + assertEquals(2, h.getOldImage(1).getLinesAdded()); + assertEquals(0, h.getOldImage(1).getLinesDeleted()); + + assertEquals(163, h.getNewStartLine()); + assertEquals(15, h.getNewLineCount()); + + assertEquals(10, h.getLinesContext()); + } + } + + public void testParse_CcNewFile() throws IOException { + final Patch p = parseTestPatchFile(); + assertEquals(1, p.getFiles().size()); + assertTrue(p.getErrors().isEmpty()); + + final CombinedFileHeader cfh = (CombinedFileHeader) p.getFiles().get(0); + + assertSame(FileHeader.DEV_NULL, cfh.getOldName()); + assertEquals("d", cfh.getNewName()); + + assertEquals(187, cfh.startOffset); + + assertEquals(2, cfh.getParentCount()); + assertSame(cfh.getOldId(0), cfh.getOldId()); + assertEquals("0000000", cfh.getOldId(0).name()); + assertEquals("0000000", cfh.getOldId(1).name()); + assertEquals("4bcfe98", cfh.getNewId().name()); + + assertSame(cfh.getOldMode(0), cfh.getOldMode()); + assertSame(FileMode.MISSING, cfh.getOldMode(0)); + assertSame(FileMode.MISSING, cfh.getOldMode(1)); + assertSame(FileMode.REGULAR_FILE, cfh.getNewMode()); + assertSame(FileHeader.ChangeType.ADD, cfh.getChangeType()); + assertSame(FileHeader.PatchType.UNIFIED, cfh.getPatchType()); + + assertEquals(1, cfh.getHunks().size()); + { + final CombinedHunkHeader h = cfh.getHunks().get(0); + + assertSame(cfh, h.getFileHeader()); + assertEquals(273, h.startOffset); + assertEquals(300, h.endOffset); + + assertSame(h.getOldImage(0), h.getOldImage()); + assertSame(cfh.getOldId(0), h.getOldImage(0).getId()); + assertSame(cfh.getOldId(1), h.getOldImage(1).getId()); + + assertEquals(1, h.getOldImage(0).getStartLine()); + assertEquals(0, h.getOldImage(0).getLineCount()); + assertEquals(1, h.getOldImage(0).getLinesAdded()); + assertEquals(0, h.getOldImage(0).getLinesDeleted()); + + assertEquals(1, h.getOldImage(1).getStartLine()); + assertEquals(0, h.getOldImage(1).getLineCount()); + assertEquals(1, h.getOldImage(1).getLinesAdded()); + assertEquals(0, h.getOldImage(1).getLinesDeleted()); + + assertEquals(1, h.getNewStartLine()); + assertEquals(1, h.getNewLineCount()); + + assertEquals(0, h.getLinesContext()); + } + } + + public void testParse_CcDeleteFile() throws IOException { + final Patch p = parseTestPatchFile(); + assertEquals(1, p.getFiles().size()); + assertTrue(p.getErrors().isEmpty()); + + final CombinedFileHeader cfh = (CombinedFileHeader) p.getFiles().get(0); + + assertEquals("a", cfh.getOldName()); + assertSame(FileHeader.DEV_NULL, cfh.getNewName()); + + assertEquals(187, cfh.startOffset); + + assertEquals(2, cfh.getParentCount()); + assertSame(cfh.getOldId(0), cfh.getOldId()); + assertEquals("7898192", cfh.getOldId(0).name()); + assertEquals("2e65efe", cfh.getOldId(1).name()); + assertEquals("0000000", cfh.getNewId().name()); + + assertSame(cfh.getOldMode(0), cfh.getOldMode()); + assertSame(FileMode.REGULAR_FILE, cfh.getOldMode(0)); + assertSame(FileMode.REGULAR_FILE, cfh.getOldMode(1)); + assertSame(FileMode.MISSING, cfh.getNewMode()); + assertSame(FileHeader.ChangeType.DELETE, cfh.getChangeType()); + assertSame(FileHeader.PatchType.UNIFIED, cfh.getPatchType()); + + assertTrue(cfh.getHunks().isEmpty()); + } + + private Patch parseTestPatchFile() throws IOException { + final String patchFile = getName() + ".patch"; + final InputStream in = getClass().getResourceAsStream(patchFile); + if (in == null) { + fail("No " + patchFile + " test vector"); + return null; // Never happens + } + try { + final Patch p = new Patch(); + p.parse(in); + return p; + } finally { + in.close(); + } + } +} diff --git a/org.spearce.jgit.test/tst/org/spearce/jgit/patch/testError_CcTruncatedOld.patch b/org.spearce.jgit.test/tst/org/spearce/jgit/patch/testError_CcTruncatedOld.patch new file mode 100644 index 0000000..1bbcfb5 --- /dev/null +++ b/org.spearce.jgit.test/tst/org/spearce/jgit/patch/testError_CcTruncatedOld.patch @@ -0,0 +1,24 @@ +commit 1a56639bbea8e8cbfbe5da87746de97f9217ce9b +Date: Tue May 13 00:43:56 2008 +0200 + ... + +diff --cc org.spearce.egit.ui/src/org/spearce/egit/ui/UIText.java +index 169356b,dd8c317..fd85931 +mode 100644,100644..100755 +--- a/org.spearce.egit.ui/src/org/spearce/egit/ui/UIText.java ++++ b/org.spearce.egit.ui/src/org/spearce/egit/ui/UIText.java +@@@ -55,12 -163,13 +163,15 @@@ public class UIText extends NLS + + /** */ + public static String ResourceHistory_toggleCommentWrap; ++ + /** */ + + /** */ + public static String ResourceHistory_toggleRevDetail; + /** */ + public static String ResourceHistory_toggleRevComment; + /** */ + public static String ResourceHistory_toggleTooltips; + + +commit 1a56639bbea8e8cbfbe5da87746de97f9217ce9b diff --git a/org.spearce.jgit.test/tst/org/spearce/jgit/patch/testParse_CcDeleteFile.patch b/org.spearce.jgit.test/tst/org/spearce/jgit/patch/testParse_CcDeleteFile.patch new file mode 100644 index 0000000..2654e09 --- /dev/null +++ b/org.spearce.jgit.test/tst/org/spearce/jgit/patch/testParse_CcDeleteFile.patch @@ -0,0 +1,12 @@ +commit 740709ece2412856c0c3eabd4dc4a4cf115b0de6 +Merge: 5c19b43... 13a2c0d... +Author: Shawn O. Pearce <sop@google.com> +Date: Fri Dec 12 13:26:52 2008 -0800 + + Merge branch 'b' into d + +diff --cc a +index 7898192,2e65efe..0000000 +deleted file mode 100644,100644 +--- a/a ++++ /dev/null diff --git a/org.spearce.jgit.test/tst/org/spearce/jgit/patch/testParse_CcNewFile.patch b/org.spearce.jgit.test/tst/org/spearce/jgit/patch/testParse_CcNewFile.patch new file mode 100644 index 0000000..1a9b7b0 --- /dev/null +++ b/org.spearce.jgit.test/tst/org/spearce/jgit/patch/testParse_CcNewFile.patch @@ -0,0 +1,14 @@ +commit 6cb8160a4717d51fd3cc0baf721946daa60cf921 +Merge: 5c19b43... 13a2c0d... +Author: Shawn O. Pearce <sop@google.com> +Date: Fri Dec 12 13:26:52 2008 -0800 + + Merge branch 'b' into d + +diff --cc d +index 0000000,0000000..4bcfe98 +new file mode 100644 +--- /dev/null ++++ b/d +@@@ -1,0 -1,0 +1,1 @@@ +++d diff --git a/org.spearce.jgit.test/tst/org/spearce/jgit/patch/testParse_OneFileCc.patch b/org.spearce.jgit.test/tst/org/spearce/jgit/patch/testParse_OneFileCc.patch new file mode 100644 index 0000000..c096b33 --- /dev/null +++ b/org.spearce.jgit.test/tst/org/spearce/jgit/patch/testParse_OneFileCc.patch @@ -0,0 +1,27 @@ +commit 1a56639bbea8e8cbfbe5da87746de97f9217ce9b +Date: Tue May 13 00:43:56 2008 +0200 + ... + +diff --cc org.spearce.egit.ui/src/org/spearce/egit/ui/UIText.java +index 169356b,dd8c317..fd85931 +mode 100644,100644..100755 +--- a/org.spearce.egit.ui/src/org/spearce/egit/ui/UIText.java ++++ b/org.spearce.egit.ui/src/org/spearce/egit/ui/UIText.java +@@@ -55,12 -163,13 +163,15 @@@ public class UIText extends NLS + + /** */ + public static String ResourceHistory_toggleCommentWrap; ++ + /** */ + + public static String ResourceHistory_toggleCommentFill; + + /** */ + public static String ResourceHistory_toggleRevDetail; ++ + /** */ + public static String ResourceHistory_toggleRevComment; ++ + /** */ + public static String ResourceHistory_toggleTooltips; + + +commit 1a56639bbea8e8cbfbe5da87746de97f9217ce9b diff --git a/org.spearce.jgit/src/org/spearce/jgit/patch/CombinedFileHeader.java b/org.spearce.jgit/src/org/spearce/jgit/patch/CombinedFileHeader.java new file mode 100644 index 0000000..7171600 --- /dev/null +++ b/org.spearce.jgit/src/org/spearce/jgit/patch/CombinedFileHeader.java @@ -0,0 +1,213 @@ +/* + * Copyright (C) 2008, Google Inc. + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * - Neither the name of the Git Development Community nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package org.spearce.jgit.patch; + +import static org.spearce.jgit.lib.Constants.encodeASCII; +import static org.spearce.jgit.util.RawParseUtils.match; +import static org.spearce.jgit.util.RawParseUtils.nextLF; + +import java.util.ArrayList; +import java.util.List; + +import org.spearce.jgit.lib.AbbreviatedObjectId; +import org.spearce.jgit.lib.FileMode; + +/** + * A file in the Git "diff --cc" or "diff --combined" format. + * <p> + * A combined diff shows an n-way comparison between two or more ancestors and + * the final revision. Its primary function is to perform code reviews on a + * merge which introduces changes not in any ancestor. + */ +public class CombinedFileHeader extends FileHeader { + private static final byte[] MODE = encodeASCII("mode "); + + private AbbreviatedObjectId[] oldIds; + + private FileMode[] oldModes; + + CombinedFileHeader(final byte[] b, final int offset) { + super(b, offset); + } + + @Override + @SuppressWarnings("unchecked") + public List<? extends CombinedHunkHeader> getHunks() { + return (List<CombinedHunkHeader>) super.getHunks(); + } + + /** @return number of ancestor revisions mentioned in this diff. */ + @Override + public int getParentCount() { + return oldIds.length; + } + + /** @return get the file mode of the first parent. */ + @Override + public FileMode getOldMode() { + return getOldMode(0); + } + + /** + * Get the file mode of the nth ancestor + * + * @param nthParent + * the ancestor to get the mode of + * @return the mode of the requested ancestor. + */ + public FileMode getOldMode(final int nthParent) { + return oldModes[nthParent]; + } + + /** @return get the object id of the first parent. */ + @Override + public AbbreviatedObjectId getOldId() { + return getOldId(0); + } + + /** + * Get the ObjectId of the nth ancestor + * + * @param nthParent + * the ancestor to get the object id of + * @return the id of the requested ancestor. + */ + public AbbreviatedObjectId getOldId(final int nthParent) { + return oldIds[nthParent]; + } + + int parseGitHeaders(int ptr, final int end) { + while (ptr < end) { + final int eol = nextLF(buf, ptr); + if (isHunkHdr(buf, ptr, end) >= 1) { + // First hunk header; break out and parse them later. + break; + + } else if (match(buf, ptr, OLD_NAME) >= 0) { + parseOldName(ptr, eol); + + } else if (match(buf, ptr, NEW_NAME) >= 0) { + parseNewName(ptr, eol); + + } else if (match(buf, ptr, INDEX) >= 0) { + parseIndexLine(ptr + INDEX.length, eol); + + } else if (match(buf, ptr, MODE) >= 0) { + parseModeLine(ptr + MODE.length, eol); + + } else if (match(buf, ptr, NEW_FILE_MODE) >= 0) { + parseNewFileMode(ptr, eol); + + } else if (match(buf, ptr, DELETED_FILE_MODE) >= 0) { + parseDeletedFileMode(ptr + DELETED_FILE_MODE.length, eol); + + } else { + // Probably an empty patch (stat dirty). + break; + } + + ptr = eol; + } + return ptr; + } + + @Override + protected void parseIndexLine(int ptr, final int eol) { + // "index $asha1,$bsha1..$csha1" + // + final List<AbbreviatedObjectId> ids = new ArrayList<AbbreviatedObjectId>(); + while (ptr < eol) { + final int comma = nextLF(buf, ptr, ','); + if (eol <= comma) + break; + ids.add(AbbreviatedObjectId.fromString(buf, ptr, comma - 1)); + ptr = comma; + } + + oldIds = new AbbreviatedObjectId[ids.size() + 1]; + ids.toArray(oldIds); + final int dot2 = nextLF(buf, ptr, '.'); + oldIds[ids.size()] = AbbreviatedObjectId.fromString(buf, ptr, dot2 - 1); + newId = AbbreviatedObjectId.fromString(buf, dot2 + 1, eol - 1); + oldModes = new FileMode[oldIds.length]; + } + + @Override + protected void parseNewFileMode(final int ptr, final int eol) { + for (int i = 0; i < oldModes.length; i++) + oldModes[i] = FileMode.MISSING; + super.parseNewFileMode(ptr, eol); + } + + @Override + HunkHeader newHunkHeader(final int offset) { + return new CombinedHunkHeader(this, offset); + } + + private void parseModeLine(int ptr, final int eol) { + // "mode $amode,$bmode..$cmode" + // + int n = 0; + while (ptr < eol) { + final int comma = nextLF(buf, ptr, ','); + if (eol <= comma) + break; + oldModes[n++] = parseFileMode(ptr, comma); + ptr = comma; + } + final int dot2 = nextLF(buf, ptr, '.'); + oldModes[n] = parseFileMode(ptr, dot2); + newMode = parseFileMode(dot2 + 1, eol); + } + + private void parseDeletedFileMode(int ptr, final int eol) { + // "deleted file mode $amode,$bmode" + // + changeType = ChangeType.DELETE; + int n = 0; + while (ptr < eol) { + final int comma = nextLF(buf, ptr, ','); + if (eol <= comma) + break; + oldModes[n++] = parseFileMode(ptr, comma); + ptr = comma; + } + oldModes[n] = parseFileMode(ptr, eol); + newMode = FileMode.MISSING; + } +} diff --git a/org.spearce.jgit/src/org/spearce/jgit/patch/CombinedHunkHeader.java b/org.spearce.jgit/src/org/spearce/jgit/patch/CombinedHunkHeader.java new file mode 100644 index 0000000..bebeafa --- /dev/null +++ b/org.spearce.jgit/src/org/spearce/jgit/patch/CombinedHunkHeader.java @@ -0,0 +1,191 @@ +/* + * Copyright (C) 2008, Google Inc. + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * - Neither the name of the Git Development Community nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package org.spearce.jgit.patch; + +import static org.spearce.jgit.util.RawParseUtils.nextLF; +import static org.spearce.jgit.util.RawParseUtils.parseBase10; + +import org.spearce.jgit.lib.AbbreviatedObjectId; +import org.spearce.jgit.util.MutableInteger; + +/** Hunk header for a hunk appearing in a "diff --cc" style patch. */ +public class CombinedHunkHeader extends HunkHeader { + private static abstract class CombinedOldImage extends OldImage { + int nContext; + } + + private CombinedOldImage[] old; + + CombinedHunkHeader(final CombinedFileHeader fh, final int offset) { + super(fh, offset, null); + old = new CombinedOldImage[fh.getParentCount()]; + for (int i = 0; i < old.length; i++) { + final int imagePos = i; + old[i] = new CombinedOldImage() { + @Override + public AbbreviatedObjectId getId() { + return fh.getOldId(imagePos); + } + }; + } + } + + @Override + public CombinedFileHeader getFileHeader() { + return (CombinedFileHeader) super.getFileHeader(); + } + + @Override + public OldImage getOldImage() { + return getOldImage(0); + } + + /** + * Get the OldImage data related to the nth ancestor + * + * @param nthParent + * the ancestor to get the old image data of + * @return image data of the requested ancestor. + */ + public OldImage getOldImage(final int nthParent) { + return old[nthParent]; + } + + @Override + void parseHeader(final int end) { + // Parse "@@@ -55,12 -163,13 +163,15 @@@ protected boolean" + // + final byte[] buf = file.buf; + final MutableInteger ptr = new MutableInteger(); + ptr.value = nextLF(buf, startOffset, ' '); + + for (int n = 0; n < old.length; n++) { + old[n].startLine = -parseBase10(buf, ptr.value, ptr); + if (buf[ptr.value] == ',') + old[n].lineCount = parseBase10(buf, ptr.value + 1, ptr); + else + old[n].lineCount = 1; + } + + newStartLine = parseBase10(buf, ptr.value + 1, ptr); + if (buf[ptr.value] == ',') + newLineCount = parseBase10(buf, ptr.value + 1, ptr); + else + newLineCount = 1; + } + + @Override + int parseBody(final Patch script, final int end) { + final byte[] buf = file.buf; + int c = nextLF(buf, startOffset); + + for (final CombinedOldImage o : old) { + o.nDeleted = 0; + o.nAdded = 0; + o.nContext = 0; + } + nContext = 0; + int nAdded = 0; + + SCAN: for (int eol; c < end; c = eol) { + eol = nextLF(buf, c); + + if (eol - c < old.length + 1) { + // Line isn't long enough to mention the state of each + // ancestor. It must be the end of the hunk. + break SCAN; + } + + switch (buf[c]) { + case ' ': + case '-': + case '+': + break; + + default: + // Line can't possibly be part of this hunk; the first + // ancestor information isn't recognizable. + // + break SCAN; + } + + int localcontext = 0; + for (int ancestor = 0; ancestor < old.length; ancestor++) { + switch (buf[c + ancestor]) { + case ' ': + localcontext++; + old[ancestor].nContext++; + continue; + + case '-': + old[ancestor].nDeleted++; + continue; + + case '+': + old[ancestor].nAdded++; + nAdded++; + continue; + + default: + break SCAN; + } + } + if (localcontext == old.length) + nContext++; + } + + for (int ancestor = 0; ancestor < old.length; ancestor++) { + final CombinedOldImage o = old[ancestor]; + final int cmp = o.nContext + o.nDeleted; + if (cmp < o.lineCount) { + final int missingCnt = o.lineCount - cmp; + script.error(buf, startOffset, "Truncated hunk, at least " + + missingCnt + " lines is missing for ancestor " + + (ancestor + 1)); + } + } + + if (nContext + nAdded < newLineCount) { + final int missingCount = newLineCount - (nContext + nAdded); + script.error(buf, startOffset, "Truncated hunk, at least " + + missingCount + " new lines is missing"); + } + + return c; + } +} diff --git a/org.spearce.jgit/src/org/spearce/jgit/patch/FileHeader.java b/org.spearce.jgit/src/org/spearce/jgit/patch/FileHeader.java index 48d7623..79e4b0a 100644 --- a/org.spearce.jgit/src/org/spearce/jgit/patch/FileHeader.java +++ b/org.spearce.jgit/src/org/spearce/jgit/patch/FileHeader.java @@ -61,9 +61,9 @@ private static final byte[] NEW_MODE = encodeASCII("new mode "); - private static final byte[] DELETED_FILE_MODE = encodeASCII("deleted file mode "); + protected static final byte[] DELETED_FILE_MODE = encodeASCII("deleted file mode "); - private static final byte[] NEW_FILE_MODE = encodeASCII("new file mode "); + protected static final byte[] NEW_FILE_MODE = encodeASCII("new file mode "); private static final byte[] COPY_FROM = encodeASCII("copy from "); @@ -81,7 +81,7 @@ private static final byte[] DISSIMILARITY_INDEX = encodeASCII("dissimilarity index "); - private static final byte[] INDEX = encodeASCII("index "); + protected static final byte[] INDEX = encodeASCII("index "); static final byte[] OLD_NAME = encodeASCII("--- "); @@ -136,10 +136,10 @@ private FileMode oldMode; /** New mode of the file, if described by the patch, else null. */ - private FileMode newMode; + protected FileMode newMode; /** General type of change indicated by the patch. */ - private ChangeType changeType; + protected ChangeType changeType; /** Similarity score if {@link #changeType} is a copy or rename. */ private int score; @@ -148,7 +148,7 @@ private AbbreviatedObjectId oldId; /** ObjectId listed on the index line for the new (post-image) */ - private AbbreviatedObjectId newId; + protected AbbreviatedObjectId newId; /** Type of patch used to modify this file */ PatchType patchType; @@ -264,7 +264,7 @@ public boolean hasMetaDataChanges() { } /** @return hunks altering this file; in order of appearance in patch */ - public List<HunkHeader> getHunks() { + public List<? extends HunkHeader> getHunks() { if (hunks == null) return Collections.emptyList(); return hunks; @@ -369,14 +369,10 @@ int parseGitHeaders(int ptr, final int end) { break; } else if (match(buf, ptr, OLD_NAME) >= 0) { - oldName = p1(parseName(oldName, ptr + OLD_NAME.length, eol)); - if (oldName == DEV_NULL) - changeType = ChangeType.ADD; + parseOldName(ptr, eol); } else if (match(buf, ptr, NEW_NAME) >= 0) { - newName = p1(parseName(newName, ptr + NEW_NAME.length, eol)); - if (newName == DEV_NULL) - changeType = ChangeType.DELETE; + parseNewName(ptr, eol); } else if (match(buf, ptr, OLD_MODE) >= 0) { oldMode = parseFileMode(ptr + OLD_MODE.length, eol); @@ -390,9 +386,7 @@ int parseGitHeaders(int ptr, final int end) { changeType = ChangeType.DELETE; } else if (match(buf, ptr, NEW_FILE_MODE) >= 0) { - oldMode = FileMode.MISSING; - newMode = parseFileMode(ptr + NEW_FILE_MODE.length, eol); - changeType = ChangeType.ADD; + parseNewFileMode(ptr, eol); } else if (match(buf, ptr, COPY_FROM) >= 0) { oldName = parseName(oldName, ptr + COPY_FROM.length, eol); @@ -437,6 +431,24 @@ int parseGitHeaders(int ptr, final int end) { return ptr; } + protected void parseOldName(int ptr, final int eol) { + oldName = p1(parseName(oldName, ptr + OLD_NAME.length, eol)); + if (oldName == DEV_NULL) + changeType = ChangeType.ADD; + } + + protected void parseNewName(int ptr, final int eol) { + newName = p1(parseName(newName, ptr + NEW_NAME.length, eol)); + if (newName == DEV_NULL) + changeType = ChangeType.DELETE; + } + + protected void parseNewFileMode(int ptr, final int eol) { + oldMode = FileMode.MISSING; + newMode = parseFileMode(ptr + NEW_FILE_MODE.length, eol); + changeType = ChangeType.ADD; + } + int parseTraditionalHeaders(int ptr, final int end) { while (ptr < end) { final int eol = nextLF(buf, ptr); @@ -445,14 +457,10 @@ int parseTraditionalHeaders(int ptr, final int end) { break; } else if (match(buf, ptr, OLD_NAME) >= 0) { - oldName = p1(parseName(oldName, ptr + OLD_NAME.length, eol)); - if (oldName == DEV_NULL) - changeType = ChangeType.ADD; + parseOldName(ptr, eol); } else if (match(buf, ptr, NEW_NAME) >= 0) { - newName = p1(parseName(newName, ptr + NEW_NAME.length, eol)); - if (newName == DEV_NULL) - changeType = ChangeType.DELETE; + parseNewName(ptr, eol); } else { // Possibly an empty patch. @@ -494,7 +502,7 @@ private static String p1(final String r) { return s > 0 ? r.substring(s + 1) : r; } - private FileMode parseFileMode(int ptr, final int end) { + protected FileMode parseFileMode(int ptr, final int end) { int tmp = 0; while (ptr < end - 1) { tmp <<= 3; @@ -503,7 +511,7 @@ private FileMode parseFileMode(int ptr, final int end) { return FileMode.fromBits(tmp); } - private void parseIndexLine(int ptr, final int end) { + protected void parseIndexLine(int ptr, final int end) { // "index $asha1..$bsha1[ $mode]" where $asha1 and $bsha1 // can be unique abbreviations // diff --git a/org.spearce.jgit/src/org/spearce/jgit/patch/HunkHeader.java b/org.spearce.jgit/src/org/spearce/jgit/patch/HunkHeader.java index f543aed..fc149ac 100644 --- a/org.spearce.jgit/src/org/spearce/jgit/patch/HunkHeader.java +++ b/org.spearce.jgit/src/org/spearce/jgit/patch/HunkHeader.java @@ -84,7 +84,7 @@ public int getLinesAdded() { public abstract AbbreviatedObjectId getId(); } - private final FileHeader file; + final FileHeader file; /** Offset within {@link #file}.buf to the "@@ -" line. */ final int startOffset; diff --git a/org.spearce.jgit/src/org/spearce/jgit/patch/Patch.java b/org.spearce.jgit/src/org/spearce/jgit/patch/Patch.java index 51f1fe5..f23ba69 100644 --- a/org.spearce.jgit/src/org/spearce/jgit/patch/Patch.java +++ b/org.spearce.jgit/src/org/spearce/jgit/patch/Patch.java @@ -94,7 +94,7 @@ public void addFile(final FileHeader fh) { } /** @return list of files described in the patch, in occurrence order. */ - public List<FileHeader> getFiles() { + public List<? extends FileHeader> getFiles() { return files; } @@ -238,9 +238,8 @@ private int parseDiffCombined(final byte[] hdr, final byte[] buf, if (ptr < 0) return skipFile(buf, start, end); - // TODO Support parsing diff --cc headers - // TODO parse diff --cc hunks - warn(buf, start, "diff --cc format not supported"); + ptr = fh.parseGitHeaders(ptr, end); + ptr = parseHunks(fh, ptr, end); fh.endOffset = ptr; addFile(fh); return ptr; -- 1.6.1.rc2.306.ge5d5e ^ permalink raw reply related [flat|nested] 14+ messages in thread
* [JGIT PATCH 12/12 v2] Add support for parsing "diff --cc" style patches 2008-12-12 22:05 ` [JGIT PATCH 12/12] Add support for parsing "diff --cc" style patches Shawn O. Pearce @ 2008-12-12 23:19 ` Shawn O. Pearce 0 siblings, 0 replies; 14+ messages in thread From: Shawn O. Pearce @ 2008-12-12 23:19 UTC (permalink / raw) To: Robin Rosenberg; +Cc: git Even though the diff --cc format used by Git is only meant to be read by humans, JGit needs to be able to parse these to get the patch metadata so it can be shown in a user interface to facilitate code review processes. Patches are parsed into the specialized CombinedFileHeader and CombinedHunkHeader classes, where the old image information is augmented with additional fields for the arbitrary number of parents that can appear in such patches. These cost more in terms of memory, but "diff --cc" style patches tend to occur very infrequently as they only occur during a merge conflict resolution. Signed-off-by: Shawn O. Pearce <spearce@spearce.org> --- v2 is necessary because of the CombinedFileHeader glitch in an earlier series. .../org/spearce/jgit/patch/PatchCcErrorTest.java | 97 +++++++++ .../tst/org/spearce/jgit/patch/PatchCcTest.java | 200 ++++++++++++++++++ .../jgit/patch/testError_CcTruncatedOld.patch | 24 +++ .../jgit/patch/testParse_CcDeleteFile.patch | 12 + .../spearce/jgit/patch/testParse_CcNewFile.patch | 14 ++ .../spearce/jgit/patch/testParse_OneFileCc.patch | 27 +++ .../org/spearce/jgit/patch/CombinedFileHeader.java | 213 ++++++++++++++++++++ .../org/spearce/jgit/patch/CombinedHunkHeader.java | 191 ++++++++++++++++++ .../src/org/spearce/jgit/patch/FileHeader.java | 56 +++--- .../src/org/spearce/jgit/patch/HunkHeader.java | 2 +- .../src/org/spearce/jgit/patch/Patch.java | 9 +- 11 files changed, 815 insertions(+), 30 deletions(-) create mode 100644 org.spearce.jgit.test/tst/org/spearce/jgit/patch/PatchCcErrorTest.java create mode 100644 org.spearce.jgit.test/tst/org/spearce/jgit/patch/PatchCcTest.java create mode 100644 org.spearce.jgit.test/tst/org/spearce/jgit/patch/testError_CcTruncatedOld.patch create mode 100644 org.spearce.jgit.test/tst/org/spearce/jgit/patch/testParse_CcDeleteFile.patch create mode 100644 org.spearce.jgit.test/tst/org/spearce/jgit/patch/testParse_CcNewFile.patch create mode 100644 org.spearce.jgit.test/tst/org/spearce/jgit/patch/testParse_OneFileCc.patch create mode 100644 org.spearce.jgit/src/org/spearce/jgit/patch/CombinedFileHeader.java create mode 100644 org.spearce.jgit/src/org/spearce/jgit/patch/CombinedHunkHeader.java diff --git a/org.spearce.jgit.test/tst/org/spearce/jgit/patch/PatchCcErrorTest.java b/org.spearce.jgit.test/tst/org/spearce/jgit/patch/PatchCcErrorTest.java new file mode 100644 index 0000000..a2f3a19 --- /dev/null +++ b/org.spearce.jgit.test/tst/org/spearce/jgit/patch/PatchCcErrorTest.java @@ -0,0 +1,97 @@ +/* + * Copyright (C) 2008, Google Inc. + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * - Neither the name of the Git Development Community nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package org.spearce.jgit.patch; + +import java.io.IOException; +import java.io.InputStream; + +import junit.framework.TestCase; + +public class PatchCcErrorTest extends TestCase { + public void testError_CcTruncatedOld() throws IOException { + final Patch p = parseTestPatchFile(); + assertEquals(1, p.getFiles().size()); + assertEquals(3, p.getErrors().size()); + { + final FormatError e = p.getErrors().get(0); + assertSame(FormatError.Severity.ERROR, e.getSeverity()); + assertEquals( + "Truncated hunk, at least 1 lines is missing for ancestor 1", + e.getMessage()); + assertEquals(346, e.getOffset()); + assertTrue(e.getLineText().startsWith( + "@@@ -55,12 -163,13 +163,15 @@@ public ")); + } + { + final FormatError e = p.getErrors().get(1); + assertSame(FormatError.Severity.ERROR, e.getSeverity()); + assertEquals( + "Truncated hunk, at least 2 lines is missing for ancestor 2", + e.getMessage()); + assertEquals(346, e.getOffset()); + assertTrue(e.getLineText().startsWith( + "@@@ -55,12 -163,13 +163,15 @@@ public ")); + } + { + final FormatError e = p.getErrors().get(2); + assertSame(FormatError.Severity.ERROR, e.getSeverity()); + assertEquals("Truncated hunk, at least 3 new lines is missing", e + .getMessage()); + assertEquals(346, e.getOffset()); + assertTrue(e.getLineText().startsWith( + "@@@ -55,12 -163,13 +163,15 @@@ public ")); + } + } + + private Patch parseTestPatchFile() throws IOException { + final String patchFile = getName() + ".patch"; + final InputStream in = getClass().getResourceAsStream(patchFile); + if (in == null) { + fail("No " + patchFile + " test vector"); + return null; // Never happens + } + try { + final Patch p = new Patch(); + p.parse(in); + return p; + } finally { + in.close(); + } + } + +} diff --git a/org.spearce.jgit.test/tst/org/spearce/jgit/patch/PatchCcTest.java b/org.spearce.jgit.test/tst/org/spearce/jgit/patch/PatchCcTest.java new file mode 100644 index 0000000..9e8650b --- /dev/null +++ b/org.spearce.jgit.test/tst/org/spearce/jgit/patch/PatchCcTest.java @@ -0,0 +1,200 @@ +/* + * Copyright (C) 2008, Google Inc. + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * - Neither the name of the Git Development Community nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package org.spearce.jgit.patch; + +import java.io.IOException; +import java.io.InputStream; + +import org.spearce.jgit.lib.FileMode; + +import junit.framework.TestCase; + +public class PatchCcTest extends TestCase { + public void testParse_OneFileCc() throws IOException { + final Patch p = parseTestPatchFile(); + assertEquals(1, p.getFiles().size()); + assertTrue(p.getErrors().isEmpty()); + + final CombinedFileHeader cfh = (CombinedFileHeader) p.getFiles().get(0); + + assertEquals("org.spearce.egit.ui/src/org/spearce/egit/ui/UIText.java", + cfh.getNewName()); + assertEquals(cfh.getNewName(), cfh.getOldName()); + + assertEquals(98, cfh.startOffset); + + assertEquals(2, cfh.getParentCount()); + assertSame(cfh.getOldId(0), cfh.getOldId()); + assertEquals("169356b", cfh.getOldId(0).name()); + assertEquals("dd8c317", cfh.getOldId(1).name()); + assertEquals("fd85931", cfh.getNewId().name()); + + assertSame(cfh.getOldMode(0), cfh.getOldMode()); + assertSame(FileMode.REGULAR_FILE, cfh.getOldMode(0)); + assertSame(FileMode.REGULAR_FILE, cfh.getOldMode(1)); + assertSame(FileMode.EXECUTABLE_FILE, cfh.getNewMode()); + assertSame(FileHeader.ChangeType.MODIFY, cfh.getChangeType()); + assertSame(FileHeader.PatchType.UNIFIED, cfh.getPatchType()); + + assertEquals(1, cfh.getHunks().size()); + { + final CombinedHunkHeader h = cfh.getHunks().get(0); + + assertSame(cfh, h.getFileHeader()); + assertEquals(346, h.startOffset); + assertEquals(764, h.endOffset); + + assertSame(h.getOldImage(0), h.getOldImage()); + assertSame(cfh.getOldId(0), h.getOldImage(0).getId()); + assertSame(cfh.getOldId(1), h.getOldImage(1).getId()); + + assertEquals(55, h.getOldImage(0).getStartLine()); + assertEquals(12, h.getOldImage(0).getLineCount()); + assertEquals(3, h.getOldImage(0).getLinesAdded()); + assertEquals(0, h.getOldImage(0).getLinesDeleted()); + + assertEquals(163, h.getOldImage(1).getStartLine()); + assertEquals(13, h.getOldImage(1).getLineCount()); + assertEquals(2, h.getOldImage(1).getLinesAdded()); + assertEquals(0, h.getOldImage(1).getLinesDeleted()); + + assertEquals(163, h.getNewStartLine()); + assertEquals(15, h.getNewLineCount()); + + assertEquals(10, h.getLinesContext()); + } + } + + public void testParse_CcNewFile() throws IOException { + final Patch p = parseTestPatchFile(); + assertEquals(1, p.getFiles().size()); + assertTrue(p.getErrors().isEmpty()); + + final CombinedFileHeader cfh = (CombinedFileHeader) p.getFiles().get(0); + + assertSame(FileHeader.DEV_NULL, cfh.getOldName()); + assertEquals("d", cfh.getNewName()); + + assertEquals(187, cfh.startOffset); + + assertEquals(2, cfh.getParentCount()); + assertSame(cfh.getOldId(0), cfh.getOldId()); + assertEquals("0000000", cfh.getOldId(0).name()); + assertEquals("0000000", cfh.getOldId(1).name()); + assertEquals("4bcfe98", cfh.getNewId().name()); + + assertSame(cfh.getOldMode(0), cfh.getOldMode()); + assertSame(FileMode.MISSING, cfh.getOldMode(0)); + assertSame(FileMode.MISSING, cfh.getOldMode(1)); + assertSame(FileMode.REGULAR_FILE, cfh.getNewMode()); + assertSame(FileHeader.ChangeType.ADD, cfh.getChangeType()); + assertSame(FileHeader.PatchType.UNIFIED, cfh.getPatchType()); + + assertEquals(1, cfh.getHunks().size()); + { + final CombinedHunkHeader h = cfh.getHunks().get(0); + + assertSame(cfh, h.getFileHeader()); + assertEquals(273, h.startOffset); + assertEquals(300, h.endOffset); + + assertSame(h.getOldImage(0), h.getOldImage()); + assertSame(cfh.getOldId(0), h.getOldImage(0).getId()); + assertSame(cfh.getOldId(1), h.getOldImage(1).getId()); + + assertEquals(1, h.getOldImage(0).getStartLine()); + assertEquals(0, h.getOldImage(0).getLineCount()); + assertEquals(1, h.getOldImage(0).getLinesAdded()); + assertEquals(0, h.getOldImage(0).getLinesDeleted()); + + assertEquals(1, h.getOldImage(1).getStartLine()); + assertEquals(0, h.getOldImage(1).getLineCount()); + assertEquals(1, h.getOldImage(1).getLinesAdded()); + assertEquals(0, h.getOldImage(1).getLinesDeleted()); + + assertEquals(1, h.getNewStartLine()); + assertEquals(1, h.getNewLineCount()); + + assertEquals(0, h.getLinesContext()); + } + } + + public void testParse_CcDeleteFile() throws IOException { + final Patch p = parseTestPatchFile(); + assertEquals(1, p.getFiles().size()); + assertTrue(p.getErrors().isEmpty()); + + final CombinedFileHeader cfh = (CombinedFileHeader) p.getFiles().get(0); + + assertEquals("a", cfh.getOldName()); + assertSame(FileHeader.DEV_NULL, cfh.getNewName()); + + assertEquals(187, cfh.startOffset); + + assertEquals(2, cfh.getParentCount()); + assertSame(cfh.getOldId(0), cfh.getOldId()); + assertEquals("7898192", cfh.getOldId(0).name()); + assertEquals("2e65efe", cfh.getOldId(1).name()); + assertEquals("0000000", cfh.getNewId().name()); + + assertSame(cfh.getOldMode(0), cfh.getOldMode()); + assertSame(FileMode.REGULAR_FILE, cfh.getOldMode(0)); + assertSame(FileMode.REGULAR_FILE, cfh.getOldMode(1)); + assertSame(FileMode.MISSING, cfh.getNewMode()); + assertSame(FileHeader.ChangeType.DELETE, cfh.getChangeType()); + assertSame(FileHeader.PatchType.UNIFIED, cfh.getPatchType()); + + assertTrue(cfh.getHunks().isEmpty()); + } + + private Patch parseTestPatchFile() throws IOException { + final String patchFile = getName() + ".patch"; + final InputStream in = getClass().getResourceAsStream(patchFile); + if (in == null) { + fail("No " + patchFile + " test vector"); + return null; // Never happens + } + try { + final Patch p = new Patch(); + p.parse(in); + return p; + } finally { + in.close(); + } + } +} diff --git a/org.spearce.jgit.test/tst/org/spearce/jgit/patch/testError_CcTruncatedOld.patch b/org.spearce.jgit.test/tst/org/spearce/jgit/patch/testError_CcTruncatedOld.patch new file mode 100644 index 0000000..1bbcfb5 --- /dev/null +++ b/org.spearce.jgit.test/tst/org/spearce/jgit/patch/testError_CcTruncatedOld.patch @@ -0,0 +1,24 @@ +commit 1a56639bbea8e8cbfbe5da87746de97f9217ce9b +Date: Tue May 13 00:43:56 2008 +0200 + ... + +diff --cc org.spearce.egit.ui/src/org/spearce/egit/ui/UIText.java +index 169356b,dd8c317..fd85931 +mode 100644,100644..100755 +--- a/org.spearce.egit.ui/src/org/spearce/egit/ui/UIText.java ++++ b/org.spearce.egit.ui/src/org/spearce/egit/ui/UIText.java +@@@ -55,12 -163,13 +163,15 @@@ public class UIText extends NLS + + /** */ + public static String ResourceHistory_toggleCommentWrap; ++ + /** */ + + /** */ + public static String ResourceHistory_toggleRevDetail; + /** */ + public static String ResourceHistory_toggleRevComment; + /** */ + public static String ResourceHistory_toggleTooltips; + + +commit 1a56639bbea8e8cbfbe5da87746de97f9217ce9b diff --git a/org.spearce.jgit.test/tst/org/spearce/jgit/patch/testParse_CcDeleteFile.patch b/org.spearce.jgit.test/tst/org/spearce/jgit/patch/testParse_CcDeleteFile.patch new file mode 100644 index 0000000..2654e09 --- /dev/null +++ b/org.spearce.jgit.test/tst/org/spearce/jgit/patch/testParse_CcDeleteFile.patch @@ -0,0 +1,12 @@ +commit 740709ece2412856c0c3eabd4dc4a4cf115b0de6 +Merge: 5c19b43... 13a2c0d... +Author: Shawn O. Pearce <sop@google.com> +Date: Fri Dec 12 13:26:52 2008 -0800 + + Merge branch 'b' into d + +diff --cc a +index 7898192,2e65efe..0000000 +deleted file mode 100644,100644 +--- a/a ++++ /dev/null diff --git a/org.spearce.jgit.test/tst/org/spearce/jgit/patch/testParse_CcNewFile.patch b/org.spearce.jgit.test/tst/org/spearce/jgit/patch/testParse_CcNewFile.patch new file mode 100644 index 0000000..1a9b7b0 --- /dev/null +++ b/org.spearce.jgit.test/tst/org/spearce/jgit/patch/testParse_CcNewFile.patch @@ -0,0 +1,14 @@ +commit 6cb8160a4717d51fd3cc0baf721946daa60cf921 +Merge: 5c19b43... 13a2c0d... +Author: Shawn O. Pearce <sop@google.com> +Date: Fri Dec 12 13:26:52 2008 -0800 + + Merge branch 'b' into d + +diff --cc d +index 0000000,0000000..4bcfe98 +new file mode 100644 +--- /dev/null ++++ b/d +@@@ -1,0 -1,0 +1,1 @@@ +++d diff --git a/org.spearce.jgit.test/tst/org/spearce/jgit/patch/testParse_OneFileCc.patch b/org.spearce.jgit.test/tst/org/spearce/jgit/patch/testParse_OneFileCc.patch new file mode 100644 index 0000000..c096b33 --- /dev/null +++ b/org.spearce.jgit.test/tst/org/spearce/jgit/patch/testParse_OneFileCc.patch @@ -0,0 +1,27 @@ +commit 1a56639bbea8e8cbfbe5da87746de97f9217ce9b +Date: Tue May 13 00:43:56 2008 +0200 + ... + +diff --cc org.spearce.egit.ui/src/org/spearce/egit/ui/UIText.java +index 169356b,dd8c317..fd85931 +mode 100644,100644..100755 +--- a/org.spearce.egit.ui/src/org/spearce/egit/ui/UIText.java ++++ b/org.spearce.egit.ui/src/org/spearce/egit/ui/UIText.java +@@@ -55,12 -163,13 +163,15 @@@ public class UIText extends NLS + + /** */ + public static String ResourceHistory_toggleCommentWrap; ++ + /** */ + + public static String ResourceHistory_toggleCommentFill; + + /** */ + public static String ResourceHistory_toggleRevDetail; ++ + /** */ + public static String ResourceHistory_toggleRevComment; ++ + /** */ + public static String ResourceHistory_toggleTooltips; + + +commit 1a56639bbea8e8cbfbe5da87746de97f9217ce9b diff --git a/org.spearce.jgit/src/org/spearce/jgit/patch/CombinedFileHeader.java b/org.spearce.jgit/src/org/spearce/jgit/patch/CombinedFileHeader.java new file mode 100644 index 0000000..7171600 --- /dev/null +++ b/org.spearce.jgit/src/org/spearce/jgit/patch/CombinedFileHeader.java @@ -0,0 +1,213 @@ +/* + * Copyright (C) 2008, Google Inc. + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * - Neither the name of the Git Development Community nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package org.spearce.jgit.patch; + +import static org.spearce.jgit.lib.Constants.encodeASCII; +import static org.spearce.jgit.util.RawParseUtils.match; +import static org.spearce.jgit.util.RawParseUtils.nextLF; + +import java.util.ArrayList; +import java.util.List; + +import org.spearce.jgit.lib.AbbreviatedObjectId; +import org.spearce.jgit.lib.FileMode; + +/** + * A file in the Git "diff --cc" or "diff --combined" format. + * <p> + * A combined diff shows an n-way comparison between two or more ancestors and + * the final revision. Its primary function is to perform code reviews on a + * merge which introduces changes not in any ancestor. + */ +public class CombinedFileHeader extends FileHeader { + private static final byte[] MODE = encodeASCII("mode "); + + private AbbreviatedObjectId[] oldIds; + + private FileMode[] oldModes; + + CombinedFileHeader(final byte[] b, final int offset) { + super(b, offset); + } + + @Override + @SuppressWarnings("unchecked") + public List<? extends CombinedHunkHeader> getHunks() { + return (List<CombinedHunkHeader>) super.getHunks(); + } + + /** @return number of ancestor revisions mentioned in this diff. */ + @Override + public int getParentCount() { + return oldIds.length; + } + + /** @return get the file mode of the first parent. */ + @Override + public FileMode getOldMode() { + return getOldMode(0); + } + + /** + * Get the file mode of the nth ancestor + * + * @param nthParent + * the ancestor to get the mode of + * @return the mode of the requested ancestor. + */ + public FileMode getOldMode(final int nthParent) { + return oldModes[nthParent]; + } + + /** @return get the object id of the first parent. */ + @Override + public AbbreviatedObjectId getOldId() { + return getOldId(0); + } + + /** + * Get the ObjectId of the nth ancestor + * + * @param nthParent + * the ancestor to get the object id of + * @return the id of the requested ancestor. + */ + public AbbreviatedObjectId getOldId(final int nthParent) { + return oldIds[nthParent]; + } + + int parseGitHeaders(int ptr, final int end) { + while (ptr < end) { + final int eol = nextLF(buf, ptr); + if (isHunkHdr(buf, ptr, end) >= 1) { + // First hunk header; break out and parse them later. + break; + + } else if (match(buf, ptr, OLD_NAME) >= 0) { + parseOldName(ptr, eol); + + } else if (match(buf, ptr, NEW_NAME) >= 0) { + parseNewName(ptr, eol); + + } else if (match(buf, ptr, INDEX) >= 0) { + parseIndexLine(ptr + INDEX.length, eol); + + } else if (match(buf, ptr, MODE) >= 0) { + parseModeLine(ptr + MODE.length, eol); + + } else if (match(buf, ptr, NEW_FILE_MODE) >= 0) { + parseNewFileMode(ptr, eol); + + } else if (match(buf, ptr, DELETED_FILE_MODE) >= 0) { + parseDeletedFileMode(ptr + DELETED_FILE_MODE.length, eol); + + } else { + // Probably an empty patch (stat dirty). + break; + } + + ptr = eol; + } + return ptr; + } + + @Override + protected void parseIndexLine(int ptr, final int eol) { + // "index $asha1,$bsha1..$csha1" + // + final List<AbbreviatedObjectId> ids = new ArrayList<AbbreviatedObjectId>(); + while (ptr < eol) { + final int comma = nextLF(buf, ptr, ','); + if (eol <= comma) + break; + ids.add(AbbreviatedObjectId.fromString(buf, ptr, comma - 1)); + ptr = comma; + } + + oldIds = new AbbreviatedObjectId[ids.size() + 1]; + ids.toArray(oldIds); + final int dot2 = nextLF(buf, ptr, '.'); + oldIds[ids.size()] = AbbreviatedObjectId.fromString(buf, ptr, dot2 - 1); + newId = AbbreviatedObjectId.fromString(buf, dot2 + 1, eol - 1); + oldModes = new FileMode[oldIds.length]; + } + + @Override + protected void parseNewFileMode(final int ptr, final int eol) { + for (int i = 0; i < oldModes.length; i++) + oldModes[i] = FileMode.MISSING; + super.parseNewFileMode(ptr, eol); + } + + @Override + HunkHeader newHunkHeader(final int offset) { + return new CombinedHunkHeader(this, offset); + } + + private void parseModeLine(int ptr, final int eol) { + // "mode $amode,$bmode..$cmode" + // + int n = 0; + while (ptr < eol) { + final int comma = nextLF(buf, ptr, ','); + if (eol <= comma) + break; + oldModes[n++] = parseFileMode(ptr, comma); + ptr = comma; + } + final int dot2 = nextLF(buf, ptr, '.'); + oldModes[n] = parseFileMode(ptr, dot2); + newMode = parseFileMode(dot2 + 1, eol); + } + + private void parseDeletedFileMode(int ptr, final int eol) { + // "deleted file mode $amode,$bmode" + // + changeType = ChangeType.DELETE; + int n = 0; + while (ptr < eol) { + final int comma = nextLF(buf, ptr, ','); + if (eol <= comma) + break; + oldModes[n++] = parseFileMode(ptr, comma); + ptr = comma; + } + oldModes[n] = parseFileMode(ptr, eol); + newMode = FileMode.MISSING; + } +} diff --git a/org.spearce.jgit/src/org/spearce/jgit/patch/CombinedHunkHeader.java b/org.spearce.jgit/src/org/spearce/jgit/patch/CombinedHunkHeader.java new file mode 100644 index 0000000..bebeafa --- /dev/null +++ b/org.spearce.jgit/src/org/spearce/jgit/patch/CombinedHunkHeader.java @@ -0,0 +1,191 @@ +/* + * Copyright (C) 2008, Google Inc. + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * - Neither the name of the Git Development Community nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package org.spearce.jgit.patch; + +import static org.spearce.jgit.util.RawParseUtils.nextLF; +import static org.spearce.jgit.util.RawParseUtils.parseBase10; + +import org.spearce.jgit.lib.AbbreviatedObjectId; +import org.spearce.jgit.util.MutableInteger; + +/** Hunk header for a hunk appearing in a "diff --cc" style patch. */ +public class CombinedHunkHeader extends HunkHeader { + private static abstract class CombinedOldImage extends OldImage { + int nContext; + } + + private CombinedOldImage[] old; + + CombinedHunkHeader(final CombinedFileHeader fh, final int offset) { + super(fh, offset, null); + old = new CombinedOldImage[fh.getParentCount()]; + for (int i = 0; i < old.length; i++) { + final int imagePos = i; + old[i] = new CombinedOldImage() { + @Override + public AbbreviatedObjectId getId() { + return fh.getOldId(imagePos); + } + }; + } + } + + @Override + public CombinedFileHeader getFileHeader() { + return (CombinedFileHeader) super.getFileHeader(); + } + + @Override + public OldImage getOldImage() { + return getOldImage(0); + } + + /** + * Get the OldImage data related to the nth ancestor + * + * @param nthParent + * the ancestor to get the old image data of + * @return image data of the requested ancestor. + */ + public OldImage getOldImage(final int nthParent) { + return old[nthParent]; + } + + @Override + void parseHeader(final int end) { + // Parse "@@@ -55,12 -163,13 +163,15 @@@ protected boolean" + // + final byte[] buf = file.buf; + final MutableInteger ptr = new MutableInteger(); + ptr.value = nextLF(buf, startOffset, ' '); + + for (int n = 0; n < old.length; n++) { + old[n].startLine = -parseBase10(buf, ptr.value, ptr); + if (buf[ptr.value] == ',') + old[n].lineCount = parseBase10(buf, ptr.value + 1, ptr); + else + old[n].lineCount = 1; + } + + newStartLine = parseBase10(buf, ptr.value + 1, ptr); + if (buf[ptr.value] == ',') + newLineCount = parseBase10(buf, ptr.value + 1, ptr); + else + newLineCount = 1; + } + + @Override + int parseBody(final Patch script, final int end) { + final byte[] buf = file.buf; + int c = nextLF(buf, startOffset); + + for (final CombinedOldImage o : old) { + o.nDeleted = 0; + o.nAdded = 0; + o.nContext = 0; + } + nContext = 0; + int nAdded = 0; + + SCAN: for (int eol; c < end; c = eol) { + eol = nextLF(buf, c); + + if (eol - c < old.length + 1) { + // Line isn't long enough to mention the state of each + // ancestor. It must be the end of the hunk. + break SCAN; + } + + switch (buf[c]) { + case ' ': + case '-': + case '+': + break; + + default: + // Line can't possibly be part of this hunk; the first + // ancestor information isn't recognizable. + // + break SCAN; + } + + int localcontext = 0; + for (int ancestor = 0; ancestor < old.length; ancestor++) { + switch (buf[c + ancestor]) { + case ' ': + localcontext++; + old[ancestor].nContext++; + continue; + + case '-': + old[ancestor].nDeleted++; + continue; + + case '+': + old[ancestor].nAdded++; + nAdded++; + continue; + + default: + break SCAN; + } + } + if (localcontext == old.length) + nContext++; + } + + for (int ancestor = 0; ancestor < old.length; ancestor++) { + final CombinedOldImage o = old[ancestor]; + final int cmp = o.nContext + o.nDeleted; + if (cmp < o.lineCount) { + final int missingCnt = o.lineCount - cmp; + script.error(buf, startOffset, "Truncated hunk, at least " + + missingCnt + " lines is missing for ancestor " + + (ancestor + 1)); + } + } + + if (nContext + nAdded < newLineCount) { + final int missingCount = newLineCount - (nContext + nAdded); + script.error(buf, startOffset, "Truncated hunk, at least " + + missingCount + " new lines is missing"); + } + + return c; + } +} diff --git a/org.spearce.jgit/src/org/spearce/jgit/patch/FileHeader.java b/org.spearce.jgit/src/org/spearce/jgit/patch/FileHeader.java index 48d7623..79e4b0a 100644 --- a/org.spearce.jgit/src/org/spearce/jgit/patch/FileHeader.java +++ b/org.spearce.jgit/src/org/spearce/jgit/patch/FileHeader.java @@ -61,9 +61,9 @@ private static final byte[] NEW_MODE = encodeASCII("new mode "); - private static final byte[] DELETED_FILE_MODE = encodeASCII("deleted file mode "); + protected static final byte[] DELETED_FILE_MODE = encodeASCII("deleted file mode "); - private static final byte[] NEW_FILE_MODE = encodeASCII("new file mode "); + protected static final byte[] NEW_FILE_MODE = encodeASCII("new file mode "); private static final byte[] COPY_FROM = encodeASCII("copy from "); @@ -81,7 +81,7 @@ private static final byte[] DISSIMILARITY_INDEX = encodeASCII("dissimilarity index "); - private static final byte[] INDEX = encodeASCII("index "); + protected static final byte[] INDEX = encodeASCII("index "); static final byte[] OLD_NAME = encodeASCII("--- "); @@ -136,10 +136,10 @@ private FileMode oldMode; /** New mode of the file, if described by the patch, else null. */ - private FileMode newMode; + protected FileMode newMode; /** General type of change indicated by the patch. */ - private ChangeType changeType; + protected ChangeType changeType; /** Similarity score if {@link #changeType} is a copy or rename. */ private int score; @@ -148,7 +148,7 @@ private AbbreviatedObjectId oldId; /** ObjectId listed on the index line for the new (post-image) */ - private AbbreviatedObjectId newId; + protected AbbreviatedObjectId newId; /** Type of patch used to modify this file */ PatchType patchType; @@ -264,7 +264,7 @@ public boolean hasMetaDataChanges() { } /** @return hunks altering this file; in order of appearance in patch */ - public List<HunkHeader> getHunks() { + public List<? extends HunkHeader> getHunks() { if (hunks == null) return Collections.emptyList(); return hunks; @@ -369,14 +369,10 @@ int parseGitHeaders(int ptr, final int end) { break; } else if (match(buf, ptr, OLD_NAME) >= 0) { - oldName = p1(parseName(oldName, ptr + OLD_NAME.length, eol)); - if (oldName == DEV_NULL) - changeType = ChangeType.ADD; + parseOldName(ptr, eol); } else if (match(buf, ptr, NEW_NAME) >= 0) { - newName = p1(parseName(newName, ptr + NEW_NAME.length, eol)); - if (newName == DEV_NULL) - changeType = ChangeType.DELETE; + parseNewName(ptr, eol); } else if (match(buf, ptr, OLD_MODE) >= 0) { oldMode = parseFileMode(ptr + OLD_MODE.length, eol); @@ -390,9 +386,7 @@ int parseGitHeaders(int ptr, final int end) { changeType = ChangeType.DELETE; } else if (match(buf, ptr, NEW_FILE_MODE) >= 0) { - oldMode = FileMode.MISSING; - newMode = parseFileMode(ptr + NEW_FILE_MODE.length, eol); - changeType = ChangeType.ADD; + parseNewFileMode(ptr, eol); } else if (match(buf, ptr, COPY_FROM) >= 0) { oldName = parseName(oldName, ptr + COPY_FROM.length, eol); @@ -437,6 +431,24 @@ int parseGitHeaders(int ptr, final int end) { return ptr; } + protected void parseOldName(int ptr, final int eol) { + oldName = p1(parseName(oldName, ptr + OLD_NAME.length, eol)); + if (oldName == DEV_NULL) + changeType = ChangeType.ADD; + } + + protected void parseNewName(int ptr, final int eol) { + newName = p1(parseName(newName, ptr + NEW_NAME.length, eol)); + if (newName == DEV_NULL) + changeType = ChangeType.DELETE; + } + + protected void parseNewFileMode(int ptr, final int eol) { + oldMode = FileMode.MISSING; + newMode = parseFileMode(ptr + NEW_FILE_MODE.length, eol); + changeType = ChangeType.ADD; + } + int parseTraditionalHeaders(int ptr, final int end) { while (ptr < end) { final int eol = nextLF(buf, ptr); @@ -445,14 +457,10 @@ int parseTraditionalHeaders(int ptr, final int end) { break; } else if (match(buf, ptr, OLD_NAME) >= 0) { - oldName = p1(parseName(oldName, ptr + OLD_NAME.length, eol)); - if (oldName == DEV_NULL) - changeType = ChangeType.ADD; + parseOldName(ptr, eol); } else if (match(buf, ptr, NEW_NAME) >= 0) { - newName = p1(parseName(newName, ptr + NEW_NAME.length, eol)); - if (newName == DEV_NULL) - changeType = ChangeType.DELETE; + parseNewName(ptr, eol); } else { // Possibly an empty patch. @@ -494,7 +502,7 @@ private static String p1(final String r) { return s > 0 ? r.substring(s + 1) : r; } - private FileMode parseFileMode(int ptr, final int end) { + protected FileMode parseFileMode(int ptr, final int end) { int tmp = 0; while (ptr < end - 1) { tmp <<= 3; @@ -503,7 +511,7 @@ private FileMode parseFileMode(int ptr, final int end) { return FileMode.fromBits(tmp); } - private void parseIndexLine(int ptr, final int end) { + protected void parseIndexLine(int ptr, final int end) { // "index $asha1..$bsha1[ $mode]" where $asha1 and $bsha1 // can be unique abbreviations // diff --git a/org.spearce.jgit/src/org/spearce/jgit/patch/HunkHeader.java b/org.spearce.jgit/src/org/spearce/jgit/patch/HunkHeader.java index f543aed..fc149ac 100644 --- a/org.spearce.jgit/src/org/spearce/jgit/patch/HunkHeader.java +++ b/org.spearce.jgit/src/org/spearce/jgit/patch/HunkHeader.java @@ -84,7 +84,7 @@ public int getLinesAdded() { public abstract AbbreviatedObjectId getId(); } - private final FileHeader file; + final FileHeader file; /** Offset within {@link #file}.buf to the "@@ -" line. */ final int startOffset; diff --git a/org.spearce.jgit/src/org/spearce/jgit/patch/Patch.java b/org.spearce.jgit/src/org/spearce/jgit/patch/Patch.java index 2886e4c..f23ba69 100644 --- a/org.spearce.jgit/src/org/spearce/jgit/patch/Patch.java +++ b/org.spearce.jgit/src/org/spearce/jgit/patch/Patch.java @@ -94,7 +94,7 @@ public void addFile(final FileHeader fh) { } /** @return list of files described in the patch, in occurrence order. */ - public List<FileHeader> getFiles() { + public List<? extends FileHeader> getFiles() { return files; } @@ -233,14 +233,13 @@ private int parseDiffGit(final byte[] buf, final int start, final int end) { private int parseDiffCombined(final byte[] hdr, final byte[] buf, final int start, final int end) { - final FileHeader fh = new FileHeader(buf, start); + final CombinedFileHeader fh = new CombinedFileHeader(buf, start); int ptr = fh.parseGitFileName(start + hdr.length, end); if (ptr < 0) return skipFile(buf, start, end); - // TODO Support parsing diff --cc headers - // TODO parse diff --cc hunks - warn(buf, start, "diff --cc format not supported"); + ptr = fh.parseGitHeaders(ptr, end); + ptr = parseHunks(fh, ptr, end); fh.endOffset = ptr; addFile(fh); return ptr; -- 1.6.1.rc2.306.ge5d5e ^ permalink raw reply related [flat|nested] 14+ messages in thread
end of thread, other threads:[~2008-12-12 23:20 UTC | newest] Thread overview: 14+ messages (download: mbox.gz follow: Atom feed -- links below jump to the message on this page -- 2008-12-12 22:05 [JGIT PATCH 00/12] Patch API bug fixes and diff --cc suport Shawn O. Pearce 2008-12-12 22:05 ` [JGIT PATCH 01/12] Assert the HunkHeader.getFileHeader returns the right file Shawn O. Pearce 2008-12-12 22:05 ` [JGIT PATCH 02/12] Add tests to cover more methods of BinaryHunk Shawn O. Pearce 2008-12-12 22:05 ` [JGIT PATCH 03/12] Add a simple toString to FormatError to facilitate debugging Shawn O. Pearce 2008-12-12 22:05 ` [JGIT PATCH 04/12] Allow FileHeader to create its HunkHeader children Shawn O. Pearce 2008-12-12 22:05 ` [JGIT PATCH 05/12] Refactor the old/pre-image data in HunkHeader to support >1 ancestor Shawn O. Pearce 2008-12-12 22:05 ` [JGIT PATCH 06/12] Assert the ChunkHeader.OldImage.getId uses FileHeader.getOldImage Shawn O. Pearce 2008-12-12 22:05 ` [JGIT PATCH 07/12] Allow a stray LF at the end of a hunk Shawn O. Pearce 2008-12-12 22:05 ` [JGIT PATCH 08/12] Fix HunkHeader start line when parsing "@@ -1 +1 @@" style headers Shawn O. Pearce 2008-12-12 22:05 ` [JGIT PATCH 09/12] Add test cases for parsing "\ No newline at end of file" style patches Shawn O. Pearce 2008-12-12 22:05 ` [JGIT PATCH 10/12] Use FileMode.MISSING when a file is added or deleted rather than null Shawn O. Pearce 2008-12-12 22:05 ` [JGIT PATCH 11/12] Add a test for delta binary patch parsing and fix a bug in it Shawn O. Pearce 2008-12-12 22:05 ` [JGIT PATCH 12/12] Add support for parsing "diff --cc" style patches Shawn O. Pearce 2008-12-12 23:19 ` [JGIT PATCH 12/12 v2] " Shawn O. Pearce
This is a public inbox, see mirroring instructions for how to clone and mirror all data and code used for this inbox; as well as URLs for NNTP newsgroup(s).