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