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