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>tags/v1.1.0.201109011030-rc2
@@ -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; | |||
} | |||
} | |||
} |
@@ -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; | |||
} | |||
} | |||
} |
@@ -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) { |
@@ -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; |
@@ -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); |