1 /* 2 * Copyright (C) 2008, 2009 Google Inc. 3 * Copyright (C) 2008, 2022 Shawn O. Pearce <spearce@spearce.org> and others 4 * 5 * This program and the accompanying materials are made available under the 6 * terms of the Eclipse Distribution License v. 1.0 which is available at 7 * https://www.eclipse.org/org/documents/edl-v10.php. 8 * 9 * SPDX-License-Identifier: BSD-3-Clause 10 */ 11 12 package org.eclipse.jgit.treewalk; 13 14 import static java.nio.charset.StandardCharsets.UTF_8; 15 16 import java.io.IOException; 17 import java.util.Arrays; 18 import java.util.HashMap; 19 import java.util.Map; 20 import java.util.Set; 21 import java.util.regex.Matcher; 22 23 import org.eclipse.jgit.annotations.Nullable; 24 import org.eclipse.jgit.api.errors.JGitInternalException; 25 import org.eclipse.jgit.attributes.Attribute; 26 import org.eclipse.jgit.attributes.Attributes; 27 import org.eclipse.jgit.attributes.AttributesHandler; 28 import org.eclipse.jgit.attributes.AttributesNodeProvider; 29 import org.eclipse.jgit.attributes.AttributesProvider; 30 import org.eclipse.jgit.attributes.FilterCommandRegistry; 31 import org.eclipse.jgit.dircache.DirCacheBuildIterator; 32 import org.eclipse.jgit.dircache.DirCacheIterator; 33 import org.eclipse.jgit.errors.CorruptObjectException; 34 import org.eclipse.jgit.errors.IncorrectObjectTypeException; 35 import org.eclipse.jgit.errors.MissingObjectException; 36 import org.eclipse.jgit.errors.StopWalkException; 37 import org.eclipse.jgit.lib.AnyObjectId; 38 import org.eclipse.jgit.lib.Config; 39 import org.eclipse.jgit.lib.ConfigConstants; 40 import org.eclipse.jgit.lib.Constants; 41 import org.eclipse.jgit.lib.CoreConfig.EolStreamType; 42 import org.eclipse.jgit.lib.FileMode; 43 import org.eclipse.jgit.lib.MutableObjectId; 44 import org.eclipse.jgit.lib.ObjectId; 45 import org.eclipse.jgit.lib.ObjectReader; 46 import org.eclipse.jgit.lib.Repository; 47 import org.eclipse.jgit.revwalk.RevTree; 48 import org.eclipse.jgit.treewalk.filter.PathFilter; 49 import org.eclipse.jgit.treewalk.filter.TreeFilter; 50 import org.eclipse.jgit.util.QuotedString; 51 import org.eclipse.jgit.util.RawParseUtils; 52 import org.eclipse.jgit.util.io.EolStreamTypeUtil; 53 54 /** 55 * Walks one or more {@link org.eclipse.jgit.treewalk.AbstractTreeIterator}s in 56 * parallel. 57 * <p> 58 * This class can perform n-way differences across as many trees as necessary. 59 * <p> 60 * Each tree added must have the same root as existing trees in the walk. 61 * <p> 62 * A TreeWalk instance can only be used once to generate results. Running a 63 * second time requires creating a new TreeWalk instance, or invoking 64 * {@link #reset()} and adding new trees before starting again. Resetting an 65 * existing instance may be faster for some applications as some internal 66 * buffers may be recycled. 67 * <p> 68 * TreeWalk instances are not thread-safe. Applications must either restrict 69 * usage of a TreeWalk instance to a single thread, or implement their own 70 * synchronization at a higher level. 71 * <p> 72 * Multiple simultaneous TreeWalk instances per 73 * {@link org.eclipse.jgit.lib.Repository} are permitted, even from concurrent 74 * threads. 75 */ 76 public class TreeWalk implements AutoCloseable, AttributesProvider { 77 78 private static final AbstractTreeIterator[] NO_TREES = {}; 79 80 /** 81 * @since 4.2 82 */ 83 public enum OperationType { 84 /** 85 * Represents a checkout operation (for example a checkout or reset 86 * operation). 87 */ 88 CHECKOUT_OP, 89 90 /** 91 * Represents a checkin operation (for example an add operation) 92 */ 93 CHECKIN_OP 94 } 95 96 /** 97 * Type of operation you want to retrieve the git attributes for. 98 */ 99 private OperationType operationType = OperationType.CHECKOUT_OP; 100 101 /** 102 * The filter command as defined in gitattributes. The keys are 103 * filterName+"."+filterCommandType. E.g. "lfs.clean" 104 */ 105 private Map<String, String> filterCommandsByNameDotType = new HashMap<>(); 106 107 /** 108 * Set the operation type of this walk 109 * 110 * @param operationType 111 * a {@link org.eclipse.jgit.treewalk.TreeWalk.OperationType} 112 * object. 113 * @since 4.2 114 */ 115 public void setOperationType(OperationType operationType) { 116 this.operationType = operationType; 117 } 118 119 /** 120 * Open a tree walk and filter to exactly one path. 121 * <p> 122 * The returned tree walk is already positioned on the requested path, so 123 * the caller should not need to invoke {@link #next()} unless they are 124 * looking for a possible directory/file name conflict. 125 * 126 * @param reader 127 * the reader the walker will obtain tree data from. 128 * @param path 129 * single path to advance the tree walk instance into. 130 * @param trees 131 * one or more trees to walk through, all with the same root. 132 * @return a new tree walk configured for exactly this one path; null if no 133 * path was found in any of the trees. 134 * @throws java.io.IOException 135 * reading a pack file or loose object failed. 136 * @throws org.eclipse.jgit.errors.CorruptObjectException 137 * an tree object could not be read as its data stream did not 138 * appear to be a tree, or could not be inflated. 139 * @throws org.eclipse.jgit.errors.IncorrectObjectTypeException 140 * an object we expected to be a tree was not a tree. 141 * @throws org.eclipse.jgit.errors.MissingObjectException 142 * a tree object was not found. 143 */ 144 public static TreeWalk forPath(final ObjectReader reader, final String path, 145 final AnyObjectId... trees) throws MissingObjectException, 146 IncorrectObjectTypeException, CorruptObjectException, IOException { 147 return forPath(null, reader, path, trees); 148 } 149 150 /** 151 * Open a tree walk and filter to exactly one path. 152 * <p> 153 * The returned tree walk is already positioned on the requested path, so 154 * the caller should not need to invoke {@link #next()} unless they are 155 * looking for a possible directory/file name conflict. 156 * 157 * @param repo 158 * repository to read config data and 159 * {@link org.eclipse.jgit.attributes.AttributesNodeProvider} 160 * from. 161 * @param reader 162 * the reader the walker will obtain tree data from. 163 * @param path 164 * single path to advance the tree walk instance into. 165 * @param trees 166 * one or more trees to walk through, all with the same root. 167 * @return a new tree walk configured for exactly this one path; null if no 168 * path was found in any of the trees. 169 * @throws java.io.IOException 170 * reading a pack file or loose object failed. 171 * @throws org.eclipse.jgit.errors.CorruptObjectException 172 * an tree object could not be read as its data stream did not 173 * appear to be a tree, or could not be inflated. 174 * @throws org.eclipse.jgit.errors.IncorrectObjectTypeException 175 * an object we expected to be a tree was not a tree. 176 * @throws org.eclipse.jgit.errors.MissingObjectException 177 * a tree object was not found. 178 * @since 4.3 179 */ 180 public static TreeWalk forPath(final @Nullable Repository repo, 181 final ObjectReader reader, final String path, 182 final AnyObjectId... trees) 183 throws MissingObjectException, IncorrectObjectTypeException, 184 CorruptObjectException, IOException { 185 TreeWalk tw = new TreeWalk(repo, reader); 186 PathFilter f = PathFilter.create(path); 187 tw.setFilter(f); 188 tw.reset(trees); 189 tw.setRecursive(false); 190 191 while (tw.next()) { 192 if (f.isDone(tw)) { 193 return tw; 194 } else if (tw.isSubtree()) { 195 tw.enterSubtree(); 196 } 197 } 198 return null; 199 } 200 201 /** 202 * Open a tree walk and filter to exactly one path. 203 * <p> 204 * The returned tree walk is already positioned on the requested path, so 205 * the caller should not need to invoke {@link #next()} unless they are 206 * looking for a possible directory/file name conflict. 207 * 208 * @param db 209 * repository to read tree object data from. 210 * @param path 211 * single path to advance the tree walk instance into. 212 * @param trees 213 * one or more trees to walk through, all with the same root. 214 * @return a new tree walk configured for exactly this one path; null if no 215 * path was found in any of the trees. 216 * @throws java.io.IOException 217 * reading a pack file or loose object failed. 218 * @throws org.eclipse.jgit.errors.CorruptObjectException 219 * an tree object could not be read as its data stream did not 220 * appear to be a tree, or could not be inflated. 221 * @throws org.eclipse.jgit.errors.IncorrectObjectTypeException 222 * an object we expected to be a tree was not a tree. 223 * @throws org.eclipse.jgit.errors.MissingObjectException 224 * a tree object was not found. 225 */ 226 public static TreeWalk forPath(final Repository db, final String path, 227 final AnyObjectId... trees) throws MissingObjectException, 228 IncorrectObjectTypeException, CorruptObjectException, IOException { 229 try (ObjectReader reader = db.newObjectReader()) { 230 return forPath(db, reader, path, trees); 231 } 232 } 233 234 /** 235 * Open a tree walk and filter to exactly one path. 236 * <p> 237 * The returned tree walk is already positioned on the requested path, so 238 * the caller should not need to invoke {@link #next()} unless they are 239 * looking for a possible directory/file name conflict. 240 * 241 * @param db 242 * repository to read tree object data from. 243 * @param path 244 * single path to advance the tree walk instance into. 245 * @param tree 246 * the single tree to walk through. 247 * @return a new tree walk configured for exactly this one path; null if no 248 * path was found in any of the trees. 249 * @throws java.io.IOException 250 * reading a pack file or loose object failed. 251 * @throws org.eclipse.jgit.errors.CorruptObjectException 252 * an tree object could not be read as its data stream did not 253 * appear to be a tree, or could not be inflated. 254 * @throws org.eclipse.jgit.errors.IncorrectObjectTypeException 255 * an object we expected to be a tree was not a tree. 256 * @throws org.eclipse.jgit.errors.MissingObjectException 257 * a tree object was not found. 258 */ 259 public static TreeWalk forPath(final Repository db, final String path, 260 final RevTree tree) throws MissingObjectException, 261 IncorrectObjectTypeException, CorruptObjectException, IOException { 262 return forPath(db, path, new ObjectId[] { tree }); 263 } 264 265 private final ObjectReader reader; 266 267 private final boolean closeReader; 268 269 private final MutableObjectId idBuffer = new MutableObjectId(); 270 271 private TreeFilter filter; 272 273 AbstractTreeIterator[] trees; 274 275 private boolean recursive; 276 277 private boolean postOrderTraversal; 278 279 int depth; 280 281 private boolean advance; 282 283 private boolean postChildren; 284 285 private AttributesNodeProvider attributesNodeProvider; 286 287 AbstractTreeIterator currentHead; 288 289 /** 290 * Cached attributes for the current entry; per tree. Index i+1 is for tree 291 * i; index 0 is for the deprecated legacy behavior. 292 */ 293 private Attributes[] attrs; 294 295 /** 296 * Cached attributes handler; per tree. Index i+1 is for tree i; index 0 is 297 * for the deprecated legacy behavior. 298 */ 299 private AttributesHandler[] attributesHandlers; 300 301 /** Can be set to identify the tree to use for {@link #getAttributes()}. */ 302 private int headIndex = -1; 303 304 private Config config; 305 306 private Set<String> filterCommands; 307 308 /** 309 * Create a new tree walker for a given repository. 310 * 311 * @param repo 312 * the repository the walker will obtain data from. An 313 * ObjectReader will be created by the walker, and will be closed 314 * when the walker is closed. 315 */ 316 public TreeWalk(Repository repo) { 317 this(repo, repo.newObjectReader(), true); 318 } 319 320 /** 321 * Create a new tree walker for a given repository. 322 * 323 * @param repo 324 * the repository the walker will obtain data from. An 325 * ObjectReader will be created by the walker, and will be closed 326 * when the walker is closed. 327 * @param or 328 * the reader the walker will obtain tree data from. The reader 329 * is not closed when the walker is closed. 330 * @since 4.3 331 */ 332 public TreeWalk(@Nullable Repository repo, ObjectReader or) { 333 this(repo, or, false); 334 } 335 336 /** 337 * Create a new tree walker for a given repository. 338 * 339 * @param or 340 * the reader the walker will obtain tree data from. The reader 341 * is not closed when the walker is closed. 342 */ 343 public TreeWalk(ObjectReader or) { 344 this(null, or, false); 345 } 346 347 private TreeWalk(final @Nullable Repository repo, final ObjectReader or, 348 final boolean closeReader) { 349 if (repo != null) { 350 config = repo.getConfig(); 351 attributesNodeProvider = repo.createAttributesNodeProvider(); 352 filterCommands = FilterCommandRegistry 353 .getRegisteredFilterCommands(); 354 } else { 355 config = null; 356 attributesNodeProvider = null; 357 } 358 reader = or; 359 filter = TreeFilter.ALL; 360 trees = NO_TREES; 361 this.closeReader = closeReader; 362 } 363 364 /** 365 * Get the reader this walker is using to load objects. 366 * 367 * @return the reader this walker is using to load objects. 368 */ 369 public ObjectReader getObjectReader() { 370 return reader; 371 } 372 373 /** 374 * Get the operation type 375 * 376 * @return the {@link org.eclipse.jgit.treewalk.TreeWalk.OperationType} 377 * @since 4.3 378 */ 379 public OperationType getOperationType() { 380 return operationType; 381 } 382 383 /** 384 * {@inheritDoc} 385 * <p> 386 * Release any resources used by this walker's reader. 387 * <p> 388 * A walker that has been released can be used again, but may need to be 389 * released after the subsequent usage. 390 * 391 * @since 4.0 392 */ 393 @Override 394 public void close() { 395 if (closeReader) { 396 reader.close(); 397 } 398 } 399 400 /** 401 * Get the currently configured filter. 402 * 403 * @return the current filter. Never null as a filter is always needed. 404 */ 405 public TreeFilter getFilter() { 406 return filter; 407 } 408 409 /** 410 * Set the tree entry filter for this walker. 411 * <p> 412 * Multiple filters may be combined by constructing an arbitrary tree of 413 * <code>AndTreeFilter</code> or <code>OrTreeFilter</code> instances to 414 * describe the boolean expression required by the application. Custom 415 * filter implementations may also be constructed by applications. 416 * <p> 417 * Note that filters are not thread-safe and may not be shared by concurrent 418 * TreeWalk instances. Every TreeWalk must be supplied its own unique 419 * filter, unless the filter implementation specifically states it is (and 420 * always will be) thread-safe. Callers may use 421 * {@link org.eclipse.jgit.treewalk.filter.TreeFilter#clone()} to create a 422 * unique filter tree for this TreeWalk instance. 423 * 424 * @param newFilter 425 * the new filter. If null the special 426 * {@link org.eclipse.jgit.treewalk.filter.TreeFilter#ALL} filter 427 * will be used instead, as it matches every entry. 428 * @see org.eclipse.jgit.treewalk.filter.AndTreeFilter 429 * @see org.eclipse.jgit.treewalk.filter.OrTreeFilter 430 */ 431 public void setFilter(TreeFilter newFilter) { 432 filter = newFilter != null ? newFilter : TreeFilter.ALL; 433 } 434 435 /** 436 * Is this walker automatically entering into subtrees? 437 * <p> 438 * If the walker is recursive then the caller will not see a subtree node 439 * and instead will only receive file nodes in all relevant subtrees. 440 * 441 * @return true if automatically entering subtrees is enabled. 442 */ 443 public boolean isRecursive() { 444 return recursive; 445 } 446 447 /** 448 * Set the walker to enter (or not enter) subtrees automatically. 449 * <p> 450 * If recursive mode is enabled the walker will hide subtree nodes from the 451 * calling application and will produce only file level nodes. If a tree 452 * (directory) is deleted then all of the file level nodes will appear to be 453 * deleted, recursively, through as many levels as necessary to account for 454 * all entries. 455 * 456 * @param b 457 * true to skip subtree nodes and only obtain files nodes. 458 */ 459 public void setRecursive(boolean b) { 460 recursive = b; 461 } 462 463 /** 464 * Does this walker return a tree entry after it exits the subtree? 465 * <p> 466 * If post order traversal is enabled then the walker will return a subtree 467 * after it has returned the last entry within that subtree. This may cause 468 * a subtree to be seen by the application twice if {@link #isRecursive()} 469 * is false, as the application will see it once, call 470 * {@link #enterSubtree()}, and then see it again as it leaves the subtree. 471 * <p> 472 * If an application does not enable {@link #isRecursive()} and it does not 473 * call {@link #enterSubtree()} then the tree is returned only once as none 474 * of the children were processed. 475 * 476 * @return true if subtrees are returned after entries within the subtree. 477 */ 478 public boolean isPostOrderTraversal() { 479 return postOrderTraversal; 480 } 481 482 /** 483 * Set the walker to return trees after their children. 484 * 485 * @param b 486 * true to get trees after their children. 487 * @see #isPostOrderTraversal() 488 */ 489 public void setPostOrderTraversal(boolean b) { 490 postOrderTraversal = b; 491 } 492 493 /** 494 * Sets the {@link org.eclipse.jgit.attributes.AttributesNodeProvider} for 495 * this {@link org.eclipse.jgit.treewalk.TreeWalk}. 496 * <p> 497 * This is a requirement for a correct computation of the git attributes. If 498 * this {@link org.eclipse.jgit.treewalk.TreeWalk} has been built using 499 * {@link #TreeWalk(Repository)} constructor, the 500 * {@link org.eclipse.jgit.attributes.AttributesNodeProvider} has already 501 * been set. Indeed,the {@link org.eclipse.jgit.lib.Repository} can provide 502 * an {@link org.eclipse.jgit.attributes.AttributesNodeProvider} using 503 * {@link org.eclipse.jgit.lib.Repository#createAttributesNodeProvider()} 504 * method. Otherwise you should provide one. 505 * </p> 506 * 507 * @see Repository#createAttributesNodeProvider() 508 * @param provider 509 * a {@link org.eclipse.jgit.attributes.AttributesNodeProvider} 510 * object. 511 * @since 4.2 512 */ 513 public void setAttributesNodeProvider(AttributesNodeProvider provider) { 514 attributesNodeProvider = provider; 515 } 516 517 /** 518 * Get the attributes node provider 519 * 520 * @return the {@link org.eclipse.jgit.attributes.AttributesNodeProvider} 521 * for this {@link org.eclipse.jgit.treewalk.TreeWalk}. 522 * @since 4.3 523 */ 524 public AttributesNodeProvider getAttributesNodeProvider() { 525 return attributesNodeProvider; 526 } 527 528 /** 529 * Identifies the tree at the given index as the head tree. This is the tree 530 * use by default to determine attributes and EOL modes. 531 * 532 * @param index 533 * of the tree to use as head 534 * @throws IllegalArgumentException 535 * if the index is out of range 536 * @since 6.1 537 */ 538 public void setHead(int index) { 539 if (index < 0 || index >= trees.length) { 540 throw new IllegalArgumentException("Head index " + index //$NON-NLS-1$ 541 + " out of range [0," + trees.length + ')'); //$NON-NLS-1$ 542 } 543 headIndex = index; 544 } 545 546 /** 547 * {@inheritDoc} 548 * <p> 549 * Retrieve the git attributes for the current entry. 550 * 551 * <h3>Git attribute computation</h3> 552 * 553 * <ul> 554 * <li>Get the attributes matching the current path entry from the info file 555 * (see {@link AttributesNodeProvider#getInfoAttributesNode()}).</li> 556 * <li>Completes the list of attributes using the .gitattributes files 557 * located on the current path (the further the directory that contains 558 * .gitattributes is from the path in question, the lower its precedence). 559 * For a checkin operation, it will look first on the working tree (if any). 560 * If there is no attributes file, it will fallback on the index. For a 561 * checkout operation, it will first use the index entry and then fallback 562 * on the working tree if none.</li> 563 * <li>In the end, completes the list of matching attributes using the 564 * global attribute file define in the configuration (see 565 * {@link AttributesNodeProvider#getGlobalAttributesNode()})</li> 566 * 567 * </ul> 568 * 569 * 570 * <h3>Iterator constraints</h3> 571 * 572 * <p> 573 * In order to have a correct list of attributes for the current entry, this 574 * {@link TreeWalk} requires to have at least one 575 * {@link AttributesNodeProvider} and a {@link DirCacheIterator} set up. An 576 * {@link AttributesNodeProvider} is used to retrieve the attributes from 577 * the info attributes file and the global attributes file. The 578 * {@link DirCacheIterator} is used to retrieve the .gitattributes files 579 * stored in the index. A {@link WorkingTreeIterator} can also be provided 580 * to access the local version of the .gitattributes files. If none is 581 * provided it will fallback on the {@link DirCacheIterator}. 582 * </p> 583 * 584 * @since 4.2 585 */ 586 @Override 587 public Attributes getAttributes() { 588 return getAttributes(headIndex); 589 } 590 591 /** 592 * Retrieves the git attributes based on the given tree. 593 * 594 * @param index 595 * of the tree to use as base for the attributes 596 * @return the attributes 597 * @since 6.1 598 */ 599 public Attributes getAttributes(int index) { 600 int attrIndex = index + 1; 601 Attributes result = attrs[attrIndex]; 602 if (result != null) { 603 return result; 604 } 605 if (attributesNodeProvider == null) { 606 throw new IllegalStateException( 607 "The tree walk should have one AttributesNodeProvider set in order to compute the git attributes."); //$NON-NLS-1$ 608 } 609 610 try { 611 AttributesHandler handler = attributesHandlers[attrIndex]; 612 if (handler == null) { 613 if (index < 0) { 614 // Legacy behavior (headIndex not set, getAttributes() above 615 // called) 616 handler = new AttributesHandler(this, () -> { 617 return getTree(CanonicalTreeParser.class); 618 }); 619 } else { 620 handler = new AttributesHandler(this, () -> { 621 AbstractTreeIterator tree = trees[index]; 622 if (tree instanceof CanonicalTreeParser) { 623 return (CanonicalTreeParser) tree; 624 } 625 return null; 626 }); 627 } 628 attributesHandlers[attrIndex] = handler; 629 } 630 result = handler.getAttributes(); 631 attrs[attrIndex] = result; 632 return result; 633 } catch (IOException e) { 634 throw new JGitInternalException("Error while parsing attributes", //$NON-NLS-1$ 635 e); 636 } 637 } 638 639 /** 640 * Get the EOL stream type of the current entry using the config and 641 * {@link #getAttributes()}. 642 * 643 * @param opType 644 * the operationtype (checkin/checkout) which should be used 645 * @return the EOL stream type of the current entry using the config and 646 * {@link #getAttributes()}. Note that this method may return null 647 * if the {@link org.eclipse.jgit.treewalk.TreeWalk} is not based on 648 * a working tree 649 * @since 4.10 650 */ 651 @Nullable 652 public EolStreamType getEolStreamType(OperationType opType) { 653 if (attributesNodeProvider == null || config == null) { 654 return null; 655 } 656 OperationType op = opType != null ? opType : operationType; 657 return EolStreamTypeUtil.detectStreamType(op, 658 config.get(WorkingTreeOptions.KEY), getAttributes()); 659 } 660 661 /** 662 * Get the EOL stream type of the current entry for checking out using the 663 * config and {@link #getAttributes()}. 664 * 665 * @param tree 666 * index of the tree the check-out is to be from 667 * @return the EOL stream type of the current entry using the config and 668 * {@link #getAttributes()}. Note that this method may return null 669 * if the {@link org.eclipse.jgit.treewalk.TreeWalk} is not based on 670 * a working tree 671 * @since 6.1 672 */ 673 @Nullable 674 public EolStreamType getCheckoutEolStreamType(int tree) { 675 if (attributesNodeProvider == null || config == null) { 676 return null; 677 } 678 Attributes attr = getAttributes(tree); 679 return EolStreamTypeUtil.detectStreamType(OperationType.CHECKOUT_OP, 680 config.get(WorkingTreeOptions.KEY), attr); 681 } 682 683 /** 684 * Reset this walker so new tree iterators can be added to it. 685 */ 686 public void reset() { 687 attrs = null; 688 attributesHandlers = null; 689 headIndex = -1; 690 trees = NO_TREES; 691 advance = false; 692 depth = 0; 693 } 694 695 /** 696 * Reset this walker to run over a single existing tree. 697 * 698 * @param id 699 * the tree we need to parse. The walker will execute over this 700 * single tree if the reset is successful. 701 * @throws org.eclipse.jgit.errors.MissingObjectException 702 * the given tree object does not exist in this repository. 703 * @throws org.eclipse.jgit.errors.IncorrectObjectTypeException 704 * the given object id does not denote a tree, but instead names 705 * some other non-tree type of object. Note that commits are not 706 * trees, even if they are sometimes called a "tree-ish". 707 * @throws org.eclipse.jgit.errors.CorruptObjectException 708 * the object claimed to be a tree, but its contents did not 709 * appear to be a tree. The repository may have data corruption. 710 * @throws java.io.IOException 711 * a loose object or pack file could not be read. 712 */ 713 public void reset(AnyObjectId id) throws MissingObjectException, 714 IncorrectObjectTypeException, CorruptObjectException, IOException { 715 if (trees.length == 1) { 716 AbstractTreeIterator o = trees[0]; 717 while (o.parent != null) 718 o = o.parent; 719 if (o instanceof CanonicalTreeParser) { 720 o.matches = null; 721 o.matchShift = 0; 722 ((CanonicalTreeParser) o).reset(reader, id); 723 trees[0] = o; 724 } else { 725 trees[0] = parserFor(id); 726 } 727 } else { 728 trees = new AbstractTreeIterator[] { parserFor(id) }; 729 } 730 731 advance = false; 732 depth = 0; 733 attrs = new Attributes[2]; 734 attributesHandlers = new AttributesHandler[2]; 735 headIndex = -1; 736 } 737 738 /** 739 * Reset this walker to run over a set of existing trees. 740 * 741 * @param ids 742 * the trees we need to parse. The walker will execute over this 743 * many parallel trees if the reset is successful. 744 * @throws org.eclipse.jgit.errors.MissingObjectException 745 * the given tree object does not exist in this repository. 746 * @throws org.eclipse.jgit.errors.IncorrectObjectTypeException 747 * the given object id does not denote a tree, but instead names 748 * some other non-tree type of object. Note that commits are not 749 * trees, even if they are sometimes called a "tree-ish". 750 * @throws org.eclipse.jgit.errors.CorruptObjectException 751 * the object claimed to be a tree, but its contents did not 752 * appear to be a tree. The repository may have data corruption. 753 * @throws java.io.IOException 754 * a loose object or pack file could not be read. 755 */ 756 public void reset(AnyObjectId... ids) throws MissingObjectException, 757 IncorrectObjectTypeException, CorruptObjectException, IOException { 758 final int oldLen = trees.length; 759 final int newLen = ids.length; 760 final AbstractTreeIterator[] r = newLen == oldLen ? trees 761 : new AbstractTreeIterator[newLen]; 762 for (int i = 0; i < newLen; i++) { 763 AbstractTreeIterator o; 764 765 if (i < oldLen) { 766 o = trees[i]; 767 while (o.parent != null) 768 o = o.parent; 769 if (o instanceof CanonicalTreeParser && o.pathOffset == 0) { 770 o.matches = null; 771 o.matchShift = 0; 772 ((CanonicalTreeParser) o).reset(reader, ids[i]); 773 r[i] = o; 774 continue; 775 } 776 } 777 778 o = parserFor(ids[i]); 779 r[i] = o; 780 } 781 782 trees = r; 783 advance = false; 784 depth = 0; 785 if (oldLen == newLen) { 786 Arrays.fill(attrs, null); 787 Arrays.fill(attributesHandlers, null); 788 } else { 789 attrs = new Attributes[newLen + 1]; 790 attributesHandlers = new AttributesHandler[newLen + 1]; 791 } 792 headIndex = -1; 793 } 794 795 /** 796 * Add an already existing tree object for walking. 797 * <p> 798 * The position of this tree is returned to the caller, in case the caller 799 * has lost track of the order they added the trees into the walker. 800 * <p> 801 * The tree must have the same root as existing trees in the walk. 802 * 803 * @param id 804 * identity of the tree object the caller wants walked. 805 * @return position of this tree within the walker. 806 * @throws org.eclipse.jgit.errors.MissingObjectException 807 * the given tree object does not exist in this repository. 808 * @throws org.eclipse.jgit.errors.IncorrectObjectTypeException 809 * the given object id does not denote a tree, but instead names 810 * some other non-tree type of object. Note that commits are not 811 * trees, even if they are sometimes called a "tree-ish". 812 * @throws org.eclipse.jgit.errors.CorruptObjectException 813 * the object claimed to be a tree, but its contents did not 814 * appear to be a tree. The repository may have data corruption. 815 * @throws java.io.IOException 816 * a loose object or pack file could not be read. 817 */ 818 public int addTree(AnyObjectId id) throws MissingObjectException, 819 IncorrectObjectTypeException, CorruptObjectException, IOException { 820 return addTree(parserFor(id)); 821 } 822 823 /** 824 * Add an already created tree iterator for walking. 825 * <p> 826 * The position of this tree is returned to the caller, in case the caller 827 * has lost track of the order they added the trees into the walker. 828 * <p> 829 * The tree which the iterator operates on must have the same root as 830 * existing trees in the walk. 831 * 832 * @param p 833 * an iterator to walk over. The iterator should be new, with no 834 * parent, and should still be positioned before the first entry. 835 * The tree which the iterator operates on must have the same 836 * root as other trees in the walk. 837 * @return position of this tree within the walker. 838 */ 839 public int addTree(AbstractTreeIterator p) { 840 int n = trees.length; 841 AbstractTreeIterator[] newTrees = new AbstractTreeIterator[n + 1]; 842 843 System.arraycopy(trees, 0, newTrees, 0, n); 844 newTrees[n] = p; 845 p.matches = null; 846 p.matchShift = 0; 847 848 trees = newTrees; 849 if (attrs == null) { 850 attrs = new Attributes[n + 2]; 851 } else { 852 attrs = Arrays.copyOf(attrs, n + 2); 853 } 854 if (attributesHandlers == null) { 855 attributesHandlers = new AttributesHandler[n + 2]; 856 } else { 857 attributesHandlers = Arrays.copyOf(attributesHandlers, n + 2); 858 } 859 return n; 860 } 861 862 /** 863 * Get the number of trees known to this walker. 864 * 865 * @return the total number of trees this walker is iterating over. 866 */ 867 public int getTreeCount() { 868 return trees.length; 869 } 870 871 /** 872 * Advance this walker to the next relevant entry. 873 * 874 * @return true if there is an entry available; false if all entries have 875 * been walked and the walk of this set of tree iterators is over. 876 * @throws org.eclipse.jgit.errors.MissingObjectException 877 * {@link #isRecursive()} was enabled, a subtree was found, but 878 * the subtree object does not exist in this repository. The 879 * repository may be missing objects. 880 * @throws org.eclipse.jgit.errors.IncorrectObjectTypeException 881 * {@link #isRecursive()} was enabled, a subtree was found, and 882 * the subtree id does not denote a tree, but instead names some 883 * other non-tree type of object. The repository may have data 884 * corruption. 885 * @throws org.eclipse.jgit.errors.CorruptObjectException 886 * the contents of a tree did not appear to be a tree. The 887 * repository may have data corruption. 888 * @throws java.io.IOException 889 * a loose object or pack file could not be read. 890 */ 891 public boolean next() throws MissingObjectException, 892 IncorrectObjectTypeException, CorruptObjectException, IOException { 893 try { 894 if (advance) { 895 advance = false; 896 postChildren = false; 897 popEntriesEqual(); 898 } 899 900 for (;;) { 901 Arrays.fill(attrs, null); 902 final AbstractTreeIterator t = min(); 903 if (t.eof()) { 904 if (depth > 0) { 905 exitSubtree(); 906 if (postOrderTraversal) { 907 advance = true; 908 postChildren = true; 909 return true; 910 } 911 popEntriesEqual(); 912 continue; 913 } 914 return false; 915 } 916 917 currentHead = t; 918 if (filter.matchFilter(this) == 1) { 919 skipEntriesEqual(); 920 continue; 921 } 922 923 if (recursive && FileMode.TREE.equals(t.mode)) { 924 enterSubtree(); 925 continue; 926 } 927 928 advance = true; 929 return true; 930 } 931 } catch (StopWalkException stop) { 932 stopWalk(); 933 return false; 934 } 935 } 936 937 /** 938 * Notify iterators the walk is aborting. 939 * <p> 940 * Primarily to notify {@link DirCacheBuildIterator} the walk is aborting so 941 * that it can copy any remaining entries. 942 * 943 * @throws IOException 944 * if traversal of remaining entries throws an exception during 945 * object access. This should never occur as remaining trees 946 * should already be in memory, however the methods used to 947 * finish traversal are declared to throw IOException. 948 */ 949 void stopWalk() throws IOException { 950 for (AbstractTreeIterator t : trees) { 951 t.stopWalk(); 952 } 953 } 954 955 /** 956 * Obtain the tree iterator for the current entry. 957 * <p> 958 * Entering into (or exiting out of) a subtree causes the current tree 959 * iterator instance to be changed for the nth tree. This allows the tree 960 * iterators to manage only one list of items, with the diving handled by 961 * recursive trees. 962 * 963 * @param nth 964 * tree to obtain the current iterator of. 965 * @param clazz 966 * type of the tree iterator expected by the caller. 967 * @return r the current iterator of the requested type; null if the tree 968 * has no entry to match the current path. 969 */ 970 @SuppressWarnings("unchecked") 971 public <T extends AbstractTreeIterator> T getTree(final int nth, 972 final Class<T> clazz) { 973 final AbstractTreeIterator t = trees[nth]; 974 return t.matches == currentHead ? (T) t : null; 975 } 976 977 /** 978 * Obtain the raw {@link org.eclipse.jgit.lib.FileMode} bits for the current 979 * entry. 980 * <p> 981 * Every added tree supplies mode bits, even if the tree does not contain 982 * the current entry. In the latter case 983 * {@link org.eclipse.jgit.lib.FileMode#MISSING}'s mode bits (0) are 984 * returned. 985 * 986 * @param nth 987 * tree to obtain the mode bits from. 988 * @return mode bits for the current entry of the nth tree. 989 * @see FileMode#fromBits(int) 990 */ 991 public int getRawMode(int nth) { 992 final AbstractTreeIterator t = trees[nth]; 993 return t.matches == currentHead ? t.mode : 0; 994 } 995 996 /** 997 * Obtain the {@link org.eclipse.jgit.lib.FileMode} for the current entry. 998 * <p> 999 * Every added tree supplies a mode, even if the tree does not contain the 1000 * current entry. In the latter case 1001 * {@link org.eclipse.jgit.lib.FileMode#MISSING} is returned. 1002 * 1003 * @param nth 1004 * tree to obtain the mode from. 1005 * @return mode for the current entry of the nth tree. 1006 */ 1007 public FileMode getFileMode(int nth) { 1008 return FileMode.fromBits(getRawMode(nth)); 1009 } 1010 1011 /** 1012 * Obtain the {@link org.eclipse.jgit.lib.FileMode} for the current entry on 1013 * the currentHead tree 1014 * 1015 * @return mode for the current entry of the currentHead tree. 1016 * @since 4.3 1017 */ 1018 public FileMode getFileMode() { 1019 return FileMode.fromBits(currentHead.mode); 1020 } 1021 1022 /** 1023 * Obtain the ObjectId for the current entry. 1024 * <p> 1025 * Using this method to compare ObjectId values between trees of this walker 1026 * is very inefficient. Applications should try to use 1027 * {@link #idEqual(int, int)} or {@link #getObjectId(MutableObjectId, int)} 1028 * whenever possible. 1029 * <p> 1030 * Every tree supplies an object id, even if the tree does not contain the 1031 * current entry. In the latter case 1032 * {@link org.eclipse.jgit.lib.ObjectId#zeroId()} is returned. 1033 * 1034 * @param nth 1035 * tree to obtain the object identifier from. 1036 * @return object identifier for the current tree entry. 1037 * @see #getObjectId(MutableObjectId, int) 1038 * @see #idEqual(int, int) 1039 * @see #getObjectId(MutableObjectId, int) 1040 * @see #idEqual(int, int) 1041 */ 1042 public ObjectId getObjectId(int nth) { 1043 final AbstractTreeIterator t = trees[nth]; 1044 return t.matches == currentHead ? t.getEntryObjectId() : ObjectId 1045 .zeroId(); 1046 } 1047 1048 /** 1049 * Obtain the ObjectId for the current entry. 1050 * <p> 1051 * Every tree supplies an object id, even if the tree does not contain the 1052 * current entry. In the latter case 1053 * {@link org.eclipse.jgit.lib.ObjectId#zeroId()} is supplied. 1054 * <p> 1055 * Applications should try to use {@link #idEqual(int, int)} when possible 1056 * as it avoids conversion overheads. 1057 * 1058 * @param out 1059 * buffer to copy the object id into. 1060 * @param nth 1061 * tree to obtain the object identifier from. 1062 * @see #idEqual(int, int) 1063 */ 1064 public void getObjectId(MutableObjectId out, int nth) { 1065 final AbstractTreeIterator t = trees[nth]; 1066 if (t.matches == currentHead) 1067 t.getEntryObjectId(out); 1068 else 1069 out.clear(); 1070 } 1071 1072 /** 1073 * Compare two tree's current ObjectId values for equality. 1074 * 1075 * @param nthA 1076 * first tree to compare the object id from. 1077 * @param nthB 1078 * second tree to compare the object id from. 1079 * @return result of 1080 * <code>getObjectId(nthA).equals(getObjectId(nthB))</code>. 1081 * @see #getObjectId(int) 1082 */ 1083 public boolean idEqual(int nthA, int nthB) { 1084 final AbstractTreeIterator ch = currentHead; 1085 final AbstractTreeIterator a = trees[nthA]; 1086 final AbstractTreeIterator b = trees[nthB]; 1087 if (a.matches != ch && b.matches != ch) { 1088 // If neither tree matches the current path node then neither 1089 // tree has this entry. In such case the ObjectId is zero(), 1090 // and zero() is always equal to zero(). 1091 // 1092 return true; 1093 } 1094 if (!a.hasId() || !b.hasId()) 1095 return false; 1096 if (a.matches == ch && b.matches == ch) 1097 return a.idEqual(b); 1098 return false; 1099 } 1100 1101 /** 1102 * Get the current entry's name within its parent tree. 1103 * <p> 1104 * This method is not very efficient and is primarily meant for debugging 1105 * and final output generation. Applications should try to avoid calling it, 1106 * and if invoked do so only once per interesting entry, where the name is 1107 * absolutely required for correct function. 1108 * 1109 * @return name of the current entry within the parent tree (or directory). 1110 * The name never includes a '/'. 1111 */ 1112 public String getNameString() { 1113 final AbstractTreeIterator t = currentHead; 1114 final int off = t.pathOffset; 1115 final int end = t.pathLen; 1116 return RawParseUtils.decode(UTF_8, t.path, off, end); 1117 } 1118 1119 /** 1120 * Get the current entry's complete path. 1121 * <p> 1122 * This method is not very efficient and is primarily meant for debugging 1123 * and final output generation. Applications should try to avoid calling it, 1124 * and if invoked do so only once per interesting entry, where the name is 1125 * absolutely required for correct function. 1126 * 1127 * @return complete path of the current entry, from the root of the 1128 * repository. If the current entry is in a subtree there will be at 1129 * least one '/' in the returned string. 1130 */ 1131 public String getPathString() { 1132 return pathOf(currentHead); 1133 } 1134 1135 /** 1136 * Get the current entry's complete path as a UTF-8 byte array. 1137 * 1138 * @return complete path of the current entry, from the root of the 1139 * repository. If the current entry is in a subtree there will be at 1140 * least one '/' in the returned string. 1141 */ 1142 public byte[] getRawPath() { 1143 final AbstractTreeIterator t = currentHead; 1144 final int n = t.pathLen; 1145 final byte[] r = new byte[n]; 1146 System.arraycopy(t.path, 0, r, 0, n); 1147 return r; 1148 } 1149 1150 /** 1151 * Get the path length of the current entry. 1152 * 1153 * @return The path length of the current entry. 1154 */ 1155 public int getPathLength() { 1156 return currentHead.pathLen; 1157 } 1158 1159 /** 1160 * Test if the supplied path matches the current entry's path. 1161 * <p> 1162 * This method detects if the supplied path is equal to, a subtree of, or 1163 * not similar at all to the current entry. It is faster to use this 1164 * method than to use {@link #getPathString()} to first create a String 1165 * object, then test <code>startsWith</code> or some other type of string 1166 * match function. 1167 * <p> 1168 * If the current entry is a subtree, then all paths within the subtree 1169 * are considered to match it. 1170 * 1171 * @param p 1172 * path buffer to test. Callers should ensure the path does not 1173 * end with '/' prior to invocation. 1174 * @param pLen 1175 * number of bytes from <code>buf</code> to test. 1176 * @return -1 if the current path is a parent to p; 0 if p matches the current 1177 * path; 1 if the current path is different and will never match 1178 * again on this tree walk. 1179 * @since 4.7 1180 */ 1181 public int isPathMatch(byte[] p, int pLen) { 1182 final AbstractTreeIterator t = currentHead; 1183 final byte[] c = t.path; 1184 final int cLen = t.pathLen; 1185 int ci; 1186 1187 for (ci = 0; ci < cLen && ci < pLen; ci++) { 1188 final int c_value = (c[ci] & 0xff) - (p[ci] & 0xff); 1189 if (c_value != 0) { 1190 // Paths do not and will never match 1191 return 1; 1192 } 1193 } 1194 1195 if (ci < cLen) { 1196 // Ran out of pattern but we still had current data. 1197 // If c[ci] == '/' then pattern matches the subtree. 1198 // Otherwise it is a partial match == miss 1199 return c[ci] == '/' ? 0 : 1; 1200 } 1201 1202 if (ci < pLen) { 1203 // Ran out of current, but we still have pattern data. 1204 // If p[ci] == '/' then this subtree is a parent in the pattern, 1205 // otherwise it's a miss. 1206 return p[ci] == '/' && FileMode.TREE.equals(t.mode) ? -1 : 1; 1207 } 1208 1209 // Both strings are identical. 1210 return 0; 1211 } 1212 1213 /** 1214 * Test if the supplied path matches the current entry's path. 1215 * <p> 1216 * This method tests that the supplied path is exactly equal to the current 1217 * entry or is one of its parent directories. It is faster to use this 1218 * method then to use {@link #getPathString()} to first create a String 1219 * object, then test <code>startsWith</code> or some other type of string 1220 * match function. 1221 * <p> 1222 * If the current entry is a subtree, then all paths within the subtree 1223 * are considered to match it. 1224 * 1225 * @param p 1226 * path buffer to test. Callers should ensure the path does not 1227 * end with '/' prior to invocation. 1228 * @param pLen 1229 * number of bytes from <code>buf</code> to test. 1230 * @return < 0 if p is before the current path; 0 if p matches the current 1231 * path; 1 if the current path is past p and p will never match 1232 * again on this tree walk. 1233 */ 1234 public int isPathPrefix(byte[] p, int pLen) { 1235 final AbstractTreeIterator t = currentHead; 1236 final byte[] c = t.path; 1237 final int cLen = t.pathLen; 1238 int ci; 1239 1240 for (ci = 0; ci < cLen && ci < pLen; ci++) { 1241 final int c_value = (c[ci] & 0xff) - (p[ci] & 0xff); 1242 if (c_value != 0) 1243 return c_value; 1244 } 1245 1246 if (ci < cLen) { 1247 // Ran out of pattern but we still had current data. 1248 // If c[ci] == '/' then pattern matches the subtree. 1249 // Otherwise we cannot be certain so we return -1. 1250 // 1251 return c[ci] == '/' ? 0 : -1; 1252 } 1253 1254 if (ci < pLen) { 1255 // Ran out of current, but we still have pattern data. 1256 // If p[ci] == '/' then pattern matches this subtree, 1257 // otherwise we cannot be certain so we return -1. 1258 // 1259 return p[ci] == '/' && FileMode.TREE.equals(t.mode) ? 0 : -1; 1260 } 1261 1262 // Both strings are identical. 1263 // 1264 return 0; 1265 } 1266 1267 /** 1268 * Test if the supplied path matches (being suffix of) the current entry's 1269 * path. 1270 * <p> 1271 * This method tests that the supplied path is exactly equal to the current 1272 * entry, or is relative to one of entry's parent directories. It is faster 1273 * to use this method then to use {@link #getPathString()} to first create 1274 * a String object, then test <code>endsWith</code> or some other type of 1275 * string match function. 1276 * 1277 * @param p 1278 * path buffer to test. 1279 * @param pLen 1280 * number of bytes from <code>buf</code> to test. 1281 * @return true if p is suffix of the current path; 1282 * false if otherwise 1283 */ 1284 public boolean isPathSuffix(byte[] p, int pLen) { 1285 final AbstractTreeIterator t = currentHead; 1286 final byte[] c = t.path; 1287 final int cLen = t.pathLen; 1288 1289 for (int i = 1; i <= pLen; i++) { 1290 // Pattern longer than current path 1291 if (i > cLen) 1292 return false; 1293 // Current path doesn't match pattern 1294 if (c[cLen - i] != p[pLen - i]) 1295 return false; 1296 } 1297 1298 // Whole pattern tested -> matches 1299 return true; 1300 } 1301 1302 /** 1303 * Get the current subtree depth of this walker. 1304 * 1305 * @return the current subtree depth of this walker. 1306 */ 1307 public int getDepth() { 1308 return depth; 1309 } 1310 1311 /** 1312 * Is the current entry a subtree? 1313 * <p> 1314 * This method is faster then testing the raw mode bits of all trees to see 1315 * if any of them are a subtree. If at least one is a subtree then this 1316 * method will return true. 1317 * 1318 * @return true if {@link #enterSubtree()} will work on the current node. 1319 */ 1320 public boolean isSubtree() { 1321 return FileMode.TREE.equals(currentHead.mode); 1322 } 1323 1324 /** 1325 * Is the current entry a subtree returned after its children? 1326 * 1327 * @return true if the current node is a tree that has been returned after 1328 * its children were already processed. 1329 * @see #isPostOrderTraversal() 1330 */ 1331 public boolean isPostChildren() { 1332 return postChildren && isSubtree(); 1333 } 1334 1335 /** 1336 * Enter into the current subtree. 1337 * <p> 1338 * If the current entry is a subtree this method arranges for its children 1339 * to be returned before the next sibling following the subtree is returned. 1340 * 1341 * @throws org.eclipse.jgit.errors.MissingObjectException 1342 * a subtree was found, but the subtree object does not exist in 1343 * this repository. The repository may be missing objects. 1344 * @throws org.eclipse.jgit.errors.IncorrectObjectTypeException 1345 * a subtree was found, and the subtree id does not denote a 1346 * tree, but instead names some other non-tree type of object. 1347 * The repository may have data corruption. 1348 * @throws org.eclipse.jgit.errors.CorruptObjectException 1349 * the contents of a tree did not appear to be a tree. The 1350 * repository may have data corruption. 1351 * @throws java.io.IOException 1352 * a loose object or pack file could not be read. 1353 */ 1354 public void enterSubtree() throws MissingObjectException, 1355 IncorrectObjectTypeException, CorruptObjectException, IOException { 1356 Arrays.fill(attrs, null); 1357 final AbstractTreeIterator ch = currentHead; 1358 final AbstractTreeIterator[] tmp = new AbstractTreeIterator[trees.length]; 1359 for (int i = 0; i < trees.length; i++) { 1360 final AbstractTreeIterator t = trees[i]; 1361 final AbstractTreeIterator n; 1362 // If we find a GITLINK when attempting to enter a subtree, then the 1363 // GITLINK must exist as a TREE in the index, meaning the working tree 1364 // entry should be treated as a TREE 1365 if (t.matches == ch && !t.eof() && 1366 (FileMode.TREE.equals(t.mode) 1367 || (FileMode.GITLINK.equals(t.mode) && t.isWorkTree()))) 1368 n = t.createSubtreeIterator(reader, idBuffer); 1369 else 1370 n = t.createEmptyTreeIterator(); 1371 tmp[i] = n; 1372 } 1373 depth++; 1374 advance = false; 1375 System.arraycopy(tmp, 0, trees, 0, trees.length); 1376 } 1377 1378 @SuppressWarnings("unused") 1379 AbstractTreeIterator min() throws CorruptObjectException { 1380 int i = 0; 1381 AbstractTreeIterator minRef = trees[i]; 1382 while (minRef.eof() && ++i < trees.length) 1383 minRef = trees[i]; 1384 if (minRef.eof()) 1385 return minRef; 1386 1387 minRef.matches = minRef; 1388 while (++i < trees.length) { 1389 final AbstractTreeIterator t = trees[i]; 1390 if (t.eof()) 1391 continue; 1392 final int cmp = t.pathCompare(minRef); 1393 if (cmp < 0) { 1394 t.matches = t; 1395 minRef = t; 1396 } else if (cmp == 0) { 1397 t.matches = minRef; 1398 } 1399 } 1400 1401 return minRef; 1402 } 1403 1404 void popEntriesEqual() throws CorruptObjectException { 1405 final AbstractTreeIterator ch = currentHead; 1406 for (AbstractTreeIterator t : trees) { 1407 if (t.matches == ch) { 1408 t.next(1); 1409 t.matches = null; 1410 } 1411 } 1412 } 1413 1414 void skipEntriesEqual() throws CorruptObjectException { 1415 final AbstractTreeIterator ch = currentHead; 1416 for (AbstractTreeIterator t : trees) { 1417 if (t.matches == ch) { 1418 t.skip(); 1419 t.matches = null; 1420 } 1421 } 1422 } 1423 1424 void exitSubtree() { 1425 depth--; 1426 for (int i = 0; i < trees.length; i++) 1427 trees[i] = trees[i].parent; 1428 1429 AbstractTreeIterator minRef = null; 1430 for (AbstractTreeIterator t : trees) { 1431 if (t.matches != t) 1432 continue; 1433 if (minRef == null || t.pathCompare(minRef) < 0) 1434 minRef = t; 1435 } 1436 currentHead = minRef; 1437 } 1438 1439 private CanonicalTreeParser parserFor(AnyObjectId id) 1440 throws IncorrectObjectTypeException, IOException { 1441 final CanonicalTreeParser p = new CanonicalTreeParser(); 1442 p.reset(reader, id); 1443 return p; 1444 } 1445 1446 static String pathOf(AbstractTreeIterator t) { 1447 return RawParseUtils.decode(UTF_8, t.path, 0, t.pathLen); 1448 } 1449 1450 static String pathOf(byte[] buf, int pos, int end) { 1451 return RawParseUtils.decode(UTF_8, buf, pos, end); 1452 } 1453 1454 /** 1455 * Get the tree of that type. 1456 * 1457 * @param type 1458 * of the tree to be queried 1459 * @return the tree of that type or null if none is present. 1460 * @since 4.3 1461 * @param <T> 1462 * a tree type. 1463 */ 1464 public <T extends AbstractTreeIterator> T getTree(Class<T> type) { 1465 for (AbstractTreeIterator tree : trees) { 1466 if (type.isInstance(tree)) { 1467 return type.cast(tree); 1468 } 1469 } 1470 return null; 1471 } 1472 1473 /** 1474 * Inspect config and attributes to return a filtercommand applicable for 1475 * the current path. 1476 * 1477 * @param filterCommandType 1478 * which type of filterCommand should be executed. E.g. "clean", 1479 * "smudge". For "smudge" consider using 1480 * {{@link #getSmudgeCommand(int)} instead. 1481 * @return a filter command 1482 * @throws java.io.IOException 1483 * @since 4.2 1484 */ 1485 public String getFilterCommand(String filterCommandType) 1486 throws IOException { 1487 Attributes attributes = getAttributes(); 1488 1489 Attribute f = attributes.get(Constants.ATTR_FILTER); 1490 if (f == null) { 1491 return null; 1492 } 1493 String filterValue = f.getValue(); 1494 if (filterValue == null) { 1495 return null; 1496 } 1497 1498 String filterCommand = getFilterCommandDefinition(filterValue, 1499 filterCommandType); 1500 if (filterCommand == null) { 1501 return null; 1502 } 1503 return filterCommand.replaceAll("%f", //$NON-NLS-1$ 1504 Matcher.quoteReplacement( 1505 QuotedString.BOURNE.quote((getPathString())))); 1506 } 1507 1508 /** 1509 * Inspect config and attributes to return a filtercommand applicable for 1510 * the current path. 1511 * 1512 * @param index 1513 * of the tree the item to be smudged is in 1514 * @return a filter command 1515 * @throws java.io.IOException 1516 * @since 6.1 1517 */ 1518 public String getSmudgeCommand(int index) 1519 throws IOException { 1520 return getSmudgeCommand(getAttributes(index)); 1521 } 1522 1523 /** 1524 * Inspect config and attributes to return a filtercommand applicable for 1525 * the current path. 1526 * 1527 * @param attributes 1528 * to use 1529 * @return a filter command 1530 * @throws java.io.IOException 1531 * @since 6.1 1532 */ 1533 public String getSmudgeCommand(Attributes attributes) throws IOException { 1534 if (attributes == null) { 1535 return null; 1536 } 1537 Attribute f = attributes.get(Constants.ATTR_FILTER); 1538 if (f == null) { 1539 return null; 1540 } 1541 String filterValue = f.getValue(); 1542 if (filterValue == null) { 1543 return null; 1544 } 1545 1546 String filterCommand = getFilterCommandDefinition(filterValue, 1547 Constants.ATTR_FILTER_TYPE_SMUDGE); 1548 if (filterCommand == null) { 1549 return null; 1550 } 1551 return filterCommand.replaceAll("%f", //$NON-NLS-1$ 1552 Matcher.quoteReplacement( 1553 QuotedString.BOURNE.quote((getPathString())))); 1554 } 1555 1556 /** 1557 * Get the filter command how it is defined in gitconfig. The returned 1558 * string may contain "%f" which needs to be replaced by the current path 1559 * before executing the filter command. These filter definitions are cached 1560 * for better performance. 1561 * 1562 * @param filterDriverName 1563 * The name of the filter driver as it is referenced in the 1564 * gitattributes file. E.g. "lfs". For each filter driver there 1565 * may be many commands defined in the .gitconfig 1566 * @param filterCommandType 1567 * The type of the filter command for a specific filter driver. 1568 * May be "clean" or "smudge". 1569 * @return the definition of the command to be executed for this filter 1570 * driver and filter command 1571 */ 1572 private String getFilterCommandDefinition(String filterDriverName, 1573 String filterCommandType) { 1574 String key = filterDriverName + "." + filterCommandType; //$NON-NLS-1$ 1575 String filterCommand = filterCommandsByNameDotType.get(key); 1576 if (filterCommand != null) 1577 return filterCommand; 1578 filterCommand = config.getString(ConfigConstants.CONFIG_FILTER_SECTION, 1579 filterDriverName, filterCommandType); 1580 boolean useBuiltin = config.getBoolean( 1581 ConfigConstants.CONFIG_FILTER_SECTION, 1582 filterDriverName, ConfigConstants.CONFIG_KEY_USEJGITBUILTIN, false); 1583 if (useBuiltin) { 1584 String builtinFilterCommand = Constants.BUILTIN_FILTER_PREFIX 1585 + filterDriverName + '/' + filterCommandType; 1586 if (filterCommands != null 1587 && filterCommands.contains(builtinFilterCommand)) { 1588 filterCommand = builtinFilterCommand; 1589 } 1590 } 1591 if (filterCommand != null) { 1592 filterCommandsByNameDotType.put(key, filterCommand); 1593 } 1594 return filterCommand; 1595 } 1596 }