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