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 RevFilter oldRF = filter; 532 TreeFilter oldTF = treeFilter; 533 try { 534 finishDelayedFreeFlags(); 535 reset(~freeFlags & APP_FLAGS); 536 filter = RevFilter.ALL; 537 treeFilter = TreeFilter.ALL; 538 for (Ref r: haystacks) { 539 if (monitor.isCancelled()) { 540 return result; 541 } 542 monitor.update(1); 543 RevObject o = parseAny(r.getObjectId()); 544 if (!(o instanceof RevCommit)) { 545 continue; 546 } 547 RevCommit c = (RevCommit) o; 548 resetRetain(RevFlag.UNINTERESTING); 549 markStart(c); 550 boolean commitFound = false; 551 RevCommit next; 552 while ((next = next()) != null) { 553 if (References.isSameObject(next, needle)) { 554 result.add(r); 555 if (returnStrategy == GetMergedIntoStrategy.RETURN_ON_FIRST_FOUND) { 556 return result; 557 } 558 commitFound = true; 559 break; 560 } 561 } 562 if(!commitFound){ 563 markUninteresting(c); 564 uninteresting.add(c); 565 if (returnStrategy == GetMergedIntoStrategy.RETURN_ON_FIRST_NOT_FOUND) { 566 return result; 567 } 568 } 569 } 570 } finally { 571 roots.addAll(uninteresting); 572 filter = oldRF; 573 treeFilter = oldTF; 574 } 575 return result; 576 } 577 578 /** 579 * Pop the next most recent commit. 580 * 581 * @return next most recent commit; null if traversal is over. 582 * @throws org.eclipse.jgit.errors.MissingObjectException 583 * one or more of the next commit's parents are not available 584 * from the object database, but were thought to be candidates 585 * for traversal. This usually indicates a broken link. 586 * @throws org.eclipse.jgit.errors.IncorrectObjectTypeException 587 * one or more of the next commit's parents are not actually 588 * commit objects. 589 * @throws java.io.IOException 590 * a pack file or loose object could not be read. 591 */ 592 public RevCommit next() throws MissingObjectException, 593 IncorrectObjectTypeException, IOException { 594 return pending.next(); 595 } 596 597 /** 598 * Obtain the sort types applied to the commits returned. 599 * 600 * @return the sorting strategies employed. At least one strategy is always 601 * used, but that strategy may be 602 * {@link org.eclipse.jgit.revwalk.RevSort#NONE}. 603 */ 604 public EnumSet<RevSort> getRevSort() { 605 return sorting.clone(); 606 } 607 608 /** 609 * Check whether the provided sorting strategy is enabled. 610 * 611 * @param sort 612 * a sorting strategy to look for. 613 * @return true if this strategy is enabled, false otherwise 614 */ 615 public boolean hasRevSort(RevSort sort) { 616 return sorting.contains(sort); 617 } 618 619 /** 620 * Select a single sorting strategy for the returned commits. 621 * <p> 622 * Disables all sorting strategies, then enables only the single strategy 623 * supplied by the caller. 624 * 625 * @param s 626 * a sorting strategy to enable. 627 */ 628 public void sort(RevSort s) { 629 assertNotStarted(); 630 sorting.clear(); 631 sorting.add(s); 632 } 633 634 /** 635 * Add or remove a sorting strategy for the returned commits. 636 * <p> 637 * Multiple strategies can be applied at once, in which case some strategies 638 * may take precedence over others. As an example, 639 * {@link org.eclipse.jgit.revwalk.RevSort#TOPO} must take precedence over 640 * {@link org.eclipse.jgit.revwalk.RevSort#COMMIT_TIME_DESC}, otherwise it 641 * cannot enforce its ordering. 642 * 643 * @param s 644 * a sorting strategy to enable or disable. 645 * @param use 646 * true if this strategy should be used, false if it should be 647 * removed. 648 */ 649 public void sort(RevSort s, boolean use) { 650 assertNotStarted(); 651 if (use) 652 sorting.add(s); 653 else 654 sorting.remove(s); 655 656 if (sorting.size() > 1) 657 sorting.remove(RevSort.NONE); 658 else if (sorting.isEmpty()) 659 sorting.add(RevSort.NONE); 660 } 661 662 /** 663 * Get the currently configured commit filter. 664 * 665 * @return the current filter. Never null as a filter is always needed. 666 */ 667 @NonNull 668 public RevFilter getRevFilter() { 669 return filter; 670 } 671 672 /** 673 * Set the commit filter for this walker. 674 * <p> 675 * Multiple filters may be combined by constructing an arbitrary tree of 676 * <code>AndRevFilter</code> or <code>OrRevFilter</code> instances to 677 * describe the boolean expression required by the application. Custom 678 * filter implementations may also be constructed by applications. 679 * <p> 680 * Note that filters are not thread-safe and may not be shared by concurrent 681 * RevWalk instances. Every RevWalk must be supplied its own unique filter, 682 * unless the filter implementation specifically states it is (and always 683 * will be) thread-safe. Callers may use 684 * {@link org.eclipse.jgit.revwalk.filter.RevFilter#clone()} to create a 685 * unique filter tree for this RevWalk instance. 686 * 687 * @param newFilter 688 * the new filter. If null the special 689 * {@link org.eclipse.jgit.revwalk.filter.RevFilter#ALL} filter 690 * will be used instead, as it matches every commit. 691 * @see org.eclipse.jgit.revwalk.filter.AndRevFilter 692 * @see org.eclipse.jgit.revwalk.filter.OrRevFilter 693 */ 694 public void setRevFilter(RevFilter newFilter) { 695 assertNotStarted(); 696 filter = newFilter != null ? newFilter : RevFilter.ALL; 697 } 698 699 /** 700 * Get the tree filter used to simplify commits by modified paths. 701 * 702 * @return the current filter. Never null as a filter is always needed. If 703 * no filter is being applied 704 * {@link org.eclipse.jgit.treewalk.filter.TreeFilter#ALL} is 705 * returned. 706 */ 707 @NonNull 708 public TreeFilter getTreeFilter() { 709 return treeFilter; 710 } 711 712 /** 713 * Set the tree filter used to simplify commits by modified paths. 714 * <p> 715 * If null or {@link org.eclipse.jgit.treewalk.filter.TreeFilter#ALL} the 716 * path limiter is removed. Commits will not be simplified. 717 * <p> 718 * If non-null and not 719 * {@link org.eclipse.jgit.treewalk.filter.TreeFilter#ALL} then the tree 720 * filter will be installed. Commits will have their ancestry simplified to 721 * hide commits that do not contain tree entries matched by the filter, 722 * unless {@code setRewriteParents(false)} is called. 723 * <p> 724 * Usually callers should be inserting a filter graph including 725 * {@link org.eclipse.jgit.treewalk.filter.TreeFilter#ANY_DIFF} along with 726 * one or more {@link org.eclipse.jgit.treewalk.filter.PathFilter} 727 * instances. 728 * 729 * @param newFilter 730 * new filter. If null the special 731 * {@link org.eclipse.jgit.treewalk.filter.TreeFilter#ALL} filter 732 * will be used instead, as it matches everything. 733 * @see org.eclipse.jgit.treewalk.filter.PathFilter 734 */ 735 public void setTreeFilter(TreeFilter newFilter) { 736 assertNotStarted(); 737 treeFilter = newFilter != null ? newFilter : TreeFilter.ALL; 738 } 739 740 /** 741 * Set whether to rewrite parent pointers when filtering by modified paths. 742 * <p> 743 * By default, when {@link #setTreeFilter(TreeFilter)} is called with non- 744 * null and non-{@link org.eclipse.jgit.treewalk.filter.TreeFilter#ALL} 745 * filter, commits will have their ancestry simplified and parents rewritten 746 * to hide commits that do not match the filter. 747 * <p> 748 * This behavior can be bypassed by passing false to this method. 749 * 750 * @param rewrite 751 * whether to rewrite parents; defaults to true. 752 * @since 3.4 753 */ 754 public void setRewriteParents(boolean rewrite) { 755 rewriteParents = rewrite; 756 } 757 758 boolean getRewriteParents() { 759 return rewriteParents; 760 } 761 762 /** 763 * Should the body of a commit or tag be retained after parsing its headers? 764 * <p> 765 * Usually the body is always retained, but some application code might not 766 * care and would prefer to discard the body of a commit as early as 767 * possible, to reduce memory usage. 768 * <p> 769 * True by default on {@link org.eclipse.jgit.revwalk.RevWalk} and false by 770 * default for {@link org.eclipse.jgit.revwalk.ObjectWalk}. 771 * 772 * @return true if the body should be retained; false it is discarded. 773 */ 774 public boolean isRetainBody() { 775 return retainBody; 776 } 777 778 /** 779 * Set whether or not the body of a commit or tag is retained. 780 * <p> 781 * If a body of a commit or tag is not retained, the application must call 782 * {@link #parseBody(RevObject)} before the body can be safely accessed 783 * through the type specific access methods. 784 * <p> 785 * True by default on {@link org.eclipse.jgit.revwalk.RevWalk} and false by 786 * default for {@link org.eclipse.jgit.revwalk.ObjectWalk}. 787 * 788 * @param retain 789 * true to retain bodies; false to discard them early. 790 */ 791 public void setRetainBody(boolean retain) { 792 retainBody = retain; 793 } 794 795 /** 796 * @return whether only first-parent links should be followed when walking. 797 * 798 * @since 5.5 799 */ 800 public boolean isFirstParent() { 801 return firstParent; 802 } 803 804 /** 805 * Set whether or not only first parent links should be followed. 806 * <p> 807 * If set, second- and higher-parent links are not traversed at all. 808 * <p> 809 * This must be called prior to {@link #markStart(RevCommit)}. 810 * 811 * @param enable 812 * true to walk only first-parent links. 813 * 814 * @since 5.5 815 */ 816 public void setFirstParent(boolean enable) { 817 assertNotStarted(); 818 assertNoCommitsMarkedStart(); 819 firstParent = enable; 820 queue = new DateRevQueue(firstParent); 821 pending = new StartGenerator(this); 822 } 823 824 /** 825 * Locate a reference to a blob without loading it. 826 * <p> 827 * The blob may or may not exist in the repository. It is impossible to tell 828 * from this method's return value. 829 * 830 * @param id 831 * name of the blob object. 832 * @return reference to the blob object. Never null. 833 */ 834 @NonNull 835 public RevBlob lookupBlob(AnyObjectId id) { 836 RevBlob c = (RevBlob) objects.get(id); 837 if (c == null) { 838 c = new RevBlob(id); 839 objects.add(c); 840 } 841 return c; 842 } 843 844 /** 845 * Locate a reference to a tree without loading it. 846 * <p> 847 * The tree may or may not exist in the repository. It is impossible to tell 848 * from this method's return value. 849 * 850 * @param id 851 * name of the tree object. 852 * @return reference to the tree object. Never null. 853 */ 854 @NonNull 855 public RevTree lookupTree(AnyObjectId id) { 856 RevTree c = (RevTree) objects.get(id); 857 if (c == null) { 858 c = new RevTree(id); 859 objects.add(c); 860 } 861 return c; 862 } 863 864 /** 865 * Locate a reference to a commit without loading it. 866 * <p> 867 * The commit may or may not exist in the repository. It is impossible to 868 * tell from this method's return value. 869 * <p> 870 * See {@link #parseHeaders(RevObject)} and {@link #parseBody(RevObject)} 871 * for loading contents. 872 * 873 * @param id 874 * name of the commit object. 875 * @return reference to the commit object. Never null. 876 */ 877 @NonNull 878 public RevCommit lookupCommit(AnyObjectId id) { 879 RevCommit c = (RevCommit) objects.get(id); 880 if (c == null) { 881 c = createCommit(id); 882 objects.add(c); 883 } 884 return c; 885 } 886 887 /** 888 * Locate a reference to a tag without loading it. 889 * <p> 890 * The tag may or may not exist in the repository. It is impossible to tell 891 * from this method's return value. 892 * 893 * @param id 894 * name of the tag object. 895 * @return reference to the tag object. Never null. 896 */ 897 @NonNull 898 public RevTag lookupTag(AnyObjectId id) { 899 RevTag c = (RevTag) objects.get(id); 900 if (c == null) { 901 c = new RevTag(id); 902 objects.add(c); 903 } 904 return c; 905 } 906 907 /** 908 * Locate a reference to any object without loading it. 909 * <p> 910 * The object may or may not exist in the repository. It is impossible to 911 * tell from this method's return value. 912 * 913 * @param id 914 * name of the object. 915 * @param type 916 * type of the object. Must be a valid Git object type. 917 * @return reference to the object. Never null. 918 */ 919 @NonNull 920 public RevObject lookupAny(AnyObjectId id, int type) { 921 RevObject r = objects.get(id); 922 if (r == null) { 923 switch (type) { 924 case Constants.OBJ_COMMIT: 925 r = createCommit(id); 926 break; 927 case Constants.OBJ_TREE: 928 r = new RevTree(id); 929 break; 930 case Constants.OBJ_BLOB: 931 r = new RevBlob(id); 932 break; 933 case Constants.OBJ_TAG: 934 r = new RevTag(id); 935 break; 936 default: 937 throw new IllegalArgumentException(MessageFormat.format( 938 JGitText.get().invalidGitType, Integer.valueOf(type))); 939 } 940 objects.add(r); 941 } 942 return r; 943 } 944 945 /** 946 * Locate an object that was previously allocated in this walk. 947 * 948 * @param id 949 * name of the object. 950 * @return reference to the object if it has been previously located; 951 * otherwise null. 952 */ 953 public RevObject lookupOrNull(AnyObjectId id) { 954 return objects.get(id); 955 } 956 957 /** 958 * Locate a reference to a commit and immediately parse its content. 959 * <p> 960 * Unlike {@link #lookupCommit(AnyObjectId)} this method only returns 961 * successfully if the commit object exists, is verified to be a commit, and 962 * was parsed without error. 963 * 964 * @param id 965 * name of the commit object. 966 * @return reference to the commit object. Never null. 967 * @throws org.eclipse.jgit.errors.MissingObjectException 968 * the supplied commit does not exist. 969 * @throws org.eclipse.jgit.errors.IncorrectObjectTypeException 970 * the supplied id is not a commit or an annotated tag. 971 * @throws java.io.IOException 972 * a pack file or loose object could not be read. 973 */ 974 @NonNull 975 public RevCommit parseCommit(AnyObjectId id) 976 throws MissingObjectException, IncorrectObjectTypeException, 977 IOException { 978 RevObject c = peel(parseAny(id)); 979 if (!(c instanceof RevCommit)) 980 throw new IncorrectObjectTypeException(id.toObjectId(), 981 Constants.TYPE_COMMIT); 982 return (RevCommit) c; 983 } 984 985 /** 986 * Locate a reference to a tree. 987 * <p> 988 * This method only returns successfully if the tree object exists, is 989 * verified to be a tree. 990 * 991 * @param id 992 * name of the tree object, or a commit or annotated tag that may 993 * reference a tree. 994 * @return reference to the tree object. Never null. 995 * @throws org.eclipse.jgit.errors.MissingObjectException 996 * the supplied tree does not exist. 997 * @throws org.eclipse.jgit.errors.IncorrectObjectTypeException 998 * the supplied id is not a tree, a commit or an annotated tag. 999 * @throws java.io.IOException 1000 * a pack file or loose object could not be read. 1001 */ 1002 @NonNull 1003 public RevTree parseTree(AnyObjectId id) 1004 throws MissingObjectException, IncorrectObjectTypeException, 1005 IOException { 1006 RevObject c = peel(parseAny(id)); 1007 1008 final RevTree t; 1009 if (c instanceof RevCommit) 1010 t = ((RevCommit) c).getTree(); 1011 else if (!(c instanceof RevTree)) 1012 throw new IncorrectObjectTypeException(id.toObjectId(), 1013 Constants.TYPE_TREE); 1014 else 1015 t = (RevTree) c; 1016 parseHeaders(t); 1017 return t; 1018 } 1019 1020 /** 1021 * Locate a reference to an annotated tag and immediately parse its content. 1022 * <p> 1023 * Unlike {@link #lookupTag(AnyObjectId)} this method only returns 1024 * successfully if the tag object exists, is verified to be a tag, and was 1025 * parsed without error. 1026 * 1027 * @param id 1028 * name of the tag object. 1029 * @return reference to the tag object. Never null. 1030 * @throws org.eclipse.jgit.errors.MissingObjectException 1031 * the supplied tag does not exist. 1032 * @throws org.eclipse.jgit.errors.IncorrectObjectTypeException 1033 * the supplied id is not a tag or an annotated tag. 1034 * @throws java.io.IOException 1035 * a pack file or loose object could not be read. 1036 */ 1037 @NonNull 1038 public RevTag parseTag(AnyObjectId id) throws MissingObjectException, 1039 IncorrectObjectTypeException, IOException { 1040 RevObject c = parseAny(id); 1041 if (!(c instanceof RevTag)) 1042 throw new IncorrectObjectTypeException(id.toObjectId(), 1043 Constants.TYPE_TAG); 1044 return (RevTag) c; 1045 } 1046 1047 /** 1048 * Locate a reference to any object and immediately parse its headers. 1049 * <p> 1050 * This method only returns successfully if the object exists and was parsed 1051 * without error. Parsing an object can be expensive as the type must be 1052 * determined. For blobs this may mean the blob content was unpacked 1053 * unnecessarily, and thrown away. 1054 * 1055 * @param id 1056 * name of the object. 1057 * @return reference to the object. Never null. 1058 * @throws org.eclipse.jgit.errors.MissingObjectException 1059 * the supplied does not exist. 1060 * @throws java.io.IOException 1061 * a pack file or loose object could not be read. 1062 */ 1063 @NonNull 1064 public RevObject parseAny(AnyObjectId id) 1065 throws MissingObjectException, IOException { 1066 RevObject r = objects.get(id); 1067 if (r == null) 1068 r = parseNew(id, reader.open(id)); 1069 else 1070 parseHeaders(r); 1071 return r; 1072 } 1073 1074 private RevObject parseNew(AnyObjectId id, ObjectLoader ldr) 1075 throws LargeObjectException, CorruptObjectException, 1076 MissingObjectException, IOException { 1077 RevObject r; 1078 int type = ldr.getType(); 1079 switch (type) { 1080 case Constants.OBJ_COMMIT: { 1081 final RevCommit c = createCommit(id); 1082 c.parseCanonical(this, getCachedBytes(c, ldr)); 1083 r = c; 1084 break; 1085 } 1086 case Constants.OBJ_TREE: { 1087 r = new RevTree(id); 1088 r.flags |= PARSED; 1089 break; 1090 } 1091 case Constants.OBJ_BLOB: { 1092 r = new RevBlob(id); 1093 r.flags |= PARSED; 1094 break; 1095 } 1096 case Constants.OBJ_TAG: { 1097 final RevTag t = new RevTag(id); 1098 t.parseCanonical(this, getCachedBytes(t, ldr)); 1099 r = t; 1100 break; 1101 } 1102 default: 1103 throw new IllegalArgumentException(MessageFormat.format( 1104 JGitText.get().badObjectType, Integer.valueOf(type))); 1105 } 1106 objects.add(r); 1107 return r; 1108 } 1109 1110 byte[] getCachedBytes(RevObject obj) throws LargeObjectException, 1111 MissingObjectException, IncorrectObjectTypeException, IOException { 1112 return getCachedBytes(obj, reader.open(obj, obj.getType())); 1113 } 1114 1115 byte[] getCachedBytes(RevObject obj, ObjectLoader ldr) 1116 throws LargeObjectException, MissingObjectException, IOException { 1117 try { 1118 return ldr.getCachedBytes(5 * MB); 1119 } catch (LargeObjectException tooBig) { 1120 tooBig.setObjectId(obj); 1121 throw tooBig; 1122 } 1123 } 1124 1125 /** 1126 * Asynchronous object parsing. 1127 * 1128 * @param objectIds 1129 * objects to open from the object store. The supplied collection 1130 * must not be modified until the queue has finished. 1131 * @param reportMissing 1132 * if true missing objects are reported by calling failure with a 1133 * MissingObjectException. This may be more expensive for the 1134 * implementation to guarantee. If false the implementation may 1135 * choose to report MissingObjectException, or silently skip over 1136 * the object with no warning. 1137 * @return queue to read the objects from. 1138 */ 1139 public <T extends ObjectId> AsyncRevObjectQueue parseAny( 1140 Iterable<T> objectIds, boolean reportMissing) { 1141 List<T> need = new ArrayList<>(); 1142 List<RevObject> have = new ArrayList<>(); 1143 for (T id : objectIds) { 1144 RevObject r = objects.get(id); 1145 if (r != null && (r.flags & PARSED) != 0) 1146 have.add(r); 1147 else 1148 need.add(id); 1149 } 1150 1151 final Iterator<RevObject> objItr = have.iterator(); 1152 if (need.isEmpty()) { 1153 return new AsyncRevObjectQueue() { 1154 @Override 1155 public RevObject next() { 1156 return objItr.hasNext() ? objItr.next() : null; 1157 } 1158 1159 @Override 1160 public boolean cancel(boolean mayInterruptIfRunning) { 1161 return true; 1162 } 1163 1164 @Override 1165 public void release() { 1166 // In-memory only, no action required. 1167 } 1168 }; 1169 } 1170 1171 final AsyncObjectLoaderQueue<T> lItr = reader.open(need, reportMissing); 1172 return new AsyncRevObjectQueue() { 1173 @Override 1174 public RevObject next() throws MissingObjectException, 1175 IncorrectObjectTypeException, IOException { 1176 if (objItr.hasNext()) 1177 return objItr.next(); 1178 if (!lItr.next()) 1179 return null; 1180 1181 ObjectId id = lItr.getObjectId(); 1182 ObjectLoader ldr = lItr.open(); 1183 RevObject r = objects.get(id); 1184 if (r == null) 1185 r = parseNew(id, ldr); 1186 else if (r instanceof RevCommit) { 1187 byte[] raw = ldr.getCachedBytes(); 1188 ((RevCommit) r).parseCanonical(RevWalk.this, raw); 1189 } else if (r instanceof RevTag) { 1190 byte[] raw = ldr.getCachedBytes(); 1191 ((RevTag) r).parseCanonical(RevWalk.this, raw); 1192 } else 1193 r.flags |= PARSED; 1194 return r; 1195 } 1196 1197 @Override 1198 public boolean cancel(boolean mayInterruptIfRunning) { 1199 return lItr.cancel(mayInterruptIfRunning); 1200 } 1201 1202 @Override 1203 public void release() { 1204 lItr.release(); 1205 } 1206 }; 1207 } 1208 1209 /** 1210 * Ensure the object's critical headers have been parsed. 1211 * <p> 1212 * This method only returns successfully if the object exists and was parsed 1213 * without error. 1214 * 1215 * @param obj 1216 * the object the caller needs to be parsed. 1217 * @throws org.eclipse.jgit.errors.MissingObjectException 1218 * the supplied does not exist. 1219 * @throws java.io.IOException 1220 * a pack file or loose object could not be read. 1221 */ 1222 public void parseHeaders(RevObject obj) 1223 throws MissingObjectException, IOException { 1224 if ((obj.flags & PARSED) == 0) 1225 obj.parseHeaders(this); 1226 } 1227 1228 /** 1229 * Ensure the object's full body content is available. 1230 * <p> 1231 * This method only returns successfully if the object exists and was parsed 1232 * without error. 1233 * 1234 * @param obj 1235 * the object the caller needs to be parsed. 1236 * @throws org.eclipse.jgit.errors.MissingObjectException 1237 * the supplied does not exist. 1238 * @throws java.io.IOException 1239 * a pack file or loose object could not be read. 1240 */ 1241 public void parseBody(RevObject obj) 1242 throws MissingObjectException, IOException { 1243 obj.parseBody(this); 1244 } 1245 1246 /** 1247 * Peel back annotated tags until a non-tag object is found. 1248 * 1249 * @param obj 1250 * the starting object. 1251 * @return If {@code obj} is not an annotated tag, {@code obj}. Otherwise 1252 * the first non-tag object that {@code obj} references. The 1253 * returned object's headers have been parsed. 1254 * @throws org.eclipse.jgit.errors.MissingObjectException 1255 * a referenced object cannot be found. 1256 * @throws java.io.IOException 1257 * a pack file or loose object could not be read. 1258 */ 1259 public RevObject peel(RevObject obj) throws MissingObjectException, 1260 IOException { 1261 while (obj instanceof RevTag) { 1262 parseHeaders(obj); 1263 obj = ((RevTag) obj).getObject(); 1264 } 1265 parseHeaders(obj); 1266 return obj; 1267 } 1268 1269 /** 1270 * Create a new flag for application use during walking. 1271 * <p> 1272 * Applications are only assured to be able to create 24 unique flags on any 1273 * given revision walker instance. Any flags beyond 24 are offered only if 1274 * the implementation has extra free space within its internal storage. 1275 * 1276 * @param name 1277 * description of the flag, primarily useful for debugging. 1278 * @return newly constructed flag instance. 1279 * @throws java.lang.IllegalArgumentException 1280 * too many flags have been reserved on this revision walker. 1281 */ 1282 public RevFlag newFlag(String name) { 1283 final int m = allocFlag(); 1284 return new RevFlag(this, name, m); 1285 } 1286 1287 int allocFlag() { 1288 if (freeFlags == 0) 1289 throw new IllegalArgumentException(MessageFormat.format( 1290 JGitText.get().flagsAlreadyCreated, 1291 Integer.valueOf(32 - RESERVED_FLAGS))); 1292 final int m = Integer.lowestOneBit(freeFlags); 1293 freeFlags &= ~m; 1294 return m; 1295 } 1296 1297 /** 1298 * Automatically carry a flag from a child commit to its parents. 1299 * <p> 1300 * A carried flag is copied from the child commit onto its parents when the 1301 * child commit is popped from the lowest level of walk's internal graph. 1302 * 1303 * @param flag 1304 * the flag to carry onto parents, if set on a descendant. 1305 */ 1306 public void carry(RevFlag flag) { 1307 if ((freeFlags & flag.mask) != 0) 1308 throw new IllegalArgumentException(MessageFormat.format(JGitText.get().flagIsDisposed, flag.name)); 1309 if (flag.walker != this) 1310 throw new IllegalArgumentException(MessageFormat.format(JGitText.get().flagNotFromThis, flag.name)); 1311 carryFlags |= flag.mask; 1312 } 1313 1314 /** 1315 * Automatically carry flags from a child commit to its parents. 1316 * <p> 1317 * A carried flag is copied from the child commit onto its parents when the 1318 * child commit is popped from the lowest level of walk's internal graph. 1319 * 1320 * @param set 1321 * the flags to carry onto parents, if set on a descendant. 1322 */ 1323 public void carry(Collection<RevFlag> set) { 1324 for (RevFlag flag : set) 1325 carry(flag); 1326 } 1327 1328 /** 1329 * Preserve a RevFlag during all {@code reset} methods. 1330 * <p> 1331 * Calling {@code retainOnReset(flag)} avoids needing to pass the flag 1332 * during each {@code resetRetain()} invocation on this instance. 1333 * <p> 1334 * Clearing flags marked retainOnReset requires disposing of the flag with 1335 * {@code #disposeFlag(RevFlag)} or disposing of the entire RevWalk by 1336 * {@code #dispose()}. 1337 * 1338 * @param flag 1339 * the flag to retain during all resets. 1340 * @since 3.6 1341 */ 1342 public final void retainOnReset(RevFlag flag) { 1343 if ((freeFlags & flag.mask) != 0) 1344 throw new IllegalArgumentException(MessageFormat.format(JGitText.get().flagIsDisposed, flag.name)); 1345 if (flag.walker != this) 1346 throw new IllegalArgumentException(MessageFormat.format(JGitText.get().flagNotFromThis, flag.name)); 1347 retainOnReset |= flag.mask; 1348 } 1349 1350 /** 1351 * Preserve a set of RevFlags during all {@code reset} methods. 1352 * <p> 1353 * Calling {@code retainOnReset(set)} avoids needing to pass the flags 1354 * during each {@code resetRetain()} invocation on this instance. 1355 * <p> 1356 * Clearing flags marked retainOnReset requires disposing of the flag with 1357 * {@code #disposeFlag(RevFlag)} or disposing of the entire RevWalk by 1358 * {@code #dispose()}. 1359 * 1360 * @param flags 1361 * the flags to retain during all resets. 1362 * @since 3.6 1363 */ 1364 public final void retainOnReset(Collection<RevFlag> flags) { 1365 for (RevFlag f : flags) 1366 retainOnReset(f); 1367 } 1368 1369 /** 1370 * Allow a flag to be recycled for a different use. 1371 * <p> 1372 * Recycled flags always come back as a different Java object instance when 1373 * assigned again by {@link #newFlag(String)}. 1374 * <p> 1375 * If the flag was previously being carried, the carrying request is 1376 * removed. Disposing of a carried flag while a traversal is in progress has 1377 * an undefined behavior. 1378 * 1379 * @param flag 1380 * the to recycle. 1381 */ 1382 public void disposeFlag(RevFlag flag) { 1383 freeFlag(flag.mask); 1384 } 1385 1386 void freeFlag(int mask) { 1387 retainOnReset &= ~mask; 1388 if (isNotStarted()) { 1389 freeFlags |= mask; 1390 carryFlags &= ~mask; 1391 } else { 1392 delayFreeFlags |= mask; 1393 } 1394 } 1395 1396 private void finishDelayedFreeFlags() { 1397 if (delayFreeFlags != 0) { 1398 freeFlags |= delayFreeFlags; 1399 carryFlags &= ~delayFreeFlags; 1400 delayFreeFlags = 0; 1401 } 1402 } 1403 1404 /** 1405 * Resets internal state and allows this instance to be used again. 1406 * <p> 1407 * Unlike {@link #dispose()} previously acquired RevObject (and RevCommit) 1408 * instances are not invalidated. RevFlag instances are not invalidated, but 1409 * are removed from all RevObjects. 1410 */ 1411 public final void reset() { 1412 reset(0); 1413 } 1414 1415 /** 1416 * Resets internal state and allows this instance to be used again. 1417 * <p> 1418 * Unlike {@link #dispose()} previously acquired RevObject (and RevCommit) 1419 * instances are not invalidated. RevFlag instances are not invalidated, but 1420 * are removed from all RevObjects. 1421 * 1422 * @param retainFlags 1423 * application flags that should <b>not</b> be cleared from 1424 * existing commit objects. 1425 */ 1426 public final void resetRetain(RevFlagSet retainFlags) { 1427 reset(retainFlags.mask); 1428 } 1429 1430 /** 1431 * Resets internal state and allows this instance to be used again. 1432 * <p> 1433 * Unlike {@link #dispose()} previously acquired RevObject (and RevCommit) 1434 * instances are not invalidated. RevFlag instances are not invalidated, but 1435 * are removed from all RevObjects. 1436 * <p> 1437 * See {@link #retainOnReset(RevFlag)} for an alternative that does not 1438 * require passing the flags during each reset. 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(RevFlag... retainFlags) { 1445 int mask = 0; 1446 for (RevFlag flag : retainFlags) 1447 mask |= flag.mask; 1448 reset(mask); 1449 } 1450 1451 /** 1452 * Resets internal state and allows this instance to be used again. 1453 * <p> 1454 * Unlike {@link #dispose()} previously acquired RevObject (and RevCommit) 1455 * instances are not invalidated. RevFlag instances are not invalidated, but 1456 * are removed from all RevObjects. The value of {@code firstParent} is 1457 * retained. 1458 * 1459 * @param retainFlags 1460 * application flags that should <b>not</b> be cleared from 1461 * existing commit objects. 1462 */ 1463 protected void reset(int retainFlags) { 1464 finishDelayedFreeFlags(); 1465 retainFlags |= PARSED | retainOnReset; 1466 final int clearFlags = ~retainFlags; 1467 1468 final FIFORevQueue q = new FIFORevQueue(); 1469 for (RevCommit c : roots) { 1470 if ((c.flags & clearFlags) == 0) 1471 continue; 1472 c.flags &= retainFlags; 1473 c.reset(); 1474 q.add(c); 1475 } 1476 1477 for (;;) { 1478 final RevCommit c = q.next(); 1479 if (c == null) 1480 break; 1481 if (c.parents == null) 1482 continue; 1483 for (RevCommit p : c.parents) { 1484 if ((p.flags & clearFlags) == 0) 1485 continue; 1486 p.flags &= retainFlags; 1487 p.reset(); 1488 q.add(p); 1489 } 1490 } 1491 1492 roots.clear(); 1493 queue = new DateRevQueue(firstParent); 1494 pending = new StartGenerator(this); 1495 } 1496 1497 /** 1498 * Dispose all internal state and invalidate all RevObject instances. 1499 * <p> 1500 * All RevObject (and thus RevCommit, etc.) instances previously acquired 1501 * from this RevWalk are invalidated by a dispose call. Applications must 1502 * not retain or use RevObject instances obtained prior to the dispose call. 1503 * All RevFlag instances are also invalidated, and must not be reused. 1504 */ 1505 public void dispose() { 1506 reader.close(); 1507 freeFlags = APP_FLAGS; 1508 delayFreeFlags = 0; 1509 retainOnReset = 0; 1510 carryFlags = UNINTERESTING; 1511 firstParent = false; 1512 objects.clear(); 1513 roots.clear(); 1514 queue = new DateRevQueue(firstParent); 1515 pending = new StartGenerator(this); 1516 shallowCommitsInitialized = false; 1517 } 1518 1519 /** 1520 * Like {@link #next()}, but if a checked exception is thrown during the 1521 * walk it is rethrown as a {@link RevWalkException}. 1522 * 1523 * @throws RevWalkException if an {@link IOException} was thrown. 1524 * @return next most recent commit; null if traversal is over. 1525 */ 1526 @Nullable 1527 private RevCommit nextForIterator() { 1528 try { 1529 return next(); 1530 } catch (IOException e) { 1531 throw new RevWalkException(e); 1532 } 1533 } 1534 1535 /** 1536 * {@inheritDoc} 1537 * <p> 1538 * Returns an Iterator over the commits of this walker. 1539 * <p> 1540 * The returned iterator is only useful for one walk. If this RevWalk gets 1541 * reset a new iterator must be obtained to walk over the new results. 1542 * <p> 1543 * Applications must not use both the Iterator and the {@link #next()} API 1544 * at the same time. Pick one API and use that for the entire walk. 1545 * <p> 1546 * If a checked exception is thrown during the walk (see {@link #next()}) it 1547 * is rethrown from the Iterator as a {@link RevWalkException}. 1548 * 1549 * @see RevWalkException 1550 */ 1551 @Override 1552 public Iterator<RevCommit> iterator() { 1553 RevCommit first = nextForIterator(); 1554 1555 return new Iterator<RevCommit>() { 1556 RevCommit next = first; 1557 1558 @Override 1559 public boolean hasNext() { 1560 return next != null; 1561 } 1562 1563 @Override 1564 public RevCommit next() { 1565 RevCommit r = next; 1566 next = nextForIterator(); 1567 return r; 1568 } 1569 1570 @Override 1571 public void remove() { 1572 throw new UnsupportedOperationException(); 1573 } 1574 }; 1575 } 1576 1577 /** 1578 * Throws an exception if we have started producing output. 1579 */ 1580 protected void assertNotStarted() { 1581 if (isNotStarted()) 1582 return; 1583 throw new IllegalStateException(JGitText.get().outputHasAlreadyBeenStarted); 1584 } 1585 1586 /** 1587 * Throws an exception if any commits have been marked as start. 1588 * <p> 1589 * If {@link #markStart(RevCommit)} has already been called, 1590 * {@link #reset()} can be called to satisfy this condition. 1591 * 1592 * @since 5.5 1593 */ 1594 protected void assertNoCommitsMarkedStart() { 1595 if (roots.isEmpty()) 1596 return; 1597 throw new IllegalStateException( 1598 JGitText.get().commitsHaveAlreadyBeenMarkedAsStart); 1599 } 1600 1601 private boolean isNotStarted() { 1602 return pending instanceof StartGenerator; 1603 } 1604 1605 /** 1606 * Create and return an {@link org.eclipse.jgit.revwalk.ObjectWalk} using 1607 * the same objects. 1608 * <p> 1609 * Prior to using this method, the caller must reset this RevWalk to clean 1610 * any flags that were used during the last traversal. 1611 * <p> 1612 * The returned ObjectWalk uses the same ObjectReader, internal object pool, 1613 * and free RevFlags. Once the ObjectWalk is created, this RevWalk should 1614 * not be used anymore. 1615 * 1616 * @return a new walk, using the exact same object pool. 1617 */ 1618 public ObjectWalk toObjectWalkWithSameObjects() { 1619 ObjectWalk ow = new ObjectWalk(reader); 1620 RevWalk rw = ow; 1621 rw.objects = objects; 1622 rw.freeFlags = freeFlags; 1623 return ow; 1624 } 1625 1626 /** 1627 * Construct a new unparsed commit for the given object. 1628 * 1629 * @param id 1630 * the object this walker requires a commit reference for. 1631 * @return a new unparsed reference for the object. 1632 */ 1633 protected RevCommit createCommit(AnyObjectId id) { 1634 return new RevCommit(id); 1635 } 1636 1637 void carryFlagsImpl(RevCommit c) { 1638 final int carry = c.flags & carryFlags; 1639 if (carry != 0) 1640 RevCommit.carryFlags(c, carry); 1641 } 1642 1643 /** 1644 * Assume additional commits are shallow (have no parents). 1645 * <p> 1646 * This method is a No-op if the collection is empty. 1647 * 1648 * @param ids 1649 * commits that should be treated as shallow commits, in addition 1650 * to any commits already known to be shallow by the repository. 1651 * @since 3.3 1652 */ 1653 public void assumeShallow(Collection<? extends ObjectId> ids) { 1654 for (ObjectId id : ids) 1655 lookupCommit(id).parents = RevCommit.NO_PARENTS; 1656 } 1657 1658 /** 1659 * Reads the "shallow" file and applies it by setting the parents of shallow 1660 * commits to an empty array. 1661 * <p> 1662 * There is a sequencing problem if the first commit being parsed is a 1663 * shallow commit, since {@link RevCommit#parseCanonical(RevWalk, byte[])} 1664 * calls this method before its callers add the new commit to the 1665 * {@link RevWalk#objects} map. That means a call from this method to 1666 * {@link #lookupCommit(AnyObjectId)} fails to find that commit and creates 1667 * a new one, which is promptly discarded. 1668 * <p> 1669 * To avoid that, {@link RevCommit#parseCanonical(RevWalk, byte[])} passes 1670 * its commit to this method, so that this method can apply the shallow 1671 * state to it directly and avoid creating the duplicate commit object. 1672 * 1673 * @param rc 1674 * the initial commit being parsed 1675 * @throws IOException 1676 * if the shallow commits file can't be read 1677 */ 1678 void initializeShallowCommits(RevCommit rc) throws IOException { 1679 if (shallowCommitsInitialized) { 1680 throw new IllegalStateException( 1681 JGitText.get().shallowCommitsAlreadyInitialized); 1682 } 1683 1684 shallowCommitsInitialized = true; 1685 1686 if (reader == null) { 1687 return; 1688 } 1689 1690 for (ObjectId id : reader.getShallowCommits()) { 1691 if (id.equals(rc.getId())) { 1692 rc.parents = RevCommit.NO_PARENTS; 1693 } else { 1694 lookupCommit(id).parents = RevCommit.NO_PARENTS; 1695 } 1696 } 1697 } 1698 }