{"thread":{"id":"20087","subject":"[JGIT PATCH 00/11] Repository instance caching","startedAt":"2009-07-11T20:19:15Z","lastAt":"2009-07-21T15:28:00Z","messageCount":14,"participants":["Shawn O. Pearce","Robin Rosenberg"],"isPatch":true,"patchVersion":1,"patchTotal":11},"messages":[{"id":"117831","messageId":"1247343566-19025-1-git-send-email-spearce@spearce.org","threadId":"20087","inReplyTo":null,"subject":"[JGIT PATCH 00/11] Repository instance caching","fromName":"Shawn O. Pearce","fromEmail":"spearce@spearce.org","sentAt":"2009-07-11T20:19:15Z","receivedAt":"2009-07-11T20:19:15Z","isPatch":true,"sender":{"key":"spearce@spearce.org","avatar":"https://avatars.githubusercontent.com/u/34844?v=4"},"body":"This series lays the groundwork for caching Repository instances\nwithin the JRE, such as a server daemon might want when it is\naccessing multiple repositories, like a git:// daemon needs to do.\n\nWe also modify the way alternates are handled, checking to see if\nan alternate is actually an objects directory for a Repository,\nand if so, using the Repository instance from the cache in the\nalternate chain.  This permits better reuse of pack files windows\nwhen repositories are using alternates.  Based on this support we can\nalso now implement the \".have\" logic in the ReceivePack server code.\n\nI plan to follow onto this series with work to support submodules in\na server setting, e.g. processing the .gitmodules directly from the\nobject database and being able to examine the submodule Repository.\n\nShawn O. Pearce (11):\n  Change Daemon to use concurrent collections for exported repositories\n  Make Daemon's exportAll check not require synchronization\n  Don't retry \".git\" suffix in daemon if already tried\n  Refactor ref advertisement code from server implementations\n  Don't advertise HEAD from ReceivePack\n  Add a use reference counter to Repository\n  Introduce RepositoryCache to cache handles of Repository objects\n  Change Daemon to use RepositoryCache\n  Expose the Repository's ObjectDatabase object\n  Use cached Repository instances when resolving alternates\n  Send .have lines in ReceivePack for alternate repositories\n\n .../org/spearce/jgit/lib/RepositoryCacheTest.java  |  126 +++++++\n .../org/spearce/jgit/lib/RepositoryTestCase.java   |   16 +-\n .../jgit/errors/RepositoryNotFoundException.java   |   65 ++++\n .../jgit/lib/AlternateRepositoryDatabase.java      |  127 +++++++\n .../src/org/spearce/jgit/lib/ObjectDatabase.java   |   16 +-\n .../src/org/spearce/jgit/lib/ObjectDirectory.java  |   18 +-\n .../src/org/spearce/jgit/lib/Repository.java       |   18 +-\n .../src/org/spearce/jgit/lib/RepositoryCache.java  |  386 ++++++++++++++++++++\n .../src/org/spearce/jgit/transport/Daemon.java     |   76 ++---\n .../org/spearce/jgit/transport/DaemonService.java  |   14 +-\n .../org/spearce/jgit/transport/ReceivePack.java    |   67 +---\n .../org/spearce/jgit/transport/RefAdvertiser.java  |  189 ++++++++++\n .../src/org/spearce/jgit/transport/UploadPack.java |   93 +-----\n 13 files changed, 1019 insertions(+), 192 deletions(-)\n create mode 100644 org.spearce.jgit.test/tst/org/spearce/jgit/lib/RepositoryCacheTest.java\n create mode 100644 org.spearce.jgit/src/org/spearce/jgit/errors/RepositoryNotFoundException.java\n create mode 100644 org.spearce.jgit/src/org/spearce/jgit/lib/AlternateRepositoryDatabase.java\n create mode 100644 org.spearce.jgit/src/org/spearce/jgit/lib/RepositoryCache.java\n create mode 100644 org.spearce.jgit/src/org/spearce/jgit/transport/RefAdvertiser.java\n"},{"id":"117833","messageId":"1247343566-19025-2-git-send-email-spearce@spearce.org","threadId":"20087","inReplyTo":"1247343566-19025-1-git-send-email-spearce@spearce.org","subject":"[JGIT PATCH 01/11] Change Daemon to use concurrent collections for exported repositories","fromName":"Shawn O. Pearce","fromEmail":"spearce@spearce.org","sentAt":"2009-07-11T20:19:16Z","receivedAt":"2009-07-11T20:19:16Z","isPatch":true,"sender":{"key":"spearce@spearce.org","avatar":"https://avatars.githubusercontent.com/u/34844?v=4"},"body":"A ConcurrentHashMap can be read without taking a lock, which makes it\nmore efficient when serving the concurrent client connections.  This\nis also true of a CopyOnWriteArrayList, where again the readers far\noutweigh the writers.  Copying on write is much more efficient than\ncopying on read.\n\nSigned-off-by: Shawn O. Pearce <spearce@spearce.org>\n---\n .../src/org/spearce/jgit/transport/Daemon.java     |   36 +++++++------------\n 1 files changed, 13 insertions(+), 23 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 3101d6f..d3f7b2e 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@@ -46,10 +46,10 @@\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.concurrent.ConcurrentHashMap;\n+import java.util.concurrent.CopyOnWriteArrayList;\n \n import org.spearce.jgit.lib.PersonIdent;\n import org.spearce.jgit.lib.Repository;\n@@ -93,8 +93,8 @@ public Daemon() {\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\texports = new ConcurrentHashMap<String, Repository>();\n+\t\texportBase = new CopyOnWriteArrayList<File>();\n \t\tprocessors = new ThreadGroup(\"Git-Daemon\");\n \n \t\tservices = new DaemonService[] {\n@@ -196,9 +196,7 @@ public synchronized void setExportAll(final boolean export) {\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\texports.put(name, db);\n \t}\n \n \t/**\n@@ -210,9 +208,7 @@ public void exportRepository(final String name, final Repository db) {\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\texportBase.add(dir);\n \t}\n \n \t/** @return timeout (in seconds) before aborting an IO operation. */\n@@ -351,21 +347,15 @@ Repository openRepository(String name) {\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+\t\tdb = exports.get(name);\n+\t\tif (db != null)\n+\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+\t\tdb = exports.get(name + \".git\");\n+\t\tif (db != null)\n+\t\t\treturn db;\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\tfor (final File f : exportBase) {\n \t\t\tdb = openRepository(new File(f, name));\n \t\t\tif (db != null)\n \t\t\t\treturn db;\n-- \n1.6.4.rc0.117.g28cb\n"},{"id":"117832","messageId":"1247343566-19025-3-git-send-email-spearce@spearce.org","threadId":"20087","inReplyTo":"1247343566-19025-2-git-send-email-spearce@spearce.org","subject":"[JGIT PATCH 02/11] Make Daemon's exportAll check not require synchronization","fromName":"Shawn O. Pearce","fromEmail":"spearce@spearce.org","sentAt":"2009-07-11T20:19:17Z","receivedAt":"2009-07-11T20:19:17Z","isPatch":true,"sender":{"key":"spearce@spearce.org","avatar":"https://avatars.githubusercontent.com/u/34844?v=4"},"body":"A volatile boolean can be read more efficiently than using a\nsynchronized monitor on \"this\", as it only requires that the\nload of the boolean be examining the most recent copy from the\nmemory bus.  Since the value of the boolean has no further\nimpact on the object state, we don't need to worry about more\nthan ensuring we see the current value of it.\n\nSigned-off-by: Shawn O. Pearce <spearce@spearce.org>\n---\n .../src/org/spearce/jgit/transport/Daemon.java     |    6 +++---\n 1 files changed, 3 insertions(+), 3 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 d3f7b2e..7b4c138 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@@ -67,7 +67,7 @@\n \n \tprivate final ThreadGroup processors;\n \n-\tprivate boolean exportAll;\n+\tprivate volatile boolean exportAll;\n \n \tprivate Map<String, Repository> exports;\n \n@@ -164,7 +164,7 @@ public synchronized DaemonService getService(String name) {\n \t *         ignored.\n \t * @see #setExportAll(boolean)\n \t */\n-\tpublic synchronized boolean isExportAll() {\n+\tpublic boolean isExportAll() {\n \t\treturn exportAll;\n \t}\n \n@@ -180,7 +180,7 @@ public synchronized boolean isExportAll() {\n \t *\n \t * @param export\n \t */\n-\tpublic synchronized void setExportAll(final boolean export) {\n+\tpublic void setExportAll(final boolean export) {\n \t\texportAll = export;\n \t}\n \n-- \n1.6.4.rc0.117.g28cb\n"},{"id":"117834","messageId":"1247343566-19025-4-git-send-email-spearce@spearce.org","threadId":"20087","inReplyTo":"1247343566-19025-3-git-send-email-spearce@spearce.org","subject":"[JGIT PATCH 03/11] Don't retry \".git\" suffix in daemon if already tried","fromName":"Shawn O. Pearce","fromEmail":"spearce@spearce.org","sentAt":"2009-07-11T20:19:18Z","receivedAt":"2009-07-11T20:19:18Z","isPatch":true,"sender":{"key":"spearce@spearce.org","avatar":"https://avatars.githubusercontent.com/u/34844?v=4"},"body":"If the name we already tried ended in \".git\", don't append \".git\"\n(creating \"foo.git.git\") and search the map again.  Instead the\nnames are normalized to always end with \".git\", and \".git\" is put\non the end if it is not present in the user request.\n\nSigned-off-by: Shawn O. Pearce <spearce@spearce.org>\n---\n .../src/org/spearce/jgit/transport/Daemon.java     |   10 ++++------\n 1 files changed, 4 insertions(+), 6 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 7b4c138..f55e049 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@@ -195,7 +195,9 @@ public void setExportAll(final boolean export) {\n \t * @param db\n \t *            the repository instance.\n \t */\n-\tpublic void exportRepository(final String name, final Repository db) {\n+\tpublic void exportRepository(String name, final Repository db) {\n+\t\tif (!name.endsWith(\".git\"))\n+\t\t\tname = name + \".git\";\n \t\texports.put(name, db);\n \t}\n \n@@ -347,11 +349,7 @@ Repository openRepository(String name) {\n \t\tname = name.substring(1);\n \n \t\tRepository db;\n-\t\tdb = exports.get(name);\n-\t\tif (db != null)\n-\t\t\treturn db;\n-\n-\t\tdb = exports.get(name + \".git\");\n+\t\tdb = exports.get(name.endsWith(\".git\") ? name : name + \".git\");\n \t\tif (db != null)\n \t\t\treturn db;\n \n-- \n1.6.4.rc0.117.g28cb\n"},{"id":"117839","messageId":"1247343566-19025-5-git-send-email-spearce@spearce.org","threadId":"20087","inReplyTo":"1247343566-19025-4-git-send-email-spearce@spearce.org","subject":"[JGIT PATCH 04/11] Refactor ref advertisement code from server implementations","fromName":"Shawn O. Pearce","fromEmail":"spearce@spearce.org","sentAt":"2009-07-11T20:19:19Z","receivedAt":"2009-07-11T20:19:19Z","isPatch":true,"sender":{"key":"spearce@spearce.org","avatar":"https://avatars.githubusercontent.com/u/34844?v=4"},"body":"The RefAdvertiser implements the common logic from both UploadPack\nand ReceivePack, as the code here was really very similar.  By using\na support class we can also cleanup the nasty passing of parameters\nfor temporary data, and instead rely on the instance members of the\nsupport class to handle it for us.\n\nA minor change in functionality exists in this change, we now make\na temporary RevFlag and tag all advertised objects in ReceivePack.\nThis is a pointless operation that currently wastes a very small\namount of CPU time during setup of the advertisement, but is so\nminor that its just not worth optimizing out.  The code reuse is\nmuch more important for maintenance.\n\nSigned-off-by: Shawn O. Pearce <spearce@spearce.org>\n---\n .../org/spearce/jgit/transport/ReceivePack.java    |   61 ++------\n .../org/spearce/jgit/transport/RefAdvertiser.java  |  155 ++++++++++++++++++++\n .../src/org/spearce/jgit/transport/UploadPack.java |   93 ++-----------\n 3 files changed, 177 insertions(+), 132 deletions(-)\n create mode 100644 org.spearce.jgit/src/org/spearce/jgit/transport/RefAdvertiser.java\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\nindex 16b0c57..fd8aa86 100644\n--- a/org.spearce.jgit/src/org/spearce/jgit/transport/ReceivePack.java\n+++ b/org.spearce.jgit/src/org/spearce/jgit/transport/ReceivePack.java\n@@ -46,8 +46,8 @@\n import java.io.PrintWriter;\n import java.util.ArrayList;\n import java.util.Collections;\n+import java.util.HashMap;\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@@ -60,12 +60,12 @@\n import org.spearce.jgit.lib.PackLock;\n import org.spearce.jgit.lib.PersonIdent;\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.RevFlag;\n import org.spearce.jgit.revwalk.RevObject;\n import org.spearce.jgit.revwalk.RevWalk;\n import org.spearce.jgit.transport.ReceiveCommand.Result;\n@@ -503,55 +503,18 @@ private void unlockPack() {\n \t}\n \n \tprivate void sendAdvertisedRefs() throws IOException {\n+\t\tfinal RevFlag advertised = walk.newFlag(\"ADVERTISED\");\n+\t\tfinal RefAdvertiser adv = new RefAdvertiser(pckOut, walk, advertised);\n+\t\tadv.advertiseCapability(CAPABILITY_DELETE_REFS);\n+\t\tadv.advertiseCapability(CAPABILITY_REPORT_STATUS);\n+\t\tif (allowOfsDelta)\n+\t\t\tadv.advertiseCapability(CAPABILITY_OFS_DELTA);\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\tboolean first = true;\n-\t\twhile (i.hasNext()) {\n-\t\t\tfinal Ref r = i.next();\n-\t\t\tif (r.getObjectId() == null)\n-\t\t\t\tcontinue;\n-\t\t\tformat(m, idtmp, r.getObjectId(), r.getOrigName());\n-\t\t\tif (first) {\n-\t\t\t\tfirst = false;\n-\t\t\t\tadvertiseCapabilities(m);\n-\t\t\t}\n-\t\t\twriteAdvertisedRef(m);\n-\t\t}\n-\t\tif (first) {\n-\t\t\tformat(m, idtmp, ObjectId.zeroId(), \"capabilities^{}\");\n-\t\t\tadvertiseCapabilities(m);\n-\t\t\twriteAdvertisedRef(m);\n-\t\t}\n+\t\tadv.send(refs.values());\n+\t\tif (adv.isEmpty())\n+\t\t\tadv.advertiseId(ObjectId.zeroId(), \"capabilities^{}\");\n \t\tpckOut.end();\n-\t}\n-\n-\tprivate void advertiseCapabilities(final StringBuilder m) {\n-\t\tm.append('\\0');\n-\t\tm.append(' ');\n-\t\tm.append(CAPABILITY_DELETE_REFS);\n-\t\tm.append(' ');\n-\t\tm.append(CAPABILITY_REPORT_STATUS);\n-\t\tif (allowOfsDelta) {\n-\t\t\tm.append(' ');\n-\t\t\tm.append(CAPABILITY_OFS_DELTA);\n-\t\t}\n-\t\tm.append(' ');\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\twalk.disposeFlag(advertised);\n \t}\n \n \tprivate void recvCommands() throws IOException {\ndiff --git a/org.spearce.jgit/src/org/spearce/jgit/transport/RefAdvertiser.java b/org.spearce.jgit/src/org/spearce/jgit/transport/RefAdvertiser.java\nnew file mode 100644\nindex 0000000..245891d\n--- /dev/null\n+++ b/org.spearce.jgit/src/org/spearce/jgit/transport/RefAdvertiser.java\n@@ -0,0 +1,155 @@\n+/*\n+ * Copyright (C) 2008, 2009, 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.util.Collection;\n+import java.util.LinkedHashSet;\n+import java.util.Set;\n+\n+import org.spearce.jgit.lib.AnyObjectId;\n+import org.spearce.jgit.lib.Constants;\n+import org.spearce.jgit.lib.Ref;\n+import org.spearce.jgit.lib.RefComparator;\n+import org.spearce.jgit.revwalk.RevFlag;\n+import org.spearce.jgit.revwalk.RevObject;\n+import org.spearce.jgit.revwalk.RevTag;\n+import org.spearce.jgit.revwalk.RevWalk;\n+\n+/** Support for the start of {@link UploadPack} and {@link ReceivePack}. */\n+class RefAdvertiser {\n+\tprivate final PacketLineOut pckOut;\n+\n+\tprivate final RevWalk walk;\n+\n+\tprivate final RevFlag ADVERTISED;\n+\n+\tprivate final StringBuilder tmpLine = new StringBuilder(100);\n+\n+\tprivate final char[] tmpId = new char[2 * Constants.OBJECT_ID_LENGTH];\n+\n+\tprivate final Set<String> capablities = new LinkedHashSet<String>();\n+\n+\tprivate boolean derefTags;\n+\n+\tprivate boolean first = true;\n+\n+\tRefAdvertiser(final PacketLineOut out, final RevWalk protoWalk,\n+\t\t\tfinal RevFlag advertisedFlag) {\n+\t\tpckOut = out;\n+\t\twalk = protoWalk;\n+\t\tADVERTISED = advertisedFlag;\n+\t}\n+\n+\tvoid setDerefTags(final boolean deref) {\n+\t\tderefTags = deref;\n+\t}\n+\n+\tvoid advertiseCapability(String name) {\n+\t\tcapablities.add(name);\n+\t}\n+\n+\tvoid send(final Collection<Ref> refs) throws IOException {\n+\t\tfor (final Ref r : RefComparator.sort(refs)) {\n+\t\t\tfinal RevObject obj = parseAnyOrNull(r.getObjectId());\n+\t\t\tif (obj != null) {\n+\t\t\t\tadvertiseAny(obj, r.getOrigName());\n+\t\t\t\tif (derefTags && obj instanceof RevTag)\n+\t\t\t\t\tadvertiseTag((RevTag) obj, r.getOrigName() + \"^{}\");\n+\t\t\t}\n+\t\t}\n+\t}\n+\n+\tboolean isEmpty() {\n+\t\treturn first;\n+\t}\n+\n+\tprivate RevObject parseAnyOrNull(final AnyObjectId id) {\n+\t\tif (id == null)\n+\t\t\treturn null;\n+\t\ttry {\n+\t\t\treturn walk.parseAny(id);\n+\t\t} catch (IOException e) {\n+\t\t\treturn null;\n+\t\t}\n+\t}\n+\n+\tprivate void advertiseAny(final RevObject obj, final String refName)\n+\t\t\tthrows IOException {\n+\t\tobj.add(ADVERTISED);\n+\t\tadvertiseId(obj, refName);\n+\t}\n+\n+\tprivate void advertiseTag(final RevTag tag, final String refName)\n+\t\t\tthrows IOException {\n+\t\tRevObject o = tag;\n+\t\tdo {\n+\t\t\t// Fully unwrap here so later on we have these already parsed.\n+\t\t\tfinal RevObject target = ((RevTag) o).getObject();\n+\t\t\ttry {\n+\t\t\t\twalk.parseHeaders(target);\n+\t\t\t} catch (IOException err) {\n+\t\t\t\treturn;\n+\t\t\t}\n+\t\t\ttarget.add(ADVERTISED);\n+\t\t\to = target;\n+\t\t} while (o instanceof RevTag);\n+\t\tadvertiseAny(tag.getObject(), refName);\n+\t}\n+\n+\tvoid advertiseId(final AnyObjectId id, final String refName)\n+\t\t\tthrows IOException {\n+\t\ttmpLine.setLength(0);\n+\t\tid.copyTo(tmpId, tmpLine);\n+\t\ttmpLine.append(' ');\n+\t\ttmpLine.append(refName);\n+\t\tif (first) {\n+\t\t\tfirst = false;\n+\t\t\tif (!capablities.isEmpty()) {\n+\t\t\t\ttmpLine.append('\\0');\n+\t\t\t\tfor (final String capName : capablities) {\n+\t\t\t\t\ttmpLine.append(' ');\n+\t\t\t\t\ttmpLine.append(capName);\n+\t\t\t\t}\n+\t\t\t\ttmpLine.append(' ');\n+\t\t\t}\n+\t\t}\n+\t\ttmpLine.append('\\n');\n+\t\tpckOut.writeString(tmpLine.toString());\n+\t}\n+}\ndiff --git a/org.spearce.jgit/src/org/spearce/jgit/transport/UploadPack.java b/org.spearce.jgit/src/org/spearce/jgit/transport/UploadPack.java\nindex b0fa885..179670f 100644\n--- a/org.spearce.jgit/src/org/spearce/jgit/transport/UploadPack.java\n+++ b/org.spearce.jgit/src/org/spearce/jgit/transport/UploadPack.java\n@@ -50,13 +50,11 @@\n import java.util.Set;\n \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.PackWriter;\n import org.spearce.jgit.lib.ProgressMonitor;\n import org.spearce.jgit.lib.Ref;\n-import org.spearce.jgit.lib.RefComparator;\n import org.spearce.jgit.lib.Repository;\n import org.spearce.jgit.revwalk.RevCommit;\n import org.spearce.jgit.revwalk.RevFlag;\n@@ -249,91 +247,20 @@ private void service() throws IOException {\n \t}\n \n \tprivate void sendAdvertisedRefs() throws IOException {\n+\t\tfinal RefAdvertiser adv = new RefAdvertiser(pckOut, walk, ADVERTISED);\n+\t\tadv.advertiseCapability(OPTION_INCLUDE_TAG);\n+\t\tadv.advertiseCapability(OPTION_MULTI_ACK);\n+\t\tadv.advertiseCapability(OPTION_OFS_DELTA);\n+\t\tadv.advertiseCapability(OPTION_SIDE_BAND);\n+\t\tadv.advertiseCapability(OPTION_SIDE_BAND_64K);\n+\t\tadv.advertiseCapability(OPTION_THIN_PACK);\n+\t\tadv.advertiseCapability(OPTION_NO_PROGRESS);\n+\t\tadv.setDerefTags(true);\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\tif (i.hasNext()) {\n-\t\t\tfinal Ref r = i.next();\n-\t\t\tfinal RevObject o = safeParseAny(r.getObjectId());\n-\t\t\tif (o != null) {\n-\t\t\t\tadvertise(m, idtmp, o, r.getOrigName());\n-\t\t\t\tm.append('\\0');\n-\t\t\t\tm.append(' ');\n-\t\t\t\tm.append(OPTION_INCLUDE_TAG);\n-\t\t\t\tm.append(' ');\n-\t\t\t\tm.append(OPTION_MULTI_ACK);\n-\t\t\t\tm.append(' ');\n-\t\t\t\tm.append(OPTION_OFS_DELTA);\n-\t\t\t\tm.append(' ');\n-\t\t\t\tm.append(OPTION_SIDE_BAND);\n-\t\t\t\tm.append(' ');\n-\t\t\t\tm.append(OPTION_SIDE_BAND_64K);\n-\t\t\t\tm.append(' ');\n-\t\t\t\tm.append(OPTION_THIN_PACK);\n-\t\t\t\tm.append(' ');\n-\t\t\t\tm.append(OPTION_NO_PROGRESS);\n-\t\t\t\tm.append(' ');\n-\t\t\t\twriteAdvertisedRef(m);\n-\t\t\t\tif (o instanceof RevTag)\n-\t\t\t\t\twriteAdvertisedTag(m, idtmp, o, r.getName());\n-\t\t\t}\n-\t\t}\n-\t\twhile (i.hasNext()) {\n-\t\t\tfinal Ref r = i.next();\n-\t\t\tif (r.getObjectId() == null)\n-\t\t\t\tcontinue;\n-\t\t\tfinal RevObject o = safeParseAny(r.getObjectId());\n-\t\t\tif (o != null) {\n-\t\t\t\tadvertise(m, idtmp, o, r.getOrigName());\n-\t\t\t\twriteAdvertisedRef(m);\n-\t\t\t\tif (o instanceof RevTag)\n-\t\t\t\t\twriteAdvertisedTag(m, idtmp, o, r.getName());\n-\t\t\t}\n-\t\t}\n+\t\tadv.send(refs.values());\n \t\tpckOut.end();\n \t}\n \n-\tprivate RevObject safeParseAny(final ObjectId id) {\n-\t\ttry {\n-\t\t\treturn walk.parseAny(id);\n-\t\t} catch (IOException e) {\n-\t\t\treturn null;\n-\t\t}\n-\t}\n-\n-\tprivate void advertise(final StringBuilder m, final char[] idtmp,\n-\t\t\tfinal RevObject o, final String name) {\n-\t\to.add(ADVERTISED);\n-\t\tm.setLength(0);\n-\t\to.getId().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 writeAdvertisedTag(final StringBuilder m, final char[] idtmp,\n-\t\t\tfinal RevObject tag, final String name) throws IOException {\n-\t\tRevObject o = tag;\n-\t\twhile (o instanceof RevTag) {\n-\t\t\t// Fully unwrap here so later on we have these already parsed.\n-\t\t\ttry {\n-\t\t\t\twalk.parseHeaders(((RevTag) o).getObject());\n-\t\t\t} catch (IOException err) {\n-\t\t\t\treturn;\n-\t\t\t}\n-\t\t\to = ((RevTag) o).getObject();\n-\t\t\to.add(ADVERTISED);\n-\t\t}\n-\t\tadvertise(m, idtmp, ((RevTag) tag).getObject(), name + \"^{}\");\n-\t\twriteAdvertisedRef(m);\n-\t}\n-\n \tprivate void recvWants() throws IOException {\n \t\tboolean isFirst = true;\n \t\tfor (;; isFirst = false) {\n-- \n1.6.4.rc0.117.g28cb\n"},{"id":"117838","messageId":"1247343566-19025-6-git-send-email-spearce@spearce.org","threadId":"20087","inReplyTo":"1247343566-19025-5-git-send-email-spearce@spearce.org","subject":"[JGIT PATCH 05/11] Don't advertise HEAD from ReceivePack","fromName":"Shawn O. Pearce","fromEmail":"spearce@spearce.org","sentAt":"2009-07-11T20:19:20Z","receivedAt":"2009-07-11T20:19:20Z","isPatch":true,"sender":{"key":"spearce@spearce.org","avatar":"https://avatars.githubusercontent.com/u/34844?v=4"},"body":"The HEAD ref cannot be pushed to by a client, if it is a detached HEAD\nthe client shouldn't be permitted to change it, if it is a symref to\nanother ref then the client should update the destination ref and not\nthe symref.  Instead offer the HEAD ref as a \".have\" line, which is an\ninvalid ref that the client can't update but still lets the client know\nwe have its target object reachable.\n\nSigned-off-by: Shawn O. Pearce <spearce@spearce.org>\n---\n .../org/spearce/jgit/transport/ReceivePack.java    |    5 ++++-\n .../org/spearce/jgit/transport/RefAdvertiser.java  |   15 +++++++++++++++\n 2 files changed, 19 insertions(+), 1 deletions(-)\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\nindex fd8aa86..1c490af 100644\n--- a/org.spearce.jgit/src/org/spearce/jgit/transport/ReceivePack.java\n+++ b/org.spearce.jgit/src/org/spearce/jgit/transport/ReceivePack.java\n@@ -509,8 +509,11 @@ private void sendAdvertisedRefs() throws IOException {\n \t\tadv.advertiseCapability(CAPABILITY_REPORT_STATUS);\n \t\tif (allowOfsDelta)\n \t\t\tadv.advertiseCapability(CAPABILITY_OFS_DELTA);\n-\t\trefs = db.getAllRefs();\n+\t\trefs = new HashMap<String, Ref>(db.getAllRefs());\n+\t\tfinal Ref head = refs.remove(Constants.HEAD);\n \t\tadv.send(refs.values());\n+\t\tif (head != null && head.getName() == head.getOrigName())\n+\t\t\tadv.advertiseHave(head.getObjectId());\n \t\tif (adv.isEmpty())\n \t\t\tadv.advertiseId(ObjectId.zeroId(), \"capabilities^{}\");\n \t\tpckOut.end();\ndiff --git a/org.spearce.jgit/src/org/spearce/jgit/transport/RefAdvertiser.java b/org.spearce.jgit/src/org/spearce/jgit/transport/RefAdvertiser.java\nindex 245891d..91700da 100644\n--- a/org.spearce.jgit/src/org/spearce/jgit/transport/RefAdvertiser.java\n+++ b/org.spearce.jgit/src/org/spearce/jgit/transport/RefAdvertiser.java\n@@ -95,6 +95,15 @@ void send(final Collection<Ref> refs) throws IOException {\n \t\t}\n \t}\n \n+\tvoid advertiseHave(AnyObjectId id) throws IOException {\n+\t\tRevObject obj = parseAnyOrNull(id);\n+\t\tif (obj != null) {\n+\t\t\tadvertiseAnyOnce(obj, \".have\");\n+\t\t\tif (obj instanceof RevTag)\n+\t\t\t\tadvertiseAnyOnce(((RevTag) obj).getObject(), \".have\");\n+\t\t}\n+\t}\n+\n \tboolean isEmpty() {\n \t\treturn first;\n \t}\n@@ -109,6 +118,12 @@ private RevObject parseAnyOrNull(final AnyObjectId id) {\n \t\t}\n \t}\n \n+\tprivate void advertiseAnyOnce(final RevObject obj, final String refName)\n+\t\t\tthrows IOException {\n+\t\tif (!obj.has(ADVERTISED))\n+\t\t\tadvertiseAny(obj, refName);\n+\t}\n+\n \tprivate void advertiseAny(final RevObject obj, final String refName)\n \t\t\tthrows IOException {\n \t\tobj.add(ADVERTISED);\n-- \n1.6.4.rc0.117.g28cb\n"},{"id":"117837","messageId":"1247343566-19025-7-git-send-email-spearce@spearce.org","threadId":"20087","inReplyTo":"1247343566-19025-6-git-send-email-spearce@spearce.org","subject":"[JGIT PATCH 06/11] Add a use reference counter to Repository","fromName":"Shawn O. Pearce","fromEmail":"spearce@spearce.org","sentAt":"2009-07-11T20:19:21Z","receivedAt":"2009-07-11T20:19:21Z","isPatch":true,"sender":{"key":"spearce@spearce.org","avatar":"https://avatars.githubusercontent.com/u/34844?v=4"},"body":"This permits callers to reuse Repository instances across threads,\nby using incrementOpen() to indicate the instance is in use, and\na matched close() to release the usage counter.\n\nSigned-off-by: Shawn O. Pearce <spearce@spearce.org>\n---\n .../src/org/spearce/jgit/lib/Repository.java       |   11 ++++++++++-\n 1 files changed, 10 insertions(+), 1 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 fb72b64..e3ac01b 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@@ -54,6 +54,7 @@\n import java.util.Map;\n import java.util.Set;\n import java.util.Vector;\n+import java.util.concurrent.atomic.AtomicInteger;\n \n import org.spearce.jgit.errors.IncorrectObjectTypeException;\n import org.spearce.jgit.errors.RevisionSyntaxException;\n@@ -85,6 +86,8 @@\n  *\n  */\n public class Repository {\n+\tprivate final AtomicInteger useCnt = new AtomicInteger(1);\n+\n \tprivate final File gitDir;\n \n \tprivate final RepositoryConfig config;\n@@ -721,11 +724,17 @@ private ObjectId resolveSimple(final String revstr) throws IOException {\n \t\treturn r != null ? r.getObjectId() : null;\n \t}\n \n+\t/** Increment the use counter by one, requiring a matched {@link #close()}. */\n+\tpublic void incrementOpen() {\n+\t\tuseCnt.incrementAndGet();\n+\t}\n+\n \t/**\n \t * Close all resources used by this repository\n \t */\n \tpublic void close() {\n-\t\tobjectDatabase.close();\n+\t\tif (useCnt.decrementAndGet() == 0)\n+\t\t\tobjectDatabase.close();\n \t}\n \n \t/**\n-- \n1.6.4.rc0.117.g28cb\n"},{"id":"117842","messageId":"1247343566-19025-8-git-send-email-spearce@spearce.org","threadId":"20087","inReplyTo":"1247343566-19025-7-git-send-email-spearce@spearce.org","subject":"[JGIT PATCH 07/11] Introduce RepositoryCache to cache handles of Repository objects","fromName":"Shawn O. Pearce","fromEmail":"spearce@spearce.org","sentAt":"2009-07-11T20:19:22Z","receivedAt":"2009-07-11T20:19:22Z","isPatch":true,"sender":{"key":"spearce@spearce.org","avatar":"https://avatars.githubusercontent.com/u/34844?v=4"},"body":"In almost any application using JGit the application code wants to\nopen more than one Repository at once.  Most applications wind up\ncreating their own form of this RepositoryCache concept, where we\nattempt to map some unique key (e.g. the directory path on disk)\nto the open Repository instance, so that concurrent accesses to\nthe same Git repository go through the same JGit object and thus\ncan share the same memory pools.\n\nSigned-off-by: Shawn O. Pearce <spearce@spearce.org>\nCC: Constantine Plotnikov <constantine.plotnikov@gmail.com>\n---\n .../org/spearce/jgit/lib/RepositoryCacheTest.java  |  126 +++++++\n .../org/spearce/jgit/lib/RepositoryTestCase.java   |   16 +-\n .../jgit/errors/RepositoryNotFoundException.java   |   65 ++++\n .../src/org/spearce/jgit/lib/RepositoryCache.java  |  386 ++++++++++++++++++++\n 4 files changed, 592 insertions(+), 1 deletions(-)\n create mode 100644 org.spearce.jgit.test/tst/org/spearce/jgit/lib/RepositoryCacheTest.java\n create mode 100644 org.spearce.jgit/src/org/spearce/jgit/errors/RepositoryNotFoundException.java\n create mode 100644 org.spearce.jgit/src/org/spearce/jgit/lib/RepositoryCache.java\n\ndiff --git a/org.spearce.jgit.test/tst/org/spearce/jgit/lib/RepositoryCacheTest.java b/org.spearce.jgit.test/tst/org/spearce/jgit/lib/RepositoryCacheTest.java\nnew file mode 100644\nindex 0000000..f466e00\n--- /dev/null\n+++ b/org.spearce.jgit.test/tst/org/spearce/jgit/lib/RepositoryCacheTest.java\n@@ -0,0 +1,126 @@\n+/*\n+ * Copyright (C) 2009, 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.lib;\n+\n+import java.io.File;\n+import java.io.IOException;\n+\n+import org.spearce.jgit.errors.RepositoryNotFoundException;\n+import org.spearce.jgit.lib.RepositoryCache.FileKey;\n+\n+public class RepositoryCacheTest extends RepositoryTestCase {\n+\tpublic void testNonBareFileKey() {\n+\t\tFile gitdir = db.getDirectory();\n+\t\tFile parent = gitdir.getParentFile();\n+\t\tFile other = new File(parent, \"notagit\");\n+\t\tassertEquals(gitdir, FileKey.exact(gitdir).getFile());\n+\t\tassertEquals(parent, FileKey.exact(parent).getFile());\n+\t\tassertEquals(other, FileKey.exact(other).getFile());\n+\n+\t\tassertEquals(gitdir, FileKey.lenient(gitdir).getFile());\n+\t\tassertEquals(gitdir, FileKey.lenient(parent).getFile());\n+\t\tassertEquals(other, FileKey.lenient(other).getFile());\n+\t}\n+\n+\tpublic void testBareFileKey() throws IOException {\n+\t\tRepository bare = createNewEmptyRepo(true);\n+\t\tFile gitdir = bare.getDirectory();\n+\t\tFile parent = gitdir.getParentFile();\n+\t\tString name = gitdir.getName();\n+\t\tassertTrue(name.endsWith(\".git\"));\n+\t\tname = name.substring(0, name.length() - 4);\n+\n+\t\tassertEquals(gitdir, FileKey.exact(gitdir).getFile());\n+\n+\t\tassertEquals(gitdir, FileKey.lenient(gitdir).getFile());\n+\t\tassertEquals(gitdir, FileKey.lenient(new File(parent, name)).getFile());\n+\t}\n+\n+\tpublic void testFileKeyOpenExisting() throws IOException {\n+\t\tRepository r;\n+\n+\t\tr = new FileKey(db.getDirectory()).open(true);\n+\t\tassertNotNull(r);\n+\t\tassertEquals(db.getDirectory(), r.getDirectory());\n+\t\tr.close();\n+\n+\t\tr = new FileKey(db.getDirectory()).open(false);\n+\t\tassertNotNull(r);\n+\t\tassertEquals(db.getDirectory(), r.getDirectory());\n+\t\tr.close();\n+\t}\n+\n+\tpublic void testFileKeyOpenNew() throws IOException {\n+\t\tfinal Repository n = createNewEmptyRepo(true);\n+\t\tfinal File gitdir = n.getDirectory();\n+\t\tn.close();\n+\t\trecursiveDelete(gitdir);\n+\t\tassertFalse(gitdir.exists());\n+\n+\t\ttry {\n+\t\t\tnew FileKey(gitdir).open(true);\n+\t\t\tfail(\"incorrectly opened a non existant repository\");\n+\t\t} catch (RepositoryNotFoundException e) {\n+\t\t\tassertEquals(\"repository not found: \" + gitdir, e.getMessage());\n+\t\t}\n+\n+\t\tfinal Repository o = new FileKey(gitdir).open(false);\n+\t\tassertNotNull(o);\n+\t\tassertEquals(gitdir, o.getDirectory());\n+\t\tassertFalse(gitdir.exists());\n+\t}\n+\n+\tpublic void testCacheRegisterOpen() throws Exception {\n+\t\tfinal File dir = db.getDirectory();\n+\t\tRepositoryCache.register(db);\n+\t\tassertSame(db, RepositoryCache.open(FileKey.exact(dir)));\n+\n+\t\tassertEquals(\".git\", dir.getName());\n+\t\tfinal File parent = dir.getParentFile();\n+\t\tassertSame(db, RepositoryCache.open(FileKey.lenient(parent)));\n+\t}\n+\n+\tpublic void testCacheOpen() throws Exception {\n+\t\tfinal FileKey loc = FileKey.exact(db.getDirectory());\n+\t\tfinal Repository d2 = RepositoryCache.open(loc);\n+\t\tassertNotSame(db, d2);\n+\t\tassertSame(d2, RepositoryCache.open(FileKey.exact(loc.getFile())));\n+\t\td2.close();\n+\t\td2.close();\n+\t}\n+}\ndiff --git a/org.spearce.jgit.test/tst/org/spearce/jgit/lib/RepositoryTestCase.java b/org.spearce.jgit.test/tst/org/spearce/jgit/lib/RepositoryTestCase.java\nindex 3837ea9..2783180 100644\n--- a/org.spearce.jgit.test/tst/org/spearce/jgit/lib/RepositoryTestCase.java\n+++ b/org.spearce.jgit.test/tst/org/spearce/jgit/lib/RepositoryTestCase.java\n@@ -307,6 +307,7 @@ public void run() {\n \t}\n \n \tprotected void tearDown() throws Exception {\n+\t\tRepositoryCache.clear();\n \t\tdb.close();\n \t\tfor (Repository r : repositoriesToClose)\n \t\t\tr.close();\n@@ -334,8 +335,21 @@ protected void tearDown() throws Exception {\n \t * @throws IOException\n \t */\n \tprotected Repository createNewEmptyRepo() throws IOException {\n+\t\treturn createNewEmptyRepo(false);\n+\t}\n+\n+\t/**\n+\t * Helper for creating extra empty repos\n+\t *\n+\t * @param bare if true, create a bare repository.\n+\t * @return a new empty git repository for testing purposes\n+\t *\n+\t * @throws IOException\n+\t */\n+\tprotected Repository createNewEmptyRepo(boolean bare) throws IOException {\n \t\tfinal File newTestRepo = new File(trashParent, \"new\"\n-\t\t\t\t+ System.currentTimeMillis() + \".\" + (testcount++) + \"/.git\");\n+\t\t\t\t+ System.currentTimeMillis() + \".\" + (testcount++)\n+\t\t\t\t+ (bare ? \"\" : \"/\") + \".git\");\n \t\tassertFalse(newTestRepo.exists());\n \t\tfinal Repository newRepo = new Repository(newTestRepo);\n \t\tnewRepo.create();\ndiff --git a/org.spearce.jgit/src/org/spearce/jgit/errors/RepositoryNotFoundException.java b/org.spearce.jgit/src/org/spearce/jgit/errors/RepositoryNotFoundException.java\nnew file mode 100644\nindex 0000000..f47f577\n--- /dev/null\n+++ b/org.spearce.jgit/src/org/spearce/jgit/errors/RepositoryNotFoundException.java\n@@ -0,0 +1,65 @@\n+/*\n+ * Copyright (C) 2009, 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.errors;\n+\n+import java.io.File;\n+\n+/** Indicates a local repository does not exist. */\n+public class RepositoryNotFoundException extends TransportException {\n+\tprivate static final long serialVersionUID = 1L;\n+\n+\t/**\n+\t * Constructs an exception indicating a local repository does not exist.\n+\t * \n+\t * @param location\n+\t *            description of the repository not found, usually file path.\n+\t */\n+\tpublic RepositoryNotFoundException(final File location) {\n+\t\tthis(location.getPath());\n+\t}\n+\n+\t/**\n+\t * Constructs an exception indicating a local repository does not exist.\n+\t * \n+\t * @param location\n+\t *            description of the repository not found, usually file path.\n+\t */\n+\tpublic RepositoryNotFoundException(final String location) {\n+\t\tsuper(\"repository not found: \" + location);\n+\t}\n+}\ndiff --git a/org.spearce.jgit/src/org/spearce/jgit/lib/RepositoryCache.java b/org.spearce.jgit/src/org/spearce/jgit/lib/RepositoryCache.java\nnew file mode 100644\nindex 0000000..50b4330\n--- /dev/null\n+++ b/org.spearce.jgit/src/org/spearce/jgit/lib/RepositoryCache.java\n@@ -0,0 +1,386 @@\n+/*\n+ * Copyright (C) 2009, 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.lib;\n+\n+import java.io.BufferedReader;\n+import java.io.File;\n+import java.io.FileInputStream;\n+import java.io.IOException;\n+import java.io.InputStreamReader;\n+import java.lang.ref.Reference;\n+import java.lang.ref.WeakReference;\n+import java.util.Iterator;\n+import java.util.Map;\n+import java.util.concurrent.ConcurrentHashMap;\n+\n+import org.spearce.jgit.errors.RepositoryNotFoundException;\n+import org.spearce.jgit.util.FS;\n+\n+/** Cache of active {@link Repository} instances. */\n+public class RepositoryCache {\n+\tprivate static final RepositoryCache cache = new RepositoryCache();\n+\n+\t/**\n+\t * Open an existing repository, reusing a cached instance if possible.\n+\t * <p>\n+\t * When done with the repository, the caller must call\n+\t * {@link Repository#close()} to decrement the repository's usage counter.\n+\t * \n+\t * @param location\n+\t *            where the local repository is. Typically a {@link FileKey}.\n+\t * @return the repository instance requested; caller must close when done.\n+\t * @throws IOException\n+\t *             the repository could not be read (likely its core.version\n+\t *             property is not supported).\n+\t * @throws RepositoryNotFoundException\n+\t *             there is no repository at the given location.\n+\t */\n+\tpublic static Repository open(final Key location) throws IOException,\n+\t\t\tRepositoryNotFoundException {\n+\t\treturn open(location, true);\n+\t}\n+\n+\t/**\n+\t * Open a repository, reusing a cached instance if possible.\n+\t * <p>\n+\t * When done with the repository, the caller must call\n+\t * {@link Repository#close()} to decrement the repository's usage counter.\n+\t * \n+\t * @param location\n+\t *            where the local repository is. Typically a {@link FileKey}.\n+\t * @param mustExist\n+\t *            If true, and the repository is not found, throws {@code\n+\t *            RepositoryNotFoundException}. If false, a repository instance\n+\t *            is created and registered anyway.\n+\t * @return the repository instance requested; caller must close when done.\n+\t * @throws IOException\n+\t *             the repository could not be read (likely its core.version\n+\t *             property is not supported).\n+\t * @throws RepositoryNotFoundException\n+\t *             There is no repository at the given location, only thrown if\n+\t *             {@code mustExist} is true.\n+\t */\n+\tpublic static Repository open(final Key location, final boolean mustExist)\n+\t\t\tthrows IOException {\n+\t\treturn cache.openRepository(location, mustExist);\n+\t}\n+\n+\t/**\n+\t * Register one repository into the cache.\n+\t * <p>\n+\t * During registration the cache automatically increments the usage counter,\n+\t * permitting it to retain the reference. A {@link FileKey} for the\n+\t * repository's {@link Repository#getDirectory()} is used to index the\n+\t * repository in the cache.\n+\t * <p>\n+\t * If another repository already is registered in the cache at this\n+\t * location, the other instance is closed.\n+\t * \n+\t * @param db\n+\t *            repository to register.\n+\t */\n+\tpublic static void register(final Repository db) {\n+\t\tcache.registerRepository(FileKey.exact(db.getDirectory()), db);\n+\t}\n+\n+\t/**\n+\t * Remove a repository from the cache.\n+\t * <p>\n+\t * Removes a repository from the cache, if it is still registered here,\n+\t * permitting it to close.\n+\t * \n+\t * @param db\n+\t *            repository to unregister.\n+\t */\n+\tpublic static void close(final Repository db) {\n+\t\tcache.unregisterRepository(FileKey.exact(db.getDirectory()));\n+\t}\n+\n+\t/** Unregister all repositories from the cache. */\n+\tpublic static void clear() {\n+\t\tcache.clearAll();\n+\t}\n+\n+\tprivate final ConcurrentHashMap<Key, Reference<Repository>> cacheMap;\n+\n+\tprivate final Lock[] openLocks;\n+\n+\tprivate RepositoryCache() {\n+\t\tcacheMap = new ConcurrentHashMap<Key, Reference<Repository>>();\n+\t\topenLocks = new Lock[4];\n+\t\tfor (int i = 0; i < openLocks.length; i++)\n+\t\t\topenLocks[i] = new Lock();\n+\t}\n+\n+\tprivate Repository openRepository(final Key location,\n+\t\t\tfinal boolean mustExist) throws IOException {\n+\t\tReference<Repository> ref = cacheMap.get(location);\n+\t\tRepository db = ref != null ? ref.get() : null;\n+\t\tif (db == null) {\n+\t\t\tsynchronized (lockFor(location)) {\n+\t\t\t\tref = cacheMap.get(location);\n+\t\t\t\tdb = ref != null ? ref.get() : null;\n+\t\t\t\tif (db == null) {\n+\t\t\t\t\tdb = location.open(mustExist);\n+\t\t\t\t\tref = new WeakReference<Repository>(db);\n+\t\t\t\t\tcacheMap.put(location, ref);\n+\t\t\t\t}\n+\t\t\t}\n+\t\t}\n+\t\tdb.incrementOpen();\n+\t\treturn db;\n+\t}\n+\n+\tprivate void registerRepository(final Key location, final Repository db) {\n+\t\tdb.incrementOpen();\n+\t\tWeakReference<Repository> newRef = new WeakReference<Repository>(db);\n+\t\tReference<Repository> oldRef = cacheMap.put(location, newRef);\n+\t\tRepository oldDb = oldRef != null ? oldRef.get() : null;\n+\t\tif (oldDb != null)\n+\t\t\toldDb.close();\n+\t}\n+\n+\tprivate void unregisterRepository(final Key location) {\n+\t\tReference<Repository> oldRef = cacheMap.remove(location);\n+\t\tRepository oldDb = oldRef != null ? oldRef.get() : null;\n+\t\tif (oldDb != null)\n+\t\t\toldDb.close();\n+\t}\n+\n+\tprivate void clearAll() {\n+\t\tfor (int stage = 0; stage < 2; stage++) {\n+\t\t\tfor (Iterator<Map.Entry<Key, Reference<Repository>>> i = cacheMap\n+\t\t\t\t\t.entrySet().iterator(); i.hasNext();) {\n+\t\t\t\tfinal Map.Entry<Key, Reference<Repository>> e = i.next();\n+\t\t\t\tfinal Repository db = e.getValue().get();\n+\t\t\t\tif (db != null)\n+\t\t\t\t\tdb.close();\n+\t\t\t\ti.remove();\n+\t\t\t}\n+\t\t}\n+\t}\n+\n+\tprivate Lock lockFor(final Key location) {\n+\t\treturn openLocks[(location.hashCode() >>> 1) % openLocks.length];\n+\t}\n+\n+\tprivate static class Lock {\n+\t\t// Used only for its monitor.\n+\t}\n+\n+\t/**\n+\t * Abstract hash key for {@link RepositoryCache} entries.\n+\t * <p>\n+\t * A Key instance should be lightweight, and implement hashCode() and\n+\t * equals() such that two Key instances are equal if they represent the same\n+\t * Repository location.\n+\t */\n+\tpublic static interface Key {\n+\t\t/**\n+\t\t * Called by {@link RepositoryCache#open(Key)} if it doesn't exist yet.\n+\t\t * <p>\n+\t\t * If a repository does not exist yet in the cache, the cache will call\n+\t\t * this method to acquire a handle to it.\n+\t\t * \n+\t\t * @param mustExist\n+\t\t *            true if the repository must exist in order to be opened;\n+\t\t *            false if a new non-existent repository is permitted to be\n+\t\t *            created (the caller is responsible for calling create).\n+\t\t * @return the new repository instance.\n+\t\t * @throws IOException\n+\t\t *             the repository could not be read (likely its core.version\n+\t\t *             property is not supported).\n+\t\t * @throws RepositoryNotFoundException\n+\t\t *             There is no repository at the given location, only thrown\n+\t\t *             if {@code mustExist} is true.\n+\t\t */\n+\t\tRepository open(boolean mustExist) throws IOException,\n+\t\t\t\tRepositoryNotFoundException;\n+\t}\n+\n+\t/** Location of a Repository, using the standard java.io.File API. */\n+\tpublic static class FileKey implements Key {\n+\t\t/**\n+\t\t * Obtain a pointer to an exact location on disk.\n+\t\t * <p>\n+\t\t * No guessing is performed, the given location is exactly the GIT_DIR\n+\t\t * directory of the repository.\n+\t\t * \n+\t\t * @param directory\n+\t\t *            location where the repository database is.\n+\t\t * @return a key for the given directory.\n+\t\t * @see #lenient(File)\n+\t\t */\n+\t\tpublic static FileKey exact(final File directory) {\n+\t\t\treturn new FileKey(directory);\n+\t\t}\n+\n+\t\t/**\n+\t\t * Obtain a pointer to a location on disk.\n+\t\t * <p>\n+\t\t * The method performs some basic guessing to locate the repository.\n+\t\t * Searched paths are:\n+\t\t * <ol>\n+\t\t * <li>{@code directory} // assume exact match</li>\n+\t\t * <li>{@code directory} + \"/.git\" // assume working directory</li>\n+\t\t * <li>{@code directory} + \".git\" // assume bare</li>\n+\t\t * </ol>\n+\t\t * \n+\t\t * @param directory\n+\t\t *            location where the repository database might be.\n+\t\t * @return a key for the given directory.\n+\t\t * @see #exact(File)\n+\t\t */\n+\t\tpublic static FileKey lenient(final File directory) {\n+\t\t\tfinal File gitdir = resolve(directory);\n+\t\t\treturn new FileKey(gitdir != null ? gitdir : directory);\n+\t\t}\n+\n+\t\tprivate final File path;\n+\n+\t\t/**\n+\t\t * @param directory\n+\t\t *            exact location of the repository.\n+\t\t */\n+\t\tprotected FileKey(final File directory) {\n+\t\t\tpath = canonical(directory);\n+\t\t}\n+\n+\t\tprivate static File canonical(final File path) {\n+\t\t\ttry {\n+\t\t\t\treturn path.getCanonicalFile();\n+\t\t\t} catch (IOException e) {\n+\t\t\t\treturn path.getAbsoluteFile();\n+\t\t\t}\n+\t\t}\n+\n+\t\t/** @return location supplied to the constructor. */\n+\t\tpublic final File getFile() {\n+\t\t\treturn path;\n+\t\t}\n+\n+\t\tpublic Repository open(final boolean mustExist) throws IOException {\n+\t\t\tif (mustExist && !isGitRepository(path))\n+\t\t\t\tthrow new RepositoryNotFoundException(path);\n+\t\t\treturn new Repository(path);\n+\t\t}\n+\n+\t\t@Override\n+\t\tpublic int hashCode() {\n+\t\t\treturn path.hashCode();\n+\t\t}\n+\n+\t\t@Override\n+\t\tpublic boolean equals(final Object o) {\n+\t\t\treturn o instanceof FileKey && path.equals(((FileKey) o).path);\n+\t\t}\n+\n+\t\t@Override\n+\t\tpublic String toString() {\n+\t\t\treturn path.toString();\n+\t\t}\n+\n+\t\t/**\n+\t\t * Guess if a directory contains a Git repository.\n+\t\t * <p>\n+\t\t * This method guesses by looking for the existence of some key files\n+\t\t * and directories.\n+\t\t * \n+\t\t * @param dir\n+\t\t *            the location of the directory to examine.\n+\t\t * @return true if the directory \"looks like\" a Git repository; false if\n+\t\t *         it doesn't look enough like a Git directory to really be a\n+\t\t *         Git directory.\n+\t\t */\n+\t\tpublic static boolean isGitRepository(final File dir) {\n+\t\t\treturn FS.resolve(dir, \"objects\").exists()\n+\t\t\t\t\t&& FS.resolve(dir, \"refs\").exists()\n+\t\t\t\t\t&& isValidHead(new File(dir, Constants.HEAD));\n+\t\t}\n+\n+\t\tprivate static boolean isValidHead(final File head) {\n+\t\t\tfinal String ref = readFirstLine(head);\n+\t\t\treturn ref != null\n+\t\t\t\t\t&& (ref.startsWith(\"ref: refs/\") || ObjectId.isId(ref));\n+\t\t}\n+\n+\t\tprivate static String readFirstLine(final File head) {\n+\t\t\ttry {\n+\t\t\t\tfinal BufferedReader br = new BufferedReader(\n+\t\t\t\t\t\tnew InputStreamReader(new FileInputStream(head),\n+\t\t\t\t\t\t\t\tConstants.CHARSET));\n+\t\t\t\ttry {\n+\t\t\t\t\treturn br.readLine();\n+\t\t\t\t} finally {\n+\t\t\t\t\tbr.close();\n+\t\t\t\t}\n+\t\t\t} catch (IOException e) {\n+\t\t\t\treturn null;\n+\t\t\t}\n+\t\t}\n+\n+\t\t/**\n+\t\t * Guess the proper path for a Git repository.\n+\t\t * <p>\n+\t\t * The method performs some basic guessing to locate the repository.\n+\t\t * Searched paths are:\n+\t\t * <ol>\n+\t\t * <li>{@code directory} // assume exact match</li>\n+\t\t * <li>{@code directory} + \"/.git\" // assume working directory</li>\n+\t\t * <li>{@code directory} + \".git\" // assume bare</li>\n+\t\t * </ol>\n+\t\t * \n+\t\t * @param directory\n+\t\t *            location to guess from. Several permutations are tried.\n+\t\t * @return the actual directory location if a better match is found;\n+\t\t *         null if there is no suitable match.\n+\t\t */\n+\t\tpublic static File resolve(final File directory) {\n+\t\t\tif (isGitRepository(directory))\n+\t\t\t\treturn directory;\n+\t\t\tif (isGitRepository(new File(directory, \".git\")))\n+\t\t\t\treturn new File(directory, \".git\");\n+\n+\t\t\tfinal String name = directory.getName();\n+\t\t\tfinal File parent = directory.getParentFile();\n+\t\t\tif (isGitRepository(new File(parent, name + \".git\")))\n+\t\t\t\treturn new File(parent, name + \".git\");\n+\t\t\treturn null;\n+\t\t}\n+\t}\n+}\n-- \n1.6.4.rc0.117.g28cb\n"},{"id":"117835","messageId":"1247343566-19025-9-git-send-email-spearce@spearce.org","threadId":"20087","inReplyTo":"1247343566-19025-8-git-send-email-spearce@spearce.org","subject":"[JGIT PATCH 08/11] Change Daemon to use RepositoryCache","fromName":"Shawn O. Pearce","fromEmail":"spearce@spearce.org","sentAt":"2009-07-11T20:19:23Z","receivedAt":"2009-07-11T20:19:23Z","isPatch":true,"sender":{"key":"spearce@spearce.org","avatar":"https://avatars.githubusercontent.com/u/34844?v=4"},"body":"Now that we have a nice JVM wide cache for Repository instances,\nwe can use that inside of the Daemon code to manage setting up\na Repository and locating it on disk using the standard search\nrules defined by FileKey.resolve().\n\nSigned-off-by: Shawn O. Pearce <spearce@spearce.org>\n---\n .../src/org/spearce/jgit/transport/Daemon.java     |   38 +++++++++-----------\n .../org/spearce/jgit/transport/DaemonService.java  |   14 +++++---\n 2 files changed, 26 insertions(+), 26 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 f55e049..790745d 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@@ -53,6 +53,8 @@\n \n import org.spearce.jgit.lib.PersonIdent;\n import org.spearce.jgit.lib.Repository;\n+import org.spearce.jgit.lib.RepositoryCache;\n+import org.spearce.jgit.lib.RepositoryCache.FileKey;\n \n /** Basic daemon for the anonymous <code>git://</code> transport protocol. */\n public class Daemon {\n@@ -199,6 +201,7 @@ public void exportRepository(String name, final Repository db) {\n \t\tif (!name.endsWith(\".git\"))\n \t\t\tname = name + \".git\";\n \t\texports.put(name, db);\n+\t\tRepositoryCache.register(db);\n \t}\n \n \t/**\n@@ -350,34 +353,27 @@ Repository openRepository(String name) {\n \n \t\tRepository db;\n \t\tdb = exports.get(name.endsWith(\".git\") ? name : name + \".git\");\n-\t\tif (db != null)\n+\t\tif (db != null) {\n+\t\t\tdb.incrementOpen();\n \t\t\treturn db;\n+\t\t}\n \n-\t\tfor (final File f : exportBase) {\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\tfor (final File baseDir : exportBase) {\n+\t\t\tfinal File gitdir = FileKey.resolve(new File(baseDir, name));\n+\t\t\tif (gitdir != null && canExport(gitdir))\n+\t\t\t\treturn openRepository(gitdir);\n \t\t}\n \t\treturn null;\n \t}\n \n-\tprivate Repository openRepository(final File d) {\n-\t\tif (d.isDirectory() && canExport(d)) {\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+\tprivate static Repository openRepository(final File gitdir) {\n+\t\ttry {\n+\t\t\treturn RepositoryCache.open(FileKey.exact(gitdir));\n+\t\t} catch (IOException err) {\n+\t\t\t// null signals it \"wasn't found\", which is all that is suitable\n+\t\t\t// for the remote client to know. \n+\t\t\treturn null;\n \t\t}\n-\t\treturn null;\n \t}\n \n \tprivate boolean canExport(final File d) {\ndiff --git a/org.spearce.jgit/src/org/spearce/jgit/transport/DaemonService.java b/org.spearce.jgit/src/org/spearce/jgit/transport/DaemonService.java\nindex c511d5a..817aeee 100644\n--- a/org.spearce.jgit/src/org/spearce/jgit/transport/DaemonService.java\n+++ b/org.spearce.jgit/src/org/spearce/jgit/transport/DaemonService.java\n@@ -108,11 +108,15 @@ void execute(final DaemonClient client, final String commandLine)\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\ttry {\n+\t\t\tboolean on = isEnabled();\n+\t\t\tif (isOverridable())\n+\t\t\t\ton = db.getConfig().getBoolean(\"daemon\", config, on);\n+\t\t\tif (on)\n+\t\t\t\texecute(client, db);\n+\t\t} finally {\n+\t\t\tdb.close();\n+\t\t}\n \t}\n \n \tabstract void execute(DaemonClient client, Repository db)\n-- \n1.6.4.rc0.117.g28cb\n"},{"id":"117836","messageId":"1247343566-19025-10-git-send-email-spearce@spearce.org","threadId":"20087","inReplyTo":"1247343566-19025-9-git-send-email-spearce@spearce.org","subject":"[JGIT PATCH 09/11] Expose the Repository's ObjectDatabase object","fromName":"Shawn O. Pearce","fromEmail":"spearce@spearce.org","sentAt":"2009-07-11T20:19:24Z","receivedAt":"2009-07-11T20:19:24Z","isPatch":true,"sender":{"key":"spearce@spearce.org","avatar":"https://avatars.githubusercontent.com/u/34844?v=4"},"body":"Some code may need direct access to the database reference; this\nactually should be preferred over direct access to the objects\ndirectory as the database is a much more abstract concept.\n\nSigned-off-by: Shawn O. Pearce <spearce@spearce.org>\n---\n .../src/org/spearce/jgit/lib/Repository.java       |    7 +++++++\n 1 files changed, 7 insertions(+), 0 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 e3ac01b..1076fe1 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@@ -187,6 +187,13 @@ public File getObjectsDirectory() {\n \t}\n \n \t/**\n+\t * @return the object database which stores this repository's data.\n+\t */\n+\tpublic ObjectDatabase getObjectDatabase() {\n+\t\treturn objectDatabase;\n+\t}\n+\n+\t/**\n \t * @return the configuration of this repository\n \t */\n \tpublic RepositoryConfig getConfig() {\n-- \n1.6.4.rc0.117.g28cb\n"},{"id":"117841","messageId":"1247343566-19025-11-git-send-email-spearce@spearce.org","threadId":"20087","inReplyTo":"1247343566-19025-10-git-send-email-spearce@spearce.org","subject":"[JGIT PATCH 10/11] Use cached Repository instances when resolving alternates","fromName":"Shawn O. Pearce","fromEmail":"spearce@spearce.org","sentAt":"2009-07-11T20:19:25Z","receivedAt":"2009-07-11T20:19:25Z","isPatch":true,"sender":{"key":"spearce@spearce.org","avatar":"https://avatars.githubusercontent.com/u/34844?v=4"},"body":"Using a cached Repository instance when opening an alternate object\ndatabase ensures that if multiple repositories share the same base\nrepository (e.g. 4 forks of the Linux kernel all share the same\nbase \"linus-2.6.git\" repository) then the base object directory is\nonly monitored and tracked once in the JVM, instead of 4 times.  In\nthe case of the WindowCache, this means we will have greater reuse\nof the windows from the base, as they won't be loaded 4 different\ntimes under different keys.\n\nSigned-off-by: Shawn O. Pearce <spearce@spearce.org>\n---\n .../jgit/lib/AlternateRepositoryDatabase.java      |  127 ++++++++++++++++++++\n .../src/org/spearce/jgit/lib/ObjectDatabase.java   |   16 ++-\n .../src/org/spearce/jgit/lib/ObjectDirectory.java  |   18 +++-\n 3 files changed, 155 insertions(+), 6 deletions(-)\n create mode 100644 org.spearce.jgit/src/org/spearce/jgit/lib/AlternateRepositoryDatabase.java\n\ndiff --git a/org.spearce.jgit/src/org/spearce/jgit/lib/AlternateRepositoryDatabase.java b/org.spearce.jgit/src/org/spearce/jgit/lib/AlternateRepositoryDatabase.java\nnew file mode 100644\nindex 0000000..ee4c4cf\n--- /dev/null\n+++ b/org.spearce.jgit/src/org/spearce/jgit/lib/AlternateRepositoryDatabase.java\n@@ -0,0 +1,127 @@\n+/*\n+ * Copyright (C) 2009, 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.lib;\n+\n+import java.io.IOException;\n+import java.util.Collection;\n+\n+/**\n+ * An ObjectDatabase of another {@link Repository}.\n+ * <p>\n+ * This {@code ObjectDatabase} wraps around another {@code Repository}'s object\n+ * database, providing its contents to the caller, and closing the Repository\n+ * when this database is closed. The primary user of this class is\n+ * {@link ObjectDirectory}, when the {@code info/alternates} file points at the\n+ * {@code objects/} directory of another repository.\n+ */\n+public final class AlternateRepositoryDatabase extends ObjectDatabase {\n+\tprivate final Repository repository;\n+\n+\tprivate final ObjectDatabase odb;\n+\n+\t/**\n+\t * @param alt\n+\t *            the alternate repository to wrap and export.\n+\t */\n+\tpublic AlternateRepositoryDatabase(final Repository alt) {\n+\t\trepository = alt;\n+\t\todb = repository.getObjectDatabase();\n+\t}\n+\n+\t/** @return the alternate repository objects are borrowed from. */\n+\tpublic Repository getRepository() {\n+\t\treturn repository;\n+\t}\n+\n+\tpublic void closeSelf() {\n+\t\trepository.close();\n+\t}\n+\n+\tpublic void create() throws IOException {\n+\t\trepository.create();\n+\t}\n+\n+\tpublic boolean exists() {\n+\t\treturn odb.exists();\n+\t}\n+\n+\t@Override\n+\tprotected boolean hasObject1(final AnyObjectId objectId) {\n+\t\treturn odb.hasObject1(objectId);\n+\t}\n+\n+\t@Override\n+\tprotected boolean tryAgain1() {\n+\t\treturn odb.tryAgain1();\n+\t}\n+\n+\t@Override\n+\tprotected boolean hasObject2(final String objectName) {\n+\t\treturn odb.hasObject2(objectName);\n+\t}\n+\n+\t@Override\n+\tprotected ObjectLoader openObject1(final WindowCursor curs,\n+\t\t\tfinal AnyObjectId objectId) throws IOException {\n+\t\treturn odb.openObject1(curs, objectId);\n+\t}\n+\n+\t@Override\n+\tprotected ObjectLoader openObject2(final WindowCursor curs,\n+\t\t\tfinal String objectName, final AnyObjectId objectId)\n+\t\t\tthrows IOException {\n+\t\treturn odb.openObject2(curs, objectName, objectId);\n+\t}\n+\n+\t@Override\n+\tvoid openObjectInAllPacks1(final Collection<PackedObjectLoader> out,\n+\t\t\tfinal WindowCursor curs, final AnyObjectId objectId)\n+\t\t\tthrows IOException {\n+\t\todb.openObjectInAllPacks1(out, curs, objectId);\n+\t}\n+\n+\t@Override\n+\tprotected ObjectDatabase[] loadAlternates() throws IOException {\n+\t\treturn odb.getAlternates();\n+\t}\n+\n+\t@Override\n+\tprotected void closeAlternates(final ObjectDatabase[] alt) {\n+\t\t// Do nothing; these belong to odb to close, not us.\n+\t}\n+}\ndiff --git a/org.spearce.jgit/src/org/spearce/jgit/lib/ObjectDatabase.java b/org.spearce.jgit/src/org/spearce/jgit/lib/ObjectDatabase.java\nindex 6a9ba4e..a547052 100644\n--- a/org.spearce.jgit/src/org/spearce/jgit/lib/ObjectDatabase.java\n+++ b/org.spearce.jgit/src/org/spearce/jgit/lib/ObjectDatabase.java\n@@ -108,9 +108,7 @@ public final void closeAlternates() {\n \t\tObjectDatabase[] alt = alternates.get();\n \t\tif (alt != null) {\n \t\t\talternates.set(null);\n-\t\t\tfor (final ObjectDatabase d : alt) {\n-\t\t\t\td.close();\n-\t\t\t}\n+\t\t\tcloseAlternates(alt);\n \t\t}\n \t}\n \n@@ -364,4 +362,16 @@ protected boolean tryAgain1() {\n \tprotected ObjectDatabase[] loadAlternates() throws IOException {\n \t\treturn NO_ALTERNATES;\n \t}\n+\n+\t/**\n+\t * Close the list of alternates returned by {@link #loadAlternates()}.\n+\t *\n+\t * @param alt\n+\t *            the alternate list, from {@link #loadAlternates()}.\n+\t */\n+\tprotected void closeAlternates(ObjectDatabase[] alt) {\n+\t\tfor (final ObjectDatabase d : alt) {\n+\t\t\td.close();\n+\t\t}\n+\t}\n }\ndiff --git a/org.spearce.jgit/src/org/spearce/jgit/lib/ObjectDirectory.java b/org.spearce.jgit/src/org/spearce/jgit/lib/ObjectDirectory.java\nindex d3c43da..7cc459c 100644\n--- a/org.spearce.jgit/src/org/spearce/jgit/lib/ObjectDirectory.java\n+++ b/org.spearce.jgit/src/org/spearce/jgit/lib/ObjectDirectory.java\n@@ -52,6 +52,7 @@\n import java.util.concurrent.atomic.AtomicReference;\n \n import org.spearce.jgit.errors.PackMismatchException;\n+import org.spearce.jgit.lib.RepositoryCache.FileKey;\n import org.spearce.jgit.util.FS;\n \n /**\n@@ -415,11 +416,11 @@ public boolean accept(final File baseDir, final String n) {\n \t@Override\n \tprotected ObjectDatabase[] loadAlternates() throws IOException {\n \t\tfinal BufferedReader br = open(alternatesFile);\n-\t\tfinal List<ObjectDirectory> l = new ArrayList<ObjectDirectory>(4);\n+\t\tfinal List<ObjectDatabase> l = new ArrayList<ObjectDatabase>(4);\n \t\ttry {\n \t\t\tString line;\n \t\t\twhile ((line = br.readLine()) != null) {\n-\t\t\t\tl.add(new ObjectDirectory(FS.resolve(objects, line)));\n+\t\t\t\tl.add(openAlternate(line));\n \t\t\t}\n \t\t} finally {\n \t\t\tbr.close();\n@@ -428,11 +429,22 @@ public boolean accept(final File baseDir, final String n) {\n \t\tif (l.isEmpty()) {\n \t\t\treturn NO_ALTERNATES;\n \t\t}\n-\t\treturn l.toArray(new ObjectDirectory[l.size()]);\n+\t\treturn l.toArray(new ObjectDatabase[l.size()]);\n \t}\n \n \tprivate static BufferedReader open(final File f)\n \t\t\tthrows FileNotFoundException {\n \t\treturn new BufferedReader(new FileReader(f));\n \t}\n+\n+\tprivate ObjectDatabase openAlternate(final String location)\n+\t\t\tthrows IOException {\n+\t\tfinal File objdir = FS.resolve(objects, location);\n+\t\tfinal File parent = objdir.getParentFile();\n+\t\tif (FileKey.isGitRepository(parent)) {\n+\t\t\tfinal Repository db = RepositoryCache.open(FileKey.exact(parent));\n+\t\t\treturn new AlternateRepositoryDatabase(db);\n+\t\t}\n+\t\treturn new ObjectDirectory(objdir);\n+\t}\n }\n-- \n1.6.4.rc0.117.g28cb\n"},{"id":"117840","messageId":"1247343566-19025-12-git-send-email-spearce@spearce.org","threadId":"20087","inReplyTo":"1247343566-19025-11-git-send-email-spearce@spearce.org","subject":"[JGIT PATCH 11/11] Send .have lines in ReceivePack for alternate repositories","fromName":"Shawn O. Pearce","fromEmail":"spearce@spearce.org","sentAt":"2009-07-11T20:19:26Z","receivedAt":"2009-07-11T20:19:26Z","isPatch":true,"sender":{"key":"spearce@spearce.org","avatar":"https://avatars.githubusercontent.com/u/34844?v=4"},"body":"If we are borrowing objects from an alternate repository we should\nsend the alternates' refs, but under the magical \".have\" name, so\nthat a push client won't transmit objects that our alternate already\ncontains for us.  To save bandwidth during the advertisement phase\nof the connection any duplicates are skipped.  This way we don't\nsend a \".have\" if we have already sent that same object name under\na different ref, or under a prior have line.\n\nIn 5d3a6706eef2 we introduced the client side support for this,\nand now we introduce the corresponding server side support.\n\nSigned-off-by: Shawn O. Pearce <spearce@spearce.org>\n---\n .../org/spearce/jgit/transport/ReceivePack.java    |    1 +\n .../org/spearce/jgit/transport/RefAdvertiser.java  |   19 +++++++++++++++++++\n 2 files changed, 20 insertions(+), 0 deletions(-)\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\nindex 1c490af..472e0b5 100644\n--- a/org.spearce.jgit/src/org/spearce/jgit/transport/ReceivePack.java\n+++ b/org.spearce.jgit/src/org/spearce/jgit/transport/ReceivePack.java\n@@ -514,6 +514,7 @@ private void sendAdvertisedRefs() throws IOException {\n \t\tadv.send(refs.values());\n \t\tif (head != null && head.getName() == head.getOrigName())\n \t\t\tadv.advertiseHave(head.getObjectId());\n+\t\tadv.includeAdditionalHaves();\n \t\tif (adv.isEmpty())\n \t\t\tadv.advertiseId(ObjectId.zeroId(), \"capabilities^{}\");\n \t\tpckOut.end();\ndiff --git a/org.spearce.jgit/src/org/spearce/jgit/transport/RefAdvertiser.java b/org.spearce.jgit/src/org/spearce/jgit/transport/RefAdvertiser.java\nindex 91700da..1f6adc5 100644\n--- a/org.spearce.jgit/src/org/spearce/jgit/transport/RefAdvertiser.java\n+++ b/org.spearce.jgit/src/org/spearce/jgit/transport/RefAdvertiser.java\n@@ -42,10 +42,13 @@\n import java.util.LinkedHashSet;\n import java.util.Set;\n \n+import org.spearce.jgit.lib.AlternateRepositoryDatabase;\n import org.spearce.jgit.lib.AnyObjectId;\n import org.spearce.jgit.lib.Constants;\n+import org.spearce.jgit.lib.ObjectDatabase;\n import org.spearce.jgit.lib.Ref;\n import org.spearce.jgit.lib.RefComparator;\n+import org.spearce.jgit.lib.Repository;\n import org.spearce.jgit.revwalk.RevFlag;\n import org.spearce.jgit.revwalk.RevObject;\n import org.spearce.jgit.revwalk.RevTag;\n@@ -104,6 +107,22 @@ void advertiseHave(AnyObjectId id) throws IOException {\n \t\t}\n \t}\n \n+\tvoid includeAdditionalHaves() throws IOException {\n+\t\tadditionalHaves(walk.getRepository().getObjectDatabase());\n+\t}\n+\n+\tprivate void additionalHaves(final ObjectDatabase db) throws IOException {\n+\t\tif (db instanceof AlternateRepositoryDatabase)\n+\t\t\tadditionalHaves(((AlternateRepositoryDatabase) db).getRepository());\n+\t\tfor (ObjectDatabase alt : db.getAlternates())\n+\t\t\tadditionalHaves(alt);\n+\t}\n+\n+\tprivate void additionalHaves(final Repository alt) throws IOException {\n+\t\tfor (final Ref r : alt.getAllRefs().values())\n+\t\t\tadvertiseHave(r.getObjectId());\n+\t}\n+\n \tboolean isEmpty() {\n \t\treturn first;\n \t}\n-- \n1.6.4.rc0.117.g28cb\n"},{"id":"118374","messageId":"200907211722.09631.robin.rosenberg.lists@dewire.com","threadId":"20087","inReplyTo":"1247343566-19025-6-git-send-email-spearce@spearce.org","subject":"Re: [JGIT PATCH 05/11] Don't advertise HEAD from ReceivePack","fromName":"Robin Rosenberg","fromEmail":"robin.rosenberg.lists@dewire.com","sentAt":"2009-07-21T15:22:09Z","receivedAt":"2009-07-21T15:22:09Z","isPatch":true,"sender":{"key":"robin.rosenberg@dewire.com","avatar":"https://avatars.githubusercontent.com/u/46357?v=4"},"body":"lördag 11 juli 2009 22:19:20 skrev \"Shawn O. Pearce\" <spearce@spearce.org>:\n> The HEAD ref cannot be pushed to by a client, if it is a detached HEAD\n> the client shouldn't be permitted to change it, if it is a symref to\n> another ref then the client should update the destination ref and not\n> the symref.  Instead offer the HEAD ref as a \".have\" line, which is an\n> invalid ref that the client can't update but still lets the client know\n> we have its target object reachable.\n> \n> Signed-off-by: Shawn O. Pearce <spearce@spearce.org>\n> ---\n>  .../org/spearce/jgit/transport/ReceivePack.java    |    5 ++++-\n>  .../org/spearce/jgit/transport/RefAdvertiser.java  |   15 +++++++++++++++\n>  2 files changed, 19 insertions(+), 1 deletions(-)\n> \n> diff --git a/org.spearce.jgit/src/org/spearce/jgit/transport/ReceivePack.java b/org.spearce.jgit/src/org/spearce/jgit/transport/ReceivePack.java\n> index fd8aa86..1c490af 100644\n> --- a/org.spearce.jgit/src/org/spearce/jgit/transport/ReceivePack.java\n> +++ b/org.spearce.jgit/src/org/spearce/jgit/transport/ReceivePack.java\n> @@ -509,8 +509,11 @@ private void sendAdvertisedRefs() throws IOException {\n>  \t\tadv.advertiseCapability(CAPABILITY_REPORT_STATUS);\n>  \t\tif (allowOfsDelta)\n>  \t\t\tadv.advertiseCapability(CAPABILITY_OFS_DELTA);\n> -\t\trefs = db.getAllRefs();\n> +\t\trefs = new HashMap<String, Ref>(db.getAllRefs());\n> +\t\tfinal Ref head = refs.remove(Constants.HEAD);\n>  \t\tadv.send(refs.values());\n> +\t\tif (head != null && head.getName() == head.getOrigName())\n> +\t\t\tadv.advertiseHave(head.getObjectId());\n\nThis relies on an implicit guarantee that == works here. Would equals cost\ntoo much? Or perhaps we should document this guarantee as part of the\ninterface.\n\n-- robin\n"},{"id":"118376","messageId":"20090721152800.GQ11191@spearce.org","threadId":"20087","inReplyTo":"200907211722.09631.robin.rosenberg.lists@dewire.com","subject":"Re: [JGIT PATCH 05/11] Don't advertise HEAD from ReceivePack","fromName":"Shawn O. Pearce","fromEmail":"spearce@spearce.org","sentAt":"2009-07-21T15:28:00Z","receivedAt":"2009-07-21T15:28:00Z","isPatch":true,"sender":{"key":"spearce@spearce.org","avatar":"https://avatars.githubusercontent.com/u/34844?v=4"},"body":"Robin Rosenberg <robin.rosenberg.lists@dewire.com> wrote:\n> l?rdag 11 juli 2009 22:19:20 skrev \"Shawn O. Pearce\" <spearce@spearce.org>:\n> > diff --git a/org.spearce.jgit/src/org/spearce/jgit/transport/ReceivePack.java b/org.spearce.jgit/src/org/spearce/jgit/transport/ReceivePack.java\n> > index fd8aa86..1c490af 100644\n> > --- a/org.spearce.jgit/src/org/spearce/jgit/transport/ReceivePack.java\n> > +++ b/org.spearce.jgit/src/org/spearce/jgit/transport/ReceivePack.java\n> > @@ -509,8 +509,11 @@ private void sendAdvertisedRefs() throws IOException {\n> >  \t\tadv.advertiseCapability(CAPABILITY_REPORT_STATUS);\n> >  \t\tif (allowOfsDelta)\n> >  \t\t\tadv.advertiseCapability(CAPABILITY_OFS_DELTA);\n> > -\t\trefs = db.getAllRefs();\n> > +\t\trefs = new HashMap<String, Ref>(db.getAllRefs());\n> > +\t\tfinal Ref head = refs.remove(Constants.HEAD);\n> >  \t\tadv.send(refs.values());\n> > +\t\tif (head != null && head.getName() == head.getOrigName())\n> > +\t\t\tadv.advertiseHave(head.getObjectId());\n> \n> This relies on an implicit guarantee that == works here. Would equals cost\n> too much? Or perhaps we should document this guarantee as part of the\n> interface.\n\nOuch, good point, .equals would be better here.  Can you amend?\nIf its the only issue in the series just amend it to be .equals(),\nif there are other things for me to fix I'll be happy to send an\nupdated patch.\n\n-- \nShawn.\n"}]}