{"thread":{"id":"16840","subject":"[JGIT PATCH 00/13] Add receive-pack server side support","startedAt":"2008-12-23T00:27:10Z","lastAt":"2009-01-05T23:07:43Z","messageCount":17,"participants":["Shawn O. Pearce","Robin Rosenberg"],"isPatch":true,"patchVersion":1,"patchTotal":13},"messages":[{"id":"98571","messageId":"1229992043-1053-1-git-send-email-spearce@spearce.org","threadId":"16840","inReplyTo":null,"subject":"[JGIT PATCH 00/13] Add receive-pack server side support","fromName":"Shawn O. Pearce","fromEmail":"spearce@spearce.org","sentAt":"2008-12-23T00:27:10Z","receivedAt":"2008-12-23T00:27:10Z","isPatch":true,"sender":{"key":"spearce@spearce.org","avatar":"https://avatars.githubusercontent.com/u/34844?v=4"},"body":"This is a simple implementation of the receive-pack part of the\nsend-pack/receive-pack protocol used by push.  Most of the logic\nthe server implements can be customized by implementing your\nown PreReceiveHook implementation.\n\nI'm actually going to use this within Gerrit 2 so users can push\ninto the special ref name \"refs/changes/new\" and have an anonymous\ntopic branch be created instead.\n\nA basic \"jgit daemon\" for the git:// protocol is also included.\nThis daemon still has some major limitations over \"git daemon\":\n\n - Only \"receive-pack\" is supported.  We do not have a Java\n   version of \"upload-pack\" so we do not support it here.\n\n - Virtual hosting is not supported; the \"host=\" header (if sent\n   by the client) is discarded.\n\n - User escapes (\"git://host/~user/foo.git\") are not honored.\n\n - No receive timeouts.  Evil clients can connect and do nothing,\n   pinning the server thread indefinitely.  This can cause the\n   server to consume resources until it crashes.\n\n - No connection limits.  Evil clients can DOS the server by just\n   opening a ton of connections.\n\n - \"jgit daemon\" must be started within a git repository, even if\n   it won't publish that repository over the network.  This is due\n   to a limitation in the way TextBuiltin initializes itself.\n\nProviding \"jgit receive-pack\" should be trivial if the last item\nis fixed, permitting jgit commands to be executed from outside of\na Git repository.\n\nJSch does not have an SSH server implementation, so we cannot easily\nprovide an SSH daemon for our new push support.  Apache MINA SSHD has\na nice daemon under development, and I plan on using it in Gerrit 2.\nPerhaps someone will eventually contribute an extension library\nfor JGit which provides the binding glue necessary for MINA SSHD\nto invoke JGit.\n\nJGit's ReceivePack class does not invoke the standard hooks honored\nby \"git receive-pack\", because doing so would require executing\nan external process.  There's enough data available in memory that\nsomeone could easily contribute a PreReceiveHook and PostRecieveHook\nimplementation that honored these.\n\nThe early part of the series has some code cleanups to ensure we\nare thread-safe, and to reduce the thrasing on the InflaterCache.\n\n\nShawn O. Pearce (13):\n  Fix invalid \"double checked locking\" in InflaterCache\n  Cleanup stupid release of the cached Inflater in IndexPack\n  Cache an Inflater inside a WindowCursor and reuse it as much as\n    possible\n  Make RefDatabase thread-safe\n  Make PackFile thread-safe\n  Make Repository thread-safe\n  Don't open a PackFile multiple times on scanForPacks\n  Expose RepositoryConfig.getBoolean so applications can use it\n  Add AnyObjectId.copyTo(StringBuilder)\n  Add compare-and-swap semantics to RefUpdate\n  Allow null new ObjectId during RefUpdate.delete\n  Implement the git-receive-pack process in Java\n  Add basic git daemon support to publish receive-pack\n\n .../services/org.spearce.jgit.pgm.TextBuiltin      |    1 +\n .../src/org/spearce/jgit/pgm/Daemon.java           |  125 +++\n .../src/org/spearce/jgit/lib/AnyObjectId.java      |   17 +-\n .../jgit/lib/DeltaRefPackedObjectLoader.java       |    2 +-\n .../src/org/spearce/jgit/lib/InflaterCache.java    |   31 +-\n .../src/org/spearce/jgit/lib/PackFile.java         |   14 +-\n .../src/org/spearce/jgit/lib/RefDatabase.java      |   29 +-\n .../src/org/spearce/jgit/lib/RefUpdate.java        |   32 +-\n .../src/org/spearce/jgit/lib/Repository.java       |   90 ++-\n .../src/org/spearce/jgit/lib/RepositoryConfig.java |    4 +-\n .../src/org/spearce/jgit/lib/WindowCursor.java     |   33 +-\n .../src/org/spearce/jgit/lib/WindowedFile.java     |   17 +-\n .../src/org/spearce/jgit/revwalk/RevWalk.java      |    4 +-\n .../src/org/spearce/jgit/transport/Daemon.java     |  309 ++++++++\n .../org/spearce/jgit/transport/DaemonClient.java   |  106 +++\n .../org/spearce/jgit/transport/DaemonService.java  |  120 +++\n .../src/org/spearce/jgit/transport/IndexPack.java  |   17 +-\n .../org/spearce/jgit/transport/PacketLineIn.java   |   12 +\n .../spearce/jgit/transport/PostReceiveHook.java    |   77 ++\n .../org/spearce/jgit/transport/PreReceiveHook.java |   94 +++\n .../org/spearce/jgit/transport/ReceiveCommand.java |  223 ++++++\n .../org/spearce/jgit/transport/ReceivePack.java    |  793 ++++++++++++++++++++\n .../spearce/jgit/transport/TransportGitAnon.java   |    3 +-\n 23 files changed, 2060 insertions(+), 93 deletions(-)\n create mode 100644 org.spearce.jgit.pgm/src/org/spearce/jgit/pgm/Daemon.java\n create mode 100644 org.spearce.jgit/src/org/spearce/jgit/transport/Daemon.java\n create mode 100644 org.spearce.jgit/src/org/spearce/jgit/transport/DaemonClient.java\n create mode 100644 org.spearce.jgit/src/org/spearce/jgit/transport/DaemonService.java\n create mode 100644 org.spearce.jgit/src/org/spearce/jgit/transport/PostReceiveHook.java\n create mode 100644 org.spearce.jgit/src/org/spearce/jgit/transport/PreReceiveHook.java\n create mode 100644 org.spearce.jgit/src/org/spearce/jgit/transport/ReceiveCommand.java\n create mode 100644 org.spearce.jgit/src/org/spearce/jgit/transport/ReceivePack.java\n"},{"id":"98574","messageId":"1229992043-1053-2-git-send-email-spearce@spearce.org","threadId":"16840","inReplyTo":"1229992043-1053-1-git-send-email-spearce@spearce.org","subject":"[JGIT PATCH 01/13] Fix invalid \"double checked locking\" in InflaterCache","fromName":"Shawn O. Pearce","fromEmail":"spearce@spearce.org","sentAt":"2008-12-23T00:27:11Z","receivedAt":"2008-12-23T00:27:11Z","isPatch":true,"sender":{"key":"spearce@spearce.org","avatar":"https://avatars.githubusercontent.com/u/34844?v=4"},"body":"We move the Inflater allocation out of the synchronized block as\nit requires JNI code to build the libz structure.  This permits\nother threads to get into the Inflater allocation faster.\n\nWe also fix the release implementation to validate the array size\nonly after we enter the lock, rather than before.  This prevents\nus from releasing an inflater due to a stale copy of the open\ncount being in our processor cache.\n\nSigned-off-by: Shawn O. Pearce <spearce@spearce.org>\n---\n .../src/org/spearce/jgit/lib/InflaterCache.java    |   31 ++++++++++---------\n 1 files changed, 16 insertions(+), 15 deletions(-)\n\ndiff --git a/org.spearce.jgit/src/org/spearce/jgit/lib/InflaterCache.java b/org.spearce.jgit/src/org/spearce/jgit/lib/InflaterCache.java\nindex f0f9139..578911c 100644\n--- a/org.spearce.jgit/src/org/spearce/jgit/lib/InflaterCache.java\n+++ b/org.spearce.jgit/src/org/spearce/jgit/lib/InflaterCache.java\n@@ -59,13 +59,18 @@\n \t * \n \t * @return an available inflater. Never null.\n \t */\n-\tpublic synchronized static Inflater get() {\n+\tpublic static Inflater get() {\n+\t\tfinal Inflater r = getImpl();\n+\t\treturn r != null ? r : new Inflater(false);\n+\t}\n+\n+\tprivate synchronized static Inflater getImpl() {\n \t\tif (openInflaterCount > 0) {\n \t\t\tfinal Inflater r = inflaterCache[--openInflaterCount];\n \t\t\tinflaterCache[openInflaterCount] = null;\n \t\t\treturn r;\n \t\t}\n-\t\treturn new Inflater(false);\n+\t\treturn null;\n \t}\n \n \t/**\n@@ -76,23 +81,19 @@ public synchronized static Inflater get() {\n \t *            does nothing.\n \t */\n \tpublic static void release(final Inflater i) {\n-\t\tif (i == null)\n-\t\t\treturn;\n-\n-\t\tif (openInflaterCount == SZ) {\n-\t\t\ti.end();\n-\t\t\treturn;\n+\t\tif (i != null) {\n+\t\t\ti.reset();\n+\t\t\tif (releaseImpl(i))\n+\t\t\t\ti.end();\n \t\t}\n-\n-\t\ti.reset();\n-\t\treleaseImpl(i);\n \t}\n \n-\tprivate static synchronized void releaseImpl(final Inflater i) {\n-\t\tif (openInflaterCount == SZ)\n-\t\t\ti.end();\n-\t\telse\n+\tprivate static synchronized boolean releaseImpl(final Inflater i) {\n+\t\tif (openInflaterCount < SZ) {\n \t\t\tinflaterCache[openInflaterCount++] = i;\n+\t\t\treturn false;\n+\t\t}\n+\t\treturn true;\n \t}\n \n \tprivate InflaterCache() {\n-- \n1.6.1.rc4.301.g5497a\n"},{"id":"98572","messageId":"1229992043-1053-3-git-send-email-spearce@spearce.org","threadId":"16840","inReplyTo":"1229992043-1053-2-git-send-email-spearce@spearce.org","subject":"[JGIT PATCH 02/13] Cleanup stupid release of the cached Inflater in IndexPack","fromName":"Shawn O. Pearce","fromEmail":"spearce@spearce.org","sentAt":"2008-12-23T00:27:12Z","receivedAt":"2008-12-23T00:27:12Z","isPatch":true,"sender":{"key":"spearce@spearce.org","avatar":"https://avatars.githubusercontent.com/u/34844?v=4"},"body":"The try/finally idiom avoids placing the Inflater on the stack,\nwhich means it goes unreachable slightly faster.  Its also much\nless obtuse to read.\n\nSigned-off-by: Shawn O. Pearce <spearce@spearce.org>\n---\n .../src/org/spearce/jgit/transport/IndexPack.java  |    8 +++++---\n 1 files changed, 5 insertions(+), 3 deletions(-)\n\ndiff --git a/org.spearce.jgit/src/org/spearce/jgit/transport/IndexPack.java b/org.spearce.jgit/src/org/spearce/jgit/transport/IndexPack.java\nindex 2752bb0..3e2187c 100644\n--- a/org.spearce.jgit/src/org/spearce/jgit/transport/IndexPack.java\n+++ b/org.spearce.jgit/src/org/spearce/jgit/transport/IndexPack.java\n@@ -320,9 +320,11 @@ public void index(final ProgressMonitor progress) throws IOException {\n \t\t\t\t\twriteIdx();\n \n \t\t\t} finally {\n-\t\t\t\tfinal Inflater inf = inflater;\n-\t\t\t\tinflater = null;\n-\t\t\t\tInflaterCache.release(inf);\n+\t\t\t\ttry {\n+\t\t\t\t\tInflaterCache.release(inflater);\n+\t\t\t\t} finally {\n+\t\t\t\t\tinflater = null;\n+\t\t\t\t}\n \n \t\t\t\tprogress.endTask();\n \t\t\t\tif (packOut != null)\n-- \n1.6.1.rc4.301.g5497a\n"},{"id":"98575","messageId":"1229992043-1053-4-git-send-email-spearce@spearce.org","threadId":"16840","inReplyTo":"1229992043-1053-3-git-send-email-spearce@spearce.org","subject":"[JGIT PATCH 03/13] Cache an Inflater inside a WindowCursor and reuse it as much as possible","fromName":"Shawn O. Pearce","fromEmail":"spearce@spearce.org","sentAt":"2008-12-23T00:27:13Z","receivedAt":"2008-12-23T00:27:13Z","isPatch":true,"sender":{"key":"spearce@spearce.org","avatar":"https://avatars.githubusercontent.com/u/34844?v=4"},"body":"By caching the Inflater within the WindowCursor we can improve\nperformance associated with reading objects from the pack files.\nEach read can use the cached Inflater, especially when chasing\ndown a delta chain.  This avoids locking on the global cache.\n\nSigned-off-by: Shawn O. Pearce <spearce@spearce.org>\n---\n .../jgit/lib/DeltaRefPackedObjectLoader.java       |    2 +-\n .../src/org/spearce/jgit/lib/PackFile.java         |    7 +++-\n .../src/org/spearce/jgit/lib/Repository.java       |    7 +++-\n .../src/org/spearce/jgit/lib/WindowCursor.java     |   33 ++++++++++++++------\n .../src/org/spearce/jgit/lib/WindowedFile.java     |   10 +-----\n .../src/org/spearce/jgit/revwalk/RevWalk.java      |    4 +-\n .../src/org/spearce/jgit/transport/IndexPack.java  |    9 ++++-\n 7 files changed, 47 insertions(+), 25 deletions(-)\n\ndiff --git a/org.spearce.jgit/src/org/spearce/jgit/lib/DeltaRefPackedObjectLoader.java b/org.spearce.jgit/src/org/spearce/jgit/lib/DeltaRefPackedObjectLoader.java\nindex 042d3a8..b126bbd 100644\n--- a/org.spearce.jgit/src/org/spearce/jgit/lib/DeltaRefPackedObjectLoader.java\n+++ b/org.spearce.jgit/src/org/spearce/jgit/lib/DeltaRefPackedObjectLoader.java\n@@ -54,7 +54,7 @@ DeltaRefPackedObjectLoader(final WindowCursor curs, final PackFile pr,\n \t}\n \n \tprotected PackedObjectLoader getBaseLoader() throws IOException {\n-\t\tfinal PackedObjectLoader or = pack.get(deltaBase);\n+\t\tfinal PackedObjectLoader or = pack.get(curs, deltaBase);\n \t\tif (or == null)\n \t\t\tthrow new MissingObjectException(deltaBase, \"delta base\");\n \t\treturn or;\ndiff --git a/org.spearce.jgit/src/org/spearce/jgit/lib/PackFile.java b/org.spearce.jgit/src/org/spearce/jgit/lib/PackFile.java\nindex 8ebd440..6cd85b1 100644\n--- a/org.spearce.jgit/src/org/spearce/jgit/lib/PackFile.java\n+++ b/org.spearce.jgit/src/org/spearce/jgit/lib/PackFile.java\n@@ -120,7 +120,12 @@ public boolean hasObject(final AnyObjectId id) {\n \t *             the pack file or the index could not be read.\n \t */\n \tpublic PackedObjectLoader get(final AnyObjectId id) throws IOException {\n-\t\treturn get(new WindowCursor(), id);\n+\t\tfinal WindowCursor wc = new WindowCursor();\n+\t\ttry {\n+\t\t\treturn get(wc, id);\n+\t\t} finally {\n+\t\t\twc.release();\n+\t\t}\n \t}\n \n \t/**\ndiff --git a/org.spearce.jgit/src/org/spearce/jgit/lib/Repository.java b/org.spearce.jgit/src/org/spearce/jgit/lib/Repository.java\nindex a319c00..ff36a3d 100644\n--- a/org.spearce.jgit/src/org/spearce/jgit/lib/Repository.java\n+++ b/org.spearce.jgit/src/org/spearce/jgit/lib/Repository.java\n@@ -251,7 +251,12 @@ public boolean hasObject(final AnyObjectId objectId) {\n \t */\n \tpublic ObjectLoader openObject(final AnyObjectId id)\n \t\t\tthrows IOException {\n-\t\treturn openObject(new WindowCursor(),id);\n+\t\tfinal WindowCursor wc = new WindowCursor();\n+\t\ttry {\n+\t\t\treturn openObject(wc, id);\n+\t\t} finally {\n+\t\t\twc.release();\n+\t\t}\n \t}\n \n \t/**\ndiff --git a/org.spearce.jgit/src/org/spearce/jgit/lib/WindowCursor.java b/org.spearce.jgit/src/org/spearce/jgit/lib/WindowCursor.java\nindex 0f4dab9..7aad081 100644\n--- a/org.spearce.jgit/src/org/spearce/jgit/lib/WindowCursor.java\n+++ b/org.spearce.jgit/src/org/spearce/jgit/lib/WindowCursor.java\n@@ -46,6 +46,8 @@\n \t/** Temporary buffer large enough for at least one raw object id. */\n \tfinal byte[] tempId = new byte[Constants.OBJECT_ID_LENGTH];\n \n+\tprivate Inflater inf;\n+\t\n \tByteWindow window;\n \n \tObject handle;\n@@ -98,16 +100,8 @@ int copy(final WindowedFile provider, long position, final byte[] dstbuf,\n \t *            data to.\n \t * @param dstoff\n \t *            current offset within <code>dstbuf</code> to inflate into.\n-\t * @param inf\n-\t *            the inflater to feed input to. The caller is responsible for\n-\t *            initializing the inflater as multiple windows may need to\n-\t *            supply data to the same inflater to completely decompress\n-\t *            something.\n \t * @return updated <code>dstoff</code> based on the number of bytes\n-\t *         successfully copied into <code>dstbuf</code> by\n-\t *         <code>inf</code>. If the inflater is not yet finished then\n-\t *         another window's data must still be supplied as input to finish\n-\t *         decompression.\n+\t *         successfully inflated into <code>dstbuf</code>.\n \t * @throws IOException\n \t *             this cursor does not match the provider or id and the proper\n \t *             window could not be acquired through the provider's cache.\n@@ -116,8 +110,12 @@ int copy(final WindowedFile provider, long position, final byte[] dstbuf,\n \t *             stream corruption is likely.\n \t */\n \tint inflate(final WindowedFile provider, long position,\n-\t\t\tfinal byte[] dstbuf, int dstoff, final Inflater inf)\n+\t\t\tfinal byte[] dstbuf, int dstoff)\n \t\t\tthrows IOException, DataFormatException {\n+\t\tif (inf == null)\n+\t\t\tinf = InflaterCache.get();\n+\t\telse\n+\t\t\tinf.reset();\n \t\tfor (;;) {\n \t\t\tpin(provider, position);\n \t\t\tdstoff = window.inflate(handle, position, dstbuf, dstoff, inf);\n@@ -138,5 +136,20 @@ private void pin(final WindowedFile provider, final long position)\n \tpublic void release() {\n \t\twindow = null;\n \t\thandle = null;\n+\t\ttry {\n+\t\t\tInflaterCache.release(inf);\n+\t\t} finally {\n+\t\t\tinf = null;\n+\t\t}\n+\t}\n+\n+\t/**\n+\t * @param curs cursor to release; may be null.\n+\t * @return always null.\n+\t */\n+\tpublic static WindowCursor release(final WindowCursor curs) {\n+\t\tif (curs != null)\n+\t\t\tcurs.release();\n+\t\treturn null;\n \t}\n }\ndiff --git a/org.spearce.jgit/src/org/spearce/jgit/lib/WindowedFile.java b/org.spearce.jgit/src/org/spearce/jgit/lib/WindowedFile.java\nindex f28524f..454f98b 100644\n--- a/org.spearce.jgit/src/org/spearce/jgit/lib/WindowedFile.java\n+++ b/org.spearce.jgit/src/org/spearce/jgit/lib/WindowedFile.java\n@@ -45,7 +45,6 @@\n import java.nio.MappedByteBuffer;\n import java.nio.channels.FileChannel.MapMode;\n import java.util.zip.DataFormatException;\n-import java.util.zip.Inflater;\n \n /**\n  * Read-only cached file access.\n@@ -240,13 +239,8 @@ public void copyToStream(long position, final byte[] buf, long cnt,\n \n \tvoid readCompressed(final long position, final byte[] dstbuf,\n \t\t\tfinal WindowCursor curs) throws IOException, DataFormatException {\n-\t\tfinal Inflater inf = InflaterCache.get();\n-\t\ttry {\n-\t\t\tif (curs.inflate(this, position, dstbuf, 0, inf) != dstbuf.length)\n-\t\t\t\tthrow new EOFException(\"Short compressed stream at \" + position);\n-\t\t} finally {\n-\t\t\tInflaterCache.release(inf);\n-\t\t}\n+\t\tif (curs.inflate(this, position, dstbuf, 0) != dstbuf.length)\n+\t\t\tthrow new EOFException(\"Short compressed stream at \" + position);\n \t}\n \n \t/**\ndiff --git a/org.spearce.jgit/src/org/spearce/jgit/revwalk/RevWalk.java b/org.spearce.jgit/src/org/spearce/jgit/revwalk/RevWalk.java\nindex d7e4c58..b1571ab 100644\n--- a/org.spearce.jgit/src/org/spearce/jgit/revwalk/RevWalk.java\n+++ b/org.spearce.jgit/src/org/spearce/jgit/revwalk/RevWalk.java\n@@ -651,7 +651,7 @@ else if (!(c instanceof RevTree))\n \n \t\tif ((t.flags & PARSED) != 0)\n \t\t\treturn t;\n-\t\tfinal ObjectLoader ldr = db.openObject(t);\n+\t\tfinal ObjectLoader ldr = db.openObject(curs, t);\n \t\tif (ldr == null)\n \t\t\tthrow new MissingObjectException(t, Constants.TYPE_TREE);\n \t\tif (ldr.getType() != Constants.OBJ_TREE)\n@@ -680,7 +680,7 @@ public RevObject parseAny(final AnyObjectId id)\n \t\t\tthrows MissingObjectException, IOException {\n \t\tRevObject r = objects.get(id);\n \t\tif (r == null) {\n-\t\t\tfinal ObjectLoader ldr = db.openObject(id);\n+\t\t\tfinal ObjectLoader ldr = db.openObject(curs, id);\n \t\t\tif (ldr == null)\n \t\t\t\tthrow new MissingObjectException(id.toObjectId(), \"unknown\");\n \t\t\tfinal byte[] data = ldr.getCachedBytes();\ndiff --git a/org.spearce.jgit/src/org/spearce/jgit/transport/IndexPack.java b/org.spearce.jgit/src/org/spearce/jgit/transport/IndexPack.java\nindex 3e2187c..82cd615 100644\n--- a/org.spearce.jgit/src/org/spearce/jgit/transport/IndexPack.java\n+++ b/org.spearce.jgit/src/org/spearce/jgit/transport/IndexPack.java\n@@ -68,6 +68,7 @@\n import org.spearce.jgit.lib.PackIndexWriter;\n import org.spearce.jgit.lib.ProgressMonitor;\n import org.spearce.jgit.lib.Repository;\n+import org.spearce.jgit.lib.WindowCursor;\n import org.spearce.jgit.util.NB;\n \n /** Indexes Git pack files for local use. */\n@@ -173,6 +174,8 @@ public static IndexPack create(final Repository db, final InputStream is)\n \t/** If {@link #fixThin} this is the last byte of the original checksum. */\n \tprivate long originalEOF;\n \n+\tprivate WindowCursor readCurs;\n+\n \t/**\n \t * Create a new pack indexer utility.\n \t * \n@@ -189,6 +192,7 @@ public IndexPack(final Repository db, final InputStream src,\n \t\trepo = db;\n \t\tin = src;\n \t\tinflater = InflaterCache.get();\n+\t\treadCurs = new WindowCursor();\n \t\tbuf = new byte[BUFFER_SIZE];\n \t\tobjectData = new byte[BUFFER_SIZE];\n \t\tobjectDigest = Constants.newMessageDigest();\n@@ -325,6 +329,7 @@ public void index(final ProgressMonitor progress) throws IOException {\n \t\t\t\t} finally {\n \t\t\t\t\tinflater = null;\n \t\t\t\t}\n+\t\t\t\treadCurs = WindowCursor.release(readCurs);\n \n \t\t\t\tprogress.endTask();\n \t\t\t\tif (packOut != null)\n@@ -461,7 +466,7 @@ private void fixThinPack(final ProgressMonitor progress) throws IOException {\n \t\tfinal Deflater def = new Deflater(Deflater.DEFAULT_COMPRESSION, false);\n \t\tlong end = originalEOF;\n \t\tfor (final ObjectId baseId : new ArrayList<ObjectId>(baseById.keySet())) {\n-\t\t\tfinal ObjectLoader ldr = repo.openObject(baseId);\n+\t\t\tfinal ObjectLoader ldr = repo.openObject(readCurs, baseId);\n \t\t\tif (ldr == null)\n \t\t\t\tcontinue;\n \t\t\tfinal byte[] data = ldr.getBytes();\n@@ -715,7 +720,7 @@ private void verifySafeObject(final AnyObjectId id, final int type,\n \t\t\t}\n \t\t}\n \n-\t\tfinal ObjectLoader ldr = repo.openObject(id);\n+\t\tfinal ObjectLoader ldr = repo.openObject(readCurs, id);\n \t\tif (ldr != null) {\n \t\t\tfinal byte[] existingData = ldr.getCachedBytes();\n \t\t\tif (ldr.getType() != type || !Arrays.equals(data, existingData)) {\n-- \n1.6.1.rc4.301.g5497a\n"},{"id":"98573","messageId":"1229992043-1053-5-git-send-email-spearce@spearce.org","threadId":"16840","inReplyTo":"1229992043-1053-4-git-send-email-spearce@spearce.org","subject":"[JGIT PATCH 04/13] Make RefDatabase thread-safe","fromName":"Shawn O. Pearce","fromEmail":"spearce@spearce.org","sentAt":"2008-12-23T00:27:14Z","receivedAt":"2008-12-23T00:27:14Z","isPatch":true,"sender":{"key":"spearce@spearce.org","avatar":"https://avatars.githubusercontent.com/u/34844?v=4"},"body":"This is necessary to support a thread-safe Repository.\n\nSigned-off-by: Shawn O. Pearce <spearce@spearce.org>\n---\n .../src/org/spearce/jgit/lib/RefDatabase.java      |   29 +++++++++++---------\n 1 files changed, 16 insertions(+), 13 deletions(-)\n\ndiff --git a/org.spearce.jgit/src/org/spearce/jgit/lib/RefDatabase.java b/org.spearce.jgit/src/org/spearce/jgit/lib/RefDatabase.java\nindex 4cf6e08..87f26bf 100644\n--- a/org.spearce.jgit/src/org/spearce/jgit/lib/RefDatabase.java\n+++ b/org.spearce.jgit/src/org/spearce/jgit/lib/RefDatabase.java\n@@ -93,7 +93,7 @@ RefDatabase(final Repository r) {\n \t\tclearCache();\n \t}\n \n-\tvoid clearCache() {\n+\tsynchronized void clearCache() {\n \t\tlooseRefs = new HashMap<String, Ref>();\n \t\tlooseRefsMTime = new HashMap<String, Long>();\n \t\tpackedRefs = new HashMap<String, Ref>();\n@@ -139,9 +139,11 @@ RefUpdate newUpdate(final String name) throws IOException {\n \t}\n \n \tvoid stored(final String origName, final String name, final ObjectId id, final long time) {\n-\t\tlooseRefs.put(name, new Ref(Ref.Storage.LOOSE, origName, name, id));\n-\t\tlooseRefsMTime.put(name, time);\n-\t\tsetModified();\n+\t\tsynchronized (this) {\n+\t\t\tlooseRefs.put(name, new Ref(Ref.Storage.LOOSE, origName, name, id));\n+\t\t\tlooseRefsMTime.put(name, time);\n+\t\t\tsetModified();\n+\t\t}\n \t\tdb.fireRefsMaybeChanged();\n \t}\n \n@@ -157,11 +159,13 @@ void stored(final String origName, final String name, final ObjectId id, final l\n \tvoid link(final String name, final String target) throws IOException {\n \t\tfinal byte[] content = Constants.encode(\"ref: \" + target + \"\\n\");\n \t\tlockAndWriteFile(fileForRef(name), content);\n-\t\tsetModified();\n+\t\tsynchronized (this) {\n+\t\t\tsetModified();\n+\t\t}\n \t\tdb.fireRefsMaybeChanged();\n \t}\n \n-\tvoid setModified() {\n+\tprivate void setModified() {\n \t\tlastRefModification = refModificationCounter++;\n \t}\n \n@@ -210,7 +214,7 @@ Ref readRef(final String partialName) throws IOException {\n \t\treturn avail;\n \t}\n \n-\tprivate void readPackedRefs(final Map<String, Ref> avail) {\n+\tprivate synchronized void readPackedRefs(final Map<String, Ref> avail) {\n \t\trefreshPackedRefs();\n \t\tavail.putAll(packedRefs);\n \t}\n@@ -229,7 +233,7 @@ private void readLooseRefs(final Map<String, Ref> avail,\n \t\t}\n \t}\n \n-\tprivate void readOneLooseRef(final Map<String, Ref> avail,\n+\tprivate synchronized void readOneLooseRef(final Map<String, Ref> avail,\n \t\t\tfinal String origName, final String refName, final File ent) {\n \t\t// Unchanged and cached? Don't read it again.\n \t\t//\n@@ -323,8 +327,8 @@ private Ref readRefBasic(final String name, final int depth) throws IOException \n \t\treturn readRefBasic(name, name, depth);\n \t}\n \n-\tprivate Ref readRefBasic(final String origName, final String name, final int depth)\n-\t\t\tthrows IOException {\n+\tprivate synchronized Ref readRefBasic(final String origName,\n+\t\t\tfinal String name, final int depth) throws IOException {\n \t\t// Prefer loose ref to packed ref as the loose\n \t\t// file can be more up-to-date than a packed one.\n \t\t//\n@@ -408,7 +412,7 @@ private Ref readRefBasic(final String origName, final String name, final int dep\n \t\treturn ref;\n \t}\n \n-\tprivate void refreshPackedRefs() {\n+\tprivate synchronized void refreshPackedRefs() {\n \t\tfinal long currTime = packedRefsFile.lastModified();\n \t\tfinal long currLen = currTime == 0 ? 0 : packedRefsFile.length();\n \t\tif (currTime == packedRefsLastModified && currLen == packedRefsLength)\n@@ -479,7 +483,7 @@ private void lockAndWriteFile(File file, byte[] content) throws IOException {\n \t\t\tthrow new ObjectWritingException(\"Unable to write \" + name);\n \t}\n \n-\tvoid removePackedRef(String name) throws IOException {\n+\tsynchronized void removePackedRef(String name) throws IOException {\n \t\tpackedRefs.remove(name);\n \t\twritePackedRefs();\n \t}\n@@ -508,5 +512,4 @@ private static BufferedReader openReader(final File fileLocation)\n \t\treturn new BufferedReader(new InputStreamReader(new FileInputStream(\n \t\t\t\tfileLocation), Constants.CHARSET));\n \t}\n-\n }\n-- \n1.6.1.rc4.301.g5497a\n"},{"id":"98583","messageId":"1229992043-1053-6-git-send-email-spearce@spearce.org","threadId":"16840","inReplyTo":"1229992043-1053-5-git-send-email-spearce@spearce.org","subject":"[JGIT PATCH 05/13] Make PackFile thread-safe","fromName":"Shawn O. Pearce","fromEmail":"spearce@spearce.org","sentAt":"2008-12-23T00:27:15Z","receivedAt":"2008-12-23T00:27:15Z","isPatch":true,"sender":{"key":"spearce@spearce.org","avatar":"https://avatars.githubusercontent.com/u/34844?v=4"},"body":"We really should try to avoid allocating the reverse index within\nthe pack object itself, and instead make the applications that do\nuse this data manage it themselves.  This way the index could be\nreleased in memory when its no longer used, and the locking could\nbe bypassed entirely.\n\nSince most of PackFile is dependent upon the WindowedFile we can\nmostly ignore synchronization in this class.\n\nSigned-off-by: Shawn O. Pearce <spearce@spearce.org>\n---\n .../src/org/spearce/jgit/lib/PackFile.java         |    2 +-\n 1 files changed, 1 insertions(+), 1 deletions(-)\n\ndiff --git a/org.spearce.jgit/src/org/spearce/jgit/lib/PackFile.java b/org.spearce.jgit/src/org/spearce/jgit/lib/PackFile.java\nindex 6cd85b1..ca5681b 100644\n--- a/org.spearce.jgit/src/org/spearce/jgit/lib/PackFile.java\n+++ b/org.spearce.jgit/src/org/spearce/jgit/lib/PackFile.java\n@@ -341,7 +341,7 @@ private long findEndOffset(final long startOffset)\n \t\treturn getReverseIdx().findNextOffset(startOffset, maxOffset);\n \t}\n \n-\tprivate PackReverseIndex getReverseIdx() {\n+\tprivate synchronized PackReverseIndex getReverseIdx() {\n \t\tif (reverseIdx == null)\n \t\t\treverseIdx = new PackReverseIndex(idx);\n \t\treturn reverseIdx;\n-- \n1.6.1.rc4.301.g5497a\n"},{"id":"98582","messageId":"1229992043-1053-7-git-send-email-spearce@spearce.org","threadId":"16840","inReplyTo":"1229992043-1053-6-git-send-email-spearce@spearce.org","subject":"[JGIT PATCH 06/13] Make Repository thread-safe","fromName":"Shawn O. Pearce","fromEmail":"spearce@spearce.org","sentAt":"2008-12-23T00:27:16Z","receivedAt":"2008-12-23T00:27:16Z","isPatch":true,"sender":{"key":"spearce@spearce.org","avatar":"https://avatars.githubusercontent.com/u/34844?v=4"},"body":"The Repository class itself is now thread-safe.  Thread safety is\nensured by copying values onto the caller stack prior to use.\n\nSigned-off-by: Shawn O. Pearce <spearce@spearce.org>\n---\n .../src/org/spearce/jgit/lib/Repository.java       |   75 ++++++++++++-------\n 1 files changed, 47 insertions(+), 28 deletions(-)\n\ndiff --git a/org.spearce.jgit/src/org/spearce/jgit/lib/Repository.java b/org.spearce.jgit/src/org/spearce/jgit/lib/Repository.java\nindex ff36a3d..89ad991 100644\n--- a/org.spearce.jgit/src/org/spearce/jgit/lib/Repository.java\n+++ b/org.spearce.jgit/src/org/spearce/jgit/lib/Repository.java\n@@ -46,6 +46,7 @@\n import java.io.FilenameFilter;\n import java.io.IOException;\n import java.util.ArrayList;\n+import java.util.Arrays;\n import java.util.Collection;\n import java.util.Collections;\n import java.util.HashMap;\n@@ -79,20 +80,22 @@\n  * \t</ul>\n  * </li>\n  * </ul>\n- *\n+ * <p>\n+ * This class is thread-safe.\n+ * <p>\n  * This implementation only handles a subtly undocumented subset of git features.\n  *\n  */\n public class Repository {\n \tprivate final File gitDir;\n \n-\tprivate final File[] objectsDirs;\n-\n \tprivate final RepositoryConfig config;\n \n \tprivate final RefDatabase refs;\n \n-\tprivate PackFile[] packs;\n+\tprivate File[] objectDirectoryList;\n+\n+\tprivate PackFile[] packFileList;\n \n \tprivate GitIndex index;\n \n@@ -111,18 +114,19 @@\n \tpublic Repository(final File d) throws IOException {\n \t\tgitDir = d.getAbsoluteFile();\n \t\ttry {\n-\t\t\tobjectsDirs = readObjectsDirs(FS.resolve(gitDir, \"objects\"),\n-\t\t\t\t\tnew ArrayList<File>()).toArray(new File[0]);\n+\t\t\tobjectDirectoryList = readObjectsDirs(\n+\t\t\t\t\tFS.resolve(gitDir, \"objects\"), new ArrayList<File>())\n+\t\t\t\t\t.toArray(new File[0]);\n \t\t} catch (IOException e) {\n \t\t\tIOException ex = new IOException(\"Cannot find all object dirs for \" + gitDir);\n \t\t\tex.initCause(e);\n \t\t\tthrow ex;\n \t\t}\n \t\trefs = new RefDatabase(this);\n-\t\tpacks = new PackFile[0];\n+\t\tpackFileList = new PackFile[0];\n \t\tconfig = new RepositoryConfig(this);\n \n-\t\tfinal boolean isExisting = objectsDirs[0].exists();\n+\t\tfinal boolean isExisting = objectDirectoryList[0].exists();\n \t\tif (isExisting) {\n \t\t\tgetConfig().load();\n \t\t\tfinal String repositoryFormatVersion = getConfig().getString(\n@@ -138,7 +142,8 @@ public Repository(final File d) throws IOException {\n \t\t\tscanForPacks();\n \t}\n \n-\tprivate Collection<File> readObjectsDirs(File objectsDir, Collection<File> ret) throws IOException {\n+\tprivate static Collection<File> readObjectsDirs(File objectsDir,\n+\t\t\tCollection<File> ret) throws IOException {\n \t\tret.add(objectsDir);\n \t\tfinal File altFile = FS.resolve(objectsDir, \"info/alternates\");\n \t\tif (altFile.exists()) {\n@@ -160,7 +165,7 @@ public Repository(final File d) throws IOException {\n \t *\n \t * @throws IOException\n \t */\n-\tpublic void create() throws IOException {\n+\tpublic synchronized void create() throws IOException {\n \t\tif (gitDir.exists()) {\n \t\t\tthrow new IllegalStateException(\"Repository already exists: \"\n \t\t\t\t\t+ gitDir);\n@@ -169,9 +174,9 @@ public void create() throws IOException {\n \t\tgitDir.mkdirs();\n \t\trefs.create();\n \n-\t\tobjectsDirs[0].mkdirs();\n-\t\tnew File(objectsDirs[0], \"pack\").mkdir();\n-\t\tnew File(objectsDirs[0], \"info\").mkdir();\n+\t\tobjectDirectoryList[0].mkdirs();\n+\t\tnew File(objectDirectoryList[0], \"pack\").mkdir();\n+\t\tnew File(objectDirectoryList[0], \"info\").mkdir();\n \n \t\tnew File(gitDir, \"branches\").mkdir();\n \t\tnew File(gitDir, \"remotes\").mkdir();\n@@ -182,6 +187,14 @@ public void create() throws IOException {\n \t\tgetConfig().save();\n \t}\n \n+\tprivate synchronized File[] objectsDirs(){\n+\t\treturn objectDirectoryList;\n+\t}\n+\n+\tprivate synchronized PackFile[] packs(){\n+\t\treturn packFileList;\n+\t}\n+\n \t/**\n \t * @return GIT_DIR\n \t */\n@@ -193,7 +206,7 @@ public File getDirectory() {\n \t * @return the directory containing the objects owned by this repository.\n \t */\n \tpublic File getObjectsDirectory() {\n-\t\treturn objectsDirs[0];\n+\t\treturn objectsDirs()[0];\n \t}\n \n \t/**\n@@ -217,6 +230,7 @@ public File toFile(final AnyObjectId objectId) {\n \t\tfinal String n = objectId.name();\n \t\tString d=n.substring(0, 2);\n \t\tString f=n.substring(2);\n+\t\tfinal File[] objectsDirs = objectsDirs();\n \t\tfor (int i=0; i<objectsDirs.length; ++i) {\n \t\t\tFile ret = new File(new File(objectsDirs[i], d), f);\n \t\t\tif (ret.exists())\n@@ -231,6 +245,7 @@ public File toFile(final AnyObjectId objectId) {\n \t *         known shared repositories.\n \t */\n \tpublic boolean hasObject(final AnyObjectId objectId) {\n+\t\tfinal PackFile[] packs = packs();\n \t\tint k = packs.length;\n \t\tif (k > 0) {\n \t\t\tdo {\n@@ -271,6 +286,7 @@ public ObjectLoader openObject(final AnyObjectId id)\n \t */\n \tpublic ObjectLoader openObject(final WindowCursor curs, final AnyObjectId id)\n \t\t\tthrows IOException {\n+\t\tfinal PackFile[] packs = packs();\n \t\tint k = packs.length;\n \t\tif (k > 0) {\n \t\t\tdo {\n@@ -341,7 +357,7 @@ public ObjectLoader openObject(final WindowCursor curs, final AnyObjectId id)\n \tvoid openObjectInAllPacks(final AnyObjectId objectId,\n \t\t\tfinal Collection<PackedObjectLoader> resultLoaders,\n \t\t\tfinal WindowCursor curs) throws IOException {\n-\t\tfor (PackFile pack : packs) {\n+\t\tfor (PackFile pack : packs()) {\n \t\t\tfinal PackedObjectLoader loader = pack.get(curs, objectId);\n \t\t\tif (loader != null)\n \t\t\t\tresultLoaders.add(loader);\n@@ -766,11 +782,10 @@ public void close() {\n \t\tclosePacks();\n \t}\n \n-\tvoid closePacks() {\n-\t\tfor (int k = packs.length - 1; k >= 0; k--) {\n-\t\t\tpacks[k].close();\n-\t\t}\n-\t\tpacks = new PackFile[0];\n+\tsynchronized void closePacks() {\n+\t\tfor (int k = packFileList.length - 1; k >= 0; k--)\n+\t\t\tpackFileList[k].close();\n+\t\tpackFileList = new PackFile[0];\n \t}\n \n \t/**\n@@ -795,11 +810,13 @@ public void openPack(final File pack, final File idx) throws IOException {\n \t\t\tthrow new IllegalArgumentException(\"Pack \" + pack\n \t\t\t\t\t+ \"does not match index \" + idx);\n \n-\t\tfinal PackFile[] cur = packs;\n-\t\tfinal PackFile[] arr = new PackFile[cur.length + 1];\n-\t\tSystem.arraycopy(cur, 0, arr, 1, cur.length);\n-\t\tarr[0] = new PackFile(this, idx, pack);\n-\t\tpacks = arr;\n+\t\tsynchronized (this) {\n+\t\t\tfinal PackFile[] cur = packFileList;\n+\t\t\tfinal PackFile[] arr = new PackFile[cur.length + 1];\n+\t\t\tSystem.arraycopy(cur, 0, arr, 1, cur.length);\n+\t\t\tarr[0] = new PackFile(this, idx, pack);\n+\t\t\tpackFileList = arr;\n+\t\t}\n \t}\n \n \t/**\n@@ -808,11 +825,13 @@ public void openPack(final File pack, final File idx) throws IOException {\n \t */\n \tpublic void scanForPacks() {\n \t\tfinal ArrayList<PackFile> p = new ArrayList<PackFile>();\n-\t\tfor (int i=0; i<objectsDirs.length; ++i)\n-\t\t\tscanForPacks(new File(objectsDirs[i], \"pack\"), p);\n+\t\tfor (final File d : objectsDirs())\n+\t\t\tscanForPacks(new File(d, \"pack\"), p);\n \t\tfinal PackFile[] arr = new PackFile[p.size()];\n \t\tp.toArray(arr);\n-\t\tpacks = arr;\n+\t\tsynchronized (this) {\n+\t\t\tpackFileList = arr;\n+\t\t}\n \t}\n \n \tprivate void scanForPacks(final File packDir, Collection<PackFile> packList) {\n-- \n1.6.1.rc4.301.g5497a\n"},{"id":"98576","messageId":"1229992043-1053-8-git-send-email-spearce@spearce.org","threadId":"16840","inReplyTo":"1229992043-1053-7-git-send-email-spearce@spearce.org","subject":"[JGIT PATCH 07/13] Don't open a PackFile multiple times on scanForPacks","fromName":"Shawn O. Pearce","fromEmail":"spearce@spearce.org","sentAt":"2008-12-23T00:27:17Z","receivedAt":"2008-12-23T00:27:17Z","isPatch":true,"sender":{"key":"spearce@spearce.org","avatar":"https://avatars.githubusercontent.com/u/34844?v=4"},"body":"If we already have a given PackFile open in this repository then\nwe should reuse the existing PackFile handle.\n\nSigned-off-by: Shawn O. Pearce <spearce@spearce.org>\n---\n .../src/org/spearce/jgit/lib/PackFile.java         |    5 +++++\n .../src/org/spearce/jgit/lib/Repository.java       |    8 +++++++-\n .../src/org/spearce/jgit/lib/WindowedFile.java     |    7 ++++++-\n 3 files changed, 18 insertions(+), 2 deletions(-)\n\ndiff --git a/org.spearce.jgit/src/org/spearce/jgit/lib/PackFile.java b/org.spearce.jgit/src/org/spearce/jgit/lib/PackFile.java\nindex ca5681b..fc1b6ea 100644\n--- a/org.spearce.jgit/src/org/spearce/jgit/lib/PackFile.java\n+++ b/org.spearce.jgit/src/org/spearce/jgit/lib/PackFile.java\n@@ -94,6 +94,11 @@ final PackedObjectLoader resolveBase(final WindowCursor curs, final long ofs)\n \t\treturn reader(curs, ofs);\n \t}\n \n+\t/** @return the File object which locates this pack on disk. */\n+\tpublic File getPackFile() {\n+\t\treturn pack.getFile();\n+\t}\n+\n \t/**\n \t * Determine if an object is contained within the pack file.\n \t * <p>\ndiff --git a/org.spearce.jgit/src/org/spearce/jgit/lib/Repository.java b/org.spearce.jgit/src/org/spearce/jgit/lib/Repository.java\nindex 89ad991..8f6e96e 100644\n--- a/org.spearce.jgit/src/org/spearce/jgit/lib/Repository.java\n+++ b/org.spearce.jgit/src/org/spearce/jgit/lib/Repository.java\n@@ -825,6 +825,7 @@ synchronized (this) {\n \t */\n \tpublic void scanForPacks() {\n \t\tfinal ArrayList<PackFile> p = new ArrayList<PackFile>();\n+\t\tp.addAll(Arrays.asList(packs()));\n \t\tfor (final File d : objectsDirs())\n \t\t\tscanForPacks(new File(d, \"pack\"), p);\n \t\tfinal PackFile[] arr = new PackFile[p.size()];\n@@ -843,7 +844,7 @@ public boolean accept(final File baseDir, final String n) {\n \t\t\t}\n \t\t});\n \t\tif (idxList != null) {\n-\t\t\tfor (final String indexName : idxList) {\n+\t\t\tSCAN: for (final String indexName : idxList) {\n \t\t\t\tfinal String n = indexName.substring(0, indexName.length() - 4);\n \t\t\t\tfinal File idxFile = new File(packDir, n + \".idx\");\n \t\t\t\tfinal File packFile = new File(packDir, n + \".pack\");\n@@ -856,6 +857,11 @@ public boolean accept(final File baseDir, final String n) {\n \t\t\t\t\tcontinue;\n \t\t\t\t}\n \n+\t\t\t\tfor (final PackFile p : packList) {\n+\t\t\t\t\tif (packFile.equals(p.getPackFile()))\n+\t\t\t\t\t\tcontinue SCAN;\n+\t\t\t\t}\n+\n \t\t\t\ttry {\n \t\t\t\t\tpackList.add(new PackFile(this, idxFile, packFile));\n \t\t\t\t} catch (IOException ioe) {\ndiff --git a/org.spearce.jgit/src/org/spearce/jgit/lib/WindowedFile.java b/org.spearce.jgit/src/org/spearce/jgit/lib/WindowedFile.java\nindex 454f98b..f640c42 100644\n--- a/org.spearce.jgit/src/org/spearce/jgit/lib/WindowedFile.java\n+++ b/org.spearce.jgit/src/org/spearce/jgit/lib/WindowedFile.java\n@@ -101,13 +101,18 @@ public long length() {\n \t\treturn length;\n \t}\n \n+\t/** @return the absolute file object this file reads from. */\n+\tpublic File getFile() {\n+\t\treturn fPath.getAbsoluteFile();\n+\t}\n+\n \t/**\n \t * Get the path name of this file.\n \t * \n \t * @return the absolute path name of the file.\n \t */\n \tpublic String getName() {\n-\t\treturn fPath.getAbsolutePath();\n+\t\treturn getFile().getPath();\n \t}\n \n \t/**\n-- \n1.6.1.rc4.301.g5497a\n"},{"id":"98579","messageId":"1229992043-1053-9-git-send-email-spearce@spearce.org","threadId":"16840","inReplyTo":"1229992043-1053-8-git-send-email-spearce@spearce.org","subject":"[JGIT PATCH 08/13] Expose RepositoryConfig.getBoolean so applications can use it","fromName":"Shawn O. Pearce","fromEmail":"spearce@spearce.org","sentAt":"2008-12-23T00:27:18Z","receivedAt":"2008-12-23T00:27:18Z","isPatch":true,"sender":{"key":"spearce@spearce.org","avatar":"https://avatars.githubusercontent.com/u/34844?v=4"},"body":"There's no reason for these to be hidden from application level code.\nWe expose getInt because it is a popular type; getBoolean should also\nbe exposed because it is also a popular type.\n\nSigned-off-by: Shawn O. Pearce <spearce@spearce.org>\n---\n .../src/org/spearce/jgit/lib/RepositoryConfig.java |    4 ++--\n 1 files changed, 2 insertions(+), 2 deletions(-)\n\ndiff --git a/org.spearce.jgit/src/org/spearce/jgit/lib/RepositoryConfig.java b/org.spearce.jgit/src/org/spearce/jgit/lib/RepositoryConfig.java\nindex 3291bba..7df90cd 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@@ -210,7 +210,7 @@ public int getInt(final String section, String subsection,\n \t * @return true if any value or defaultValue is true, false for missing or\n \t *         explicit false\n \t */\n-\tprotected boolean getBoolean(final String section, final String name,\n+\tpublic boolean getBoolean(final String section, final String name,\n \t\t\tfinal boolean defaultValue) {\n \t\treturn getBoolean(section, null, name, defaultValue);\n \t}\n@@ -229,7 +229,7 @@ protected boolean getBoolean(final String section, final String name,\n \t * @return true if any value or defaultValue is true, false for missing or\n \t *         explicit false\n \t */\n-\tprotected boolean getBoolean(final String section, String subsection,\n+\tpublic boolean getBoolean(final String section, String subsection,\n \t\t\tfinal String name, final boolean defaultValue) {\n \t\tString n = getRawString(section, subsection, name);\n \t\tif (n == null)\n-- \n1.6.1.rc4.301.g5497a\n"},{"id":"98577","messageId":"1229992043-1053-10-git-send-email-spearce@spearce.org","threadId":"16840","inReplyTo":"1229992043-1053-9-git-send-email-spearce@spearce.org","subject":"[JGIT PATCH 09/13] Add AnyObjectId.copyTo(StringBuilder)","fromName":"Shawn O. Pearce","fromEmail":"spearce@spearce.org","sentAt":"2008-12-23T00:27:19Z","receivedAt":"2008-12-23T00:27:19Z","isPatch":true,"sender":{"key":"spearce@spearce.org","avatar":"https://avatars.githubusercontent.com/u/34844?v=4"},"body":"This makes it easier to append an ObjectId onto a StringBuilder.\n\nSigned-off-by: Shawn O. Pearce <spearce@spearce.org>\n---\n .../src/org/spearce/jgit/lib/AnyObjectId.java      |   17 ++++++++++++++++-\n 1 files changed, 16 insertions(+), 1 deletions(-)\n\ndiff --git a/org.spearce.jgit/src/org/spearce/jgit/lib/AnyObjectId.java b/org.spearce.jgit/src/org/spearce/jgit/lib/AnyObjectId.java\nindex 943d916..3ec00ea 100644\n--- a/org.spearce.jgit/src/org/spearce/jgit/lib/AnyObjectId.java\n+++ b/org.spearce.jgit/src/org/spearce/jgit/lib/AnyObjectId.java\n@@ -387,7 +387,22 @@ public void copyTo(final Writer w) throws IOException {\n \t */\n \tpublic void copyTo(final char[] tmp, final Writer w) throws IOException {\n \t\ttoHexCharArray(tmp);\n-\t\tw.write(tmp);\n+\t\tw.write(tmp, 0, STR_LEN);\n+\t}\n+\n+\t/**\n+\t * Copy this ObjectId to a StringBuilder in hex format.\n+\t * \n+\t * @param tmp\n+\t *            temporary char array to buffer construct into before writing.\n+\t *            Must be at least large enough to hold 2 digits for each byte\n+\t *            of object id (40 characters or larger).\n+\t * @param w\n+\t *            the string to append onto.\n+\t */\n+\tpublic void copyTo(final char[] tmp, final StringBuilder w) {\n+\t\ttoHexCharArray(tmp);\n+\t\tw.append(tmp, 0, STR_LEN);\n \t}\n \n \tprivate char[] toHexCharArray() {\n-- \n1.6.1.rc4.301.g5497a\n"},{"id":"98578","messageId":"1229992043-1053-11-git-send-email-spearce@spearce.org","threadId":"16840","inReplyTo":"1229992043-1053-10-git-send-email-spearce@spearce.org","subject":"[JGIT PATCH 10/13] Add compare-and-swap semantics to RefUpdate","fromName":"Shawn O. Pearce","fromEmail":"spearce@spearce.org","sentAt":"2008-12-23T00:27:20Z","receivedAt":"2008-12-23T00:27:20Z","isPatch":true,"sender":{"key":"spearce@spearce.org","avatar":"https://avatars.githubusercontent.com/u/34844?v=4"},"body":"This permits the caller to set the value it expects to find in the\nRef upon obtaining the update lock.  If the ref value doesn't match\nthen the update is aborted with Result.LOCK_FAILURE, so the caller\ncan recover gracefully without making unexpected changes.\n\nSigned-off-by: Shawn O. Pearce <spearce@spearce.org>\n---\n .../src/org/spearce/jgit/lib/RefUpdate.java        |   30 ++++++++++++++++++++\n 1 files changed, 30 insertions(+), 0 deletions(-)\n\ndiff --git a/org.spearce.jgit/src/org/spearce/jgit/lib/RefUpdate.java b/org.spearce.jgit/src/org/spearce/jgit/lib/RefUpdate.java\nindex 235c2fd..0c9ce91 100644\n--- a/org.spearce.jgit/src/org/spearce/jgit/lib/RefUpdate.java\n+++ b/org.spearce.jgit/src/org/spearce/jgit/lib/RefUpdate.java\n@@ -149,6 +149,9 @@\n \t/** Old value of the ref, obtained after we lock it. */\n \tprivate ObjectId oldValue;\n \n+\t/** If non-null, the value {@link #oldValue} must have to continue. */\n+\tprivate ObjectId expValue;\n+\n \t/** Result of the update operation. */\n \tprivate Result result = Result.NOT_ATTEMPTED;\n \n@@ -190,6 +193,27 @@ public void setNewObjectId(final AnyObjectId id) {\n \t}\n \n \t/**\n+\t * @return the expected value of the ref after the lock is taken, but before\n+\t *         update occurs. Null to avoid the compare and swap test. Use\n+\t *         {@link ObjectId#zeroId()} to indicate expectation of a\n+\t *         non-existant ref.\n+\t */\n+\tpublic ObjectId getExpectedOldObjectId() {\n+\t\treturn expValue;\n+\t}\n+\n+\t/**\n+\t * @param id\n+\t *            the expected value of the ref after the lock is taken, but\n+\t *            before update occurs. Null to avoid the compare and swap test.\n+\t *            Use {@link ObjectId#zeroId()} to indicate expectation of a\n+\t *            non-existant ref.\n+\t */\n+\tpublic void setExpectedOldObjectId(final AnyObjectId id) {\n+\t\texpValue = id != null ? id.toObjectId() : null;\n+\t}\n+\n+\t/**\n \t * Check if this update wants to forcefully change the ref.\n \t * \n \t * @return true if this update should ignore merge tests.\n@@ -370,6 +394,12 @@ private Result updateImpl(final RevWalk walk, final Store store)\n \t\t\treturn Result.LOCK_FAILURE;\n \t\ttry {\n \t\t\toldValue = db.idOf(getName());\n+\t\t\tif (expValue != null) {\n+\t\t\t\tfinal ObjectId o;\n+\t\t\t\to = oldValue != null ? oldValue : ObjectId.zeroId();\n+\t\t\t\tif (!expValue.equals(o))\n+\t\t\t\t\treturn Result.LOCK_FAILURE;\n+\t\t\t}\n \t\t\tif (oldValue == null)\n \t\t\t\treturn store.store(lock, Result.NEW);\n \n-- \n1.6.1.rc4.301.g5497a\n"},{"id":"98584","messageId":"1229992043-1053-12-git-send-email-spearce@spearce.org","threadId":"16840","inReplyTo":"1229992043-1053-11-git-send-email-spearce@spearce.org","subject":"[JGIT PATCH 11/13] Allow null new ObjectId during RefUpdate.delete","fromName":"Shawn O. Pearce","fromEmail":"spearce@spearce.org","sentAt":"2008-12-23T00:27:21Z","receivedAt":"2008-12-23T00:27:21Z","isPatch":true,"sender":{"key":"spearce@spearce.org","avatar":"https://avatars.githubusercontent.com/u/34844?v=4"},"body":"If we are deleting a ref we really don't care about what the\nnew ObjectId value should be; it just doesn't matter.  If we\ndidn't set the value we should consider it the same as if we\nhad a MissingObjectException, which means treat the value as\nnull and perform a force update test.\n\nSigned-off-by: Shawn O. Pearce <spearce@spearce.org>\n---\n .../src/org/spearce/jgit/lib/RefUpdate.java        |    2 +-\n 1 files changed, 1 insertions(+), 1 deletions(-)\n\ndiff --git a/org.spearce.jgit/src/org/spearce/jgit/lib/RefUpdate.java b/org.spearce.jgit/src/org/spearce/jgit/lib/RefUpdate.java\nindex 0c9ce91..1417f2c 100644\n--- a/org.spearce.jgit/src/org/spearce/jgit/lib/RefUpdate.java\n+++ b/org.spearce.jgit/src/org/spearce/jgit/lib/RefUpdate.java\n@@ -424,7 +424,7 @@ private Result updateImpl(final RevWalk walk, final Store store)\n \tprivate static RevObject safeParse(final RevWalk rw, final AnyObjectId id)\n \t\t\tthrows IOException {\n \t\ttry {\n-\t\t\treturn rw.parseAny(id);\n+\t\t\treturn id != null ? rw.parseAny(id) : null;\n \t\t} catch (MissingObjectException e) {\n \t\t\t// We can expect some objects to be missing, like if we are\n \t\t\t// trying to force a deletion of a branch and the object it\n-- \n1.6.1.rc4.301.g5497a\n"},{"id":"98580","messageId":"1229992043-1053-13-git-send-email-spearce@spearce.org","threadId":"16840","inReplyTo":"1229992043-1053-12-git-send-email-spearce@spearce.org","subject":"[JGIT PATCH 12/13] Implement the git-receive-pack process in Java","fromName":"Shawn O. Pearce","fromEmail":"spearce@spearce.org","sentAt":"2008-12-23T00:27:22Z","receivedAt":"2008-12-23T00:27:22Z","isPatch":true,"sender":{"key":"spearce@spearce.org","avatar":"https://avatars.githubusercontent.com/u/34844?v=4"},"body":"This implementation provides a basic git-receive-pack service within\nJava.  Two hooks APIs are supported before and after commands are\nexecuted within the connection, allowing daemons to customize the\nbehavior of the updates.\n\nLogic to bind the ReceivePack class to a pipe or network socket is\nomitted, as it depends on the transport.  SSH servers will need a\npure Java SSH implementation such as Apache MINA SSHD.  Anonymous\npush over git:// requires a basic git-daemon functionality.  Local\npipe access might use pure-Java pipes, or System.in/System.out.\n\nSigned-off-by: Shawn O. Pearce <spearce@spearce.org>\n---\n .../org/spearce/jgit/transport/PacketLineIn.java   |   12 +\n .../spearce/jgit/transport/PostReceiveHook.java    |   77 ++\n .../org/spearce/jgit/transport/PreReceiveHook.java |   94 +++\n .../org/spearce/jgit/transport/ReceiveCommand.java |  223 ++++++\n .../org/spearce/jgit/transport/ReceivePack.java    |  793 ++++++++++++++++++++\n 5 files changed, 1199 insertions(+), 0 deletions(-)\n create mode 100644 org.spearce.jgit/src/org/spearce/jgit/transport/PostReceiveHook.java\n create mode 100644 org.spearce.jgit/src/org/spearce/jgit/transport/PreReceiveHook.java\n create mode 100644 org.spearce.jgit/src/org/spearce/jgit/transport/ReceiveCommand.java\n create mode 100644 org.spearce.jgit/src/org/spearce/jgit/transport/ReceivePack.java\n\ndiff --git a/org.spearce.jgit/src/org/spearce/jgit/transport/PacketLineIn.java b/org.spearce.jgit/src/org/spearce/jgit/transport/PacketLineIn.java\nindex f87517d..ef218be 100644\n--- a/org.spearce.jgit/src/org/spearce/jgit/transport/PacketLineIn.java\n+++ b/org.spearce.jgit/src/org/spearce/jgit/transport/PacketLineIn.java\n@@ -111,6 +111,18 @@ String readString() throws IOException {\n \t\treturn RawParseUtils.decode(Constants.CHARSET, raw, 0, len);\n \t}\n \n+\tString readStringNoLF() throws IOException {\n+\t\tint len = readLength();\n+\t\tif (len == 0)\n+\t\t\treturn \"\";\n+\n+\t\tlen -= 4; // length header (4 bytes)\n+\n+\t\tfinal byte[] raw = new byte[len];\n+\t\tNB.readFully(in, raw, 0, len);\n+\t\treturn RawParseUtils.decode(Constants.CHARSET, raw, 0, len);\n+\t}\n+\n \tprivate void readLF() throws IOException {\n \t\tif (in.read() != '\\n')\n \t\t\tthrow new IOException(\"Protocol error: expected LF\");\ndiff --git a/org.spearce.jgit/src/org/spearce/jgit/transport/PostReceiveHook.java b/org.spearce.jgit/src/org/spearce/jgit/transport/PostReceiveHook.java\nnew file mode 100644\nindex 0000000..060efb3\n--- /dev/null\n+++ b/org.spearce.jgit/src/org/spearce/jgit/transport/PostReceiveHook.java\n@@ -0,0 +1,77 @@\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.transport;\n+\n+import java.util.Collection;\n+\n+/**\n+ * Hook invoked by {@link ReceivePack} after all updates are executed.\n+ * <p>\n+ * The hook is called after all commands have been processed. Only commands with\n+ * a status of {@link ReceiveCommand.Result#OK} are passed into the hook. To get\n+ * all commands within the hook, see {@link ReceivePack#getAllCommands()}.\n+ * <p>\n+ * Any post-receive hook implementation should not update the status of a\n+ * command, as the command has already completed or failed, and the status has\n+ * already been returned to the client.\n+ * <p>\n+ * Hooks should execute quickly, as they block the server and the client from\n+ * completing the connection.\n+ */\n+public interface PostReceiveHook {\n+\t/** A simple no-op hook. */\n+\tpublic static final PostReceiveHook NULL = new PostReceiveHook() {\n+\t\tpublic void onPostReceive(final ReceivePack rp,\n+\t\t\t\tfinal Collection<ReceiveCommand> commands) {\n+\t\t\t// Do nothing.\n+\t\t}\n+\t};\n+\n+\t/**\n+\t * Invoked after all commands are executed and status has been returned.\n+\t * \n+\t * @param rp\n+\t *            the process handling the current receive. Hooks may obtain\n+\t *            details about the destination repository through this handle.\n+\t * @param commands\n+\t *            unmodifiable set of successfully completed commands. May be\n+\t *            the empty set.\n+\t */\n+\tpublic void onPostReceive(ReceivePack rp,\n+\t\t\tCollection<ReceiveCommand> commands);\n+}\ndiff --git a/org.spearce.jgit/src/org/spearce/jgit/transport/PreReceiveHook.java b/org.spearce.jgit/src/org/spearce/jgit/transport/PreReceiveHook.java\nnew file mode 100644\nindex 0000000..0af466d\n--- /dev/null\n+++ b/org.spearce.jgit/src/org/spearce/jgit/transport/PreReceiveHook.java\n@@ -0,0 +1,94 @@\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.transport;\n+\n+import java.util.Collection;\n+\n+/**\n+ * Hook invoked by {@link ReceivePack} before any updates are executed.\n+ * <p>\n+ * The hook is called with any commands that are deemed valid after parsing them\n+ * from the client and applying the standard receive configuration options to\n+ * them:\n+ * <ul>\n+ * <li><code>receive.denyDenyDeletes</code></li>\n+ * <li><code>receive.denyNonFastForwards</code></li>\n+ * </ul>\n+ * This means the hook will not receive a non-fast-forward update command if\n+ * denyNonFastForwards is set to true in the configuration file. To get all\n+ * commands within the hook, see {@link ReceivePack#getAllCommands()}.\n+ * <p>\n+ * As the hook is invoked prior to the commands being executed, the hook may\n+ * choose to block any command by setting its result status with\n+ * {@link ReceiveCommand#setResult(ReceiveCommand.Result)}.\n+ * <p>\n+ * The hook may also choose to perform the command itself (or merely pretend\n+ * that it has performed the command), by setting the result status to\n+ * {@link ReceiveCommand.Result#OK}.\n+ * <p>\n+ * Hooks should run quickly, as they block the caller thread and the client\n+ * process from completing.\n+ * <p>\n+ * Hooks may send optional messages back to the client via methods on\n+ * {@link ReceivePack}. Implementors should be aware that not all network\n+ * transports support this output, so some (or all) messages may simply be\n+ * discarded. These messages should be advisory only.\n+ */\n+public interface PreReceiveHook {\n+\t/** A simple no-op hook. */\n+\tpublic static final PreReceiveHook NULL = new PreReceiveHook() {\n+\t\tpublic void onPreReceive(final ReceivePack rp,\n+\t\t\t\tfinal Collection<ReceiveCommand> commands) {\n+\t\t\t// Do nothing.\n+\t\t}\n+\t};\n+\n+\t/**\n+\t * Invoked just before commands are executed.\n+\t * <p>\n+\t * See the class description for how this method can impact execution.\n+\t * \n+\t * @param rp\n+\t *            the process handling the current receive. Hooks may obtain\n+\t *            details about the destination repository through this handle.\n+\t * @param commands\n+\t *            unmodifiable set of valid commands still pending execution.\n+\t *            May be the empty set.\n+\t */\n+\tpublic void onPreReceive(ReceivePack rp, Collection<ReceiveCommand> commands);\n+}\ndiff --git a/org.spearce.jgit/src/org/spearce/jgit/transport/ReceiveCommand.java b/org.spearce.jgit/src/org/spearce/jgit/transport/ReceiveCommand.java\nnew file mode 100644\nindex 0000000..b07b976\n--- /dev/null\n+++ b/org.spearce.jgit/src/org/spearce/jgit/transport/ReceiveCommand.java\n@@ -0,0 +1,223 @@\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.transport;\n+\n+import org.spearce.jgit.lib.ObjectId;\n+import org.spearce.jgit.lib.Ref;\n+\n+/**\n+ * A command being processed by {@link ReceivePack}.\n+ * <p>\n+ * This command instance roughly translates to the server side representation of\n+ * the {@link RemoteRefUpdate} created by the client.\n+ */\n+public class ReceiveCommand {\n+\t/** Type of operation requested. */\n+\tpublic static enum Type {\n+\t\t/** Create a new ref; the ref must not already exist. */\n+\t\tCREATE,\n+\n+\t\t/**\n+\t\t * Update an existing ref with a fast-forward update.\n+\t\t * <p>\n+\t\t * During a fast-forward update no changes will be lost; only new\n+\t\t * commits are inserted into the ref.\n+\t\t */\n+\t\tUPDATE,\n+\n+\t\t/**\n+\t\t * Update an existing ref by potentially discarding objects.\n+\t\t * <p>\n+\t\t * The current value of the ref is not fully reachable from the new\n+\t\t * value of the ref, so a successful command may result in one or more\n+\t\t * objects becoming unreachable.\n+\t\t */\n+\t\tUPDATE_NONFASTFORWARD,\n+\n+\t\t/** Delete an existing ref; the ref should already exist. */\n+\t\tDELETE;\n+\t}\n+\n+\t/** Result of the update command. */\n+\tpublic static enum Result {\n+\t\t/** The command has not yet been attempted by the server. */\n+\t\tNOT_ATTEMPTED,\n+\n+\t\t/** The server is configured to deny creation of this ref. */\n+\t\tREJECTED_NOCREATE,\n+\n+\t\t/** The server is configured to deny deletion of this ref. */\n+\t\tREJECTED_NODELETE,\n+\n+\t\t/** The update is a non-fast-forward update and isn't permitted. */\n+\t\tREJECTED_NONFASTFORWARD,\n+\n+\t\t/** The update affects <code>HEAD</code> and cannot be permitted. */\n+\t\tREJECTED_CURRENT_BRANCH,\n+\n+\t\t/**\n+\t\t * One or more objects aren't in the repository.\n+\t\t * <p>\n+\t\t * This is severe indication of either repository corruption on the\n+\t\t * server side, or a bug in the client wherein the client did not supply\n+\t\t * all required objects during the pack transfer.\n+\t\t */\n+\t\tREJECTED_MISSING_OBJECT,\n+\n+\t\t/** Other failure; see {@link ReceiveCommand#getMessage()}. */\n+\t\tREJECTED_OTHER_REASON,\n+\n+\t\t/** The ref could not be locked and updated atomically; try again. */\n+\t\tLOCK_FAILURE,\n+\n+\t\t/** The change was completed successfully. */\n+\t\tOK;\n+\t}\n+\n+\tprivate final ObjectId oldId;\n+\n+\tprivate final ObjectId newId;\n+\n+\tprivate final String name;\n+\n+\tprivate Type type;\n+\n+\tprivate Ref ref;\n+\n+\tprivate Result status;\n+\n+\tprivate String message;\n+\n+\t/**\n+\t * Create a new command for {@link ReceivePack}.\n+\t * \n+\t * @param oldId\n+\t *            the old object id; must not be null. Use\n+\t *            {@link ObjectId#zeroId()} to indicate a ref creation.\n+\t * @param newId\n+\t *            the new object id; must not be null. Use\n+\t *            {@link ObjectId#zeroId()} to indicate a ref deletion.\n+\t * @param name\n+\t *            name of the ref being affected.\n+\t */\n+\tpublic ReceiveCommand(final ObjectId oldId, final ObjectId newId,\n+\t\t\tfinal String name) {\n+\t\tthis.oldId = oldId;\n+\t\tthis.newId = newId;\n+\t\tthis.name = name;\n+\n+\t\ttype = Type.UPDATE;\n+\t\tif (ObjectId.zeroId().equals(oldId))\n+\t\t\ttype = Type.CREATE;\n+\t\tif (ObjectId.zeroId().equals(newId))\n+\t\t\ttype = Type.DELETE;\n+\t\tstatus = Result.NOT_ATTEMPTED;\n+\t}\n+\n+\t/** @return the old value the client thinks the ref has. */\n+\tpublic ObjectId getOldId() {\n+\t\treturn oldId;\n+\t}\n+\n+\t/** @return the requested new value for this ref. */\n+\tpublic ObjectId getNewId() {\n+\t\treturn newId;\n+\t}\n+\n+\t/** @return the name of the ref being updated. */\n+\tpublic String getRefName() {\n+\t\treturn name;\n+\t}\n+\n+\t/** @return the type of this command; see {@link Type}. */\n+\tpublic Type getType() {\n+\t\treturn type;\n+\t}\n+\n+\t/** @return the ref, if this was advertised by the connection. */\n+\tpublic Ref getRef() {\n+\t\treturn ref;\n+\t}\n+\n+\t/** @return the current status code of this command. */\n+\tpublic Result getResult() {\n+\t\treturn status;\n+\t}\n+\n+\t/** @return the message associated with a failure status. */\n+\tpublic String getMessage() {\n+\t\treturn message;\n+\t}\n+\n+\t/**\n+\t * Set the status of this command.\n+\t * \n+\t * @param s\n+\t *            the new status code for this command.\n+\t */\n+\tpublic void setResult(final Result s) {\n+\t\tsetResult(s, null);\n+\t}\n+\n+\t/**\n+\t * Set the status of this command.\n+\t * \n+\t * @param s\n+\t *            new status code for this command.\n+\t * @param m\n+\t *            optional message explaining the new status.\n+\t */\n+\tpublic void setResult(final Result s, final String m) {\n+\t\tstatus = s;\n+\t\tmessage = m;\n+\t}\n+\n+\tvoid setRef(final Ref r) {\n+\t\tref = r;\n+\t}\n+\n+\tvoid setType(final Type t) {\n+\t\ttype = t;\n+\t}\n+\n+\t@Override\n+\tpublic String toString() {\n+\t\treturn getType().name() + \": \" + getOldId().name() + \" \"\n+\t\t\t\t+ getNewId().name() + \" \" + getRefName();\n+\t}\n+}\ndiff --git a/org.spearce.jgit/src/org/spearce/jgit/transport/ReceivePack.java b/org.spearce.jgit/src/org/spearce/jgit/transport/ReceivePack.java\nnew file mode 100644\nindex 0000000..95519b8\n--- /dev/null\n+++ b/org.spearce.jgit/src/org/spearce/jgit/transport/ReceivePack.java\n@@ -0,0 +1,793 @@\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.transport;\n+\n+import java.io.BufferedWriter;\n+import java.io.EOFException;\n+import java.io.IOException;\n+import java.io.InputStream;\n+import java.io.OutputStream;\n+import java.io.OutputStreamWriter;\n+import java.io.PrintWriter;\n+import java.util.ArrayList;\n+import java.util.Collections;\n+import java.util.HashSet;\n+import java.util.Iterator;\n+import java.util.List;\n+import java.util.Map;\n+import java.util.Set;\n+\n+import org.spearce.jgit.errors.MissingObjectException;\n+import org.spearce.jgit.errors.PackProtocolException;\n+import org.spearce.jgit.lib.Constants;\n+import org.spearce.jgit.lib.NullProgressMonitor;\n+import org.spearce.jgit.lib.ObjectId;\n+import org.spearce.jgit.lib.Ref;\n+import org.spearce.jgit.lib.RefComparator;\n+import org.spearce.jgit.lib.RefUpdate;\n+import org.spearce.jgit.lib.Repository;\n+import org.spearce.jgit.lib.RepositoryConfig;\n+import org.spearce.jgit.revwalk.ObjectWalk;\n+import org.spearce.jgit.revwalk.RevCommit;\n+import org.spearce.jgit.revwalk.RevObject;\n+import org.spearce.jgit.revwalk.RevWalk;\n+import org.spearce.jgit.transport.ReceiveCommand.Result;\n+\n+/**\n+ * Implements the server side of a push connection, receiving objects.\n+ */\n+public class ReceivePack {\n+\tstatic final String CAPABILITY_REPORT_STATUS = BasePackPushConnection.CAPABILITY_REPORT_STATUS;\n+\n+\tstatic final String CAPABILITY_DELETE_REFS = BasePackPushConnection.CAPABILITY_DELETE_REFS;\n+\n+\t/** Database we write the stored objects into. */\n+\tprivate final Repository db;\n+\n+\t/** Revision traversal support over {@link #db}. */\n+\tprivate final RevWalk walk;\n+\n+\t/** Should an incoming transfer validate objects? */\n+\tprivate boolean checkReceivedObjects;\n+\n+\t/** Should an incoming transfer permit create requests? */\n+\tprivate boolean allowCreates;\n+\n+\t/** Should an incoming transfer permit delete requests? */\n+\tprivate boolean allowDeletes;\n+\n+\t/** Should an incoming transfer permit non-fast-forward requests? */\n+\tprivate boolean allowNonFastForwards;\n+\n+\t/** Hook to validate the update commands before execution. */\n+\tprivate PreReceiveHook preReceive;\n+\n+\t/** Hook to report on the commands after execution. */\n+\tprivate PostReceiveHook postReceive;\n+\n+\tprivate InputStream rawIn;\n+\n+\tprivate OutputStream rawOut;\n+\n+\tprivate PacketLineIn pckIn;\n+\n+\tprivate PacketLineOut pckOut;\n+\n+\tprivate PrintWriter msgs;\n+\n+\t/** The refs we advertised as existing at the start of the connection. */\n+\tprivate Map<String, Ref> refs;\n+\n+\t/** Capabilities requested by the client. */\n+\tprivate Set<String> enabledCapablities;\n+\n+\t/** Commands to execute, as received by the client. */\n+\tprivate List<ReceiveCommand> commands;\n+\n+\t/** An exception caught while unpacking and fsck'ing the objects. */\n+\tprivate Throwable unpackError;\n+\n+\t/** if {@link #enabledCapablities} has {@link #CAPABILITY_REPORT_STATUS} */\n+\tprivate boolean reportStatus;\n+\n+\t/**\n+\t * Create a new pack receive for an open repository.\n+\t * \n+\t * @param into\n+\t *            the destination repository.\n+\t */\n+\tpublic ReceivePack(final Repository into) {\n+\t\tdb = into;\n+\t\twalk = new RevWalk(db);\n+\n+\t\tfinal RepositoryConfig cfg = db.getConfig();\n+\t\tcheckReceivedObjects = cfg.getBoolean(\"receive\", \"fsckobjects\", false);\n+\t\tallowCreates = true;\n+\t\tallowDeletes = !cfg.getBoolean(\"receive\", \"denydeletes\", false);\n+\t\tallowNonFastForwards = !cfg.getBoolean(\"receive\",\n+\t\t\t\t\"denynonfastforwards\", false);\n+\t\tpreReceive = PreReceiveHook.NULL;\n+\t\tpostReceive = PostReceiveHook.NULL;\n+\t}\n+\n+\t/** @return the repository this receive completes into. */\n+\tpublic final Repository getRepository() {\n+\t\treturn db;\n+\t}\n+\n+\t/** @return the RevWalk instance used by this connection. */\n+\tpublic final RevWalk getRevWalk() {\n+\t\treturn walk;\n+\t}\n+\n+\t/**\n+\t * @return true if this instance will verify received objects are formatted\n+\t *         correctly. Validating objects requires more CPU time on this side\n+\t *         of the connection.\n+\t */\n+\tpublic boolean isCheckReceivedObjects() {\n+\t\treturn checkReceivedObjects;\n+\t}\n+\n+\t/**\n+\t * @param check\n+\t *            true to enable checking received objects; false to assume all\n+\t *            received objects are valid.\n+\t */\n+\tpublic void setCheckReceivedObjects(final boolean check) {\n+\t\tcheckReceivedObjects = check;\n+\t}\n+\n+\t/** @return true if the client can request refs to be created. */\n+\tpublic boolean isAllowCreates() {\n+\t\treturn allowCreates;\n+\t}\n+\n+\t/**\n+\t * @param canCreate\n+\t *            true to permit create ref commands to be processed.\n+\t */\n+\tpublic void setAllowCreates(final boolean canCreate) {\n+\t\tallowCreates = canCreate;\n+\t}\n+\n+\t/** @return true if the client can request refs to be deleted. */\n+\tpublic boolean isAllowDeletes() {\n+\t\treturn allowDeletes;\n+\t}\n+\n+\t/**\n+\t * @param canDelete\n+\t *            true to permit delete ref commands to be processed.\n+\t */\n+\tpublic void setAllowDeletes(final boolean canDelete) {\n+\t\tallowDeletes = canDelete;\n+\t}\n+\n+\t/**\n+\t * @return true if the client can request non-fast-forward updates of a ref,\n+\t *         possibly making objects unreachable.\n+\t */\n+\tpublic boolean isAllowNonFastForwards() {\n+\t\treturn allowNonFastForwards;\n+\t}\n+\n+\t/**\n+\t * @param canRewind\n+\t *            true to permit the client to ask for non-fast-forward updates\n+\t *            of an existing ref.\n+\t */\n+\tpublic void setAllowNonFastForwards(final boolean canRewind) {\n+\t\tallowNonFastForwards = canRewind;\n+\t}\n+\n+\t/** @return get the hook invoked before updates occur. */\n+\tpublic PreReceiveHook getPreReceiveHook() {\n+\t\treturn preReceive;\n+\t}\n+\n+\t/**\n+\t * Set the hook which is invoked prior to commands being executed.\n+\t * <p>\n+\t * Only valid commands (those which have no obvious errors according to the\n+\t * received input and this instance's configuration) are passed into the\n+\t * hook. The hook may mark a command with a result of any value other than\n+\t * {@link Result#NOT_ATTEMPTED} to block its execution.\n+\t * <p>\n+\t * The hook may be called with an empty command collection if the current\n+\t * set is completely invalid.\n+\t * \n+\t * @param h\n+\t *            the hook instance; may be null to disable the hook.\n+\t */\n+\tpublic void setPreReceiveHook(final PreReceiveHook h) {\n+\t\tpreReceive = h != null ? h : PreReceiveHook.NULL;\n+\t}\n+\n+\t/** @return get the hook invoked after updates occur. */\n+\tpublic PostReceiveHook getPostReceiveHook() {\n+\t\treturn postReceive;\n+\t}\n+\n+\t/**\n+\t * Set the hook which is invoked after commands are executed.\n+\t * <p>\n+\t * Only successful commands (type is {@link Result#OK}) are passed into the\n+\t * hook. The hook may be called with an empty command collection if the\n+\t * current set all resulted in an error.\n+\t * \n+\t * @param h\n+\t *            the hook instance; may be null to disable the hook.\n+\t */\n+\tpublic void setPostReceiveHook(final PostReceiveHook h) {\n+\t\tpostReceive = h != null ? h : PostReceiveHook.NULL;\n+\t}\n+\n+\t/** @return all of the command received by the current request. */\n+\tpublic List<ReceiveCommand> getAllCommands() {\n+\t\treturn Collections.unmodifiableList(commands);\n+\t}\n+\n+\t/**\n+\t * Send an error message to the client, if it supports receiving them.\n+\t * <p>\n+\t * If the client doesn't support receiving messages, the message will be\n+\t * discarded, with no other indication to the caller or to the client.\n+\t * <p>\n+\t * {@link PreReceiveHook}s should always try to use\n+\t * {@link ReceiveCommand#setResult(Result, String)} with a result status of\n+\t * {@link Result#REJECTED_OTHER_REASON} to indicate any reasons for\n+\t * rejecting an update. Messages attached to a command are much more likely\n+\t * to be returned to the client.\n+\t * \n+\t * @param what\n+\t *            string describing the problem identified by the hook. The\n+\t *            string must not end with an LF, and must not contain an LF.\n+\t */\n+\tpublic void sendError(final String what) {\n+\t\tsendMessage(\"error\", what);\n+\t}\n+\n+\t/**\n+\t * Send a message to the client, if it supports receiving them.\n+\t * <p>\n+\t * If the client doesn't support receiving messages, the message will be\n+\t * discarded, with no other indication to the caller or to the client.\n+\t * \n+\t * @param what\n+\t *            string describing the problem identified by the hook. The\n+\t *            string must not end with an LF, and must not contain an LF.\n+\t */\n+\tpublic void sendMessage(final String what) {\n+\t\tsendMessage(\"remote\", what);\n+\t}\n+\n+\tprivate void sendMessage(final String type, final String what) {\n+\t\tif (msgs != null)\n+\t\t\tmsgs.println(type + \": \" + what);\n+\t}\n+\n+\t/**\n+\t * Execute the receive task on the socket.\n+\t * \n+\t * @param input\n+\t *            raw input to read client commands and pack data from. Caller\n+\t *            must ensure the input is buffered, otherwise read performance\n+\t *            may suffer.\n+\t * @param output\n+\t *            response back to the Git network client. Caller must ensure\n+\t *            the output is buffered, otherwise write performance may\n+\t *            suffer.\n+\t * @param messages\n+\t *            secondary \"notice\" channel to send additional messages out\n+\t *            through. When run over SSH this should be tied back to the\n+\t *            standard error channel of the command execution. For most\n+\t *            other network connections this should be null.\n+\t * @throws IOException\n+\t */\n+\tpublic void receive(final InputStream input, final OutputStream output,\n+\t\t\tfinal OutputStream messages) throws IOException {\n+\t\ttry {\n+\t\t\trawIn = input;\n+\t\t\trawOut = output;\n+\n+\t\t\tpckIn = new PacketLineIn(rawIn);\n+\t\t\tpckOut = new PacketLineOut(rawOut);\n+\t\t\tif (messages != null) {\n+\t\t\t\tmsgs = new PrintWriter(new BufferedWriter(\n+\t\t\t\t\t\tnew OutputStreamWriter(messages, Constants.CHARSET),\n+\t\t\t\t\t\t8192)) {\n+\t\t\t\t\t@Override\n+\t\t\t\t\tpublic void println() {\n+\t\t\t\t\t\tprint('\\n');\n+\t\t\t\t\t}\n+\t\t\t\t};\n+\t\t\t}\n+\n+\t\t\tenabledCapablities = new HashSet<String>();\n+\t\t\tcommands = new ArrayList<ReceiveCommand>();\n+\n+\t\t\tservice();\n+\t\t} finally {\n+\t\t\ttry {\n+\t\t\t\tif (msgs != null) {\n+\t\t\t\t\tmsgs.flush();\n+\t\t\t\t}\n+\t\t\t} finally {\n+\t\t\t\trawIn = null;\n+\t\t\t\trawOut = null;\n+\t\t\t\tpckIn = null;\n+\t\t\t\tpckOut = null;\n+\t\t\t\tmsgs = null;\n+\t\t\t\trefs = null;\n+\t\t\t\tenabledCapablities = null;\n+\t\t\t\tcommands = null;\n+\t\t\t}\n+\t\t}\n+\t}\n+\n+\tprivate void service() throws IOException {\n+\t\tsendAdvertisedRefs();\n+\t\trecvCommands();\n+\t\tif (!commands.isEmpty()) {\n+\t\t\tenableCapabilities();\n+\n+\t\t\tif (needPack()) {\n+\t\t\t\ttry {\n+\t\t\t\t\treceivePack();\n+\t\t\t\t\tif (isCheckReceivedObjects())\n+\t\t\t\t\t\tcheckConnectivity();\n+\t\t\t\t\tunpackError = null;\n+\t\t\t\t} catch (IOException err) {\n+\t\t\t\t\tunpackError = err;\n+\t\t\t\t} catch (RuntimeException err) {\n+\t\t\t\t\tunpackError = err;\n+\t\t\t\t} catch (Error err) {\n+\t\t\t\t\tunpackError = err;\n+\t\t\t\t}\n+\t\t\t}\n+\n+\t\t\tif (unpackError == null) {\n+\t\t\t\tvalidateCommands();\n+\t\t\t\texecuteCommands();\n+\t\t\t}\n+\n+\t\t\tif (reportStatus) {\n+\t\t\t\tsendStatusReport(true, new Reporter() {\n+\t\t\t\t\tvoid sendString(final String s) throws IOException {\n+\t\t\t\t\t\tpckOut.writeString(s + \"\\n\");\n+\t\t\t\t\t}\n+\t\t\t\t});\n+\t\t\t\tpckOut.end();\n+\t\t\t} else if (msgs != null) {\n+\t\t\t\tsendStatusReport(false, new Reporter() {\n+\t\t\t\t\tvoid sendString(final String s) throws IOException {\n+\t\t\t\t\t\tmsgs.println(s);\n+\t\t\t\t\t}\n+\t\t\t\t});\n+\t\t\t\tmsgs.flush();\n+\t\t\t}\n+\n+\t\t\tpostReceive.onPostReceive(this, filterCommands(Result.OK));\n+\t\t}\n+\t}\n+\n+\tprivate void sendAdvertisedRefs() throws IOException {\n+\t\trefs = db.getAllRefs();\n+\n+\t\tfinal StringBuilder m = new StringBuilder(100);\n+\t\tfinal char[] idtmp = new char[2 * Constants.OBJECT_ID_LENGTH];\n+\t\tfinal Iterator<Ref> i = RefComparator.sort(refs.values()).iterator();\n+\t\t{\n+\t\t\tif (i.hasNext()) {\n+\t\t\t\tfinal Ref r = i.next();\n+\t\t\t\tformat(m, idtmp, r.getObjectId(), r.getOrigName());\n+\t\t\t} else {\n+\t\t\t\tformat(m, idtmp, ObjectId.zeroId(), \"capabilities^{}\");\n+\t\t\t}\n+\t\t\tm.append('\\0');\n+\t\t\tm.append(' ');\n+\t\t\tm.append(CAPABILITY_DELETE_REFS);\n+\t\t\tm.append(' ');\n+\t\t\tm.append(CAPABILITY_REPORT_STATUS);\n+\t\t\tm.append(' ');\n+\t\t\twriteAdvertisedRef(m);\n+\t\t}\n+\n+\t\twhile (i.hasNext()) {\n+\t\t\tfinal Ref r = i.next();\n+\t\t\tformat(m, idtmp, r.getObjectId(), r.getOrigName());\n+\t\t\twriteAdvertisedRef(m);\n+\t\t}\n+\t\tpckOut.end();\n+\t}\n+\n+\tprivate void format(final StringBuilder m, final char[] idtmp,\n+\t\t\tfinal ObjectId id, final String name) {\n+\t\tm.setLength(0);\n+\t\tid.copyTo(idtmp, m);\n+\t\tm.append(' ');\n+\t\tm.append(name);\n+\t}\n+\n+\tprivate void writeAdvertisedRef(final StringBuilder m) throws IOException {\n+\t\tm.append('\\n');\n+\t\tpckOut.writeString(m.toString());\n+\t}\n+\n+\tprivate void recvCommands() throws IOException {\n+\t\tfor (;;) {\n+\t\t\tString line;\n+\t\t\ttry {\n+\t\t\t\tline = pckIn.readStringNoLF();\n+\t\t\t} catch (EOFException eof) {\n+\t\t\t\tif (commands.isEmpty())\n+\t\t\t\t\treturn;\n+\t\t\t\tthrow eof;\n+\t\t\t}\n+\n+\t\t\tif (commands.isEmpty()) {\n+\t\t\t\tfinal int nul = line.indexOf('\\0');\n+\t\t\t\tif (nul >= 0) {\n+\t\t\t\t\tfor (String c : line.substring(nul + 1).split(\" \"))\n+\t\t\t\t\t\tenabledCapablities.add(c);\n+\t\t\t\t\tline = line.substring(0, nul);\n+\t\t\t\t}\n+\t\t\t}\n+\n+\t\t\tif (line.length() == 0)\n+\t\t\t\tbreak;\n+\t\t\tif (line.length() < 83) {\n+\t\t\t\tfinal String m = \"error: invalid protocol: wanted 'old new ref'\";\n+\t\t\t\tsendError(m);\n+\t\t\t\tthrow new PackProtocolException(m);\n+\t\t\t}\n+\n+\t\t\tfinal ObjectId oldId = ObjectId.fromString(line.substring(0, 40));\n+\t\t\tfinal ObjectId newId = ObjectId.fromString(line.substring(41, 81));\n+\t\t\tfinal String name = line.substring(82);\n+\t\t\tfinal ReceiveCommand cmd = new ReceiveCommand(oldId, newId, name);\n+\t\t\tcmd.setRef(refs.get(cmd.getRefName()));\n+\t\t\tcommands.add(cmd);\n+\t\t}\n+\t}\n+\n+\tprivate void enableCapabilities() {\n+\t\treportStatus = enabledCapablities.contains(CAPABILITY_REPORT_STATUS);\n+\t}\n+\n+\tprivate boolean needPack() {\n+\t\tfor (final ReceiveCommand cmd : commands) {\n+\t\t\tif (cmd.getType() != ReceiveCommand.Type.DELETE)\n+\t\t\t\treturn true;\n+\t\t}\n+\t\treturn false;\n+\t}\n+\n+\tprivate void receivePack() throws IOException {\n+\t\tfinal IndexPack ip = IndexPack.create(db, rawIn);\n+\t\tip.setFixThin(true);\n+\t\tip.setObjectChecking(isCheckReceivedObjects());\n+\t\tip.index(NullProgressMonitor.INSTANCE);\n+\t\tip.renameAndOpenPack();\n+\t}\n+\n+\tprivate void checkConnectivity() throws IOException {\n+\t\tfinal ObjectWalk ow = new ObjectWalk(db);\n+\t\tfor (final ReceiveCommand cmd : commands) {\n+\t\t\tif (cmd.getResult() != Result.NOT_ATTEMPTED)\n+\t\t\t\tcontinue;\n+\t\t\tif (cmd.getType() == ReceiveCommand.Type.DELETE)\n+\t\t\t\tcontinue;\n+\t\t\tow.markStart(ow.parseAny(cmd.getNewId()));\n+\t\t}\n+\t\tfor (final Ref ref : refs.values())\n+\t\t\tow.markUninteresting(ow.parseAny(ref.getObjectId()));\n+\t\tow.checkConnectivity();\n+\t}\n+\n+\tprivate void validateCommands() {\n+\t\tfor (final ReceiveCommand cmd : commands) {\n+\t\t\tfinal Ref ref = cmd.getRef();\n+\t\t\tif (cmd.getResult() != Result.NOT_ATTEMPTED)\n+\t\t\t\tcontinue;\n+\n+\t\t\tif (cmd.getType() == ReceiveCommand.Type.DELETE\n+\t\t\t\t\t&& !isAllowDeletes()) {\n+\t\t\t\t// Deletes are not supported on this repository.\n+\t\t\t\t//\n+\t\t\t\tcmd.setResult(Result.REJECTED_NODELETE);\n+\t\t\t\tcontinue;\n+\t\t\t}\n+\n+\t\t\tif (cmd.getType() == ReceiveCommand.Type.CREATE) {\n+\t\t\t\tif (!isAllowCreates()) {\n+\t\t\t\t\tcmd.setResult(Result.REJECTED_NOCREATE);\n+\t\t\t\t\tcontinue;\n+\t\t\t\t}\n+\n+\t\t\t\tif (ref != null && !isAllowNonFastForwards()) {\n+\t\t\t\t\t// Creation over an existing ref is certainly not going\n+\t\t\t\t\t// to be a fast-forward update. We can reject it early.\n+\t\t\t\t\t//\n+\t\t\t\t\tcmd.setResult(Result.REJECTED_NONFASTFORWARD);\n+\t\t\t\t\tcontinue;\n+\t\t\t\t}\n+\n+\t\t\t\tif (ref != null) {\n+\t\t\t\t\t// A well behaved client shouldn't have sent us an\n+\t\t\t\t\t// update command for a ref we advertised to it.\n+\t\t\t\t\t//\n+\t\t\t\t\tcmd.setResult(Result.REJECTED_OTHER_REASON, \"ref exists\");\n+\t\t\t\t\tcontinue;\n+\t\t\t\t}\n+\t\t\t}\n+\n+\t\t\tif (cmd.getType() == ReceiveCommand.Type.DELETE && ref != null\n+\t\t\t\t\t&& !ObjectId.zeroId().equals(cmd.getOldId())\n+\t\t\t\t\t&& !ref.getObjectId().equals(cmd.getOldId())) {\n+\t\t\t\t// Delete commands can be sent with the old id matching our\n+\t\t\t\t// advertised value, *OR* with the old id being 0{40}. Any\n+\t\t\t\t// other requested old id is invalid.\n+\t\t\t\t//\n+\t\t\t\tcmd.setResult(Result.REJECTED_OTHER_REASON,\n+\t\t\t\t\t\t\"invalid old id sent\");\n+\t\t\t\tcontinue;\n+\t\t\t}\n+\n+\t\t\tif (cmd.getType() == ReceiveCommand.Type.UPDATE) {\n+\t\t\t\tif (ref == null) {\n+\t\t\t\t\t// The ref must have been advertised in order to be updated.\n+\t\t\t\t\t//\n+\t\t\t\t\tcmd.setResult(Result.REJECTED_OTHER_REASON, \"no such ref\");\n+\t\t\t\t\tcontinue;\n+\t\t\t\t}\n+\n+\t\t\t\tif (!ref.getObjectId().equals(cmd.getOldId())) {\n+\t\t\t\t\t// A properly functioning client will send the same\n+\t\t\t\t\t// object id we advertised.\n+\t\t\t\t\t//\n+\t\t\t\t\tcmd.setResult(Result.REJECTED_OTHER_REASON,\n+\t\t\t\t\t\t\t\"invalid old id sent\");\n+\t\t\t\t\tcontinue;\n+\t\t\t\t}\n+\n+\t\t\t\t// Is this possibly a non-fast-forward style update?\n+\t\t\t\t//\n+\t\t\t\tRevObject oldObj, newObj;\n+\t\t\t\ttry {\n+\t\t\t\t\toldObj = walk.parseAny(cmd.getOldId());\n+\t\t\t\t} catch (IOException e) {\n+\t\t\t\t\tcmd.setResult(Result.REJECTED_MISSING_OBJECT, cmd\n+\t\t\t\t\t\t\t.getOldId().name());\n+\t\t\t\t\tcontinue;\n+\t\t\t\t}\n+\n+\t\t\t\ttry {\n+\t\t\t\t\tnewObj = walk.parseAny(cmd.getNewId());\n+\t\t\t\t} catch (IOException e) {\n+\t\t\t\t\tcmd.setResult(Result.REJECTED_MISSING_OBJECT, cmd\n+\t\t\t\t\t\t\t.getNewId().name());\n+\t\t\t\t\tcontinue;\n+\t\t\t\t}\n+\n+\t\t\t\tif (oldObj instanceof RevCommit && newObj instanceof RevCommit) {\n+\t\t\t\t\ttry {\n+\t\t\t\t\t\tif (!walk.isMergedInto((RevCommit) oldObj,\n+\t\t\t\t\t\t\t\t(RevCommit) newObj)) {\n+\t\t\t\t\t\t\tcmd\n+\t\t\t\t\t\t\t\t\t.setType(ReceiveCommand.Type.UPDATE_NONFASTFORWARD);\n+\t\t\t\t\t\t}\n+\t\t\t\t\t} catch (MissingObjectException e) {\n+\t\t\t\t\t\tcmd.setResult(Result.REJECTED_MISSING_OBJECT, e\n+\t\t\t\t\t\t\t\t.getMessage());\n+\t\t\t\t\t} catch (IOException e) {\n+\t\t\t\t\t\tcmd.setResult(Result.REJECTED_OTHER_REASON);\n+\t\t\t\t\t}\n+\t\t\t\t} else {\n+\t\t\t\t\tcmd.setType(ReceiveCommand.Type.UPDATE_NONFASTFORWARD);\n+\t\t\t\t}\n+\t\t\t}\n+\n+\t\t\tif (!cmd.getRefName().startsWith(Constants.R_REFS)\n+\t\t\t\t\t|| !Repository.isValidRefName(cmd.getRefName())) {\n+\t\t\t\tcmd.setResult(Result.REJECTED_OTHER_REASON, \"funny refname\");\n+\t\t\t}\n+\t\t}\n+\t}\n+\n+\tprivate void executeCommands() {\n+\t\tpreReceive.onPreReceive(this, filterCommands(Result.NOT_ATTEMPTED));\n+\t\tfor (final ReceiveCommand cmd : filterCommands(Result.NOT_ATTEMPTED))\n+\t\t\texecute(cmd);\n+\t}\n+\n+\tprivate void execute(final ReceiveCommand cmd) {\n+\t\ttry {\n+\t\t\tfinal RefUpdate ru = db.updateRef(cmd.getRefName());\n+\t\t\tswitch (cmd.getType()) {\n+\t\t\tcase DELETE:\n+\t\t\t\tif (!ObjectId.zeroId().equals(cmd.getOldId())) {\n+\t\t\t\t\t// We can only do a CAS style delete if the client\n+\t\t\t\t\t// didn't bork its delete request by sending the\n+\t\t\t\t\t// wrong zero id rather than the advertised one.\n+\t\t\t\t\t//\n+\t\t\t\t\tru.setExpectedOldObjectId(cmd.getOldId());\n+\t\t\t\t}\n+\t\t\t\tru.setForceUpdate(true);\n+\t\t\t\tstatus(cmd, ru.delete(walk));\n+\t\t\t\tbreak;\n+\n+\t\t\tcase CREATE:\n+\t\t\tcase UPDATE:\n+\t\t\tcase UPDATE_NONFASTFORWARD:\n+\t\t\t\tru.setForceUpdate(isAllowNonFastForwards());\n+\t\t\t\tru.setExpectedOldObjectId(cmd.getOldId());\n+\t\t\t\tru.setNewObjectId(cmd.getNewId());\n+\t\t\t\tru.setRefLogMessage(\"push\", true);\n+\t\t\t\tstatus(cmd, ru.update(walk));\n+\t\t\t\tbreak;\n+\t\t\t}\n+\t\t} catch (IOException err) {\n+\t\t\tcmd.setResult(Result.REJECTED_OTHER_REASON, \"lock error: \"\n+\t\t\t\t\t+ err.getMessage());\n+\t\t}\n+\t}\n+\n+\tprivate void status(final ReceiveCommand cmd, final RefUpdate.Result result) {\n+\t\tswitch (result) {\n+\t\tcase NOT_ATTEMPTED:\n+\t\t\tcmd.setResult(Result.NOT_ATTEMPTED);\n+\t\t\tbreak;\n+\n+\t\tcase LOCK_FAILURE:\n+\t\tcase IO_FAILURE:\n+\t\t\tcmd.setResult(Result.LOCK_FAILURE);\n+\t\t\tbreak;\n+\n+\t\tcase NO_CHANGE:\n+\t\tcase NEW:\n+\t\tcase FORCED:\n+\t\tcase FAST_FORWARD:\n+\t\t\tcmd.setResult(Result.OK);\n+\t\t\tbreak;\n+\n+\t\tcase REJECTED:\n+\t\t\tcmd.setResult(Result.REJECTED_NONFASTFORWARD);\n+\t\t\tbreak;\n+\n+\t\tcase REJECTED_CURRENT_BRANCH:\n+\t\t\tcmd.setResult(Result.REJECTED_CURRENT_BRANCH);\n+\t\t\tbreak;\n+\n+\t\tdefault:\n+\t\t\tcmd.setResult(Result.REJECTED_OTHER_REASON, result.name());\n+\t\t\tbreak;\n+\t\t}\n+\t}\n+\n+\tprivate List<ReceiveCommand> filterCommands(final Result want) {\n+\t\tfinal List<ReceiveCommand> r = new ArrayList<ReceiveCommand>(commands\n+\t\t\t\t.size());\n+\t\tfor (final ReceiveCommand cmd : commands) {\n+\t\t\tif (cmd.getResult() == want)\n+\t\t\t\tr.add(cmd);\n+\t\t}\n+\t\treturn r;\n+\t}\n+\n+\tprivate void sendStatusReport(final boolean forClient, final Reporter out)\n+\t\t\tthrows IOException {\n+\t\tif (unpackError != null) {\n+\t\t\tout.sendString(\"unpack error \" + unpackError.getMessage());\n+\t\t\tif (forClient) {\n+\t\t\t\tfor (final ReceiveCommand cmd : commands) {\n+\t\t\t\t\tout.sendString(\"ng \" + cmd.getRefName()\n+\t\t\t\t\t\t\t+ \" n/a (unpacker error)\");\n+\t\t\t\t}\n+\t\t\t}\n+\t\t\treturn;\n+\t\t}\n+\n+\t\tif (forClient)\n+\t\t\tout.sendString(\"unpack ok\");\n+\t\tfor (final ReceiveCommand cmd : commands) {\n+\t\t\tif (cmd.getResult() == Result.OK) {\n+\t\t\t\tif (forClient)\n+\t\t\t\t\tout.sendString(\"ok \" + cmd.getRefName());\n+\t\t\t\tcontinue;\n+\t\t\t}\n+\n+\t\t\tfinal StringBuilder r = new StringBuilder();\n+\t\t\tr.append(\"ng \");\n+\t\t\tr.append(cmd.getRefName());\n+\t\t\tr.append(\" \");\n+\n+\t\t\tswitch (cmd.getResult()) {\n+\t\t\tcase NOT_ATTEMPTED:\n+\t\t\t\tr.append(\"server bug; ref not processed\");\n+\t\t\t\tbreak;\n+\n+\t\t\tcase REJECTED_NOCREATE:\n+\t\t\t\tr.append(\"creation prohibited\");\n+\t\t\t\tbreak;\n+\n+\t\t\tcase REJECTED_NODELETE:\n+\t\t\t\tr.append(\"deletion prohibited\");\n+\t\t\t\tbreak;\n+\n+\t\t\tcase REJECTED_NONFASTFORWARD:\n+\t\t\t\tr.append(\"non-fast forward\");\n+\t\t\t\tbreak;\n+\n+\t\t\tcase REJECTED_CURRENT_BRANCH:\n+\t\t\t\tr.append(\"branch is currently checked out\");\n+\t\t\t\tbreak;\n+\n+\t\t\tcase REJECTED_MISSING_OBJECT:\n+\t\t\t\tif (cmd.getMessage() == null)\n+\t\t\t\t\tr.append(\"missing object(s)\");\n+\t\t\t\telse if (cmd.getMessage().length() == 2 * Constants.OBJECT_ID_LENGTH)\n+\t\t\t\t\tr.append(\"object \" + cmd.getMessage() + \" missing\");\n+\t\t\t\telse\n+\t\t\t\t\tr.append(cmd.getMessage());\n+\t\t\t\tbreak;\n+\n+\t\t\tcase REJECTED_OTHER_REASON:\n+\t\t\t\tif (cmd.getMessage() == null)\n+\t\t\t\t\tr.append(\"unspecified reason\");\n+\t\t\t\telse\n+\t\t\t\t\tr.append(cmd.getMessage());\n+\t\t\t\tbreak;\n+\n+\t\t\tcase LOCK_FAILURE:\n+\t\t\t\tr.append(\"failed to lock\");\n+\t\t\t\tbreak;\n+\n+\t\t\tcase OK:\n+\t\t\t\t// We shouldn't have reached this case (see 'ok' case above).\n+\t\t\t\tcontinue;\n+\t\t\t}\n+\t\t\tout.sendString(r.toString());\n+\t\t}\n+\t}\n+\n+\tstatic abstract class Reporter {\n+\t\tabstract void sendString(String s) throws IOException;\n+\t}\n+}\n-- \n1.6.1.rc4.301.g5497a\n"},{"id":"98581","messageId":"1229992043-1053-14-git-send-email-spearce@spearce.org","threadId":"16840","inReplyTo":"1229992043-1053-13-git-send-email-spearce@spearce.org","subject":"[JGIT PATCH 13/13] Add basic git daemon support to publish receive-pack","fromName":"Shawn O. Pearce","fromEmail":"spearce@spearce.org","sentAt":"2008-12-23T00:27:23Z","receivedAt":"2008-12-23T00:27:23Z","isPatch":true,"sender":{"key":"spearce@spearce.org","avatar":"https://avatars.githubusercontent.com/u/34844?v=4"},"body":"The git:// daemon service receives anonymous TCP connections and runs\ncommands as they are received.\n\nCurrently we only support the server portion of send-pack/receive-pack,\nso that is the only service registered in our Daemon class.\n\nSigned-off-by: Shawn O. Pearce <spearce@spearce.org>\n---\n .../services/org.spearce.jgit.pgm.TextBuiltin      |    1 +\n .../src/org/spearce/jgit/pgm/Daemon.java           |  125 ++++++++\n .../src/org/spearce/jgit/transport/Daemon.java     |  309 ++++++++++++++++++++\n .../org/spearce/jgit/transport/DaemonClient.java   |  106 +++++++\n .../org/spearce/jgit/transport/DaemonService.java  |  120 ++++++++\n .../spearce/jgit/transport/TransportGitAnon.java   |    3 +-\n 6 files changed, 662 insertions(+), 2 deletions(-)\n create mode 100644 org.spearce.jgit.pgm/src/org/spearce/jgit/pgm/Daemon.java\n create mode 100644 org.spearce.jgit/src/org/spearce/jgit/transport/Daemon.java\n create mode 100644 org.spearce.jgit/src/org/spearce/jgit/transport/DaemonClient.java\n create mode 100644 org.spearce.jgit/src/org/spearce/jgit/transport/DaemonService.java\n\ndiff --git a/org.spearce.jgit.pgm/src/META-INF/services/org.spearce.jgit.pgm.TextBuiltin b/org.spearce.jgit.pgm/src/META-INF/services/org.spearce.jgit.pgm.TextBuiltin\nindex e2e7938..5fb0953 100644\n--- a/org.spearce.jgit.pgm/src/META-INF/services/org.spearce.jgit.pgm.TextBuiltin\n+++ b/org.spearce.jgit.pgm/src/META-INF/services/org.spearce.jgit.pgm.TextBuiltin\n@@ -1,4 +1,5 @@\n org.spearce.jgit.pgm.Branch\n+org.spearce.jgit.pgm.Daemon\n org.spearce.jgit.pgm.DiffTree\n org.spearce.jgit.pgm.Fetch\n org.spearce.jgit.pgm.Glog\ndiff --git a/org.spearce.jgit.pgm/src/org/spearce/jgit/pgm/Daemon.java b/org.spearce.jgit.pgm/src/org/spearce/jgit/pgm/Daemon.java\nnew file mode 100644\nindex 0000000..aafc82e\n--- /dev/null\n+++ b/org.spearce.jgit.pgm/src/org/spearce/jgit/pgm/Daemon.java\n@@ -0,0 +1,125 @@\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.pgm;\n+\n+import java.io.File;\n+import java.net.InetSocketAddress;\n+import java.util.ArrayList;\n+import java.util.List;\n+\n+import org.kohsuke.args4j.Argument;\n+import org.kohsuke.args4j.Option;\n+import org.spearce.jgit.lib.Repository;\n+import org.spearce.jgit.transport.DaemonService;\n+\n+@Command(common = true, usage = \"Export repositories over git://\")\n+class Daemon extends TextBuiltin {\n+\t@Option(name = \"--port\", metaVar = \"PORT\", usage = \"port number to listen on\")\n+\tint port = org.spearce.jgit.transport.Daemon.DEFAULT_PORT;\n+\n+\t@Option(name = \"--listen\", metaVar = \"HOSTNAME\", usage = \"hostname (or ip) to listen on\")\n+\tString host;\n+\n+\t@Option(name = \"--enable\", metaVar = \"SERVICE\", usage = \"enable the service in all repositories\", multiValued = true)\n+\tfinal List<String> enable = new ArrayList<String>();\n+\n+\t@Option(name = \"--disable\", metaVar = \"SERVICE\", usage = \"disable the service in all repositories\", multiValued = true)\n+\tfinal List<String> disable = new ArrayList<String>();\n+\n+\t@Option(name = \"--allow-override\", metaVar = \"SERVICE\", usage = \"configure the service in daemon.servicename\", multiValued = true)\n+\tfinal List<String> canOverride = new ArrayList<String>();\n+\n+\t@Option(name = \"--forbid-override\", metaVar = \"SERVICE\", usage = \"configure the service in daemon.servicename\", multiValued = true)\n+\tfinal List<String> forbidOverride = new ArrayList<String>();\n+\n+\t@Argument(metaVar = \"DIRECTORY\", usage = \"directories to export\")\n+\tfinal List<File> directory = new ArrayList<File>();\n+\n+\t@Override\n+\tprotected void run() throws Exception {\n+\t\tfinal org.spearce.jgit.transport.Daemon d;\n+\n+\t\td = new org.spearce.jgit.transport.Daemon(\n+\t\t\t\thost != null ? new InetSocketAddress(host, port)\n+\t\t\t\t\t\t: new InetSocketAddress(port));\n+\n+\t\tfor (final String n : enable)\n+\t\t\tservice(d, n).setEnabled(true);\n+\t\tfor (final String n : disable)\n+\t\t\tservice(d, n).setEnabled(false);\n+\n+\t\tfor (final String n : canOverride)\n+\t\t\tservice(d, n).setOverridable(true);\n+\t\tfor (final String n : forbidOverride)\n+\t\t\tservice(d, n).setOverridable(false);\n+\n+\t\tif (directory.isEmpty()) {\n+\t\t\texport(d, db);\n+\t\t} else {\n+\t\t\tfor (final File f : directory) {\n+\t\t\t\tout.println(\"Exporting \" + f.getAbsolutePath());\n+\t\t\t\td.exportDirectory(f);\n+\t\t\t}\n+\t\t}\n+\t\td.start();\n+\t\tout.println(\"Listening on \" + d.getAddress());\n+\t}\n+\n+\tprivate DaemonService service(final org.spearce.jgit.transport.Daemon d,\n+\t\t\tfinal String n) {\n+\t\tfinal DaemonService svc = d.getService(n);\n+\t\tif (svc == null)\n+\t\t\tthrow die(\"Service '\" + n + \"' not supported\");\n+\t\treturn svc;\n+\t}\n+\n+\tprivate void export(final org.spearce.jgit.transport.Daemon daemon,\n+\t\t\tfinal Repository repo) {\n+\t\tFile d = repo.getDirectory();\n+\t\tString name = d.getName();\n+\t\twhile (name.equals(\".git\") || name.equals(\".\")) {\n+\t\t\td = d.getParentFile();\n+\t\t\tname = d.getName();\n+\t\t}\n+\t\tif (!name.endsWith(\".git\"))\n+\t\t\tname += \".git\";\n+\n+\t\tout.println(\"Exporting current repository as \\\"\" + name + \"\\\"\");\n+\t\tdaemon.exportRepository(name, repo);\n+\t}\n+}\ndiff --git a/org.spearce.jgit/src/org/spearce/jgit/transport/Daemon.java b/org.spearce.jgit/src/org/spearce/jgit/transport/Daemon.java\nnew file mode 100644\nindex 0000000..c225740\n--- /dev/null\n+++ b/org.spearce.jgit/src/org/spearce/jgit/transport/Daemon.java\n@@ -0,0 +1,309 @@\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.transport;\n+\n+import java.io.BufferedInputStream;\n+import java.io.BufferedOutputStream;\n+import java.io.File;\n+import java.io.IOException;\n+import java.io.InterruptedIOException;\n+import java.net.InetSocketAddress;\n+import java.net.ServerSocket;\n+import java.net.Socket;\n+import java.net.SocketAddress;\n+import java.util.ArrayList;\n+import java.util.Collection;\n+import java.util.HashMap;\n+import java.util.Map;\n+import java.util.regex.Pattern;\n+\n+import org.spearce.jgit.lib.Repository;\n+\n+/** Basic daemon for the anonymous <code>git://</code> transport protocol. */\n+public class Daemon {\n+\t/** 9418: IANA assigned port number for Git. */\n+\tpublic static final int DEFAULT_PORT = 9418;\n+\n+\tprivate static final int BACKLOG = 5;\n+\n+\tprivate static final Pattern SAFE_REPOSITORY_NAME = Pattern\n+\t\t\t.compile(\"^[A-Za-z][A-Za-z0-9/_ -]+(\\\\.git)?$\");\n+\n+\tprivate InetSocketAddress myAddress;\n+\n+\tprivate final DaemonService[] services;\n+\n+\tprivate final ThreadGroup processors;\n+\n+\tprivate Map<String, Repository> exports;\n+\n+\tprivate Collection<File> exportBase;\n+\n+\tprivate boolean run;\n+\n+\tprivate Thread acceptThread;\n+\n+\t/** Configure a daemon to listen on any available network port. */\n+\tpublic Daemon() {\n+\t\tthis(null);\n+\t}\n+\n+\t/**\n+\t * Configure a new daemon for the specified network address.\n+\t * \n+\t * @param addr\n+\t *            address to listen for connections on. If null, any available\n+\t *            port will be chosen on all network interfaces.\n+\t */\n+\tpublic Daemon(final InetSocketAddress addr) {\n+\t\tmyAddress = addr;\n+\t\texports = new HashMap<String, Repository>();\n+\t\texportBase = new ArrayList<File>();\n+\t\tprocessors = new ThreadGroup(\"Git-Daemon\");\n+\n+\t\tservices = new DaemonService[] { new DaemonService(\"receive-pack\",\n+\t\t\t\t\"receivepack\") {\n+\t\t\t@Override\n+\t\t\tprotected void execute(final DaemonClient dc, final Repository db)\n+\t\t\t\t\tthrows IOException {\n+\t\t\t\tfinal ReceivePack rp = new ReceivePack(db);\n+\t\t\t\trp.receive(dc.getInputStream(), dc.getOutputStream(), null);\n+\t\t\t}\n+\t\t} };\n+\t}\n+\n+\t/** @return the address connections are received on. */\n+\tpublic synchronized InetSocketAddress getAddress() {\n+\t\treturn myAddress;\n+\t}\n+\n+\t/**\n+\t * Lookup a supported service so it can be reconfigured.\n+\t * \n+\t * @param name\n+\t *            name of the service; e.g. \"receive-pack\"/\"git-receive-pack\" or\n+\t *            \"upload-pack\"/\"git-upload-pack\".\n+\t * @return the service; null if this daemon implementation doesn't support\n+\t *         the requested service type.\n+\t */\n+\tpublic synchronized DaemonService getService(String name) {\n+\t\tif (!name.startsWith(\"git-\"))\n+\t\t\tname = \"git-\" + name;\n+\t\tfor (final DaemonService s : services) {\n+\t\t\tif (s.getCommandName().equals(name))\n+\t\t\t\treturn s;\n+\t\t}\n+\t\treturn null;\n+\t}\n+\n+\t/**\n+\t * Add a single repository to the set that is exported by this daemon.\n+\t * <p>\n+\t * The existence (or lack-thereof) of <code>git-daemon-export-ok</code> is\n+\t * ignored by this method. The repository is always published.\n+\t * \n+\t * @param name\n+\t *            name the repository will be published under.\n+\t * @param db\n+\t *            the repository instance.\n+\t */\n+\tpublic void exportRepository(final String name, final Repository db) {\n+\t\tsynchronized (exports) {\n+\t\t\texports.put(name, db);\n+\t\t}\n+\t}\n+\n+\t/**\n+\t * Recursively export all Git repositories within a directory.\n+\t * \n+\t * @param dir\n+\t *            the directory to export. This directory must not itself be a\n+\t *            git repository, but any directory below it which has a file\n+\t *            named <code>git-daemon-export-ok</code> will be published.\n+\t */\n+\tpublic void exportDirectory(final File dir) {\n+\t\tsynchronized (exportBase) {\n+\t\t\texportBase.add(dir);\n+\t\t}\n+\t}\n+\n+\t/**\n+\t * Start this daemon on a background thread.\n+\t * \n+\t * @throws IOException\n+\t *             the server socket could not be opened.\n+\t * @throws IllegalStateException\n+\t *             the daemon is already running.\n+\t */\n+\tpublic synchronized void start() throws IOException {\n+\t\tif (acceptThread != null)\n+\t\t\tthrow new IllegalStateException(\"Daemon already running\");\n+\n+\t\tfinal ServerSocket listenSock = new ServerSocket(\n+\t\t\t\tmyAddress != null ? myAddress.getPort() : 0, BACKLOG,\n+\t\t\t\tmyAddress != null ? myAddress.getAddress() : null);\n+\t\tmyAddress = (InetSocketAddress) listenSock.getLocalSocketAddress();\n+\n+\t\trun = true;\n+\t\tacceptThread = new Thread(processors, \"Git-Daemon-Accept\") {\n+\t\t\tpublic void run() {\n+\t\t\t\twhile (isRunning()) {\n+\t\t\t\t\ttry {\n+\t\t\t\t\t\tstartClient(listenSock.accept());\n+\t\t\t\t\t} catch (InterruptedIOException e) {\n+\t\t\t\t\t\t// Test again to see if we should keep accepting.\n+\t\t\t\t\t} catch (IOException e) {\n+\t\t\t\t\t\tbreak;\n+\t\t\t\t\t}\n+\t\t\t\t}\n+\n+\t\t\t\ttry {\n+\t\t\t\t\tlistenSock.close();\n+\t\t\t\t} catch (IOException err) {\n+\t\t\t\t\t//\n+\t\t\t\t} finally {\n+\t\t\t\t\tsynchronized (Daemon.this) {\n+\t\t\t\t\t\tacceptThread = null;\n+\t\t\t\t\t}\n+\t\t\t\t}\n+\t\t\t}\n+\t\t};\n+\t\tacceptThread.start();\n+\t}\n+\n+\t/** @return true if this daemon is receiving connections. */\n+\tpublic synchronized boolean isRunning() {\n+\t\treturn run;\n+\t}\n+\n+\t/** Stop this daemon. */\n+\tpublic synchronized void stop() {\n+\t\tif (acceptThread != null) {\n+\t\t\trun = false;\n+\t\t\tacceptThread.interrupt();\n+\t\t}\n+\t}\n+\n+\tprivate void startClient(final Socket s) {\n+\t\tfinal DaemonClient dc = new DaemonClient(this);\n+\n+\t\tfinal SocketAddress peer = s.getRemoteSocketAddress();\n+\t\tif (peer instanceof InetSocketAddress)\n+\t\t\tdc.setRemoteAddress(((InetSocketAddress) peer).getAddress());\n+\n+\t\tnew Thread(processors, \"Git-Daemon-Client \" + peer.toString()) {\n+\t\t\tpublic void run() {\n+\t\t\t\ttry {\n+\t\t\t\t\tdc.execute(new BufferedInputStream(s.getInputStream()),\n+\t\t\t\t\t\t\tnew BufferedOutputStream(s.getOutputStream()));\n+\t\t\t\t} catch (IOException e) {\n+\t\t\t\t\t// Ignore unexpected IO exceptions from clients\n+\t\t\t\t\te.printStackTrace();\n+\t\t\t\t} finally {\n+\t\t\t\t\ttry {\n+\t\t\t\t\t\ts.getInputStream().close();\n+\t\t\t\t\t} catch (IOException e) {\n+\t\t\t\t\t\t// Ignore close exceptions\n+\t\t\t\t\t}\n+\t\t\t\t\ttry {\n+\t\t\t\t\t\ts.getOutputStream().close();\n+\t\t\t\t\t} catch (IOException e) {\n+\t\t\t\t\t\t// Ignore close exceptions\n+\t\t\t\t\t}\n+\t\t\t\t}\n+\t\t\t}\n+\t\t}.start();\n+\t}\n+\n+\tsynchronized DaemonService matchService(final String cmd) {\n+\t\tfor (final DaemonService d : services) {\n+\t\t\tif (d.handles(cmd))\n+\t\t\t\treturn d;\n+\t\t}\n+\t\treturn null;\n+\t}\n+\n+\tRepository openRepository(String name) {\n+\t\tif (!name.startsWith(\"/\"))\n+\t\t\treturn null;\n+\t\tname = name.substring(1);\n+\n+\t\tRepository db;\n+\t\tsynchronized (exports) {\n+\t\t\tdb = exports.get(name);\n+\t\t\tif (db != null)\n+\t\t\t\treturn db;\n+\n+\t\t\tdb = exports.get(name + \".git\");\n+\t\t\tif (db != null)\n+\t\t\t\treturn db;\n+\t\t}\n+\n+\t\tif (SAFE_REPOSITORY_NAME.matcher(name).matches()) {\n+\t\t\tfinal File[] search;\n+\t\t\tsynchronized (exportBase) {\n+\t\t\t\tsearch = exportBase.toArray(new File[exportBase.size()]);\n+\t\t\t}\n+\t\t\tfor (final File f : search) {\n+\t\t\t\tdb = openRepository(new File(f, name));\n+\t\t\t\tif (db != null)\n+\t\t\t\t\treturn db;\n+\n+\t\t\t\tdb = openRepository(new File(f, name + \".git\"));\n+\t\t\t\tif (db != null)\n+\t\t\t\t\treturn db;\n+\n+\t\t\t\tdb = openRepository(new File(f, name + \"/.git\"));\n+\t\t\t\tif (db != null)\n+\t\t\t\t\treturn db;\n+\t\t\t}\n+\t\t}\n+\t\treturn null;\n+\t}\n+\n+\tprivate Repository openRepository(final File d) {\n+\t\tif (d.isDirectory() && new File(d, \"git-daemon-export-ok\").exists()) {\n+\t\t\ttry {\n+\t\t\t\treturn new Repository(d);\n+\t\t\t} catch (IOException err) {\n+\t\t\t\t// Ignore\n+\t\t\t}\n+\t\t}\n+\t\treturn null;\n+\t}\n+}\ndiff --git a/org.spearce.jgit/src/org/spearce/jgit/transport/DaemonClient.java b/org.spearce.jgit/src/org/spearce/jgit/transport/DaemonClient.java\nnew file mode 100644\nindex 0000000..636cf22\n--- /dev/null\n+++ b/org.spearce.jgit/src/org/spearce/jgit/transport/DaemonClient.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.transport;\n+\n+import java.io.IOException;\n+import java.io.InputStream;\n+import java.io.OutputStream;\n+import java.net.InetAddress;\n+\n+/** Active network client of {@link Daemon}. */\n+public class DaemonClient {\n+\tprivate final Daemon daemon;\n+\n+\tprivate InetAddress peer;\n+\n+\tprivate InputStream rawIn;\n+\n+\tprivate OutputStream rawOut;\n+\n+\tDaemonClient(final Daemon d) {\n+\t\tdaemon = d;\n+\t}\n+\n+\tvoid setRemoteAddress(final InetAddress ia) {\n+\t\tpeer = ia;\n+\t}\n+\n+\t/** @return the daemon which spawned this client. */\n+\tpublic Daemon getDaemon() {\n+\t\treturn daemon;\n+\t}\n+\n+\t/** @return Internet address of the remote client. */\n+\tpublic InetAddress getRemoteAddress() {\n+\t\treturn peer;\n+\t}\n+\n+\t/** @return input stream to read from the connected client. */\n+\tpublic InputStream getInputStream() {\n+\t\treturn rawIn;\n+\t}\n+\n+\t/** @return output stream to send data to the connected client. */\n+\tpublic OutputStream getOutputStream() {\n+\t\treturn rawOut;\n+\t}\n+\n+\tvoid execute(final InputStream in, final OutputStream out)\n+\t\t\tthrows IOException {\n+\t\trawIn = in;\n+\t\trawOut = out;\n+\n+\t\tString cmd = new PacketLineIn(rawIn).readStringNoLF();\n+\t\tif (cmd == null || cmd.length() == 0)\n+\t\t\treturn;\n+\n+\t\tfinal int nul = cmd.indexOf('\\0');\n+\t\tif (nul >= 0) {\n+\t\t\t// Newer clients hide a \"host\" header behind this byte.\n+\t\t\t// Currently we don't use it for anything, so we ignore\n+\t\t\t// this portion of the command.\n+\t\t\t//\n+\t\t\tcmd = cmd.substring(0, nul);\n+\t\t}\n+\n+\t\tfinal DaemonService srv = getDaemon().matchService(cmd);\n+\t\tif (srv == null)\n+\t\t\treturn;\n+\t\tsrv.execute(this, cmd);\n+\t}\n+}\ndiff --git a/org.spearce.jgit/src/org/spearce/jgit/transport/DaemonService.java b/org.spearce.jgit/src/org/spearce/jgit/transport/DaemonService.java\nnew file mode 100644\nindex 0000000..775a506\n--- /dev/null\n+++ b/org.spearce.jgit/src/org/spearce/jgit/transport/DaemonService.java\n@@ -0,0 +1,120 @@\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.transport;\n+\n+import java.io.IOException;\n+\n+import org.spearce.jgit.lib.Repository;\n+\n+/** A service exposed by {@link Daemon} over anonymous <code>git://</code>. */\n+public abstract class DaemonService {\n+\tprivate final String command;\n+\n+\tprivate final String config;\n+\n+\tprivate boolean enabled;\n+\n+\tprivate boolean overridable;\n+\n+\tprotected DaemonService(final String cmdName, final String cfgName) {\n+\t\tcommand = cmdName.startsWith(\"git-\") ? cmdName : \"git-\" + cmdName;\n+\t\tconfig = cfgName;\n+\t\toverridable = true;\n+\t}\n+\n+\t/** @return is this service enabled for invocation? */\n+\tpublic boolean isEnabled() {\n+\t\treturn enabled;\n+\t}\n+\n+\t/**\n+\t * @param on\n+\t *            true to allow this service to be used; false to deny it.\n+\t */\n+\tpublic void setEnabled(final boolean on) {\n+\t\tenabled = on;\n+\t}\n+\n+\t/** @return can this service be configured in the repository config file? */\n+\tpublic boolean isOverridable() {\n+\t\treturn overridable;\n+\t}\n+\n+\t/**\n+\t * @param on\n+\t *            true to permit repositories to override this service's enabled\n+\t *            state with the <code>daemon.servicename</code> config setting.\n+\t */\n+\tpublic void setOverridable(final boolean on) {\n+\t\toverridable = on;\n+\t}\n+\n+\t/** @return name of the command requested by clients. */\n+\tpublic String getCommandName() {\n+\t\treturn command;\n+\t}\n+\n+\t/**\n+\t * Determine if this service can handle the requested command.\n+\t * \n+\t * @param commandLine\n+\t *            input line from the client.\n+\t * @return true if this command can accept the given command line.\n+\t */\n+\tpublic boolean handles(final String commandLine) {\n+\t\treturn command.length() + 1 < commandLine.length()\n+\t\t\t\t&& commandLine.charAt(command.length()) == ' '\n+\t\t\t\t&& commandLine.startsWith(command);\n+\t}\n+\n+\tvoid execute(final DaemonClient client, final String commandLine)\n+\t\t\tthrows IOException {\n+\t\tfinal String name = commandLine.substring(command.length() + 1);\n+\t\tfinal Repository db = client.getDaemon().openRepository(name);\n+\t\tif (db == null)\n+\t\t\treturn;\n+\t\tboolean on = isEnabled();\n+\t\tif (isOverridable())\n+\t\t\ton = db.getConfig().getBoolean(\"daemon\", config, on);\n+\t\tif (on)\n+\t\t\texecute(client, db);\n+\t}\n+\n+\tprotected abstract void execute(DaemonClient client, Repository db)\n+\t\t\tthrows IOException;\n+}\ndiff --git a/org.spearce.jgit/src/org/spearce/jgit/transport/TransportGitAnon.java b/org.spearce.jgit/src/org/spearce/jgit/transport/TransportGitAnon.java\nindex a80c335..a11f293 100644\n--- a/org.spearce.jgit/src/org/spearce/jgit/transport/TransportGitAnon.java\n+++ b/org.spearce.jgit/src/org/spearce/jgit/transport/TransportGitAnon.java\n@@ -56,8 +56,7 @@\n  * source projects, as there are no authentication or authorization overheads.\n  */\n class TransportGitAnon extends PackTransport {\n-\t/** IANA assigned port number for Git. */\n-\tstatic final int GIT_PORT = 9418;\n+\tstatic final int GIT_PORT = Daemon.DEFAULT_PORT;\n \n \tstatic boolean canHandle(final URIish uri) {\n \t\treturn \"git\".equals(uri.getScheme());\n-- \n1.6.1.rc4.301.g5497a\n"},{"id":"99273","messageId":"200901040048.01520.robin.rosenberg@dewire.com","threadId":"16840","inReplyTo":"1229992043-1053-14-git-send-email-spearce@spearce.org","subject":"Re: [JGIT PATCH 13/13] Add basic git daemon support to publish receive-pack","fromName":"Robin Rosenberg","fromEmail":"robin.rosenberg@dewire.com","sentAt":"2009-01-03T23:48:01Z","receivedAt":"2009-01-03T23:48:01Z","isPatch":true,"sender":{"key":"robin.rosenberg@dewire.com","avatar":"https://avatars.githubusercontent.com/u/46357?v=4"},"body":"tisdag 23 december 2008 01:27:23 skrev Shawn O. Pearce:\n> +\tprivate static final Pattern SAFE_REPOSITORY_NAME = Pattern\n> +\t\t\t.compile(\"^[A-Za-z][A-Za-z0-9/_ -]+(\\\\.git)?$\");\n\nThis restriction is too strict. Wouldn't any path not containing \"..\" be valid? In particular this did not work with my \"EGIT.contrib\" repo. I\nhave a lot of repos with names llike \"name.purpose\". Just adding\n'.' to the character set isn't really enough.\n\n-- robin\n"},{"id":"99345","messageId":"20090105024622.GC20973@spearce.org","threadId":"16840","inReplyTo":"200901040048.01520.robin.rosenberg@dewire.com","subject":"[PATCH] Permit a wider range of repository names in jgit daemon requests","fromName":"Shawn O. Pearce","fromEmail":"spearce@spearce.org","sentAt":"2009-01-05T02:46:22Z","receivedAt":"2009-01-05T02:46:22Z","isPatch":true,"sender":{"key":"spearce@spearce.org","avatar":"https://avatars.githubusercontent.com/u/34844?v=4"},"body":"The earlier restriction was too narrow for some applications, for\nexample repositories named \"jgit.dev\" and \"jgit.test\" are perfectly\nvalid Git repositories and should still be able to be served by\nthe daemon.\n\nBy blocking out only uses of \"..\" as a path component and Windows\nUNC paths (by blocking \"\\\") we can reasonably prevent the client\nfrom escaping the base dirctories configured in the daemon.\n\nSigned-off-by: Shawn O. Pearce <spearce@spearce.org>\n---\n  Robin Rosenberg <robin.rosenberg@dewire.com> wrote:\n  > tisdag 23 december 2008 01:27:23 skrev Shawn O. Pearce:\n  > > +\tprivate static final Pattern SAFE_REPOSITORY_NAME = Pattern\n  > > +\t\t\t.compile(\"^[A-Za-z][A-Za-z0-9/_ -]+(\\\\.git)?$\");\n  > \n  > This restriction is too strict. Wouldn't any path not containing \"..\" be valid? In particular this did not work with my \"EGIT.contrib\" repo. I\n  > have a lot of repos with names llike \"name.purpose\". Just adding\n  > '.' to the character set isn't really enough.\n\n  Yup.\n  \n .../src/org/spearce/jgit/transport/Daemon.java     |   42 +++++++++----------\n 1 files changed, 20 insertions(+), 22 deletions(-)\n\ndiff --git a/org.spearce.jgit/src/org/spearce/jgit/transport/Daemon.java b/org.spearce.jgit/src/org/spearce/jgit/transport/Daemon.java\nindex 646c88d..d39fd04 100644\n--- a/org.spearce.jgit/src/org/spearce/jgit/transport/Daemon.java\n+++ b/org.spearce.jgit/src/org/spearce/jgit/transport/Daemon.java\n@@ -51,7 +51,6 @@\n import java.util.Collection;\n import java.util.HashMap;\n import java.util.Map;\n-import java.util.regex.Pattern;\n \n import org.spearce.jgit.lib.Repository;\n \n@@ -62,9 +61,6 @@\n \n \tprivate static final int BACKLOG = 5;\n \n-\tprivate static final Pattern SAFE_REPOSITORY_NAME = Pattern\n-\t\t\t.compile(\"^[A-Za-z][A-Za-z0-9/_ -]+(\\\\.git)?$\");\n-\n \tprivate InetSocketAddress myAddress;\n \n \tprivate final DaemonService[] services;\n@@ -292,24 +288,26 @@ synchronized (exports) {\n \t\t\t\treturn db;\n \t\t}\n \n-\t\tif (SAFE_REPOSITORY_NAME.matcher(name).matches()) {\n-\t\t\tfinal File[] search;\n-\t\t\tsynchronized (exportBase) {\n-\t\t\t\tsearch = exportBase.toArray(new File[exportBase.size()]);\n-\t\t\t}\n-\t\t\tfor (final File f : search) {\n-\t\t\t\tdb = openRepository(new File(f, name));\n-\t\t\t\tif (db != null)\n-\t\t\t\t\treturn db;\n-\n-\t\t\t\tdb = openRepository(new File(f, name + \".git\"));\n-\t\t\t\tif (db != null)\n-\t\t\t\t\treturn db;\n-\n-\t\t\t\tdb = openRepository(new File(f, name + \"/.git\"));\n-\t\t\t\tif (db != null)\n-\t\t\t\t\treturn db;\n-\t\t\t}\n+\t\tif (name.startsWith(\"../\") || name.contains(\"/../\")\n+\t\t\t\t|| name.contains(\"\\\\\"))\n+\t\t\treturn null;\n+\n+\t\tfinal File[] search;\n+\t\tsynchronized (exportBase) {\n+\t\t\tsearch = exportBase.toArray(new File[exportBase.size()]);\n+\t\t}\n+\t\tfor (final File f : search) {\n+\t\t\tdb = openRepository(new File(f, name));\n+\t\t\tif (db != null)\n+\t\t\t\treturn db;\n+\n+\t\t\tdb = openRepository(new File(f, name + \".git\"));\n+\t\t\tif (db != null)\n+\t\t\t\treturn db;\n+\n+\t\t\tdb = openRepository(new File(f, name + \"/.git\"));\n+\t\t\tif (db != null)\n+\t\t\t\treturn db;\n \t\t}\n \t\treturn null;\n \t}\n-- \n1.6.1.94.g9388\n\n-- \nShawn.\n"},{"id":"99387","messageId":"200901060007.43909.robin.rosenberg.lists@dewire.com","threadId":"16840","inReplyTo":"20090105024622.GC20973@spearce.org","subject":"Re: [PATCH] Permit a wider range of repository names in jgit daemon requests","fromName":"Robin Rosenberg","fromEmail":"robin.rosenberg.lists@dewire.com","sentAt":"2009-01-05T23:07:43Z","receivedAt":"2009-01-05T23:07:43Z","isPatch":true,"sender":{"key":"robin.rosenberg@dewire.com","avatar":"https://avatars.githubusercontent.com/u/46357?v=4"},"body":"måndag 05 januari 2009 03:46:22 skrev Shawn O. Pearce:\n> The earlier restriction was too narrow for some applications, for\n> example repositories named \"jgit.dev\" and \"jgit.test\" are perfectly\n> valid Git repositories and should still be able to be served by\n> the daemon.\n> \n> By blocking out only uses of \"..\" as a path component and Windows\n> UNC paths (by blocking \"\\\") we can reasonably prevent the client\n> from escaping the base dirctories configured in the daemon.\n> \n> +\t\tif (name.startsWith(\"../\") || name.contains(\"/../\")\n> +\t\t\t\t|| name.contains(\"\\\\\"))\n\n//host/share also works as UNC path (even the DOS commands support it, provided\nyou quote the paths) and if you block // shuldn't '/', and '[A-Z]:' also be blocked? \n\\\\ is a UNC-prefix only at the beginning of a path so if / need not be filtered, nor \ndoes //. Inside a path \\\\ is the same as \\ AFAIK (except directly after the drive letter.\nThis should probablybe factored out into a utilty so we can have a simple unit test for it.\n\n-- robin\n"}]}