ObjectWalk.java

  1. /*
  2.  * Copyright (C) 2008, 2022 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(), true);
  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.         this(or, false);
  134.     }

  135.     private ObjectWalk(ObjectReader or, boolean closeReader) {
  136.         super(or, closeReader);
  137.         setRetainBody(false);
  138.         rootObjects = new ArrayList<>();
  139.         pendingObjects = new BlockObjQueue();
  140.         objectFilter = ObjectFilter.ALL;
  141.         pathBuf = new byte[256];
  142.     }

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

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

  210.         if (o instanceof RevCommit)
  211.             super.markStart((RevCommit) o);
  212.         else
  213.             addObject(o);
  214.     }

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

  265.         if (o instanceof RevCommit)
  266.             super.markUninteresting((RevCommit) o);
  267.         else if (o instanceof RevTree)
  268.             markTreeUninteresting((RevTree) o);
  269.         else
  270.             o.flags |= UNINTERESTING;

  271.         if (o.getType() != OBJ_COMMIT && boundary)
  272.             addObject(o);
  273.     }

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

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

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

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

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

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

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

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

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

  388.                 if (!objectFilter.include(this, idBuffer)) {
  389.                     continue;
  390.                 }

  391.                 RevObject obj = objects.get(idBuffer);
  392.                 if (obj != null && !visitationPolicy.shouldVisit(obj))
  393.                     continue;

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

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

  427.                 case TYPE_GITLINK:
  428.                     continue;

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

  438.             currVisit = tv.parent;
  439.             releaseTreeVisit(tv);
  440.             tv = currVisit;
  441.         }

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

  456.                     pushTree(o);
  457.                 }
  458.                 return o;
  459.             }
  460.         }
  461.     }

  462.     private static int findObjectId(byte[] buf, int ptr) {
  463.         // Skip over the mode and name until the NUL before the ObjectId
  464.         // can be located. Skip the NUL as the function returns.
  465.         for (;;) {
  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.             if (buf[++ptr] == 0) return ++ptr;
  480.             if (buf[++ptr] == 0) return ++ptr;
  481.             if (buf[++ptr] == 0) return ++ptr;
  482.         }
  483.     }

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

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

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

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

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

  512.             c = buf[++startPtr];
  513.             if (' ' == c)
  514.                 break;
  515.             mode <<= 3;
  516.             mode += c - '0';

  517.             c = buf[++startPtr];
  518.             if (' ' == c)
  519.                 break;
  520.             mode <<= 3;
  521.             mode += c - '0';
  522.         }

  523.         tv.ptr = recEndPtr;
  524.         tv.namePtr = startPtr + 1;
  525.         tv.nameEnd = recEndPtr - (ID_SZ + 1);
  526.         return mode;
  527.     }

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

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

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

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

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

  619.         byte[] buf;
  620.         int ptr;

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

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

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

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

  661.     private int updatePathBuf(TreeVisit tv) {
  662.         if (tv == null)
  663.             return 0;

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

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

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

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

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

  699.     /** {@inheritDoc} */
  700.     @Override
  701.     protected void reset(int retainFlags) {
  702.         super.reset(retainFlags);

  703.         for (RevObject obj : rootObjects)
  704.             obj.flags &= ~IN_PENDING;

  705.         rootObjects = new ArrayList<>();
  706.         pendingObjects = new BlockObjQueue();
  707.         currVisit = null;
  708.         freeVisit = null;
  709.     }

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

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

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

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

  744.             case TYPE_TREE:
  745.                 idBuffer.fromRaw(raw, ptr);
  746.                 markTreeUninteresting(lookupTree(idBuffer));
  747.                 break;

  748.             case TYPE_GITLINK:
  749.                 break;

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

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

  781.         return obj;
  782.     }

  783.     private void releaseTreeVisit(TreeVisit tv) {
  784.         tv.buf = null;
  785.         tv.parent = freeVisit;
  786.         freeVisit = tv;
  787.     }

  788.     private static class TreeVisit {
  789.         /** Parent tree visit that entered this tree, null if root tree. */
  790.         TreeVisit parent;

  791.         /** The RevTree currently being iterated through. */
  792.         RevObject obj;

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

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

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

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

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

  803.         /** Number of levels deep from the root tree. 0 for root tree. */
  804.         int depth;
  805.     }
  806. }