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