PacketLineIn.END);
}
+ @Test
+ public void testV2FetchDeepenNot() throws Exception {
+ RevCommit one = remote.commit().message("one").create();
+ RevCommit two = remote.commit().message("two").parent(one).create();
+ RevCommit three = remote.commit().message("three").parent(two).create();
+ RevCommit side = remote.commit().message("side").parent(one).create();
+ RevCommit merge = remote.commit().message("merge")
+ .parent(three).parent(side).create();
+
+ remote.update("branch1", merge);
+ remote.update("side", side);
+
+ // The client is a shallow clone that only has "three", and
+ // wants "merge" while excluding "side".
+ ByteArrayInputStream recvStream = uploadPackV2(
+ "command=fetch\n",
+ PacketLineIn.DELIM,
+ "shallow " + three.toObjectId().getName() + "\n",
+ "deepen-not side\n",
+ "want " + merge.toObjectId().getName() + "\n",
+ "have " + three.toObjectId().getName() + "\n",
+ "done\n",
+ PacketLineIn.END);
+ PacketLineIn pckIn = new PacketLineIn(recvStream);
+ assertThat(pckIn.readString(), is("shallow-info"));
+
+ // "merge" is shallow because "side" is excluded by deepen-not.
+ // "two" is shallow because "one" (as parent of "side") is excluded by deepen-not.
+ assertThat(
+ Arrays.asList(pckIn.readString(), pckIn.readString()),
+ hasItems(
+ "shallow " + merge.toObjectId().getName(),
+ "shallow " + two.toObjectId().getName()));
+
+ // "three" is unshallow because its parent "two" is now available.
+ assertThat(pckIn.readString(), is("unshallow " + three.toObjectId().getName()));
+
+ assertThat(pckIn.readString(), theInstance(PacketLineIn.DELIM));
+ assertThat(pckIn.readString(), is("packfile"));
+ parsePack(recvStream);
+
+ // The server does not send these because they are excluded by
+ // deepen-not.
+ assertFalse(client.hasObject(side.toObjectId()));
+ assertFalse(client.hasObject(one.toObjectId()));
+
+ // The server does not send this because the client claims to
+ // have it.
+ assertFalse(client.hasObject(three.toObjectId()));
+
+ // The server sends both these commits.
+ assertTrue(client.hasObject(merge.toObjectId()));
+ assertTrue(client.hasObject(two.toObjectId()));
+ }
+
+ @Test
+ public void testV2FetchDeepenNot_excludeDescendantOfWant() throws Exception {
+ RevCommit one = remote.commit().message("one").create();
+ RevCommit two = remote.commit().message("two").parent(one).create();
+ RevCommit three = remote.commit().message("three").parent(two).create();
+ RevCommit four = remote.commit().message("four").parent(three).create();
+
+ remote.update("two", two);
+ remote.update("four", four);
+
+ thrown.expect(PackProtocolException.class);
+ thrown.expectMessage("No commits selected for shallow request");
+ uploadPackV2(
+ "command=fetch\n",
+ PacketLineIn.DELIM,
+ "deepen-not four\n",
+ "want " + two.toObjectId().getName() + "\n",
+ "done\n",
+ PacketLineIn.END);
+ }
+
+ @Test
+ public void testV2FetchDeepenNot_supportAnnotatedTags() throws Exception {
+ RevCommit one = remote.commit().message("one").create();
+ RevCommit two = remote.commit().message("two").parent(one).create();
+ RevCommit three = remote.commit().message("three").parent(two).create();
+ RevCommit four = remote.commit().message("four").parent(three).create();
+ RevTag twoTag = remote.tag("twotag", two);
+
+ remote.update("refs/tags/twotag", twoTag);
+ remote.update("four", four);
+
+ ByteArrayInputStream recvStream = uploadPackV2(
+ "command=fetch\n",
+ PacketLineIn.DELIM,
+ "deepen-not twotag\n",
+ "want " + four.toObjectId().getName() + "\n",
+ "done\n",
+ PacketLineIn.END);
+ PacketLineIn pckIn = new PacketLineIn(recvStream);
+ assertThat(pckIn.readString(), is("shallow-info"));
+ assertThat(pckIn.readString(), is("shallow " + three.toObjectId().getName()));
+ assertThat(pckIn.readString(), theInstance(PacketLineIn.DELIM));
+ assertThat(pckIn.readString(), is("packfile"));
+ parsePack(recvStream);
+ assertFalse(client.hasObject(one.toObjectId()));
+ assertFalse(client.hasObject(two.toObjectId()));
+ assertTrue(client.hasObject(three.toObjectId()));
+ assertTrue(client.hasObject(four.toObjectId()));
+ }
+
@Test
public void testV2FetchUnrecognizedArgument() throws Exception {
thrown.expect(PackProtocolException.class);
import org.eclipse.jgit.errors.IncorrectObjectTypeException;
import org.eclipse.jgit.errors.MissingObjectException;
+import org.eclipse.jgit.lib.ObjectId;
/**
* Only produce commits which are below a specified depth.
*/
private final RevFlag REINTERESTING;
+ /**
+ * Commits reachable from commits that the client specified using --shallow-exclude.
+ */
+ private final RevFlag DEEPEN_NOT;
+
/**
* @param w
* @param s Parent generator
this.deepenSince = w.getDeepenSince();
this.UNSHALLOW = w.getUnshallowFlag();
this.REINTERESTING = w.getReinterestingFlag();
+ this.DEEPEN_NOT = w.getDeepenNotFlag();
s.shareFreeList(pending);
if (((DepthWalk.Commit) c).getDepth() == 0)
pending.add(c);
}
+
+ // Mark DEEPEN_NOT on all deepen-not commits and their ancestors.
+ // TODO(jonathantanmy): This implementation is somewhat
+ // inefficient in that any "deepen-not <ref>" in the request
+ // results in all commits reachable from that ref being parsed
+ // and marked, even if the commit topology is such that it is
+ // not necessary.
+ for (ObjectId oid : w.getDeepenNots()) {
+ RevCommit c;
+ try {
+ c = walk.parseCommit(oid);
+ } catch (IncorrectObjectTypeException notCommit) {
+ // The C Git implementation silently tolerates
+ // non-commits, so do the same here.
+ continue;
+ }
+
+ FIFORevQueue queue = new FIFORevQueue();
+ queue.add(c);
+ while ((c = queue.next()) != null) {
+ if (c.has(DEEPEN_NOT)) {
+ continue;
+ }
+
+ walk.parseHeaders(c);
+ c.add(DEEPEN_NOT);
+ for (RevCommit p : c.getParents()) {
+ queue.add(p);
+ }
+ }
+ }
}
@Override
continue;
}
+ if (c.has(DEEPEN_NOT)) {
+ continue;
+ }
+
int newDepth = c.depth + 1;
for (RevCommit p : c.parents) {
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 && !failsDeepenSince) {
+ // If the parent is not too deep and was not excluded, add
+ // it to the queue so that we can produce it later
+ if (newDepth <= depth && !failsDeepenSince &&
+ !p.has(DEEPEN_NOT)) {
pending.add(p);
} else {
c.isBoundary = true;
if ((c.flags & RevWalk.UNINTERESTING) != 0 && !c.has(UNSHALLOW))
produce = false;
+ if (c.getCommitTime() < deepenSince) {
+ produce = false;
+ }
+
if (produce)
return c;
}
package org.eclipse.jgit.revwalk;
import java.io.IOException;
+import java.util.Collections;
+import java.util.List;
+import java.util.Objects;
import org.eclipse.jgit.errors.IncorrectObjectTypeException;
import org.eclipse.jgit.errors.MissingObjectException;
import org.eclipse.jgit.lib.AnyObjectId;
+import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.lib.ObjectReader;
import org.eclipse.jgit.lib.Repository;
return 0;
}
+ /**
+ * @return the objects specified by the client using --shallow-exclude
+ * @since 5.2
+ */
+ public default List<ObjectId> getDeepenNots() {
+ return Collections.emptyList();
+ }
+
/** @return flag marking commits that should become unshallow. */
/**
* Get flag marking commits that should become unshallow.
*/
public RevFlag getReinterestingFlag();
+ /**
+ * @return flag marking commits that are to be excluded because of --shallow-exclude
+ * @since 5.2
+ */
+ public RevFlag getDeepenNotFlag();
+
/** 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. */
private int deepenSince;
+ private List<ObjectId> deepenNots;
+
private final RevFlag UNSHALLOW;
private final RevFlag REINTERESTING;
+ private final RevFlag DEEPEN_NOT;
+
/**
* @param repo Repository to walk
* @param depth Maximum depth to return
super(repo);
this.depth = depth;
+ this.deepenNots = Collections.emptyList();
this.UNSHALLOW = newFlag("UNSHALLOW"); //$NON-NLS-1$
this.REINTERESTING = newFlag("REINTERESTING"); //$NON-NLS-1$
+ this.DEEPEN_NOT = newFlag("DEEPEN_NOT"); //$NON-NLS-1$
}
/**
super(or);
this.depth = depth;
+ this.deepenNots = Collections.emptyList();
this.UNSHALLOW = newFlag("UNSHALLOW"); //$NON-NLS-1$
this.REINTERESTING = newFlag("REINTERESTING"); //$NON-NLS-1$
+ this.DEEPEN_NOT = newFlag("DEEPEN_NOT"); //$NON-NLS-1$
}
/**
deepenSince = limit;
}
+ @Override
+ public List<ObjectId> getDeepenNots() {
+ return deepenNots;
+ }
+
+ /**
+ * Mark objects that the client specified using
+ * --shallow-exclude. Objects that are not commits have no
+ * effect.
+ *
+ * @param deepenNots specified objects
+ * @since 5.2
+ */
+ public void setDeepenNots(List<ObjectId> deepenNots) {
+ this.deepenNots = Objects.requireNonNull(deepenNots);
+ }
+
@Override
public RevFlag getUnshallowFlag() {
return UNSHALLOW;
return REINTERESTING;
}
+ @Override
+ public RevFlag getDeepenNotFlag() {
+ return DEEPEN_NOT;
+ }
+
/**
* @since 4.5
*/
public ObjectWalk toObjectWalkWithSameObjects() {
ObjectWalk ow = new ObjectWalk(reader, depth);
ow.deepenSince = deepenSince;
+ ow.deepenNots = deepenNots;
ow.objects = objects;
ow.freeFlags = freeFlags;
return ow;
private int deepenSince;
+ private List<ObjectId> deepenNots;
+
private final RevFlag UNSHALLOW;
private final RevFlag REINTERESTING;
+ private final RevFlag DEEPEN_NOT;
+
/**
* @param repo Repository to walk
* @param depth Maximum depth to return
super(repo);
this.depth = depth;
+ this.deepenNots = Collections.emptyList();
this.UNSHALLOW = newFlag("UNSHALLOW"); //$NON-NLS-1$
this.REINTERESTING = newFlag("REINTERESTING"); //$NON-NLS-1$
+ this.DEEPEN_NOT = newFlag("DEEPEN_NOT"); //$NON-NLS-1$
}
/**
super(or);
this.depth = depth;
+ this.deepenNots = Collections.emptyList();
this.UNSHALLOW = newFlag("UNSHALLOW"); //$NON-NLS-1$
this.REINTERESTING = newFlag("REINTERESTING"); //$NON-NLS-1$
+ this.DEEPEN_NOT = newFlag("DEEPEN_NOT"); //$NON-NLS-1$
}
/**
return deepenSince;
}
+ @Override
+ public List<ObjectId> getDeepenNots() {
+ return deepenNots;
+ }
+
@Override
public RevFlag getUnshallowFlag() {
return UNSHALLOW;
public RevFlag getReinterestingFlag() {
return REINTERESTING;
}
+
+ @Override
+ public RevFlag getDeepenNotFlag() {
+ return DEEPEN_NOT;
+ }
}
}
}, unshallow -> {
pckOut.writeString("unshallow " + unshallow.name() + '\n'); //$NON-NLS-1$
unshallowCommits.add(unshallow);
- });
+ }, Collections.emptyList());
pckOut.end();
}
if (sendPack) {
sendPack(accumulator, req, refs == null ? null : refs.values(),
- unshallowCommits);
+ unshallowCommits, Collections.emptyList());
}
}
// copying data back to class fields
wantIds = req.getWantIds();
+ List<ObjectId> deepenNots = new ArrayList<>();
+ for (String s : req.getDeepenNotRefs()) {
+ Ref ref = db.getRefDatabase().getRef(s);
+ if (ref == null) {
+ throw new PackProtocolException(MessageFormat
+ .format(JGitText.get().invalidRefName, s));
+ }
+ deepenNots.add(ref.getObjectId());
+ }
+
boolean sectionSent = false;
boolean mayHaveShallow = req.getDepth() != 0
|| req.getDeepenSince() != 0
if (mayHaveShallow) {
computeShallowsAndUnshallows(req,
shallowCommit -> shallowCommits.add(shallowCommit),
- unshallowCommit -> unshallowCommits.add(unshallowCommit));
+ unshallowCommit -> unshallowCommits.add(unshallowCommit),
+ deepenNots);
}
if (!req.getClientShallowCommits().isEmpty())
walk.assumeShallow(req.getClientShallowCommits());
req.getClientCapabilities().contains(OPTION_INCLUDE_TAG)
? db.getRefDatabase().getRefsByPrefix(R_TAGS)
: null,
- unshallowCommits);
+ unshallowCommits, deepenNots);
// sendPack invokes pckOut.end() for us, so we do not
// need to invoke it here.
} else {
*/
private void computeShallowsAndUnshallows(FetchRequest req,
IOConsumer<ObjectId> shallowFunc,
- IOConsumer<ObjectId> unshallowFunc)
+ IOConsumer<ObjectId> unshallowFunc,
+ List<ObjectId> deepenNots)
throws IOException {
- if (req.getClientCapabilities().contains(OPTION_DEEPEN_RELATIVE)
- || !req.getDeepenNotRefs().isEmpty()) {
+ if (req.getClientCapabilities().contains(OPTION_DEEPEN_RELATIVE)) {
// TODO(jonathantanmy): Implement deepen-relative
- // and deepen-not.
throw new UnsupportedOperationException();
}
}
}
+ depthWalk.setDeepenNots(deepenNots);
+
RevCommit o;
boolean atLeastOne = false;
while ((o = depthWalk.next()) != null) {
* the {@link #OPTION_INCLUDE_TAG} capability was requested.
* @param unshallowCommits
* shallow commits on the client that are now becoming unshallow
+ * @param deepenNots
+ * objects that the client specified using --shallow-exclude
* @throws IOException
* if an error occured while generating or writing the pack.
*/
private void sendPack(PackStatistics.Accumulator accumulator,
FetchRequest req,
@Nullable Collection<Ref> allTags,
- List<ObjectId> unshallowCommits) throws IOException {
+ List<ObjectId> unshallowCommits,
+ List<ObjectId> deepenNots) throws IOException {
Set<String> caps = req.getClientCapabilities();
boolean sideband = caps.contains(OPTION_SIDE_BAND)
|| caps.contains(OPTION_SIDE_BAND_64K);
if (sideband) {
try {
- sendPack(true, req, accumulator, allTags, unshallowCommits);
+ sendPack(true, req, accumulator, allTags, unshallowCommits,
+ deepenNots);
} catch (ServiceMayNotContinueException noPack) {
// This was already reported on (below).
throw noPack;
throw err;
}
} else {
- sendPack(false, req, accumulator, allTags, unshallowCommits);
+ sendPack(false, req, accumulator, allTags, unshallowCommits, deepenNots);
}
}
* the {@link #OPTION_INCLUDE_TAG} capability was requested.
* @param unshallowCommits
* shallow commits on the client that are now becoming unshallow
+ * @param deepenNots
+ * objects that the client specified using --shallow-exclude
* @throws IOException
* if an error occured while generating or writing the pack.
*/
FetchRequest req,
PackStatistics.Accumulator accumulator,
@Nullable Collection<Ref> allTags,
- List<ObjectId> unshallowCommits) throws IOException {
+ List<ObjectId> unshallowCommits,
+ List<ObjectId> deepenNots) throws IOException {
ProgressMonitor pm = NullProgressMonitor.INSTANCE;
OutputStream packOut = rawOut;
}
RevWalk rw = walk;
- if (req.getDepth() > 0 || req.getDeepenSince() != 0) {
+ if (req.getDepth() > 0 || req.getDeepenSince() != 0 || !deepenNots.isEmpty()) {
int walkDepth = req.getDepth() == 0 ? Integer.MAX_VALUE
: req.getDepth() - 1;
pw.setShallowPack(req.getDepth(), unshallowCommits);
- rw = new DepthWalk.RevWalk(walk.getObjectReader(), walkDepth);
- ((DepthWalk.RevWalk) rw).setDeepenSince(req.getDeepenSince());
- rw.assumeShallow(req.getClientShallowCommits());
+
+ DepthWalk.RevWalk dw = new DepthWalk.RevWalk(
+ walk.getObjectReader(), walkDepth);
+ dw.setDeepenSince(req.getDeepenSince());
+ dw.setDeepenNots(deepenNots);
+ dw.assumeShallow(req.getClientShallowCommits());
+ rw = dw;
}
if (wantAll.isEmpty()) {