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