View Javadoc
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  
11  package org.eclipse.jgit.revwalk;
12  
13  import static java.util.Objects.requireNonNull;
14  import static org.eclipse.jgit.lib.Constants.OBJ_BLOB;
15  import static org.eclipse.jgit.lib.Constants.OBJ_COMMIT;
16  import static org.eclipse.jgit.lib.Constants.OBJ_TREE;
17  
18  import java.io.IOException;
19  import java.text.MessageFormat;
20  import java.util.ArrayList;
21  import java.util.List;
22  
23  import org.eclipse.jgit.errors.CorruptObjectException;
24  import org.eclipse.jgit.errors.IncorrectObjectTypeException;
25  import org.eclipse.jgit.errors.LargeObjectException;
26  import org.eclipse.jgit.errors.MissingObjectException;
27  import org.eclipse.jgit.internal.JGitText;
28  import org.eclipse.jgit.lib.AnyObjectId;
29  import org.eclipse.jgit.lib.ObjectReader;
30  import org.eclipse.jgit.lib.Repository;
31  import org.eclipse.jgit.revwalk.filter.ObjectFilter;
32  import org.eclipse.jgit.util.RawParseUtils;
33  
34  /**
35   * Specialized subclass of RevWalk to include trees, blobs and tags.
36   * <p>
37   * Unlike RevWalk this subclass is able to remember starting roots that include
38   * annotated tags, or arbitrary trees or blobs. Once commit generation is
39   * complete and all commits have been popped by the application, individual
40   * annotated tag, tree and blob objects can be popped through the additional
41   * method {@link #nextObject()}.
42   * <p>
43   * Tree and blob objects reachable from interesting commits are automatically
44   * scheduled for inclusion in the results of {@link #nextObject()}, returning
45   * each object exactly once. Objects are sorted and returned according to the
46   * the commits that reference them and the order they appear within a tree.
47   * Ordering can be affected by changing the
48   * {@link org.eclipse.jgit.revwalk.RevSort} used to order the commits that are
49   * returned first.
50   */
51  public class ObjectWalk extends RevWalk {
52  	private static final int ID_SZ = 20;
53  	private static final int TYPE_SHIFT = 12;
54  	private static final int TYPE_TREE = 0040000 >>> TYPE_SHIFT;
55  	private static final int TYPE_SYMLINK = 0120000 >>> TYPE_SHIFT;
56  	private static final int TYPE_FILE = 0100000 >>> TYPE_SHIFT;
57  	private static final int TYPE_GITLINK = 0160000 >>> TYPE_SHIFT;
58  
59  	/**
60  	 * Indicates a non-RevCommit is in {@link #pendingObjects}.
61  	 * <p>
62  	 * We can safely reuse {@link RevWalk#REWRITE} here for the same value as it
63  	 * is only set on RevCommit and {@link #pendingObjects} never has RevCommit
64  	 * instances inserted into it.
65  	 */
66  	private static final int IN_PENDING = RevWalk.REWRITE;
67  
68  	/**
69  	 * When walking over a tree and blob graph, objects are usually marked as
70  	 * seen as they are visited and this "seen" status is checked upon the next
71  	 * visit. If they are already "seen" then they are not processed (returned
72  	 * by {@link ObjectWalk#nextObject()}) again. However, this behavior can be
73  	 * overridden by supplying a different implementation of this class.
74  	 *
75  	 * @since 5.4
76  	 */
77  	public interface VisitationPolicy {
78  		/**
79  		 * Whenever the rev or object walk reaches a Git object, if that object
80  		 * already exists as a RevObject, this method is called to determine if
81  		 * that object should be visited.
82  		 *
83  		 * @param o
84  		 *            the object to check if it should be visited
85  		 * @return true if the object should be visited
86  		 */
87  		boolean shouldVisit(RevObject o);
88  
89  		/**
90  		 * Called when an object is visited.
91  		 *
92  		 * @param o
93  		 *            the object that was visited
94  		 */
95  		void visited(RevObject o);
96  	}
97  
98  	/**
99  	 * The default visitation policy: causes all objects to be visited exactly
100 	 * once.
101 	 *
102 	 * @since 5.4
103 	 */
104 	public static final VisitationPolicy SIMPLE_VISITATION_POLICY =
105 			new VisitationPolicy() {
106 		@Override
107 		public boolean shouldVisit(RevObject o) {
108 			return (o.flags & SEEN) == 0;
109 		}
110 
111 		@Override
112 		public void visited(RevObject o) {
113 			o.flags |= SEEN;
114 		}
115 	};
116 
117 	private List<RevObject> rootObjects;
118 
119 	private BlockObjQueue pendingObjects;
120 
121 	private ObjectFilter objectFilter;
122 
123 	private TreeVisit freeVisit;
124 
125 	private TreeVisit currVisit;
126 
127 	private byte[] pathBuf;
128 
129 	private int pathLen;
130 
131 	private boolean boundary;
132 
133 	private VisitationPolicy visitationPolicy = SIMPLE_VISITATION_POLICY;
134 
135 	/**
136 	 * Create a new revision and object walker for a given repository.
137 	 *
138 	 * @param repo
139 	 *            the repository the walker will obtain data from.
140 	 */
141 	public ObjectWalk(Repository repo) {
142 		this(repo.newObjectReader(), true);
143 	}
144 
145 	/**
146 	 * Create a new revision and object walker for a given repository.
147 	 *
148 	 * @param or
149 	 *            the reader the walker will obtain data from. The reader should
150 	 *            be released by the caller when the walker is no longer
151 	 *            required.
152 	 */
153 	public ObjectWalk(ObjectReader or) {
154 		this(or, false);
155 	}
156 
157 	private ObjectWalk(ObjectReader or, boolean closeReader) {
158 		super(or, closeReader);
159 		setRetainBody(false);
160 		rootObjects = new ArrayList<>();
161 		pendingObjects = new BlockObjQueue();
162 		objectFilter = ObjectFilter.ALL;
163 		pathBuf = new byte[256];
164 	}
165 
166 	/**
167 	 * Create an object reachability checker that will use bitmaps if possible.
168 	 *
169 	 * This reachability checker accepts any object as target. For checks
170 	 * exclusively between commits, see
171 	 * {@link RevWalk#createReachabilityChecker()}.
172 	 *
173 	 * @return an object reachability checker, using bitmaps if possible.
174 	 *
175 	 * @throws IOException
176 	 *             when the index fails to load.
177 	 *
178 	 * @since 5.8
179 	 * @deprecated use
180 	 *             {@code ObjectReader#createObjectReachabilityChecker(ObjectWalk)}
181 	 *             instead.
182 	 */
183 	@Deprecated
184 	public final ObjectReachabilityChecker createObjectReachabilityChecker()
185 			throws IOException {
186 		return reader.createObjectReachabilityChecker(this);
187 	}
188 
189 	/**
190 	 * Mark an object or commit to start graph traversal from.
191 	 * <p>
192 	 * Callers are encouraged to use
193 	 * {@link org.eclipse.jgit.revwalk.RevWalk#parseAny(AnyObjectId)} instead of
194 	 * {@link org.eclipse.jgit.revwalk.RevWalk#lookupAny(AnyObjectId, int)}, as
195 	 * this method requires the object to be parsed before it can be added as a
196 	 * root for the traversal.
197 	 * <p>
198 	 * The method will automatically parse an unparsed object, but error
199 	 * handling may be more difficult for the application to explain why a
200 	 * RevObject is not actually valid. The object pool of this walker would
201 	 * also be 'poisoned' by the invalid RevObject.
202 	 * <p>
203 	 * This method will automatically call
204 	 * {@link org.eclipse.jgit.revwalk.RevWalk#markStart(RevCommit)} if passed
205 	 * RevCommit instance, or a RevTag that directly (or indirectly) references
206 	 * a RevCommit.
207 	 *
208 	 * @param o
209 	 *            the object to start traversing from. The object passed must be
210 	 *            from this same revision walker.
211 	 * @throws org.eclipse.jgit.errors.MissingObjectException
212 	 *             the object supplied is not available from the object
213 	 *             database. This usually indicates the supplied object is
214 	 *             invalid, but the reference was constructed during an earlier
215 	 *             invocation to
216 	 *             {@link org.eclipse.jgit.revwalk.RevWalk#lookupAny(AnyObjectId, int)}.
217 	 * @throws org.eclipse.jgit.errors.IncorrectObjectTypeException
218 	 *             the object was not parsed yet and it was discovered during
219 	 *             parsing that it is not actually the type of the instance
220 	 *             passed in. This usually indicates the caller used the wrong
221 	 *             type in a
222 	 *             {@link org.eclipse.jgit.revwalk.RevWalk#lookupAny(AnyObjectId, int)}
223 	 *             call.
224 	 * @throws java.io.IOException
225 	 *             a pack file or loose object could not be read.
226 	 */
227 	public void markStart(RevObject o) throws MissingObjectException,
228 			IncorrectObjectTypeException, IOException {
229 		while (o instanceof RevTag) {
230 			addObject(o);
231 			o = ((RevTag) o).getObject();
232 			parseHeaders(o);
233 		}
234 
235 		if (o instanceof RevCommit)
236 			super.markStart((RevCommit) o);
237 		else
238 			addObject(o);
239 	}
240 
241 	/**
242 	 * Mark an object to not produce in the output.
243 	 * <p>
244 	 * Uninteresting objects denote not just themselves but also their entire
245 	 * reachable chain, back until the merge base of an uninteresting commit and
246 	 * an otherwise interesting commit.
247 	 * <p>
248 	 * Callers are encouraged to use
249 	 * {@link org.eclipse.jgit.revwalk.RevWalk#parseAny(AnyObjectId)} instead of
250 	 * {@link org.eclipse.jgit.revwalk.RevWalk#lookupAny(AnyObjectId, int)}, as
251 	 * this method requires the object to be parsed before it can be added as a
252 	 * root for the traversal.
253 	 * <p>
254 	 * The method will automatically parse an unparsed object, but error
255 	 * handling may be more difficult for the application to explain why a
256 	 * RevObject is not actually valid. The object pool of this walker would
257 	 * also be 'poisoned' by the invalid RevObject.
258 	 * <p>
259 	 * This method will automatically call
260 	 * {@link org.eclipse.jgit.revwalk.RevWalk#markStart(RevCommit)} if passed
261 	 * RevCommit instance, or a RevTag that directly (or indirectly) references
262 	 * a RevCommit.
263 	 *
264 	 * @param o
265 	 *            the object to start traversing from. The object passed must be
266 	 * @throws org.eclipse.jgit.errors.MissingObjectException
267 	 *             the object supplied is not available from the object
268 	 *             database. This usually indicates the supplied object is
269 	 *             invalid, but the reference was constructed during an earlier
270 	 *             invocation to
271 	 *             {@link org.eclipse.jgit.revwalk.RevWalk#lookupAny(AnyObjectId, int)}.
272 	 * @throws org.eclipse.jgit.errors.IncorrectObjectTypeException
273 	 *             the object was not parsed yet and it was discovered during
274 	 *             parsing that it is not actually the type of the instance
275 	 *             passed in. This usually indicates the caller used the wrong
276 	 *             type in a
277 	 *             {@link org.eclipse.jgit.revwalk.RevWalk#lookupAny(AnyObjectId, int)}
278 	 *             call.
279 	 * @throws java.io.IOException
280 	 *             a pack file or loose object could not be read.
281 	 */
282 	public void markUninteresting(RevObject o) throws MissingObjectException,
283 			IncorrectObjectTypeException, IOException {
284 		while (o instanceof RevTag) {
285 			o.flags |= UNINTERESTING;
286 			if (boundary)
287 				addObject(o);
288 			o = ((RevTag) o).getObject();
289 			parseHeaders(o);
290 		}
291 
292 		if (o instanceof RevCommit)
293 			super.markUninteresting((RevCommit) o);
294 		else if (o instanceof RevTree)
295 			markTreeUninteresting((RevTree) o);
296 		else
297 			o.flags |= UNINTERESTING;
298 
299 		if (o.getType() != OBJ_COMMIT && boundary)
300 			addObject(o);
301 	}
302 
303 	/** {@inheritDoc} */
304 	@Override
305 	public void sort(RevSort s) {
306 		super.sort(s);
307 		boundary = hasRevSort(RevSort.BOUNDARY);
308 	}
309 
310 	/** {@inheritDoc} */
311 	@Override
312 	public void sort(RevSort s, boolean use) {
313 		super.sort(s, use);
314 		boundary = hasRevSort(RevSort.BOUNDARY);
315 	}
316 
317 	/**
318 	 * Get the currently configured object filter.
319 	 *
320 	 * @return the current filter. Never null as a filter is always needed.
321 	 * @since 4.0
322 	 */
323 	public ObjectFilter getObjectFilter() {
324 		return objectFilter;
325 	}
326 
327 	/**
328 	 * Set the object filter for this walker. This filter affects the objects
329 	 * visited by {@link #nextObject()}. It does not affect the commits listed
330 	 * by {@link #next()}.
331 	 * <p>
332 	 * If the filter returns false for an object, then that object is skipped
333 	 * and objects reachable from it are not enqueued to be walked recursively.
334 	 * This can be used to speed up the object walk by skipping subtrees that
335 	 * are known to be uninteresting.
336 	 *
337 	 * @param newFilter
338 	 *            the new filter. If null the special
339 	 *            {@link org.eclipse.jgit.revwalk.filter.ObjectFilter#ALL}
340 	 *            filter will be used instead, as it matches every object.
341 	 * @since 4.0
342 	 */
343 	public void setObjectFilter(ObjectFilter newFilter) {
344 		assertNotStarted();
345 		objectFilter = newFilter != null ? newFilter : ObjectFilter.ALL;
346 	}
347 
348 	/**
349 	 * Sets the visitation policy to use during this walk.
350 	 *
351 	 * @param policy
352 	 *            the {@code VisitationPolicy} to use
353 	 * @since 5.4
354 	 */
355 	public void setVisitationPolicy(VisitationPolicy policy) {
356 		assertNotStarted();
357 		visitationPolicy = requireNonNull(policy);
358 	}
359 
360 	/** {@inheritDoc} */
361 	@Override
362 	public RevCommit next() throws MissingObjectException,
363 			IncorrectObjectTypeException, IOException {
364 		for (;;) {
365 			final RevCommit r = super.next();
366 			if (r == null) {
367 				return null;
368 			}
369 			final RevTree t = r.getTree();
370 			if ((r.flags & UNINTERESTING) != 0) {
371 				if (objectFilter.include(this, t)) {
372 					markTreeUninteresting(t);
373 				}
374 				if (boundary) {
375 					return r;
376 				}
377 				continue;
378 			}
379 			if (objectFilter.include(this, t)) {
380 				pendingObjects.add(t);
381 			}
382 			return r;
383 		}
384 	}
385 
386 	/**
387 	 * Skips the current tree such that {@link #nextObject()} does not return
388 	 * any objects inside it. This should be called right after
389 	 * {@link #nextObject()} returns the tree.
390 	 *
391 	 * @since 5.4
392 	 */
393 	public void skipTree() {
394 		if (currVisit != null) {
395 			currVisit.ptr = currVisit.buf.length;
396 		}
397 	}
398 
399 	/**
400 	 * Pop the next most recent object.
401 	 *
402 	 * @return next most recent object; null if traversal is over.
403 	 * @throws org.eclipse.jgit.errors.MissingObjectException
404 	 *             one or more of the next objects are not available from the
405 	 *             object database, but were thought to be candidates for
406 	 *             traversal. This usually indicates a broken link.
407 	 * @throws org.eclipse.jgit.errors.IncorrectObjectTypeException
408 	 *             one or more of the objects in a tree do not match the type
409 	 *             indicated.
410 	 * @throws java.io.IOException
411 	 *             a pack file or loose object could not be read.
412 	 */
413 	public RevObject nextObject() throws MissingObjectException,
414 			IncorrectObjectTypeException, IOException {
415 		pathLen = 0;
416 
417 		TreeVisit tv = currVisit;
418 		while (tv != null) {
419 			byte[] buf = tv.buf;
420 			for (int ptr = tv.ptr; ptr < buf.length;) {
421 				int startPtr = ptr;
422 				ptr = findObjectId(buf, ptr);
423 				idBuffer.fromRaw(buf, ptr);
424 				ptr += ID_SZ;
425 
426 				if (!objectFilter.include(this, idBuffer)) {
427 					continue;
428 				}
429 
430 				RevObject obj = objects.get(idBuffer);
431 				if (obj != null && !visitationPolicy.shouldVisit(obj))
432 					continue;
433 
434 				int mode = parseMode(buf, startPtr, ptr, tv);
435 				switch (mode >>> TYPE_SHIFT) {
436 				case TYPE_FILE:
437 				case TYPE_SYMLINK:
438 					if (obj == null) {
439 						obj = new RevBlob(idBuffer);
440 						visitationPolicy.visited(obj);
441 						objects.add(obj);
442 						return obj;
443 					}
444 					if (!(obj instanceof RevBlob))
445 						throw new IncorrectObjectTypeException(obj, OBJ_BLOB);
446 					visitationPolicy.visited(obj);
447 					if ((obj.flags & UNINTERESTING) == 0)
448 						return obj;
449 					if (boundary)
450 						return obj;
451 					continue;
452 
453 				case TYPE_TREE:
454 					if (obj == null) {
455 						obj = new RevTree(idBuffer);
456 						visitationPolicy.visited(obj);
457 						objects.add(obj);
458 						return pushTree(obj);
459 					}
460 					if (!(obj instanceof RevTree))
461 						throw new IncorrectObjectTypeException(obj, OBJ_TREE);
462 					visitationPolicy.visited(obj);
463 					if ((obj.flags & UNINTERESTING) == 0)
464 						return pushTree(obj);
465 					if (boundary)
466 						return pushTree(obj);
467 					continue;
468 
469 				case TYPE_GITLINK:
470 					continue;
471 
472 				default:
473 					throw new CorruptObjectException(MessageFormat.format(
474 							JGitText.get().corruptObjectInvalidMode3,
475 							String.format("%o", Integer.valueOf(mode)), //$NON-NLS-1$
476 							idBuffer.name(),
477 							RawParseUtils.decode(buf, tv.namePtr, tv.nameEnd),
478 							tv.obj));
479 				}
480 			}
481 
482 			currVisit = tv.parent;
483 			releaseTreeVisit(tv);
484 			tv = currVisit;
485 		}
486 
487 		for (;;) {
488 			RevObject o = pendingObjects.next();
489 			if (o == null) {
490 				return null;
491 			}
492 			if (!visitationPolicy.shouldVisit(o)) {
493 				continue;
494 			}
495 			visitationPolicy.visited(o);
496 			if ((o.flags & UNINTERESTING) == 0 || boundary) {
497 				if (o instanceof RevTree) {
498 					// The previous while loop should have exhausted the stack
499 					// of trees.
500 					assert currVisit == null;
501 
502 					pushTree(o);
503 				}
504 				return o;
505 			}
506 		}
507 	}
508 
509 	private static int findObjectId(byte[] buf, int ptr) {
510 		// Skip over the mode and name until the NUL before the ObjectId
511 		// can be located. Skip the NUL as the function returns.
512 		for (;;) {
513 			if (buf[++ptr] == 0) return ++ptr;
514 			if (buf[++ptr] == 0) return ++ptr;
515 			if (buf[++ptr] == 0) return ++ptr;
516 			if (buf[++ptr] == 0) return ++ptr;
517 
518 			if (buf[++ptr] == 0) return ++ptr;
519 			if (buf[++ptr] == 0) return ++ptr;
520 			if (buf[++ptr] == 0) return ++ptr;
521 			if (buf[++ptr] == 0) return ++ptr;
522 
523 			if (buf[++ptr] == 0) return ++ptr;
524 			if (buf[++ptr] == 0) return ++ptr;
525 			if (buf[++ptr] == 0) return ++ptr;
526 			if (buf[++ptr] == 0) return ++ptr;
527 
528 			if (buf[++ptr] == 0) return ++ptr;
529 			if (buf[++ptr] == 0) return ++ptr;
530 			if (buf[++ptr] == 0) return ++ptr;
531 			if (buf[++ptr] == 0) return ++ptr;
532 		}
533 	}
534 
535 	private static int parseMode(byte[] buf, int startPtr, int recEndPtr, TreeVisit tv) {
536 		int mode = buf[startPtr] - '0';
537 		for (;;) {
538 			byte c = buf[++startPtr];
539 			if (' ' == c)
540 				break;
541 			mode <<= 3;
542 			mode += c - '0';
543 
544 			c = buf[++startPtr];
545 			if (' ' == c)
546 				break;
547 			mode <<= 3;
548 			mode += c - '0';
549 
550 			c = buf[++startPtr];
551 			if (' ' == c)
552 				break;
553 			mode <<= 3;
554 			mode += c - '0';
555 
556 			c = buf[++startPtr];
557 			if (' ' == c)
558 				break;
559 			mode <<= 3;
560 			mode += c - '0';
561 
562 			c = buf[++startPtr];
563 			if (' ' == c)
564 				break;
565 			mode <<= 3;
566 			mode += c - '0';
567 
568 			c = buf[++startPtr];
569 			if (' ' == c)
570 				break;
571 			mode <<= 3;
572 			mode += c - '0';
573 
574 			c = buf[++startPtr];
575 			if (' ' == c)
576 				break;
577 			mode <<= 3;
578 			mode += c - '0';
579 		}
580 
581 		tv.ptr = recEndPtr;
582 		tv.namePtr = startPtr + 1;
583 		tv.nameEnd = recEndPtr - (ID_SZ + 1);
584 		return mode;
585 	}
586 
587 	/**
588 	 * Verify all interesting objects are available, and reachable.
589 	 * <p>
590 	 * Callers should populate starting points and ending points with
591 	 * {@link #markStart(RevObject)} and {@link #markUninteresting(RevObject)}
592 	 * and then use this method to verify all objects between those two points
593 	 * exist in the repository and are readable.
594 	 * <p>
595 	 * This method returns successfully if everything is connected; it throws an
596 	 * exception if there is a connectivity problem. The exception message
597 	 * provides some detail about the connectivity failure.
598 	 *
599 	 * @throws org.eclipse.jgit.errors.MissingObjectException
600 	 *             one or more of the next objects are not available from the
601 	 *             object database, but were thought to be candidates for
602 	 *             traversal. This usually indicates a broken link.
603 	 * @throws org.eclipse.jgit.errors.IncorrectObjectTypeException
604 	 *             one or more of the objects in a tree do not match the type
605 	 *             indicated.
606 	 * @throws java.io.IOException
607 	 *             a pack file or loose object could not be read.
608 	 */
609 	public void checkConnectivity() throws MissingObjectException,
610 			IncorrectObjectTypeException, IOException {
611 		for (;;) {
612 			final RevCommit c = next();
613 			if (c == null)
614 				break;
615 		}
616 		for (;;) {
617 			final RevObject o = nextObject();
618 			if (o == null)
619 				break;
620 			if (o instanceof RevBlob && !reader.has(o))
621 				throw new MissingObjectException(o, OBJ_BLOB);
622 		}
623 	}
624 
625 	/**
626 	 * Get the current object's complete path.
627 	 * <p>
628 	 * This method is not very efficient and is primarily meant for debugging
629 	 * and final output generation. Applications should try to avoid calling it,
630 	 * and if invoked do so only once per interesting entry, where the name is
631 	 * absolutely required for correct function.
632 	 *
633 	 * @return complete path of the current entry, from the root of the
634 	 *         repository. If the current entry is in a subtree there will be at
635 	 *         least one '/' in the returned string. Null if the current entry
636 	 *         has no path, such as for annotated tags or root level trees.
637 	 */
638 	public String getPathString() {
639 		if (pathLen == 0) {
640 			pathLen = updatePathBuf(currVisit);
641 			if (pathLen == 0)
642 				return null;
643 		}
644 		return RawParseUtils.decode(pathBuf, 0, pathLen);
645 	}
646 
647 	/**
648 	 * @return the current traversal depth from the root tree object
649 	 * @since 5.4
650 	 */
651 	public int getTreeDepth() {
652 		if (currVisit == null) {
653 			return 0;
654 		}
655 		return currVisit.depth;
656 	}
657 
658 	/**
659 	 * Get the current object's path hash code.
660 	 * <p>
661 	 * This method computes a hash code on the fly for this path, the hash is
662 	 * suitable to cluster objects that may have similar paths together.
663 	 *
664 	 * @return path hash code; any integer may be returned.
665 	 */
666 	public int getPathHashCode() {
667 		TreeVisit tv = currVisit;
668 		if (tv == null)
669 			return 0;
670 
671 		int nameEnd = tv.nameEnd;
672 		if (nameEnd == 0) {
673 			// When nameEnd == 0 the subtree is itself the current path
674 			// being visited. The name hash must be obtained from its
675 			// parent tree. If there is no parent, this is a root tree with
676 			// a hash code of 0.
677 			tv = tv.parent;
678 			if (tv == null)
679 				return 0;
680 			nameEnd = tv.nameEnd;
681 		}
682 
683 		byte[] buf;
684 		int ptr;
685 
686 		if (16 <= (nameEnd - tv.namePtr)) {
687 			buf = tv.buf;
688 			ptr = nameEnd - 16;
689 		} else {
690 			nameEnd = pathLen;
691 			if (nameEnd == 0) {
692 				nameEnd = updatePathBuf(currVisit);
693 				pathLen = nameEnd;
694 			}
695 			buf = pathBuf;
696 			ptr = Math.max(0, nameEnd - 16);
697 		}
698 
699 		int hash = 0;
700 		for (; ptr < nameEnd; ptr++) {
701 			byte c = buf[ptr];
702 			if (c != ' ')
703 				hash = (hash >>> 2) + (c << 24);
704 		}
705 		return hash;
706 	}
707 
708 	/**
709 	 * Get the internal buffer holding the current path.
710 	 *
711 	 * @return the internal buffer holding the current path.
712 	 */
713 	public byte[] getPathBuffer() {
714 		if (pathLen == 0)
715 			pathLen = updatePathBuf(currVisit);
716 		return pathBuf;
717 	}
718 
719 	/**
720 	 * Get length of the path in {@link #getPathBuffer()}.
721 	 *
722 	 * @return length of the path in {@link #getPathBuffer()}.
723 	 */
724 	public int getPathLength() {
725 		if (pathLen == 0)
726 			pathLen = updatePathBuf(currVisit);
727 		return pathLen;
728 	}
729 
730 	private int updatePathBuf(TreeVisit tv) {
731 		if (tv == null)
732 			return 0;
733 
734 		// If nameEnd == 0 this tree has not yet contributed an entry.
735 		// Update only for the parent, which if null will be empty.
736 		int nameEnd = tv.nameEnd;
737 		if (nameEnd == 0)
738 			return updatePathBuf(tv.parent);
739 
740 		int ptr = tv.pathLen;
741 		if (ptr == 0) {
742 			ptr = updatePathBuf(tv.parent);
743 			if (ptr == pathBuf.length)
744 				growPathBuf(ptr);
745 			if (ptr != 0)
746 				pathBuf[ptr++] = '/';
747 			tv.pathLen = ptr;
748 		}
749 
750 		int namePtr = tv.namePtr;
751 		int nameLen = nameEnd - namePtr;
752 		int end = ptr + nameLen;
753 		while (pathBuf.length < end)
754 			growPathBuf(ptr);
755 		System.arraycopy(tv.buf, namePtr, pathBuf, ptr, nameLen);
756 		return end;
757 	}
758 
759 	private void growPathBuf(int ptr) {
760 		byte[] newBuf = new byte[pathBuf.length << 1];
761 		System.arraycopy(pathBuf, 0, newBuf, 0, ptr);
762 		pathBuf = newBuf;
763 	}
764 
765 	/** {@inheritDoc} */
766 	@Override
767 	public void dispose() {
768 		super.dispose();
769 		pendingObjects = new BlockObjQueue();
770 		currVisit = null;
771 		freeVisit = null;
772 	}
773 
774 	/** {@inheritDoc} */
775 	@Override
776 	protected void reset(int retainFlags) {
777 		super.reset(retainFlags);
778 
779 		for (RevObject obj : rootObjects)
780 			obj.flags &= ~IN_PENDING;
781 
782 		rootObjects = new ArrayList<>();
783 		pendingObjects = new BlockObjQueue();
784 		currVisit = null;
785 		freeVisit = null;
786 	}
787 
788 	private void addObject(RevObject o) {
789 		if ((o.flags & IN_PENDING) == 0) {
790 			o.flags |= IN_PENDING;
791 			rootObjects.add(o);
792 			pendingObjects.add(o);
793 		}
794 	}
795 
796 	private void markTreeUninteresting(RevTree tree)
797 			throws MissingObjectException, IncorrectObjectTypeException,
798 			IOException {
799 		if ((tree.flags & UNINTERESTING) != 0)
800 			return;
801 		tree.flags |= UNINTERESTING;
802 
803 		byte[] raw = reader.open(tree, OBJ_TREE).getCachedBytes();
804 		for (int ptr = 0; ptr < raw.length;) {
805 			byte c = raw[ptr];
806 			int mode = c - '0';
807 			for (;;) {
808 				c = raw[++ptr];
809 				if (' ' == c)
810 					break;
811 				mode <<= 3;
812 				mode += c - '0';
813 			}
814 			while (raw[++ptr] != 0) {
815 				// Skip entry name.
816 			}
817 			ptr++; // Skip NUL after entry name.
818 
819 			switch (mode >>> TYPE_SHIFT) {
820 			case TYPE_FILE:
821 			case TYPE_SYMLINK:
822 				idBuffer.fromRaw(raw, ptr);
823 				lookupBlob(idBuffer).flags |= UNINTERESTING;
824 				break;
825 
826 			case TYPE_TREE:
827 				idBuffer.fromRaw(raw, ptr);
828 				markTreeUninteresting(lookupTree(idBuffer));
829 				break;
830 
831 			case TYPE_GITLINK:
832 				break;
833 
834 			default:
835 				idBuffer.fromRaw(raw, ptr);
836 				throw new CorruptObjectException(MessageFormat.format(
837 						JGitText.get().corruptObjectInvalidMode3,
838 						String.format("%o", Integer.valueOf(mode)), //$NON-NLS-1$
839 						idBuffer.name(), "", tree)); //$NON-NLS-1$
840 			}
841 			ptr += ID_SZ;
842 		}
843 	}
844 
845 	private RevObject pushTree(RevObject obj) throws LargeObjectException,
846 			MissingObjectException, IncorrectObjectTypeException, IOException {
847 		TreeVisit tv = freeVisit;
848 		if (tv != null) {
849 			freeVisit = tv.parent;
850 			tv.ptr = 0;
851 			tv.namePtr = 0;
852 			tv.nameEnd = 0;
853 			tv.pathLen = 0;
854 		} else {
855 			tv = new TreeVisit();
856 		}
857 		tv.obj = obj;
858 		tv.buf = reader.open(obj, OBJ_TREE).getCachedBytes();
859 		tv.parent = currVisit;
860 		currVisit = tv;
861 		if (tv.parent == null) {
862 			tv.depth = 1;
863 		} else {
864 			tv.depth = tv.parent.depth + 1;
865 		}
866 
867 		return obj;
868 	}
869 
870 	private void releaseTreeVisit(TreeVisit tv) {
871 		tv.buf = null;
872 		tv.parent = freeVisit;
873 		freeVisit = tv;
874 	}
875 
876 	private static class TreeVisit {
877 		/** Parent tree visit that entered this tree, null if root tree. */
878 		TreeVisit parent;
879 
880 		/** The RevTree currently being iterated through. */
881 		RevObject obj;
882 
883 		/** Canonical encoding of the tree named by {@link #obj}. */
884 		byte[] buf;
885 
886 		/** Index of next entry to parse in {@link #buf}. */
887 		int ptr;
888 
889 		/** Start of the current name entry in {@link #buf}. */
890 		int namePtr;
891 
892 		/** One past end of name, {@code nameEnd - namePtr} is the length. */
893 		int nameEnd;
894 
895 		/** Number of bytes in the path leading up to this tree. */
896 		int pathLen;
897 
898 		/** Number of levels deep from the root tree. 0 for root tree. */
899 		int depth;
900 	}
901 }