{"thread":{"id":"17406","subject":"[JGIT PATCH] Add getTaggerIdent, getShortMessage, getFullMessage to RevTag","startedAt":"2009-01-28T04:35:16Z","lastAt":"2009-01-31T00:56:01Z","messageCount":3,"participants":["Shawn O. Pearce","Robin Rosenberg"],"isPatch":true,"patchVersion":1,"patchTotal":null},"messages":[{"id":"102247","messageId":"1233117316-3291-1-git-send-email-spearce@spearce.org","threadId":"17406","inReplyTo":null,"subject":"[JGIT PATCH] Add getTaggerIdent, getShortMessage, getFullMessage to RevTag","fromName":"Shawn O. Pearce","fromEmail":"spearce@spearce.org","sentAt":"2009-01-28T04:35:16Z","receivedAt":"2009-01-28T04:35:16Z","isPatch":true,"sender":{"key":"spearce@spearce.org","avatar":"https://avatars.githubusercontent.com/u/34844?v=4"},"body":"These methods make the RevTag API more like the RevCommit API, such\nthat it is more consistent for applications to access the \"fields\"\nof a tag object in the same way that they access the fields of any\ncommit object.\n\nSigned-off-by: Shawn O. Pearce <spearce@spearce.org>\n---\n\n Actually, this has also gone through Gerrit2.  If you want to see\n what the same patch looks like (just for fun):\n\n http://review.source.android.com/8681\n   or\n http://review.source.android.com/r/633476cf478da7c9375abf5fe\n\n .../src/org/spearce/jgit/revwalk/RevCommit.java    |    2 +-\n .../src/org/spearce/jgit/revwalk/RevTag.java       |   74 +++++++++++++++++++-\n .../src/org/spearce/jgit/util/RawParseUtils.java   |   47 ++++++++++++-\n 3 files changed, 119 insertions(+), 4 deletions(-)\n\ndiff --git a/org.spearce.jgit/src/org/spearce/jgit/revwalk/RevCommit.java b/org.spearce.jgit/src/org/spearce/jgit/revwalk/RevCommit.java\nindex 7454d8e..de11c39 100644\n--- a/org.spearce.jgit/src/org/spearce/jgit/revwalk/RevCommit.java\n+++ b/org.spearce.jgit/src/org/spearce/jgit/revwalk/RevCommit.java\n@@ -367,7 +367,7 @@ public final String getShortMessage() {\n \t\treturn str;\n \t}\n \n-\tprivate static boolean hasLF(final byte[] r, int b, final int e) {\n+\tstatic boolean hasLF(final byte[] r, int b, final int e) {\n \t\twhile (b < e)\n \t\t\tif (r[b++] == '\\n')\n \t\t\t\treturn true;\ndiff --git a/org.spearce.jgit/src/org/spearce/jgit/revwalk/RevTag.java b/org.spearce.jgit/src/org/spearce/jgit/revwalk/RevTag.java\nindex 77a55cd..82f0009 100644\n--- a/org.spearce.jgit/src/org/spearce/jgit/revwalk/RevTag.java\n+++ b/org.spearce.jgit/src/org/spearce/jgit/revwalk/RevTag.java\n@@ -38,6 +38,7 @@\n package org.spearce.jgit.revwalk;\n \n import java.io.IOException;\n+import java.nio.charset.Charset;\n \n import org.spearce.jgit.errors.CorruptObjectException;\n import org.spearce.jgit.errors.IncorrectObjectTypeException;\n@@ -45,6 +46,7 @@\n import org.spearce.jgit.lib.AnyObjectId;\n import org.spearce.jgit.lib.Constants;\n import org.spearce.jgit.lib.ObjectLoader;\n+import org.spearce.jgit.lib.PersonIdent;\n import org.spearce.jgit.lib.Tag;\n import org.spearce.jgit.util.MutableInteger;\n import org.spearce.jgit.util.RawParseUtils;\n@@ -100,7 +102,77 @@ void parseCanonical(final RevWalk walk, final byte[] rawTag)\n \tpublic int getType() {\n \t\treturn Constants.OBJ_TAG;\n \t}\n-\t\n+\n+\t/**\n+\t * Parse the tagger identity from the raw buffer.\n+\t * <p>\n+\t * This method parses and returns the content of the tagger line, after\n+\t * taking the tag's character set into account and decoding the tagger\n+\t * name and email address. This method is fairly expensive and produces a\n+\t * new PersonIdent instance on each invocation. Callers should invoke this\n+\t * method only if they are certain they will be outputting the result, and\n+\t * should cache the return value for as long as necessary to use all\n+\t * information from it.\n+\t * \n+\t * @return identity of the tagger (name, email) and the time the tag\n+\t *         was made by the tagger; null if no tagger line was found.\n+\t */\n+\tpublic final PersonIdent getTaggerIdent() {\n+\t\tfinal byte[] raw = buffer;\n+\t\tfinal int nameB = RawParseUtils.tagger(raw, 0);\n+\t\tif (nameB < 0)\n+\t\t\treturn null;\n+\t\treturn RawParseUtils.parsePersonIdent(raw, nameB);\n+\t}\n+\n+\t/**\n+\t * Parse the complete tag message and decode it to a string.\n+\t * <p>\n+\t * This method parses and returns the message portion of the tag buffer,\n+\t * after taking the tag's character set into account and decoding the buffer\n+\t * using that character set. This method is a fairly expensive operation and\n+\t * produces a new string on each invocation.\n+\t * \n+\t * @return decoded tag message as a string. Never null.\n+\t */\n+\tpublic final String getFullMessage() {\n+\t\tfinal byte[] raw = buffer;\n+\t\tfinal int msgB = RawParseUtils.tagMessage(raw, 0);\n+\t\tif (msgB < 0)\n+\t\t\treturn \"\";\n+\t\tfinal Charset enc = RawParseUtils.parseEncoding(raw);\n+\t\treturn RawParseUtils.decode(enc, raw, msgB, raw.length);\n+\t}\n+\n+\t/**\n+\t * Parse the tag message and return the first \"line\" of it.\n+\t * <p>\n+\t * The first line is everything up to the first pair of LFs. This is the\n+\t * \"oneline\" format, suitable for output in a single line display.\n+\t * <p>\n+\t * This method parses and returns the message portion of the tag buffer,\n+\t * after taking the tag's character set into account and decoding the buffer\n+\t * using that character set. This method is a fairly expensive operation and\n+\t * produces a new string on each invocation.\n+\t * \n+\t * @return decoded tag message as a string. Never null. The returned string\n+\t *         does not contain any LFs, even if the first paragraph spanned\n+\t *         multiple lines. Embedded LFs are converted to spaces.\n+\t */\n+\tpublic final String getShortMessage() {\n+\t\tfinal byte[] raw = buffer;\n+\t\tfinal int msgB = RawParseUtils.tagMessage(raw, 0);\n+\t\tif (msgB < 0)\n+\t\t\treturn \"\";\n+\n+\t\tfinal Charset enc = RawParseUtils.parseEncoding(raw);\n+\t\tfinal int msgE = RawParseUtils.endOfParagraph(raw, msgB);\n+\t\tString str = RawParseUtils.decode(enc, raw, msgB, msgE);\n+\t\tif (RevCommit.hasLF(raw, msgB, msgE))\n+\t\t\tstr = str.replace('\\n', ' ');\n+\t\treturn str;\n+\t}\n+\n \t/**\n \t * Parse this tag buffer for display.\n \t * \ndiff --git a/org.spearce.jgit/src/org/spearce/jgit/util/RawParseUtils.java b/org.spearce.jgit/src/org/spearce/jgit/util/RawParseUtils.java\nindex 758e7af..646a654 100644\n--- a/org.spearce.jgit/src/org/spearce/jgit/util/RawParseUtils.java\n+++ b/org.spearce.jgit/src/org/spearce/jgit/util/RawParseUtils.java\n@@ -40,6 +40,7 @@\n import static org.spearce.jgit.lib.ObjectChecker.author;\n import static org.spearce.jgit.lib.ObjectChecker.committer;\n import static org.spearce.jgit.lib.ObjectChecker.encoding;\n+import static org.spearce.jgit.lib.ObjectChecker.tagger;\n \n import java.nio.ByteBuffer;\n import java.nio.charset.CharacterCodingException;\n@@ -397,6 +398,32 @@ public static final int committer(final byte[] b, int ptr) {\n \t}\n \n \t/**\n+\t * Locate the \"tagger \" header line data.\n+\t * \n+\t * @param b\n+\t *            buffer to scan.\n+\t * @param ptr\n+\t *            position in buffer to start the scan at. Most callers should\n+\t *            pass 0 to ensure the scan starts from the beginning of the tag\n+\t *            buffer and does not accidentally look at message body.\n+\t * @return position just after the space in \"tagger \", so the first\n+\t *         character of the tagger's name. If no tagger header can be\n+\t *         located -1 is returned.\n+\t */\n+\tpublic static final int tagger(final byte[] b, int ptr) {\n+\t\tfinal int sz = b.length;\n+\t\twhile (ptr < sz) {\n+\t\t\tif (b[ptr] == '\\n')\n+\t\t\t\treturn -1;\n+\t\t\tfinal int m = match(b, ptr, tagger);\n+\t\t\tif (m >= 0)\n+\t\t\t\treturn m;\n+\t\t\tptr = nextLF(b, ptr);\n+\t\t}\n+\t\treturn -1;\n+\t}\n+\n+\t/**\n \t * Locate the \"encoding \" header line.\n \t * \n \t * @param b\n@@ -648,9 +675,25 @@ public static final int commitMessage(final byte[] b, int ptr) {\n \t\twhile (ptr < sz && b[ptr] == 'p')\n \t\t\tptr += 48; // skip this parent.\n \n-\t\t// skip any remaining header lines, ignoring what their actual\n-\t\t// header line type is.\n+\t\t// Skip any remaining header lines, ignoring what their actual\n+\t\t// header line type is. This is identical to the logic for a tag.\n \t\t//\n+\t\treturn tagMessage(b, ptr);\n+\t}\n+\n+\t/**\n+\t * Locate the position of the tag message body.\n+\t * \n+\t * @param b\n+\t *            buffer to scan.\n+\t * @param ptr\n+\t *            position in buffer to start the scan at. Most callers should\n+\t *            pass 0 to ensure the scan starts from the beginning of the tag\n+\t *            buffer.\n+\t * @return position of the user's message buffer.\n+\t */\n+\tpublic static final int tagMessage(final byte[] b, int ptr) {\n+\t\tfinal int sz = b.length;\n \t\twhile (ptr < sz && b[ptr] != '\\n')\n \t\t\tptr = nextLF(b, ptr);\n \t\tif (ptr < sz && b[ptr] == '\\n')\n-- \n1.6.1.1.374.g0d9d7\n"},{"id":"102340","messageId":"200901282110.48899.robin.rosenberg.lists@dewire.com","threadId":"17406","inReplyTo":"1233117316-3291-1-git-send-email-spearce@spearce.org","subject":"Re: [JGIT PATCH] Add getTaggerIdent, getShortMessage, getFullMessage to RevTag","fromName":"Robin Rosenberg","fromEmail":"robin.rosenberg.lists@dewire.com","sentAt":"2009-01-28T20:10:48Z","receivedAt":"2009-01-28T20:10:48Z","isPatch":true,"sender":{"key":"robin.rosenberg@dewire.com","avatar":"https://avatars.githubusercontent.com/u/46357?v=4"},"body":"onsdag 28 januari 2009 05:35:16 skrev Shawn O. Pearce:\n> These methods make the RevTag API more like the RevCommit API, such\n> that it is more consistent for applications to access the \"fields\"\n> of a tag object in the same way that they access the fields of any\n> commit object.\n> \n> Signed-off-by: Shawn O. Pearce <spearce@spearce.org>\n> ---\n> \n>  Actually, this has also gone through Gerrit2.  If you want to see\n>  what the same patch looks like (just for fun):\n\nThe reviewer is biased :) Where are the unit tests?\n\nI'm kind of surprised you did not optimized skipping past the object header,\nlike the parents are skipped when parsing commits.\n\n-- robin\n"},{"id":"102666","messageId":"20090131005601.GH26880@spearce.org","threadId":"17406","inReplyTo":"200901282110.48899.robin.rosenberg.lists@dewire.com","subject":"[PATCH v2] Add getTaggerIdent, getShortMessage, getFullMessage to RevTag","fromName":"Shawn O. Pearce","fromEmail":"spearce@spearce.org","sentAt":"2009-01-31T00:56:01Z","receivedAt":"2009-01-31T00:56:01Z","isPatch":true,"sender":{"key":"spearce@spearce.org","avatar":"https://avatars.githubusercontent.com/u/34844?v=4"},"body":"These methods make the RevTag API more like the RevCommit API, such\nthat it is more consistent for applications to access the \"fields\"\nof a tag object in the same way that they access the fields of any\ncommit object.\n\nSigned-off-by: Shawn O. Pearce <spearce@spearce.org>\n---\n  Robin Rosenberg <robin.rosenberg.lists@dewire.com> wrote:\n  > Where are the unit tests?\n\n  New test file included.\n\n  > I'm kind of surprised you did not optimized skipping past the object header,\n  > like the parents are skipped when parsing commits.\n\n  Also included.  Otherwise unmodified from v1.\n\n .../org/spearce/jgit/revwalk/RevTagParseTest.java  |  350 ++++++++++++++++++++\n .../src/org/spearce/jgit/revwalk/RevCommit.java    |    2 +-\n .../src/org/spearce/jgit/revwalk/RevTag.java       |   74 ++++-\n .../src/org/spearce/jgit/util/RawParseUtils.java   |   51 +++-\n 4 files changed, 473 insertions(+), 4 deletions(-)\n create mode 100644 org.spearce.jgit.test/tst/org/spearce/jgit/revwalk/RevTagParseTest.java\n\ndiff --git a/org.spearce.jgit.test/tst/org/spearce/jgit/revwalk/RevTagParseTest.java b/org.spearce.jgit.test/tst/org/spearce/jgit/revwalk/RevTagParseTest.java\nnew file mode 100644\nindex 0000000..82680f3\n--- /dev/null\n+++ b/org.spearce.jgit.test/tst/org/spearce/jgit/revwalk/RevTagParseTest.java\n@@ -0,0 +1,350 @@\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.revwalk;\n+\n+import java.io.ByteArrayOutputStream;\n+\n+import org.spearce.jgit.lib.Constants;\n+import org.spearce.jgit.lib.ObjectId;\n+import org.spearce.jgit.lib.PersonIdent;\n+import org.spearce.jgit.lib.RepositoryTestCase;\n+\n+public class RevTagParseTest extends RepositoryTestCase {\n+\tpublic void testTagBlob() throws Exception {\n+\t\ttestOneType(Constants.OBJ_BLOB);\n+\t}\n+\n+\tpublic void testTagTree() throws Exception {\n+\t\ttestOneType(Constants.OBJ_TREE);\n+\t}\n+\n+\tpublic void testTagCommit() throws Exception {\n+\t\ttestOneType(Constants.OBJ_COMMIT);\n+\t}\n+\n+\tpublic void testTagTag() throws Exception {\n+\t\ttestOneType(Constants.OBJ_TAG);\n+\t}\n+\n+\tprivate void testOneType(final int typeCode) throws Exception {\n+\t\tfinal ObjectId id = id(\"9788669ad918b6fcce64af8882fc9a81cb6aba67\");\n+\t\tfinal StringBuilder b = new StringBuilder();\n+\t\tb.append(\"object \" + id.name() + \"\\n\");\n+\t\tb.append(\"type \" + Constants.typeString(typeCode) + \"\\n\");\n+\t\tb.append(\"tag v1.2.3.4.5\\n\");\n+\t\tb.append(\"tagger A U. Thor <a_u_thor@example.com> 1218123387 +0700\\n\");\n+\t\tb.append(\"\\n\");\n+\n+\t\tfinal RevWalk rw = new RevWalk(db);\n+\t\tfinal RevTag c;\n+\n+\t\tc = new RevTag(id(\"9473095c4cb2f12aefe1db8a355fe3fafba42f67\"));\n+\t\tassertNull(c.getObject());\n+\t\tassertNull(c.getName());\n+\n+\t\tc.parseCanonical(rw, b.toString().getBytes(\"UTF-8\"));\n+\t\tassertNotNull(c.getObject());\n+\t\tassertEquals(id, c.getObject().getId());\n+\t\tassertSame(rw.lookupAny(id, typeCode), c.getObject());\n+\t}\n+\n+\tpublic void testParseAllFields() throws Exception {\n+\t\tfinal ObjectId treeId = id(\"9788669ad918b6fcce64af8882fc9a81cb6aba67\");\n+\t\tfinal String name = \"v1.2.3.4.5\";\n+\t\tfinal String taggerName = \"A U. Thor\";\n+\t\tfinal String taggerEmail = \"a_u_thor@example.com\";\n+\t\tfinal int taggerTime = 1218123387;\n+\n+\t\tfinal StringBuilder body = new StringBuilder();\n+\n+\t\tbody.append(\"object \");\n+\t\tbody.append(treeId.name());\n+\t\tbody.append(\"\\n\");\n+\n+\t\tbody.append(\"type tree\\n\");\n+\n+\t\tbody.append(\"tag \");\n+\t\tbody.append(name);\n+\t\tbody.append(\"\\n\");\n+\n+\t\tbody.append(\"tagger \");\n+\t\tbody.append(taggerName);\n+\t\tbody.append(\" <\");\n+\t\tbody.append(taggerEmail);\n+\t\tbody.append(\"> \");\n+\t\tbody.append(taggerTime);\n+\t\tbody.append(\" +0700\\n\");\n+\n+\t\tbody.append(\"\\n\");\n+\n+\t\tfinal RevWalk rw = new RevWalk(db);\n+\t\tfinal RevTag c;\n+\n+\t\tc = new RevTag(id(\"9473095c4cb2f12aefe1db8a355fe3fafba42f67\"));\n+\t\tassertNull(c.getObject());\n+\t\tassertNull(c.getName());\n+\n+\t\tc.parseCanonical(rw, body.toString().getBytes(\"UTF-8\"));\n+\t\tassertNotNull(c.getObject());\n+\t\tassertEquals(treeId, c.getObject().getId());\n+\t\tassertSame(rw.lookupTree(treeId), c.getObject());\n+\n+\t\tassertNotNull(c.getName());\n+\t\tassertEquals(name, c.getName());\n+\t\tassertEquals(\"\", c.getFullMessage());\n+\n+\t\tfinal PersonIdent cTagger = c.getTaggerIdent();\n+\t\tassertNotNull(cTagger);\n+\t\tassertEquals(taggerName, cTagger.getName());\n+\t\tassertEquals(taggerEmail, cTagger.getEmailAddress());\n+\t}\n+\n+\tprivate RevTag create(final String msg) throws Exception {\n+\t\tfinal StringBuilder b = new StringBuilder();\n+\t\tb.append(\"object 9788669ad918b6fcce64af8882fc9a81cb6aba67\\n\");\n+\t\tb.append(\"type tree\\n\");\n+\t\tb.append(\"tag v1.2.3.4.5\\n\");\n+\t\tb.append(\"tagger A U. Thor <a_u_thor@example.com> 1218123387 +0700\\n\");\n+\t\tb.append(\"\\n\");\n+\t\tb.append(msg);\n+\n+\t\tfinal RevTag c;\n+\t\tc = new RevTag(id(\"9473095c4cb2f12aefe1db8a355fe3fafba42f67\"));\n+\t\tc.parseCanonical(new RevWalk(db), b.toString().getBytes(\"UTF-8\"));\n+\t\treturn c;\n+\t}\n+\n+\tpublic void testParse_implicit_UTF8_encoded() throws Exception {\n+\t\tfinal ByteArrayOutputStream b = new ByteArrayOutputStream();\n+\t\tb.write(\"object 9788669ad918b6fcce64af8882fc9a81cb6aba67\\n\"\n+\t\t\t\t.getBytes(\"UTF-8\"));\n+\t\tb.write(\"type tree\\n\".getBytes(\"UTF-8\"));\n+\t\tb.write(\"tag v1.2.3.4.5\\n\".getBytes(\"UTF-8\"));\n+\n+\t\tb\n+\t\t\t\t.write(\"tagger F\\u00f6r fattare <a_u_thor@example.com> 1218123387 +0700\\n\"\n+\t\t\t\t\t\t.getBytes(\"UTF-8\"));\n+\t\tb.write(\"\\n\".getBytes(\"UTF-8\"));\n+\t\tb.write(\"Sm\\u00f6rg\\u00e5sbord\\n\".getBytes(\"UTF-8\"));\n+\t\tb.write(\"\\n\".getBytes(\"UTF-8\"));\n+\t\tb.write(\"\\u304d\\u308c\\u3044\\n\".getBytes(\"UTF-8\"));\n+\t\tfinal RevTag c;\n+\t\tc = new RevTag(id(\"9473095c4cb2f12aefe1db8a355fe3fafba42f67\"));\n+\t\tc.parseCanonical(new RevWalk(db), b.toByteArray());\n+\n+\t\tassertEquals(\"F\\u00f6r fattare\", c.getTaggerIdent().getName());\n+\t\tassertEquals(\"Sm\\u00f6rg\\u00e5sbord\", c.getShortMessage());\n+\t\tassertEquals(\"Sm\\u00f6rg\\u00e5sbord\\n\\n\\u304d\\u308c\\u3044\\n\", c\n+\t\t\t\t.getFullMessage());\n+\t}\n+\n+\tpublic void testParse_implicit_mixed_encoded() throws Exception {\n+\t\tfinal ByteArrayOutputStream b = new ByteArrayOutputStream();\n+\t\tb.write(\"object 9788669ad918b6fcce64af8882fc9a81cb6aba67\\n\"\n+\t\t\t\t.getBytes(\"UTF-8\"));\n+\t\tb.write(\"type tree\\n\".getBytes(\"UTF-8\"));\n+\t\tb.write(\"tag v1.2.3.4.5\\n\".getBytes(\"UTF-8\"));\n+\t\tb\n+\t\t\t\t.write(\"tagger F\\u00f6r fattare <a_u_thor@example.com> 1218123387 +0700\\n\"\n+\t\t\t\t\t\t.getBytes(\"ISO-8859-1\"));\n+\t\tb.write(\"\\n\".getBytes(\"UTF-8\"));\n+\t\tb.write(\"Sm\\u00f6rg\\u00e5sbord\\n\".getBytes(\"UTF-8\"));\n+\t\tb.write(\"\\n\".getBytes(\"UTF-8\"));\n+\t\tb.write(\"\\u304d\\u308c\\u3044\\n\".getBytes(\"UTF-8\"));\n+\t\tfinal RevTag c;\n+\t\tc = new RevTag(id(\"9473095c4cb2f12aefe1db8a355fe3fafba42f67\"));\n+\t\tc.parseCanonical(new RevWalk(db), b.toByteArray());\n+\n+\t\tassertEquals(\"F\\u00f6r fattare\", c.getTaggerIdent().getName());\n+\t\tassertEquals(\"Sm\\u00f6rg\\u00e5sbord\", c.getShortMessage());\n+\t\tassertEquals(\"Sm\\u00f6rg\\u00e5sbord\\n\\n\\u304d\\u308c\\u3044\\n\", c\n+\t\t\t\t.getFullMessage());\n+\t}\n+\n+\t/**\n+\t * Test parsing of a commit whose encoding is given and works.\n+\t * \n+\t * @throws Exception\n+\t */\n+\tpublic void testParse_explicit_encoded() throws Exception {\n+\t\tfinal ByteArrayOutputStream b = new ByteArrayOutputStream();\n+\t\tb.write(\"object 9788669ad918b6fcce64af8882fc9a81cb6aba67\\n\"\n+\t\t\t\t.getBytes(\"EUC-JP\"));\n+\t\tb.write(\"type tree\\n\".getBytes(\"EUC-JP\"));\n+\t\tb.write(\"tag v1.2.3.4.5\\n\".getBytes(\"EUC-JP\"));\n+\t\tb\n+\t\t\t\t.write(\"tagger F\\u00f6r fattare <a_u_thor@example.com> 1218123387 +0700\\n\"\n+\t\t\t\t\t\t.getBytes(\"EUC-JP\"));\n+\t\tb.write(\"encoding euc_JP\\n\".getBytes(\"EUC-JP\"));\n+\t\tb.write(\"\\n\".getBytes(\"EUC-JP\"));\n+\t\tb.write(\"\\u304d\\u308c\\u3044\\n\".getBytes(\"EUC-JP\"));\n+\t\tb.write(\"\\n\".getBytes(\"EUC-JP\"));\n+\t\tb.write(\"Hi\\n\".getBytes(\"EUC-JP\"));\n+\t\tfinal RevTag c;\n+\t\tc = new RevTag(id(\"9473095c4cb2f12aefe1db8a355fe3fafba42f67\"));\n+\t\tc.parseCanonical(new RevWalk(db), b.toByteArray());\n+\n+\t\tassertEquals(\"F\\u00f6r fattare\", c.getTaggerIdent().getName());\n+\t\tassertEquals(\"\\u304d\\u308c\\u3044\", c.getShortMessage());\n+\t\tassertEquals(\"\\u304d\\u308c\\u3044\\n\\nHi\\n\", c.getFullMessage());\n+\t}\n+\n+\t/**\n+\t * This is a twisted case, but show what we expect here. We can revise the\n+\t * expectations provided this case is updated.\n+\t * \n+\t * What happens here is that an encoding us given, but data is not encoded\n+\t * that way (and we can detect it), so we try other encodings.\n+\t * \n+\t * @throws Exception\n+\t */\n+\tpublic void testParse_explicit_bad_encoded() throws Exception {\n+\t\tfinal ByteArrayOutputStream b = new ByteArrayOutputStream();\n+\t\tb.write(\"object 9788669ad918b6fcce64af8882fc9a81cb6aba67\\n\"\n+\t\t\t\t.getBytes(\"UTF-8\"));\n+\t\tb.write(\"type tree\\n\".getBytes(\"UTF-8\"));\n+\t\tb.write(\"tag v1.2.3.4.5\\n\".getBytes(\"UTF-8\"));\n+\t\tb\n+\t\t\t\t.write(\"tagger F\\u00f6r fattare <a_u_thor@example.com> 1218123387 +0700\\n\"\n+\t\t\t\t\t\t.getBytes(\"ISO-8859-1\"));\n+\t\tb.write(\"encoding EUC-JP\\n\".getBytes(\"UTF-8\"));\n+\t\tb.write(\"\\n\".getBytes(\"UTF-8\"));\n+\t\tb.write(\"\\u304d\\u308c\\u3044\\n\".getBytes(\"UTF-8\"));\n+\t\tb.write(\"\\n\".getBytes(\"UTF-8\"));\n+\t\tb.write(\"Hi\\n\".getBytes(\"UTF-8\"));\n+\t\tfinal RevTag c;\n+\t\tc = new RevTag(id(\"9473095c4cb2f12aefe1db8a355fe3fafba42f67\"));\n+\t\tc.parseCanonical(new RevWalk(db), b.toByteArray());\n+\n+\t\tassertEquals(\"F\\u00f6r fattare\", c.getTaggerIdent().getName());\n+\t\tassertEquals(\"\\u304d\\u308c\\u3044\", c.getShortMessage());\n+\t\tassertEquals(\"\\u304d\\u308c\\u3044\\n\\nHi\\n\", c.getFullMessage());\n+\t}\n+\n+\t/**\n+\t * This is a twisted case too, but show what we expect here. We can revise\n+\t * the expectations provided this case is updated.\n+\t * \n+\t * What happens here is that an encoding us given, but data is not encoded\n+\t * that way (and we can detect it), so we try other encodings. Here data\n+\t * could actually be decoded in the stated encoding, but we override using\n+\t * UTF-8.\n+\t * \n+\t * @throws Exception\n+\t */\n+\tpublic void testParse_explicit_bad_encoded2() throws Exception {\n+\t\tfinal ByteArrayOutputStream b = new ByteArrayOutputStream();\n+\t\tb.write(\"object 9788669ad918b6fcce64af8882fc9a81cb6aba67\\n\"\n+\t\t\t\t.getBytes(\"UTF-8\"));\n+\t\tb.write(\"type tree\\n\".getBytes(\"UTF-8\"));\n+\t\tb.write(\"tag v1.2.3.4.5\\n\".getBytes(\"UTF-8\"));\n+\t\tb\n+\t\t\t\t.write(\"tagger F\\u00f6r fattare <a_u_thor@example.com> 1218123387 +0700\\n\"\n+\t\t\t\t\t\t.getBytes(\"UTF-8\"));\n+\t\tb.write(\"encoding ISO-8859-1\\n\".getBytes(\"UTF-8\"));\n+\t\tb.write(\"\\n\".getBytes(\"UTF-8\"));\n+\t\tb.write(\"\\u304d\\u308c\\u3044\\n\".getBytes(\"UTF-8\"));\n+\t\tb.write(\"\\n\".getBytes(\"UTF-8\"));\n+\t\tb.write(\"Hi\\n\".getBytes(\"UTF-8\"));\n+\t\tfinal RevTag c;\n+\t\tc = new RevTag(id(\"9473095c4cb2f12aefe1db8a355fe3fafba42f67\"));\n+\t\tc.parseCanonical(new RevWalk(db), b.toByteArray());\n+\n+\t\tassertEquals(\"F\\u00f6r fattare\", c.getTaggerIdent().getName());\n+\t\tassertEquals(\"\\u304d\\u308c\\u3044\", c.getShortMessage());\n+\t\tassertEquals(\"\\u304d\\u308c\\u3044\\n\\nHi\\n\", c.getFullMessage());\n+\t}\n+\n+\tpublic void testParse_NoMessage() throws Exception {\n+\t\tfinal String msg = \"\";\n+\t\tfinal RevTag c = create(msg);\n+\t\tassertEquals(msg, c.getFullMessage());\n+\t\tassertEquals(msg, c.getShortMessage());\n+\t}\n+\n+\tpublic void testParse_OnlyLFMessage() throws Exception {\n+\t\tfinal RevTag c = create(\"\\n\");\n+\t\tassertEquals(\"\\n\", c.getFullMessage());\n+\t\tassertEquals(\"\", c.getShortMessage());\n+\t}\n+\n+\tpublic void testParse_ShortLineOnlyNoLF() throws Exception {\n+\t\tfinal String shortMsg = \"This is a short message.\";\n+\t\tfinal RevTag c = create(shortMsg);\n+\t\tassertEquals(shortMsg, c.getFullMessage());\n+\t\tassertEquals(shortMsg, c.getShortMessage());\n+\t}\n+\n+\tpublic void testParse_ShortLineOnlyEndLF() throws Exception {\n+\t\tfinal String shortMsg = \"This is a short message.\";\n+\t\tfinal String fullMsg = shortMsg + \"\\n\";\n+\t\tfinal RevTag c = create(fullMsg);\n+\t\tassertEquals(fullMsg, c.getFullMessage());\n+\t\tassertEquals(shortMsg, c.getShortMessage());\n+\t}\n+\n+\tpublic void testParse_ShortLineOnlyEmbeddedLF() throws Exception {\n+\t\tfinal String fullMsg = \"This is a\\nshort message.\";\n+\t\tfinal String shortMsg = fullMsg.replace('\\n', ' ');\n+\t\tfinal RevTag c = create(fullMsg);\n+\t\tassertEquals(fullMsg, c.getFullMessage());\n+\t\tassertEquals(shortMsg, c.getShortMessage());\n+\t}\n+\n+\tpublic void testParse_ShortLineOnlyEmbeddedAndEndingLF() throws Exception {\n+\t\tfinal String fullMsg = \"This is a\\nshort message.\\n\";\n+\t\tfinal String shortMsg = \"This is a short message.\";\n+\t\tfinal RevTag c = create(fullMsg);\n+\t\tassertEquals(fullMsg, c.getFullMessage());\n+\t\tassertEquals(shortMsg, c.getShortMessage());\n+\t}\n+\n+\tpublic void testParse_GitStyleMessage() throws Exception {\n+\t\tfinal String shortMsg = \"This fixes a bug.\";\n+\t\tfinal String body = \"We do it with magic and pixie dust and stuff.\\n\"\n+\t\t\t\t+ \"\\n\" + \"Signed-off-by: A U. Thor <author@example.com>\\n\";\n+\t\tfinal String fullMsg = shortMsg + \"\\n\" + \"\\n\" + body;\n+\t\tfinal RevTag c = create(fullMsg);\n+\t\tassertEquals(fullMsg, c.getFullMessage());\n+\t\tassertEquals(shortMsg, c.getShortMessage());\n+\t}\n+\n+\tprivate static ObjectId id(final String str) {\n+\t\treturn ObjectId.fromString(str);\n+\t}\n+}\ndiff --git a/org.spearce.jgit/src/org/spearce/jgit/revwalk/RevCommit.java b/org.spearce.jgit/src/org/spearce/jgit/revwalk/RevCommit.java\nindex 7454d8e..de11c39 100644\n--- a/org.spearce.jgit/src/org/spearce/jgit/revwalk/RevCommit.java\n+++ b/org.spearce.jgit/src/org/spearce/jgit/revwalk/RevCommit.java\n@@ -367,7 +367,7 @@ public final String getShortMessage() {\n \t\treturn str;\n \t}\n \n-\tprivate static boolean hasLF(final byte[] r, int b, final int e) {\n+\tstatic boolean hasLF(final byte[] r, int b, final int e) {\n \t\twhile (b < e)\n \t\t\tif (r[b++] == '\\n')\n \t\t\t\treturn true;\ndiff --git a/org.spearce.jgit/src/org/spearce/jgit/revwalk/RevTag.java b/org.spearce.jgit/src/org/spearce/jgit/revwalk/RevTag.java\nindex 77a55cd..82f0009 100644\n--- a/org.spearce.jgit/src/org/spearce/jgit/revwalk/RevTag.java\n+++ b/org.spearce.jgit/src/org/spearce/jgit/revwalk/RevTag.java\n@@ -38,6 +38,7 @@\n package org.spearce.jgit.revwalk;\n \n import java.io.IOException;\n+import java.nio.charset.Charset;\n \n import org.spearce.jgit.errors.CorruptObjectException;\n import org.spearce.jgit.errors.IncorrectObjectTypeException;\n@@ -45,6 +46,7 @@\n import org.spearce.jgit.lib.AnyObjectId;\n import org.spearce.jgit.lib.Constants;\n import org.spearce.jgit.lib.ObjectLoader;\n+import org.spearce.jgit.lib.PersonIdent;\n import org.spearce.jgit.lib.Tag;\n import org.spearce.jgit.util.MutableInteger;\n import org.spearce.jgit.util.RawParseUtils;\n@@ -100,7 +102,77 @@ void parseCanonical(final RevWalk walk, final byte[] rawTag)\n \tpublic int getType() {\n \t\treturn Constants.OBJ_TAG;\n \t}\n-\t\n+\n+\t/**\n+\t * Parse the tagger identity from the raw buffer.\n+\t * <p>\n+\t * This method parses and returns the content of the tagger line, after\n+\t * taking the tag's character set into account and decoding the tagger\n+\t * name and email address. This method is fairly expensive and produces a\n+\t * new PersonIdent instance on each invocation. Callers should invoke this\n+\t * method only if they are certain they will be outputting the result, and\n+\t * should cache the return value for as long as necessary to use all\n+\t * information from it.\n+\t * \n+\t * @return identity of the tagger (name, email) and the time the tag\n+\t *         was made by the tagger; null if no tagger line was found.\n+\t */\n+\tpublic final PersonIdent getTaggerIdent() {\n+\t\tfinal byte[] raw = buffer;\n+\t\tfinal int nameB = RawParseUtils.tagger(raw, 0);\n+\t\tif (nameB < 0)\n+\t\t\treturn null;\n+\t\treturn RawParseUtils.parsePersonIdent(raw, nameB);\n+\t}\n+\n+\t/**\n+\t * Parse the complete tag message and decode it to a string.\n+\t * <p>\n+\t * This method parses and returns the message portion of the tag buffer,\n+\t * after taking the tag's character set into account and decoding the buffer\n+\t * using that character set. This method is a fairly expensive operation and\n+\t * produces a new string on each invocation.\n+\t * \n+\t * @return decoded tag message as a string. Never null.\n+\t */\n+\tpublic final String getFullMessage() {\n+\t\tfinal byte[] raw = buffer;\n+\t\tfinal int msgB = RawParseUtils.tagMessage(raw, 0);\n+\t\tif (msgB < 0)\n+\t\t\treturn \"\";\n+\t\tfinal Charset enc = RawParseUtils.parseEncoding(raw);\n+\t\treturn RawParseUtils.decode(enc, raw, msgB, raw.length);\n+\t}\n+\n+\t/**\n+\t * Parse the tag message and return the first \"line\" of it.\n+\t * <p>\n+\t * The first line is everything up to the first pair of LFs. This is the\n+\t * \"oneline\" format, suitable for output in a single line display.\n+\t * <p>\n+\t * This method parses and returns the message portion of the tag buffer,\n+\t * after taking the tag's character set into account and decoding the buffer\n+\t * using that character set. This method is a fairly expensive operation and\n+\t * produces a new string on each invocation.\n+\t * \n+\t * @return decoded tag message as a string. Never null. The returned string\n+\t *         does not contain any LFs, even if the first paragraph spanned\n+\t *         multiple lines. Embedded LFs are converted to spaces.\n+\t */\n+\tpublic final String getShortMessage() {\n+\t\tfinal byte[] raw = buffer;\n+\t\tfinal int msgB = RawParseUtils.tagMessage(raw, 0);\n+\t\tif (msgB < 0)\n+\t\t\treturn \"\";\n+\n+\t\tfinal Charset enc = RawParseUtils.parseEncoding(raw);\n+\t\tfinal int msgE = RawParseUtils.endOfParagraph(raw, msgB);\n+\t\tString str = RawParseUtils.decode(enc, raw, msgB, msgE);\n+\t\tif (RevCommit.hasLF(raw, msgB, msgE))\n+\t\t\tstr = str.replace('\\n', ' ');\n+\t\treturn str;\n+\t}\n+\n \t/**\n \t * Parse this tag buffer for display.\n \t * \ndiff --git a/org.spearce.jgit/src/org/spearce/jgit/util/RawParseUtils.java b/org.spearce.jgit/src/org/spearce/jgit/util/RawParseUtils.java\nindex 758e7af..1b8bf94 100644\n--- a/org.spearce.jgit/src/org/spearce/jgit/util/RawParseUtils.java\n+++ b/org.spearce.jgit/src/org/spearce/jgit/util/RawParseUtils.java\n@@ -40,6 +40,7 @@\n import static org.spearce.jgit.lib.ObjectChecker.author;\n import static org.spearce.jgit.lib.ObjectChecker.committer;\n import static org.spearce.jgit.lib.ObjectChecker.encoding;\n+import static org.spearce.jgit.lib.ObjectChecker.tagger;\n \n import java.nio.ByteBuffer;\n import java.nio.charset.CharacterCodingException;\n@@ -397,6 +398,34 @@ public static final int committer(final byte[] b, int ptr) {\n \t}\n \n \t/**\n+\t * Locate the \"tagger \" header line data.\n+\t * \n+\t * @param b\n+\t *            buffer to scan.\n+\t * @param ptr\n+\t *            position in buffer to start the scan at. Most callers should\n+\t *            pass 0 to ensure the scan starts from the beginning of the tag\n+\t *            buffer and does not accidentally look at message body.\n+\t * @return position just after the space in \"tagger \", so the first\n+\t *         character of the tagger's name. If no tagger header can be\n+\t *         located -1 is returned.\n+\t */\n+\tpublic static final int tagger(final byte[] b, int ptr) {\n+\t\tfinal int sz = b.length;\n+\t\tif (ptr == 0)\n+\t\t\tptr += 48; // skip the \"object ...\" line.\n+\t\twhile (ptr < sz) {\n+\t\t\tif (b[ptr] == '\\n')\n+\t\t\t\treturn -1;\n+\t\t\tfinal int m = match(b, ptr, tagger);\n+\t\t\tif (m >= 0)\n+\t\t\t\treturn m;\n+\t\t\tptr = nextLF(b, ptr);\n+\t\t}\n+\t\treturn -1;\n+\t}\n+\n+\t/**\n \t * Locate the \"encoding \" header line.\n \t * \n \t * @param b\n@@ -648,9 +677,27 @@ public static final int commitMessage(final byte[] b, int ptr) {\n \t\twhile (ptr < sz && b[ptr] == 'p')\n \t\t\tptr += 48; // skip this parent.\n \n-\t\t// skip any remaining header lines, ignoring what their actual\n-\t\t// header line type is.\n+\t\t// Skip any remaining header lines, ignoring what their actual\n+\t\t// header line type is. This is identical to the logic for a tag.\n \t\t//\n+\t\treturn tagMessage(b, ptr);\n+\t}\n+\n+\t/**\n+\t * Locate the position of the tag message body.\n+\t * \n+\t * @param b\n+\t *            buffer to scan.\n+\t * @param ptr\n+\t *            position in buffer to start the scan at. Most callers should\n+\t *            pass 0 to ensure the scan starts from the beginning of the tag\n+\t *            buffer.\n+\t * @return position of the user's message buffer.\n+\t */\n+\tpublic static final int tagMessage(final byte[] b, int ptr) {\n+\t\tfinal int sz = b.length;\n+\t\tif (ptr == 0)\n+\t\t\tptr += 48; // skip the \"object ...\" line.\n \t\twhile (ptr < sz && b[ptr] != '\\n')\n \t\t\tptr = nextLF(b, ptr);\n \t\tif (ptr < sz && b[ptr] == '\\n')\n-- \n1.6.1.2.418.gd79e6\n\n\n-- \nShawn.\n"}]}