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