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