summaryrefslogtreecommitdiffstats
path: root/org.eclipse.jgit/src/org
diff options
context:
space:
mode:
Diffstat (limited to 'org.eclipse.jgit/src/org')
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/pack/PackWriter.java54
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/revwalk/ObjectWalk.java107
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/transport/FilterSpec.java80
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/transport/UploadPack.java3
4 files changed, 207 insertions, 37 deletions
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/pack/PackWriter.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/pack/PackWriter.java
index 70df8c08e2..c86afb08c3 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/pack/PackWriter.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/pack/PackWriter.java
@@ -873,6 +873,21 @@ public class PackWriter implements AutoCloseable {
}
/**
+ * A visitation policy which causes objects to be visited repeatedly by
+ * making {@code shouldVisit} always return {@code true}.
+ */
+ private static final ObjectWalk.VisitationPolicy ALWAYS_VISIT_POLICY =
+ new ObjectWalk.VisitationPolicy() {
+ @Override
+ public boolean shouldVisit(RevObject o) {
+ return true;
+ }
+
+ @Override
+ public void visited(RevObject o) {}
+ };
+
+ /**
* Prepare the list of objects to be written to the pack stream.
* <p>
* Basing on these 2 sets, another set of objects to put in a pack file is
@@ -913,6 +928,9 @@ public class PackWriter implements AutoCloseable {
if (shallowPack && !(walk instanceof DepthWalk.ObjectWalk))
throw new IllegalArgumentException(
JGitText.get().shallowPacksRequireDepthWalk);
+ if (filterSpec.getTreeDepthLimit() >= 0) {
+ walk.setVisitationPolicy(ALWAYS_VISIT_POLICY);
+ }
findObjectsToPack(countingMonitor, walk, interestingObjects,
uninterestingObjects, noBitmaps);
}
@@ -1972,7 +1990,9 @@ public class PackWriter implements AutoCloseable {
byte[] pathBuf = walker.getPathBuffer();
int pathLen = walker.getPathLength();
bases.addBase(o.getType(), pathBuf, pathLen, pathHash);
- filterAndAddObject(o, o.getType(), pathHash, want);
+ if (!depthSkip(o, walker)) {
+ filterAndAddObject(o, o.getType(), pathHash, want);
+ }
countingMonitor.update(1);
}
} else {
@@ -1982,7 +2002,10 @@ public class PackWriter implements AutoCloseable {
continue;
if (exclude(o))
continue;
- filterAndAddObject(o, o.getType(), walker.getPathHashCode(), want);
+ if (!depthSkip(o, walker)) {
+ filterAndAddObject(o, o.getType(), walker.getPathHashCode(),
+ want);
+ }
countingMonitor.update(1);
}
}
@@ -2074,6 +2097,33 @@ public class PackWriter implements AutoCloseable {
objectsMap.add(otp);
}
+ /**
+ * Determines if the object should be omitted from the pack as a result of
+ * its depth (probably because of the tree:<depth> filter).
+ *
+ * @param obj
+ * the object to check whether it should be omitted.
+ * @param walker
+ * the walker being used for traveresal.
+ * @return whether the given object should be skipped.
+ */
+ private boolean depthSkip(@NonNull RevObject obj, ObjectWalk walker) {
+ long treeDepth = walker.getTreeDepth();
+
+ // Check if this object needs to be rejected because it is a tree or
+ // blob that is too deep from the root tree.
+
+ // A blob is considered one level deeper than the tree that contains it.
+ if (obj.getType() == OBJ_BLOB) {
+ treeDepth++;
+ }
+
+ // TODO: Do not continue traversing the tree, since its children
+ // will also be too deep.
+ return filterSpec.getTreeDepthLimit() != -1 &&
+ treeDepth > filterSpec.getTreeDepthLimit();
+ }
+
// Adds the given object as an object to be packed, first performing
// filtering on blobs at or exceeding a given size.
private void filterAndAddObject(@NonNull AnyObjectId src, int type,
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/ObjectWalk.java b/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/ObjectWalk.java
index 8f0dd9beb2..3312b8aa81 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/ObjectWalk.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/ObjectWalk.java
@@ -43,6 +43,7 @@
package org.eclipse.jgit.revwalk;
+import static java.util.Objects.requireNonNull;
import static org.eclipse.jgit.lib.Constants.OBJ_BLOB;
import static org.eclipse.jgit.lib.Constants.OBJ_COMMIT;
import static org.eclipse.jgit.lib.Constants.OBJ_TREE;
@@ -97,6 +98,55 @@ public class ObjectWalk extends RevWalk {
*/
private static final int IN_PENDING = RevWalk.REWRITE;
+ /**
+ * When walking over a tree and blob graph, objects are usually marked as
+ * seen as they are visited and this "seen" status is checked upon the next
+ * visit. If they are already "seen" then they are not processed (returned
+ * by {@link ObjectWalk#nextObject()}) again. However, this behavior can be
+ * overridden by supplying a different implementation of this class.
+ *
+ * @since 5.4
+ */
+ public interface VisitationPolicy {
+ /**
+ * Whenever the rev or object walk reaches a Git object, if that object
+ * already exists as a RevObject, this method is called to determine if
+ * that object should be visited.
+ *
+ * @param o
+ * the object to check if it should be visited
+ * @return true if the object should be visited
+ */
+ boolean shouldVisit(RevObject o);
+
+ /**
+ * Called when an object is visited.
+ *
+ * @param o
+ * the object that was visited
+ */
+ void visited(RevObject o);
+ }
+
+ /**
+ * The default visitation policy: causes all objects to be visited exactly
+ * once.
+ *
+ * @since 5.4
+ */
+ public static final VisitationPolicy SIMPLE_VISITATION_POLICY =
+ new VisitationPolicy() {
+ @Override
+ public boolean shouldVisit(RevObject o) {
+ return (o.flags & SEEN) == 0;
+ }
+
+ @Override
+ public void visited(RevObject o) {
+ o.flags |= SEEN;
+ }
+ };
+
private List<RevObject> rootObjects;
private BlockObjQueue pendingObjects;
@@ -113,6 +163,8 @@ public class ObjectWalk extends RevWalk {
private boolean boundary;
+ private VisitationPolicy visitationPolicy = SIMPLE_VISITATION_POLICY;
+
/**
* Create a new revision and object walker for a given repository.
*
@@ -299,6 +351,18 @@ public class ObjectWalk extends RevWalk {
objectFilter = newFilter != null ? newFilter : ObjectFilter.ALL;
}
+ /**
+ * Sets the visitation policy to use during this walk.
+ *
+ * @param policy
+ * the {@code VisitationPolicy} to use
+ * @since 5.4
+ */
+ public void setVisitationPolicy(VisitationPolicy policy) {
+ assertNotStarted();
+ visitationPolicy = requireNonNull(policy);
+ }
+
/** {@inheritDoc} */
@Override
public RevCommit next() throws MissingObjectException,
@@ -357,24 +421,23 @@ public class ObjectWalk extends RevWalk {
}
RevObject obj = objects.get(idBuffer);
- if (obj != null && (obj.flags & SEEN) != 0)
+ if (obj != null && !visitationPolicy.shouldVisit(obj))
continue;
int mode = parseMode(buf, startPtr, ptr, tv);
- int flags;
switch (mode >>> TYPE_SHIFT) {
case TYPE_FILE:
case TYPE_SYMLINK:
if (obj == null) {
obj = new RevBlob(idBuffer);
- obj.flags = SEEN;
+ visitationPolicy.visited(obj);
objects.add(obj);
return obj;
}
if (!(obj instanceof RevBlob))
throw new IncorrectObjectTypeException(obj, OBJ_BLOB);
- obj.flags = flags = obj.flags | SEEN;
- if ((flags & UNINTERESTING) == 0)
+ visitationPolicy.visited(obj);
+ if ((obj.flags & UNINTERESTING) == 0)
return obj;
if (boundary)
return obj;
@@ -383,14 +446,14 @@ public class ObjectWalk extends RevWalk {
case TYPE_TREE:
if (obj == null) {
obj = new RevTree(idBuffer);
- obj.flags = SEEN;
+ visitationPolicy.visited(obj);
objects.add(obj);
return pushTree(obj);
}
if (!(obj instanceof RevTree))
throw new IncorrectObjectTypeException(obj, OBJ_TREE);
- obj.flags = flags = obj.flags | SEEN;
- if ((flags & UNINTERESTING) == 0)
+ visitationPolicy.visited(obj);
+ if ((obj.flags & UNINTERESTING) == 0)
return pushTree(obj);
if (boundary)
return pushTree(obj);
@@ -419,12 +482,11 @@ public class ObjectWalk extends RevWalk {
if (o == null) {
return null;
}
- int flags = o.flags;
- if ((flags & SEEN) != 0)
+ if (!visitationPolicy.shouldVisit(o)) {
continue;
- flags |= SEEN;
- o.flags = flags;
- if ((flags & UNINTERESTING) == 0 | boundary) {
+ }
+ visitationPolicy.visited(o);
+ if ((o.flags & UNINTERESTING) == 0 | boundary) {
if (o instanceof RevTree) {
// The previous while loop should have exhausted the stack
// of trees.
@@ -576,6 +638,17 @@ public class ObjectWalk extends RevWalk {
}
/**
+ * @return the current traversal depth from the root tree object
+ * @since 5.4
+ */
+ public int getTreeDepth() {
+ if (currVisit == null) {
+ return 0;
+ }
+ return currVisit.depth;
+ }
+
+ /**
* Get the current object's path hash code.
* <p>
* This method computes a hash code on the fly for this path, the hash is
@@ -778,6 +851,11 @@ public class ObjectWalk extends RevWalk {
tv.buf = reader.open(obj, OBJ_TREE).getCachedBytes();
tv.parent = currVisit;
currVisit = tv;
+ if (tv.parent == null) {
+ tv.depth = 1;
+ } else {
+ tv.depth = tv.parent.depth + 1;
+ }
return obj;
}
@@ -809,5 +887,8 @@ public class ObjectWalk extends RevWalk {
/** Number of bytes in the path leading up to this tree. */
int pathLen;
+
+ /** Number of levels deep from the root tree. 0 for root tree. */
+ int depth;
}
}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/FilterSpec.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/FilterSpec.java
index 7e77419dec..a663c9b470 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/FilterSpec.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/FilterSpec.java
@@ -59,13 +59,22 @@ public final class FilterSpec {
private final long blobLimit;
- private FilterSpec(long blobLimit) {
+ private final long treeDepthLimit;
+
+ private FilterSpec(long blobLimit, long treeDepthLimit) {
this.blobLimit = blobLimit;
+ this.treeDepthLimit = treeDepthLimit;
}
/**
* Process the content of "filter" line from the protocol. It has a shape
- * like "blob:none" or "blob:limit=N", with limit a positive number.
+ * like:
+ *
+ * <ul>
+ * <li>"blob:none"
+ * <li>"blob:limit=N", with N &gt;= 0
+ * <li>"tree:DEPTH", with DEPTH &gt;= 0
+ * </ul>
*
* @param filterLine
* the content of the "filter" line in the protocol
@@ -76,31 +85,37 @@ public final class FilterSpec {
*/
public static FilterSpec fromFilterLine(String filterLine)
throws PackProtocolException {
- long blobLimit = -1;
-
if (filterLine.equals("blob:none")) { //$NON-NLS-1$
- blobLimit = 0;
+ return FilterSpec.withBlobLimit(0);
} else if (filterLine.startsWith("blob:limit=")) { //$NON-NLS-1$
+ long blobLimit = -1;
try {
blobLimit = Long
.parseLong(filterLine.substring("blob:limit=".length())); //$NON-NLS-1$
} catch (NumberFormatException e) {
- throw new PackProtocolException(MessageFormat
- .format(JGitText.get().invalidFilter, filterLine));
+ // Do not change blobLimit so that we throw a
+ // PackProtocolException later.
+ }
+ if (blobLimit >= 0) {
+ return FilterSpec.withBlobLimit(blobLimit);
+ }
+ } else if (filterLine.startsWith("tree:")) { //$NON-NLS-1$
+ long treeDepthLimit = -1;
+ try {
+ treeDepthLimit = Long
+ .parseLong(filterLine.substring("tree:".length())); //$NON-NLS-1$
+ } catch (NumberFormatException e) {
+ // Do not change blobLimit so that we throw a
+ // PackProtocolException later.
+ }
+ if (treeDepthLimit >= 0) {
+ return FilterSpec.withTreeDepthLimit(treeDepthLimit);
}
- }
- /*
- * We must have (1) either "blob:none" or "blob:limit=" set (because we
- * only support blob size limits for now), and (2) if the latter, then
- * it must be nonnegative. Throw if (1) or (2) is not met.
- */
- if (blobLimit < 0) {
- throw new PackProtocolException(
- MessageFormat.format(
- JGitText.get().invalidFilter, filterLine));
}
- return new FilterSpec(blobLimit);
+ // Did not match any known filter format.
+ throw new PackProtocolException(
+ MessageFormat.format(JGitText.get().invalidFilter, filterLine));
}
/**
@@ -113,13 +128,27 @@ public final class FilterSpec {
throw new IllegalArgumentException(
"blobLimit cannot be negative: " + blobLimit); //$NON-NLS-1$
}
- return new FilterSpec(blobLimit);
+ return new FilterSpec(blobLimit, -1);
+ }
+
+ /**
+ * @param treeDepthLimit
+ * the tree depth limit in a "tree:[depth]" filter line
+ * @return a filter spec which filters blobs and trees beyond a certain tree
+ * depth
+ */
+ static FilterSpec withTreeDepthLimit(long treeDepthLimit) {
+ if (treeDepthLimit < 0) {
+ throw new IllegalArgumentException(
+ "treeDepthLimit cannot be negative: " + treeDepthLimit); //$NON-NLS-1$
+ }
+ return new FilterSpec(-1, treeDepthLimit);
}
/**
* A placeholder that indicates no filtering.
*/
- public static final FilterSpec NO_FILTER = new FilterSpec(-1);
+ public static final FilterSpec NO_FILTER = new FilterSpec(-1, -1);
/**
* @return -1 if this filter does not filter blobs based on size, or a
@@ -130,10 +159,19 @@ public final class FilterSpec {
}
/**
+ * @return -1 if this filter does not filter blobs and trees based on depth,
+ * or a non-negative integer representing the max tree depth of
+ * blobs and trees to fetch
+ */
+ public long getTreeDepthLimit() {
+ return treeDepthLimit;
+ }
+
+ /**
* @return true if this filter doesn't filter out anything
*/
public boolean isNoOp() {
- return blobLimit == -1;
+ return blobLimit == -1 && treeDepthLimit == -1;
}
/**
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/UploadPack.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/UploadPack.java
index fb50a533f4..27090f4383 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/UploadPack.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/UploadPack.java
@@ -2110,7 +2110,8 @@ public class UploadPack {
}
pw.setUseBitmaps(
req.getDepth() == 0
- && req.getClientShallowCommits().isEmpty());
+ && req.getClientShallowCommits().isEmpty()
+ && req.getFilterSpec().getTreeDepthLimit() == -1);
pw.setClientShallowCommits(req.getClientShallowCommits());
pw.setReuseDeltaCommits(true);
pw.setDeltaBaseAsOffset(