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