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(final 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(final @Nullable Repository repo, final 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(final 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(final 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(final 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(final 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 * Get the EOL stream type of the current entry using the config and 637 * {@link #getAttributes()}. 638 * 639 * @return the EOL stream type of the current entry using the config and 640 * {@link #getAttributes()}. Note that this method may return null 641 * if the {@link org.eclipse.jgit.treewalk.TreeWalk} is not based on 642 * a working tree 643 * @since 4.3 644 * @deprecated use {@link #getEolStreamType(OperationType)} instead. 645 */ 646 @Deprecated 647 public @Nullable EolStreamType getEolStreamType() { 648 return (getEolStreamType(operationType)); 649 } 650 651 /** 652 * Reset this walker so new tree iterators can be added to it. 653 */ 654 public void reset() { 655 attrs = null; 656 attributesHandler = null; 657 trees = NO_TREES; 658 advance = false; 659 depth = 0; 660 } 661 662 /** 663 * Reset this walker to run over a single existing tree. 664 * 665 * @param id 666 * the tree we need to parse. The walker will execute over this 667 * single tree if the reset is successful. 668 * @throws org.eclipse.jgit.errors.MissingObjectException 669 * the given tree object does not exist in this repository. 670 * @throws org.eclipse.jgit.errors.IncorrectObjectTypeException 671 * the given object id does not denote a tree, but instead names 672 * some other non-tree type of object. Note that commits are not 673 * trees, even if they are sometimes called a "tree-ish". 674 * @throws org.eclipse.jgit.errors.CorruptObjectException 675 * the object claimed to be a tree, but its contents did not 676 * appear to be a tree. The repository may have data corruption. 677 * @throws java.io.IOException 678 * a loose object or pack file could not be read. 679 */ 680 public void reset(final AnyObjectId id) throws MissingObjectException, 681 IncorrectObjectTypeException, CorruptObjectException, IOException { 682 if (trees.length == 1) { 683 AbstractTreeIterator o = trees[0]; 684 while (o.parent != null) 685 o = o.parent; 686 if (o instanceof CanonicalTreeParser) { 687 o.matches = null; 688 o.matchShift = 0; 689 ((CanonicalTreeParser) o).reset(reader, id); 690 trees[0] = o; 691 } else { 692 trees[0] = parserFor(id); 693 } 694 } else { 695 trees = new AbstractTreeIterator[] { parserFor(id) }; 696 } 697 698 advance = false; 699 depth = 0; 700 attrs = null; 701 } 702 703 /** 704 * Reset this walker to run over a set of existing trees. 705 * 706 * @param ids 707 * the trees we need to parse. The walker will execute over this 708 * many parallel trees if the reset is successful. 709 * @throws org.eclipse.jgit.errors.MissingObjectException 710 * the given tree object does not exist in this repository. 711 * @throws org.eclipse.jgit.errors.IncorrectObjectTypeException 712 * the given object id does not denote a tree, but instead names 713 * some other non-tree type of object. Note that commits are not 714 * trees, even if they are sometimes called a "tree-ish". 715 * @throws org.eclipse.jgit.errors.CorruptObjectException 716 * the object claimed to be a tree, but its contents did not 717 * appear to be a tree. The repository may have data corruption. 718 * @throws java.io.IOException 719 * a loose object or pack file could not be read. 720 */ 721 public void reset(final AnyObjectId... ids) throws MissingObjectException, 722 IncorrectObjectTypeException, CorruptObjectException, IOException { 723 final int oldLen = trees.length; 724 final int newLen = ids.length; 725 final AbstractTreeIterator[] r = newLen == oldLen ? trees 726 : new AbstractTreeIterator[newLen]; 727 for (int i = 0; i < newLen; i++) { 728 AbstractTreeIterator o; 729 730 if (i < oldLen) { 731 o = trees[i]; 732 while (o.parent != null) 733 o = o.parent; 734 if (o instanceof CanonicalTreeParser && o.pathOffset == 0) { 735 o.matches = null; 736 o.matchShift = 0; 737 ((CanonicalTreeParser) o).reset(reader, ids[i]); 738 r[i] = o; 739 continue; 740 } 741 } 742 743 o = parserFor(ids[i]); 744 r[i] = o; 745 } 746 747 trees = r; 748 advance = false; 749 depth = 0; 750 attrs = null; 751 } 752 753 /** 754 * Add an already existing tree object for walking. 755 * <p> 756 * The position of this tree is returned to the caller, in case the caller 757 * has lost track of the order they added the trees into the walker. 758 * <p> 759 * The tree must have the same root as existing trees in the walk. 760 * 761 * @param id 762 * identity of the tree object the caller wants walked. 763 * @return position of this tree within the walker. 764 * @throws org.eclipse.jgit.errors.MissingObjectException 765 * the given tree object does not exist in this repository. 766 * @throws org.eclipse.jgit.errors.IncorrectObjectTypeException 767 * the given object id does not denote a tree, but instead names 768 * some other non-tree type of object. Note that commits are not 769 * trees, even if they are sometimes called a "tree-ish". 770 * @throws org.eclipse.jgit.errors.CorruptObjectException 771 * the object claimed to be a tree, but its contents did not 772 * appear to be a tree. The repository may have data corruption. 773 * @throws java.io.IOException 774 * a loose object or pack file could not be read. 775 */ 776 public int addTree(final AnyObjectId id) throws MissingObjectException, 777 IncorrectObjectTypeException, CorruptObjectException, IOException { 778 return addTree(parserFor(id)); 779 } 780 781 /** 782 * Add an already created tree iterator for walking. 783 * <p> 784 * The position of this tree is returned to the caller, in case the caller 785 * has lost track of the order they added the trees into the walker. 786 * <p> 787 * The tree which the iterator operates on must have the same root as 788 * existing trees in the walk. 789 * 790 * @param p 791 * an iterator to walk over. The iterator should be new, with no 792 * parent, and should still be positioned before the first entry. 793 * The tree which the iterator operates on must have the same 794 * root as other trees in the walk. 795 * @return position of this tree within the walker. 796 */ 797 public int addTree(AbstractTreeIterator p) { 798 int n = trees.length; 799 AbstractTreeIterator[] newTrees = new AbstractTreeIterator[n + 1]; 800 801 System.arraycopy(trees, 0, newTrees, 0, n); 802 newTrees[n] = p; 803 p.matches = null; 804 p.matchShift = 0; 805 806 trees = newTrees; 807 return n; 808 } 809 810 /** 811 * Get the number of trees known to this walker. 812 * 813 * @return the total number of trees this walker is iterating over. 814 */ 815 public int getTreeCount() { 816 return trees.length; 817 } 818 819 /** 820 * Advance this walker to the next relevant entry. 821 * 822 * @return true if there is an entry available; false if all entries have 823 * been walked and the walk of this set of tree iterators is over. 824 * @throws org.eclipse.jgit.errors.MissingObjectException 825 * {@link #isRecursive()} was enabled, a subtree was found, but 826 * the subtree object does not exist in this repository. The 827 * repository may be missing objects. 828 * @throws org.eclipse.jgit.errors.IncorrectObjectTypeException 829 * {@link #isRecursive()} was enabled, a subtree was found, and 830 * the subtree id does not denote a tree, but instead names some 831 * other non-tree type of object. The repository may have data 832 * corruption. 833 * @throws org.eclipse.jgit.errors.CorruptObjectException 834 * the contents of a tree did not appear to be a tree. The 835 * repository may have data corruption. 836 * @throws java.io.IOException 837 * a loose object or pack file could not be read. 838 */ 839 public boolean next() throws MissingObjectException, 840 IncorrectObjectTypeException, CorruptObjectException, IOException { 841 try { 842 if (advance) { 843 advance = false; 844 postChildren = false; 845 popEntriesEqual(); 846 } 847 848 for (;;) { 849 attrs = null; 850 final AbstractTreeIterator t = min(); 851 if (t.eof()) { 852 if (depth > 0) { 853 exitSubtree(); 854 if (postOrderTraversal) { 855 advance = true; 856 postChildren = true; 857 return true; 858 } 859 popEntriesEqual(); 860 continue; 861 } 862 return false; 863 } 864 865 currentHead = t; 866 if (filter.matchFilter(this) == 1) { 867 skipEntriesEqual(); 868 continue; 869 } 870 871 if (recursive && FileMode.TREE.equals(t.mode)) { 872 enterSubtree(); 873 continue; 874 } 875 876 advance = true; 877 return true; 878 } 879 } catch (StopWalkException stop) { 880 stopWalk(); 881 return false; 882 } 883 } 884 885 /** 886 * Notify iterators the walk is aborting. 887 * <p> 888 * Primarily to notify {@link DirCacheBuildIterator} the walk is aborting so 889 * that it can copy any remaining entries. 890 * 891 * @throws IOException 892 * if traversal of remaining entries throws an exception during 893 * object access. This should never occur as remaining trees 894 * should already be in memory, however the methods used to 895 * finish traversal are declared to throw IOException. 896 */ 897 void stopWalk() throws IOException { 898 for (AbstractTreeIterator t : trees) { 899 t.stopWalk(); 900 } 901 } 902 903 /** 904 * Obtain the tree iterator for the current entry. 905 * <p> 906 * Entering into (or exiting out of) a subtree causes the current tree 907 * iterator instance to be changed for the nth tree. This allows the tree 908 * iterators to manage only one list of items, with the diving handled by 909 * recursive trees. 910 * 911 * @param nth 912 * tree to obtain the current iterator of. 913 * @param clazz 914 * type of the tree iterator expected by the caller. 915 * @return r the current iterator of the requested type; null if the tree 916 * has no entry to match the current path. 917 */ 918 @SuppressWarnings("unchecked") 919 public <T extends AbstractTreeIterator> T getTree(final int nth, 920 final Class<T> clazz) { 921 final AbstractTreeIterator t = trees[nth]; 922 return t.matches == currentHead ? (T) t : null; 923 } 924 925 /** 926 * Obtain the raw {@link org.eclipse.jgit.lib.FileMode} bits for the current 927 * entry. 928 * <p> 929 * Every added tree supplies mode bits, even if the tree does not contain 930 * the current entry. In the latter case 931 * {@link org.eclipse.jgit.lib.FileMode#MISSING}'s mode bits (0) are 932 * returned. 933 * 934 * @param nth 935 * tree to obtain the mode bits from. 936 * @return mode bits for the current entry of the nth tree. 937 * @see FileMode#fromBits(int) 938 */ 939 public int getRawMode(final int nth) { 940 final AbstractTreeIterator t = trees[nth]; 941 return t.matches == currentHead ? t.mode : 0; 942 } 943 944 /** 945 * Obtain the {@link org.eclipse.jgit.lib.FileMode} for the current entry. 946 * <p> 947 * Every added tree supplies a mode, even if the tree does not contain the 948 * current entry. In the latter case 949 * {@link org.eclipse.jgit.lib.FileMode#MISSING} is returned. 950 * 951 * @param nth 952 * tree to obtain the mode from. 953 * @return mode for the current entry of the nth tree. 954 */ 955 public FileMode getFileMode(final int nth) { 956 return FileMode.fromBits(getRawMode(nth)); 957 } 958 959 /** 960 * Obtain the {@link org.eclipse.jgit.lib.FileMode} for the current entry on 961 * the currentHead tree 962 * 963 * @return mode for the current entry of the currentHead tree. 964 * @since 4.3 965 */ 966 public FileMode getFileMode() { 967 return FileMode.fromBits(currentHead.mode); 968 } 969 970 /** 971 * Obtain the ObjectId for the current entry. 972 * <p> 973 * Using this method to compare ObjectId values between trees of this walker 974 * is very inefficient. Applications should try to use 975 * {@link #idEqual(int, int)} or {@link #getObjectId(MutableObjectId, int)} 976 * whenever possible. 977 * <p> 978 * Every tree supplies an object id, even if the tree does not contain the 979 * current entry. In the latter case 980 * {@link org.eclipse.jgit.lib.ObjectId#zeroId()} is returned. 981 * 982 * @param nth 983 * tree to obtain the object identifier from. 984 * @return object identifier for the current tree entry. 985 * @see #getObjectId(MutableObjectId, int) 986 * @see #idEqual(int, int) 987 * @see #getObjectId(MutableObjectId, int) 988 * @see #idEqual(int, int) 989 */ 990 public ObjectId getObjectId(final int nth) { 991 final AbstractTreeIterator t = trees[nth]; 992 return t.matches == currentHead ? t.getEntryObjectId() : ObjectId 993 .zeroId(); 994 } 995 996 /** 997 * Obtain the ObjectId for the current entry. 998 * <p> 999 * Every tree supplies an object id, even if the tree does not contain the 1000 * current entry. In the latter case 1001 * {@link org.eclipse.jgit.lib.ObjectId#zeroId()} is supplied. 1002 * <p> 1003 * Applications should try to use {@link #idEqual(int, int)} when possible 1004 * as it avoids conversion overheads. 1005 * 1006 * @param out 1007 * buffer to copy the object id into. 1008 * @param nth 1009 * tree to obtain the object identifier from. 1010 * @see #idEqual(int, int) 1011 */ 1012 public void getObjectId(final MutableObjectId out, final int nth) { 1013 final AbstractTreeIterator t = trees[nth]; 1014 if (t.matches == currentHead) 1015 t.getEntryObjectId(out); 1016 else 1017 out.clear(); 1018 } 1019 1020 /** 1021 * Compare two tree's current ObjectId values for equality. 1022 * 1023 * @param nthA 1024 * first tree to compare the object id from. 1025 * @param nthB 1026 * second tree to compare the object id from. 1027 * @return result of 1028 * <code>getObjectId(nthA).equals(getObjectId(nthB))</code>. 1029 * @see #getObjectId(int) 1030 */ 1031 public boolean idEqual(final int nthA, final int nthB) { 1032 final AbstractTreeIterator ch = currentHead; 1033 final AbstractTreeIterator a = trees[nthA]; 1034 final AbstractTreeIterator b = trees[nthB]; 1035 if (a.matches != ch && b.matches != ch) { 1036 // If neither tree matches the current path node then neither 1037 // tree has this entry. In such case the ObjectId is zero(), 1038 // and zero() is always equal to zero(). 1039 // 1040 return true; 1041 } 1042 if (!a.hasId() || !b.hasId()) 1043 return false; 1044 if (a.matches == ch && b.matches == ch) 1045 return a.idEqual(b); 1046 return false; 1047 } 1048 1049 /** 1050 * Get the current entry's name within its parent tree. 1051 * <p> 1052 * This method is not very efficient and is primarily meant for debugging 1053 * and final output generation. Applications should try to avoid calling it, 1054 * and if invoked do so only once per interesting entry, where the name is 1055 * absolutely required for correct function. 1056 * 1057 * @return name of the current entry within the parent tree (or directory). 1058 * The name never includes a '/'. 1059 */ 1060 public String getNameString() { 1061 final AbstractTreeIterator t = currentHead; 1062 final int off = t.pathOffset; 1063 final int end = t.pathLen; 1064 return RawParseUtils.decode(Constants.CHARSET, t.path, off, end); 1065 } 1066 1067 /** 1068 * Get the current entry's complete path. 1069 * <p> 1070 * This method is not very efficient and is primarily meant for debugging 1071 * and final output generation. Applications should try to avoid calling it, 1072 * and if invoked do so only once per interesting entry, where the name is 1073 * absolutely required for correct function. 1074 * 1075 * @return complete path of the current entry, from the root of the 1076 * repository. If the current entry is in a subtree there will be at 1077 * least one '/' in the returned string. 1078 */ 1079 public String getPathString() { 1080 return pathOf(currentHead); 1081 } 1082 1083 /** 1084 * Get the current entry's complete path as a UTF-8 byte array. 1085 * 1086 * @return complete path of the current entry, from the root of the 1087 * repository. If the current entry is in a subtree there will be at 1088 * least one '/' in the returned string. 1089 */ 1090 public byte[] getRawPath() { 1091 final AbstractTreeIterator t = currentHead; 1092 final int n = t.pathLen; 1093 final byte[] r = new byte[n]; 1094 System.arraycopy(t.path, 0, r, 0, n); 1095 return r; 1096 } 1097 1098 /** 1099 * Get the path length of the current entry. 1100 * 1101 * @return The path length of the current entry. 1102 */ 1103 public int getPathLength() { 1104 return currentHead.pathLen; 1105 } 1106 1107 /** 1108 * Test if the supplied path matches the current entry's path. 1109 * <p> 1110 * This method detects if the supplied path is equal to, a subtree of, or 1111 * not similar at all to the current entry. It is faster to use this 1112 * method than to use {@link #getPathString()} to first create a String 1113 * object, then test <code>startsWith</code> or some other type of string 1114 * match function. 1115 * <p> 1116 * If the current entry is a subtree, then all paths within the subtree 1117 * are considered to match it. 1118 * 1119 * @param p 1120 * path buffer to test. Callers should ensure the path does not 1121 * end with '/' prior to invocation. 1122 * @param pLen 1123 * number of bytes from <code>buf</code> to test. 1124 * @return -1 if the current path is a parent to p; 0 if p matches the current 1125 * path; 1 if the current path is different and will never match 1126 * again on this tree walk. 1127 * @since 4.7 1128 */ 1129 public int isPathMatch(final byte[] p, final int pLen) { 1130 final AbstractTreeIterator t = currentHead; 1131 final byte[] c = t.path; 1132 final int cLen = t.pathLen; 1133 int ci; 1134 1135 for (ci = 0; ci < cLen && ci < pLen; ci++) { 1136 final int c_value = (c[ci] & 0xff) - (p[ci] & 0xff); 1137 if (c_value != 0) { 1138 // Paths do not and will never match 1139 return 1; 1140 } 1141 } 1142 1143 if (ci < cLen) { 1144 // Ran out of pattern but we still had current data. 1145 // If c[ci] == '/' then pattern matches the subtree. 1146 // Otherwise it is a partial match == miss 1147 return c[ci] == '/' ? 0 : 1; 1148 } 1149 1150 if (ci < pLen) { 1151 // Ran out of current, but we still have pattern data. 1152 // If p[ci] == '/' then this subtree is a parent in the pattern, 1153 // otherwise it's a miss. 1154 return p[ci] == '/' && FileMode.TREE.equals(t.mode) ? -1 : 1; 1155 } 1156 1157 // Both strings are identical. 1158 return 0; 1159 } 1160 1161 /** 1162 * Test if the supplied path matches the current entry's path. 1163 * <p> 1164 * This method tests that the supplied path is exactly equal to the current 1165 * entry or is one of its parent directories. It is faster to use this 1166 * method then to use {@link #getPathString()} to first create a String 1167 * object, then test <code>startsWith</code> or some other type of string 1168 * match function. 1169 * <p> 1170 * If the current entry is a subtree, then all paths within the subtree 1171 * are considered to match it. 1172 * 1173 * @param p 1174 * path buffer to test. Callers should ensure the path does not 1175 * end with '/' prior to invocation. 1176 * @param pLen 1177 * number of bytes from <code>buf</code> to test. 1178 * @return < 0 if p is before the current path; 0 if p matches the current 1179 * path; 1 if the current path is past p and p will never match 1180 * again on this tree walk. 1181 */ 1182 public int isPathPrefix(final byte[] p, final int pLen) { 1183 final AbstractTreeIterator t = currentHead; 1184 final byte[] c = t.path; 1185 final int cLen = t.pathLen; 1186 int ci; 1187 1188 for (ci = 0; ci < cLen && ci < pLen; ci++) { 1189 final int c_value = (c[ci] & 0xff) - (p[ci] & 0xff); 1190 if (c_value != 0) 1191 return c_value; 1192 } 1193 1194 if (ci < cLen) { 1195 // Ran out of pattern but we still had current data. 1196 // If c[ci] == '/' then pattern matches the subtree. 1197 // Otherwise we cannot be certain so we return -1. 1198 // 1199 return c[ci] == '/' ? 0 : -1; 1200 } 1201 1202 if (ci < pLen) { 1203 // Ran out of current, but we still have pattern data. 1204 // If p[ci] == '/' then pattern matches this subtree, 1205 // otherwise we cannot be certain so we return -1. 1206 // 1207 return p[ci] == '/' && FileMode.TREE.equals(t.mode) ? 0 : -1; 1208 } 1209 1210 // Both strings are identical. 1211 // 1212 return 0; 1213 } 1214 1215 /** 1216 * Test if the supplied path matches (being suffix of) the current entry's 1217 * path. 1218 * <p> 1219 * This method tests that the supplied path is exactly equal to the current 1220 * entry, or is relative to one of entry's parent directories. It is faster 1221 * to use this method then to use {@link #getPathString()} to first create 1222 * a String object, then test <code>endsWith</code> or some other type of 1223 * string match function. 1224 * 1225 * @param p 1226 * path buffer to test. 1227 * @param pLen 1228 * number of bytes from <code>buf</code> to test. 1229 * @return true if p is suffix of the current path; 1230 * false if otherwise 1231 */ 1232 public boolean isPathSuffix(final byte[] p, final int pLen) { 1233 final AbstractTreeIterator t = currentHead; 1234 final byte[] c = t.path; 1235 final int cLen = t.pathLen; 1236 1237 for (int i = 1; i <= pLen; i++) { 1238 // Pattern longer than current path 1239 if (i > cLen) 1240 return false; 1241 // Current path doesn't match pattern 1242 if (c[cLen - i] != p[pLen - i]) 1243 return false; 1244 } 1245 1246 // Whole pattern tested -> matches 1247 return true; 1248 } 1249 1250 /** 1251 * Get the current subtree depth of this walker. 1252 * 1253 * @return the current subtree depth of this walker. 1254 */ 1255 public int getDepth() { 1256 return depth; 1257 } 1258 1259 /** 1260 * Is the current entry a subtree? 1261 * <p> 1262 * This method is faster then testing the raw mode bits of all trees to see 1263 * if any of them are a subtree. If at least one is a subtree then this 1264 * method will return true. 1265 * 1266 * @return true if {@link #enterSubtree()} will work on the current node. 1267 */ 1268 public boolean isSubtree() { 1269 return FileMode.TREE.equals(currentHead.mode); 1270 } 1271 1272 /** 1273 * Is the current entry a subtree returned after its children? 1274 * 1275 * @return true if the current node is a tree that has been returned after 1276 * its children were already processed. 1277 * @see #isPostOrderTraversal() 1278 */ 1279 public boolean isPostChildren() { 1280 return postChildren && isSubtree(); 1281 } 1282 1283 /** 1284 * Enter into the current subtree. 1285 * <p> 1286 * If the current entry is a subtree this method arranges for its children 1287 * to be returned before the next sibling following the subtree is returned. 1288 * 1289 * @throws org.eclipse.jgit.errors.MissingObjectException 1290 * a subtree was found, but the subtree object does not exist in 1291 * this repository. The repository may be missing objects. 1292 * @throws org.eclipse.jgit.errors.IncorrectObjectTypeException 1293 * a subtree was found, and the subtree id does not denote a 1294 * tree, but instead names some other non-tree type of object. 1295 * The repository may have data corruption. 1296 * @throws org.eclipse.jgit.errors.CorruptObjectException 1297 * the contents of a tree did not appear to be a tree. The 1298 * repository may have data corruption. 1299 * @throws java.io.IOException 1300 * a loose object or pack file could not be read. 1301 */ 1302 public void enterSubtree() throws MissingObjectException, 1303 IncorrectObjectTypeException, CorruptObjectException, IOException { 1304 attrs = null; 1305 final AbstractTreeIterator ch = currentHead; 1306 final AbstractTreeIterator[] tmp = new AbstractTreeIterator[trees.length]; 1307 for (int i = 0; i < trees.length; i++) { 1308 final AbstractTreeIterator t = trees[i]; 1309 final AbstractTreeIterator n; 1310 // If we find a GITLINK when attempting to enter a subtree, then the 1311 // GITLINK must exist as a TREE in the index, meaning the working tree 1312 // entry should be treated as a TREE 1313 if (t.matches == ch && !t.eof() && 1314 (FileMode.TREE.equals(t.mode) 1315 || (FileMode.GITLINK.equals(t.mode) && t.isWorkTree()))) 1316 n = t.createSubtreeIterator(reader, idBuffer); 1317 else 1318 n = t.createEmptyTreeIterator(); 1319 tmp[i] = n; 1320 } 1321 depth++; 1322 advance = false; 1323 System.arraycopy(tmp, 0, trees, 0, trees.length); 1324 } 1325 1326 @SuppressWarnings("unused") 1327 AbstractTreeIterator min() throws CorruptObjectException { 1328 int i = 0; 1329 AbstractTreeIterator minRef = trees[i]; 1330 while (minRef.eof() && ++i < trees.length) 1331 minRef = trees[i]; 1332 if (minRef.eof()) 1333 return minRef; 1334 1335 minRef.matches = minRef; 1336 while (++i < trees.length) { 1337 final AbstractTreeIterator t = trees[i]; 1338 if (t.eof()) 1339 continue; 1340 final int cmp = t.pathCompare(minRef); 1341 if (cmp < 0) { 1342 t.matches = t; 1343 minRef = t; 1344 } else if (cmp == 0) { 1345 t.matches = minRef; 1346 } 1347 } 1348 1349 return minRef; 1350 } 1351 1352 void popEntriesEqual() throws CorruptObjectException { 1353 final AbstractTreeIterator ch = currentHead; 1354 for (int i = 0; i < trees.length; i++) { 1355 final AbstractTreeIterator t = trees[i]; 1356 if (t.matches == ch) { 1357 t.next(1); 1358 t.matches = null; 1359 } 1360 } 1361 } 1362 1363 void skipEntriesEqual() throws CorruptObjectException { 1364 final AbstractTreeIterator ch = currentHead; 1365 for (int i = 0; i < trees.length; i++) { 1366 final AbstractTreeIterator t = trees[i]; 1367 if (t.matches == ch) { 1368 t.skip(); 1369 t.matches = null; 1370 } 1371 } 1372 } 1373 1374 void exitSubtree() { 1375 depth--; 1376 for (int i = 0; i < trees.length; i++) 1377 trees[i] = trees[i].parent; 1378 1379 AbstractTreeIterator minRef = null; 1380 for (final AbstractTreeIterator t : trees) { 1381 if (t.matches != t) 1382 continue; 1383 if (minRef == null || t.pathCompare(minRef) < 0) 1384 minRef = t; 1385 } 1386 currentHead = minRef; 1387 } 1388 1389 private CanonicalTreeParser parserFor(final AnyObjectId id) 1390 throws IncorrectObjectTypeException, IOException { 1391 final CanonicalTreeParser p = new CanonicalTreeParser(); 1392 p.reset(reader, id); 1393 return p; 1394 } 1395 1396 static String pathOf(final AbstractTreeIterator t) { 1397 return RawParseUtils.decode(Constants.CHARSET, t.path, 0, t.pathLen); 1398 } 1399 1400 static String pathOf(final byte[] buf, int pos, int end) { 1401 return RawParseUtils.decode(Constants.CHARSET, buf, pos, end); 1402 } 1403 1404 /** 1405 * Get the tree of that type. 1406 * 1407 * @param type 1408 * of the tree to be queried 1409 * @return the tree of that type or null if none is present. 1410 * @since 4.3 1411 * @param <T> 1412 * a tree type. 1413 */ 1414 public <T extends AbstractTreeIterator> T getTree( 1415 Class<T> type) { 1416 for (int i = 0; i < trees.length; i++) { 1417 AbstractTreeIterator tree = trees[i]; 1418 if (type.isInstance(tree)) { 1419 return type.cast(tree); 1420 } 1421 } 1422 return null; 1423 } 1424 1425 /** 1426 * Inspect config and attributes to return a filtercommand applicable for 1427 * the current path, but without expanding %f occurences 1428 * 1429 * @param filterCommandType 1430 * which type of filterCommand should be executed. E.g. "clean", 1431 * "smudge" 1432 * @return a filter command 1433 * @throws java.io.IOException 1434 * @since 4.2 1435 */ 1436 public String getFilterCommand(String filterCommandType) 1437 throws IOException { 1438 Attributes attributes = getAttributes(); 1439 1440 Attribute f = attributes.get(Constants.ATTR_FILTER); 1441 if (f == null) { 1442 return null; 1443 } 1444 String filterValue = f.getValue(); 1445 if (filterValue == null) { 1446 return null; 1447 } 1448 1449 String filterCommand = getFilterCommandDefinition(filterValue, 1450 filterCommandType); 1451 if (filterCommand == null) { 1452 return null; 1453 } 1454 return filterCommand.replaceAll("%f", //$NON-NLS-1$ 1455 QuotedString.BOURNE.quote((getPathString()))); 1456 } 1457 1458 /** 1459 * Get the filter command how it is defined in gitconfig. The returned 1460 * string may contain "%f" which needs to be replaced by the current path 1461 * before executing the filter command. These filter definitions are cached 1462 * for better performance. 1463 * 1464 * @param filterDriverName 1465 * The name of the filter driver as it is referenced in the 1466 * gitattributes file. E.g. "lfs". For each filter driver there 1467 * may be many commands defined in the .gitconfig 1468 * @param filterCommandType 1469 * The type of the filter command for a specific filter driver. 1470 * May be "clean" or "smudge". 1471 * @return the definition of the command to be executed for this filter 1472 * driver and filter command 1473 */ 1474 private String getFilterCommandDefinition(String filterDriverName, 1475 String filterCommandType) { 1476 String key = filterDriverName + "." + filterCommandType; //$NON-NLS-1$ 1477 String filterCommand = filterCommandsByNameDotType.get(key); 1478 if (filterCommand != null) 1479 return filterCommand; 1480 filterCommand = config.getString(ConfigConstants.CONFIG_FILTER_SECTION, 1481 filterDriverName, filterCommandType); 1482 boolean useBuiltin = config.getBoolean( 1483 ConfigConstants.CONFIG_FILTER_SECTION, 1484 filterDriverName, ConfigConstants.CONFIG_KEY_USEJGITBUILTIN, false); 1485 if (useBuiltin) { 1486 String builtinFilterCommand = Constants.BUILTIN_FILTER_PREFIX 1487 + filterDriverName + '/' + filterCommandType; 1488 if (filterCommands != null 1489 && filterCommands.contains(builtinFilterCommand)) { 1490 filterCommand = builtinFilterCommand; 1491 } 1492 } 1493 if (filterCommand != null) { 1494 filterCommandsByNameDotType.put(key, filterCommand); 1495 } 1496 return filterCommand; 1497 } 1498 }