{"thread":{"id":"19178","subject":"[JGIT PATCH] Use pack-*.keep files during fetch and receive-pack to prevent GC","startedAt":"2009-05-04T20:34:52Z","lastAt":"2009-05-04T20:34:52Z","messageCount":1,"participants":["Shawn O. Pearce"],"isPatch":true,"patchVersion":1,"patchTotal":null},"messages":[{"id":"113002","messageId":"1241469292-20784-1-git-send-email-spearce@spearce.org","threadId":"19178","inReplyTo":null,"subject":"[JGIT PATCH] Use pack-*.keep files during fetch and receive-pack to prevent GC","fromName":"Shawn O. Pearce","fromEmail":"spearce@spearce.org","sentAt":"2009-05-04T20:34:52Z","receivedAt":"2009-05-04T20:34:52Z","isPatch":true,"sender":{"key":"spearce@spearce.org","avatar":"https://avatars.githubusercontent.com/u/34844?v=4"},"body":"C Git creates a pack-*.keep for a pack file while the pack file is\nbeing stored on disk between the time it is initially created by\nindex-pack and when all refs finished being updated with the new\nvalues.  By creating this file we permit concurrent \"git gc\" or\n\"git repack\" in a repository while a fetch or push into the same\nrepository is occurring.  The pack-*.keep file prevents the gc\ntask from deleting the pack file, which is necessary to prevent\nthe objects from disappearing, as the contained objects were not\nreachable from the set of existing refs when the gc started.\n\nSigned-off-by: Shawn O. Pearce <spearce@spearce.org>\n---\n\n I think that between this, what's in master, and \"[PATCH v2 1/2]\n Ensure that a PackFile instance stays invalid when overwritten\",\n its now safe to run \"git repack -a -d\" while a JGit based daemon\n is using the same repository.  Which brings us in line with how\n git-daemon or OpenSSH based access behaves with C Git.\n\n .../src/org/spearce/jgit/lib/PackLock.java         |   84 ++++++++++++++++++++\n .../jgit/transport/BasePackFetchConnection.java    |   18 ++++-\n .../jgit/transport/BundleFetchConnection.java      |   18 ++++-\n .../spearce/jgit/transport/FetchConnection.java    |   25 ++++++-\n .../org/spearce/jgit/transport/FetchProcess.java   |   22 +++++-\n .../src/org/spearce/jgit/transport/IndexPack.java  |   45 ++++++++++-\n .../org/spearce/jgit/transport/ReceivePack.java    |   19 ++++-\n .../jgit/transport/WalkFetchConnection.java        |   18 ++++-\n 8 files changed, 241 insertions(+), 8 deletions(-)\n create mode 100644 org.spearce.jgit/src/org/spearce/jgit/lib/PackLock.java\n\ndiff --git a/org.spearce.jgit/src/org/spearce/jgit/lib/PackLock.java b/org.spearce.jgit/src/org/spearce/jgit/lib/PackLock.java\nnew file mode 100644\nindex 0000000..681c1e1\n--- /dev/null\n+++ b/org.spearce.jgit/src/org/spearce/jgit/lib/PackLock.java\n@@ -0,0 +1,84 @@\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+/** Keeps track of a {@link PackFile}'s associated <code>.keep</code> file. */\n+public class PackLock {\n+\tprivate final File keepFile;\n+\n+\t/**\n+\t * Create a new lock for a pack file.\n+\t *\n+\t * @param packFile\n+\t *            location of the <code>pack-*.pack</code> file.\n+\t */\n+\tpublic PackLock(final File packFile) {\n+\t\tfinal File p = packFile.getParentFile();\n+\t\tfinal String n = packFile.getName();\n+\t\tkeepFile = new File(p, n.substring(0, n.length() - 5) + \".keep\");\n+\t}\n+\n+\t/**\n+\t * Create the <code>pack-*.keep</code> file, with the given message.\n+\t *\n+\t * @param msg\n+\t *            message to store in the file.\n+\t * @return true if the keep file was successfully written; false otherwise.\n+\t * @throws IOException\n+\t *             the keep file could not be written.\n+\t */\n+\tpublic boolean lock(String msg) throws IOException {\n+\t\tif (msg == null)\n+\t\t\treturn false;\n+\t\tif (!msg.endsWith(\"\\n\"))\n+\t\t\tmsg += \"\\n\";\n+\t\tfinal LockFile lf = new LockFile(keepFile);\n+\t\tif (!lf.lock())\n+\t\t\treturn false;\n+\t\tlf.write(Constants.encode(msg));\n+\t\treturn lf.commit();\n+\t}\n+\n+\t/** Remove the <code>.keep</code> file that holds this pack in place. */\n+\tpublic void unlock() {\n+\t\tkeepFile.delete();\n+\t}\n+}\ndiff --git a/org.spearce.jgit/src/org/spearce/jgit/transport/BasePackFetchConnection.java b/org.spearce.jgit/src/org/spearce/jgit/transport/BasePackFetchConnection.java\nindex b75e0ef..f4cd946 100644\n--- a/org.spearce.jgit/src/org/spearce/jgit/transport/BasePackFetchConnection.java\n+++ b/org.spearce.jgit/src/org/spearce/jgit/transport/BasePackFetchConnection.java\n@@ -40,6 +40,7 @@\n \n import java.io.IOException;\n import java.util.Collection;\n+import java.util.Collections;\n import java.util.Date;\n import java.util.Set;\n \n@@ -47,6 +48,7 @@\n import org.spearce.jgit.lib.AnyObjectId;\n import org.spearce.jgit.lib.MutableObjectId;\n import org.spearce.jgit.lib.ObjectId;\n+import org.spearce.jgit.lib.PackLock;\n import org.spearce.jgit.lib.ProgressMonitor;\n import org.spearce.jgit.lib.Ref;\n import org.spearce.jgit.revwalk.RevCommit;\n@@ -126,6 +128,10 @@\n \n \tprivate boolean includeTags;\n \n+\tprivate String lockMessage;\n+\n+\tprivate PackLock packLock;\n+\n \tBasePackFetchConnection(final PackTransport packTransport) {\n \t\tsuper(packTransport);\n \t\tincludeTags = transport.getTagOpt() != TagOpt.NO_TAGS;\n@@ -157,6 +163,16 @@ public boolean didFetchTestConnectivity() {\n \t\treturn false;\n \t}\n \n+\tpublic void setPackLockMessage(final String message) {\n+\t\tlockMessage = message;\n+\t}\n+\n+\tpublic Collection<PackLock> getPackLocks() {\n+\t\tif (packLock != null)\n+\t\t\treturn Collections.singleton(packLock);\n+\t\treturn Collections.<PackLock> emptyList();\n+\t}\n+\n \tprotected void doFetch(final ProgressMonitor monitor,\n \t\t\tfinal Collection<Ref> want, final Set<ObjectId> have)\n \t\t\tthrows TransportException {\n@@ -477,7 +493,7 @@ private void receivePack(final ProgressMonitor monitor) throws IOException {\n \t\tip.setFixThin(thinPack);\n \t\tip.setObjectChecking(transport.isCheckFetchedObjects());\n \t\tip.index(monitor);\n-\t\tip.renameAndOpenPack();\n+\t\tpackLock = ip.renameAndOpenPack(lockMessage);\n \t}\n \n \tprivate static class CancelledException extends Exception {\ndiff --git a/org.spearce.jgit/src/org/spearce/jgit/transport/BundleFetchConnection.java b/org.spearce.jgit/src/org/spearce/jgit/transport/BundleFetchConnection.java\nindex 2e2977e..14e0c7d 100644\n--- a/org.spearce.jgit/src/org/spearce/jgit/transport/BundleFetchConnection.java\n+++ b/org.spearce.jgit/src/org/spearce/jgit/transport/BundleFetchConnection.java\n@@ -43,6 +43,7 @@\n import java.io.InputStream;\n import java.util.ArrayList;\n import java.util.Collection;\n+import java.util.Collections;\n import java.util.HashSet;\n import java.util.LinkedHashMap;\n import java.util.List;\n@@ -54,6 +55,7 @@\n import org.spearce.jgit.errors.TransportException;\n import org.spearce.jgit.lib.Constants;\n import org.spearce.jgit.lib.ObjectId;\n+import org.spearce.jgit.lib.PackLock;\n import org.spearce.jgit.lib.ProgressMonitor;\n import org.spearce.jgit.lib.Ref;\n import org.spearce.jgit.revwalk.RevCommit;\n@@ -75,6 +77,10 @@\n \n \tfinal Set<ObjectId> prereqs = new HashSet<ObjectId>();\n \n+\tprivate String lockMessage;\n+\n+\tprivate PackLock packLock;\n+\n \tBundleFetchConnection(Transport transportBundle, final InputStream src) throws TransportException {\n \t\ttransport = transportBundle;\n \t\tbin = new BufferedInputStream(src, IndexPack.BUFFER_SIZE);\n@@ -158,7 +164,7 @@ protected void doFetch(final ProgressMonitor monitor,\n \t\ttry {\n \t\t\tfinal IndexPack ip = newIndexPack();\n \t\t\tip.index(monitor);\n-\t\t\tip.renameAndOpenPack();\n+\t\t\tpackLock = ip.renameAndOpenPack(lockMessage);\n \t\t} catch (IOException err) {\n \t\t\tclose();\n \t\t\tthrow new TransportException(transport.uri, err.getMessage(), err);\n@@ -168,6 +174,16 @@ protected void doFetch(final ProgressMonitor monitor,\n \t\t}\n \t}\n \n+\tpublic void setPackLockMessage(final String message) {\n+\t\tlockMessage = message;\n+\t}\n+\n+\tpublic Collection<PackLock> getPackLocks() {\n+\t\tif (packLock != null)\n+\t\t\treturn Collections.singleton(packLock);\n+\t\treturn Collections.<PackLock> emptyList();\n+\t}\n+\n \tprivate IndexPack newIndexPack() throws IOException {\n \t\tfinal IndexPack ip = IndexPack.create(transport.local, bin);\n \t\tip.setFixThin(true);\ndiff --git a/org.spearce.jgit/src/org/spearce/jgit/transport/FetchConnection.java b/org.spearce.jgit/src/org/spearce/jgit/transport/FetchConnection.java\nindex 61ef219..1138614 100644\n--- a/org.spearce.jgit/src/org/spearce/jgit/transport/FetchConnection.java\n+++ b/org.spearce.jgit/src/org/spearce/jgit/transport/FetchConnection.java\n@@ -42,6 +42,7 @@\n \n import org.spearce.jgit.errors.TransportException;\n import org.spearce.jgit.lib.ObjectId;\n+import org.spearce.jgit.lib.PackLock;\n import org.spearce.jgit.lib.ProgressMonitor;\n import org.spearce.jgit.lib.Ref;\n \n@@ -142,4 +143,26 @@ public void fetch(final ProgressMonitor monitor,\n \t *         the remote peer supplied a complete graph.\n \t */\n \tpublic boolean didFetchTestConnectivity();\n-}\n\\ No newline at end of file\n+\n+\t/**\n+\t * Set the lock message used when holding a pack out of garbage collection.\n+\t * <p>\n+\t * Callers that set a lock message <b>must</b> ensure they call\n+\t * {@link #getPackLocks()} after\n+\t * {@link #fetch(ProgressMonitor, Collection, Set)}, even if an exception\n+\t * was thrown, and release the locks that are held.\n+\t *\n+\t * @param message message to use when holding a pack in place.\n+\t */\n+\tpublic void setPackLockMessage(String message);\n+\t\n+\t/**\n+\t * All locks created by the last\n+\t * {@link #fetch(ProgressMonitor, Collection, Set)} call.\n+\t *\n+\t * @return collection (possibly empty) of locks created by the last call to\n+\t *         fetch. The caller must release these after refs are updated in\n+\t *         order to safely permit garbage collection.\n+\t */\n+\tpublic Collection<PackLock> getPackLocks();\n+}\ndiff --git a/org.spearce.jgit/src/org/spearce/jgit/transport/FetchProcess.java b/org.spearce.jgit/src/org/spearce/jgit/transport/FetchProcess.java\nindex f216cba..08d7d65 100644\n--- a/org.spearce.jgit/src/org/spearce/jgit/transport/FetchProcess.java\n+++ b/org.spearce.jgit/src/org/spearce/jgit/transport/FetchProcess.java\n@@ -57,6 +57,7 @@\n import org.spearce.jgit.lib.Constants;\n import org.spearce.jgit.lib.LockFile;\n import org.spearce.jgit.lib.ObjectId;\n+import org.spearce.jgit.lib.PackLock;\n import org.spearce.jgit.lib.ProgressMonitor;\n import org.spearce.jgit.lib.Ref;\n import org.spearce.jgit.lib.Repository;\n@@ -82,6 +83,8 @@\n \t/** Records to be recorded into FETCH_HEAD. */\n \tprivate final ArrayList<FetchHeadRecord> fetchHeadUpdates = new ArrayList<FetchHeadRecord>();\n \n+\tprivate final ArrayList<PackLock> packLocks = new ArrayList<PackLock>();\n+\n \tprivate FetchConnection conn;\n \n \tFetchProcess(final Transport t, final Collection<RefSpec> f) {\n@@ -94,7 +97,19 @@ void execute(final ProgressMonitor monitor, final FetchResult result)\n \t\taskFor.clear();\n \t\tlocalUpdates.clear();\n \t\tfetchHeadUpdates.clear();\n+\t\tpackLocks.clear();\n+\n+\t\ttry {\n+\t\t\texecuteImp(monitor, result);\n+\t\t} finally {\n+\t\t\tfor (final PackLock lock : packLocks)\n+\t\t\t\tlock.unlock();\n+\t\t}\n+\t}\n \n+\tprivate void executeImp(final ProgressMonitor monitor,\n+\t\t\tfinal FetchResult result) throws NotSupportedException,\n+\t\t\tTransportException {\n \t\tconn = transport.openFetch();\n \t\ttry {\n \t\t\tresult.setAdvertisedRefs(transport.getURI(), conn.getRefsMap());\n@@ -177,7 +192,12 @@ else if (tagopt == TagOpt.FETCH_TAGS)\n \n \tprivate void fetchObjects(final ProgressMonitor monitor)\n \t\t\tthrows TransportException {\n-\t\tconn.fetch(monitor, askFor.values(), have);\n+\t\ttry {\n+\t\t\tconn.setPackLockMessage(\"jgit fetch \" + transport.uri);\n+\t\t\tconn.fetch(monitor, askFor.values(), have);\n+\t\t} finally {\n+\t\t\tpackLocks.addAll(conn.getPackLocks());\n+\t\t}\n \t\tif (transport.isCheckFetchedObjects()\n \t\t\t\t&& !conn.didFetchTestConnectivity() && !askForIsComplete())\n \t\t\tthrow new TransportException(transport.getURI(),\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 eeeae16..1d709ac 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@@ -65,6 +65,7 @@\n import org.spearce.jgit.lib.ObjectIdSubclassMap;\n import org.spearce.jgit.lib.ObjectLoader;\n import org.spearce.jgit.lib.PackIndexWriter;\n+import org.spearce.jgit.lib.PackLock;\n import org.spearce.jgit.lib.ProgressMonitor;\n import org.spearce.jgit.lib.Repository;\n import org.spearce.jgit.lib.WindowCursor;\n@@ -996,9 +997,30 @@ UnresolvedDelta(final long headerOffset, final int crc32) {\n \t *             removed prior to throwing the exception to the caller.\n \t */\n \tpublic void renameAndOpenPack() throws IOException {\n+\t\trenameAndOpenPack(null);\n+\t}\n+\n+\t/**\n+\t * Rename the pack to it's final name and location and open it.\n+\t * <p>\n+\t * If the call completes successfully the repository this IndexPack instance\n+\t * was created with will have the objects in the pack available for reading\n+\t * and use, without needing to scan for packs.\n+\t * \n+\t * @param lockMessage\n+\t *            message to place in the pack-*.keep file. If null, no lock\n+\t *            will be created, and this method returns null.\n+\t * @return the pack lock object, if lockMessage is not null.\n+\t * @throws IOException\n+\t *             The pack could not be inserted into the repository's objects\n+\t *             directory. The pack no longer exists on disk, as it was\n+\t *             removed prior to throwing the exception to the caller.\n+\t */\n+\tpublic PackLock renameAndOpenPack(final String lockMessage)\n+\t\t\tthrows IOException {\n \t\tif (!keepEmpty && entryCount == 0) {\n \t\t\tcleanupTemporaryFiles();\n-\t\t\treturn;\n+\t\t\treturn null;\n \t\t}\n \n \t\tfinal MessageDigest d = Constants.newMessageDigest();\n@@ -1013,21 +1035,37 @@ public void renameAndOpenPack() throws IOException {\n \t\tfinal File packDir = new File(repo.getObjectsDirectory(), \"pack\");\n \t\tfinal File finalPack = new File(packDir, \"pack-\" + name + \".pack\");\n \t\tfinal File finalIdx = new File(packDir, \"pack-\" + name + \".idx\");\n+\t\tfinal PackLock keep = new PackLock(finalPack);\n \n \t\tif (finalPack.exists()) {\n \t\t\t// If the pack is already present we should never replace it.\n \t\t\t//\n \t\t\tcleanupTemporaryFiles();\n-\t\t\treturn;\n+\t\t\treturn null;\n+\t\t}\n+\n+\t\tif (lockMessage != null) {\n+\t\t\t// If we have a reason to create a keep file for this pack, do\n+\t\t\t// so, or fail fast and don't put the pack in place.\n+\t\t\t//\n+\t\t\ttry {\n+\t\t\t\tif (!keep.lock(lockMessage))\n+\t\t\t\t\tthrow new IOException(\"Cannot lock pack in \" + finalPack);\n+\t\t\t} catch (IOException e) {\n+\t\t\t\tcleanupTemporaryFiles();\n+\t\t\t\tthrow e;\n+\t\t\t}\n \t\t}\n \n \t\tif (!dstPack.renameTo(finalPack)) {\n \t\t\tcleanupTemporaryFiles();\n+\t\t\tkeep.unlock();\n \t\t\tthrow new IOException(\"Cannot move pack to \" + finalPack);\n \t\t}\n \n \t\tif (!dstIdx.renameTo(finalIdx)) {\n \t\t\tcleanupTemporaryFiles();\n+\t\t\tkeep.unlock();\n \t\t\tif (!finalPack.delete())\n \t\t\t\tfinalPack.deleteOnExit();\n \t\t\tthrow new IOException(\"Cannot move index to \" + finalIdx);\n@@ -1036,10 +1074,13 @@ public void renameAndOpenPack() throws IOException {\n \t\ttry {\n \t\t\trepo.openPack(finalPack, finalIdx);\n \t\t} catch (IOException err) {\n+\t\t\tkeep.unlock();\n \t\t\tfinalPack.delete();\n \t\t\tfinalIdx.delete();\n \t\t\tthrow err;\n \t\t}\n+\n+\t\treturn lockMessage != null ? keep : null;\n \t}\n \n \tprivate void cleanupTemporaryFiles() {\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 89ddafe..7d20079 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@@ -57,6 +57,7 @@\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.PackLock;\n import org.spearce.jgit.lib.PersonIdent;\n import org.spearce.jgit.lib.Ref;\n import org.spearce.jgit.lib.RefComparator;\n@@ -129,6 +130,9 @@\n \t/** if {@link #enabledCapablities} has {@link #CAPABILITY_REPORT_STATUS} */\n \tprivate boolean reportStatus;\n \n+\t/** Lock around the received pack file, while updating refs. */\n+\tprivate PackLock packLock;\n+\n \t/**\n \t * Create a new pack receive for an open repository.\n \t *\n@@ -379,6 +383,7 @@ public void println() {\n \t\t\t\t\tmsgs.flush();\n \t\t\t\t}\n \t\t\t} finally {\n+\t\t\t\tunlockPack();\n \t\t\t\trawIn = null;\n \t\t\t\trawOut = null;\n \t\t\t\tpckIn = null;\n@@ -416,6 +421,7 @@ private void service() throws IOException {\n \t\t\t\tvalidateCommands();\n \t\t\t\texecuteCommands();\n \t\t\t}\n+\t\t\tunlockPack();\n \n \t\t\tif (reportStatus) {\n \t\t\t\tsendStatusReport(true, new Reporter() {\n@@ -437,6 +443,13 @@ void sendString(final String s) throws IOException {\n \t\t}\n \t}\n \n+\tprivate void unlockPack() {\n+\t\tif (packLock != null) {\n+\t\t\tpackLock.unlock();\n+\t\t\tpackLock = null;\n+\t\t}\n+\t}\n+\n \tprivate void sendAdvertisedRefs() throws IOException {\n \t\trefs = db.getAllRefs();\n \n@@ -534,7 +547,11 @@ private void receivePack() throws IOException {\n \t\tip.setFixThin(true);\n \t\tip.setObjectChecking(isCheckReceivedObjects());\n \t\tip.index(NullProgressMonitor.INSTANCE);\n-\t\tip.renameAndOpenPack();\n+\t\t\n+\t\tString lockMsg = \"jgit receive-pack\";\n+\t\tif (getRefLogIdent() != null)\n+\t\t\tlockMsg += \" from \" + getRefLogIdent().toExternalString();\n+\t\tpackLock = ip.renameAndOpenPack(lockMsg);\n \t}\n \n \tprivate void checkConnectivity() throws IOException {\ndiff --git a/org.spearce.jgit/src/org/spearce/jgit/transport/WalkFetchConnection.java b/org.spearce.jgit/src/org/spearce/jgit/transport/WalkFetchConnection.java\nindex 6912a1f..06b2c87 100644\n--- a/org.spearce.jgit/src/org/spearce/jgit/transport/WalkFetchConnection.java\n+++ b/org.spearce.jgit/src/org/spearce/jgit/transport/WalkFetchConnection.java\n@@ -64,6 +64,7 @@\n import org.spearce.jgit.lib.ObjectChecker;\n import org.spearce.jgit.lib.ObjectId;\n import org.spearce.jgit.lib.PackIndex;\n+import org.spearce.jgit.lib.PackLock;\n import org.spearce.jgit.lib.ProgressMonitor;\n import org.spearce.jgit.lib.Ref;\n import org.spearce.jgit.lib.Repository;\n@@ -167,6 +168,10 @@\n \t */\n \tprivate final HashMap<ObjectId, List<Throwable>> fetchErrors;\n \n+\tprivate String lockMessage;\n+\n+\tprivate final List<PackLock> packLocks;\n+\n \tWalkFetchConnection(final WalkTransport t, final WalkRemoteObjectDatabase w) {\n \t\tTransport wt = (Transport)t;\n \t\tlocal = wt.local;\n@@ -185,6 +190,7 @@ WalkFetchConnection(final WalkTransport t, final WalkRemoteObjectDatabase w) {\n \t\tnoAlternatesYet.add(w);\n \n \t\tfetchErrors = new HashMap<ObjectId, List<Throwable>>();\n+\t\tpackLocks = new ArrayList<PackLock>(4);\n \n \t\trevWalk = new RevWalk(local);\n \t\ttreeWalk = new TreeWalk(local);\n@@ -215,6 +221,14 @@ protected void doFetch(final ProgressMonitor monitor,\n \t\t}\n \t}\n \n+\tpublic Collection<PackLock> getPackLocks() {\n+\t\treturn packLocks;\n+\t}\n+\n+\tpublic void setPackLockMessage(final String message) {\n+\t\tlockMessage = message;\n+\t}\n+\n \t@Override\n \tpublic void close() {\n \t\tfor (final RemotePack p : unfetchedPacks)\n@@ -857,7 +871,9 @@ void downloadPack(final ProgressMonitor monitor) throws IOException {\n \t\t\tip.setFixThin(false);\n \t\t\tip.setObjectChecker(objCheck);\n \t\t\tip.index(monitor);\n-\t\t\tip.renameAndOpenPack();\n+\t\t\tfinal PackLock keep = ip.renameAndOpenPack(lockMessage);\n+\t\t\tif (keep != null)\n+\t\t\t\tpackLocks.add(keep);\n \t\t}\n \t}\n }\n-- \n1.6.3.rc4.206.g03e16\n"}]}