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