1 /*
2 * Copyright (C) 2007, Robin Rosenberg <robin.rosenberg@dewire.com>
3 * Copyright (C) 2008, Shawn O. Pearce <spearce@spearce.org>
4 * Copyright (C) 2014, Gustaf Lundh <gustaf.lundh@sonymobile.com> and others
5 *
6 * This program and the accompanying materials are made available under the
7 * terms of the Eclipse Distribution License v. 1.0 which is available at
8 * https://www.eclipse.org/org/documents/edl-v10.php.
9 *
10 * SPDX-License-Identifier: BSD-3-Clause
11 */
12
13 package org.eclipse.jgit.revwalk;
14
15 import java.io.IOException;
16 import java.text.MessageFormat;
17 import java.util.ArrayList;
18 import java.util.Collection;
19 import java.util.EnumSet;
20 import java.util.Iterator;
21 import java.util.List;
22
23 import org.eclipse.jgit.annotations.NonNull;
24 import org.eclipse.jgit.annotations.Nullable;
25 import org.eclipse.jgit.errors.CorruptObjectException;
26 import org.eclipse.jgit.errors.IncorrectObjectTypeException;
27 import org.eclipse.jgit.errors.LargeObjectException;
28 import org.eclipse.jgit.errors.MissingObjectException;
29 import org.eclipse.jgit.errors.RevWalkException;
30 import org.eclipse.jgit.internal.JGitText;
31 import org.eclipse.jgit.lib.AnyObjectId;
32 import org.eclipse.jgit.lib.AsyncObjectLoaderQueue;
33 import org.eclipse.jgit.lib.Constants;
34 import org.eclipse.jgit.lib.MutableObjectId;
35 import org.eclipse.jgit.lib.ObjectId;
36 import org.eclipse.jgit.lib.ObjectIdOwnerMap;
37 import org.eclipse.jgit.lib.ObjectLoader;
38 import org.eclipse.jgit.lib.ObjectReader;
39 import org.eclipse.jgit.lib.Repository;
40 import org.eclipse.jgit.revwalk.filter.RevFilter;
41 import org.eclipse.jgit.treewalk.filter.TreeFilter;
42 import org.eclipse.jgit.util.References;
43
44 /**
45 * Walks a commit graph and produces the matching commits in order.
46 * <p>
47 * A RevWalk instance can only be used once to generate results. Running a
48 * second time requires creating a new RevWalk instance, or invoking
49 * {@link #reset()} before starting again. Resetting an existing instance may be
50 * faster for some applications as commit body parsing can be avoided on the
51 * later invocations.
52 * <p>
53 * RevWalk instances are not thread-safe. Applications must either restrict
54 * usage of a RevWalk instance to a single thread, or implement their own
55 * synchronization at a higher level.
56 * <p>
57 * Multiple simultaneous RevWalk instances per
58 * {@link org.eclipse.jgit.lib.Repository} are permitted, even from concurrent
59 * threads. Equality of {@link org.eclipse.jgit.revwalk.RevCommit}s from two
60 * different RevWalk instances is never true, even if their
61 * {@link org.eclipse.jgit.lib.ObjectId}s are equal (and thus they describe the
62 * same commit).
63 * <p>
64 * The offered iterator is over the list of RevCommits described by the
65 * configuration of this instance. Applications should restrict themselves to
66 * using either the provided Iterator or {@link #next()}, but never use both on
67 * the same RevWalk at the same time. The Iterator may buffer RevCommits, while
68 * {@link #next()} does not.
69 */
70 public class RevWalk implements Iterable<RevCommit>, AutoCloseable {
71 private static final int MB = 1 << 20;
72
73 /**
74 * Set on objects whose important header data has been loaded.
75 * <p>
76 * For a RevCommit this indicates we have pulled apart the tree and parent
77 * references from the raw bytes available in the repository and translated
78 * those to our own local RevTree and RevCommit instances. The raw buffer is
79 * also available for message and other header filtering.
80 * <p>
81 * For a RevTag this indicates we have pulled part the tag references to
82 * find out who the tag refers to, and what that object's type is.
83 */
84 static final int PARSED = 1 << 0;
85
86 /**
87 * Set on RevCommit instances added to our {@link #pending} queue.
88 * <p>
89 * We use this flag to avoid adding the same commit instance twice to our
90 * queue, especially if we reached it by more than one path.
91 */
92 static final int SEEN = 1 << 1;
93
94 /**
95 * Set on RevCommit instances the caller does not want output.
96 * <p>
97 * We flag commits as uninteresting if the caller does not want commits
98 * reachable from a commit given to {@link #markUninteresting(RevCommit)}.
99 * This flag is always carried into the commit's parents and is a key part
100 * of the "rev-list B --not A" feature; A is marked UNINTERESTING.
101 */
102 static final int UNINTERESTING = 1 << 2;
103
104 /**
105 * Set on a RevCommit that can collapse out of the history.
106 * <p>
107 * If the {@link #treeFilter} concluded that this commit matches his
108 * parents' for all of the paths that the filter is interested in then we
109 * mark the commit REWRITE. Later we can rewrite the parents of a REWRITE
110 * child to remove chains of REWRITE commits before we produce the child to
111 * the application.
112 *
113 * @see RewriteGenerator
114 */
115 static final int REWRITE = 1 << 3;
116
117 /**
118 * Temporary mark for use within generators or filters.
119 * <p>
120 * This mark is only for local use within a single scope. If someone sets
121 * the mark they must unset it before any other code can see the mark.
122 */
123 static final int TEMP_MARK = 1 << 4;
124
125 /**
126 * Temporary mark for use within {@link TopoSortGenerator}.
127 * <p>
128 * This mark indicates the commit could not produce when it wanted to, as at
129 * least one child was behind it. Commits with this flag are delayed until
130 * all children have been output first.
131 */
132 static final int TOPO_DELAY = 1 << 5;
133
134 /**
135 * Temporary mark for use within {@link TopoNonIntermixSortGenerator}.
136 * <p>
137 * This mark indicates the commit has been queued for emission in
138 * {@link TopoSortGenerator} and can be produced. This mark is removed when
139 * the commit has been produced.
140 */
141 static final int TOPO_QUEUED = 1 << 6;
142
143 /** Number of flag bits we keep internal for our own use. See above flags. */
144 static final int RESERVED_FLAGS = 7;
145
146 private static final int APP_FLAGS = -1 & ~((1 << RESERVED_FLAGS) - 1);
147
148 final ObjectReader reader;
149
150 private final boolean closeReader;
151
152 final MutableObjectId idBuffer;
153
154 ObjectIdOwnerMap<RevObject> objects;
155
156 int freeFlags = APP_FLAGS;
157
158 private int delayFreeFlags;
159
160 private int retainOnReset;
161
162 int carryFlags = UNINTERESTING;
163
164 final ArrayList<RevCommit> roots;
165
166 AbstractRevQueue queue;
167
168 Generator pending;
169
170 private final EnumSet<RevSort> sorting;
171
172 private RevFilter filter;
173
174 private TreeFilter treeFilter;
175
176 private boolean retainBody = true;
177
178 private boolean rewriteParents = true;
179
180 private boolean firstParent;
181
182 boolean shallowCommitsInitialized;
183
184 /**
185 * Create a new revision walker for a given repository.
186 *
187 * @param repo
188 * the repository the walker will obtain data from. An
189 * ObjectReader will be created by the walker, and will be closed
190 * when the walker is closed.
191 */
192 public RevWalk(Repository repo) {
193 this(repo.newObjectReader(), true);
194 }
195
196 /**
197 * Create a new revision walker for a given repository.
198 * <p>
199 *
200 * @param or
201 * the reader the walker will obtain data from. The reader is not
202 * closed when the walker is closed (but is closed by {@link
203 * #dispose()}.
204 */
205 public RevWalk(ObjectReader or) {
206 this(or, false);
207 }
208
209 private RevWalk(ObjectReader or, boolean closeReader) {
210 reader = or;
211 idBuffer = new MutableObjectId();
212 objects = new ObjectIdOwnerMap<>();
213 roots = new ArrayList<>();
214 queue = new DateRevQueue(false);
215 pending = new StartGenerator(this);
216 sorting = EnumSet.of(RevSort.NONE);
217 filter = RevFilter.ALL;
218 treeFilter = TreeFilter.ALL;
219 this.closeReader = closeReader;
220 }
221
222 /**
223 * Get the reader this walker is using to load objects.
224 *
225 * @return the reader this walker is using to load objects.
226 */
227 public ObjectReader getObjectReader() {
228 return reader;
229 }
230
231 /**
232 * Get a reachability checker for commits over this revwalk.
233 *
234 * @return the most efficient reachability checker for this repository.
235 * @throws IOException
236 * if it cannot open any of the underlying indices.
237 *
238 * @since 5.4
239 */
240 public ReachabilityChecker createReachabilityChecker() throws IOException {
241 if (reader.getBitmapIndex() != null) {
242 return new BitmappedReachabilityChecker(this);
243 }
244
245 return new PedestrianReachabilityChecker(true, this);
246 }
247
248 /**
249 * {@inheritDoc}
250 * <p>
251 * Release any resources used by this walker's reader.
252 * <p>
253 * A walker that has been released can be used again, but may need to be
254 * released after the subsequent usage.
255 *
256 * @since 4.0
257 */
258 @Override
259 public void close() {
260 if (closeReader) {
261 reader.close();
262 }
263 }
264
265 /**
266 * Mark a commit to start graph traversal from.
267 * <p>
268 * Callers are encouraged to use {@link #parseCommit(AnyObjectId)} to obtain
269 * the commit reference, rather than {@link #lookupCommit(AnyObjectId)}, as
270 * this method requires the commit to be parsed before it can be added as a
271 * root for the traversal.
272 * <p>
273 * The method will automatically parse an unparsed commit, but error
274 * handling may be more difficult for the application to explain why a
275 * RevCommit is not actually a commit. The object pool of this walker would
276 * also be 'poisoned' by the non-commit RevCommit.
277 *
278 * @param c
279 * the commit to start traversing from. The commit passed must be
280 * from this same revision walker.
281 * @throws org.eclipse.jgit.errors.MissingObjectException
282 * the commit supplied is not available from the object
283 * database. This usually indicates the supplied commit is
284 * invalid, but the reference was constructed during an earlier
285 * invocation to {@link #lookupCommit(AnyObjectId)}.
286 * @throws org.eclipse.jgit.errors.IncorrectObjectTypeException
287 * the object was not parsed yet and it was discovered during
288 * parsing that it is not actually a commit. This usually
289 * indicates the caller supplied a non-commit SHA-1 to
290 * {@link #lookupCommit(AnyObjectId)}.
291 * @throws java.io.IOException
292 * a pack file or loose object could not be read.
293 */
294 public void markStart(RevCommit c) throws MissingObjectException,
295 IncorrectObjectTypeException, IOException {
296 if ((c.flags & SEEN) != 0)
297 return;
298 if ((c.flags & PARSED) == 0)
299 c.parseHeaders(this);
300 c.flags |= SEEN;
301 roots.add(c);
302 queue.add(c);
303 }
304
305 /**
306 * Mark commits to start graph traversal from.
307 *
308 * @param list
309 * commits to start traversing from. The commits passed must be
310 * from this same revision walker.
311 * @throws org.eclipse.jgit.errors.MissingObjectException
312 * one of the commits supplied is not available from the object
313 * database. This usually indicates the supplied commit is
314 * invalid, but the reference was constructed during an earlier
315 * invocation to {@link #lookupCommit(AnyObjectId)}.
316 * @throws org.eclipse.jgit.errors.IncorrectObjectTypeException
317 * the object was not parsed yet and it was discovered during
318 * parsing that it is not actually a commit. This usually
319 * indicates the caller supplied a non-commit SHA-1 to
320 * {@link #lookupCommit(AnyObjectId)}.
321 * @throws java.io.IOException
322 * a pack file or loose object could not be read.
323 */
324 public void markStart(Collection<RevCommit> list)
325 throws MissingObjectException, IncorrectObjectTypeException,
326 IOException {
327 for (RevCommit c : list)
328 markStart(c);
329 }
330
331 /**
332 * Mark a commit to not produce in the output.
333 * <p>
334 * Uninteresting commits denote not just themselves but also their entire
335 * ancestry chain, back until the merge base of an uninteresting commit and
336 * an otherwise interesting commit.
337 * <p>
338 * Callers are encouraged to use {@link #parseCommit(AnyObjectId)} to obtain
339 * the commit reference, rather than {@link #lookupCommit(AnyObjectId)}, as
340 * this method requires the commit to be parsed before it can be added as a
341 * root for the traversal.
342 * <p>
343 * The method will automatically parse an unparsed commit, but error
344 * handling may be more difficult for the application to explain why a
345 * RevCommit is not actually a commit. The object pool of this walker would
346 * also be 'poisoned' by the non-commit RevCommit.
347 *
348 * @param c
349 * the commit to start traversing from. The commit passed must be
350 * from this same revision walker.
351 * @throws org.eclipse.jgit.errors.MissingObjectException
352 * the commit supplied is not available from the object
353 * database. This usually indicates the supplied commit is
354 * invalid, but the reference was constructed during an earlier
355 * invocation to {@link #lookupCommit(AnyObjectId)}.
356 * @throws org.eclipse.jgit.errors.IncorrectObjectTypeException
357 * the object was not parsed yet and it was discovered during
358 * parsing that it is not actually a commit. This usually
359 * indicates the caller supplied a non-commit SHA-1 to
360 * {@link #lookupCommit(AnyObjectId)}.
361 * @throws java.io.IOException
362 * a pack file or loose object could not be read.
363 */
364 public void markUninteresting(RevCommit c)
365 throws MissingObjectException, IncorrectObjectTypeException,
366 IOException {
367 c.flags |= UNINTERESTING;
368 carryFlagsImpl(c);
369 markStart(c);
370 }
371
372 /**
373 * Determine if a commit is reachable from another commit.
374 * <p>
375 * A commit <code>base</code> is an ancestor of <code>tip</code> if we
376 * can find a path of commits that leads from <code>tip</code> and ends at
377 * <code>base</code>.
378 * <p>
379 * This utility function resets the walker, inserts the two supplied
380 * commits, and then executes a walk until an answer can be obtained.
381 * Currently allocated RevFlags that have been added to RevCommit instances
382 * will be retained through the reset.
383 *
384 * @param base
385 * commit the caller thinks is reachable from <code>tip</code>.
386 * @param tip
387 * commit to start iteration from, and which is most likely a
388 * descendant (child) of <code>base</code>.
389 * @return true if there is a path directly from <code>tip</code> to
390 * <code>base</code> (and thus <code>base</code> is fully merged
391 * into <code>tip</code>); false otherwise.
392 * @throws org.eclipse.jgit.errors.MissingObjectException
393 * one or more of the next commit's parents are not available
394 * from the object database, but were thought to be candidates
395 * for traversal. This usually indicates a broken link.
396 * @throws org.eclipse.jgit.errors.IncorrectObjectTypeException
397 * one or more of the next commit's parents are not actually
398 * commit objects.
399 * @throws java.io.IOException
400 * a pack file or loose object could not be read.
401 */
402 public boolean isMergedInto(RevCommit"../../../../org/eclipse/jgit/revwalk/RevCommit.html#RevCommit">RevCommit base, RevCommit tip)
403 throws MissingObjectException, IncorrectObjectTypeException,
404 IOException {
405 final RevFilter oldRF = filter;
406 final TreeFilter oldTF = treeFilter;
407 try {
408 finishDelayedFreeFlags();
409 reset(~freeFlags & APP_FLAGS);
410 filter = RevFilter.MERGE_BASE;
411 treeFilter = TreeFilter.ALL;
412 markStart(tip);
413 markStart(base);
414 RevCommit mergeBase;
415 while ((mergeBase = next()) != null) {
416 if (References.isSameObject(mergeBase, base)) {
417 return true;
418 }
419 }
420 return false;
421 } finally {
422 filter = oldRF;
423 treeFilter = oldTF;
424 }
425 }
426
427 /**
428 * Pop the next most recent commit.
429 *
430 * @return next most recent commit; null if traversal is over.
431 * @throws org.eclipse.jgit.errors.MissingObjectException
432 * one or more of the next commit's parents are not available
433 * from the object database, but were thought to be candidates
434 * for traversal. This usually indicates a broken link.
435 * @throws org.eclipse.jgit.errors.IncorrectObjectTypeException
436 * one or more of the next commit's parents are not actually
437 * commit objects.
438 * @throws java.io.IOException
439 * a pack file or loose object could not be read.
440 */
441 public RevCommit next() throws MissingObjectException,
442 IncorrectObjectTypeException, IOException {
443 return pending.next();
444 }
445
446 /**
447 * Obtain the sort types applied to the commits returned.
448 *
449 * @return the sorting strategies employed. At least one strategy is always
450 * used, but that strategy may be
451 * {@link org.eclipse.jgit.revwalk.RevSort#NONE}.
452 */
453 public EnumSet<RevSort> getRevSort() {
454 return sorting.clone();
455 }
456
457 /**
458 * Check whether the provided sorting strategy is enabled.
459 *
460 * @param sort
461 * a sorting strategy to look for.
462 * @return true if this strategy is enabled, false otherwise
463 */
464 public boolean hasRevSort(RevSort sort) {
465 return sorting.contains(sort);
466 }
467
468 /**
469 * Select a single sorting strategy for the returned commits.
470 * <p>
471 * Disables all sorting strategies, then enables only the single strategy
472 * supplied by the caller.
473 *
474 * @param s
475 * a sorting strategy to enable.
476 */
477 public void sort(RevSort s) {
478 assertNotStarted();
479 sorting.clear();
480 sorting.add(s);
481 }
482
483 /**
484 * Add or remove a sorting strategy for the returned commits.
485 * <p>
486 * Multiple strategies can be applied at once, in which case some strategies
487 * may take precedence over others. As an example,
488 * {@link org.eclipse.jgit.revwalk.RevSort#TOPO} must take precedence over
489 * {@link org.eclipse.jgit.revwalk.RevSort#COMMIT_TIME_DESC}, otherwise it
490 * cannot enforce its ordering.
491 *
492 * @param s
493 * a sorting strategy to enable or disable.
494 * @param use
495 * true if this strategy should be used, false if it should be
496 * removed.
497 */
498 public void sort(RevSort s, boolean use) {
499 assertNotStarted();
500 if (use)
501 sorting.add(s);
502 else
503 sorting.remove(s);
504
505 if (sorting.size() > 1)
506 sorting.remove(RevSort.NONE);
507 else if (sorting.isEmpty())
508 sorting.add(RevSort.NONE);
509 }
510
511 /**
512 * Get the currently configured commit filter.
513 *
514 * @return the current filter. Never null as a filter is always needed.
515 */
516 @NonNull
517 public RevFilter getRevFilter() {
518 return filter;
519 }
520
521 /**
522 * Set the commit filter for this walker.
523 * <p>
524 * Multiple filters may be combined by constructing an arbitrary tree of
525 * <code>AndRevFilter</code> or <code>OrRevFilter</code> instances to
526 * describe the boolean expression required by the application. Custom
527 * filter implementations may also be constructed by applications.
528 * <p>
529 * Note that filters are not thread-safe and may not be shared by concurrent
530 * RevWalk instances. Every RevWalk must be supplied its own unique filter,
531 * unless the filter implementation specifically states it is (and always
532 * will be) thread-safe. Callers may use
533 * {@link org.eclipse.jgit.revwalk.filter.RevFilter#clone()} to create a
534 * unique filter tree for this RevWalk instance.
535 *
536 * @param newFilter
537 * the new filter. If null the special
538 * {@link org.eclipse.jgit.revwalk.filter.RevFilter#ALL} filter
539 * will be used instead, as it matches every commit.
540 * @see org.eclipse.jgit.revwalk.filter.AndRevFilter
541 * @see org.eclipse.jgit.revwalk.filter.OrRevFilter
542 */
543 public void setRevFilter(RevFilter newFilter) {
544 assertNotStarted();
545 filter = newFilter != null ? newFilter : RevFilter.ALL;
546 }
547
548 /**
549 * Get the tree filter used to simplify commits by modified paths.
550 *
551 * @return the current filter. Never null as a filter is always needed. If
552 * no filter is being applied
553 * {@link org.eclipse.jgit.treewalk.filter.TreeFilter#ALL} is
554 * returned.
555 */
556 @NonNull
557 public TreeFilter getTreeFilter() {
558 return treeFilter;
559 }
560
561 /**
562 * Set the tree filter used to simplify commits by modified paths.
563 * <p>
564 * If null or {@link org.eclipse.jgit.treewalk.filter.TreeFilter#ALL} the
565 * path limiter is removed. Commits will not be simplified.
566 * <p>
567 * If non-null and not
568 * {@link org.eclipse.jgit.treewalk.filter.TreeFilter#ALL} then the tree
569 * filter will be installed. Commits will have their ancestry simplified to
570 * hide commits that do not contain tree entries matched by the filter,
571 * unless {@code setRewriteParents(false)} is called.
572 * <p>
573 * Usually callers should be inserting a filter graph including
574 * {@link org.eclipse.jgit.treewalk.filter.TreeFilter#ANY_DIFF} along with
575 * one or more {@link org.eclipse.jgit.treewalk.filter.PathFilter}
576 * instances.
577 *
578 * @param newFilter
579 * new filter. If null the special
580 * {@link org.eclipse.jgit.treewalk.filter.TreeFilter#ALL} filter
581 * will be used instead, as it matches everything.
582 * @see org.eclipse.jgit.treewalk.filter.PathFilter
583 */
584 public void setTreeFilter(TreeFilter newFilter) {
585 assertNotStarted();
586 treeFilter = newFilter != null ? newFilter : TreeFilter.ALL;
587 }
588
589 /**
590 * Set whether to rewrite parent pointers when filtering by modified paths.
591 * <p>
592 * By default, when {@link #setTreeFilter(TreeFilter)} is called with non-
593 * null and non-{@link org.eclipse.jgit.treewalk.filter.TreeFilter#ALL}
594 * filter, commits will have their ancestry simplified and parents rewritten
595 * to hide commits that do not match the filter.
596 * <p>
597 * This behavior can be bypassed by passing false to this method.
598 *
599 * @param rewrite
600 * whether to rewrite parents; defaults to true.
601 * @since 3.4
602 */
603 public void setRewriteParents(boolean rewrite) {
604 rewriteParents = rewrite;
605 }
606
607 boolean getRewriteParents() {
608 return rewriteParents;
609 }
610
611 /**
612 * Should the body of a commit or tag be retained after parsing its headers?
613 * <p>
614 * Usually the body is always retained, but some application code might not
615 * care and would prefer to discard the body of a commit as early as
616 * possible, to reduce memory usage.
617 * <p>
618 * True by default on {@link org.eclipse.jgit.revwalk.RevWalk} and false by
619 * default for {@link org.eclipse.jgit.revwalk.ObjectWalk}.
620 *
621 * @return true if the body should be retained; false it is discarded.
622 */
623 public boolean isRetainBody() {
624 return retainBody;
625 }
626
627 /**
628 * Set whether or not the body of a commit or tag is retained.
629 * <p>
630 * If a body of a commit or tag is not retained, the application must call
631 * {@link #parseBody(RevObject)} before the body can be safely accessed
632 * through the type specific access methods.
633 * <p>
634 * True by default on {@link org.eclipse.jgit.revwalk.RevWalk} and false by
635 * default for {@link org.eclipse.jgit.revwalk.ObjectWalk}.
636 *
637 * @param retain
638 * true to retain bodies; false to discard them early.
639 */
640 public void setRetainBody(boolean retain) {
641 retainBody = retain;
642 }
643
644 /**
645 * @return whether only first-parent links should be followed when walking.
646 *
647 * @since 5.5
648 */
649 public boolean isFirstParent() {
650 return firstParent;
651 }
652
653 /**
654 * Set whether or not only first parent links should be followed.
655 * <p>
656 * If set, second- and higher-parent links are not traversed at all.
657 * <p>
658 * This must be called prior to {@link #markStart(RevCommit)}.
659 *
660 * @param enable
661 * true to walk only first-parent links.
662 *
663 * @since 5.5
664 */
665 public void setFirstParent(boolean enable) {
666 assertNotStarted();
667 assertNoCommitsMarkedStart();
668 firstParent = enable;
669 queue = new DateRevQueue(firstParent);
670 pending = new StartGenerator(this);
671 }
672
673 /**
674 * Locate a reference to a blob without loading it.
675 * <p>
676 * The blob may or may not exist in the repository. It is impossible to tell
677 * from this method's return value.
678 *
679 * @param id
680 * name of the blob object.
681 * @return reference to the blob object. Never null.
682 */
683 @NonNull
684 public RevBlob lookupBlob(AnyObjectId id) {
685 RevBlob c = (RevBlob) objects.get(id);
686 if (c == null) {
687 c = new RevBlob(id);
688 objects.add(c);
689 }
690 return c;
691 }
692
693 /**
694 * Locate a reference to a tree without loading it.
695 * <p>
696 * The tree may or may not exist in the repository. It is impossible to tell
697 * from this method's return value.
698 *
699 * @param id
700 * name of the tree object.
701 * @return reference to the tree object. Never null.
702 */
703 @NonNull
704 public RevTree lookupTree(AnyObjectId id) {
705 RevTree c = (RevTree) objects.get(id);
706 if (c == null) {
707 c = new RevTree(id);
708 objects.add(c);
709 }
710 return c;
711 }
712
713 /**
714 * Locate a reference to a commit without loading it.
715 * <p>
716 * The commit may or may not exist in the repository. It is impossible to
717 * tell from this method's return value.
718 * <p>
719 * See {@link #parseHeaders(RevObject)} and {@link #parseBody(RevObject)}
720 * for loading contents.
721 *
722 * @param id
723 * name of the commit object.
724 * @return reference to the commit object. Never null.
725 */
726 @NonNull
727 public RevCommit lookupCommit(AnyObjectId id) {
728 RevCommit c = (RevCommit) objects.get(id);
729 if (c == null) {
730 c = createCommit(id);
731 objects.add(c);
732 }
733 return c;
734 }
735
736 /**
737 * Locate a reference to a tag without loading it.
738 * <p>
739 * The tag may or may not exist in the repository. It is impossible to tell
740 * from this method's return value.
741 *
742 * @param id
743 * name of the tag object.
744 * @return reference to the tag object. Never null.
745 */
746 @NonNull
747 public RevTag lookupTag(AnyObjectId id) {
748 RevTag c = (RevTag) objects.get(id);
749 if (c == null) {
750 c = new RevTag(id);
751 objects.add(c);
752 }
753 return c;
754 }
755
756 /**
757 * Locate a reference to any object without loading it.
758 * <p>
759 * The object may or may not exist in the repository. It is impossible to
760 * tell from this method's return value.
761 *
762 * @param id
763 * name of the object.
764 * @param type
765 * type of the object. Must be a valid Git object type.
766 * @return reference to the object. Never null.
767 */
768 @NonNull
769 public RevObject lookupAny(AnyObjectId id, int type) {
770 RevObject r = objects.get(id);
771 if (r == null) {
772 switch (type) {
773 case Constants.OBJ_COMMIT:
774 r = createCommit(id);
775 break;
776 case Constants.OBJ_TREE:
777 r = new RevTree(id);
778 break;
779 case Constants.OBJ_BLOB:
780 r = new RevBlob(id);
781 break;
782 case Constants.OBJ_TAG:
783 r = new RevTag(id);
784 break;
785 default:
786 throw new IllegalArgumentException(MessageFormat.format(
787 JGitText.get().invalidGitType, Integer.valueOf(type)));
788 }
789 objects.add(r);
790 }
791 return r;
792 }
793
794 /**
795 * Locate an object that was previously allocated in this walk.
796 *
797 * @param id
798 * name of the object.
799 * @return reference to the object if it has been previously located;
800 * otherwise null.
801 */
802 public RevObject lookupOrNull(AnyObjectId id) {
803 return objects.get(id);
804 }
805
806 /**
807 * Locate a reference to a commit and immediately parse its content.
808 * <p>
809 * Unlike {@link #lookupCommit(AnyObjectId)} this method only returns
810 * successfully if the commit object exists, is verified to be a commit, and
811 * was parsed without error.
812 *
813 * @param id
814 * name of the commit object.
815 * @return reference to the commit object. Never null.
816 * @throws org.eclipse.jgit.errors.MissingObjectException
817 * the supplied commit does not exist.
818 * @throws org.eclipse.jgit.errors.IncorrectObjectTypeException
819 * the supplied id is not a commit or an annotated tag.
820 * @throws java.io.IOException
821 * a pack file or loose object could not be read.
822 */
823 @NonNull
824 public RevCommit parseCommit(AnyObjectId id)
825 throws MissingObjectException, IncorrectObjectTypeException,
826 IOException {
827 RevObject c = peel(parseAny(id));
828 if (!(c instanceof RevCommit))
829 throw new IncorrectObjectTypeException(id.toObjectId(),
830 Constants.TYPE_COMMIT);
831 return (RevCommit) c;
832 }
833
834 /**
835 * Locate a reference to a tree.
836 * <p>
837 * This method only returns successfully if the tree object exists, is
838 * verified to be a tree.
839 *
840 * @param id
841 * name of the tree object, or a commit or annotated tag that may
842 * reference a tree.
843 * @return reference to the tree object. Never null.
844 * @throws org.eclipse.jgit.errors.MissingObjectException
845 * the supplied tree does not exist.
846 * @throws org.eclipse.jgit.errors.IncorrectObjectTypeException
847 * the supplied id is not a tree, a commit or an annotated tag.
848 * @throws java.io.IOException
849 * a pack file or loose object could not be read.
850 */
851 @NonNull
852 public RevTree parseTree(AnyObjectId id)
853 throws MissingObjectException, IncorrectObjectTypeException,
854 IOException {
855 RevObject c = peel(parseAny(id));
856
857 final RevTree t;
858 if (c instanceof RevCommit)
859 t = ((RevCommit) c).getTree();
860 else if (!(c instanceof RevTree))
861 throw new IncorrectObjectTypeException(id.toObjectId(),
862 Constants.TYPE_TREE);
863 else
864 t = (RevTree) c;
865 parseHeaders(t);
866 return t;
867 }
868
869 /**
870 * Locate a reference to an annotated tag and immediately parse its content.
871 * <p>
872 * Unlike {@link #lookupTag(AnyObjectId)} this method only returns
873 * successfully if the tag object exists, is verified to be a tag, and was
874 * parsed without error.
875 *
876 * @param id
877 * name of the tag object.
878 * @return reference to the tag object. Never null.
879 * @throws org.eclipse.jgit.errors.MissingObjectException
880 * the supplied tag does not exist.
881 * @throws org.eclipse.jgit.errors.IncorrectObjectTypeException
882 * the supplied id is not a tag or an annotated tag.
883 * @throws java.io.IOException
884 * a pack file or loose object could not be read.
885 */
886 @NonNull
887 public RevTag parseTag(AnyObjectId id) throws MissingObjectException,
888 IncorrectObjectTypeException, IOException {
889 RevObject c = parseAny(id);
890 if (!(c instanceof RevTag))
891 throw new IncorrectObjectTypeException(id.toObjectId(),
892 Constants.TYPE_TAG);
893 return (RevTag) c;
894 }
895
896 /**
897 * Locate a reference to any object and immediately parse its headers.
898 * <p>
899 * This method only returns successfully if the object exists and was parsed
900 * without error. Parsing an object can be expensive as the type must be
901 * determined. For blobs this may mean the blob content was unpacked
902 * unnecessarily, and thrown away.
903 *
904 * @param id
905 * name of the object.
906 * @return reference to the object. Never null.
907 * @throws org.eclipse.jgit.errors.MissingObjectException
908 * the supplied does not exist.
909 * @throws java.io.IOException
910 * a pack file or loose object could not be read.
911 */
912 @NonNull
913 public RevObject parseAny(AnyObjectId id)
914 throws MissingObjectException, IOException {
915 RevObject r = objects.get(id);
916 if (r == null)
917 r = parseNew(id, reader.open(id));
918 else
919 parseHeaders(r);
920 return r;
921 }
922
923 private RevObject parseNew(AnyObjectId id, ObjectLoader ldr)
924 throws LargeObjectException, CorruptObjectException,
925 MissingObjectException, IOException {
926 RevObject r;
927 int type = ldr.getType();
928 switch (type) {
929 case Constants.OBJ_COMMIT: {
930 final RevCommit c = createCommit(id);
931 c.parseCanonical(this, getCachedBytes(c, ldr));
932 r = c;
933 break;
934 }
935 case Constants.OBJ_TREE: {
936 r = new RevTree(id);
937 r.flags |= PARSED;
938 break;
939 }
940 case Constants.OBJ_BLOB: {
941 r = new RevBlob(id);
942 r.flags |= PARSED;
943 break;
944 }
945 case Constants.OBJ_TAG: {
946 final RevTagk/RevTag.html#RevTag">RevTag t = new RevTag(id);
947 t.parseCanonical(this, getCachedBytes(t, ldr));
948 r = t;
949 break;
950 }
951 default:
952 throw new IllegalArgumentException(MessageFormat.format(
953 JGitText.get().badObjectType, Integer.valueOf(type)));
954 }
955 objects.add(r);
956 return r;
957 }
958
959 byte[] getCachedBytes(RevObject obj) throws LargeObjectException,
960 MissingObjectException, IncorrectObjectTypeException, IOException {
961 return getCachedBytes(obj, reader.open(obj, obj.getType()));
962 }
963
964 byte[] getCachedBytes(RevObject obj, ObjectLoader ldr)
965 throws LargeObjectException, MissingObjectException, IOException {
966 try {
967 return ldr.getCachedBytes(5 * MB);
968 } catch (LargeObjectException tooBig) {
969 tooBig.setObjectId(obj);
970 throw tooBig;
971 }
972 }
973
974 /**
975 * Asynchronous object parsing.
976 *
977 * @param objectIds
978 * objects to open from the object store. The supplied collection
979 * must not be modified until the queue has finished.
980 * @param reportMissing
981 * if true missing objects are reported by calling failure with a
982 * MissingObjectException. This may be more expensive for the
983 * implementation to guarantee. If false the implementation may
984 * choose to report MissingObjectException, or silently skip over
985 * the object with no warning.
986 * @return queue to read the objects from.
987 */
988 public <T extends ObjectId> AsyncRevObjectQueue parseAny(
989 Iterable<T> objectIds, boolean reportMissing) {
990 List<T> need = new ArrayList<>();
991 List<RevObject> have = new ArrayList<>();
992 for (T id : objectIds) {
993 RevObject r = objects.get(id);
994 if (r != null && (r.flags & PARSED) != 0)
995 have.add(r);
996 else
997 need.add(id);
998 }
999
1000 final Iterator<RevObject> objItr = have.iterator();
1001 if (need.isEmpty()) {
1002 return new AsyncRevObjectQueue() {
1003 @Override
1004 public RevObject next() {
1005 return objItr.hasNext() ? objItr.next() : null;
1006 }
1007
1008 @Override
1009 public boolean cancel(boolean mayInterruptIfRunning) {
1010 return true;
1011 }
1012
1013 @Override
1014 public void release() {
1015 // In-memory only, no action required.
1016 }
1017 };
1018 }
1019
1020 final AsyncObjectLoaderQueue<T> lItr = reader.open(need, reportMissing);
1021 return new AsyncRevObjectQueue() {
1022 @Override
1023 public RevObject next() throws MissingObjectException,
1024 IncorrectObjectTypeException, IOException {
1025 if (objItr.hasNext())
1026 return objItr.next();
1027 if (!lItr.next())
1028 return null;
1029
1030 ObjectId id = lItr.getObjectId();
1031 ObjectLoader ldr = lItr.open();
1032 RevObject r = objects.get(id);
1033 if (r == null)
1034 r = parseNew(id, ldr);
1035 else if (r instanceof RevCommit) {
1036 byte[] raw = ldr.getCachedBytes();
1037 ((RevCommit) r).parseCanonical(RevWalk.this, raw);
1038 } else if (r instanceof RevTag) {
1039 byte[] raw = ldr.getCachedBytes();
1040 ((RevTag) r).parseCanonical(RevWalk.this, raw);
1041 } else
1042 r.flags |= PARSED;
1043 return r;
1044 }
1045
1046 @Override
1047 public boolean cancel(boolean mayInterruptIfRunning) {
1048 return lItr.cancel(mayInterruptIfRunning);
1049 }
1050
1051 @Override
1052 public void release() {
1053 lItr.release();
1054 }
1055 };
1056 }
1057
1058 /**
1059 * Ensure the object's critical headers have been parsed.
1060 * <p>
1061 * This method only returns successfully if the object exists and was parsed
1062 * without error.
1063 *
1064 * @param obj
1065 * the object the caller needs to be parsed.
1066 * @throws org.eclipse.jgit.errors.MissingObjectException
1067 * the supplied does not exist.
1068 * @throws java.io.IOException
1069 * a pack file or loose object could not be read.
1070 */
1071 public void parseHeaders(RevObject obj)
1072 throws MissingObjectException, IOException {
1073 if ((obj.flags & PARSED) == 0)
1074 obj.parseHeaders(this);
1075 }
1076
1077 /**
1078 * Ensure the object's full body content is available.
1079 * <p>
1080 * This method only returns successfully if the object exists and was parsed
1081 * without error.
1082 *
1083 * @param obj
1084 * the object the caller needs to be parsed.
1085 * @throws org.eclipse.jgit.errors.MissingObjectException
1086 * the supplied does not exist.
1087 * @throws java.io.IOException
1088 * a pack file or loose object could not be read.
1089 */
1090 public void parseBody(RevObject obj)
1091 throws MissingObjectException, IOException {
1092 obj.parseBody(this);
1093 }
1094
1095 /**
1096 * Peel back annotated tags until a non-tag object is found.
1097 *
1098 * @param obj
1099 * the starting object.
1100 * @return If {@code obj} is not an annotated tag, {@code obj}. Otherwise
1101 * the first non-tag object that {@code obj} references. The
1102 * returned object's headers have been parsed.
1103 * @throws org.eclipse.jgit.errors.MissingObjectException
1104 * a referenced object cannot be found.
1105 * @throws java.io.IOException
1106 * a pack file or loose object could not be read.
1107 */
1108 public RevObject="../../../../org/eclipse/jgit/revwalk/RevObject.html#RevObject">RevObject peel(RevObject obj) throws MissingObjectException,
1109 IOException {
1110 while (obj instanceof RevTag) {
1111 parseHeaders(obj);
1112 obj = ((RevTag) obj).getObject();
1113 }
1114 parseHeaders(obj);
1115 return obj;
1116 }
1117
1118 /**
1119 * Create a new flag for application use during walking.
1120 * <p>
1121 * Applications are only assured to be able to create 24 unique flags on any
1122 * given revision walker instance. Any flags beyond 24 are offered only if
1123 * the implementation has extra free space within its internal storage.
1124 *
1125 * @param name
1126 * description of the flag, primarily useful for debugging.
1127 * @return newly constructed flag instance.
1128 * @throws java.lang.IllegalArgumentException
1129 * too many flags have been reserved on this revision walker.
1130 */
1131 public RevFlag newFlag(String name) {
1132 final int m = allocFlag();
1133 return new RevFlag(this, name, m);
1134 }
1135
1136 int allocFlag() {
1137 if (freeFlags == 0)
1138 throw new IllegalArgumentException(MessageFormat.format(
1139 JGitText.get().flagsAlreadyCreated,
1140 Integer.valueOf(32 - RESERVED_FLAGS)));
1141 final int m = Integer.lowestOneBit(freeFlags);
1142 freeFlags &= ~m;
1143 return m;
1144 }
1145
1146 /**
1147 * Automatically carry a flag from a child commit to its parents.
1148 * <p>
1149 * A carried flag is copied from the child commit onto its parents when the
1150 * child commit is popped from the lowest level of walk's internal graph.
1151 *
1152 * @param flag
1153 * the flag to carry onto parents, if set on a descendant.
1154 */
1155 public void carry(RevFlag flag) {
1156 if ((freeFlags & flag.mask) != 0)
1157 throw new IllegalArgumentException(MessageFormat.format(JGitText.get().flagIsDisposed, flag.name));
1158 if (flag.walker != this)
1159 throw new IllegalArgumentException(MessageFormat.format(JGitText.get().flagNotFromThis, flag.name));
1160 carryFlags |= flag.mask;
1161 }
1162
1163 /**
1164 * Automatically carry flags from a child commit to its parents.
1165 * <p>
1166 * A carried flag is copied from the child commit onto its parents when the
1167 * child commit is popped from the lowest level of walk's internal graph.
1168 *
1169 * @param set
1170 * the flags to carry onto parents, if set on a descendant.
1171 */
1172 public void carry(Collection<RevFlag> set) {
1173 for (RevFlag flag : set)
1174 carry(flag);
1175 }
1176
1177 /**
1178 * Preserve a RevFlag during all {@code reset} methods.
1179 * <p>
1180 * Calling {@code retainOnReset(flag)} avoids needing to pass the flag
1181 * during each {@code resetRetain()} invocation on this instance.
1182 * <p>
1183 * Clearing flags marked retainOnReset requires disposing of the flag with
1184 * {@code #disposeFlag(RevFlag)} or disposing of the entire RevWalk by
1185 * {@code #dispose()}.
1186 *
1187 * @param flag
1188 * the flag to retain during all resets.
1189 * @since 3.6
1190 */
1191 public final void retainOnReset(RevFlag flag) {
1192 if ((freeFlags & flag.mask) != 0)
1193 throw new IllegalArgumentException(MessageFormat.format(JGitText.get().flagIsDisposed, flag.name));
1194 if (flag.walker != this)
1195 throw new IllegalArgumentException(MessageFormat.format(JGitText.get().flagNotFromThis, flag.name));
1196 retainOnReset |= flag.mask;
1197 }
1198
1199 /**
1200 * Preserve a set of RevFlags during all {@code reset} methods.
1201 * <p>
1202 * Calling {@code retainOnReset(set)} avoids needing to pass the flags
1203 * during each {@code resetRetain()} invocation on this instance.
1204 * <p>
1205 * Clearing flags marked retainOnReset requires disposing of the flag with
1206 * {@code #disposeFlag(RevFlag)} or disposing of the entire RevWalk by
1207 * {@code #dispose()}.
1208 *
1209 * @param flags
1210 * the flags to retain during all resets.
1211 * @since 3.6
1212 */
1213 public final void retainOnReset(Collection<RevFlag> flags) {
1214 for (RevFlag f : flags)
1215 retainOnReset(f);
1216 }
1217
1218 /**
1219 * Allow a flag to be recycled for a different use.
1220 * <p>
1221 * Recycled flags always come back as a different Java object instance when
1222 * assigned again by {@link #newFlag(String)}.
1223 * <p>
1224 * If the flag was previously being carried, the carrying request is
1225 * removed. Disposing of a carried flag while a traversal is in progress has
1226 * an undefined behavior.
1227 *
1228 * @param flag
1229 * the to recycle.
1230 */
1231 public void disposeFlag(RevFlag flag) {
1232 freeFlag(flag.mask);
1233 }
1234
1235 void freeFlag(int mask) {
1236 retainOnReset &= ~mask;
1237 if (isNotStarted()) {
1238 freeFlags |= mask;
1239 carryFlags &= ~mask;
1240 } else {
1241 delayFreeFlags |= mask;
1242 }
1243 }
1244
1245 private void finishDelayedFreeFlags() {
1246 if (delayFreeFlags != 0) {
1247 freeFlags |= delayFreeFlags;
1248 carryFlags &= ~delayFreeFlags;
1249 delayFreeFlags = 0;
1250 }
1251 }
1252
1253 /**
1254 * Resets internal state and allows this instance to be used again.
1255 * <p>
1256 * Unlike {@link #dispose()} previously acquired RevObject (and RevCommit)
1257 * instances are not invalidated. RevFlag instances are not invalidated, but
1258 * are removed from all RevObjects.
1259 */
1260 public final void reset() {
1261 reset(0);
1262 }
1263
1264 /**
1265 * Resets internal state and allows this instance to be used again.
1266 * <p>
1267 * Unlike {@link #dispose()} previously acquired RevObject (and RevCommit)
1268 * instances are not invalidated. RevFlag instances are not invalidated, but
1269 * are removed from all RevObjects.
1270 *
1271 * @param retainFlags
1272 * application flags that should <b>not</b> be cleared from
1273 * existing commit objects.
1274 */
1275 public final void resetRetain(RevFlagSet retainFlags) {
1276 reset(retainFlags.mask);
1277 }
1278
1279 /**
1280 * Resets internal state and allows this instance to be used again.
1281 * <p>
1282 * Unlike {@link #dispose()} previously acquired RevObject (and RevCommit)
1283 * instances are not invalidated. RevFlag instances are not invalidated, but
1284 * are removed from all RevObjects.
1285 * <p>
1286 * See {@link #retainOnReset(RevFlag)} for an alternative that does not
1287 * require passing the flags during each reset.
1288 *
1289 * @param retainFlags
1290 * application flags that should <b>not</b> be cleared from
1291 * existing commit objects.
1292 */
1293 public final void resetRetain(RevFlag... retainFlags) {
1294 int mask = 0;
1295 for (RevFlag flag : retainFlags)
1296 mask |= flag.mask;
1297 reset(mask);
1298 }
1299
1300 /**
1301 * Resets internal state and allows this instance to be used again.
1302 * <p>
1303 * Unlike {@link #dispose()} previously acquired RevObject (and RevCommit)
1304 * instances are not invalidated. RevFlag instances are not invalidated, but
1305 * are removed from all RevObjects. The value of {@code firstParent} is
1306 * retained.
1307 *
1308 * @param retainFlags
1309 * application flags that should <b>not</b> be cleared from
1310 * existing commit objects.
1311 */
1312 protected void reset(int retainFlags) {
1313 finishDelayedFreeFlags();
1314 retainFlags |= PARSED | retainOnReset;
1315 final int clearFlags = ~retainFlags;
1316
1317 final FIFORevQueueRevQueue.html#FIFORevQueue">FIFORevQueue q = new FIFORevQueue();
1318 for (RevCommit c : roots) {
1319 if ((c.flags & clearFlags) == 0)
1320 continue;
1321 c.flags &= retainFlags;
1322 c.reset();
1323 q.add(c);
1324 }
1325
1326 for (;;) {
1327 final RevCommit c = q.next();
1328 if (c == null)
1329 break;
1330 if (c.parents == null)
1331 continue;
1332 for (RevCommit p : c.parents) {
1333 if ((p.flags & clearFlags) == 0)
1334 continue;
1335 p.flags &= retainFlags;
1336 p.reset();
1337 q.add(p);
1338 }
1339 }
1340
1341 roots.clear();
1342 queue = new DateRevQueue(firstParent);
1343 pending = new StartGenerator(this);
1344 }
1345
1346 /**
1347 * Dispose all internal state and invalidate all RevObject instances.
1348 * <p>
1349 * All RevObject (and thus RevCommit, etc.) instances previously acquired
1350 * from this RevWalk are invalidated by a dispose call. Applications must
1351 * not retain or use RevObject instances obtained prior to the dispose call.
1352 * All RevFlag instances are also invalidated, and must not be reused.
1353 */
1354 public void dispose() {
1355 reader.close();
1356 freeFlags = APP_FLAGS;
1357 delayFreeFlags = 0;
1358 retainOnReset = 0;
1359 carryFlags = UNINTERESTING;
1360 firstParent = false;
1361 objects.clear();
1362 roots.clear();
1363 queue = new DateRevQueue(firstParent);
1364 pending = new StartGenerator(this);
1365 shallowCommitsInitialized = false;
1366 }
1367
1368 /**
1369 * Like {@link #next()}, but if a checked exception is thrown during the
1370 * walk it is rethrown as a {@link RevWalkException}.
1371 *
1372 * @throws RevWalkException if an {@link IOException} was thrown.
1373 * @return next most recent commit; null if traversal is over.
1374 */
1375 @Nullable
1376 private RevCommit nextForIterator() {
1377 try {
1378 return next();
1379 } catch (IOException e) {
1380 throw new RevWalkException(e);
1381 }
1382 }
1383
1384 /**
1385 * {@inheritDoc}
1386 * <p>
1387 * Returns an Iterator over the commits of this walker.
1388 * <p>
1389 * The returned iterator is only useful for one walk. If this RevWalk gets
1390 * reset a new iterator must be obtained to walk over the new results.
1391 * <p>
1392 * Applications must not use both the Iterator and the {@link #next()} API
1393 * at the same time. Pick one API and use that for the entire walk.
1394 * <p>
1395 * If a checked exception is thrown during the walk (see {@link #next()}) it
1396 * is rethrown from the Iterator as a {@link RevWalkException}.
1397 *
1398 * @see RevWalkException
1399 */
1400 @Override
1401 public Iterator<RevCommit> iterator() {
1402 RevCommit first = nextForIterator();
1403
1404 return new Iterator<RevCommit>() {
1405 RevCommit next = first;
1406
1407 @Override
1408 public boolean hasNext() {
1409 return next != null;
1410 }
1411
1412 @Override
1413 public RevCommit next() {
1414 RevCommit r = next;
1415 next = nextForIterator();
1416 return r;
1417 }
1418
1419 @Override
1420 public void remove() {
1421 throw new UnsupportedOperationException();
1422 }
1423 };
1424 }
1425
1426 /**
1427 * Throws an exception if we have started producing output.
1428 */
1429 protected void assertNotStarted() {
1430 if (isNotStarted())
1431 return;
1432 throw new IllegalStateException(JGitText.get().outputHasAlreadyBeenStarted);
1433 }
1434
1435 /**
1436 * Throws an exception if any commits have been marked as start.
1437 * <p>
1438 * If {@link #markStart(RevCommit)} has already been called,
1439 * {@link #reset()} can be called to satisfy this condition.
1440 *
1441 * @since 5.5
1442 */
1443 protected void assertNoCommitsMarkedStart() {
1444 if (roots.isEmpty())
1445 return;
1446 throw new IllegalStateException(
1447 JGitText.get().commitsHaveAlreadyBeenMarkedAsStart);
1448 }
1449
1450 private boolean isNotStarted() {
1451 return pending instanceof StartGenerator;
1452 }
1453
1454 /**
1455 * Create and return an {@link org.eclipse.jgit.revwalk.ObjectWalk} using
1456 * the same objects.
1457 * <p>
1458 * Prior to using this method, the caller must reset this RevWalk to clean
1459 * any flags that were used during the last traversal.
1460 * <p>
1461 * The returned ObjectWalk uses the same ObjectReader, internal object pool,
1462 * and free RevFlags. Once the ObjectWalk is created, this RevWalk should
1463 * not be used anymore.
1464 *
1465 * @return a new walk, using the exact same object pool.
1466 */
1467 public ObjectWalk toObjectWalkWithSameObjects() {
1468 ObjectWalk ow = new ObjectWalk(reader);
1469 RevWalk rw = ow;
1470 rw.objects = objects;
1471 rw.freeFlags = freeFlags;
1472 return ow;
1473 }
1474
1475 /**
1476 * Construct a new unparsed commit for the given object.
1477 *
1478 * @param id
1479 * the object this walker requires a commit reference for.
1480 * @return a new unparsed reference for the object.
1481 */
1482 protected RevCommit createCommit(AnyObjectId id) {
1483 return new RevCommit(id);
1484 }
1485
1486 void carryFlagsImpl(RevCommit c) {
1487 final int carry = c.flags & carryFlags;
1488 if (carry != 0)
1489 RevCommit.carryFlags(c, carry);
1490 }
1491
1492 /**
1493 * Assume additional commits are shallow (have no parents).
1494 * <p>
1495 * This method is a No-op if the collection is empty.
1496 *
1497 * @param ids
1498 * commits that should be treated as shallow commits, in addition
1499 * to any commits already known to be shallow by the repository.
1500 * @since 3.3
1501 */
1502 public void assumeShallow(Collection<? extends ObjectId> ids) {
1503 for (ObjectId id : ids)
1504 lookupCommit(id).parents = RevCommit.NO_PARENTS;
1505 }
1506
1507 /**
1508 * Reads the "shallow" file and applies it by setting the parents of shallow
1509 * commits to an empty array.
1510 * <p>
1511 * There is a sequencing problem if the first commit being parsed is a
1512 * shallow commit, since {@link RevCommit#parseCanonical(RevWalk, byte[])}
1513 * calls this method before its callers add the new commit to the
1514 * {@link RevWalk#objects} map. That means a call from this method to
1515 * {@link #lookupCommit(AnyObjectId)} fails to find that commit and creates
1516 * a new one, which is promptly discarded.
1517 * <p>
1518 * To avoid that, {@link RevCommit#parseCanonical(RevWalk, byte[])} passes
1519 * its commit to this method, so that this method can apply the shallow
1520 * state to it directly and avoid creating the duplicate commit object.
1521 *
1522 * @param rc
1523 * the initial commit being parsed
1524 * @throws IOException
1525 * if the shallow commits file can't be read
1526 */
1527 void initializeShallowCommits(RevCommit rc) throws IOException {
1528 if (shallowCommitsInitialized) {
1529 throw new IllegalStateException(
1530 JGitText.get().shallowCommitsAlreadyInitialized);
1531 }
1532
1533 shallowCommitsInitialized = true;
1534
1535 if (reader == null) {
1536 return;
1537 }
1538
1539 for (ObjectId id : reader.getShallowCommits()) {
1540 if (id.equals(rc.getId())) {
1541 rc.parents = RevCommit.NO_PARENTS;
1542 } else {
1543 lookupCommit(id).parents = RevCommit.NO_PARENTS;
1544 }
1545 }
1546 }
1547 }