From 9952223e0645fd7a8cddc6093a7f449c6390238d Mon Sep 17 00:00:00 2001 From: Matt Fischer Date: Mon, 26 Jul 2010 22:39:37 -0500 Subject: [PATCH] Implement server support for shallow clones This implements the server side of shallow clones only (i.e. git-upload-pack), not the client side. CQ: 5517 Bug: 301627 Change-Id: Ied5f501f9c8d1fe90ab2ba44fac5fa67ed0035a4 Signed-off-by: Chris Aniszczyk --- .../eclipse/jgit/revwalk/DepthGenerator.java | 176 +++++++++++++ .../org/eclipse/jgit/revwalk/DepthWalk.java | 243 ++++++++++++++++++ .../eclipse/jgit/revwalk/StartGenerator.java | 20 +- .../eclipse/jgit/storage/pack/PackWriter.java | 105 +++++--- .../eclipse/jgit/transport/UploadPack.java | 70 +++++ 5 files changed, 577 insertions(+), 37 deletions(-) create mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/revwalk/DepthGenerator.java create mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/revwalk/DepthWalk.java diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/DepthGenerator.java b/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/DepthGenerator.java new file mode 100644 index 000000000..ad0518635 --- /dev/null +++ b/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/DepthGenerator.java @@ -0,0 +1,176 @@ +/* + * Copyright (C) 2010, Garmin International + * Copyright (C) 2010, Matt Fischer + * and other copyright owners as documented in the project's IP log. + * + * This program and the accompanying materials are made available + * under the terms of the Eclipse Distribution License v1.0 which + * accompanies this distribution, is reproduced below, and is + * available at http://www.eclipse.org/org/documents/edl-v10.php + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * - Neither the name of the Eclipse Foundation, Inc. nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package org.eclipse.jgit.revwalk; + +import java.io.IOException; + +import org.eclipse.jgit.errors.IncorrectObjectTypeException; +import org.eclipse.jgit.errors.MissingObjectException; + +/** + * Only produce commits which are below a specified depth. + * + * @see DepthWalk + */ +class DepthGenerator extends Generator { + private final FIFORevQueue pending; + + private final int depth; + + private final RevWalk walk; + + /** + * Commits which used to be shallow in the client, but which are + * being extended as part of this fetch. These commits should be + * returned to the caller as UNINTERESTING so that their blobs/trees + * can be marked appropriately in the pack writer. + */ + private final RevFlag UNSHALLOW; + + /** + * Commits which the normal framework has marked as UNINTERESTING, + * but which we now care about again. This happens if a client is + * extending a shallow checkout to become deeper--the new commits at + * the bottom of the graph need to be sent, even though they are + * below other commits which the client already has. + */ + private final RevFlag REINTERESTING; + + /** + * @param w + * @param s Parent generator + * @throws MissingObjectException + * @throws IncorrectObjectTypeException + * @throws IOException + */ + DepthGenerator(DepthWalk w, Generator s) throws MissingObjectException, + IncorrectObjectTypeException, IOException { + pending = new FIFORevQueue(); + walk = (RevWalk)w; + + this.depth = w.getDepth(); + this.UNSHALLOW = w.getUnshallowFlag(); + this.REINTERESTING = w.getReinterestingFlag(); + + s.shareFreeList(pending); + + // Begin by sucking out all of the source's commits, and + // adding them to the pending queue + for (;;) { + RevCommit c = s.next(); + if (c == null) + break; + if (((DepthWalk.Commit) c).getDepth() == 0) + pending.add(c); + } + } + + @Override + int outputType() { + return pending.outputType() | HAS_UNINTERESTING; + } + + @Override + void shareFreeList(final BlockRevQueue q) { + pending.shareFreeList(q); + } + + @Override + RevCommit next() throws MissingObjectException, + IncorrectObjectTypeException, IOException { + // Perform a breadth-first descent into the commit graph, + // marking depths as we go. This means that if a commit is + // reachable by more than one route, we are guaranteed to + // arrive by the shortest route first. + for (;;) { + final DepthWalk.Commit c = (DepthWalk.Commit) pending.next(); + if (c == null) + return null; + + if ((c.flags & RevWalk.PARSED) == 0) + c.parseHeaders(walk); + + int newDepth = c.depth + 1; + + for (final RevCommit p : c.parents) { + DepthWalk.Commit dp = (DepthWalk.Commit) p; + + // If no depth has been assigned to this commit, assign + // it now. Since we arrive by the shortest route first, + // this depth is guaranteed to be the smallest value that + // any path could produce. + if (dp.depth == -1) { + dp.depth = newDepth; + + // If the parent is not too deep, add it to the queue + // so that we can produce it later + if (newDepth <= depth) + pending.add(p); + } + + // If the current commit has become unshallowed, everything + // below us is new to the client. Mark its parent as + // re-interesting, and carry that flag downward to all + // of its ancestors. + if(c.has(UNSHALLOW) || c.has(REINTERESTING)) { + p.add(REINTERESTING); + p.flags &= ~RevWalk.UNINTERESTING; + } + } + + // Produce all commits less than the depth cutoff + boolean produce = c.depth <= depth; + + // Unshallow commits are uninteresting, but still need to be sent + // up to the PackWriter so that it will exclude objects correctly. + // All other uninteresting commits should be omitted. + if ((c.flags & RevWalk.UNINTERESTING) != 0 && !c.has(UNSHALLOW)) + produce = false; + + if (produce) + return c; + } + } +} diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/DepthWalk.java b/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/DepthWalk.java new file mode 100644 index 000000000..9c5eaffd7 --- /dev/null +++ b/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/DepthWalk.java @@ -0,0 +1,243 @@ +/* + * Copyright (C) 2010, Garmin International + * Copyright (C) 2010, Matt Fischer + * and other copyright owners as documented in the project's IP log. + * + * This program and the accompanying materials are made available + * under the terms of the Eclipse Distribution License v1.0 which + * accompanies this distribution, is reproduced below, and is + * available at http://www.eclipse.org/org/documents/edl-v10.php + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * - Neither the name of the Eclipse Foundation, Inc. nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package org.eclipse.jgit.revwalk; + +import java.io.IOException; + +import org.eclipse.jgit.errors.IncorrectObjectTypeException; +import org.eclipse.jgit.errors.MissingObjectException; +import org.eclipse.jgit.lib.AnyObjectId; +import org.eclipse.jgit.lib.ObjectReader; +import org.eclipse.jgit.lib.Repository; + +/** Interface for revision walkers that perform depth filtering. */ +public interface DepthWalk { + /** @return Depth to filter to. */ + public int getDepth(); + + /** @return flag marking commits that should become unshallow. */ + public RevFlag getUnshallowFlag(); + + /** @return flag marking commits that are interesting again. */ + public RevFlag getReinterestingFlag(); + + /** RevCommit with a depth (in commits) from a root. */ + public static class Commit extends RevCommit { + /** Depth of this commit in the graph, via shortest path. */ + int depth; + + /** @return depth of this commit, as found by the shortest path. */ + public int getDepth() { + return depth; + } + + /** + * Initialize a new commit. + * + * @param id + * object name for the commit. + */ + protected Commit(AnyObjectId id) { + super(id); + depth = -1; + } + } + + /** Subclass of RevWalk that performs depth filtering. */ + public class RevWalk extends org.eclipse.jgit.revwalk.RevWalk implements DepthWalk { + private final int depth; + + private final RevFlag UNSHALLOW; + + private final RevFlag REINTERESTING; + + /** + * @param repo Repository to walk + * @param depth Maximum depth to return + */ + public RevWalk(Repository repo, int depth) { + super(repo); + + this.depth = depth; + this.UNSHALLOW = newFlag("UNSHALLOW"); + this.REINTERESTING = newFlag("REINTERESTING"); + } + + /** + * @param or ObjectReader to use + * @param depth Maximum depth to return + */ + public RevWalk(ObjectReader or, int depth) { + super(or); + + this.depth = depth; + this.UNSHALLOW = newFlag("UNSHALLOW"); + this.REINTERESTING = newFlag("REINTERESTING"); + } + + /** + * Mark a root commit (i.e., one whose depth should be considered 0.) + * + * @param c + * Commit to mark + * @throws IOException + * @throws IncorrectObjectTypeException + * @throws MissingObjectException + */ + public void markRoot(RevCommit c) throws MissingObjectException, + IncorrectObjectTypeException, IOException { + if (c instanceof Commit) + ((Commit) c).depth = 0; + super.markStart(c); + } + + @Override + protected RevCommit createCommit(AnyObjectId id) { + return new Commit(id); + } + + public int getDepth() { + return depth; + } + + public RevFlag getUnshallowFlag() { + return UNSHALLOW; + } + + public RevFlag getReinterestingFlag() { + return REINTERESTING; + } + } + + /** Subclass of ObjectWalk that performs depth filtering. */ + public class ObjectWalk extends org.eclipse.jgit.revwalk.ObjectWalk implements DepthWalk { + private final int depth; + + private final RevFlag UNSHALLOW; + + private final RevFlag REINTERESTING; + + /** + * @param repo Repository to walk + * @param depth Maximum depth to return + */ + public ObjectWalk(Repository repo, int depth) { + super(repo); + + this.depth = depth; + this.UNSHALLOW = newFlag("UNSHALLOW"); + this.REINTERESTING = newFlag("REINTERESTING"); + } + + /** + * @param or Object Reader + * @param depth Maximum depth to return + */ + public ObjectWalk(ObjectReader or, int depth) { + super(or); + + this.depth = depth; + this.UNSHALLOW = newFlag("UNSHALLOW"); + this.REINTERESTING = newFlag("REINTERESTING"); + } + + /** + * Mark a root commit (i.e., one whose depth should be considered 0.) + * + * @param o + * Commit to mark + * @throws IOException + * @throws IncorrectObjectTypeException + * @throws MissingObjectException + */ + public void markRoot(RevObject o) throws MissingObjectException, + IncorrectObjectTypeException, IOException { + RevObject c = o; + while (c instanceof RevTag) { + c = ((RevTag) c).getObject(); + parseHeaders(c); + } + if (c instanceof Commit) + ((Commit) c).depth = 0; + super.markStart(o); + } + + /** + * Mark an element which used to be shallow in the client, but which + * should now be considered a full commit. Any ancestors of this commit + * should be included in the walk, even if they are the ancestor of an + * uninteresting commit. + * + * @param c + * Commit to mark + * @throws MissingObjectException + * @throws IncorrectObjectTypeException + * @throws IOException + */ + public void markUnshallow(RevObject c) throws MissingObjectException, + IncorrectObjectTypeException, IOException { + if (c instanceof RevCommit) + c.add(UNSHALLOW); + super.markStart(c); + } + + @Override + protected RevCommit createCommit(AnyObjectId id) { + return new Commit(id); + } + + public int getDepth() { + return depth; + } + + public RevFlag getUnshallowFlag() { + return UNSHALLOW; + } + + public RevFlag getReinterestingFlag() { + return REINTERESTING; + } + } +} diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/StartGenerator.java b/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/StartGenerator.java index fbff02778..7fb958bbb 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/StartGenerator.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/StartGenerator.java @@ -132,14 +132,20 @@ class StartGenerator extends Generator { } walker.queue = q; - g = new PendingGenerator(w, pending, rf, pendingOutputType); - if (boundary) { - // Because the boundary generator may produce uninteresting - // commits we cannot allow the pending generator to dispose - // of them early. - // - ((PendingGenerator) g).canDispose = false; + if (walker instanceof DepthWalk) { + DepthWalk dw = (DepthWalk) walker; + g = new DepthGenerator(dw, pending); + } else { + g = new PendingGenerator(w, pending, rf, pendingOutputType); + + if (boundary) { + // Because the boundary generator may produce uninteresting + // commits we cannot allow the pending generator to dispose + // of them early. + // + ((PendingGenerator) g).canDispose = false; + } } if ((g.outputType() & NEEDS_REWRITE) != 0) { diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/storage/pack/PackWriter.java b/org.eclipse.jgit/src/org/eclipse/jgit/storage/pack/PackWriter.java index d3bd693aa..0e3455439 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/storage/pack/PackWriter.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/storage/pack/PackWriter.java @@ -91,6 +91,7 @@ import org.eclipse.jgit.lib.ProgressMonitor; import org.eclipse.jgit.lib.Repository; import org.eclipse.jgit.lib.ThreadSafeProgressMonitor; import org.eclipse.jgit.revwalk.AsyncRevObjectQueue; +import org.eclipse.jgit.revwalk.DepthWalk; import org.eclipse.jgit.revwalk.ObjectWalk; import org.eclipse.jgit.revwalk.RevCommit; import org.eclipse.jgit.revwalk.RevFlag; @@ -195,6 +196,12 @@ public class PackWriter { private boolean pruneCurrentObjectList; + private boolean shallowPack; + + private int depth; + + private Collection unshallowObjects; + /** * Create writer for specified repository. *

@@ -407,6 +414,22 @@ public class PackWriter { tagTargets = objects; } + /** + * Configure this pack for a shallow clone. + * + * @param depth + * maximum depth to traverse the commit graph + * @param unshallow + * objects which used to be shallow on the client, but are being + * extended as part of this fetch + */ + public void setShallowPack(int depth, + Collection unshallow) { + this.shallowPack = true; + this.depth = depth; + this.unshallowObjects = unshallow; + } + /** * Returns objects number in a pack file that was created by this writer. * @@ -538,7 +561,7 @@ public class PackWriter { */ @Deprecated public void preparePack(ProgressMonitor countingMonitor, - final ObjectWalk walk, + ObjectWalk walk, final Collection interestingObjects, final Collection uninterestingObjects) throws IOException { @@ -585,7 +608,11 @@ public class PackWriter { public void preparePack(ProgressMonitor countingMonitor, Set want, Set have) throws IOException { - ObjectWalk ow = new ObjectWalk(reader); + ObjectWalk ow; + if (shallowPack) + ow = new DepthWalk.ObjectWalk(reader, depth); + else + ow = new ObjectWalk(reader); preparePack(countingMonitor, ow, want, have); } @@ -615,12 +642,14 @@ public class PackWriter { * when some I/O problem occur during reading objects. */ public void preparePack(ProgressMonitor countingMonitor, - final ObjectWalk walk, + ObjectWalk walk, final Set interestingObjects, final Set uninterestingObjects) throws IOException { if (countingMonitor == null) countingMonitor = NullProgressMonitor.INSTANCE; + if (shallowPack && !(walk instanceof DepthWalk.ObjectWalk)) + walk = new DepthWalk.ObjectWalk(reader, depth); findObjectsToPack(countingMonitor, walk, interestingObjects, uninterestingObjects); } @@ -1443,9 +1472,9 @@ public class PackWriter { if (tipToPack.containsKey(o)) o.add(inCachedPack); - if (have.contains(o)) { + if (have.contains(o)) haveObjs.add(o); - } else if (want.contains(o)) { + if (want.contains(o)) { o.add(include); wantObjs.add(o); if (o instanceof RevTag) @@ -1476,8 +1505,18 @@ public class PackWriter { } } - for (RevObject obj : wantObjs) - walker.markStart(obj); + if (walker instanceof DepthWalk.ObjectWalk) { + DepthWalk.ObjectWalk depthWalk = (DepthWalk.ObjectWalk) walker; + for (RevObject obj : wantObjs) + depthWalk.markRoot(obj); + if (unshallowObjects != null) { + for (ObjectId id : unshallowObjects) + depthWalk.markUnshallow(walker.parseAny(id)); + } + } else { + for (RevObject obj : wantObjs) + walker.markStart(obj); + } for (RevObject obj : haveObjs) walker.markUninteresting(obj); @@ -1512,36 +1551,42 @@ public class PackWriter { countingMonitor.update(1); } - int commitCnt = 0; - boolean putTagTargets = false; - for (RevCommit cmit : commits) { - if (!cmit.has(added)) { - cmit.add(added); + if (shallowPack) { + for (RevCommit cmit : commits) { addObject(cmit, 0); - commitCnt++; } - - for (int i = 0; i < cmit.getParentCount(); i++) { - RevCommit p = cmit.getParent(i); - if (!p.has(added) && !p.has(RevFlag.UNINTERESTING)) { - p.add(added); - addObject(p, 0); + } else { + int commitCnt = 0; + boolean putTagTargets = false; + for (RevCommit cmit : commits) { + if (!cmit.has(added)) { + cmit.add(added); + addObject(cmit, 0); commitCnt++; } - } - if (!putTagTargets && 4096 < commitCnt) { - for (ObjectId id : tagTargets) { - RevObject obj = walker.lookupOrNull(id); - if (obj instanceof RevCommit - && obj.has(include) - && !obj.has(RevFlag.UNINTERESTING) - && !obj.has(added)) { - obj.add(added); - addObject(obj, 0); + for (int i = 0; i < cmit.getParentCount(); i++) { + RevCommit p = cmit.getParent(i); + if (!p.has(added) && !p.has(RevFlag.UNINTERESTING)) { + p.add(added); + addObject(p, 0); + commitCnt++; + } + } + + if (!putTagTargets && 4096 < commitCnt) { + for (ObjectId id : tagTargets) { + RevObject obj = walker.lookupOrNull(id); + if (obj instanceof RevCommit + && obj.has(include) + && !obj.has(RevFlag.UNINTERESTING) + && !obj.has(added)) { + obj.add(added); + addObject(obj, 0); + } } + putTagTargets = true; } - putTagTargets = true; } } commits = null; diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/UploadPack.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/UploadPack.java index a9921fd8c..459ee6a95 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/UploadPack.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/UploadPack.java @@ -56,6 +56,7 @@ import java.util.Set; import org.eclipse.jgit.JGitText; import org.eclipse.jgit.errors.CorruptObjectException; +import org.eclipse.jgit.errors.IncorrectObjectTypeException; import org.eclipse.jgit.errors.MissingObjectException; import org.eclipse.jgit.errors.PackProtocolException; import org.eclipse.jgit.lib.Constants; @@ -65,6 +66,7 @@ import org.eclipse.jgit.lib.ProgressMonitor; import org.eclipse.jgit.lib.Ref; import org.eclipse.jgit.lib.Repository; import org.eclipse.jgit.revwalk.AsyncRevObjectQueue; +import org.eclipse.jgit.revwalk.DepthWalk; import org.eclipse.jgit.revwalk.ObjectWalk; import org.eclipse.jgit.revwalk.RevCommit; import org.eclipse.jgit.revwalk.RevFlag; @@ -103,6 +105,8 @@ public class UploadPack { static final String OPTION_NO_DONE = BasePackFetchConnection.OPTION_NO_DONE; + static final String OPTION_SHALLOW = BasePackFetchConnection.OPTION_SHALLOW; + /** Database we read the objects from. */ private final Repository db; @@ -160,6 +164,15 @@ public class UploadPack { /** Objects on both sides, these don't have to be sent. */ private final Set commonBase = new HashSet(); + /** Shallow commits the client already has. */ + private final Set clientShallowCommits = new HashSet(); + + /** Shallow commits on the client which are now becoming unshallow */ + private final List unshallowCommits = new ArrayList(); + + /** Desired depth from the client on a shallow request. */ + private int depth; + /** Commit time of the oldest common commit, in seconds. */ private int oldestTime; @@ -418,6 +431,8 @@ public class UploadPack { else multiAck = MultiAck.OFF; + if (depth != 0) + processShallow(); sendPack = negotiate(); } catch (PackProtocolException err) { reportErrorDuringNegotiate(err.getMessage()); @@ -457,6 +472,39 @@ public class UploadPack { } } + private void processShallow() throws IOException { + DepthWalk.RevWalk depthWalk = + new DepthWalk.RevWalk(walk.getObjectReader(), depth); + + // Find all the commits which will be shallow + for (ObjectId o : wantIds) { + try { + depthWalk.markRoot(depthWalk.parseCommit(o)); + } catch (IncorrectObjectTypeException notCommit) { + // Ignore non-commits in this loop. + } + } + + RevCommit o; + while ((o = depthWalk.next()) != null) { + DepthWalk.Commit c = (DepthWalk.Commit) o; + + // Commits at the boundary which aren't already shallow in + // the client need to be marked as such + if (c.getDepth() == depth && !clientShallowCommits.contains(c)) + pckOut.writeString("shallow " + o.name()); + + // Commits not on the boundary which are shallow in the client + // need to become unshallowed + if (c.getDepth() < depth && clientShallowCommits.contains(c)) { + unshallowCommits.add(c.copy()); + pckOut.writeString("unshallow " + c.name()); + } + } + + pckOut.end(); + } + /** * Generate an advertisement of available refs and capabilities. * @@ -488,6 +536,7 @@ public class UploadPack { adv.advertiseCapability(OPTION_SIDE_BAND_64K); adv.advertiseCapability(OPTION_THIN_PACK); adv.advertiseCapability(OPTION_NO_PROGRESS); + adv.advertiseCapability(OPTION_SHALLOW); if (!biDirectionalPipe) adv.advertiseCapability(OPTION_NO_DONE); adv.setDerefTags(true); @@ -509,6 +558,17 @@ public class UploadPack { if (line == PacketLineIn.END) break; + + if (line.startsWith("deepen ")) { + depth = Integer.parseInt(line.substring(7)); + continue; + } + + if (line.startsWith("shallow ")) { + clientShallowCommits.add(ObjectId.fromString(line.substring(8))); + continue; + } + if (!line.startsWith("want ") || line.length() < 45) throw new PackProtocolException(MessageFormat.format(JGitText.get().expectedGot, "want", line)); @@ -536,6 +596,13 @@ public class UploadPack { try { line = pckIn.readString(); } catch (EOFException eof) { + // EOF on stateless RPC (aka smart HTTP) and non-shallow request + // means the client asked for the updated shallow/unshallow data, + // disconnected, and will try another request with actual want/have. + // Don't report the EOF here, its a bug in the protocol that the client + // just disconnects without sending an END. + if (!biDirectionalPipe && depth > 0) + return false; throw eof; } @@ -887,6 +954,9 @@ public class UploadPack { pw.setTagTargets(tagTargets); } + if (depth > 0) + pw.setShallowPack(depth, unshallowCommits); + RevWalk rw = walk; if (wantAll.isEmpty()) { pw.preparePack(pm, wantIds, commonBase);