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