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