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