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 {@link RevSort} used to order the
80   * commits that are returned first.
81   */
82  public class ObjectWalk extends RevWalk {
83  	private static final int ID_SZ = 20;
84  	private static final int TYPE_SHIFT = 12;
85  	private static final int TYPE_TREE = 0040000 >>> TYPE_SHIFT;
86  	private static final int TYPE_SYMLINK = 0120000 >>> TYPE_SHIFT;
87  	private static final int TYPE_FILE = 0100000 >>> TYPE_SHIFT;
88  	private static final int TYPE_GITLINK = 0160000 >>> TYPE_SHIFT;
89  
90  	/**
91  	 * Indicates a non-RevCommit is in {@link #pendingObjects}.
92  	 * <p>
93  	 * We can safely reuse {@link RevWalk#REWRITE} here for the same value as it
94  	 * is only set on RevCommit and {@link #pendingObjects} never has RevCommit
95  	 * instances inserted into it.
96  	 */
97  	private static final int IN_PENDING = RevWalk.REWRITE;
98  
99  	private List<RevObject> rootObjects;
100 
101 	private BlockObjQueue pendingObjects;
102 
103 	private ObjectFilter objectFilter;
104 
105 	private TreeVisit freeVisit;
106 
107 	private TreeVisit currVisit;
108 
109 	private byte[] pathBuf;
110 
111 	private int pathLen;
112 
113 	private boolean boundary;
114 
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(final Repository repo) {
122 		this(repo.newObjectReader());
123 	}
124 
125 	/**
126 	 * Create a new revision and object walker for a given repository.
127 	 *
128 	 * @param or
129 	 *            the reader the walker will obtain data from. The reader should
130 	 *            be released by the caller when the walker is no longer
131 	 *            required.
132 	 */
133 	public ObjectWalk(ObjectReader or) {
134 		super(or);
135 		setRetainBody(false);
136 		rootObjects = new ArrayList<>();
137 		pendingObjects = new BlockObjQueue();
138 		objectFilter = ObjectFilter.ALL;
139 		pathBuf = new byte[256];
140 	}
141 
142 	/**
143 	 * Mark an object or commit to start graph traversal from.
144 	 * <p>
145 	 * Callers are encouraged to use {@link RevWalk#parseAny(AnyObjectId)}
146 	 * instead of {@link RevWalk#lookupAny(AnyObjectId, int)}, as this method
147 	 * requires the object to be parsed before it can be added as a root for the
148 	 * traversal.
149 	 * <p>
150 	 * The method will automatically parse an unparsed object, but error
151 	 * handling may be more difficult for the application to explain why a
152 	 * RevObject is not actually valid. The object pool of this walker would
153 	 * also be 'poisoned' by the invalid RevObject.
154 	 * <p>
155 	 * This method will automatically call {@link RevWalk#markStart(RevCommit)}
156 	 * if passed RevCommit instance, or a RevTag that directly (or indirectly)
157 	 * references a RevCommit.
158 	 *
159 	 * @param o
160 	 *            the object to start traversing from. The object passed must be
161 	 *            from this same revision walker.
162 	 * @throws MissingObjectException
163 	 *             the object supplied is not available from the object
164 	 *             database. This usually indicates the supplied object is
165 	 *             invalid, but the reference was constructed during an earlier
166 	 *             invocation to {@link RevWalk#lookupAny(AnyObjectId, int)}.
167 	 * @throws IncorrectObjectTypeException
168 	 *             the object was not parsed yet and it was discovered during
169 	 *             parsing that it is not actually the type of the instance
170 	 *             passed in. This usually indicates the caller used the wrong
171 	 *             type in a {@link RevWalk#lookupAny(AnyObjectId, int)} call.
172 	 * @throws IOException
173 	 *             a pack file or loose object could not be read.
174 	 */
175 	public void markStart(RevObject o) throws MissingObjectException,
176 			IncorrectObjectTypeException, IOException {
177 		while (o instanceof RevTag) {
178 			addObject(o);
179 			o = ((RevTag) o).getObject();
180 			parseHeaders(o);
181 		}
182 
183 		if (o instanceof RevCommit)
184 			super.markStart((RevCommit) o);
185 		else
186 			addObject(o);
187 	}
188 
189 	/**
190 	 * Mark an object to not produce in the output.
191 	 * <p>
192 	 * Uninteresting objects denote not just themselves but also their entire
193 	 * reachable chain, back until the merge base of an uninteresting commit and
194 	 * an otherwise interesting commit.
195 	 * <p>
196 	 * Callers are encouraged to use {@link RevWalk#parseAny(AnyObjectId)}
197 	 * instead of {@link RevWalk#lookupAny(AnyObjectId, int)}, as this method
198 	 * requires the object to be parsed before it can be added as a root for the
199 	 * traversal.
200 	 * <p>
201 	 * The method will automatically parse an unparsed object, but error
202 	 * handling may be more difficult for the application to explain why a
203 	 * RevObject is not actually valid. The object pool of this walker would
204 	 * also be 'poisoned' by the invalid RevObject.
205 	 * <p>
206 	 * This method will automatically call {@link RevWalk#markStart(RevCommit)}
207 	 * if passed RevCommit instance, or a RevTag that directly (or indirectly)
208 	 * references a RevCommit.
209 	 *
210 	 * @param o
211 	 *            the object to start traversing from. The object passed must be
212 	 * @throws MissingObjectException
213 	 *             the object supplied is not available from the object
214 	 *             database. This usually indicates the supplied object is
215 	 *             invalid, but the reference was constructed during an earlier
216 	 *             invocation to {@link RevWalk#lookupAny(AnyObjectId, int)}.
217 	 * @throws 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 {@link RevWalk#lookupAny(AnyObjectId, int)} call.
222 	 * @throws IOException
223 	 *             a pack file or loose object could not be read.
224 	 */
225 	public void markUninteresting(RevObject o) throws MissingObjectException,
226 			IncorrectObjectTypeException, IOException {
227 		while (o instanceof RevTag) {
228 			o.flags |= UNINTERESTING;
229 			if (boundary)
230 				addObject(o);
231 			o = ((RevTag) o).getObject();
232 			parseHeaders(o);
233 		}
234 
235 		if (o instanceof RevCommit)
236 			super.markUninteresting((RevCommit) o);
237 		else if (o instanceof RevTree)
238 			markTreeUninteresting((RevTree) o);
239 		else
240 			o.flags |= UNINTERESTING;
241 
242 		if (o.getType() != OBJ_COMMIT && boundary)
243 			addObject(o);
244 	}
245 
246 	@Override
247 	public void sort(RevSort s) {
248 		super.sort(s);
249 		boundary = hasRevSort(RevSort.BOUNDARY);
250 	}
251 
252 	@Override
253 	public void sort(RevSort s, boolean use) {
254 		super.sort(s, use);
255 		boundary = hasRevSort(RevSort.BOUNDARY);
256 	}
257 
258 	/**
259 	 * Get the currently configured object filter.
260 	 *
261 	 * @return the current filter. Never null as a filter is always needed.
262 	 *
263 	 * @since 4.0
264 	 */
265 	public ObjectFilter getObjectFilter() {
266 		return objectFilter;
267 	}
268 
269 	/**
270 	 * Set the object filter for this walker.  This filter affects the objects
271 	 * visited by {@link #nextObject()}.  It does not affect the commits
272 	 * listed by {@link #next()}.
273 	 * <p>
274 	 * If the filter returns false for an object, then that object is skipped
275 	 * and objects reachable from it are not enqueued to be walked recursively.
276 	 * This can be used to speed up the object walk by skipping subtrees that
277 	 * are known to be uninteresting.
278 	 *
279 	 * @param newFilter
280 	 *            the new filter. If null the special {@link ObjectFilter#ALL}
281 	 *            filter will be used instead, as it matches every object.
282 	 *
283 	 * @since 4.0
284 	 */
285 	public void setObjectFilter(ObjectFilter newFilter) {
286 		assertNotStarted();
287 		objectFilter = newFilter != null ? newFilter : ObjectFilter.ALL;
288 	}
289 
290 	@Override
291 	public RevCommit next() throws MissingObjectException,
292 			IncorrectObjectTypeException, IOException {
293 		for (;;) {
294 			final RevCommit r = super.next();
295 			if (r == null) {
296 				return null;
297 			}
298 			final RevTree t = r.getTree();
299 			if ((r.flags & UNINTERESTING) != 0) {
300 				if (objectFilter.include(this, t)) {
301 					markTreeUninteresting(t);
302 				}
303 				if (boundary) {
304 					return r;
305 				}
306 				continue;
307 			}
308 			if (objectFilter.include(this, t)) {
309 				pendingObjects.add(t);
310 			}
311 			return r;
312 		}
313 	}
314 
315 	/**
316 	 * Pop the next most recent object.
317 	 *
318 	 * @return next most recent object; null if traversal is over.
319 	 * @throws MissingObjectException
320 	 *             one or or more of the next objects are not available from the
321 	 *             object database, but were thought to be candidates for
322 	 *             traversal. This usually indicates a broken link.
323 	 * @throws IncorrectObjectTypeException
324 	 *             one or or more of the objects in a tree do not match the type
325 	 *             indicated.
326 	 * @throws IOException
327 	 *             a pack file or loose object could not be read.
328 	 */
329 	public RevObject nextObject() throws MissingObjectException,
330 			IncorrectObjectTypeException, IOException {
331 		pathLen = 0;
332 
333 		TreeVisit tv = currVisit;
334 		while (tv != null) {
335 			byte[] buf = tv.buf;
336 			for (int ptr = tv.ptr; ptr < buf.length;) {
337 				int startPtr = ptr;
338 				ptr = findObjectId(buf, ptr);
339 				idBuffer.fromRaw(buf, ptr);
340 				ptr += ID_SZ;
341 
342 				if (!objectFilter.include(this, idBuffer)) {
343 					continue;
344 				}
345 
346 				RevObject obj = objects.get(idBuffer);
347 				if (obj != null && (obj.flags & SEEN) != 0)
348 					continue;
349 
350 				int mode = parseMode(buf, startPtr, ptr, tv);
351 				int flags;
352 				switch (mode >>> TYPE_SHIFT) {
353 				case TYPE_FILE:
354 				case TYPE_SYMLINK:
355 					if (obj == null) {
356 						obj = new RevBlob(idBuffer);
357 						obj.flags = SEEN;
358 						objects.add(obj);
359 						return obj;
360 					}
361 					if (!(obj instanceof RevBlob))
362 						throw new IncorrectObjectTypeException(obj, OBJ_BLOB);
363 					obj.flags = flags = obj.flags | SEEN;
364 					if ((flags & UNINTERESTING) == 0)
365 						return obj;
366 					if (boundary)
367 						return obj;
368 					continue;
369 
370 				case TYPE_TREE:
371 					if (obj == null) {
372 						obj = new RevTree(idBuffer);
373 						obj.flags = SEEN;
374 						objects.add(obj);
375 						return enterTree(obj);
376 					}
377 					if (!(obj instanceof RevTree))
378 						throw new IncorrectObjectTypeException(obj, OBJ_TREE);
379 					obj.flags = flags = obj.flags | SEEN;
380 					if ((flags & UNINTERESTING) == 0)
381 						return enterTree(obj);
382 					if (boundary)
383 						return enterTree(obj);
384 					continue;
385 
386 				case TYPE_GITLINK:
387 					continue;
388 
389 				default:
390 					throw new CorruptObjectException(MessageFormat.format(
391 							JGitText.get().corruptObjectInvalidMode3,
392 							String.format("%o", Integer.valueOf(mode)), //$NON-NLS-1$
393 							idBuffer.name(),
394 							RawParseUtils.decode(buf, tv.namePtr, tv.nameEnd),
395 							tv.obj));
396 				}
397 			}
398 
399 			currVisit = tv.parent;
400 			releaseTreeVisit(tv);
401 			tv = currVisit;
402 		}
403 
404 		for (;;) {
405 			RevObject o = pendingObjects.next();
406 			if (o == null) {
407 				return null;
408 			}
409 			int flags = o.flags;
410 			if ((flags & SEEN) != 0)
411 				continue;
412 			flags |= SEEN;
413 			o.flags = flags;
414 			if ((flags & UNINTERESTING) == 0 | boundary) {
415 				if (o instanceof RevTree) {
416 					tv = newTreeVisit(o);
417 					tv.parent = null;
418 					currVisit = tv;
419 				}
420 				return o;
421 			}
422 		}
423 	}
424 
425 	private RevObject enterTree(RevObject obj) throws MissingObjectException,
426 			IncorrectObjectTypeException, IOException {
427 		TreeVisit tv = newTreeVisit(obj);
428 		tv.parent = currVisit;
429 		currVisit = tv;
430 		return obj;
431 	}
432 
433 	private static int findObjectId(byte[] buf, int ptr) {
434 		// Skip over the mode and name until the NUL before the ObjectId
435 		// can be located. Skip the NUL as the function returns.
436 		for (;;) {
437 			if (buf[++ptr] == 0) return ++ptr;
438 			if (buf[++ptr] == 0) return ++ptr;
439 			if (buf[++ptr] == 0) return ++ptr;
440 			if (buf[++ptr] == 0) return ++ptr;
441 
442 			if (buf[++ptr] == 0) return ++ptr;
443 			if (buf[++ptr] == 0) return ++ptr;
444 			if (buf[++ptr] == 0) return ++ptr;
445 			if (buf[++ptr] == 0) return ++ptr;
446 
447 			if (buf[++ptr] == 0) return ++ptr;
448 			if (buf[++ptr] == 0) return ++ptr;
449 			if (buf[++ptr] == 0) return ++ptr;
450 			if (buf[++ptr] == 0) return ++ptr;
451 
452 			if (buf[++ptr] == 0) return ++ptr;
453 			if (buf[++ptr] == 0) return ++ptr;
454 			if (buf[++ptr] == 0) return ++ptr;
455 			if (buf[++ptr] == 0) return ++ptr;
456 		}
457 	}
458 
459 	private static int parseMode(byte[] buf, int startPtr, int recEndPtr, TreeVisit tv) {
460 		int mode = buf[startPtr] - '0';
461 		for (;;) {
462 			byte c = buf[++startPtr];
463 			if (' ' == c)
464 				break;
465 			mode <<= 3;
466 			mode += c - '0';
467 
468 			c = buf[++startPtr];
469 			if (' ' == c)
470 				break;
471 			mode <<= 3;
472 			mode += c - '0';
473 
474 			c = buf[++startPtr];
475 			if (' ' == c)
476 				break;
477 			mode <<= 3;
478 			mode += c - '0';
479 
480 			c = buf[++startPtr];
481 			if (' ' == c)
482 				break;
483 			mode <<= 3;
484 			mode += c - '0';
485 
486 			c = buf[++startPtr];
487 			if (' ' == c)
488 				break;
489 			mode <<= 3;
490 			mode += c - '0';
491 
492 			c = buf[++startPtr];
493 			if (' ' == c)
494 				break;
495 			mode <<= 3;
496 			mode += c - '0';
497 
498 			c = buf[++startPtr];
499 			if (' ' == c)
500 				break;
501 			mode <<= 3;
502 			mode += c - '0';
503 		}
504 
505 		tv.ptr = recEndPtr;
506 		tv.namePtr = startPtr + 1;
507 		tv.nameEnd = recEndPtr - (ID_SZ + 1);
508 		return mode;
509 	}
510 
511 	/**
512 	 * Verify all interesting objects are available, and reachable.
513 	 * <p>
514 	 * Callers should populate starting points and ending points with
515 	 * {@link #markStart(RevObject)} and {@link #markUninteresting(RevObject)}
516 	 * and then use this method to verify all objects between those two points
517 	 * exist in the repository and are readable.
518 	 * <p>
519 	 * This method returns successfully if everything is connected; it throws an
520 	 * exception if there is a connectivity problem. The exception message
521 	 * provides some detail about the connectivity failure.
522 	 *
523 	 * @throws MissingObjectException
524 	 *             one or or more of the next objects are not available from the
525 	 *             object database, but were thought to be candidates for
526 	 *             traversal. This usually indicates a broken link.
527 	 * @throws IncorrectObjectTypeException
528 	 *             one or or more of the objects in a tree do not match the type
529 	 *             indicated.
530 	 * @throws IOException
531 	 *             a pack file or loose object could not be read.
532 	 */
533 	public void checkConnectivity() throws MissingObjectException,
534 			IncorrectObjectTypeException, IOException {
535 		for (;;) {
536 			final RevCommit c = next();
537 			if (c == null)
538 				break;
539 		}
540 		for (;;) {
541 			final RevObject o = nextObject();
542 			if (o == null)
543 				break;
544 			if (o instanceof RevBlob && !reader.has(o))
545 				throw new MissingObjectException(o, OBJ_BLOB);
546 		}
547 	}
548 
549 	/**
550 	 * Get the current object's complete path.
551 	 * <p>
552 	 * This method is not very efficient and is primarily meant for debugging
553 	 * and final output generation. Applications should try to avoid calling it,
554 	 * and if invoked do so only once per interesting entry, where the name is
555 	 * absolutely required for correct function.
556 	 *
557 	 * @return complete path of the current entry, from the root of the
558 	 *         repository. If the current entry is in a subtree there will be at
559 	 *         least one '/' in the returned string. Null if the current entry
560 	 *         has no path, such as for annotated tags or root level trees.
561 	 */
562 	public String getPathString() {
563 		if (pathLen == 0) {
564 			pathLen = updatePathBuf(currVisit);
565 			if (pathLen == 0)
566 				return null;
567 		}
568 		return RawParseUtils.decode(pathBuf, 0, pathLen);
569 	}
570 
571 	/**
572 	 * Get the current object's path hash code.
573 	 * <p>
574 	 * This method computes a hash code on the fly for this path, the hash is
575 	 * suitable to cluster objects that may have similar paths together.
576 	 *
577 	 * @return path hash code; any integer may be returned.
578 	 */
579 	public int getPathHashCode() {
580 		TreeVisit tv = currVisit;
581 		if (tv == null)
582 			return 0;
583 
584 		int nameEnd = tv.nameEnd;
585 		if (nameEnd == 0) {
586 			// When nameEnd == 0 the subtree is itself the current path
587 			// being visited. The name hash must be obtained from its
588 			// parent tree. If there is no parent, this is a root tree with
589 			// a hash code of 0.
590 			tv = tv.parent;
591 			if (tv == null)
592 				return 0;
593 			nameEnd = tv.nameEnd;
594 		}
595 
596 		byte[] buf;
597 		int ptr;
598 
599 		if (16 <= (nameEnd - tv.namePtr)) {
600 			buf = tv.buf;
601 			ptr = nameEnd - 16;
602 		} else {
603 			nameEnd = pathLen;
604 			if (nameEnd == 0) {
605 				nameEnd = updatePathBuf(currVisit);
606 				pathLen = nameEnd;
607 			}
608 			buf = pathBuf;
609 			ptr = Math.max(0, nameEnd - 16);
610 		}
611 
612 		int hash = 0;
613 		for (; ptr < nameEnd; ptr++) {
614 			byte c = buf[ptr];
615 			if (c != ' ')
616 				hash = (hash >>> 2) + (c << 24);
617 		}
618 		return hash;
619 	}
620 
621 	/** @return the internal buffer holding the current path. */
622 	public byte[] getPathBuffer() {
623 		if (pathLen == 0)
624 			pathLen = updatePathBuf(currVisit);
625 		return pathBuf;
626 	}
627 
628 	/** @return length of the path in {@link #getPathBuffer()}. */
629 	public int getPathLength() {
630 		if (pathLen == 0)
631 			pathLen = updatePathBuf(currVisit);
632 		return pathLen;
633 	}
634 
635 	private int updatePathBuf(TreeVisit tv) {
636 		if (tv == null)
637 			return 0;
638 
639 		// If nameEnd == 0 this tree has not yet contributed an entry.
640 		// Update only for the parent, which if null will be empty.
641 		int nameEnd = tv.nameEnd;
642 		if (nameEnd == 0)
643 			return updatePathBuf(tv.parent);
644 
645 		int ptr = tv.pathLen;
646 		if (ptr == 0) {
647 			ptr = updatePathBuf(tv.parent);
648 			if (ptr == pathBuf.length)
649 				growPathBuf(ptr);
650 			if (ptr != 0)
651 				pathBuf[ptr++] = '/';
652 			tv.pathLen = ptr;
653 		}
654 
655 		int namePtr = tv.namePtr;
656 		int nameLen = nameEnd - namePtr;
657 		int end = ptr + nameLen;
658 		while (pathBuf.length < end)
659 			growPathBuf(ptr);
660 		System.arraycopy(tv.buf, namePtr, pathBuf, ptr, nameLen);
661 		return end;
662 	}
663 
664 	private void growPathBuf(int ptr) {
665 		byte[] newBuf = new byte[pathBuf.length << 1];
666 		System.arraycopy(pathBuf, 0, newBuf, 0, ptr);
667 		pathBuf = newBuf;
668 	}
669 
670 	@Override
671 	public void dispose() {
672 		super.dispose();
673 		pendingObjects = new BlockObjQueue();
674 		currVisit = null;
675 		freeVisit = null;
676 	}
677 
678 	@Override
679 	protected void reset(final int retainFlags) {
680 		super.reset(retainFlags);
681 
682 		for (RevObject obj : rootObjects)
683 			obj.flags &= ~IN_PENDING;
684 
685 		rootObjects = new ArrayList<>();
686 		pendingObjects = new BlockObjQueue();
687 		currVisit = null;
688 		freeVisit = null;
689 	}
690 
691 	private void addObject(final RevObject o) {
692 		if ((o.flags & IN_PENDING) == 0) {
693 			o.flags |= IN_PENDING;
694 			rootObjects.add(o);
695 			pendingObjects.add(o);
696 		}
697 	}
698 
699 	private void markTreeUninteresting(final RevTree tree)
700 			throws MissingObjectException, IncorrectObjectTypeException,
701 			IOException {
702 		if ((tree.flags & UNINTERESTING) != 0)
703 			return;
704 		tree.flags |= UNINTERESTING;
705 
706 		byte[] raw = reader.open(tree, OBJ_TREE).getCachedBytes();
707 		for (int ptr = 0; ptr < raw.length;) {
708 			byte c = raw[ptr];
709 			int mode = c - '0';
710 			for (;;) {
711 				c = raw[++ptr];
712 				if (' ' == c)
713 					break;
714 				mode <<= 3;
715 				mode += c - '0';
716 			}
717 			while (raw[++ptr] != 0) {
718 				// Skip entry name.
719 			}
720 			ptr++; // Skip NUL after entry name.
721 
722 			switch (mode >>> TYPE_SHIFT) {
723 			case TYPE_FILE:
724 			case TYPE_SYMLINK:
725 				idBuffer.fromRaw(raw, ptr);
726 				lookupBlob(idBuffer).flags |= UNINTERESTING;
727 				break;
728 
729 			case TYPE_TREE:
730 				idBuffer.fromRaw(raw, ptr);
731 				markTreeUninteresting(lookupTree(idBuffer));
732 				break;
733 
734 			case TYPE_GITLINK:
735 				break;
736 
737 			default:
738 				idBuffer.fromRaw(raw, ptr);
739 				throw new CorruptObjectException(MessageFormat.format(
740 						JGitText.get().corruptObjectInvalidMode3,
741 						String.format("%o", Integer.valueOf(mode)), //$NON-NLS-1$
742 						idBuffer.name(), "", tree)); //$NON-NLS-1$
743 			}
744 			ptr += ID_SZ;
745 		}
746 	}
747 
748 	private TreeVisit newTreeVisit(RevObject obj) throws LargeObjectException,
749 			MissingObjectException, IncorrectObjectTypeException, IOException {
750 		TreeVisit tv = freeVisit;
751 		if (tv != null) {
752 			freeVisit = tv.parent;
753 			tv.ptr = 0;
754 			tv.namePtr = 0;
755 			tv.nameEnd = 0;
756 			tv.pathLen = 0;
757 		} else {
758 			tv = new TreeVisit();
759 		}
760 		tv.obj = obj;
761 		tv.buf = reader.open(obj, OBJ_TREE).getCachedBytes();
762 		return tv;
763 	}
764 
765 	private void releaseTreeVisit(TreeVisit tv) {
766 		tv.buf = null;
767 		tv.parent = freeVisit;
768 		freeVisit = tv;
769 	}
770 
771 	private static class TreeVisit {
772 		/** Parent tree visit that entered this tree, null if root tree. */
773 		TreeVisit parent;
774 
775 		/** The RevTree currently being iterated through. */
776 		RevObject obj;
777 
778 		/** Canonical encoding of the tree named by {@link #obj}. */
779 		byte[] buf;
780 
781 		/** Index of next entry to parse in {@link #buf}. */
782 		int ptr;
783 
784 		/** Start of the current name entry in {@link #buf}. */
785 		int namePtr;
786 
787 		/** One past end of name, {@code nameEnd - namePtr} is the length. */
788 		int nameEnd;
789 
790 		/** Number of bytes in the path leading up to this tree. */
791 		int pathLen;
792 	}
793 }