ObjectWalk.java

  1. /*
  2.  * Copyright (C) 2008, Shawn O. Pearce <spearce@spearce.org> and others
  3.  *
  4.  * This program and the accompanying materials are made available under the
  5.  * terms of the Eclipse Distribution License v. 1.0 which is available at
  6.  * https://www.eclipse.org/org/documents/edl-v10.php.
  7.  *
  8.  * SPDX-License-Identifier: BSD-3-Clause
  9.  */

  10. package org.eclipse.jgit.revwalk;

  11. import static java.util.Objects.requireNonNull;
  12. import static org.eclipse.jgit.lib.Constants.OBJ_BLOB;
  13. import static org.eclipse.jgit.lib.Constants.OBJ_COMMIT;
  14. import static org.eclipse.jgit.lib.Constants.OBJ_TREE;

  15. import java.io.IOException;
  16. import java.text.MessageFormat;
  17. import java.util.ArrayList;
  18. import java.util.List;

  19. import org.eclipse.jgit.errors.CorruptObjectException;
  20. import org.eclipse.jgit.errors.IncorrectObjectTypeException;
  21. import org.eclipse.jgit.errors.LargeObjectException;
  22. import org.eclipse.jgit.errors.MissingObjectException;
  23. import org.eclipse.jgit.internal.JGitText;
  24. import org.eclipse.jgit.lib.AnyObjectId;
  25. import org.eclipse.jgit.lib.ObjectReader;
  26. import org.eclipse.jgit.lib.Repository;
  27. import org.eclipse.jgit.revwalk.filter.ObjectFilter;
  28. import org.eclipse.jgit.util.RawParseUtils;

  29. /**
  30.  * Specialized subclass of RevWalk to include trees, blobs and tags.
  31.  * <p>
  32.  * Unlike RevWalk this subclass is able to remember starting roots that include
  33.  * annotated tags, or arbitrary trees or blobs. Once commit generation is
  34.  * complete and all commits have been popped by the application, individual
  35.  * annotated tag, tree and blob objects can be popped through the additional
  36.  * method {@link #nextObject()}.
  37.  * <p>
  38.  * Tree and blob objects reachable from interesting commits are automatically
  39.  * scheduled for inclusion in the results of {@link #nextObject()}, returning
  40.  * each object exactly once. Objects are sorted and returned according to the
  41.  * the commits that reference them and the order they appear within a tree.
  42.  * Ordering can be affected by changing the
  43.  * {@link org.eclipse.jgit.revwalk.RevSort} used to order the commits that are
  44.  * returned first.
  45.  */
  46. public class ObjectWalk extends RevWalk {
  47.     private static final int ID_SZ = 20;
  48.     private static final int TYPE_SHIFT = 12;
  49.     private static final int TYPE_TREE = 0040000 >>> TYPE_SHIFT;
  50.     private static final int TYPE_SYMLINK = 0120000 >>> TYPE_SHIFT;
  51.     private static final int TYPE_FILE = 0100000 >>> TYPE_SHIFT;
  52.     private static final int TYPE_GITLINK = 0160000 >>> TYPE_SHIFT;

  53.     /**
  54.      * Indicates a non-RevCommit is in {@link #pendingObjects}.
  55.      * <p>
  56.      * We can safely reuse {@link RevWalk#REWRITE} here for the same value as it
  57.      * is only set on RevCommit and {@link #pendingObjects} never has RevCommit
  58.      * instances inserted into it.
  59.      */
  60.     private static final int IN_PENDING = RevWalk.REWRITE;

  61.     /**
  62.      * When walking over a tree and blob graph, objects are usually marked as
  63.      * seen as they are visited and this "seen" status is checked upon the next
  64.      * visit. If they are already "seen" then they are not processed (returned
  65.      * by {@link ObjectWalk#nextObject()}) again. However, this behavior can be
  66.      * overridden by supplying a different implementation of this class.
  67.      *
  68.      * @since 5.4
  69.      */
  70.     public interface VisitationPolicy {
  71.         /**
  72.          * Whenever the rev or object walk reaches a Git object, if that object
  73.          * already exists as a RevObject, this method is called to determine if
  74.          * that object should be visited.
  75.          *
  76.          * @param o
  77.          *            the object to check if it should be visited
  78.          * @return true if the object should be visited
  79.          */
  80.         boolean shouldVisit(RevObject o);

  81.         /**
  82.          * Called when an object is visited.
  83.          *
  84.          * @param o
  85.          *            the object that was visited
  86.          */
  87.         void visited(RevObject o);
  88.     }

  89.     /**
  90.      * The default visitation policy: causes all objects to be visited exactly
  91.      * once.
  92.      *
  93.      * @since 5.4
  94.      */
  95.     public static final VisitationPolicy SIMPLE_VISITATION_POLICY =
  96.             new VisitationPolicy() {
  97.         @Override
  98.         public boolean shouldVisit(RevObject o) {
  99.             return (o.flags & SEEN) == 0;
  100.         }

  101.         @Override
  102.         public void visited(RevObject o) {
  103.             o.flags |= SEEN;
  104.         }
  105.     };

  106.     private List<RevObject> rootObjects;

  107.     private BlockObjQueue pendingObjects;

  108.     private ObjectFilter objectFilter;

  109.     private TreeVisit freeVisit;

  110.     private TreeVisit currVisit;

  111.     private byte[] pathBuf;

  112.     private int pathLen;

  113.     private boolean boundary;

  114.     private VisitationPolicy visitationPolicy = SIMPLE_VISITATION_POLICY;

  115.     /**
  116.      * Create a new revision and object walker for a given repository.
  117.      *
  118.      * @param repo
  119.      *            the repository the walker will obtain data from.
  120.      */
  121.     public ObjectWalk(Repository repo) {
  122.         this(repo.newObjectReader());
  123.     }

  124.     /**
  125.      * Create a new revision and object walker for a given repository.
  126.      *
  127.      * @param or
  128.      *            the reader the walker will obtain data from. The reader should
  129.      *            be released by the caller when the walker is no longer
  130.      *            required.
  131.      */
  132.     public ObjectWalk(ObjectReader or) {
  133.         super(or);
  134.         setRetainBody(false);
  135.         rootObjects = new ArrayList<>();
  136.         pendingObjects = new BlockObjQueue();
  137.         objectFilter = ObjectFilter.ALL;
  138.         pathBuf = new byte[256];
  139.     }

  140.     /**
  141.      * Create an object reachability checker that will use bitmaps if possible.
  142.      *
  143.      * This reachability checker accepts any object as target. For checks
  144.      * exclusively between commits, see
  145.      * {@link RevWalk#createReachabilityChecker()}.
  146.      *
  147.      * @return an object reachability checker, using bitmaps if possible.
  148.      *
  149.      * @throws IOException
  150.      *             when the index fails to load.
  151.      *
  152.      * @since 5.8
  153.      * @deprecated use
  154.      *             {@code ObjectReader#createObjectReachabilityChecker(ObjectWalk)}
  155.      *             instead.
  156.      */
  157.     @Deprecated
  158.     public final ObjectReachabilityChecker createObjectReachabilityChecker()
  159.             throws IOException {
  160.         return reader.createObjectReachabilityChecker(this);
  161.     }

  162.     /**
  163.      * Mark an object or commit to start graph traversal from.
  164.      * <p>
  165.      * Callers are encouraged to use
  166.      * {@link org.eclipse.jgit.revwalk.RevWalk#parseAny(AnyObjectId)} instead of
  167.      * {@link org.eclipse.jgit.revwalk.RevWalk#lookupAny(AnyObjectId, int)}, as
  168.      * this method requires the object to be parsed before it can be added as a
  169.      * root for the traversal.
  170.      * <p>
  171.      * The method will automatically parse an unparsed object, but error
  172.      * handling may be more difficult for the application to explain why a
  173.      * RevObject is not actually valid. The object pool of this walker would
  174.      * also be 'poisoned' by the invalid RevObject.
  175.      * <p>
  176.      * This method will automatically call
  177.      * {@link org.eclipse.jgit.revwalk.RevWalk#markStart(RevCommit)} if passed
  178.      * RevCommit instance, or a RevTag that directly (or indirectly) references
  179.      * a RevCommit.
  180.      *
  181.      * @param o
  182.      *            the object to start traversing from. The object passed must be
  183.      *            from this same revision walker.
  184.      * @throws org.eclipse.jgit.errors.MissingObjectException
  185.      *             the object supplied is not available from the object
  186.      *             database. This usually indicates the supplied object is
  187.      *             invalid, but the reference was constructed during an earlier
  188.      *             invocation to
  189.      *             {@link org.eclipse.jgit.revwalk.RevWalk#lookupAny(AnyObjectId, int)}.
  190.      * @throws org.eclipse.jgit.errors.IncorrectObjectTypeException
  191.      *             the object was not parsed yet and it was discovered during
  192.      *             parsing that it is not actually the type of the instance
  193.      *             passed in. This usually indicates the caller used the wrong
  194.      *             type in a
  195.      *             {@link org.eclipse.jgit.revwalk.RevWalk#lookupAny(AnyObjectId, int)}
  196.      *             call.
  197.      * @throws java.io.IOException
  198.      *             a pack file or loose object could not be read.
  199.      */
  200.     public void markStart(RevObject o) throws MissingObjectException,
  201.             IncorrectObjectTypeException, IOException {
  202.         while (o instanceof RevTag) {
  203.             addObject(o);
  204.             o = ((RevTag) o).getObject();
  205.             parseHeaders(o);
  206.         }

  207.         if (o instanceof RevCommit)
  208.             super.markStart((RevCommit) o);
  209.         else
  210.             addObject(o);
  211.     }

  212.     /**
  213.      * Mark an object to not produce in the output.
  214.      * <p>
  215.      * Uninteresting objects denote not just themselves but also their entire
  216.      * reachable chain, back until the merge base of an uninteresting commit and
  217.      * an otherwise interesting commit.
  218.      * <p>
  219.      * Callers are encouraged to use
  220.      * {@link org.eclipse.jgit.revwalk.RevWalk#parseAny(AnyObjectId)} instead of
  221.      * {@link org.eclipse.jgit.revwalk.RevWalk#lookupAny(AnyObjectId, int)}, as
  222.      * this method requires the object to be parsed before it can be added as a
  223.      * root for the traversal.
  224.      * <p>
  225.      * The method will automatically parse an unparsed object, but error
  226.      * handling may be more difficult for the application to explain why a
  227.      * RevObject is not actually valid. The object pool of this walker would
  228.      * also be 'poisoned' by the invalid RevObject.
  229.      * <p>
  230.      * This method will automatically call
  231.      * {@link org.eclipse.jgit.revwalk.RevWalk#markStart(RevCommit)} if passed
  232.      * RevCommit instance, or a RevTag that directly (or indirectly) references
  233.      * a RevCommit.
  234.      *
  235.      * @param o
  236.      *            the object to start traversing from. The object passed must be
  237.      * @throws org.eclipse.jgit.errors.MissingObjectException
  238.      *             the object supplied is not available from the object
  239.      *             database. This usually indicates the supplied object is
  240.      *             invalid, but the reference was constructed during an earlier
  241.      *             invocation to
  242.      *             {@link org.eclipse.jgit.revwalk.RevWalk#lookupAny(AnyObjectId, int)}.
  243.      * @throws org.eclipse.jgit.errors.IncorrectObjectTypeException
  244.      *             the object was not parsed yet and it was discovered during
  245.      *             parsing that it is not actually the type of the instance
  246.      *             passed in. This usually indicates the caller used the wrong
  247.      *             type in a
  248.      *             {@link org.eclipse.jgit.revwalk.RevWalk#lookupAny(AnyObjectId, int)}
  249.      *             call.
  250.      * @throws java.io.IOException
  251.      *             a pack file or loose object could not be read.
  252.      */
  253.     public void markUninteresting(RevObject o) throws MissingObjectException,
  254.             IncorrectObjectTypeException, IOException {
  255.         while (o instanceof RevTag) {
  256.             o.flags |= UNINTERESTING;
  257.             if (boundary)
  258.                 addObject(o);
  259.             o = ((RevTag) o).getObject();
  260.             parseHeaders(o);
  261.         }

  262.         if (o instanceof RevCommit)
  263.             super.markUninteresting((RevCommit) o);
  264.         else if (o instanceof RevTree)
  265.             markTreeUninteresting((RevTree) o);
  266.         else
  267.             o.flags |= UNINTERESTING;

  268.         if (o.getType() != OBJ_COMMIT && boundary)
  269.             addObject(o);
  270.     }

  271.     /** {@inheritDoc} */
  272.     @Override
  273.     public void sort(RevSort s) {
  274.         super.sort(s);
  275.         boundary = hasRevSort(RevSort.BOUNDARY);
  276.     }

  277.     /** {@inheritDoc} */
  278.     @Override
  279.     public void sort(RevSort s, boolean use) {
  280.         super.sort(s, use);
  281.         boundary = hasRevSort(RevSort.BOUNDARY);
  282.     }

  283.     /**
  284.      * Get the currently configured object filter.
  285.      *
  286.      * @return the current filter. Never null as a filter is always needed.
  287.      * @since 4.0
  288.      */
  289.     public ObjectFilter getObjectFilter() {
  290.         return objectFilter;
  291.     }

  292.     /**
  293.      * Set the object filter for this walker. This filter affects the objects
  294.      * visited by {@link #nextObject()}. It does not affect the commits listed
  295.      * by {@link #next()}.
  296.      * <p>
  297.      * If the filter returns false for an object, then that object is skipped
  298.      * and objects reachable from it are not enqueued to be walked recursively.
  299.      * This can be used to speed up the object walk by skipping subtrees that
  300.      * are known to be uninteresting.
  301.      *
  302.      * @param newFilter
  303.      *            the new filter. If null the special
  304.      *            {@link org.eclipse.jgit.revwalk.filter.ObjectFilter#ALL}
  305.      *            filter will be used instead, as it matches every object.
  306.      * @since 4.0
  307.      */
  308.     public void setObjectFilter(ObjectFilter newFilter) {
  309.         assertNotStarted();
  310.         objectFilter = newFilter != null ? newFilter : ObjectFilter.ALL;
  311.     }

  312.     /**
  313.      * Sets the visitation policy to use during this walk.
  314.      *
  315.      * @param policy
  316.      *            the {@code VisitationPolicy} to use
  317.      * @since 5.4
  318.      */
  319.     public void setVisitationPolicy(VisitationPolicy policy) {
  320.         assertNotStarted();
  321.         visitationPolicy = requireNonNull(policy);
  322.     }

  323.     /** {@inheritDoc} */
  324.     @Override
  325.     public RevCommit next() throws MissingObjectException,
  326.             IncorrectObjectTypeException, IOException {
  327.         for (;;) {
  328.             final RevCommit r = super.next();
  329.             if (r == null) {
  330.                 return null;
  331.             }
  332.             final RevTree t = r.getTree();
  333.             if ((r.flags & UNINTERESTING) != 0) {
  334.                 if (objectFilter.include(this, t)) {
  335.                     markTreeUninteresting(t);
  336.                 }
  337.                 if (boundary) {
  338.                     return r;
  339.                 }
  340.                 continue;
  341.             }
  342.             if (objectFilter.include(this, t)) {
  343.                 pendingObjects.add(t);
  344.             }
  345.             return r;
  346.         }
  347.     }

  348.     /**
  349.      * Skips the current tree such that {@link #nextObject()} does not return
  350.      * any objects inside it. This should be called right after
  351.      * {@link #nextObject()} returns the tree.
  352.      *
  353.      * @since 5.4
  354.      */
  355.     public void skipTree() {
  356.         if (currVisit != null) {
  357.             currVisit.ptr = currVisit.buf.length;
  358.         }
  359.     }

  360.     /**
  361.      * Pop the next most recent object.
  362.      *
  363.      * @return next most recent object; null if traversal is over.
  364.      * @throws org.eclipse.jgit.errors.MissingObjectException
  365.      *             one or more of the next objects are not available from the
  366.      *             object database, but were thought to be candidates for
  367.      *             traversal. This usually indicates a broken link.
  368.      * @throws org.eclipse.jgit.errors.IncorrectObjectTypeException
  369.      *             one or more of the objects in a tree do not match the type
  370.      *             indicated.
  371.      * @throws java.io.IOException
  372.      *             a pack file or loose object could not be read.
  373.      */
  374.     public RevObject nextObject() throws MissingObjectException,
  375.             IncorrectObjectTypeException, IOException {
  376.         pathLen = 0;

  377.         TreeVisit tv = currVisit;
  378.         while (tv != null) {
  379.             byte[] buf = tv.buf;
  380.             for (int ptr = tv.ptr; ptr < buf.length;) {
  381.                 int startPtr = ptr;
  382.                 ptr = findObjectId(buf, ptr);
  383.                 idBuffer.fromRaw(buf, ptr);
  384.                 ptr += ID_SZ;

  385.                 if (!objectFilter.include(this, idBuffer)) {
  386.                     continue;
  387.                 }

  388.                 RevObject obj = objects.get(idBuffer);
  389.                 if (obj != null && !visitationPolicy.shouldVisit(obj))
  390.                     continue;

  391.                 int mode = parseMode(buf, startPtr, ptr, tv);
  392.                 switch (mode >>> TYPE_SHIFT) {
  393.                 case TYPE_FILE:
  394.                 case TYPE_SYMLINK:
  395.                     if (obj == null) {
  396.                         obj = new RevBlob(idBuffer);
  397.                         visitationPolicy.visited(obj);
  398.                         objects.add(obj);
  399.                         return obj;
  400.                     }
  401.                     if (!(obj instanceof RevBlob))
  402.                         throw new IncorrectObjectTypeException(obj, OBJ_BLOB);
  403.                     visitationPolicy.visited(obj);
  404.                     if ((obj.flags & UNINTERESTING) == 0)
  405.                         return obj;
  406.                     if (boundary)
  407.                         return obj;
  408.                     continue;

  409.                 case TYPE_TREE:
  410.                     if (obj == null) {
  411.                         obj = new RevTree(idBuffer);
  412.                         visitationPolicy.visited(obj);
  413.                         objects.add(obj);
  414.                         return pushTree(obj);
  415.                     }
  416.                     if (!(obj instanceof RevTree))
  417.                         throw new IncorrectObjectTypeException(obj, OBJ_TREE);
  418.                     visitationPolicy.visited(obj);
  419.                     if ((obj.flags & UNINTERESTING) == 0)
  420.                         return pushTree(obj);
  421.                     if (boundary)
  422.                         return pushTree(obj);
  423.                     continue;

  424.                 case TYPE_GITLINK:
  425.                     continue;

  426.                 default:
  427.                     throw new CorruptObjectException(MessageFormat.format(
  428.                             JGitText.get().corruptObjectInvalidMode3,
  429.                             String.format("%o", Integer.valueOf(mode)), //$NON-NLS-1$
  430.                             idBuffer.name(),
  431.                             RawParseUtils.decode(buf, tv.namePtr, tv.nameEnd),
  432.                             tv.obj));
  433.                 }
  434.             }

  435.             currVisit = tv.parent;
  436.             releaseTreeVisit(tv);
  437.             tv = currVisit;
  438.         }

  439.         for (;;) {
  440.             RevObject o = pendingObjects.next();
  441.             if (o == null) {
  442.                 return null;
  443.             }
  444.             if (!visitationPolicy.shouldVisit(o)) {
  445.                 continue;
  446.             }
  447.             visitationPolicy.visited(o);
  448.             if ((o.flags & UNINTERESTING) == 0 || boundary) {
  449.                 if (o instanceof RevTree) {
  450.                     // The previous while loop should have exhausted the stack
  451.                     // of trees.
  452.                     assert currVisit == null;

  453.                     pushTree(o);
  454.                 }
  455.                 return o;
  456.             }
  457.         }
  458.     }

  459.     private static int findObjectId(byte[] buf, int ptr) {
  460.         // Skip over the mode and name until the NUL before the ObjectId
  461.         // can be located. Skip the NUL as the function returns.
  462.         for (;;) {
  463.             if (buf[++ptr] == 0) return ++ptr;
  464.             if (buf[++ptr] == 0) return ++ptr;
  465.             if (buf[++ptr] == 0) return ++ptr;
  466.             if (buf[++ptr] == 0) return ++ptr;

  467.             if (buf[++ptr] == 0) return ++ptr;
  468.             if (buf[++ptr] == 0) return ++ptr;
  469.             if (buf[++ptr] == 0) return ++ptr;
  470.             if (buf[++ptr] == 0) return ++ptr;

  471.             if (buf[++ptr] == 0) return ++ptr;
  472.             if (buf[++ptr] == 0) return ++ptr;
  473.             if (buf[++ptr] == 0) return ++ptr;
  474.             if (buf[++ptr] == 0) return ++ptr;

  475.             if (buf[++ptr] == 0) return ++ptr;
  476.             if (buf[++ptr] == 0) return ++ptr;
  477.             if (buf[++ptr] == 0) return ++ptr;
  478.             if (buf[++ptr] == 0) return ++ptr;
  479.         }
  480.     }

  481.     private static int parseMode(byte[] buf, int startPtr, int recEndPtr, TreeVisit tv) {
  482.         int mode = buf[startPtr] - '0';
  483.         for (;;) {
  484.             byte c = buf[++startPtr];
  485.             if (' ' == c)
  486.                 break;
  487.             mode <<= 3;
  488.             mode += c - '0';

  489.             c = buf[++startPtr];
  490.             if (' ' == c)
  491.                 break;
  492.             mode <<= 3;
  493.             mode += c - '0';

  494.             c = buf[++startPtr];
  495.             if (' ' == c)
  496.                 break;
  497.             mode <<= 3;
  498.             mode += c - '0';

  499.             c = buf[++startPtr];
  500.             if (' ' == c)
  501.                 break;
  502.             mode <<= 3;
  503.             mode += c - '0';

  504.             c = buf[++startPtr];
  505.             if (' ' == c)
  506.                 break;
  507.             mode <<= 3;
  508.             mode += c - '0';

  509.             c = buf[++startPtr];
  510.             if (' ' == c)
  511.                 break;
  512.             mode <<= 3;
  513.             mode += c - '0';

  514.             c = buf[++startPtr];
  515.             if (' ' == c)
  516.                 break;
  517.             mode <<= 3;
  518.             mode += c - '0';
  519.         }

  520.         tv.ptr = recEndPtr;
  521.         tv.namePtr = startPtr + 1;
  522.         tv.nameEnd = recEndPtr - (ID_SZ + 1);
  523.         return mode;
  524.     }

  525.     /**
  526.      * Verify all interesting objects are available, and reachable.
  527.      * <p>
  528.      * Callers should populate starting points and ending points with
  529.      * {@link #markStart(RevObject)} and {@link #markUninteresting(RevObject)}
  530.      * and then use this method to verify all objects between those two points
  531.      * exist in the repository and are readable.
  532.      * <p>
  533.      * This method returns successfully if everything is connected; it throws an
  534.      * exception if there is a connectivity problem. The exception message
  535.      * provides some detail about the connectivity failure.
  536.      *
  537.      * @throws org.eclipse.jgit.errors.MissingObjectException
  538.      *             one or more of the next objects are not available from the
  539.      *             object database, but were thought to be candidates for
  540.      *             traversal. This usually indicates a broken link.
  541.      * @throws org.eclipse.jgit.errors.IncorrectObjectTypeException
  542.      *             one or more of the objects in a tree do not match the type
  543.      *             indicated.
  544.      * @throws java.io.IOException
  545.      *             a pack file or loose object could not be read.
  546.      */
  547.     public void checkConnectivity() throws MissingObjectException,
  548.             IncorrectObjectTypeException, IOException {
  549.         for (;;) {
  550.             final RevCommit c = next();
  551.             if (c == null)
  552.                 break;
  553.         }
  554.         for (;;) {
  555.             final RevObject o = nextObject();
  556.             if (o == null)
  557.                 break;
  558.             if (o instanceof RevBlob && !reader.has(o))
  559.                 throw new MissingObjectException(o, OBJ_BLOB);
  560.         }
  561.     }

  562.     /**
  563.      * Get the current object's complete path.
  564.      * <p>
  565.      * This method is not very efficient and is primarily meant for debugging
  566.      * and final output generation. Applications should try to avoid calling it,
  567.      * and if invoked do so only once per interesting entry, where the name is
  568.      * absolutely required for correct function.
  569.      *
  570.      * @return complete path of the current entry, from the root of the
  571.      *         repository. If the current entry is in a subtree there will be at
  572.      *         least one '/' in the returned string. Null if the current entry
  573.      *         has no path, such as for annotated tags or root level trees.
  574.      */
  575.     public String getPathString() {
  576.         if (pathLen == 0) {
  577.             pathLen = updatePathBuf(currVisit);
  578.             if (pathLen == 0)
  579.                 return null;
  580.         }
  581.         return RawParseUtils.decode(pathBuf, 0, pathLen);
  582.     }

  583.     /**
  584.      * @return the current traversal depth from the root tree object
  585.      * @since 5.4
  586.      */
  587.     public int getTreeDepth() {
  588.         if (currVisit == null) {
  589.             return 0;
  590.         }
  591.         return currVisit.depth;
  592.     }

  593.     /**
  594.      * Get the current object's path hash code.
  595.      * <p>
  596.      * This method computes a hash code on the fly for this path, the hash is
  597.      * suitable to cluster objects that may have similar paths together.
  598.      *
  599.      * @return path hash code; any integer may be returned.
  600.      */
  601.     public int getPathHashCode() {
  602.         TreeVisit tv = currVisit;
  603.         if (tv == null)
  604.             return 0;

  605.         int nameEnd = tv.nameEnd;
  606.         if (nameEnd == 0) {
  607.             // When nameEnd == 0 the subtree is itself the current path
  608.             // being visited. The name hash must be obtained from its
  609.             // parent tree. If there is no parent, this is a root tree with
  610.             // a hash code of 0.
  611.             tv = tv.parent;
  612.             if (tv == null)
  613.                 return 0;
  614.             nameEnd = tv.nameEnd;
  615.         }

  616.         byte[] buf;
  617.         int ptr;

  618.         if (16 <= (nameEnd - tv.namePtr)) {
  619.             buf = tv.buf;
  620.             ptr = nameEnd - 16;
  621.         } else {
  622.             nameEnd = pathLen;
  623.             if (nameEnd == 0) {
  624.                 nameEnd = updatePathBuf(currVisit);
  625.                 pathLen = nameEnd;
  626.             }
  627.             buf = pathBuf;
  628.             ptr = Math.max(0, nameEnd - 16);
  629.         }

  630.         int hash = 0;
  631.         for (; ptr < nameEnd; ptr++) {
  632.             byte c = buf[ptr];
  633.             if (c != ' ')
  634.                 hash = (hash >>> 2) + (c << 24);
  635.         }
  636.         return hash;
  637.     }

  638.     /**
  639.      * Get the internal buffer holding the current path.
  640.      *
  641.      * @return the internal buffer holding the current path.
  642.      */
  643.     public byte[] getPathBuffer() {
  644.         if (pathLen == 0)
  645.             pathLen = updatePathBuf(currVisit);
  646.         return pathBuf;
  647.     }

  648.     /**
  649.      * Get length of the path in {@link #getPathBuffer()}.
  650.      *
  651.      * @return length of the path in {@link #getPathBuffer()}.
  652.      */
  653.     public int getPathLength() {
  654.         if (pathLen == 0)
  655.             pathLen = updatePathBuf(currVisit);
  656.         return pathLen;
  657.     }

  658.     private int updatePathBuf(TreeVisit tv) {
  659.         if (tv == null)
  660.             return 0;

  661.         // If nameEnd == 0 this tree has not yet contributed an entry.
  662.         // Update only for the parent, which if null will be empty.
  663.         int nameEnd = tv.nameEnd;
  664.         if (nameEnd == 0)
  665.             return updatePathBuf(tv.parent);

  666.         int ptr = tv.pathLen;
  667.         if (ptr == 0) {
  668.             ptr = updatePathBuf(tv.parent);
  669.             if (ptr == pathBuf.length)
  670.                 growPathBuf(ptr);
  671.             if (ptr != 0)
  672.                 pathBuf[ptr++] = '/';
  673.             tv.pathLen = ptr;
  674.         }

  675.         int namePtr = tv.namePtr;
  676.         int nameLen = nameEnd - namePtr;
  677.         int end = ptr + nameLen;
  678.         while (pathBuf.length < end)
  679.             growPathBuf(ptr);
  680.         System.arraycopy(tv.buf, namePtr, pathBuf, ptr, nameLen);
  681.         return end;
  682.     }

  683.     private void growPathBuf(int ptr) {
  684.         byte[] newBuf = new byte[pathBuf.length << 1];
  685.         System.arraycopy(pathBuf, 0, newBuf, 0, ptr);
  686.         pathBuf = newBuf;
  687.     }

  688.     /** {@inheritDoc} */
  689.     @Override
  690.     public void dispose() {
  691.         super.dispose();
  692.         pendingObjects = new BlockObjQueue();
  693.         currVisit = null;
  694.         freeVisit = null;
  695.     }

  696.     /** {@inheritDoc} */
  697.     @Override
  698.     protected void reset(int retainFlags) {
  699.         super.reset(retainFlags);

  700.         for (RevObject obj : rootObjects)
  701.             obj.flags &= ~IN_PENDING;

  702.         rootObjects = new ArrayList<>();
  703.         pendingObjects = new BlockObjQueue();
  704.         currVisit = null;
  705.         freeVisit = null;
  706.     }

  707.     private void addObject(RevObject o) {
  708.         if ((o.flags & IN_PENDING) == 0) {
  709.             o.flags |= IN_PENDING;
  710.             rootObjects.add(o);
  711.             pendingObjects.add(o);
  712.         }
  713.     }

  714.     private void markTreeUninteresting(RevTree tree)
  715.             throws MissingObjectException, IncorrectObjectTypeException,
  716.             IOException {
  717.         if ((tree.flags & UNINTERESTING) != 0)
  718.             return;
  719.         tree.flags |= UNINTERESTING;

  720.         byte[] raw = reader.open(tree, OBJ_TREE).getCachedBytes();
  721.         for (int ptr = 0; ptr < raw.length;) {
  722.             byte c = raw[ptr];
  723.             int mode = c - '0';
  724.             for (;;) {
  725.                 c = raw[++ptr];
  726.                 if (' ' == c)
  727.                     break;
  728.                 mode <<= 3;
  729.                 mode += c - '0';
  730.             }
  731.             while (raw[++ptr] != 0) {
  732.                 // Skip entry name.
  733.             }
  734.             ptr++; // Skip NUL after entry name.

  735.             switch (mode >>> TYPE_SHIFT) {
  736.             case TYPE_FILE:
  737.             case TYPE_SYMLINK:
  738.                 idBuffer.fromRaw(raw, ptr);
  739.                 lookupBlob(idBuffer).flags |= UNINTERESTING;
  740.                 break;

  741.             case TYPE_TREE:
  742.                 idBuffer.fromRaw(raw, ptr);
  743.                 markTreeUninteresting(lookupTree(idBuffer));
  744.                 break;

  745.             case TYPE_GITLINK:
  746.                 break;

  747.             default:
  748.                 idBuffer.fromRaw(raw, ptr);
  749.                 throw new CorruptObjectException(MessageFormat.format(
  750.                         JGitText.get().corruptObjectInvalidMode3,
  751.                         String.format("%o", Integer.valueOf(mode)), //$NON-NLS-1$
  752.                         idBuffer.name(), "", tree)); //$NON-NLS-1$
  753.             }
  754.             ptr += ID_SZ;
  755.         }
  756.     }

  757.     private RevObject pushTree(RevObject obj) throws LargeObjectException,
  758.             MissingObjectException, IncorrectObjectTypeException, IOException {
  759.         TreeVisit tv = freeVisit;
  760.         if (tv != null) {
  761.             freeVisit = tv.parent;
  762.             tv.ptr = 0;
  763.             tv.namePtr = 0;
  764.             tv.nameEnd = 0;
  765.             tv.pathLen = 0;
  766.         } else {
  767.             tv = new TreeVisit();
  768.         }
  769.         tv.obj = obj;
  770.         tv.buf = reader.open(obj, OBJ_TREE).getCachedBytes();
  771.         tv.parent = currVisit;
  772.         currVisit = tv;
  773.         if (tv.parent == null) {
  774.             tv.depth = 1;
  775.         } else {
  776.             tv.depth = tv.parent.depth + 1;
  777.         }

  778.         return obj;
  779.     }

  780.     private void releaseTreeVisit(TreeVisit tv) {
  781.         tv.buf = null;
  782.         tv.parent = freeVisit;
  783.         freeVisit = tv;
  784.     }

  785.     private static class TreeVisit {
  786.         /** Parent tree visit that entered this tree, null if root tree. */
  787.         TreeVisit parent;

  788.         /** The RevTree currently being iterated through. */
  789.         RevObject obj;

  790.         /** Canonical encoding of the tree named by {@link #obj}. */
  791.         byte[] buf;

  792.         /** Index of next entry to parse in {@link #buf}. */
  793.         int ptr;

  794.         /** Start of the current name entry in {@link #buf}. */
  795.         int namePtr;

  796.         /** One past end of name, {@code nameEnd - namePtr} is the length. */
  797.         int nameEnd;

  798.         /** Number of bytes in the path leading up to this tree. */
  799.         int pathLen;

  800.         /** Number of levels deep from the root tree. 0 for root tree. */
  801.         int depth;
  802.     }
  803. }