]> source.dussan.org Git - jgit.git/commitdiff
Implement server support for shallow clones 20/1320/12
authorMatt Fischer <matt.fischer@garmin.com>
Tue, 27 Jul 2010 03:39:37 +0000 (22:39 -0500)
committerChris Aniszczyk <zx@twitter.com>
Sun, 21 Aug 2011 21:04:23 +0000 (14:04 -0700)
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 <caniszczyk@gmail.com>
org.eclipse.jgit/src/org/eclipse/jgit/revwalk/DepthGenerator.java [new file with mode: 0644]
org.eclipse.jgit/src/org/eclipse/jgit/revwalk/DepthWalk.java [new file with mode: 0644]
org.eclipse.jgit/src/org/eclipse/jgit/revwalk/StartGenerator.java
org.eclipse.jgit/src/org/eclipse/jgit/storage/pack/PackWriter.java
org.eclipse.jgit/src/org/eclipse/jgit/transport/UploadPack.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 (file)
index 0000000..ad05186
--- /dev/null
@@ -0,0 +1,176 @@
+/*
+ * Copyright (C) 2010, Garmin International
+ * Copyright (C) 2010, Matt Fischer <matt.fischer@garmin.com>
+ * 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 (file)
index 0000000..9c5eaff
--- /dev/null
@@ -0,0 +1,243 @@
+/*
+ * Copyright (C) 2010, Garmin International
+ * Copyright (C) 2010, Matt Fischer <matt.fischer@garmin.com>
+ * 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;
+               }
+       }
+}
index fbff027784325a326ff65b341ed58c7036956877..7fb958bbb6a0763aafc4bf09c15a68d27c1616b3 100644 (file)
@@ -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) {
index d3bd693aa835179a5df6a6543f8c9357abbe1adc..0e3455439563f08672fa35764c4120023ad80956 100644 (file)
@@ -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<? extends ObjectId> unshallowObjects;
+
        /**
         * Create writer for specified repository.
         * <p>
@@ -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<? extends ObjectId> 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<? extends ObjectId> interestingObjects,
                        final Collection<? extends ObjectId> uninterestingObjects)
                        throws IOException {
@@ -585,7 +608,11 @@ public class PackWriter {
        public void preparePack(ProgressMonitor countingMonitor,
                        Set<? extends ObjectId> want,
                        Set<? extends ObjectId> 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<? extends ObjectId> interestingObjects,
                        final Set<? extends ObjectId> 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;
index a9921fd8c3b09fa4973b30aacefd89ba2ff7c7ff..459ee6a95d59a4af8c68271864980ccda51c8f57 100644 (file)
@@ -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<RevObject> commonBase = new HashSet<RevObject>();
 
+       /** Shallow commits the client already has. */
+       private final Set<ObjectId> clientShallowCommits = new HashSet<ObjectId>();
+
+       /** Shallow commits on the client which are now becoming unshallow */
+       private final List<ObjectId> unshallowCommits = new ArrayList<ObjectId>();
+
+       /** 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);