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