{"thread":{"id":"15096","subject":"[JGIT PATCH] Support path names longer than 4095 bytes in DirCache","startedAt":"2008-08-19T22:51:53Z","lastAt":"2008-08-19T22:54:03Z","messageCount":3,"participants":["Shawn O. Pearce"],"isPatch":true,"patchVersion":1,"patchTotal":null},"messages":[{"id":"87763","messageId":"1219186314-24048-1-git-send-email-spearce@spearce.org","threadId":"15096","inReplyTo":null,"subject":"[JGIT PATCH 00/14] TreeWalk D/F conflict detection","fromName":"Shawn O. Pearce","fromEmail":"spearce@spearce.org","sentAt":"2008-08-19T22:51:53Z","receivedAt":"2008-08-19T22:51:53Z","isPatch":true,"sender":{"key":"spearce@spearce.org","avatar":"https://avatars.githubusercontent.com/u/34844?v=4"},"body":"This series is about fixing the \"mistake\" in Git trees where\nsubtrees sort as through their name is \"path/\" and not \"path\".\n\nWithin a TreeWalk this is a problem because the tree contents:\n\n    Tree 1     Tree 2\n  ------------------------\n    100644 a\n               100644 a.c\n               040000 a\n    100644 b\n\nneeds to merge together both \"a\" paths from tree 1 and tree 2, but\nthese appear at different points in time when we merge-sort the two\ntrees together.\n\nWe use an infinite look-ahead and look-behind to identify these cases\nand make the iteration look like this instead:\n\n    Tree 1     Tree 2\n  ------------------------\n    100644 a   040000 a\n               100644 a.c\n    100644 b\n\nwhich allows the application to handle the D/F conflict in a single\nstep, even though the contents of \"a\" may now be out-of-order within\nthe DirCache.  Fortunately DirCacheBuilder can automatically fix this\nsort of ordering problem.\n\n\nShawn O. Pearce (14):\n  Detect path names which overflow the name length field in the index\n  Fix NB.decodeUInt16 to correctly handle the high byte\n  Add test cases for NB.encode and NB.decode family of routines\n  Fix DirCache's skip over null byte padding when reading a DIRC file\n  Fix usage of assertEquals in DirCacheIteratorTest\n  Refactor AbstractTreeIterator.pathCompare to force another mode\n  Micro-optimize AbstractTreeIterator.pathCompare\n  Optimize path comparsion within subtrees during TreeWalk\n  Refactor AbstractTreeIterator semantics to start on first entry\n  Make all AbstractTreeIterator implementations bi-directional\n  Expose beginning of iterator indication from AbstractTreeIterator\n  Allow application code to set ObjectIds in DirCacheEntry\n  Create NameConflictTreeWalk to transparently detect D/F conflicts\n  Add test case for NameConflictTreeWalk\n\n .../spearce/egit/core/ContainerTreeIterator.java   |    9 +-\n .../jgit/dircache/DirCacheBuilderIteratorTest.java |    2 +-\n .../jgit/dircache/DirCacheIteratorTest.java        |   28 +-\n .../jgit/treewalk/CanonicalTreeParserTest.java     |  261 ++++++++++++++++\n .../jgit/treewalk/NameConflictTreeWalkTest.java    |  205 ++++++++++++\n .../tst/org/spearce/jgit/util/NBTest.java          |  328 ++++++++++++++++++++\n .../src/org/spearce/jgit/dircache/DirCache.java    |    2 +-\n .../jgit/dircache/DirCacheBuildIterator.java       |    9 +-\n .../org/spearce/jgit/dircache/DirCacheEntry.java   |   41 +++-\n .../spearce/jgit/dircache/DirCacheIterator.java    |   84 +++--\n .../jgit/treewalk/AbstractTreeIterator.java        |  129 +++++---\n .../spearce/jgit/treewalk/CanonicalTreeParser.java |   94 +++++-\n .../spearce/jgit/treewalk/EmptyTreeIterator.java   |   12 +-\n .../spearce/jgit/treewalk/FileTreeIterator.java    |    5 +-\n .../jgit/treewalk/NameConflictTreeWalk.java        |  237 ++++++++++++++\n .../src/org/spearce/jgit/treewalk/TreeWalk.java    |   18 +-\n .../spearce/jgit/treewalk/WorkingTreeIterator.java |  132 ++++-----\n org.spearce.jgit/src/org/spearce/jgit/util/NB.java |   29 ++-\n 18 files changed, 1418 insertions(+), 207 deletions(-)\n create mode 100644 org.spearce.jgit.test/tst/org/spearce/jgit/treewalk/CanonicalTreeParserTest.java\n create mode 100644 org.spearce.jgit.test/tst/org/spearce/jgit/treewalk/NameConflictTreeWalkTest.java\n create mode 100644 org.spearce.jgit.test/tst/org/spearce/jgit/util/NBTest.java\n create mode 100644 org.spearce.jgit/src/org/spearce/jgit/treewalk/NameConflictTreeWalk.java\n"},{"id":"87762","messageId":"1219186314-24048-2-git-send-email-spearce@spearce.org","threadId":"15096","inReplyTo":"1219186314-24048-1-git-send-email-spearce@spearce.org","subject":"[JGIT PATCH] Support path names longer than 4095 bytes in DirCache","fromName":"Shawn O. Pearce","fromEmail":"spearce@spearce.org","sentAt":"2008-08-19T22:51:54Z","receivedAt":"2008-08-19T22:51:54Z","isPatch":true,"sender":{"key":"spearce@spearce.org","avatar":"https://avatars.githubusercontent.com/u/34844?v=4"},"body":"C Git supports long paths within the index file by setting the\nlength portion of the flags field to 4095 (0xfff) and then it\ndoes a simple C strlen() on the remaining part of the string\nto locate the first null byte.\n\nThe trick works because every index record must have between\n1 and 8 null bytes trailing the entry as padding.  C Git had\nthis rule to make it easy to pass the mmap'd name portion of\nan entry directly to system calls, and it helped to ensure\nall members were aligned properly in memory.\n\nWe now support infinite length paths (well, up to 2GB) by\nusing the same encoding strategy, however paths under the\n4095 limit still perform better as they do not require a\nsingle byte read loop and a growing/shrinking byte buffer.\n\nSigned-off-by: Shawn O. Pearce <spearce@spearce.org>\n---\n .../jgit/dircache/DirCacheLargePathTest.java       |  106 ++++++++++++++++++++\n .../org/spearce/jgit/dircache/DirCacheEntry.java   |   39 ++++++--\n 2 files changed, 136 insertions(+), 9 deletions(-)\n create mode 100644 org.spearce.jgit.test/tst/org/spearce/jgit/dircache/DirCacheLargePathTest.java\n\ndiff --git a/org.spearce.jgit.test/tst/org/spearce/jgit/dircache/DirCacheLargePathTest.java b/org.spearce.jgit.test/tst/org/spearce/jgit/dircache/DirCacheLargePathTest.java\nnew file mode 100644\nindex 0000000..4ea286c\n--- /dev/null\n+++ b/org.spearce.jgit.test/tst/org/spearce/jgit/dircache/DirCacheLargePathTest.java\n@@ -0,0 +1,106 @@\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.dircache;\n+\n+import java.io.IOException;\n+\n+import org.spearce.jgit.errors.CorruptObjectException;\n+import org.spearce.jgit.lib.RepositoryTestCase;\n+\n+public class DirCacheLargePathTest extends RepositoryTestCase {\n+\tpublic void testPath_4090() throws Exception {\n+\t\ttestLongPath(4090);\n+\t}\n+\n+\tpublic void testPath_4094() throws Exception {\n+\t\ttestLongPath(4094);\n+\t}\n+\n+\tpublic void testPath_4095() throws Exception {\n+\t\ttestLongPath(4095);\n+\t}\n+\n+\tpublic void testPath_4096() throws Exception {\n+\t\ttestLongPath(4096);\n+\t}\n+\n+\tpublic void testPath_16384() throws Exception {\n+\t\ttestLongPath(16384);\n+\t}\n+\n+\tprivate void testLongPath(final int len) throws CorruptObjectException,\n+\t\t\tIOException {\n+\t\tfinal String longPath = makeLongPath(len);\n+\t\tfinal String shortPath = \"~~~ shorter-path\";\n+\n+\t\tfinal DirCacheEntry longEnt = new DirCacheEntry(longPath);\n+\t\tfinal DirCacheEntry shortEnt = new DirCacheEntry(shortPath);\n+\t\tassertEquals(longPath, longEnt.getPathString());\n+\t\tassertEquals(shortPath, shortEnt.getPathString());\n+\n+\t\t{\n+\t\t\tfinal DirCache dc1 = DirCache.lock(db);\n+\t\t\t{\n+\t\t\t\tfinal DirCacheBuilder b = dc1.builder();\n+\t\t\t\tb.add(longEnt);\n+\t\t\t\tb.add(shortEnt);\n+\t\t\t\tassertTrue(b.commit());\n+\t\t\t}\n+\t\t\tassertEquals(2, dc1.getEntryCount());\n+\t\t\tassertSame(longEnt, dc1.getEntry(0));\n+\t\t\tassertSame(shortEnt, dc1.getEntry(1));\n+\t\t}\n+\t\t{\n+\t\t\tfinal DirCache dc2 = DirCache.read(db);\n+\t\t\tassertEquals(2, dc2.getEntryCount());\n+\n+\t\t\tassertNotSame(longEnt, dc2.getEntry(0));\n+\t\t\tassertEquals(longPath, dc2.getEntry(0).getPathString());\n+\n+\t\t\tassertNotSame(shortEnt, dc2.getEntry(1));\n+\t\t\tassertEquals(shortPath, dc2.getEntry(1).getPathString());\n+\t\t}\n+\t}\n+\n+\tprivate static String makeLongPath(final int len) {\n+\t\tfinal StringBuilder r = new StringBuilder(len);\n+\t\tfor (int i = 0; i < len; i++)\n+\t\t\tr.append('a' + (i % 26));\n+\t\treturn r.toString();\n+\t}\n+}\ndiff --git a/org.spearce.jgit/src/org/spearce/jgit/dircache/DirCacheEntry.java b/org.spearce.jgit/src/org/spearce/jgit/dircache/DirCacheEntry.java\nindex 011bc16..dbd74b5 100644\n--- a/org.spearce.jgit/src/org/spearce/jgit/dircache/DirCacheEntry.java\n+++ b/org.spearce.jgit/src/org/spearce/jgit/dircache/DirCacheEntry.java\n@@ -37,6 +37,8 @@\n \n package org.spearce.jgit.dircache;\n \n+import java.io.ByteArrayOutputStream;\n+import java.io.EOFException;\n import java.io.IOException;\n import java.io.InputStream;\n import java.io.OutputStream;\n@@ -105,18 +107,36 @@ DirCacheEntry(final byte[] sharedInfo, final int infoAt,\n \t\tNB.readFully(in, info, infoOffset, INFO_LEN);\n \n \t\tint pathLen = NB.decodeUInt16(info, infoOffset + P_FLAGS) & NAME_MASK;\n-\t\tif (pathLen == NAME_MASK)\n-\t\t\tthrow new IOException(\"Path name too long for jgit\");\n-\t\tpath = new byte[pathLen];\n-\t\tNB.readFully(in, path, 0, pathLen);\n+\t\tint skipped = 0;\n+\t\tif (pathLen < NAME_MASK) {\n+\t\t\tpath = new byte[pathLen];\n+\t\t\tNB.readFully(in, path, 0, pathLen);\n+\t\t} else {\n+\t\t\tfinal ByteArrayOutputStream tmp = new ByteArrayOutputStream();\n+\t\t\t{\n+\t\t\t\tfinal byte[] buf = new byte[NAME_MASK];\n+\t\t\t\tNB.readFully(in, buf, 0, NAME_MASK);\n+\t\t\t\ttmp.write(buf);\n+\t\t\t}\n+\t\t\tfor (;;) {\n+\t\t\t\tfinal int c = in.read();\n+\t\t\t\tif (c < 0)\n+\t\t\t\t\tthrow new EOFException(\"Short read of block.\");\n+\t\t\t\tif (c == 0)\n+\t\t\t\t\tbreak;\n+\t\t\t\ttmp.write(c);\n+\t\t\t}\n+\t\t\tpath = tmp.toByteArray();\n+\t\t\tpathLen = path.length;\n+\t\t\tskipped = 1; // we already skipped 1 '\\0' above to break the loop.\n+\t\t}\n \n \t\t// Index records are padded out to the next 8 byte alignment\n \t\t// for historical reasons related to how C Git read the files.\n \t\t//\n \t\tfinal int actLen = INFO_LEN + pathLen;\n \t\tfinal int expLen = (actLen + 8) & ~7;\n-\t\tif (actLen != expLen)\n-\t\t\tNB.skipFully(in, expLen - actLen);\n+\t\tNB.skipFully(in, expLen - actLen - skipped);\n \t}\n \n \t/**\n@@ -140,9 +160,10 @@ public DirCacheEntry(final byte[] newPath) {\n \t\tinfoOffset = 0;\n \n \t\tpath = newPath;\n-\t\tif (path.length >= NAME_MASK)\n-\t\t\tthrow new IllegalArgumentException(\"Path name too long for jgit\");\n-\t\tNB.encodeInt16(info, infoOffset + P_FLAGS, path.length);\n+\t\tif (path.length < NAME_MASK)\n+\t\t\tNB.encodeInt16(info, infoOffset + P_FLAGS, path.length);\n+\t\telse\n+\t\t\tNB.encodeInt16(info, infoOffset + P_FLAGS, NAME_MASK);\n \t}\n \n \tvoid write(final OutputStream os) throws IOException {\n-- \n1.6.0.96.g2fad1.dirty\n"},{"id":"87764","messageId":"20080819225403.GG24212@spearce.org","threadId":"15096","inReplyTo":"1219186314-24048-1-git-send-email-spearce@spearce.org","subject":"Re: [JGIT PATCH 00/14] TreeWalk D/F conflict detection","fromName":"Shawn O. Pearce","fromEmail":"spearce@spearce.org","sentAt":"2008-08-19T22:54:03Z","receivedAt":"2008-08-19T22:54:03Z","isPatch":true,"sender":{"key":"spearce@spearce.org","avatar":"https://avatars.githubusercontent.com/u/34844?v=4"},"body":"Sorry about that, my git-send-email directory had this cover letter\nlaying around in it from last time.  There is no series, I only meant\nto post the single patch \"Support path names longer than 4095...\".\n\n> Shawn O. Pearce (14):\n>   Detect path names which overflow the name length field in the index\n>   Fix NB.decodeUInt16 to correctly handle the high byte\n>   Add test cases for NB.encode and NB.decode family of routines\n>   Fix DirCache's skip over null byte padding when reading a DIRC file\n>   Fix usage of assertEquals in DirCacheIteratorTest\n>   Refactor AbstractTreeIterator.pathCompare to force another mode\n>   Micro-optimize AbstractTreeIterator.pathCompare\n>   Optimize path comparsion within subtrees during TreeWalk\n>   Refactor AbstractTreeIterator semantics to start on first entry\n>   Make all AbstractTreeIterator implementations bi-directional\n>   Expose beginning of iterator indication from AbstractTreeIterator\n>   Allow application code to set ObjectIds in DirCacheEntry\n>   Create NameConflictTreeWalk to transparently detect D/F conflicts\n>   Add test case for NameConflictTreeWalk\n...\n>  18 files changed, 1418 insertions(+), 207 deletions(-)\n\n-- \nShawn.\n"}]}