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