View Javadoc
1   /*
2    * Copyright (C) 2008, Shawn O. Pearce <spearce@spearce.org>
3    * and other copyright owners as documented in the project's IP log.
4    *
5    * This program and the accompanying materials are made available
6    * under the terms of the Eclipse Distribution License v1.0 which
7    * accompanies this distribution, is reproduced below, and is
8    * available at http://www.eclipse.org/org/documents/edl-v10.php
9    *
10   * All rights reserved.
11   *
12   * Redistribution and use in source and binary forms, with or
13   * without modification, are permitted provided that the following
14   * conditions are met:
15   *
16   * - Redistributions of source code must retain the above copyright
17   *   notice, this list of conditions and the following disclaimer.
18   *
19   * - Redistributions in binary form must reproduce the above
20   *   copyright notice, this list of conditions and the following
21   *   disclaimer in the documentation and/or other materials provided
22   *   with the distribution.
23   *
24   * - Neither the name of the Eclipse Foundation, Inc. nor the
25   *   names of its contributors may be used to endorse or promote
26   *   products derived from this software without specific prior
27   *   written permission.
28   *
29   * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
30   * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
31   * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
32   * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
33   * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
34   * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
35   * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
36   * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
37   * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
38   * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
39   * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
40   * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
41   * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
42   */
43  
44  package org.eclipse.jgit.revwalk;
45  
46  import static org.eclipse.jgit.lib.Constants.OBJ_BLOB;
47  import static org.eclipse.jgit.lib.Constants.OBJ_COMMIT;
48  import static org.eclipse.jgit.lib.Constants.OBJ_TREE;
49  
50  import java.io.IOException;
51  import java.text.MessageFormat;
52  import java.util.ArrayList;
53  import java.util.List;
54  
55  import org.eclipse.jgit.errors.CorruptObjectException;
56  import org.eclipse.jgit.errors.IncorrectObjectTypeException;
57  import org.eclipse.jgit.errors.LargeObjectException;
58  import org.eclipse.jgit.errors.MissingObjectException;
59  import org.eclipse.jgit.internal.JGitText;
60  import org.eclipse.jgit.lib.AnyObjectId;
61  import org.eclipse.jgit.lib.ObjectReader;
62  import org.eclipse.jgit.lib.Repository;
63  import org.eclipse.jgit.revwalk.filter.ObjectFilter;
64  import org.eclipse.jgit.util.RawParseUtils;
65  
66  /**
67   * Specialized subclass of RevWalk to include trees, blobs and tags.
68   * <p>
69   * Unlike RevWalk this subclass is able to remember starting roots that include
70   * annotated tags, or arbitrary trees or blobs. Once commit generation is
71   * complete and all commits have been popped by the application, individual
72   * annotated tag, tree and blob objects can be popped through the additional
73   * method {@link #nextObject()}.
74   * <p>
75   * Tree and blob objects reachable from interesting commits are automatically
76   * scheduled for inclusion in the results of {@link #nextObject()}, returning
77   * each object exactly once. Objects are sorted and returned according to the
78   * the commits that reference them and the order they appear within a tree.
79   * Ordering can be affected by changing the
80   * {@link org.eclipse.jgit.revwalk.RevSort} used to order the commits that are
81   * returned first.
82   */
83  public class ObjectWalk extends RevWalk {
84  	private static final int ID_SZ = 20;
85  	private static final int TYPE_SHIFT = 12;
86  	private static final int TYPE_TREE = 0040000 >>> TYPE_SHIFT;
87  	private static final int TYPE_SYMLINK = 0120000 >>> TYPE_SHIFT;
88  	private static final int TYPE_FILE = 0100000 >>> TYPE_SHIFT;
89  	private static final int TYPE_GITLINK = 0160000 >>> TYPE_SHIFT;
90  
91  	/**
92  	 * Indicates a non-RevCommit is in {@link #pendingObjects}.
93  	 * <p>
94  	 * We can safely reuse {@link RevWalk#REWRITE} here for the same value as it
95  	 * is only set on RevCommit and {@link #pendingObjects} never has RevCommit
96  	 * instances inserted into it.
97  	 */
98  	private static final int IN_PENDING = RevWalk.REWRITE;
99  
100 	private List<RevObject> rootObjects;
101 
102 	private BlockObjQueue pendingObjects;
103 
104 	private ObjectFilter objectFilter;
105 
106 	private TreeVisit freeVisit;
107 
108 	private TreeVisit currVisit;
109 
110 	private byte[] pathBuf;
111 
112 	private int pathLen;
113 
114 	private boolean boundary;
115 
116 	/**
117 	 * Create a new revision and object walker for a given repository.
118 	 *
119 	 * @param repo
120 	 *            the repository the walker will obtain data from.
121 	 */
122 	public ObjectWalk(Repository repo) {
123 		this(repo.newObjectReader());
124 	}
125 
126 	/**
127 	 * Create a new revision and object walker for a given repository.
128 	 *
129 	 * @param or
130 	 *            the reader the walker will obtain data from. The reader should
131 	 *            be released by the caller when the walker is no longer
132 	 *            required.
133 	 */
134 	public ObjectWalk(ObjectReader or) {
135 		super(or);
136 		setRetainBody(false);
137 		rootObjects = new ArrayList<>();
138 		pendingObjects = new BlockObjQueue();
139 		objectFilter = ObjectFilter.ALL;
140 		pathBuf = new byte[256];
141 	}
142 
143 	/**
144 	 * Mark an object or commit to start graph traversal from.
145 	 * <p>
146 	 * Callers are encouraged to use
147 	 * {@link org.eclipse.jgit.revwalk.RevWalk#parseAny(AnyObjectId)} instead of
148 	 * {@link org.eclipse.jgit.revwalk.RevWalk#lookupAny(AnyObjectId, int)}, as
149 	 * this method requires the object to be parsed before it can be added as a
150 	 * root for the traversal.
151 	 * <p>
152 	 * The method will automatically parse an unparsed object, but error
153 	 * handling may be more difficult for the application to explain why a
154 	 * RevObject is not actually valid. The object pool of this walker would
155 	 * also be 'poisoned' by the invalid RevObject.
156 	 * <p>
157 	 * This method will automatically call
158 	 * {@link org.eclipse.jgit.revwalk.RevWalk#markStart(RevCommit)} if passed
159 	 * RevCommit instance, or a RevTag that directly (or indirectly) references
160 	 * a RevCommit.
161 	 *
162 	 * @param o
163 	 *            the object to start traversing from. The object passed must be
164 	 *            from this same revision walker.
165 	 * @throws org.eclipse.jgit.errors.MissingObjectException
166 	 *             the object supplied is not available from the object
167 	 *             database. This usually indicates the supplied object is
168 	 *             invalid, but the reference was constructed during an earlier
169 	 *             invocation to
170 	 *             {@link org.eclipse.jgit.revwalk.RevWalk#lookupAny(AnyObjectId, int)}.
171 	 * @throws org.eclipse.jgit.errors.IncorrectObjectTypeException
172 	 *             the object was not parsed yet and it was discovered during
173 	 *             parsing that it is not actually the type of the instance
174 	 *             passed in. This usually indicates the caller used the wrong
175 	 *             type in a
176 	 *             {@link org.eclipse.jgit.revwalk.RevWalk#lookupAny(AnyObjectId, int)}
177 	 *             call.
178 	 * @throws java.io.IOException
179 	 *             a pack file or loose object could not be read.
180 	 */
181 	public void markStart(RevObject o) throws MissingObjectException,
182 			IncorrectObjectTypeException, IOException {
183 		while (o instanceof RevTag) {
184 			addObject(o);
185 			o = ((RevTag) o).getObject();
186 			parseHeaders(o);
187 		}
188 
189 		if (o instanceof RevCommit)
190 			super.markStart((RevCommit) o);
191 		else
192 			addObject(o);
193 	}
194 
195 	/**
196 	 * Mark an object to not produce in the output.
197 	 * <p>
198 	 * Uninteresting objects denote not just themselves but also their entire
199 	 * reachable chain, back until the merge base of an uninteresting commit and
200 	 * an otherwise interesting commit.
201 	 * <p>
202 	 * Callers are encouraged to use
203 	 * {@link org.eclipse.jgit.revwalk.RevWalk#parseAny(AnyObjectId)} instead of
204 	 * {@link org.eclipse.jgit.revwalk.RevWalk#lookupAny(AnyObjectId, int)}, as
205 	 * this method requires the object to be parsed before it can be added as a
206 	 * root for the traversal.
207 	 * <p>
208 	 * The method will automatically parse an unparsed object, but error
209 	 * handling may be more difficult for the application to explain why a
210 	 * RevObject is not actually valid. The object pool of this walker would
211 	 * also be 'poisoned' by the invalid RevObject.
212 	 * <p>
213 	 * This method will automatically call
214 	 * {@link org.eclipse.jgit.revwalk.RevWalk#markStart(RevCommit)} if passed
215 	 * RevCommit instance, or a RevTag that directly (or indirectly) references
216 	 * a RevCommit.
217 	 *
218 	 * @param o
219 	 *            the object to start traversing from. The object passed must be
220 	 * @throws org.eclipse.jgit.errors.MissingObjectException
221 	 *             the object supplied is not available from the object
222 	 *             database. This usually indicates the supplied object is
223 	 *             invalid, but the reference was constructed during an earlier
224 	 *             invocation to
225 	 *             {@link org.eclipse.jgit.revwalk.RevWalk#lookupAny(AnyObjectId, int)}.
226 	 * @throws org.eclipse.jgit.errors.IncorrectObjectTypeException
227 	 *             the object was not parsed yet and it was discovered during
228 	 *             parsing that it is not actually the type of the instance
229 	 *             passed in. This usually indicates the caller used the wrong
230 	 *             type in a
231 	 *             {@link org.eclipse.jgit.revwalk.RevWalk#lookupAny(AnyObjectId, int)}
232 	 *             call.
233 	 * @throws java.io.IOException
234 	 *             a pack file or loose object could not be read.
235 	 */
236 	public void markUninteresting(RevObject o) throws MissingObjectException,
237 			IncorrectObjectTypeException, IOException {
238 		while (o instanceof RevTag) {
239 			o.flags |= UNINTERESTING;
240 			if (boundary)
241 				addObject(o);
242 			o = ((RevTag) o).getObject();
243 			parseHeaders(o);
244 		}
245 
246 		if (o instanceof RevCommit)
247 			super.markUninteresting((RevCommit) o);
248 		else if (o instanceof RevTree)
249 			markTreeUninteresting((RevTree) o);
250 		else
251 			o.flags |= UNINTERESTING;
252 
253 		if (o.getType() != OBJ_COMMIT && boundary)
254 			addObject(o);
255 	}
256 
257 	/** {@inheritDoc} */
258 	@Override
259 	public void sort(RevSort s) {
260 		super.sort(s);
261 		boundary = hasRevSort(RevSort.BOUNDARY);
262 	}
263 
264 	/** {@inheritDoc} */
265 	@Override
266 	public void sort(RevSort s, boolean use) {
267 		super.sort(s, use);
268 		boundary = hasRevSort(RevSort.BOUNDARY);
269 	}
270 
271 	/**
272 	 * Get the currently configured object filter.
273 	 *
274 	 * @return the current filter. Never null as a filter is always needed.
275 	 * @since 4.0
276 	 */
277 	public ObjectFilter getObjectFilter() {
278 		return objectFilter;
279 	}
280 
281 	/**
282 	 * Set the object filter for this walker. This filter affects the objects
283 	 * visited by {@link #nextObject()}. It does not affect the commits listed
284 	 * by {@link #next()}.
285 	 * <p>
286 	 * If the filter returns false for an object, then that object is skipped
287 	 * and objects reachable from it are not enqueued to be walked recursively.
288 	 * This can be used to speed up the object walk by skipping subtrees that
289 	 * are known to be uninteresting.
290 	 *
291 	 * @param newFilter
292 	 *            the new filter. If null the special
293 	 *            {@link org.eclipse.jgit.revwalk.filter.ObjectFilter#ALL}
294 	 *            filter will be used instead, as it matches every object.
295 	 * @since 4.0
296 	 */
297 	public void setObjectFilter(ObjectFilter newFilter) {
298 		assertNotStarted();
299 		objectFilter = newFilter != null ? newFilter : ObjectFilter.ALL;
300 	}
301 
302 	/** {@inheritDoc} */
303 	@Override
304 	public RevCommit next() throws MissingObjectException,
305 			IncorrectObjectTypeException, IOException {
306 		for (;;) {
307 			final RevCommit r = super.next();
308 			if (r == null) {
309 				return null;
310 			}
311 			final RevTree t = r.getTree();
312 			if ((r.flags & UNINTERESTING) != 0) {
313 				if (objectFilter.include(this, t)) {
314 					markTreeUninteresting(t);
315 				}
316 				if (boundary) {
317 					return r;
318 				}
319 				continue;
320 			}
321 			if (objectFilter.include(this, t)) {
322 				pendingObjects.add(t);
323 			}
324 			return r;
325 		}
326 	}
327 
328 	/**
329 	 * Pop the next most recent object.
330 	 *
331 	 * @return next most recent object; null if traversal is over.
332 	 * @throws org.eclipse.jgit.errors.MissingObjectException
333 	 *             one or or more of the next objects are not available from the
334 	 *             object database, but were thought to be candidates for
335 	 *             traversal. This usually indicates a broken link.
336 	 * @throws org.eclipse.jgit.errors.IncorrectObjectTypeException
337 	 *             one or or more of the objects in a tree do not match the type
338 	 *             indicated.
339 	 * @throws java.io.IOException
340 	 *             a pack file or loose object could not be read.
341 	 */
342 	public RevObject nextObject() throws MissingObjectException,
343 			IncorrectObjectTypeException, IOException {
344 		pathLen = 0;
345 
346 		TreeVisit tv = currVisit;
347 		while (tv != null) {
348 			byte[] buf = tv.buf;
349 			for (int ptr = tv.ptr; ptr < buf.length;) {
350 				int startPtr = ptr;
351 				ptr = findObjectId(buf, ptr);
352 				idBuffer.fromRaw(buf, ptr);
353 				ptr += ID_SZ;
354 
355 				if (!objectFilter.include(this, idBuffer)) {
356 					continue;
357 				}
358 
359 				RevObject obj = objects.get(idBuffer);
360 				if (obj != null && (obj.flags & SEEN) != 0)
361 					continue;
362 
363 				int mode = parseMode(buf, startPtr, ptr, tv);
364 				int flags;
365 				switch (mode >>> TYPE_SHIFT) {
366 				case TYPE_FILE:
367 				case TYPE_SYMLINK:
368 					if (obj == null) {
369 						obj = new RevBlob(idBuffer);
370 						obj.flags = SEEN;
371 						objects.add(obj);
372 						return obj;
373 					}
374 					if (!(obj instanceof RevBlob))
375 						throw new IncorrectObjectTypeException(obj, OBJ_BLOB);
376 					obj.flags = flags = obj.flags | SEEN;
377 					if ((flags & UNINTERESTING) == 0)
378 						return obj;
379 					if (boundary)
380 						return obj;
381 					continue;
382 
383 				case TYPE_TREE:
384 					if (obj == null) {
385 						obj = new RevTree(idBuffer);
386 						obj.flags = SEEN;
387 						objects.add(obj);
388 						return enterTree(obj);
389 					}
390 					if (!(obj instanceof RevTree))
391 						throw new IncorrectObjectTypeException(obj, OBJ_TREE);
392 					obj.flags = flags = obj.flags | SEEN;
393 					if ((flags & UNINTERESTING) == 0)
394 						return enterTree(obj);
395 					if (boundary)
396 						return enterTree(obj);
397 					continue;
398 
399 				case TYPE_GITLINK:
400 					continue;
401 
402 				default:
403 					throw new CorruptObjectException(MessageFormat.format(
404 							JGitText.get().corruptObjectInvalidMode3,
405 							String.format("%o", Integer.valueOf(mode)), //$NON-NLS-1$
406 							idBuffer.name(),
407 							RawParseUtils.decode(buf, tv.namePtr, tv.nameEnd),
408 							tv.obj));
409 				}
410 			}
411 
412 			currVisit = tv.parent;
413 			releaseTreeVisit(tv);
414 			tv = currVisit;
415 		}
416 
417 		for (;;) {
418 			RevObject o = pendingObjects.next();
419 			if (o == null) {
420 				return null;
421 			}
422 			int flags = o.flags;
423 			if ((flags & SEEN) != 0)
424 				continue;
425 			flags |= SEEN;
426 			o.flags = flags;
427 			if ((flags & UNINTERESTING) == 0 | boundary) {
428 				if (o instanceof RevTree) {
429 					tv = newTreeVisit(o);
430 					tv.parent = null;
431 					currVisit = tv;
432 				}
433 				return o;
434 			}
435 		}
436 	}
437 
438 	private RevObject enterTree(RevObject obj) throws MissingObjectException,
439 			IncorrectObjectTypeException, IOException {
440 		TreeVisit tv = newTreeVisit(obj);
441 		tv.parent = currVisit;
442 		currVisit = tv;
443 		return obj;
444 	}
445 
446 	private static int findObjectId(byte[] buf, int ptr) {
447 		// Skip over the mode and name until the NUL before the ObjectId
448 		// can be located. Skip the NUL as the function returns.
449 		for (;;) {
450 			if (buf[++ptr] == 0) return ++ptr;
451 			if (buf[++ptr] == 0) return ++ptr;
452 			if (buf[++ptr] == 0) return ++ptr;
453 			if (buf[++ptr] == 0) return ++ptr;
454 
455 			if (buf[++ptr] == 0) return ++ptr;
456 			if (buf[++ptr] == 0) return ++ptr;
457 			if (buf[++ptr] == 0) return ++ptr;
458 			if (buf[++ptr] == 0) return ++ptr;
459 
460 			if (buf[++ptr] == 0) return ++ptr;
461 			if (buf[++ptr] == 0) return ++ptr;
462 			if (buf[++ptr] == 0) return ++ptr;
463 			if (buf[++ptr] == 0) return ++ptr;
464 
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 		}
470 	}
471 
472 	private static int parseMode(byte[] buf, int startPtr, int recEndPtr, TreeVisit tv) {
473 		int mode = buf[startPtr] - '0';
474 		for (;;) {
475 			byte c = buf[++startPtr];
476 			if (' ' == c)
477 				break;
478 			mode <<= 3;
479 			mode += c - '0';
480 
481 			c = buf[++startPtr];
482 			if (' ' == c)
483 				break;
484 			mode <<= 3;
485 			mode += c - '0';
486 
487 			c = buf[++startPtr];
488 			if (' ' == c)
489 				break;
490 			mode <<= 3;
491 			mode += c - '0';
492 
493 			c = buf[++startPtr];
494 			if (' ' == c)
495 				break;
496 			mode <<= 3;
497 			mode += c - '0';
498 
499 			c = buf[++startPtr];
500 			if (' ' == c)
501 				break;
502 			mode <<= 3;
503 			mode += c - '0';
504 
505 			c = buf[++startPtr];
506 			if (' ' == c)
507 				break;
508 			mode <<= 3;
509 			mode += c - '0';
510 
511 			c = buf[++startPtr];
512 			if (' ' == c)
513 				break;
514 			mode <<= 3;
515 			mode += c - '0';
516 		}
517 
518 		tv.ptr = recEndPtr;
519 		tv.namePtr = startPtr + 1;
520 		tv.nameEnd = recEndPtr - (ID_SZ + 1);
521 		return mode;
522 	}
523 
524 	/**
525 	 * Verify all interesting objects are available, and reachable.
526 	 * <p>
527 	 * Callers should populate starting points and ending points with
528 	 * {@link #markStart(RevObject)} and {@link #markUninteresting(RevObject)}
529 	 * and then use this method to verify all objects between those two points
530 	 * exist in the repository and are readable.
531 	 * <p>
532 	 * This method returns successfully if everything is connected; it throws an
533 	 * exception if there is a connectivity problem. The exception message
534 	 * provides some detail about the connectivity failure.
535 	 *
536 	 * @throws org.eclipse.jgit.errors.MissingObjectException
537 	 *             one or or more of the next objects are not available from the
538 	 *             object database, but were thought to be candidates for
539 	 *             traversal. This usually indicates a broken link.
540 	 * @throws org.eclipse.jgit.errors.IncorrectObjectTypeException
541 	 *             one or or more of the objects in a tree do not match the type
542 	 *             indicated.
543 	 * @throws java.io.IOException
544 	 *             a pack file or loose object could not be read.
545 	 */
546 	public void checkConnectivity() throws MissingObjectException,
547 			IncorrectObjectTypeException, IOException {
548 		for (;;) {
549 			final RevCommit c = next();
550 			if (c == null)
551 				break;
552 		}
553 		for (;;) {
554 			final RevObject o = nextObject();
555 			if (o == null)
556 				break;
557 			if (o instanceof RevBlob && !reader.has(o))
558 				throw new MissingObjectException(o, OBJ_BLOB);
559 		}
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 	/**
585 	 * Get the current object's path hash code.
586 	 * <p>
587 	 * This method computes a hash code on the fly for this path, the hash is
588 	 * suitable to cluster objects that may have similar paths together.
589 	 *
590 	 * @return path hash code; any integer may be returned.
591 	 */
592 	public int getPathHashCode() {
593 		TreeVisit tv = currVisit;
594 		if (tv == null)
595 			return 0;
596 
597 		int nameEnd = tv.nameEnd;
598 		if (nameEnd == 0) {
599 			// When nameEnd == 0 the subtree is itself the current path
600 			// being visited. The name hash must be obtained from its
601 			// parent tree. If there is no parent, this is a root tree with
602 			// a hash code of 0.
603 			tv = tv.parent;
604 			if (tv == null)
605 				return 0;
606 			nameEnd = tv.nameEnd;
607 		}
608 
609 		byte[] buf;
610 		int ptr;
611 
612 		if (16 <= (nameEnd - tv.namePtr)) {
613 			buf = tv.buf;
614 			ptr = nameEnd - 16;
615 		} else {
616 			nameEnd = pathLen;
617 			if (nameEnd == 0) {
618 				nameEnd = updatePathBuf(currVisit);
619 				pathLen = nameEnd;
620 			}
621 			buf = pathBuf;
622 			ptr = Math.max(0, nameEnd - 16);
623 		}
624 
625 		int hash = 0;
626 		for (; ptr < nameEnd; ptr++) {
627 			byte c = buf[ptr];
628 			if (c != ' ')
629 				hash = (hash >>> 2) + (c << 24);
630 		}
631 		return hash;
632 	}
633 
634 	/**
635 	 * Get the internal buffer holding the current path.
636 	 *
637 	 * @return the internal buffer holding the current path.
638 	 */
639 	public byte[] getPathBuffer() {
640 		if (pathLen == 0)
641 			pathLen = updatePathBuf(currVisit);
642 		return pathBuf;
643 	}
644 
645 	/**
646 	 * Get length of the path in {@link #getPathBuffer()}.
647 	 *
648 	 * @return length of the path in {@link #getPathBuffer()}.
649 	 */
650 	public int getPathLength() {
651 		if (pathLen == 0)
652 			pathLen = updatePathBuf(currVisit);
653 		return pathLen;
654 	}
655 
656 	private int updatePathBuf(TreeVisit tv) {
657 		if (tv == null)
658 			return 0;
659 
660 		// If nameEnd == 0 this tree has not yet contributed an entry.
661 		// Update only for the parent, which if null will be empty.
662 		int nameEnd = tv.nameEnd;
663 		if (nameEnd == 0)
664 			return updatePathBuf(tv.parent);
665 
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 
676 		int namePtr = tv.namePtr;
677 		int nameLen = nameEnd - namePtr;
678 		int end = ptr + nameLen;
679 		while (pathBuf.length < end)
680 			growPathBuf(ptr);
681 		System.arraycopy(tv.buf, namePtr, pathBuf, ptr, nameLen);
682 		return end;
683 	}
684 
685 	private void growPathBuf(int ptr) {
686 		byte[] newBuf = new byte[pathBuf.length << 1];
687 		System.arraycopy(pathBuf, 0, newBuf, 0, ptr);
688 		pathBuf = newBuf;
689 	}
690 
691 	/** {@inheritDoc} */
692 	@Override
693 	public void dispose() {
694 		super.dispose();
695 		pendingObjects = new BlockObjQueue();
696 		currVisit = null;
697 		freeVisit = null;
698 	}
699 
700 	/** {@inheritDoc} */
701 	@Override
702 	protected void reset(int retainFlags) {
703 		super.reset(retainFlags);
704 
705 		for (RevObject obj : rootObjects)
706 			obj.flags &= ~IN_PENDING;
707 
708 		rootObjects = new ArrayList<>();
709 		pendingObjects = new BlockObjQueue();
710 		currVisit = null;
711 		freeVisit = null;
712 	}
713 
714 	private void addObject(RevObject o) {
715 		if ((o.flags & IN_PENDING) == 0) {
716 			o.flags |= IN_PENDING;
717 			rootObjects.add(o);
718 			pendingObjects.add(o);
719 		}
720 	}
721 
722 	private void markTreeUninteresting(RevTree tree)
723 			throws MissingObjectException, IncorrectObjectTypeException,
724 			IOException {
725 		if ((tree.flags & UNINTERESTING) != 0)
726 			return;
727 		tree.flags |= UNINTERESTING;
728 
729 		byte[] raw = reader.open(tree, OBJ_TREE).getCachedBytes();
730 		for (int ptr = 0; ptr < raw.length;) {
731 			byte c = raw[ptr];
732 			int mode = c - '0';
733 			for (;;) {
734 				c = raw[++ptr];
735 				if (' ' == c)
736 					break;
737 				mode <<= 3;
738 				mode += c - '0';
739 			}
740 			while (raw[++ptr] != 0) {
741 				// Skip entry name.
742 			}
743 			ptr++; // Skip NUL after entry name.
744 
745 			switch (mode >>> TYPE_SHIFT) {
746 			case TYPE_FILE:
747 			case TYPE_SYMLINK:
748 				idBuffer.fromRaw(raw, ptr);
749 				lookupBlob(idBuffer).flags |= UNINTERESTING;
750 				break;
751 
752 			case TYPE_TREE:
753 				idBuffer.fromRaw(raw, ptr);
754 				markTreeUninteresting(lookupTree(idBuffer));
755 				break;
756 
757 			case TYPE_GITLINK:
758 				break;
759 
760 			default:
761 				idBuffer.fromRaw(raw, ptr);
762 				throw new CorruptObjectException(MessageFormat.format(
763 						JGitText.get().corruptObjectInvalidMode3,
764 						String.format("%o", Integer.valueOf(mode)), //$NON-NLS-1$
765 						idBuffer.name(), "", tree)); //$NON-NLS-1$
766 			}
767 			ptr += ID_SZ;
768 		}
769 	}
770 
771 	private TreeVisit newTreeVisit(RevObject obj) throws LargeObjectException,
772 			MissingObjectException, IncorrectObjectTypeException, IOException {
773 		TreeVisit tv = freeVisit;
774 		if (tv != null) {
775 			freeVisit = tv.parent;
776 			tv.ptr = 0;
777 			tv.namePtr = 0;
778 			tv.nameEnd = 0;
779 			tv.pathLen = 0;
780 		} else {
781 			tv = new TreeVisit();
782 		}
783 		tv.obj = obj;
784 		tv.buf = reader.open(obj, OBJ_TREE).getCachedBytes();
785 		return tv;
786 	}
787 
788 	private void releaseTreeVisit(TreeVisit tv) {
789 		tv.buf = null;
790 		tv.parent = freeVisit;
791 		freeVisit = tv;
792 	}
793 
794 	private static class TreeVisit {
795 		/** Parent tree visit that entered this tree, null if root tree. */
796 		TreeVisit parent;
797 
798 		/** The RevTree currently being iterated through. */
799 		RevObject obj;
800 
801 		/** Canonical encoding of the tree named by {@link #obj}. */
802 		byte[] buf;
803 
804 		/** Index of next entry to parse in {@link #buf}. */
805 		int ptr;
806 
807 		/** Start of the current name entry in {@link #buf}. */
808 		int namePtr;
809 
810 		/** One past end of name, {@code nameEnd - namePtr} is the length. */
811 		int nameEnd;
812 
813 		/** Number of bytes in the path leading up to this tree. */
814 		int pathLen;
815 	}
816 }