{"thread":{"id":"16678","subject":"[JGIT PATCH 2/5] Add copy(InputStream) to TemporaryBuffer","startedAt":"2008-12-11T04:58:37Z","lastAt":"2008-12-11T20:41:37Z","messageCount":15,"participants":["Shawn O. Pearce","Robin Rosenberg"],"isPatch":true,"patchVersion":1,"patchTotal":5},"messages":[{"id":"97576","messageId":"1228971522-28764-1-git-send-email-spearce@spearce.org","threadId":"16678","inReplyTo":null,"subject":"[JGIT PATCH 0/5] Patch parsing API","fromName":"Shawn O. Pearce","fromEmail":"spearce@spearce.org","sentAt":"2008-12-11T04:58:37Z","receivedAt":"2008-12-11T04:58:37Z","isPatch":true,"sender":{"key":"spearce@spearce.org","avatar":"https://avatars.githubusercontent.com/u/34844?v=4"},"body":"This is an API to parse a Git style patch file and extract the\ncritical metadata from the header lines, including the hunk headers\nand what lines they correspond to in the pre and post image files.\n\nIt requires the two other series I already sent out today for\nQuotedString and AbbreviatedObjectId.\n\nThere's TODO markers left where we still need to insert code to\ncreate some sort of warning object, and then hang the warnings off\nthe Patch class.  Given the size of the code I'm inclined to do that\nas yet an additional patch, rather than squash it into this series.\n\nMy short-term roadmap related to this part of JGit:\n\n * Compute and collect warnings from malformed git-style patches\n * Correctly parse \"git diff --cc\" style output\n * Get it into egit.git\n\nI'm stopping development once I have the diff --cc output parsing\ncorrectly.  My rationale is right now I need the patch metadata\nparsing in Gerrit 2, so that's what I'm teaching JGit to do.  Maybe\nlater in the month or early next I'll add patch application support,\nbecause I also want that in Gerrit 2.  Patch application is not\ncurrently a blocking item for me; but reading the patch metadata is.\n\nTraditional patch support is really stubbed out too; there's a very\nsmall subset of traditional (non-git) style patches this code can\nscan the metadata from, but no tests to verify it.  Gerrit 2 gets\nall of its data from a \"git diff\" process, so I only need support\nfor git diffs right now.  Yes, I'd like to add traditional patch\nsupport too, but it won't be until later in 2009 that I would even\nthink about working on that myself.\n\n\nShawn O. Pearce (5):\n  Add toByteArray() to TemporaryBuffer\n  Add copy(InputStream) to TemporaryBuffer\n  Define FileHeader to parse the header block of a git diff\n  Define Patch to parse a sequence of patch FileHeaders\n  Add HunkHeader to represent a single hunk of a file within a patch\n\n .../tst/org/spearce/jgit/patch/FileHeaderTest.java |  395 ++++++++++++++++\n .../tst/org/spearce/jgit/patch/PatchTest.java      |  155 +++++++\n .../patch/testParse_ConfigCaseInsensitive.patch    |   67 +++\n .../src/org/spearce/jgit/patch/FileHeader.java     |  480 ++++++++++++++++++++\n .../src/org/spearce/jgit/patch/HunkHeader.java     |  185 ++++++++\n .../src/org/spearce/jgit/patch/Patch.java          |  267 +++++++++++\n .../src/org/spearce/jgit/util/TemporaryBuffer.java |   51 ++\n 7 files changed, 1600 insertions(+), 0 deletions(-)\n create mode 100644 org.spearce.jgit.test/tst/org/spearce/jgit/patch/FileHeaderTest.java\n create mode 100644 org.spearce.jgit.test/tst/org/spearce/jgit/patch/PatchTest.java\n create mode 100644 org.spearce.jgit.test/tst/org/spearce/jgit/patch/testParse_ConfigCaseInsensitive.patch\n create mode 100644 org.spearce.jgit/src/org/spearce/jgit/patch/FileHeader.java\n create mode 100644 org.spearce.jgit/src/org/spearce/jgit/patch/HunkHeader.java\n create mode 100644 org.spearce.jgit/src/org/spearce/jgit/patch/Patch.java\n"},{"id":"97575","messageId":"1228971522-28764-2-git-send-email-spearce@spearce.org","threadId":"16678","inReplyTo":"1228971522-28764-1-git-send-email-spearce@spearce.org","subject":"[JGIT PATCH 1/5] Add toByteArray() to TemporaryBuffer","fromName":"Shawn O. Pearce","fromEmail":"spearce@spearce.org","sentAt":"2008-12-11T04:58:38Z","receivedAt":"2008-12-11T04:58:38Z","isPatch":true,"sender":{"key":"spearce@spearce.org","avatar":"https://avatars.githubusercontent.com/u/34844?v=4"},"body":"It can be more useful to convert a buffered output stream into\na single byte array, without paying the penalties associated\nwith ByteArrayOutputStream to do the same action.\n\nSigned-off-by: Shawn O. Pearce <spearce@spearce.org>\n---\n .../src/org/spearce/jgit/util/TemporaryBuffer.java |   34 ++++++++++++++++++++\n 1 files changed, 34 insertions(+), 0 deletions(-)\n\ndiff --git a/org.spearce.jgit/src/org/spearce/jgit/util/TemporaryBuffer.java b/org.spearce.jgit/src/org/spearce/jgit/util/TemporaryBuffer.java\nindex d597c38..b1ffd6e 100644\n--- a/org.spearce.jgit/src/org/spearce/jgit/util/TemporaryBuffer.java\n+++ b/org.spearce.jgit/src/org/spearce/jgit/util/TemporaryBuffer.java\n@@ -182,6 +182,40 @@ public long length() {\n \t}\n \n \t/**\n+\t * Convert this buffer's contents into a contiguous byte array.\n+\t * <p>\n+\t * The buffer is only complete after {@link #close()} has been invoked.\n+\t * \n+\t * @return the complete byte array; length matches {@link #length()}.\n+\t * @throws IOException\n+\t *             an error occurred reading from a local temporary file\n+\t * @throws OutOfMemoryError\n+\t *             the buffer cannot fit in memory\n+\t */\n+\tpublic byte[] toByteArray() throws IOException {\n+\t\tfinal long len = length();\n+\t\tif (Integer.MAX_VALUE < len)\n+\t\t\tthrow new OutOfMemoryError(\"Length exceeds maximum array size\");\n+\n+\t\tfinal byte[] out = new byte[(int) len];\n+\t\tif (blocks != null) {\n+\t\t\tint outPtr = 0;\n+\t\t\tfor (final Block b : blocks) {\n+\t\t\t\tSystem.arraycopy(b.buffer, 0, out, outPtr, b.count);\n+\t\t\t\toutPtr += b.count;\n+\t\t\t}\n+\t\t} else {\n+\t\t\tfinal FileInputStream in = new FileInputStream(onDiskFile);\n+\t\t\ttry {\n+\t\t\t\tNB.readFully(in, out, 0, (int) len);\n+\t\t\t} finally {\n+\t\t\t\tin.close();\n+\t\t\t}\n+\t\t}\n+\t\treturn out;\n+\t}\n+\n+\t/**\n \t * Send this buffer to an output stream.\n \t * <p>\n \t * This method may only be invoked after {@link #close()} has completed\n-- \n1.6.1.rc2.299.gead4c\n"},{"id":"97574","messageId":"1228971522-28764-3-git-send-email-spearce@spearce.org","threadId":"16678","inReplyTo":"1228971522-28764-2-git-send-email-spearce@spearce.org","subject":"[JGIT PATCH 2/5] Add copy(InputStream) to TemporaryBuffer","fromName":"Shawn O. Pearce","fromEmail":"spearce@spearce.org","sentAt":"2008-12-11T04:58:39Z","receivedAt":"2008-12-11T04:58:39Z","isPatch":true,"sender":{"key":"spearce@spearce.org","avatar":"https://avatars.githubusercontent.com/u/34844?v=4"},"body":"In some places we may find it ourselves with an InputStream we\nneed to copy into a TemporaryBuffer, so we can flatten out the\nentire stream to a single byte[].  Putting the copy loop here\nis more useful then duplicating it in application level code.\n\nSigned-off-by: Shawn O. Pearce <spearce@spearce.org>\n---\n .../src/org/spearce/jgit/util/TemporaryBuffer.java |   17 +++++++++++++++++\n 1 files changed, 17 insertions(+), 0 deletions(-)\n\ndiff --git a/org.spearce.jgit/src/org/spearce/jgit/util/TemporaryBuffer.java b/org.spearce.jgit/src/org/spearce/jgit/util/TemporaryBuffer.java\nindex b1ffd6e..8f91246 100644\n--- a/org.spearce.jgit/src/org/spearce/jgit/util/TemporaryBuffer.java\n+++ b/org.spearce.jgit/src/org/spearce/jgit/util/TemporaryBuffer.java\n@@ -42,6 +42,7 @@\n import java.io.FileInputStream;\n import java.io.FileOutputStream;\n import java.io.IOException;\n+import java.io.InputStream;\n import java.io.OutputStream;\n import java.util.ArrayList;\n \n@@ -135,6 +136,22 @@ public void write(final byte[] b, int off, int len) throws IOException {\n \t\t\tdiskOut.write(b, off, len);\n \t}\n \n+\t/**\n+\t * Copy all bytes remaining on the input stream into this buffer.\n+\t * \n+\t * @param in\n+\t *            the stream to read from, until EOF is reached.\n+\t * @throws IOException\n+\t *             an error occurred reading from the input stream, or while\n+\t *             writing to a local temporary file.\n+\t */\n+\tpublic void copy(final InputStream in) throws IOException {\n+\t\tfinal byte[] b = new byte[2048];\n+\t\tint n;\n+\t\twhile ((n = in.read(b)) > 0)\n+\t\t\twrite(b, 0, n);\n+\t}\n+\n \tprivate Block last() {\n \t\treturn blocks.get(blocks.size() - 1);\n \t}\n-- \n1.6.1.rc2.299.gead4c\n"},{"id":"97577","messageId":"1228971522-28764-4-git-send-email-spearce@spearce.org","threadId":"16678","inReplyTo":"1228971522-28764-3-git-send-email-spearce@spearce.org","subject":"[JGIT PATCH 3/5] Define FileHeader to parse the header block of a git diff","fromName":"Shawn O. Pearce","fromEmail":"spearce@spearce.org","sentAt":"2008-12-11T04:58:40Z","receivedAt":"2008-12-11T04:58:40Z","isPatch":true,"sender":{"key":"spearce@spearce.org","avatar":"https://avatars.githubusercontent.com/u/34844?v=4"},"body":"This class parses the top header lines of a git style diff, such as:\n\n  diff --git a/SUBMITTING_PATCHES b/Q\n  similarity index 100%\n  copy from SUBMITTING_PATCHES\n  copy to Q\n\nor:\n\n  diff --git a/Q b/Q\n  new file mode 100644\n  index 0000000..e4a135e\n  --- /dev/null\n  +++ b/Q\n\nand makes the information available in an object form.  Unit tests\ncover the different styles of headers that are commonly created by\nC git, including both rename formats.\n\nThe hunk header information is not handled by this class, and it\ndoes not have a public API.  It is my intention to wrap this into\na larger container class that handles multiple FileHeaders at once,\nwith the base case of course being a single FileHeader describing\na patch that impacts only one file.\n\nSigned-off-by: Shawn O. Pearce <spearce@spearce.org>\n---\n .../tst/org/spearce/jgit/patch/FileHeaderTest.java |  391 ++++++++++++++++++\n .../src/org/spearce/jgit/patch/FileHeader.java     |  430 ++++++++++++++++++++\n 2 files changed, 821 insertions(+), 0 deletions(-)\n create mode 100644 org.spearce.jgit.test/tst/org/spearce/jgit/patch/FileHeaderTest.java\n create mode 100644 org.spearce.jgit/src/org/spearce/jgit/patch/FileHeader.java\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\nnew file mode 100644\nindex 0000000..1d87bc0\n--- /dev/null\n+++ b/org.spearce.jgit.test/tst/org/spearce/jgit/patch/FileHeaderTest.java\n@@ -0,0 +1,391 @@\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+package org.spearce.jgit.patch;\n+\n+import junit.framework.TestCase;\n+\n+import org.spearce.jgit.lib.Constants;\n+import org.spearce.jgit.lib.FileMode;\n+import org.spearce.jgit.lib.ObjectId;\n+\n+public class FileHeaderTest extends TestCase {\n+\tpublic void testParseGitFileName_Empty() {\n+\t\tassertEquals(-1, data(\"\").parseGitFileName(0));\n+\t}\n+\n+\tpublic void testParseGitFileName_NoLF() {\n+\t\tassertEquals(-1, data(\"a/ b/\").parseGitFileName(0));\n+\t}\n+\n+\tpublic void testParseGitFileName_NoSecondLine() {\n+\t\tassertEquals(-1, data(\"\\n\").parseGitFileName(0));\n+\t}\n+\n+\tpublic void testParseGitFileName_EmptyHeader() {\n+\t\tassertEquals(1, data(\"\\n\\n\").parseGitFileName(0));\n+\t}\n+\n+\tpublic void testParseGitFileName_Foo() {\n+\t\tfinal String name = \"foo\";\n+\t\tfinal FileHeader fh = header(name);\n+\t\tassertEquals(gitLine(name).length(), fh.parseGitFileName(0));\n+\t\tassertEquals(name, fh.getOldName());\n+\t\tassertSame(fh.getOldName(), fh.getNewName());\n+\t}\n+\n+\tpublic void testParseGitFileName_FailFooBar() {\n+\t\tfinal FileHeader fh = data(\"a/foo b/bar\\n-\");\n+\t\tassertTrue(fh.parseGitFileName(0) > 0);\n+\t\tassertNull(fh.getOldName());\n+\t\tassertNull(fh.getNewName());\n+\t}\n+\n+\tpublic void testParseGitFileName_FooSpBar() {\n+\t\tfinal String name = \"foo bar\";\n+\t\tfinal FileHeader fh = header(name);\n+\t\tassertEquals(gitLine(name).length(), fh.parseGitFileName(0));\n+\t\tassertEquals(name, fh.getOldName());\n+\t\tassertSame(fh.getOldName(), fh.getNewName());\n+\t}\n+\n+\tpublic void testParseGitFileName_DqFooTabBar() {\n+\t\tfinal String name = \"foo\\tbar\";\n+\t\tfinal String dqName = \"foo\\\\tbar\";\n+\t\tfinal FileHeader fh = dqHeader(dqName);\n+\t\tassertEquals(dqGitLine(dqName).length(), fh.parseGitFileName(0));\n+\t\tassertEquals(name, fh.getOldName());\n+\t\tassertSame(fh.getOldName(), fh.getNewName());\n+\t}\n+\n+\tpublic void testParseGitFileName_DqFooSpLfNulBar() {\n+\t\tfinal String name = \"foo \\n\\0bar\";\n+\t\tfinal String dqName = \"foo \\\\n\\\\0bar\";\n+\t\tfinal FileHeader fh = dqHeader(dqName);\n+\t\tassertEquals(dqGitLine(dqName).length(), fh.parseGitFileName(0));\n+\t\tassertEquals(name, fh.getOldName());\n+\t\tassertSame(fh.getOldName(), fh.getNewName());\n+\t}\n+\n+\tpublic void testParseGitFileName_SrcFooC() {\n+\t\tfinal String name = \"src/foo/bar/argh/code.c\";\n+\t\tfinal FileHeader fh = header(name);\n+\t\tassertEquals(gitLine(name).length(), fh.parseGitFileName(0));\n+\t\tassertEquals(name, fh.getOldName());\n+\t\tassertSame(fh.getOldName(), fh.getNewName());\n+\t}\n+\n+\tpublic void testParseGitFileName_SrcFooCNonStandardPrefix() {\n+\t\tfinal String name = \"src/foo/bar/argh/code.c\";\n+\t\tfinal String header = \"project-v-1.0/\" + name + \" mydev/\" + name + \"\\n\";\n+\t\tfinal FileHeader fh = data(header + \"-\");\n+\t\tassertEquals(header.length(), fh.parseGitFileName(0));\n+\t\tassertEquals(name, fh.getOldName());\n+\t\tassertSame(fh.getOldName(), fh.getNewName());\n+\t}\n+\n+\tpublic void testParseUnicodeName_NewFile() {\n+\t\tfinal FileHeader fh = data(\"diff --git \\\"a/\\\\303\\\\205ngstr\\\\303\\\\266m\\\" \\\"b/\\\\303\\\\205ngstr\\\\303\\\\266m\\\"\\n\"\n+\t\t\t\t+ \"new file mode 100644\\n\"\n+\t\t\t\t+ \"index 0000000..7898192\\n\"\n+\t\t\t\t+ \"--- /dev/null\\n\"\n+\t\t\t\t+ \"+++ \\\"b/\\\\303\\\\205ngstr\\\\303\\\\266m\\\"\\n\"\n+\t\t\t\t+ \"@@ -0,0 +1 @@\\n\" + \"+a\\n\");\n+\t\tassertParse(fh);\n+\n+\t\tassertEquals(\"/dev/null\", fh.getOldName());\n+\t\tassertSame(FileHeader.DEV_NULL, fh.getOldName());\n+\t\tassertEquals(\"\\u00c5ngstr\\u00f6m\", fh.getNewName());\n+\n+\t\tassertSame(FileHeader.ChangeType.ADD, fh.getChangeType());\n+\n+\t\tassertNull(fh.getOldMode());\n+\t\tassertSame(FileMode.REGULAR_FILE, fh.getNewMode());\n+\n+\t\tassertEquals(\"0000000\", fh.getOldId().name());\n+\t\tassertEquals(\"7898192\", fh.getNewId().name());\n+\t\tassertEquals(0, fh.getScore());\n+\t}\n+\n+\tpublic void testParseUnicodeName_DeleteFile() {\n+\t\tfinal FileHeader fh = data(\"diff --git \\\"a/\\\\303\\\\205ngstr\\\\303\\\\266m\\\" \\\"b/\\\\303\\\\205ngstr\\\\303\\\\266m\\\"\\n\"\n+\t\t\t\t+ \"deleted file mode 100644\\n\"\n+\t\t\t\t+ \"index 7898192..0000000\\n\"\n+\t\t\t\t+ \"--- \\\"a/\\\\303\\\\205ngstr\\\\303\\\\266m\\\"\\n\"\n+\t\t\t\t+ \"+++ /dev/null\\n\"\n+\t\t\t\t+ \"@@ -1 +0,0 @@\\n\" + \"-a\\n\");\n+\t\tassertParse(fh);\n+\n+\t\tassertEquals(\"\\u00c5ngstr\\u00f6m\", fh.getOldName());\n+\t\tassertEquals(\"/dev/null\", fh.getNewName());\n+\t\tassertSame(FileHeader.DEV_NULL, fh.getNewName());\n+\n+\t\tassertSame(FileHeader.ChangeType.DELETE, fh.getChangeType());\n+\n+\t\tassertSame(FileMode.REGULAR_FILE, fh.getOldMode());\n+\t\tassertNull(fh.getNewMode());\n+\n+\t\tassertEquals(\"7898192\", fh.getOldId().name());\n+\t\tassertEquals(\"0000000\", fh.getNewId().name());\n+\t\tassertEquals(0, fh.getScore());\n+\t}\n+\n+\tpublic void testParseModeChange() {\n+\t\tfinal FileHeader fh = data(\"diff --git a/a b b/a b\\n\"\n+\t\t\t\t+ \"old mode 100644\\n\" + \"new mode 100755\\n\");\n+\t\tassertParse(fh);\n+\t\tassertEquals(\"a b\", fh.getOldName());\n+\t\tassertEquals(\"a b\", fh.getNewName());\n+\n+\t\tassertSame(FileHeader.ChangeType.MODIFY, fh.getChangeType());\n+\n+\t\tassertNull(fh.getOldId());\n+\t\tassertNull(fh.getNewId());\n+\n+\t\tassertSame(FileMode.REGULAR_FILE, fh.getOldMode());\n+\t\tassertSame(FileMode.EXECUTABLE_FILE, fh.getNewMode());\n+\t\tassertEquals(0, fh.getScore());\n+\t}\n+\n+\tpublic void testParseRename100_NewStyle() {\n+\t\tfinal FileHeader fh = data(\"diff --git a/a b/ c/\\\\303\\\\205ngstr\\\\303\\\\266m\\n\"\n+\t\t\t\t+ \"similarity index 100%\\n\"\n+\t\t\t\t+ \"rename from a\\n\"\n+\t\t\t\t+ \"rename to \\\" c/\\\\303\\\\205ngstr\\\\303\\\\266m\\\"\\n\");\n+\t\tint ptr = fh.parseGitFileName(0);\n+\t\tassertTrue(ptr > 0);\n+\t\tassertNull(fh.getOldName()); // can't parse names on a rename\n+\t\tassertNull(fh.getNewName());\n+\n+\t\tptr = fh.parseGitHeaders(ptr);\n+\t\tassertTrue(ptr > 0);\n+\n+\t\tassertEquals(\"a\", fh.getOldName());\n+\t\tassertEquals(\" c/\\u00c5ngstr\\u00f6m\", fh.getNewName());\n+\n+\t\tassertSame(FileHeader.ChangeType.RENAME, fh.getChangeType());\n+\n+\t\tassertNull(fh.getOldId());\n+\t\tassertNull(fh.getNewId());\n+\n+\t\tassertNull(fh.getOldMode());\n+\t\tassertNull(fh.getNewMode());\n+\n+\t\tassertEquals(100, fh.getScore());\n+\t}\n+\n+\tpublic void testParseRename100_OldStyle() {\n+\t\tfinal FileHeader fh = data(\"diff --git a/a b/ c/\\\\303\\\\205ngstr\\\\303\\\\266m\\n\"\n+\t\t\t\t+ \"similarity index 100%\\n\"\n+\t\t\t\t+ \"rename old a\\n\"\n+\t\t\t\t+ \"rename new \\\" c/\\\\303\\\\205ngstr\\\\303\\\\266m\\\"\\n\");\n+\t\tint ptr = fh.parseGitFileName(0);\n+\t\tassertTrue(ptr > 0);\n+\t\tassertNull(fh.getOldName()); // can't parse names on a rename\n+\t\tassertNull(fh.getNewName());\n+\n+\t\tptr = fh.parseGitHeaders(ptr);\n+\t\tassertTrue(ptr > 0);\n+\n+\t\tassertEquals(\"a\", fh.getOldName());\n+\t\tassertEquals(\" c/\\u00c5ngstr\\u00f6m\", fh.getNewName());\n+\n+\t\tassertSame(FileHeader.ChangeType.RENAME, fh.getChangeType());\n+\n+\t\tassertNull(fh.getOldId());\n+\t\tassertNull(fh.getNewId());\n+\n+\t\tassertNull(fh.getOldMode());\n+\t\tassertNull(fh.getNewMode());\n+\n+\t\tassertEquals(100, fh.getScore());\n+\t}\n+\tpublic void testParseCopy100() {\n+\t\tfinal FileHeader fh = data(\"diff --git a/a b/ c/\\\\303\\\\205ngstr\\\\303\\\\266m\\n\"\n+\t\t\t\t+ \"similarity index 100%\\n\"\n+\t\t\t\t+ \"copy from a\\n\"\n+\t\t\t\t+ \"copy to \\\" c/\\\\303\\\\205ngstr\\\\303\\\\266m\\\"\\n\");\n+\t\tint ptr = fh.parseGitFileName(0);\n+\t\tassertTrue(ptr > 0);\n+\t\tassertNull(fh.getOldName()); // can't parse names on a copy\n+\t\tassertNull(fh.getNewName());\n+\n+\t\tptr = fh.parseGitHeaders(ptr);\n+\t\tassertTrue(ptr > 0);\n+\n+\t\tassertEquals(\"a\", fh.getOldName());\n+\t\tassertEquals(\" c/\\u00c5ngstr\\u00f6m\", fh.getNewName());\n+\n+\t\tassertSame(FileHeader.ChangeType.COPY, fh.getChangeType());\n+\n+\t\tassertNull(fh.getOldId());\n+\t\tassertNull(fh.getNewId());\n+\n+\t\tassertNull(fh.getOldMode());\n+\t\tassertNull(fh.getNewMode());\n+\n+\t\tassertEquals(100, fh.getScore());\n+\t}\n+\n+\tpublic void testParseFullIndexLine_WithMode() {\n+\t\tfinal String oid = \"78981922613b2afb6025042ff6bd878ac1994e85\";\n+\t\tfinal String nid = \"61780798228d17af2d34fce4cfbdf35556832472\";\n+\t\tfinal FileHeader fh = data(\"diff --git a/a b/a\\n\" + \"index \" + oid\n+\t\t\t\t+ \"..\" + nid + \" 100644\\n\" + \"--- a/a\\n\" + \"+++ b/a\\n\");\n+\t\tassertParse(fh);\n+\n+\t\tassertEquals(\"a\", fh.getOldName());\n+\t\tassertEquals(\"a\", fh.getNewName());\n+\n+\t\tassertSame(FileMode.REGULAR_FILE, fh.getOldMode());\n+\t\tassertSame(FileMode.REGULAR_FILE, fh.getNewMode());\n+\n+\t\tassertNotNull(fh.getOldId());\n+\t\tassertNotNull(fh.getNewId());\n+\n+\t\tassertTrue(fh.getOldId().isComplete());\n+\t\tassertTrue(fh.getNewId().isComplete());\n+\n+\t\tassertEquals(ObjectId.fromString(oid), fh.getOldId().toObjectId());\n+\t\tassertEquals(ObjectId.fromString(nid), fh.getNewId().toObjectId());\n+\t}\n+\n+\tpublic void testParseFullIndexLine_NoMode() {\n+\t\tfinal String oid = \"78981922613b2afb6025042ff6bd878ac1994e85\";\n+\t\tfinal String nid = \"61780798228d17af2d34fce4cfbdf35556832472\";\n+\t\tfinal FileHeader fh = data(\"diff --git a/a b/a\\n\" + \"index \" + oid\n+\t\t\t\t+ \"..\" + nid + \"\\n\" + \"--- a/a\\n\" + \"+++ b/a\\n\");\n+\t\tassertParse(fh);\n+\n+\t\tassertEquals(\"a\", fh.getOldName());\n+\t\tassertEquals(\"a\", fh.getNewName());\n+\n+\t\tassertNull(fh.getOldMode());\n+\t\tassertNull(fh.getNewMode());\n+\n+\t\tassertNotNull(fh.getOldId());\n+\t\tassertNotNull(fh.getNewId());\n+\n+\t\tassertTrue(fh.getOldId().isComplete());\n+\t\tassertTrue(fh.getNewId().isComplete());\n+\n+\t\tassertEquals(ObjectId.fromString(oid), fh.getOldId().toObjectId());\n+\t\tassertEquals(ObjectId.fromString(nid), fh.getNewId().toObjectId());\n+\t}\n+\n+\tpublic void testParseAbbrIndexLine_WithMode() {\n+\t\tfinal int a = 7;\n+\t\tfinal String oid = \"78981922613b2afb6025042ff6bd878ac1994e85\";\n+\t\tfinal String nid = \"61780798228d17af2d34fce4cfbdf35556832472\";\n+\t\tfinal FileHeader fh = data(\"diff --git a/a b/a\\n\" + \"index \"\n+\t\t\t\t+ oid.substring(0, a - 1) + \"..\" + nid.substring(0, a - 1)\n+\t\t\t\t+ \" 100644\\n\" + \"--- a/a\\n\" + \"+++ b/a\\n\");\n+\t\tassertParse(fh);\n+\n+\t\tassertEquals(\"a\", fh.getOldName());\n+\t\tassertEquals(\"a\", fh.getNewName());\n+\n+\t\tassertSame(FileMode.REGULAR_FILE, fh.getOldMode());\n+\t\tassertSame(FileMode.REGULAR_FILE, fh.getNewMode());\n+\n+\t\tassertNotNull(fh.getOldId());\n+\t\tassertNotNull(fh.getNewId());\n+\n+\t\tassertFalse(fh.getOldId().isComplete());\n+\t\tassertFalse(fh.getNewId().isComplete());\n+\n+\t\tassertEquals(oid.substring(0, a - 1), fh.getOldId().name());\n+\t\tassertEquals(nid.substring(0, a - 1), fh.getNewId().name());\n+\n+\t\tassertTrue(ObjectId.fromString(oid).startsWith(fh.getOldId()));\n+\t\tassertTrue(ObjectId.fromString(nid).startsWith(fh.getNewId()));\n+\t}\n+\n+\tpublic void testParseAbbrIndexLine_NoMode() {\n+\t\tfinal int a = 7;\n+\t\tfinal String oid = \"78981922613b2afb6025042ff6bd878ac1994e85\";\n+\t\tfinal String nid = \"61780798228d17af2d34fce4cfbdf35556832472\";\n+\t\tfinal FileHeader fh = data(\"diff --git a/a b/a\\n\" + \"index \"\n+\t\t\t\t+ oid.substring(0, a - 1) + \"..\" + nid.substring(0, a - 1)\n+\t\t\t\t+ \"\\n\" + \"--- a/a\\n\" + \"+++ b/a\\n\");\n+\t\tassertParse(fh);\n+\n+\t\tassertEquals(\"a\", fh.getOldName());\n+\t\tassertEquals(\"a\", fh.getNewName());\n+\n+\t\tassertNull(fh.getOldMode());\n+\t\tassertNull(fh.getNewMode());\n+\n+\t\tassertNotNull(fh.getOldId());\n+\t\tassertNotNull(fh.getNewId());\n+\n+\t\tassertFalse(fh.getOldId().isComplete());\n+\t\tassertFalse(fh.getNewId().isComplete());\n+\n+\t\tassertEquals(oid.substring(0, a - 1), fh.getOldId().name());\n+\t\tassertEquals(nid.substring(0, a - 1), fh.getNewId().name());\n+\n+\t\tassertTrue(ObjectId.fromString(oid).startsWith(fh.getOldId()));\n+\t\tassertTrue(ObjectId.fromString(nid).startsWith(fh.getNewId()));\n+\t}\n+\n+\tprivate static void assertParse(final FileHeader fh) {\n+\t\tint ptr = fh.parseGitFileName(0);\n+\t\tassertTrue(ptr > 0);\n+\t\tptr = fh.parseGitHeaders(ptr);\n+\t\tassertTrue(ptr > 0);\n+\t}\n+\n+\tprivate static FileHeader data(final String in) {\n+\t\treturn new FileHeader(Constants.encodeASCII(in), 0);\n+\t}\n+\n+\tprivate static FileHeader header(final String path) {\n+\t\treturn data(gitLine(path) + \"--- \" + path + \"\\n\");\n+\t}\n+\n+\tprivate static String gitLine(final String path) {\n+\t\treturn \"a/\" + path + \" b/\" + path + \"\\n\";\n+\t}\n+\n+\tprivate static FileHeader dqHeader(final String path) {\n+\t\treturn data(dqGitLine(path) + \"--- \" + path + \"\\n\");\n+\t}\n+\n+\tprivate static String dqGitLine(final String path) {\n+\t\treturn \"\\\"a/\" + path + \"\\\" \\\"b/\" + path + \"\\\"\\n\";\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\nnew file mode 100644\nindex 0000000..5d1454b\n--- /dev/null\n+++ b/org.spearce.jgit/src/org/spearce/jgit/patch/FileHeader.java\n@@ -0,0 +1,430 @@\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.decode;\n+import static org.spearce.jgit.util.RawParseUtils.match;\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.lib.Constants;\n+import org.spearce.jgit.lib.FileMode;\n+import org.spearce.jgit.util.QuotedString;\n+\n+/** Patch header describing an action for a single file path. */\n+public class FileHeader {\n+\t/** Magical file name used for file adds or deletes. */\n+\tpublic static final String DEV_NULL = \"/dev/null\";\n+\n+\tprivate static final byte[] OLD_MODE = encodeASCII(\"old mode \");\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+\n+\tprivate static final byte[] NEW_FILE_MODE = encodeASCII(\"new file mode \");\n+\n+\tprivate static final byte[] COPY_FROM = encodeASCII(\"copy from \");\n+\n+\tprivate static final byte[] COPY_TO = encodeASCII(\"copy to \");\n+\n+\tprivate static final byte[] RENAME_OLD = encodeASCII(\"rename old \");\n+\n+\tprivate static final byte[] RENAME_NEW = encodeASCII(\"rename new \");\n+\n+\tprivate static final byte[] RENAME_FROM = encodeASCII(\"rename from \");\n+\n+\tprivate static final byte[] RENAME_TO = encodeASCII(\"rename to \");\n+\n+\tprivate static final byte[] SIMILARITY_INDEX = encodeASCII(\"similarity index \");\n+\n+\tprivate static final byte[] DISSIMILARITY_INDEX = encodeASCII(\"dissimilarity index \");\n+\n+\tprivate static final byte[] INDEX = encodeASCII(\"index \");\n+\n+\tstatic final byte[] OLD_NAME = encodeASCII(\"--- \");\n+\n+\tstatic final byte[] NEW_NAME = encodeASCII(\"+++ \");\n+\n+\tstatic final byte[] HUNK_HDR = encodeASCII(\"@@ -\");\n+\n+\t/** General type of change a single file-level patch describes. */\n+\tpublic static enum ChangeType {\n+\t\t/** Add a new file to the project */\n+\t\tADD,\n+\n+\t\t/** Modify an existing file in the project (content and/or mode) */\n+\t\tMODIFY,\n+\n+\t\t/** Delete an existing file from the project */\n+\t\tDELETE,\n+\n+\t\t/** Rename an existing file to a new location */\n+\t\tRENAME,\n+\n+\t\t/** Copy an existing file to a new location, keeping the original */\n+\t\tCOPY;\n+\t}\n+\n+\t/** Buffer holding the patch data for this file. */\n+\tfinal byte[] buf;\n+\n+\t/** Offset within {@link #buf} to the \"diff ...\" line. */\n+\tfinal int startOffset;\n+\n+\t/** Position 1 past the end of this file within {@link #buf}. */\n+\tint endOffset;\n+\n+\t/** File name of the old (pre-image). */\n+\tprivate String oldName;\n+\n+\t/** File name of the new (post-image). */\n+\tprivate String newName;\n+\n+\t/** Old mode of the file, if described by the patch, else null. */\n+\tprivate FileMode oldMode;\n+\n+\t/** New mode of the file, if described by the patch, else null. */\n+\tprivate FileMode newMode;\n+\n+\t/** General type of change indicated by the patch. */\n+\tprivate ChangeType changeType;\n+\n+\t/** Similarity score if {@link #changeType} is a copy or rename. */\n+\tprivate int score;\n+\n+\t/** ObjectId listed on the index line for the old (pre-image) */\n+\tprivate AbbreviatedObjectId oldId;\n+\n+\t/** ObjectId listed on the index line for the new (post-image) */\n+\tprivate AbbreviatedObjectId newId;\n+\n+\tFileHeader(final byte[] b, final int offset) {\n+\t\tbuf = b;\n+\t\tstartOffset = offset;\n+\t\tchangeType = ChangeType.MODIFY; // unless otherwise designated\n+\t}\n+\n+\t/**\n+\t * Get the old name associated with this file.\n+\t * <p>\n+\t * The meaning of the old name can differ depending on the semantic meaning\n+\t * of this patch:\n+\t * <ul>\n+\t * <li><i>file add</i>: always <code>/dev/null</code></li>\n+\t * <li><i>file modify</i>: always {@link #getNewName()}</li>\n+\t * <li><i>file delete</i>: always the file being deleted</li>\n+\t * <li><i>file copy</i>: source file the copy originates from</li>\n+\t * <li><i>file rename</i>: source file the rename originates from</li>\n+\t * </ul>\n+\t * \n+\t * @return old name for this file.\n+\t */\n+\tpublic String getOldName() {\n+\t\treturn oldName;\n+\t}\n+\n+\t/**\n+\t * Get the new name associated with this file.\n+\t * <p>\n+\t * The meaning of the new name can differ depending on the semantic meaning\n+\t * of this patch:\n+\t * <ul>\n+\t * <li><i>file add</i>: always the file being created</li>\n+\t * <li><i>file modify</i>: always {@link #getOldName()}</li>\n+\t * <li><i>file delete</i>: always <code>/dev/null</code></li>\n+\t * <li><i>file copy</i>: destination file the copy ends up at</li>\n+\t * <li><i>file rename</i>: destination file the rename ends up at/li>\n+\t * </ul>\n+\t * \n+\t * @return new name for this file.\n+\t */\n+\tpublic String getNewName() {\n+\t\treturn newName;\n+\t}\n+\n+\t/** @return the old file mode, if described in the patch */\n+\tpublic FileMode getOldMode() {\n+\t\treturn oldMode;\n+\t}\n+\n+\t/** @return the new file mode, if described in the patch */\n+\tpublic FileMode getNewMode() {\n+\t\treturn newMode;\n+\t}\n+\n+\t/** @return the type of change this patch makes on {@link #getNewName()} */\n+\tpublic ChangeType getChangeType() {\n+\t\treturn changeType;\n+\t}\n+\n+\t/**\n+\t * @return similarity score between {@link #getOldName()} and\n+\t *         {@link #getNewName()} if {@link #getChangeType()} is\n+\t *         {@link ChangeType#COPY} or {@link ChangeType#RENAME}.\n+\t */\n+\tpublic int getScore() {\n+\t\treturn score;\n+\t}\n+\n+\t/**\n+\t * Get the old object id from the <code>index</code>.\n+\t * \n+\t * @return the object id; null if there is no index line\n+\t */\n+\tpublic AbbreviatedObjectId getOldId() {\n+\t\treturn oldId;\n+\t}\n+\n+\t/**\n+\t * Get the new object id from the <code>index</code>.\n+\t * \n+\t * @return the object id; null if there is no index line\n+\t */\n+\tpublic AbbreviatedObjectId getNewId() {\n+\t\treturn newId;\n+\t}\n+\n+\t/**\n+\t * Parse a \"diff --git\" or \"diff --cc\" line.\n+\t * \n+\t * @param ptr\n+\t *            first character after the \"diff --git \" or \"diff --cc \" part.\n+\t * @return first character after the LF at the end of the line; -1 on error.\n+\t */\n+\tint parseGitFileName(int ptr) {\n+\t\tfinal int eol = nextLF(buf, ptr);\n+\t\tfinal int bol = ptr;\n+\t\tif (eol >= buf.length) {\n+\t\t\treturn -1;\n+\t\t}\n+\n+\t\t// buffer[ptr..eol] looks like \"a/foo b/foo\\n\". After the first\n+\t\t// A regex to match this is \"^[^/]+/(.*?) [^/+]+/\\1\\n$\". There\n+\t\t// is only one way to split the line such that text to the left\n+\t\t// of the space matches the text to the right, excluding the part\n+\t\t// before the first slash.\n+\t\t//\n+\n+\t\tfinal int aStart = nextLF(buf, ptr, '/');\n+\t\tif (aStart >= eol)\n+\t\t\treturn eol;\n+\n+\t\twhile (ptr < eol) {\n+\t\t\tfinal int sp = nextLF(buf, ptr, ' ');\n+\t\t\tif (sp >= eol) {\n+\t\t\t\t// We can't split the header, it isn't valid.\n+\t\t\t\t// This may be OK if this is a rename patch.\n+\t\t\t\t//\n+\t\t\t\treturn eol;\n+\t\t\t}\n+\t\t\tfinal int bStart = nextLF(buf, sp, '/');\n+\t\t\tif (bStart >= eol)\n+\t\t\t\treturn eol;\n+\n+\t\t\t// If buffer[aStart..sp - 1] = buffer[bStart..eol - 1]\n+\t\t\t// we have a valid split.\n+\t\t\t//\n+\t\t\tif (eq(aStart, sp - 1, bStart, eol - 1)) {\n+\t\t\t\tif (buf[bol] == '\"') {\n+\t\t\t\t\t// We're a double quoted name. The region better end\n+\t\t\t\t\t// in a double quote too, and we need to decode the\n+\t\t\t\t\t// characters before reading the name.\n+\t\t\t\t\t//\n+\t\t\t\t\tif (buf[sp - 2] != '\"') {\n+\t\t\t\t\t\treturn eol;\n+\t\t\t\t\t}\n+\t\t\t\t\toldName = QuotedString.GIT_PATH.dequote(buf, bol, sp - 1);\n+\t\t\t\t\toldName = p1(oldName);\n+\t\t\t\t} else {\n+\t\t\t\t\toldName = decode(Constants.CHARSET, buf, aStart, sp - 1);\n+\t\t\t\t}\n+\t\t\t\tnewName = oldName;\n+\t\t\t\treturn eol;\n+\t\t\t}\n+\n+\t\t\t// This split wasn't correct. Move past the space and try\n+\t\t\t// another split as the space must be part of the file name.\n+\t\t\t//\n+\t\t\tptr = sp;\n+\t\t}\n+\n+\t\treturn eol;\n+\t}\n+\n+\tint parseGitHeaders(int ptr) {\n+\t\tfinal int sz = buf.length;\n+\t\twhile (ptr < sz) {\n+\t\t\tfinal int eol = nextLF(buf, ptr);\n+\t\t\tif (match(buf, ptr, HUNK_HDR) >= 0) {\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\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+\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+\n+\t\t\t} else if (match(buf, ptr, OLD_MODE) >= 0) {\n+\t\t\t\toldMode = parseFileMode(ptr + OLD_MODE.length, eol);\n+\n+\t\t\t} else if (match(buf, ptr, NEW_MODE) >= 0) {\n+\t\t\t\tnewMode = parseFileMode(ptr + NEW_MODE.length, eol);\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\tchangeType = ChangeType.DELETE;\n+\n+\t\t\t} else if (match(buf, ptr, NEW_FILE_MODE) >= 0) {\n+\t\t\t\tnewMode = parseFileMode(ptr + NEW_FILE_MODE.length, eol);\n+\t\t\t\tchangeType = ChangeType.ADD;\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+\t\t\t\tchangeType = ChangeType.COPY;\n+\n+\t\t\t} else if (match(buf, ptr, COPY_TO) >= 0) {\n+\t\t\t\tnewName = parseName(newName, ptr + COPY_TO.length, eol);\n+\t\t\t\tchangeType = ChangeType.COPY;\n+\n+\t\t\t} else if (match(buf, ptr, RENAME_OLD) >= 0) {\n+\t\t\t\toldName = parseName(oldName, ptr + RENAME_OLD.length, eol);\n+\t\t\t\tchangeType = ChangeType.RENAME;\n+\n+\t\t\t} else if (match(buf, ptr, RENAME_NEW) >= 0) {\n+\t\t\t\tnewName = parseName(newName, ptr + RENAME_NEW.length, eol);\n+\t\t\t\tchangeType = ChangeType.RENAME;\n+\n+\t\t\t} else if (match(buf, ptr, RENAME_FROM) >= 0) {\n+\t\t\t\toldName = parseName(oldName, ptr + RENAME_FROM.length, eol);\n+\t\t\t\tchangeType = ChangeType.RENAME;\n+\n+\t\t\t} else if (match(buf, ptr, RENAME_TO) >= 0) {\n+\t\t\t\tnewName = parseName(newName, ptr + RENAME_TO.length, eol);\n+\t\t\t\tchangeType = ChangeType.RENAME;\n+\n+\t\t\t} else if (match(buf, ptr, SIMILARITY_INDEX) >= 0) {\n+\t\t\t\tscore = parseBase10(buf, ptr + SIMILARITY_INDEX.length, null);\n+\n+\t\t\t} else if (match(buf, ptr, DISSIMILARITY_INDEX) >= 0) {\n+\t\t\t\tscore = parseBase10(buf, ptr + DISSIMILARITY_INDEX.length, null);\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 {\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+\tprivate String parseName(final String expect, int ptr, final int end) {\n+\t\tif (ptr == end)\n+\t\t\treturn expect;\n+\n+\t\tString r;\n+\t\tif (buf[ptr] == '\"') {\n+\t\t\t// New style GNU diff format\n+\t\t\t//\n+\t\t\tr = QuotedString.GIT_PATH.dequote(buf, ptr, end - 1);\n+\t\t} else {\n+\t\t\t// Older style GNU diff format, an optional tab ends the name.\n+\t\t\t//\n+\t\t\tint tab = end;\n+\t\t\twhile (ptr < tab && buf[tab - 1] != '\\t')\n+\t\t\t\ttab--;\n+\t\t\tif (ptr == tab)\n+\t\t\t\ttab = end;\n+\t\t\tr = decode(Constants.CHARSET, buf, ptr, tab - 1);\n+\t\t}\n+\n+\t\tif (r.equals(DEV_NULL))\n+\t\t\tr = DEV_NULL;\n+\t\treturn r;\n+\t}\n+\n+\tprivate static String p1(final String r) {\n+\t\tfinal int s = r.indexOf('/');\n+\t\treturn s > 0 ? r.substring(s + 1) : r;\n+\t}\n+\n+\tprivate FileMode parseFileMode(int ptr, final int end) {\n+\t\tint tmp = 0;\n+\t\twhile (ptr < end - 1) {\n+\t\t\ttmp <<= 3;\n+\t\t\ttmp += buf[ptr++] - '0';\n+\t\t}\n+\t\treturn FileMode.fromBits(tmp);\n+\t}\n+\n+\tprivate 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//\n+\t\tfinal int dot2 = nextLF(buf, ptr, '.');\n+\t\tfinal int mode = nextLF(buf, dot2, ' ');\n+\n+\t\toldId = AbbreviatedObjectId.fromString(buf, ptr, dot2 - 1);\n+\t\tnewId = AbbreviatedObjectId.fromString(buf, dot2 + 1, mode - 1);\n+\n+\t\tif (mode < end)\n+\t\t\tnewMode = oldMode = parseFileMode(mode, end);\n+\t}\n+\n+\tprivate boolean eq(int aPtr, int aEnd, int bPtr, int bEnd) {\n+\t\tif (aEnd - aPtr != bEnd - bPtr) {\n+\t\t\treturn false;\n+\t\t}\n+\t\twhile (aPtr < aEnd) {\n+\t\t\tif (buf[aPtr++] != buf[bPtr++])\n+\t\t\t\treturn false;\n+\t\t}\n+\t\treturn true;\n+\t}\n+}\n-- \n1.6.1.rc2.299.gead4c\n"},{"id":"97578","messageId":"1228971522-28764-5-git-send-email-spearce@spearce.org","threadId":"16678","inReplyTo":"1228971522-28764-4-git-send-email-spearce@spearce.org","subject":"[JGIT PATCH 4/5] Define Patch to parse a sequence of patch FileHeaders","fromName":"Shawn O. Pearce","fromEmail":"spearce@spearce.org","sentAt":"2008-12-11T04:58:41Z","receivedAt":"2008-12-11T04:58:41Z","isPatch":true,"sender":{"key":"spearce@spearce.org","avatar":"https://avatars.githubusercontent.com/u/34844?v=4"},"body":"Most patch scripts impact more than one file at a time, so we need\nto support parsing multiple FileHeaders from the same input stream\nand collect them into a larger entity representing the entire script.\n\nSigned-off-by: Shawn O. Pearce <spearce@spearce.org>\n---\n .../tst/org/spearce/jgit/patch/PatchTest.java      |   97 +++++++++\n .../patch/testParse_ConfigCaseInsensitive.patch    |   67 ++++++\n .../src/org/spearce/jgit/patch/FileHeader.java     |   28 +++\n .../src/org/spearce/jgit/patch/Patch.java          |  217 ++++++++++++++++++++\n 4 files changed, 409 insertions(+), 0 deletions(-)\n create mode 100644 org.spearce.jgit.test/tst/org/spearce/jgit/patch/PatchTest.java\n create mode 100644 org.spearce.jgit.test/tst/org/spearce/jgit/patch/testParse_ConfigCaseInsensitive.patch\n create mode 100644 org.spearce.jgit/src/org/spearce/jgit/patch/Patch.java\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\nnew file mode 100644\nindex 0000000..f389d89\n--- /dev/null\n+++ b/org.spearce.jgit.test/tst/org/spearce/jgit/patch/PatchTest.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+import org.spearce.jgit.lib.FileMode;\n+\n+public class PatchTest extends TestCase {\n+\tpublic void testEmpty() {\n+\t\tfinal Patch p = new Patch();\n+\t\tassertTrue(p.getFiles().isEmpty());\n+\t}\n+\n+\tpublic void testParse_ConfigCaseInsensitive() throws IOException {\n+\t\tfinal Patch p = parseTestPatchFile();\n+\t\tassertEquals(2, p.getFiles().size());\n+\n+\t\tfinal FileHeader fRepositoryConfigTest = p.getFiles().get(0);\n+\t\tfinal FileHeader fRepositoryConfig = p.getFiles().get(1);\n+\n+\t\tassertEquals(\n+\t\t\t\t\"org.spearce.jgit.test/tst/org/spearce/jgit/lib/RepositoryConfigTest.java\",\n+\t\t\t\tfRepositoryConfigTest.getNewName());\n+\n+\t\tassertEquals(\n+\t\t\t\t\"org.spearce.jgit/src/org/spearce/jgit/lib/RepositoryConfig.java\",\n+\t\t\t\tfRepositoryConfig.getNewName());\n+\n+\t\tassertEquals(572, fRepositoryConfigTest.startOffset);\n+\t\tassertEquals(1490, fRepositoryConfig.startOffset);\n+\n+\t\tassertEquals(\"da7e704\", fRepositoryConfigTest.getOldId().name());\n+\t\tassertEquals(\"34ce04a\", fRepositoryConfigTest.getNewId().name());\n+\t\tassertSame(FileMode.REGULAR_FILE, fRepositoryConfigTest.getOldMode());\n+\t\tassertSame(FileMode.REGULAR_FILE, fRepositoryConfigTest.getNewMode());\n+\n+\t\tassertEquals(\"45c2f8a\", fRepositoryConfig.getOldId().name());\n+\t\tassertEquals(\"3291bba\", fRepositoryConfig.getNewId().name());\n+\t\tassertSame(FileMode.REGULAR_FILE, fRepositoryConfig.getOldMode());\n+\t\tassertSame(FileMode.REGULAR_FILE, fRepositoryConfig.getNewMode());\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/testParse_ConfigCaseInsensitive.patch b/org.spearce.jgit.test/tst/org/spearce/jgit/patch/testParse_ConfigCaseInsensitive.patch\nnew file mode 100644\nindex 0000000..b30418e\n--- /dev/null\n+++ b/org.spearce.jgit.test/tst/org/spearce/jgit/patch/testParse_ConfigCaseInsensitive.patch\n@@ -0,0 +1,67 @@\n+From ce9b593ddf2530613f6da9d7f7e4a5ff93da8b36 Mon Sep 17 00:00:00 2001\n+From: Robin Rosenberg <robin.rosenberg@dewire.com>\n+Date: Mon, 13 Oct 2008 00:50:59 +0200\n+Subject: [PATCH] git config file is case insensitive\n+\n+Keys are now always compared with ignore case rules.\n+\n+Signed-off-by: Robin Rosenberg <robin.rosenberg@dewire.com>\n+Signed-off-by: Shawn O. Pearce <spearce@spearce.org>\n+---\n+ .../org/spearce/jgit/lib/RepositoryConfigTest.java |    7 +++++++\n+ .../src/org/spearce/jgit/lib/RepositoryConfig.java |    8 ++++----\n+ 2 files changed, 11 insertions(+), 4 deletions(-)\n+\n+diff --git a/org.spearce.jgit.test/tst/org/spearce/jgit/lib/RepositoryConfigTest.java b/org.spearce.jgit.test/tst/org/spearce/jgit/lib/RepositoryConfigTest.java\n+index da7e704..34ce04a 100644\n+--- a/org.spearce.jgit.test/tst/org/spearce/jgit/lib/RepositoryConfigTest.java\n++++ b/org.spearce.jgit.test/tst/org/spearce/jgit/lib/RepositoryConfigTest.java\n+@@ -109,4 +109,11 @@ assertTrue(Arrays.equals(values.toArray(), repositoryConfig\n+ \t\t\t\t.getStringList(\"my\", null, \"somename\")));\n+ \t\tcheckFile(cfgFile, \"[my]\\n\\tsomename = value1\\n\\tsomename = value2\\n\");\n+ \t}\n++\n++\tpublic void test006_readCaseInsensitive() throws IOException {\n++\t\tfinal File path = writeTrashFile(\"config_001\", \"[Foo]\\nBar\\n\");\n++\t\tRepositoryConfig repositoryConfig = new RepositoryConfig(null, path);\n++\t\tassertEquals(true, repositoryConfig.getBoolean(\"foo\", null, \"bar\", false));\n++\t\tassertEquals(\"\", repositoryConfig.getString(\"foo\", null, \"bar\"));\n++\t}\n+ }\n+diff --git a/org.spearce.jgit/src/org/spearce/jgit/lib/RepositoryConfig.java b/org.spearce.jgit/src/org/spearce/jgit/lib/RepositoryConfig.java\n+index 45c2f8a..3291bba 100644\n+--- a/org.spearce.jgit/src/org/spearce/jgit/lib/RepositoryConfig.java\n++++ b/org.spearce.jgit/src/org/spearce/jgit/lib/RepositoryConfig.java\n+@@ -236,9 +236,9 @@ protected boolean getBoolean(final String section, String subsection,\n+ \t\t\treturn defaultValue;\n+ \n+ \t\tn = n.toLowerCase();\n+-\t\tif (MAGIC_EMPTY_VALUE.equals(n) || \"yes\".equals(n) || \"true\".equals(n) || \"1\".equals(n)) {\n++\t\tif (MAGIC_EMPTY_VALUE.equals(n) || \"yes\".equalsIgnoreCase(n) || \"true\".equalsIgnoreCase(n) || \"1\".equals(n)) {\n+ \t\t\treturn true;\n+-\t\t} else if (\"no\".equals(n) || \"false\".equals(n) || \"0\".equals(n)) {\n++\t\t} else if (\"no\".equalsIgnoreCase(n) || \"false\".equalsIgnoreCase(n) || \"0\".equalsIgnoreCase(n)) {\n+ \t\t\treturn false;\n+ \t\t} else {\n+ \t\t\tthrow new IllegalArgumentException(\"Invalid boolean value: \"\n+@@ -300,7 +300,7 @@ public String getString(final String section, String subsection, final String na\n+ \t\tfinal Set<String> result = new HashSet<String>();\n+ \n+ \t\tfor (final Entry e : entries) {\n+-\t\t\tif (section.equals(e.base) && e.extendedBase != null)\n++\t\t\tif (section.equalsIgnoreCase(e.base) && e.extendedBase != null)\n+ \t\t\t\tresult.add(e.extendedBase);\n+ \t\t}\n+ \t\tif (baseConfig != null)\n+@@ -954,7 +954,7 @@ private static boolean eq(final String a, final String b) {\n+ \t\t\t\treturn true;\n+ \t\t\tif (a == null || b == null)\n+ \t\t\t\treturn false;\n+-\t\t\treturn a.equals(b);\n++\t\t\treturn a.equalsIgnoreCase(b);\n+ \t\t}\n+ \t}\n+ }\n+-- \n+1.6.1.rc2.299.gead4c\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 5d1454b..f57a0ff 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@@ -364,6 +364,34 @@ int parseGitHeaders(int ptr) {\n \t\treturn ptr;\n \t}\n \n+\tint parseTraditionalHeaders(int ptr) {\n+\t\tfinal int sz = buf.length;\n+\t\twhile (ptr < sz) {\n+\t\t\tfinal int eol = nextLF(buf, ptr);\n+\t\t\tif (match(buf, ptr, HUNK_HDR) >= 0) {\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\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+\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+\n+\t\t\t} else {\n+\t\t\t\t// Possibly an empty patch.\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 \tprivate String parseName(final String expect, int ptr, final int end) {\n \t\tif (ptr == end)\n \t\t\treturn expect;\ndiff --git a/org.spearce.jgit/src/org/spearce/jgit/patch/Patch.java b/org.spearce.jgit/src/org/spearce/jgit/patch/Patch.java\nnew file mode 100644\nindex 0000000..30d12a5\n--- /dev/null\n+++ b/org.spearce.jgit/src/org/spearce/jgit/patch/Patch.java\n@@ -0,0 +1,217 @@\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.patch.FileHeader.HUNK_HDR;\n+import static org.spearce.jgit.patch.FileHeader.NEW_NAME;\n+import static org.spearce.jgit.patch.FileHeader.OLD_NAME;\n+import static org.spearce.jgit.util.RawParseUtils.match;\n+import static org.spearce.jgit.util.RawParseUtils.nextLF;\n+\n+import java.io.IOException;\n+import java.io.InputStream;\n+import java.util.ArrayList;\n+import java.util.List;\n+\n+import org.spearce.jgit.util.TemporaryBuffer;\n+\n+/** A parsed collection of {@link FileHeader}s from a unified diff patch file */\n+public class Patch {\n+\tprivate static final byte[] DIFF_GIT = encodeASCII(\"diff --git \");\n+\n+\tprivate static final byte[] DIFF_CC = encodeASCII(\"diff --cc \");\n+\n+\t/** The files, in the order they were parsed out of the input. */\n+\tprivate final List<FileHeader> files;\n+\n+\t/** Create an empty patch. */\n+\tpublic Patch() {\n+\t\tfiles = new ArrayList<FileHeader>();\n+\t}\n+\n+\t/**\n+\t * Add a single file to this patch.\n+\t * <p>\n+\t * Typically files should be added by parsing the text through one of this\n+\t * class's parse methods.\n+\t * \n+\t * @param fh\n+\t *            the header of the file.\n+\t */\n+\tpublic void addFile(final FileHeader fh) {\n+\t\tfiles.add(fh);\n+\t}\n+\n+\t/** @return list of files described in the patch, in occurrence order. */\n+\tpublic List<FileHeader> getFiles() {\n+\t\treturn files;\n+\t}\n+\n+\t/**\n+\t * Parse a patch received from an InputStream.\n+\t * <p>\n+\t * Multiple parse calls on the same instance will concatenate the patch\n+\t * data, but each parse input must start with a valid file header (don't\n+\t * split a single file across parse calls).\n+\t * \n+\t * @param is\n+\t *            the stream to read the patch data from. The stream is read\n+\t *            until EOF is reached.\n+\t * @throws IOException\n+\t *             there was an error reading from the input stream.\n+\t */\n+\tpublic void parse(final InputStream is) throws IOException {\n+\t\tfinal byte[] buf = readFully(is);\n+\t\tparse(buf, 0, buf.length);\n+\t}\n+\n+\tprivate static byte[] readFully(final InputStream is) throws IOException {\n+\t\tfinal TemporaryBuffer b = new TemporaryBuffer();\n+\t\tb.copy(is);\n+\t\tfinal byte[] buf = b.toByteArray();\n+\t\treturn buf;\n+\t}\n+\n+\t/**\n+\t * Parse a patch stored in a byte[].\n+\t * <p>\n+\t * Multiple parse calls on the same instance will concatenate the patch\n+\t * data, but each parse input must start with a valid file header (don't\n+\t * split a single file across parse calls).\n+\t * \n+\t * @param buf\n+\t *            the buffer to parse.\n+\t * @param ptr\n+\t *            starting position to parse from.\n+\t * @param end\n+\t *            1 past the last position to end parsing. The total length to\n+\t *            be parsed is <code>end - ptr</code>.\n+\t */\n+\tpublic void parse(final byte[] buf, int ptr, final int end) {\n+\t\twhile (ptr < end)\n+\t\t\tptr = parseFile(buf, ptr);\n+\t}\n+\n+\tprivate int parseFile(final byte[] buf, int c) {\n+\t\tfinal int sz = buf.length;\n+\t\twhile (c < sz) {\n+\t\t\t// Valid git style patch?\n+\t\t\t//\n+\t\t\tif (match(buf, c, DIFF_GIT) >= 0)\n+\t\t\t\treturn parseDiffGit(buf, c);\n+\t\t\tif (match(buf, c, DIFF_CC) >= 0)\n+\t\t\t\treturn parseDiffCC(buf, c);\n+\n+\t\t\t// Junk between files? Leading junk? Traditional\n+\t\t\t// (non-git generated) patch?\n+\t\t\t//\n+\t\t\tfinal int n = nextLF(buf, c);\n+\t\t\tif (n >= sz) {\n+\t\t\t\t// Patches cannot be only one line long. This must be\n+\t\t\t\t// trailing junk that we should ignore.\n+\t\t\t\t//\n+\t\t\t\treturn sz;\n+\t\t\t}\n+\n+\t\t\tif (n - c < 6) {\n+\t\t\t\t// A valid header must be at least 6 bytes on the\n+\t\t\t\t// first line, e.g. \"--- a/b\\n\".\n+\t\t\t\t//\n+\t\t\t\tc = n;\n+\t\t\t\tcontinue;\n+\t\t\t}\n+\n+\t\t\tif (match(buf, c, OLD_NAME) >= 0 && match(buf, n, NEW_NAME) >= 0) {\n+\t\t\t\t// Probably a traditional patch. Ensure we have at least\n+\t\t\t\t// a \"@@ -0,0\" smelling line next. We only check the \"@@ -\".\n+\t\t\t\t//\n+\t\t\t\tfinal int f = nextLF(buf, n);\n+\t\t\t\tif (f >= sz)\n+\t\t\t\t\treturn sz;\n+\t\t\t\tif (match(buf, f, HUNK_HDR) >= 0)\n+\t\t\t\t\treturn parseTraditionalPatch(buf, c);\n+\t\t\t}\n+\n+\t\t\tc = n;\n+\t\t}\n+\t\treturn c;\n+\t}\n+\n+\tprivate int parseDiffGit(final byte[] buf, final int startOffset) {\n+\t\tfinal FileHeader fh = new FileHeader(buf, startOffset);\n+\t\tint ptr = fh.parseGitFileName(startOffset + DIFF_GIT.length);\n+\t\tif (ptr < 0)\n+\t\t\treturn skipFile(buf, startOffset);\n+\n+\t\tptr = fh.parseGitHeaders(ptr);\n+\t\t// TODO parse hunks\n+\t\tfh.endOffset = ptr;\n+\t\taddFile(fh);\n+\t\treturn ptr;\n+\t}\n+\n+\tprivate int parseDiffCC(final byte[] buf, final int startOffset) {\n+\t\tfinal FileHeader fh = new FileHeader(buf, startOffset);\n+\t\tint ptr = fh.parseGitFileName(startOffset + DIFF_CC.length);\n+\t\tif (ptr < 0)\n+\t\t\treturn skipFile(buf, startOffset);\n+\n+\t\t// TODO Support parsing diff --cc headers\n+\t\t// TODO parse diff --cc hunks\n+\t\tfh.endOffset = ptr;\n+\t\taddFile(fh);\n+\t\treturn ptr;\n+\t}\n+\n+\tprivate int parseTraditionalPatch(final byte[] buf, final int startOffset) {\n+\t\tfinal FileHeader fh = new FileHeader(buf, startOffset);\n+\t\tint ptr = fh.parseTraditionalHeaders(startOffset);\n+\t\t// TODO parse hunks\n+\t\tfh.endOffset = ptr;\n+\t\taddFile(fh);\n+\t\treturn ptr;\n+\t}\n+\n+\tprivate static int skipFile(final byte[] buf, int ptr) {\n+\t\tptr = nextLF(buf, ptr);\n+\t\tif (match(buf, ptr, OLD_NAME) >= 0)\n+\t\t\tptr = nextLF(buf, ptr);\n+\t\treturn ptr;\n+\t}\n+}\n-- \n1.6.1.rc2.299.gead4c\n"},{"id":"97579","messageId":"1228971522-28764-6-git-send-email-spearce@spearce.org","threadId":"16678","inReplyTo":"1228971522-28764-5-git-send-email-spearce@spearce.org","subject":"[JGIT PATCH 5/5] Add HunkHeader to represent a single hunk of a file within a patch","fromName":"Shawn O. Pearce","fromEmail":"spearce@spearce.org","sentAt":"2008-12-11T04:58:42Z","receivedAt":"2008-12-11T04:58:42Z","isPatch":true,"sender":{"key":"spearce@spearce.org","avatar":"https://avatars.githubusercontent.com/u/34844?v=4"},"body":"The hunk header parses the lines of the hunk, counting up the body\nto ensure it matches with the header line.  We store the hunks on\non the FileHeader, providing public access to them after a patch\nhas been parsed.\n\nIf the line counts in the body differ from the header we don't make\nthat an immediate error.  This permits a future implementation of\nsomething like \"git apply --recount\", where the hunk headers can be\nrecomputed based on the actual hunk contents after a human has done\nmanual editing.  We may also need to parse a corrupt patch and show\nit in a visual tool for a human to repair, so aborting with any sort\nof exception isn't useful.\n\nSigned-off-by: Shawn O. Pearce <spearce@spearce.org>\n---\n .../tst/org/spearce/jgit/patch/FileHeaderTest.java |    6 +-\n .../tst/org/spearce/jgit/patch/PatchTest.java      |   60 ++++++-\n .../src/org/spearce/jgit/patch/FileHeader.java     |   22 +++\n .../src/org/spearce/jgit/patch/HunkHeader.java     |  185 ++++++++++++++++++++\n .../src/org/spearce/jgit/patch/Patch.java          |   54 ++++++-\n 5 files changed, 323 insertions(+), 4 deletions(-)\n create mode 100644 org.spearce.jgit/src/org/spearce/jgit/patch/HunkHeader.java\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 1d87bc0..4094a5c 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@@ -44,7 +44,10 @@\n \n public class FileHeaderTest extends TestCase {\n \tpublic void testParseGitFileName_Empty() {\n-\t\tassertEquals(-1, data(\"\").parseGitFileName(0));\n+\t\tfinal FileHeader fh = data(\"\");\n+\t\tassertEquals(-1, fh.parseGitFileName(0));\n+\t\tassertNotNull(fh.getHunks());\n+\t\tassertTrue(fh.getHunks().isEmpty());\n \t}\n \n \tpublic void testParseGitFileName_NoLF() {\n@@ -233,6 +236,7 @@ public void testParseRename100_OldStyle() {\n \n \t\tassertEquals(100, fh.getScore());\n \t}\n+\n \tpublic void testParseCopy100() {\n \t\tfinal FileHeader fh = data(\"diff --git a/a b/ c/\\\\303\\\\205ngstr\\\\303\\\\266m\\n\"\n \t\t\t\t+ \"similarity index 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 f389d89..8ddaadc 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@@ -72,11 +72,69 @@ assertEquals(\n \t\tassertEquals(\"34ce04a\", fRepositoryConfigTest.getNewId().name());\n \t\tassertSame(FileMode.REGULAR_FILE, fRepositoryConfigTest.getOldMode());\n \t\tassertSame(FileMode.REGULAR_FILE, fRepositoryConfigTest.getNewMode());\n+\t\tassertEquals(1, fRepositoryConfigTest.getHunks().size());\n+\t\t{\n+\t\t\tfinal HunkHeader h = fRepositoryConfigTest.getHunks().get(0);\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.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+\n+\t\t\tassertEquals(1490, h.endOffset);\n+\t\t}\n \n \t\tassertEquals(\"45c2f8a\", fRepositoryConfig.getOldId().name());\n \t\tassertEquals(\"3291bba\", fRepositoryConfig.getNewId().name());\n \t\tassertSame(FileMode.REGULAR_FILE, fRepositoryConfig.getOldMode());\n \t\tassertSame(FileMode.REGULAR_FILE, fRepositoryConfig.getNewMode());\n+\t\tassertEquals(3, fRepositoryConfig.getHunks().size());\n+\t\t{\n+\t\t\tfinal HunkHeader h = fRepositoryConfig.getHunks().get(0);\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.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+\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.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+\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.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+\n+\t\t\tassertEquals(3035, h.endOffset);\n+\t\t}\n \t}\n \n \tprivate Patch parseTestPatchFile() throws IOException {\n@@ -84,7 +142,7 @@ private Patch parseTestPatchFile() throws IOException {\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\treturn null; // Never happens\n \t\t}\n \t\ttry {\n \t\t\tfinal Patch p = new Patch();\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 f57a0ff..b49cb7b 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@@ -43,6 +43,10 @@\n import static org.spearce.jgit.util.RawParseUtils.nextLF;\n import static org.spearce.jgit.util.RawParseUtils.parseBase10;\n \n+import java.util.ArrayList;\n+import java.util.Collections;\n+import java.util.List;\n+\n import org.spearce.jgit.lib.AbbreviatedObjectId;\n import org.spearce.jgit.lib.Constants;\n import org.spearce.jgit.lib.FileMode;\n@@ -136,6 +140,9 @@\n \t/** ObjectId listed on the index line for the new (post-image) */\n \tprivate AbbreviatedObjectId newId;\n \n+\t/** The hunks of this file */\n+\tprivate List<HunkHeader> hunks;\n+\n \tFileHeader(final byte[] b, final int offset) {\n \t\tbuf = b;\n \t\tstartOffset = offset;\n@@ -222,6 +229,21 @@ public AbbreviatedObjectId getNewId() {\n \t\treturn newId;\n \t}\n \n+\t/** @return hunks altering this file; in order of appearance in patch */\n+\tpublic List<HunkHeader> getHunks() {\n+\t\tif (hunks == null)\n+\t\t\treturn Collections.emptyList();\n+\t\treturn hunks;\n+\t}\n+\n+\tvoid addHunk(final HunkHeader h) {\n+\t\tif (h.getFileHeader() != this)\n+\t\t\tthrow new IllegalArgumentException(\"Hunk belongs to another file\");\n+\t\tif (hunks == null)\n+\t\t\thunks = new ArrayList<HunkHeader>();\n+\t\thunks.add(h);\n+\t}\n+\n \t/**\n \t * Parse a \"diff --git\" or \"diff --cc\" line.\n \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\nnew file mode 100644\nindex 0000000..fc606c3\n--- /dev/null\n+++ b/org.spearce.jgit/src/org/spearce/jgit/patch/HunkHeader.java\n@@ -0,0 +1,185 @@\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.match;\n+import static org.spearce.jgit.util.RawParseUtils.nextLF;\n+import static org.spearce.jgit.util.RawParseUtils.parseBase10;\n+\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+\tprivate final FileHeader file;\n+\n+\t/** Offset within {@link #file}.buf to the \"@@ -\" line. */\n+\tfinal int startOffset;\n+\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+\n+\t/** First line number in the post-image file where the hunk starts */\n+\tint newStartLine;\n+\n+\t/** Total number of post-image lines this hunk covers (context + inserted) */\n+\tint newLineCount;\n+\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\tfile = fh;\n+\t\tstartOffset = offset;\n+\t}\n+\n+\t/** @return header for the file this hunk applies to */\n+\tpublic 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}\n+\n+\t/** @return first line number in the post-image file where the hunk starts */\n+\tpublic int getNewStartLine() {\n+\t\treturn newStartLine;\n+\t}\n+\n+\t/** @return Total number of post-image lines this hunk covers */\n+\tpublic int getNewLineCount() {\n+\t\treturn newLineCount;\n+\t}\n+\n+\t/** @return total number of lines of context appearing in this hunk */\n+\tpublic 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() {\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\toldLineCount = parseBase10(buf, ptr.value + 1, ptr);\n+\n+\t\tnewStartLine = parseBase10(buf, ptr.value + 1, ptr);\n+\t\tnewLineCount = parseBase10(buf, ptr.value + 1, ptr);\n+\t}\n+\n+\tint parseBody() {\n+\t\tfinal byte[] buf = file.buf;\n+\t\tfinal int sz = buf.length;\n+\t\tint c = nextLF(buf, startOffset), last = c;\n+\n+\t\tnDeleted = 0;\n+\t\tnAdded = 0;\n+\n+\t\tSCAN: for (; c < sz; last = c, c = nextLF(buf, c)) {\n+\t\t\tswitch (buf[c]) {\n+\t\t\tcase ' ':\n+\t\t\tcase '\\n':\n+\t\t\t\tnContext++;\n+\t\t\t\tcontinue;\n+\n+\t\t\tcase '-':\n+\t\t\t\tnDeleted++;\n+\t\t\t\tcontinue;\n+\n+\t\t\tcase '+':\n+\t\t\t\tnAdded++;\n+\t\t\t\tcontinue;\n+\n+\t\t\tcase '\\\\': // Matches \"\\ No newline at end of file\"\n+\t\t\t\tcontinue;\n+\n+\t\t\tdefault:\n+\t\t\t\tbreak SCAN;\n+\t\t\t}\n+\t\t}\n+\n+\t\tif (last < sz && nContext + nDeleted - 1 == oldLineCount\n+\t\t\t\t&& nContext + 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\treturn last;\n+\t\t}\n+\n+\t\tif (nContext + nDeleted != oldLineCount\n+\t\t\t\t|| nContext + nAdded != newLineCount) {\n+\t\t\t// TODO report on truncated hunk\n+\t\t}\n+\n+\t\treturn c;\n+\t}\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 30d12a5..165058d 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@@ -57,6 +57,8 @@\n \n \tprivate static final byte[] DIFF_CC = encodeASCII(\"diff --cc \");\n \n+\tstatic final byte[] SIG_FOOTER = encodeASCII(\"-- \\n\");\n+\n \t/** The files, in the order they were parsed out of the input. */\n \tprivate final List<FileHeader> files;\n \n@@ -131,6 +133,17 @@ public void parse(final byte[] buf, int ptr, final int end) {\n \tprivate int parseFile(final byte[] buf, int c) {\n \t\tfinal int sz = buf.length;\n \t\twhile (c < sz) {\n+\t\t\tif (match(buf, c, HUNK_HDR) >= 0) {\n+\t\t\t\t// If we find a disconnected hunk header we might\n+\t\t\t\t// have missed a file header previously. The hunk\n+\t\t\t\t// isn't valid without knowing where it comes from.\n+\t\t\t\t//\n+\n+\t\t\t\t// TODO handle a disconnected hunk fragment\n+\t\t\t\tc = nextLF(buf, c);\n+\t\t\t\tcontinue;\n+\t\t\t}\n+\n \t\t\t// Valid git style patch?\n \t\t\t//\n \t\t\tif (match(buf, c, DIFF_GIT) >= 0)\n@@ -180,7 +193,7 @@ private int parseDiffGit(final byte[] buf, final int startOffset) {\n \t\t\treturn skipFile(buf, startOffset);\n \n \t\tptr = fh.parseGitHeaders(ptr);\n-\t\t// TODO parse hunks\n+\t\tptr = parseHunks(fh, buf, ptr);\n \t\tfh.endOffset = ptr;\n \t\taddFile(fh);\n \t\treturn ptr;\n@@ -202,7 +215,7 @@ private int parseDiffCC(final byte[] buf, final int startOffset) {\n \tprivate int parseTraditionalPatch(final byte[] buf, final int startOffset) {\n \t\tfinal FileHeader fh = new FileHeader(buf, startOffset);\n \t\tint ptr = fh.parseTraditionalHeaders(startOffset);\n-\t\t// TODO parse hunks\n+\t\tptr = parseHunks(fh, buf, ptr);\n \t\tfh.endOffset = ptr;\n \t\taddFile(fh);\n \t\treturn ptr;\n@@ -214,4 +227,41 @@ private static int skipFile(final byte[] buf, int ptr) {\n \t\t\tptr = nextLF(buf, ptr);\n \t\treturn ptr;\n \t}\n+\n+\tprivate int parseHunks(final FileHeader fh, final byte[] buf, int c) {\n+\t\tfinal int sz = buf.length;\n+\t\twhile (c < sz) {\n+\t\t\t// If we see a file header at this point, we have all of the\n+\t\t\t// hunks for our current file. We should stop and report back\n+\t\t\t// with this position so it can be parsed again later.\n+\t\t\t//\n+\t\t\tif (match(buf, c, DIFF_GIT) >= 0)\n+\t\t\t\treturn c;\n+\t\t\tif (match(buf, c, DIFF_CC) >= 0)\n+\t\t\t\treturn c;\n+\t\t\tif (match(buf, c, OLD_NAME) >= 0)\n+\t\t\t\treturn c;\n+\t\t\tif (match(buf, c, NEW_NAME) >= 0)\n+\t\t\t\treturn c;\n+\n+\t\t\tif (match(buf, c, HUNK_HDR) >= 0) {\n+\t\t\t\tfinal HunkHeader h = new HunkHeader(fh, c);\n+\t\t\t\th.parseHeader();\n+\t\t\t\tc = h.parseBody();\n+\t\t\t\th.endOffset = c;\n+\t\t\t\tfh.addHunk(h);\n+\t\t\t\tif (c < sz && buf[c] != '@' && buf[c] != 'd'\n+\t\t\t\t\t\t&& match(buf, c, SIG_FOOTER) < 0) {\n+\t\t\t\t\t// TODO report on noise between hunks, might be an error\n+\t\t\t\t}\n+\t\t\t\tcontinue;\n+\t\t\t}\n+\n+\t\t\t// Skip this line and move to the next. Its probably garbage\n+\t\t\t// after the last hunk of a file.\n+\t\t\t//\n+\t\t\tc = nextLF(buf, c);\n+\t\t}\n+\t\treturn c;\n+\t}\n }\n-- \n1.6.1.rc2.299.gead4c\n"},{"id":"97598","messageId":"200812111640.34435.robin.rosenberg@dewire.com","threadId":"16678","inReplyTo":"1228971522-28764-3-git-send-email-spearce@spearce.org","subject":"Re: [JGIT PATCH 2/5] Add copy(InputStream) to TemporaryBuffer","fromName":"Robin Rosenberg","fromEmail":"robin.rosenberg@dewire.com","sentAt":"2008-12-11T15:40:34Z","receivedAt":"2008-12-11T15:40:34Z","isPatch":true,"sender":{"key":"robin.rosenberg@dewire.com","avatar":"https://avatars.githubusercontent.com/u/46357?v=4"},"body":"torsdag 11 december 2008 05:58:39 skrev Shawn O. Pearce:\n> In some places we may find it ourselves with an InputStream we\n> need to copy into a TemporaryBuffer, so we can flatten out the\n> entire stream to a single byte[].  Putting the copy loop here\n> is more useful then duplicating it in application level code.\n> +\tpublic void copy(final InputStream in) throws IOException {\n> +\t\tfinal byte[] b = new byte[2048];\nWhy not 8192 here too?\n\n-- robin\n"},{"id":"97600","messageId":"20081211155218.GF32487@spearce.org","threadId":"16678","inReplyTo":"200812111640.34435.robin.rosenberg@dewire.com","subject":"Re: [JGIT PATCH 2/5] Add copy(InputStream) to TemporaryBuffer","fromName":"Shawn O. Pearce","fromEmail":"spearce@spearce.org","sentAt":"2008-12-11T15:52:18Z","receivedAt":"2008-12-11T15:52:18Z","isPatch":true,"sender":{"key":"spearce@spearce.org","avatar":"https://avatars.githubusercontent.com/u/34844?v=4"},"body":"Robin Rosenberg <robin.rosenberg@dewire.com> wrote:\n> torsdag 11 december 2008 05:58:39 skrev Shawn O. Pearce:\n> > In some places we may find it ourselves with an InputStream we\n> > need to copy into a TemporaryBuffer, so we can flatten out the\n> > entire stream to a single byte[].  Putting the copy loop here\n> > is more useful then duplicating it in application level code.\n> > +\tpublic void copy(final InputStream in) throws IOException {\n> > +\t\tfinal byte[] b = new byte[2048];\n>\n> Why not 8192 here too?\n\nBlargh, you're right.  Actually what I should do is look to see\nif blocks != null, in which case I should alloc a block and read\ndirectly into it.  That would avoid one copy of the data.\n\n-- \nShawn.\n"},{"id":"97603","messageId":"20081211165345.GG32487@spearce.org","threadId":"16678","inReplyTo":"20081211155218.GF32487@spearce.org","subject":"[JGIT PATCH 2/5 v2] Add copy(InputStream) to TemporaryBuffer","fromName":"Shawn O. Pearce","fromEmail":"spearce@spearce.org","sentAt":"2008-12-11T16:53:45Z","receivedAt":"2008-12-11T16:53:45Z","isPatch":true,"sender":{"key":"spearce@spearce.org","avatar":"https://avatars.githubusercontent.com/u/34844?v=4"},"body":"In some places we may find ourselves with an InputStream we\nneed to copy into a TemporaryBuffer, so we can flatten out the\nentire stream to a single byte[].  Putting the copy loop here\nis more useful then duplicating it in application level code.\n\nSigned-off-by: Shawn O. Pearce <spearce@spearce.org>\n---\n  \"Shawn O. Pearce\" <spearce@spearce.org> wrote:\n  > Robin Rosenberg <robin.rosenberg@dewire.com> wrote:\n  > > torsdag 11 december 2008 05:58:39 skrev Shawn O. Pearce:\n  > > > +\t\tfinal byte[] b = new byte[2048];\n  > >\n  > > Why not 8192 here too?\n  > \n  > Blargh, you're right.  Actually what I should do is look to see\n  > if blocks != null, in which case I should alloc a block and read\n  > directly into it.  That would avoid one copy of the data.\n\n  And now we do that...\n  \ndiff --git a/org.spearce.jgit/src/org/spearce/jgit/util/TemporaryBuffer.java b/org.spearce.jgit/src/org/spearce/jgit/util/TemporaryBuffer.java\nindex b1ffd6e..761f359 100644\n--- a/org.spearce.jgit/src/org/spearce/jgit/util/TemporaryBuffer.java\n+++ b/org.spearce.jgit/src/org/spearce/jgit/util/TemporaryBuffer.java\n@@ -42,6 +42,7 @@\n import java.io.FileInputStream;\n import java.io.FileOutputStream;\n import java.io.IOException;\n+import java.io.InputStream;\n import java.io.OutputStream;\n import java.util.ArrayList;\n \n@@ -135,6 +136,39 @@ public void write(final byte[] b, int off, int len) throws IOException {\n \t\t\tdiskOut.write(b, off, len);\n \t}\n \n+\t/**\n+\t * Copy all bytes remaining on the input stream into this buffer.\n+\t * \n+\t * @param in\n+\t *            the stream to read from, until EOF is reached.\n+\t * @throws IOException\n+\t *             an error occurred reading from the input stream, or while\n+\t *             writing to a local temporary file.\n+\t */\n+\tpublic void copy(final InputStream in) throws IOException {\n+\t\tif (blocks != null) {\n+\t\t\tfor (;;) {\n+\t\t\t\tBlock s = last();\n+\t\t\t\tif (s.isFull()) {\n+\t\t\t\t\tif (reachedInCoreLimit())\n+\t\t\t\t\t\tbreak;\n+\t\t\t\t\ts = new Block();\n+\t\t\t\t\tblocks.add(s);\n+\t\t\t\t}\n+\n+\t\t\t\tfinal int n = in.read(s.buffer, s.count, Block.SZ - s.count);\n+\t\t\t\tif (n < 1)\n+\t\t\t\t\treturn;\n+\t\t\t\ts.count += n;\n+\t\t\t}\n+\t\t}\n+\n+\t\tfinal byte[] tmp = new byte[Block.SZ];\n+\t\tint n;\n+\t\twhile ((n = in.read(tmp)) > 0)\n+\t\t\tdiskOut.write(tmp, 0, n);\n+\t}\n+\n \tprivate Block last() {\n \t\treturn blocks.get(blocks.size() - 1);\n \t}\n-- \n1.6.1.rc2.306.ge5d5e\n\n\n-- \nShawn.\n"},{"id":"97609","messageId":"200812111934.13218.robin.rosenberg.lists@dewire.com","threadId":"16678","inReplyTo":"1228971522-28764-5-git-send-email-spearce@spearce.org","subject":"Re: [JGIT PATCH 4/5] Define Patch to parse a sequence of patch FileHeaders","fromName":"Robin Rosenberg","fromEmail":"robin.rosenberg.lists@dewire.com","sentAt":"2008-12-11T18:34:12Z","receivedAt":"2008-12-11T18:34:12Z","isPatch":true,"sender":{"key":"robin.rosenberg@dewire.com","avatar":"https://avatars.githubusercontent.com/u/46357?v=4"},"body":"torsdag 11 december 2008 05:58:41 skrev Shawn O. Pearce:\n> Most patch scripts impact more than one file at a time, so we need\n> to support parsing multiple FileHeaders from the same input stream\n> and collect them into a larger entity representing the entire script.\n...\n> +\tpublic void testParse_ConfigCaseInsensitive() throws IOException {\n> +\t\tfinal Patch p = parseTestPatchFile();\n> +\t\tassertEquals(2, p.getFiles().size());\n> +\n> +\t\tfinal FileHeader fRepositoryConfigTest = p.getFiles().get(0);\n> +\t\tfinal FileHeader fRepositoryConfig = p.getFiles().get(1);\n> +\n> +\t\tassertEquals(\n> +\t\t\t\t\"org.spearce.jgit.test/tst/org/spearce/jgit/lib/RepositoryConfigTest.java\",\n> +\t\t\t\tfRepositoryConfigTest.getNewName());\n> +\n> +\t\tassertEquals(\n> +\t\t\t\t\"org.spearce.jgit/src/org/spearce/jgit/lib/RepositoryConfig.java\",\n> +\t\t\t\tfRepositoryConfig.getNewName());\n> +\n> +\t\tassertEquals(572, fRepositoryConfigTest.startOffset);\n> +\t\tassertEquals(1490, fRepositoryConfig.startOffset);\n1487 here\n\n-- robin\n"},{"id":"97610","messageId":"20081211183954.GH32487@spearce.org","threadId":"16678","inReplyTo":"200812111934.13218.robin.rosenberg.lists@dewire.com","subject":"Re: [JGIT PATCH 4/5] Define Patch to parse a sequence of patch FileHeaders","fromName":"Shawn O. Pearce","fromEmail":"spearce@spearce.org","sentAt":"2008-12-11T18:39:54Z","receivedAt":"2008-12-11T18:39:54Z","isPatch":true,"sender":{"key":"spearce@spearce.org","avatar":"https://avatars.githubusercontent.com/u/34844?v=4"},"body":"Robin Rosenberg <robin.rosenberg.lists@dewire.com> wrote:\n> > +\t\tassertEquals(572, fRepositoryConfigTest.startOffset);\n> > +\t\tassertEquals(1490, fRepositoryConfig.startOffset);\n>\n> 1487 here\n\nReally?  1490 is the only value that the test vector passes with.\nWhat's the 3 bytes you think I'm off by?\n\n-- \nShawn.\n"},{"id":"97613","messageId":"200812112123.49659.robin.rosenberg.lists@dewire.com","threadId":"16678","inReplyTo":"20081211183954.GH32487@spearce.org","subject":"Re: [JGIT PATCH 4/5] Define Patch to parse a sequence of patch FileHeaders","fromName":"Robin Rosenberg","fromEmail":"robin.rosenberg.lists@dewire.com","sentAt":"2008-12-11T20:23:49Z","receivedAt":"2008-12-11T20:23:49Z","isPatch":true,"sender":{"key":"robin.rosenberg@dewire.com","avatar":"https://avatars.githubusercontent.com/u/46357?v=4"},"body":"torsdag 11 december 2008 19:39:54 skrev Shawn O. Pearce:\n> Robin Rosenberg <robin.rosenberg.lists@dewire.com> wrote:\n> > > +\t\tassertEquals(572, fRepositoryConfigTest.startOffset);\n> > > +\t\tassertEquals(1490, fRepositoryConfig.startOffset);\n> >\n> > 1487 here\n> \n> Really?  1490 is the only value that the test vector passes with.\n> What's the 3 bytes you think I'm off by?\n\nAh, --whitespace=fix did that.\n\n-- robin\n"},{"id":"97615","messageId":"20081211202701.GI32487@spearce.org","threadId":"16678","inReplyTo":"200812112123.49659.robin.rosenberg.lists@dewire.com","subject":"Re: [JGIT PATCH 4/5] Define Patch to parse a sequence of patch FileHeaders","fromName":"Shawn O. Pearce","fromEmail":"spearce@spearce.org","sentAt":"2008-12-11T20:27:01Z","receivedAt":"2008-12-11T20:27:01Z","isPatch":true,"sender":{"key":"spearce@spearce.org","avatar":"https://avatars.githubusercontent.com/u/34844?v=4"},"body":"Robin Rosenberg <robin.rosenberg.lists@dewire.com> wrote:\n> torsdag 11 december 2008 19:39:54 skrev Shawn O. Pearce:\n> > Robin Rosenberg <robin.rosenberg.lists@dewire.com> wrote:\n> > > > +\t\tassertEquals(572, fRepositoryConfigTest.startOffset);\n> > > > +\t\tassertEquals(1490, fRepositoryConfig.startOffset);\n> > >\n> > > 1487 here\n> > \n> > Really?  1490 is the only value that the test vector passes with.\n> > What's the 3 bytes you think I'm off by?\n> \n> Ah, --whitespace=fix did that.\n\nOk.  I know you like to apply with --whitespace=fix, but I would\nprefer to leave these *.patch test input files[*1*] exactly as they\nwere created by git format-patch or git diff, so I'm sure we are\nparsing the same thing git would have produced and sent to us.\n\n\n*1*: I have more patches coming which add 2 more test inputs\n     to the same PatchTest suite.\n\n-- \nShawn.\n"},{"id":"97621","messageId":"200812112139.29875.robin.rosenberg.lists@dewire.com","threadId":"16678","inReplyTo":"20081211183954.GH32487@spearce.org","subject":"Re: [JGIT PATCH 4/5] Define Patch to parse a sequence of patch FileHeaders","fromName":"Robin Rosenberg","fromEmail":"robin.rosenberg.lists@dewire.com","sentAt":"2008-12-11T20:39:29Z","receivedAt":"2008-12-11T20:39:29Z","isPatch":true,"sender":{"key":"robin.rosenberg@dewire.com","avatar":"https://avatars.githubusercontent.com/u/46357?v=4"},"body":"torsdag 11 december 2008 19:39:54 skrev Shawn O. Pearce:\n> Robin Rosenberg <robin.rosenberg.lists@dewire.com> wrote:\n> > > +\t\tassertEquals(572, fRepositoryConfigTest.startOffset);\n> > > +\t\tassertEquals(1490, fRepositoryConfig.startOffset);\n> >\n> > 1487 here\n> \n> Really?  1490 is the only value that the test vector passes with.\n> What's the 3 bytes you think I'm off by?\n\n...Quick, quick, find something else to pick on..  :->\n\nYes. Very little of the code in TemporaryBuffer is covered by the unit tests\nand number of conditionals in there are rather large. I tried messing with\nthe constants in there to improve that and then PatchTest started to fail.\n\nHere are the changes I tried with. I think it should still work with thes\nchanges. Rather than changing the other tests, we might want to create\na special test for only the buffer class.\n\n-- robin\n\ndiff --git a/org.spearce.jgit/src/org/spearce/jgit/util/TemporaryBuffer.java b/org.spearce.jgit/src/org/spearce/jgit/util/TemporaryBuffer.java\nindex 27f6444..556ab71 100644\n--- a/org.spearce.jgit/src/org/spearce/jgit/util/TemporaryBuffer.java\n+++ b/org.spearce.jgit/src/org/spearce/jgit/util/TemporaryBuffer.java\n@@ -60,7 +60,7 @@\n  * after this stream has been properly closed by {@link #close()}.\n  */\n public class TemporaryBuffer extends OutputStream {\n-       private static final int DEFAULT_IN_CORE_LIMIT = 1024 * 1024;\n+       private static final int DEFAULT_IN_CORE_LIMIT = 1024;\n\n        /** Chain of data, if we are still completely in-core; otherwise null. */\n        private ArrayList<Block> blocks;\n@@ -315,7 +315,7 @@ public void destroy() {\n        }\n\n        private static class Block {\n-               static final int SZ = 8 * 1024;\n+               static final int SZ = 512;\n\n                final byte[] buffer = new byte[SZ];\n"},{"id":"97622","messageId":"20081211204137.GJ32487@spearce.org","threadId":"16678","inReplyTo":"200812112139.29875.robin.rosenberg.lists@dewire.com","subject":"Re: [JGIT PATCH 4/5] Define Patch to parse a sequence of patch FileHeaders","fromName":"Shawn O. Pearce","fromEmail":"spearce@spearce.org","sentAt":"2008-12-11T20:41:37Z","receivedAt":"2008-12-11T20:41:37Z","isPatch":true,"sender":{"key":"spearce@spearce.org","avatar":"https://avatars.githubusercontent.com/u/34844?v=4"},"body":"Robin Rosenberg <robin.rosenberg.lists@dewire.com> wrote:\n> ...Quick, quick, find something else to pick on..  :->\n\nHeh, you'll get more patches again from me today, so there'll still\nbe more to pick on. :)\n \n> Yes. Very little of the code in TemporaryBuffer is covered by the unit tests\n> and number of conditionals in there are rather large. I tried messing with\n> the constants in there to improve that and then PatchTest started to fail.\n> \n> Here are the changes I tried with. I think it should still work with thes\n> changes. Rather than changing the other tests, we might want to create\n> a special test for only the buffer class.\n\nOk.  I was thinking the same thing actually, that I should spend a\nbit of time today and try to get coverage on TemporaryBuffer.  I'll\nwrite a unit test for it.\n\n-- \nShawn.\n"}]}