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<RevObject>();
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 	public void sort(RevSort s) {
247 		super.sort(s);
248 		boundary = hasRevSort(RevSort.BOUNDARY);
249 	}
250 
251 	@Override
252 	public void sort(RevSort s, boolean use) {
253 		super.sort(s, use);
254 		boundary = hasRevSort(RevSort.BOUNDARY);
255 	}
256 
257 	/**
258 	 * Get the currently configured object filter.
259 	 *
260 	 * @return the current filter. Never null as a filter is always needed.
261 	 *
262 	 * @since 4.0
263 	 */
264 	public ObjectFilter getObjectFilter() {
265 		return objectFilter;
266 	}
267 
268 	/**
269 	 * Set the object filter for this walker.  This filter affects the objects
270 	 * visited by {@link #nextObject()}.  It does not affect the commits
271 	 * listed by {@link #next()}.
272 	 * <p>
273 	 * If the filter returns false for an object, then that object is skipped
274 	 * and objects reachable from it are not enqueued to be walked recursively.
275 	 * This can be used to speed up the object walk by skipping subtrees that
276 	 * are known to be uninteresting.
277 	 *
278 	 * @param newFilter
279 	 *            the new filter. If null the special {@link ObjectFilter#ALL}
280 	 *            filter will be used instead, as it matches every object.
281 	 *
282 	 * @since 4.0
283 	 */
284 	public void setObjectFilter(ObjectFilter newFilter) {
285 		assertNotStarted();
286 		objectFilter = newFilter != null ? newFilter : ObjectFilter.ALL;
287 	}
288 
289 	@Override
290 	public RevCommit next() throws MissingObjectException,
291 			IncorrectObjectTypeException, IOException {
292 		for (;;) {
293 			final RevCommit r = super.next();
294 			if (r == null) {
295 				return null;
296 			}
297 			final RevTree t = r.getTree();
298 			if ((r.flags & UNINTERESTING) != 0) {
299 				if (objectFilter.include(this, t)) {
300 					markTreeUninteresting(t);
301 				}
302 				if (boundary) {
303 					return r;
304 				}
305 				continue;
306 			}
307 			if (objectFilter.include(this, t)) {
308 				pendingObjects.add(t);
309 			}
310 			return r;
311 		}
312 	}
313 
314 	/**
315 	 * Pop the next most recent object.
316 	 *
317 	 * @return next most recent object; null if traversal is over.
318 	 * @throws MissingObjectException
319 	 *             one or or more of the next objects are not available from the
320 	 *             object database, but were thought to be candidates for
321 	 *             traversal. This usually indicates a broken link.
322 	 * @throws IncorrectObjectTypeException
323 	 *             one or or more of the objects in a tree do not match the type
324 	 *             indicated.
325 	 * @throws IOException
326 	 *             a pack file or loose object could not be read.
327 	 */
328 	public RevObject nextObject() throws MissingObjectException,
329 			IncorrectObjectTypeException, IOException {
330 		pathLen = 0;
331 
332 		TreeVisit tv = currVisit;
333 		while (tv != null) {
334 			byte[] buf = tv.buf;
335 			for (int ptr = tv.ptr; ptr < buf.length;) {
336 				int startPtr = ptr;
337 				ptr = findObjectId(buf, ptr);
338 				idBuffer.fromRaw(buf, ptr);
339 				ptr += ID_SZ;
340 
341 				if (!objectFilter.include(this, idBuffer)) {
342 					continue;
343 				}
344 
345 				RevObject obj = objects.get(idBuffer);
346 				if (obj != null && (obj.flags & SEEN) != 0)
347 					continue;
348 
349 				int mode = parseMode(buf, startPtr, ptr, tv);
350 				int flags;
351 				switch (mode >>> TYPE_SHIFT) {
352 				case TYPE_FILE:
353 				case TYPE_SYMLINK:
354 					if (obj == null) {
355 						obj = new RevBlob(idBuffer);
356 						obj.flags = SEEN;
357 						objects.add(obj);
358 						return obj;
359 					}
360 					if (!(obj instanceof RevBlob))
361 						throw new IncorrectObjectTypeException(obj, OBJ_BLOB);
362 					obj.flags = flags = obj.flags | SEEN;
363 					if ((flags & UNINTERESTING) == 0)
364 						return obj;
365 					if (boundary)
366 						return obj;
367 					continue;
368 
369 				case TYPE_TREE:
370 					if (obj == null) {
371 						obj = new RevTree(idBuffer);
372 						obj.flags = SEEN;
373 						objects.add(obj);
374 						return enterTree(obj);
375 					}
376 					if (!(obj instanceof RevTree))
377 						throw new IncorrectObjectTypeException(obj, OBJ_TREE);
378 					obj.flags = flags = obj.flags | SEEN;
379 					if ((flags & UNINTERESTING) == 0)
380 						return enterTree(obj);
381 					if (boundary)
382 						return enterTree(obj);
383 					continue;
384 
385 				case TYPE_GITLINK:
386 					continue;
387 
388 				default:
389 					throw new CorruptObjectException(MessageFormat.format(
390 							JGitText.get().corruptObjectInvalidMode3,
391 							String.format("%o", Integer.valueOf(mode)), //$NON-NLS-1$
392 							idBuffer.name(),
393 							RawParseUtils.decode(buf, tv.namePtr, tv.nameEnd),
394 							tv.obj));
395 				}
396 			}
397 
398 			currVisit = tv.parent;
399 			releaseTreeVisit(tv);
400 			tv = currVisit;
401 		}
402 
403 		for (;;) {
404 			RevObject o = pendingObjects.next();
405 			if (o == null) {
406 				return null;
407 			}
408 			int flags = o.flags;
409 			if ((flags & SEEN) != 0)
410 				continue;
411 			flags |= SEEN;
412 			o.flags = flags;
413 			if ((flags & UNINTERESTING) == 0 | boundary) {
414 				if (o instanceof RevTree) {
415 					tv = newTreeVisit(o);
416 					tv.parent = null;
417 					currVisit = tv;
418 				}
419 				return o;
420 			}
421 		}
422 	}
423 
424 	private RevObject enterTree(RevObject obj) throws MissingObjectException,
425 			IncorrectObjectTypeException, IOException {
426 		TreeVisit tv = newTreeVisit(obj);
427 		tv.parent = currVisit;
428 		currVisit = tv;
429 		return obj;
430 	}
431 
432 	private static int findObjectId(byte[] buf, int ptr) {
433 		// Skip over the mode and name until the NUL before the ObjectId
434 		// can be located. Skip the NUL as the function returns.
435 		for (;;) {
436 			if (buf[++ptr] == 0) return ++ptr;
437 			if (buf[++ptr] == 0) return ++ptr;
438 			if (buf[++ptr] == 0) return ++ptr;
439 			if (buf[++ptr] == 0) return ++ptr;
440 
441 			if (buf[++ptr] == 0) return ++ptr;
442 			if (buf[++ptr] == 0) return ++ptr;
443 			if (buf[++ptr] == 0) return ++ptr;
444 			if (buf[++ptr] == 0) return ++ptr;
445 
446 			if (buf[++ptr] == 0) return ++ptr;
447 			if (buf[++ptr] == 0) return ++ptr;
448 			if (buf[++ptr] == 0) return ++ptr;
449 			if (buf[++ptr] == 0) return ++ptr;
450 
451 			if (buf[++ptr] == 0) return ++ptr;
452 			if (buf[++ptr] == 0) return ++ptr;
453 			if (buf[++ptr] == 0) return ++ptr;
454 			if (buf[++ptr] == 0) return ++ptr;
455 		}
456 	}
457 
458 	private static int parseMode(byte[] buf, int startPtr, int recEndPtr, TreeVisit tv) {
459 		int mode = buf[startPtr] - '0';
460 		for (;;) {
461 			byte c = buf[++startPtr];
462 			if (' ' == c)
463 				break;
464 			mode <<= 3;
465 			mode += c - '0';
466 
467 			c = buf[++startPtr];
468 			if (' ' == c)
469 				break;
470 			mode <<= 3;
471 			mode += c - '0';
472 
473 			c = buf[++startPtr];
474 			if (' ' == c)
475 				break;
476 			mode <<= 3;
477 			mode += c - '0';
478 
479 			c = buf[++startPtr];
480 			if (' ' == c)
481 				break;
482 			mode <<= 3;
483 			mode += c - '0';
484 
485 			c = buf[++startPtr];
486 			if (' ' == c)
487 				break;
488 			mode <<= 3;
489 			mode += c - '0';
490 
491 			c = buf[++startPtr];
492 			if (' ' == c)
493 				break;
494 			mode <<= 3;
495 			mode += c - '0';
496 
497 			c = buf[++startPtr];
498 			if (' ' == c)
499 				break;
500 			mode <<= 3;
501 			mode += c - '0';
502 		}
503 
504 		tv.ptr = recEndPtr;
505 		tv.namePtr = startPtr + 1;
506 		tv.nameEnd = recEndPtr - (ID_SZ + 1);
507 		return mode;
508 	}
509 
510 	/**
511 	 * Verify all interesting objects are available, and reachable.
512 	 * <p>
513 	 * Callers should populate starting points and ending points with
514 	 * {@link #markStart(RevObject)} and {@link #markUninteresting(RevObject)}
515 	 * and then use this method to verify all objects between those two points
516 	 * exist in the repository and are readable.
517 	 * <p>
518 	 * This method returns successfully if everything is connected; it throws an
519 	 * exception if there is a connectivity problem. The exception message
520 	 * provides some detail about the connectivity failure.
521 	 *
522 	 * @throws MissingObjectException
523 	 *             one or or more of the next objects are not available from the
524 	 *             object database, but were thought to be candidates for
525 	 *             traversal. This usually indicates a broken link.
526 	 * @throws IncorrectObjectTypeException
527 	 *             one or or more of the objects in a tree do not match the type
528 	 *             indicated.
529 	 * @throws IOException
530 	 *             a pack file or loose object could not be read.
531 	 */
532 	public void checkConnectivity() throws MissingObjectException,
533 			IncorrectObjectTypeException, IOException {
534 		for (;;) {
535 			final RevCommit c = next();
536 			if (c == null)
537 				break;
538 		}
539 		for (;;) {
540 			final RevObject o = nextObject();
541 			if (o == null)
542 				break;
543 			if (o instanceof RevBlob && !reader.has(o))
544 				throw new MissingObjectException(o, OBJ_BLOB);
545 		}
546 	}
547 
548 	/**
549 	 * Get the current object's complete path.
550 	 * <p>
551 	 * This method is not very efficient and is primarily meant for debugging
552 	 * and final output generation. Applications should try to avoid calling it,
553 	 * and if invoked do so only once per interesting entry, where the name is
554 	 * absolutely required for correct function.
555 	 *
556 	 * @return complete path of the current entry, from the root of the
557 	 *         repository. If the current entry is in a subtree there will be at
558 	 *         least one '/' in the returned string. Null if the current entry
559 	 *         has no path, such as for annotated tags or root level trees.
560 	 */
561 	public String getPathString() {
562 		if (pathLen == 0) {
563 			pathLen = updatePathBuf(currVisit);
564 			if (pathLen == 0)
565 				return null;
566 		}
567 		return RawParseUtils.decode(pathBuf, 0, pathLen);
568 	}
569 
570 	/**
571 	 * Get the current object's path hash code.
572 	 * <p>
573 	 * This method computes a hash code on the fly for this path, the hash is
574 	 * suitable to cluster objects that may have similar paths together.
575 	 *
576 	 * @return path hash code; any integer may be returned.
577 	 */
578 	public int getPathHashCode() {
579 		TreeVisit tv = currVisit;
580 		if (tv == null)
581 			return 0;
582 
583 		int nameEnd = tv.nameEnd;
584 		if (nameEnd == 0) {
585 			// When nameEnd == 0 the subtree is itself the current path
586 			// being visited. The name hash must be obtained from its
587 			// parent tree. If there is no parent, this is a root tree with
588 			// a hash code of 0.
589 			tv = tv.parent;
590 			if (tv == null)
591 				return 0;
592 			nameEnd = tv.nameEnd;
593 		}
594 
595 		byte[] buf;
596 		int ptr;
597 
598 		if (16 <= (nameEnd - tv.namePtr)) {
599 			buf = tv.buf;
600 			ptr = nameEnd - 16;
601 		} else {
602 			nameEnd = pathLen;
603 			if (nameEnd == 0) {
604 				nameEnd = updatePathBuf(currVisit);
605 				pathLen = nameEnd;
606 			}
607 			buf = pathBuf;
608 			ptr = Math.max(0, nameEnd - 16);
609 		}
610 
611 		int hash = 0;
612 		for (; ptr < nameEnd; ptr++) {
613 			byte c = buf[ptr];
614 			if (c != ' ')
615 				hash = (hash >>> 2) + (c << 24);
616 		}
617 		return hash;
618 	}
619 
620 	/** @return the internal buffer holding the current path. */
621 	public byte[] getPathBuffer() {
622 		if (pathLen == 0)
623 			pathLen = updatePathBuf(currVisit);
624 		return pathBuf;
625 	}
626 
627 	/** @return length of the path in {@link #getPathBuffer()}. */
628 	public int getPathLength() {
629 		if (pathLen == 0)
630 			pathLen = updatePathBuf(currVisit);
631 		return pathLen;
632 	}
633 
634 	private int updatePathBuf(TreeVisit tv) {
635 		if (tv == null)
636 			return 0;
637 
638 		// If nameEnd == 0 this tree has not yet contributed an entry.
639 		// Update only for the parent, which if null will be empty.
640 		int nameEnd = tv.nameEnd;
641 		if (nameEnd == 0)
642 			return updatePathBuf(tv.parent);
643 
644 		int ptr = tv.pathLen;
645 		if (ptr == 0) {
646 			ptr = updatePathBuf(tv.parent);
647 			if (ptr == pathBuf.length)
648 				growPathBuf(ptr);
649 			if (ptr != 0)
650 				pathBuf[ptr++] = '/';
651 			tv.pathLen = ptr;
652 		}
653 
654 		int namePtr = tv.namePtr;
655 		int nameLen = nameEnd - namePtr;
656 		int end = ptr + nameLen;
657 		while (pathBuf.length < end)
658 			growPathBuf(ptr);
659 		System.arraycopy(tv.buf, namePtr, pathBuf, ptr, nameLen);
660 		return end;
661 	}
662 
663 	private void growPathBuf(int ptr) {
664 		byte[] newBuf = new byte[pathBuf.length << 1];
665 		System.arraycopy(pathBuf, 0, newBuf, 0, ptr);
666 		pathBuf = newBuf;
667 	}
668 
669 	@Override
670 	public void dispose() {
671 		super.dispose();
672 		pendingObjects = new BlockObjQueue();
673 		currVisit = null;
674 		freeVisit = null;
675 	}
676 
677 	@Override
678 	protected void reset(final int retainFlags) {
679 		super.reset(retainFlags);
680 
681 		for (RevObject obj : rootObjects)
682 			obj.flags &= ~IN_PENDING;
683 
684 		rootObjects = new ArrayList<RevObject>();
685 		pendingObjects = new BlockObjQueue();
686 		currVisit = null;
687 		freeVisit = null;
688 	}
689 
690 	private void addObject(final RevObject o) {
691 		if ((o.flags & IN_PENDING) == 0) {
692 			o.flags |= IN_PENDING;
693 			rootObjects.add(o);
694 			pendingObjects.add(o);
695 		}
696 	}
697 
698 	private void markTreeUninteresting(final RevTree tree)
699 			throws MissingObjectException, IncorrectObjectTypeException,
700 			IOException {
701 		if ((tree.flags & UNINTERESTING) != 0)
702 			return;
703 		tree.flags |= UNINTERESTING;
704 
705 		byte[] raw = reader.open(tree, OBJ_TREE).getCachedBytes();
706 		for (int ptr = 0; ptr < raw.length;) {
707 			byte c = raw[ptr];
708 			int mode = c - '0';
709 			for (;;) {
710 				c = raw[++ptr];
711 				if (' ' == c)
712 					break;
713 				mode <<= 3;
714 				mode += c - '0';
715 			}
716 			while (raw[++ptr] != 0) {
717 				// Skip entry name.
718 			}
719 			ptr++; // Skip NUL after entry name.
720 
721 			switch (mode >>> TYPE_SHIFT) {
722 			case TYPE_FILE:
723 			case TYPE_SYMLINK:
724 				idBuffer.fromRaw(raw, ptr);
725 				lookupBlob(idBuffer).flags |= UNINTERESTING;
726 				break;
727 
728 			case TYPE_TREE:
729 				idBuffer.fromRaw(raw, ptr);
730 				markTreeUninteresting(lookupTree(idBuffer));
731 				break;
732 
733 			case TYPE_GITLINK:
734 				break;
735 
736 			default:
737 				idBuffer.fromRaw(raw, ptr);
738 				throw new CorruptObjectException(MessageFormat.format(
739 						JGitText.get().corruptObjectInvalidMode3,
740 						String.format("%o", Integer.valueOf(mode)), //$NON-NLS-1$
741 						idBuffer.name(), "", tree)); //$NON-NLS-1$
742 			}
743 			ptr += ID_SZ;
744 		}
745 	}
746 
747 	private TreeVisit newTreeVisit(RevObject obj) throws LargeObjectException,
748 			MissingObjectException, IncorrectObjectTypeException, IOException {
749 		TreeVisit tv = freeVisit;
750 		if (tv != null) {
751 			freeVisit = tv.parent;
752 			tv.ptr = 0;
753 			tv.namePtr = 0;
754 			tv.nameEnd = 0;
755 			tv.pathLen = 0;
756 		} else {
757 			tv = new TreeVisit();
758 		}
759 		tv.obj = obj;
760 		tv.buf = reader.open(obj, OBJ_TREE).getCachedBytes();
761 		return tv;
762 	}
763 
764 	private void releaseTreeVisit(TreeVisit tv) {
765 		tv.buf = null;
766 		tv.parent = freeVisit;
767 		freeVisit = tv;
768 	}
769 
770 	private static class TreeVisit {
771 		/** Parent tree visit that entered this tree, null if root tree. */
772 		TreeVisit parent;
773 
774 		/** The RevTree currently being iterated through. */
775 		RevObject obj;
776 
777 		/** Canonical encoding of the tree named by {@link #obj}. */
778 		byte[] buf;
779 
780 		/** Index of next entry to parse in {@link #buf}. */
781 		int ptr;
782 
783 		/** Start of the current name entry in {@link #buf}. */
784 		int namePtr;
785 
786 		/** One past end of name, {@code nameEnd - namePtr} is the length. */
787 		int nameEnd;
788 
789 		/** Number of bytes in the path leading up to this tree. */
790 		int pathLen;
791 	}
792 }