Browse Source

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 <caniszczyk@gmail.com>
stable-1.1
Matt Fischer 15 years ago committed by Chris Aniszczyk
parent
commit
9952223e06
  1. 176
      org.eclipse.jgit/src/org/eclipse/jgit/revwalk/DepthGenerator.java
  2. 243
      org.eclipse.jgit/src/org/eclipse/jgit/revwalk/DepthWalk.java
  3. 20
      org.eclipse.jgit/src/org/eclipse/jgit/revwalk/StartGenerator.java
  4. 105
      org.eclipse.jgit/src/org/eclipse/jgit/storage/pack/PackWriter.java
  5. 70
      org.eclipse.jgit/src/org/eclipse/jgit/transport/UploadPack.java

176
org.eclipse.jgit/src/org/eclipse/jgit/revwalk/DepthGenerator.java

@ -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;
}
}
}

243
org.eclipse.jgit/src/org/eclipse/jgit/revwalk/DepthWalk.java

@ -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;
}
}
}

20
org.eclipse.jgit/src/org/eclipse/jgit/revwalk/StartGenerator.java

@ -132,14 +132,20 @@ class StartGenerator extends Generator {
} }
walker.queue = q; walker.queue = q;
g = new PendingGenerator(w, pending, rf, pendingOutputType);
if (boundary) { if (walker instanceof DepthWalk) {
// Because the boundary generator may produce uninteresting DepthWalk dw = (DepthWalk) walker;
// commits we cannot allow the pending generator to dispose g = new DepthGenerator(dw, pending);
// of them early. } else {
// g = new PendingGenerator(w, pending, rf, pendingOutputType);
((PendingGenerator) g).canDispose = false;
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) { if ((g.outputType() & NEEDS_REWRITE) != 0) {

105
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.Repository;
import org.eclipse.jgit.lib.ThreadSafeProgressMonitor; import org.eclipse.jgit.lib.ThreadSafeProgressMonitor;
import org.eclipse.jgit.revwalk.AsyncRevObjectQueue; import org.eclipse.jgit.revwalk.AsyncRevObjectQueue;
import org.eclipse.jgit.revwalk.DepthWalk;
import org.eclipse.jgit.revwalk.ObjectWalk; import org.eclipse.jgit.revwalk.ObjectWalk;
import org.eclipse.jgit.revwalk.RevCommit; import org.eclipse.jgit.revwalk.RevCommit;
import org.eclipse.jgit.revwalk.RevFlag; import org.eclipse.jgit.revwalk.RevFlag;
@ -195,6 +196,12 @@ public class PackWriter {
private boolean pruneCurrentObjectList; private boolean pruneCurrentObjectList;
private boolean shallowPack;
private int depth;
private Collection<? extends ObjectId> unshallowObjects;
/** /**
* Create writer for specified repository. * Create writer for specified repository.
* <p> * <p>
@ -407,6 +414,22 @@ public class PackWriter {
tagTargets = objects; 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. * Returns objects number in a pack file that was created by this writer.
* *
@ -538,7 +561,7 @@ public class PackWriter {
*/ */
@Deprecated @Deprecated
public void preparePack(ProgressMonitor countingMonitor, public void preparePack(ProgressMonitor countingMonitor,
final ObjectWalk walk, ObjectWalk walk,
final Collection<? extends ObjectId> interestingObjects, final Collection<? extends ObjectId> interestingObjects,
final Collection<? extends ObjectId> uninterestingObjects) final Collection<? extends ObjectId> uninterestingObjects)
throws IOException { throws IOException {
@ -585,7 +608,11 @@ public class PackWriter {
public void preparePack(ProgressMonitor countingMonitor, public void preparePack(ProgressMonitor countingMonitor,
Set<? extends ObjectId> want, Set<? extends ObjectId> want,
Set<? extends ObjectId> have) throws IOException { 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); preparePack(countingMonitor, ow, want, have);
} }
@ -615,12 +642,14 @@ public class PackWriter {
* when some I/O problem occur during reading objects. * when some I/O problem occur during reading objects.
*/ */
public void preparePack(ProgressMonitor countingMonitor, public void preparePack(ProgressMonitor countingMonitor,
final ObjectWalk walk, ObjectWalk walk,
final Set<? extends ObjectId> interestingObjects, final Set<? extends ObjectId> interestingObjects,
final Set<? extends ObjectId> uninterestingObjects) final Set<? extends ObjectId> uninterestingObjects)
throws IOException { throws IOException {
if (countingMonitor == null) if (countingMonitor == null)
countingMonitor = NullProgressMonitor.INSTANCE; countingMonitor = NullProgressMonitor.INSTANCE;
if (shallowPack && !(walk instanceof DepthWalk.ObjectWalk))
walk = new DepthWalk.ObjectWalk(reader, depth);
findObjectsToPack(countingMonitor, walk, interestingObjects, findObjectsToPack(countingMonitor, walk, interestingObjects,
uninterestingObjects); uninterestingObjects);
} }
@ -1443,9 +1472,9 @@ public class PackWriter {
if (tipToPack.containsKey(o)) if (tipToPack.containsKey(o))
o.add(inCachedPack); o.add(inCachedPack);
if (have.contains(o)) { if (have.contains(o))
haveObjs.add(o); haveObjs.add(o);
} else if (want.contains(o)) { if (want.contains(o)) {
o.add(include); o.add(include);
wantObjs.add(o); wantObjs.add(o);
if (o instanceof RevTag) if (o instanceof RevTag)
@ -1476,8 +1505,18 @@ public class PackWriter {
} }
} }
for (RevObject obj : wantObjs) if (walker instanceof DepthWalk.ObjectWalk) {
walker.markStart(obj); 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) for (RevObject obj : haveObjs)
walker.markUninteresting(obj); walker.markUninteresting(obj);
@ -1512,36 +1551,42 @@ public class PackWriter {
countingMonitor.update(1); countingMonitor.update(1);
} }
int commitCnt = 0; if (shallowPack) {
boolean putTagTargets = false; for (RevCommit cmit : commits) {
for (RevCommit cmit : commits) {
if (!cmit.has(added)) {
cmit.add(added);
addObject(cmit, 0); addObject(cmit, 0);
commitCnt++;
} }
} else {
for (int i = 0; i < cmit.getParentCount(); i++) { int commitCnt = 0;
RevCommit p = cmit.getParent(i); boolean putTagTargets = false;
if (!p.has(added) && !p.has(RevFlag.UNINTERESTING)) { for (RevCommit cmit : commits) {
p.add(added); if (!cmit.has(added)) {
addObject(p, 0); cmit.add(added);
addObject(cmit, 0);
commitCnt++; commitCnt++;
} }
}
if (!putTagTargets && 4096 < commitCnt) { for (int i = 0; i < cmit.getParentCount(); i++) {
for (ObjectId id : tagTargets) { RevCommit p = cmit.getParent(i);
RevObject obj = walker.lookupOrNull(id); if (!p.has(added) && !p.has(RevFlag.UNINTERESTING)) {
if (obj instanceof RevCommit p.add(added);
&& obj.has(include) addObject(p, 0);
&& !obj.has(RevFlag.UNINTERESTING) commitCnt++;
&& !obj.has(added)) { }
obj.add(added); }
addObject(obj, 0);
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; commits = null;

70
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.JGitText;
import org.eclipse.jgit.errors.CorruptObjectException; import org.eclipse.jgit.errors.CorruptObjectException;
import org.eclipse.jgit.errors.IncorrectObjectTypeException;
import org.eclipse.jgit.errors.MissingObjectException; import org.eclipse.jgit.errors.MissingObjectException;
import org.eclipse.jgit.errors.PackProtocolException; import org.eclipse.jgit.errors.PackProtocolException;
import org.eclipse.jgit.lib.Constants; 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.Ref;
import org.eclipse.jgit.lib.Repository; import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.revwalk.AsyncRevObjectQueue; import org.eclipse.jgit.revwalk.AsyncRevObjectQueue;
import org.eclipse.jgit.revwalk.DepthWalk;
import org.eclipse.jgit.revwalk.ObjectWalk; import org.eclipse.jgit.revwalk.ObjectWalk;
import org.eclipse.jgit.revwalk.RevCommit; import org.eclipse.jgit.revwalk.RevCommit;
import org.eclipse.jgit.revwalk.RevFlag; 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_NO_DONE = BasePackFetchConnection.OPTION_NO_DONE;
static final String OPTION_SHALLOW = BasePackFetchConnection.OPTION_SHALLOW;
/** Database we read the objects from. */ /** Database we read the objects from. */
private final Repository db; private final Repository db;
@ -160,6 +164,15 @@ public class UploadPack {
/** Objects on both sides, these don't have to be sent. */ /** Objects on both sides, these don't have to be sent. */
private final Set<RevObject> commonBase = new HashSet<RevObject>(); 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. */ /** Commit time of the oldest common commit, in seconds. */
private int oldestTime; private int oldestTime;
@ -418,6 +431,8 @@ public class UploadPack {
else else
multiAck = MultiAck.OFF; multiAck = MultiAck.OFF;
if (depth != 0)
processShallow();
sendPack = negotiate(); sendPack = negotiate();
} catch (PackProtocolException err) { } catch (PackProtocolException err) {
reportErrorDuringNegotiate(err.getMessage()); 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. * Generate an advertisement of available refs and capabilities.
* *
@ -488,6 +536,7 @@ public class UploadPack {
adv.advertiseCapability(OPTION_SIDE_BAND_64K); adv.advertiseCapability(OPTION_SIDE_BAND_64K);
adv.advertiseCapability(OPTION_THIN_PACK); adv.advertiseCapability(OPTION_THIN_PACK);
adv.advertiseCapability(OPTION_NO_PROGRESS); adv.advertiseCapability(OPTION_NO_PROGRESS);
adv.advertiseCapability(OPTION_SHALLOW);
if (!biDirectionalPipe) if (!biDirectionalPipe)
adv.advertiseCapability(OPTION_NO_DONE); adv.advertiseCapability(OPTION_NO_DONE);
adv.setDerefTags(true); adv.setDerefTags(true);
@ -509,6 +558,17 @@ public class UploadPack {
if (line == PacketLineIn.END) if (line == PacketLineIn.END)
break; 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) if (!line.startsWith("want ") || line.length() < 45)
throw new PackProtocolException(MessageFormat.format(JGitText.get().expectedGot, "want", line)); throw new PackProtocolException(MessageFormat.format(JGitText.get().expectedGot, "want", line));
@ -536,6 +596,13 @@ public class UploadPack {
try { try {
line = pckIn.readString(); line = pckIn.readString();
} catch (EOFException eof) { } 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; throw eof;
} }
@ -887,6 +954,9 @@ public class UploadPack {
pw.setTagTargets(tagTargets); pw.setTagTargets(tagTargets);
} }
if (depth > 0)
pw.setShallowPack(depth, unshallowCommits);
RevWalk rw = walk; RevWalk rw = walk;
if (wantAll.isEmpty()) { if (wantAll.isEmpty()) {
pw.preparePack(pm, wantIds, commonBase); pw.preparePack(pm, wantIds, commonBase);

Loading…
Cancel
Save