Always initialize TreeWalk with a single empty tree
[egit/zawir.git] / org.spearce.jgit / src / org / spearce / jgit / treewalk / TreeWalk.java
blobd7927c77e6124a814aec50ac4a6e0e327d1ee13b
1 /*
2 * Copyright (C) 2008 Shawn Pearce <spearce@spearce.org>
4 * This library is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU General Public
6 * License, version 2, as published by the Free Software Foundation.
8 * This library is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
11 * General Public License for more details.
13 * You should have received a copy of the GNU General Public
14 * License along with this library; if not, write to the Free Software
15 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301
17 package org.spearce.jgit.treewalk;
19 import java.io.IOException;
20 import java.io.UnsupportedEncodingException;
21 import java.util.Collections;
23 import org.spearce.jgit.errors.CorruptObjectException;
24 import org.spearce.jgit.errors.IncorrectObjectTypeException;
25 import org.spearce.jgit.errors.MissingObjectException;
26 import org.spearce.jgit.errors.StopWalkException;
27 import org.spearce.jgit.lib.Constants;
28 import org.spearce.jgit.lib.FileMode;
29 import org.spearce.jgit.lib.ObjectId;
30 import org.spearce.jgit.lib.Repository;
31 import org.spearce.jgit.revwalk.RevTree;
32 import org.spearce.jgit.treewalk.filter.PathFilterGroup;
33 import org.spearce.jgit.treewalk.filter.TreeFilter;
35 /**
36 * Walks one or more {@link AbstractTreeIterator}s in parallel.
37 * <p>
38 * This class can perform n-way differences across as many trees as necessary.
39 * <p>
40 * A TreeWalk instance can only be used once to generate results. Running a
41 * second time requires creating a new TreeWalk instance, or invoking
42 * {@link #reset()} and adding new trees before starting again. Resetting an
43 * existing instance may be faster for some applications as some internal
44 * buffers may be recycled.
45 * <p>
46 * TreeWalk instances are not thread-safe. Applications must either restrict
47 * usage of a TreeWalk instance to a single thread, or implement their own
48 * synchronization at a higher level.
49 * <p>
50 * Multiple simultaneous TreeWalk instances per {@link Repository} are
51 * permitted, even from concurrent threads.
53 public class TreeWalk {
54 /**
55 * Open a tree walk and filter to exactly one path.
56 * <p>
57 * The returned tree walk is already positioned on the requested path, so
58 * the caller should not need to invoke {@link #next()} unless they are
59 * looking for a possible directory/file name conflict.
61 * @param db
62 * repository to read tree object data from.
63 * @param path
64 * single path to advance the tree walk instance into.
65 * @param trees
66 * one or more trees to walk through.
67 * @return a new tree walk configured for exactly this one path; null if no
68 * path was found in any of the trees.
69 * @throws IOException
70 * reading a pack file or loose object failed.
71 * @throws CorruptObjectException
72 * an tree object could not be read as its data stream did not
73 * appear to be a tree, or could not be inflated.
74 * @throws IncorrectObjectTypeException
75 * an object we expected to be a tree was not a tree.
76 * @throws MissingObjectException
77 * a tree object was not found.
79 public static TreeWalk forPath(final Repository db, final String path,
80 final ObjectId[] trees) throws MissingObjectException,
81 IncorrectObjectTypeException, CorruptObjectException, IOException {
82 final TreeWalk r = new TreeWalk(db);
83 r.setFilter(PathFilterGroup.createFromStrings(Collections
84 .singleton(path)));
85 r.setRecursive(r.getFilter().shouldBeRecursive());
86 r.reset(trees);
87 return r.next() ? r : null;
90 /**
91 * Open a tree walk and filter to exactly one path.
92 * <p>
93 * The returned tree walk is already positioned on the requested path, so
94 * the caller should not need to invoke {@link #next()} unless they are
95 * looking for a possible directory/file name conflict.
97 * @param db
98 * repository to read tree object data from.
99 * @param path
100 * single path to advance the tree walk instance into.
101 * @param tree
102 * the single tree to walk through.
103 * @return a new tree walk configured for exactly this one path; null if no
104 * path was found in any of the trees.
105 * @throws IOException
106 * reading a pack file or loose object failed.
107 * @throws CorruptObjectException
108 * an tree object could not be read as its data stream did not
109 * appear to be a tree, or could not be inflated.
110 * @throws IncorrectObjectTypeException
111 * an object we expected to be a tree was not a tree.
112 * @throws MissingObjectException
113 * a tree object was not found.
115 public static TreeWalk forPath(final Repository db, final String path,
116 final RevTree tree) throws MissingObjectException,
117 IncorrectObjectTypeException, CorruptObjectException, IOException {
118 return forPath(db, path, new ObjectId[] { tree });
121 private final Repository db;
123 private TreeFilter filter;
125 private AbstractTreeIterator[] trees;
127 private boolean recursive;
129 private int depth;
131 private boolean advance;
133 private AbstractTreeIterator currentHead;
136 * Create a new tree walker for a given repository.
138 * @param repo
139 * the repository the walker will obtain data from.
141 public TreeWalk(final Repository repo) {
142 db = repo;
143 filter = TreeFilter.ALL;
144 trees = new AbstractTreeIterator[] { new EmptyTreeIterator() };
148 * Get the repository this tree walker is reading from.
150 * @return the repository configured when the walker was created.
152 public Repository getRepository() {
153 return db;
157 * Get the currently configured filter.
159 * @return the current filter. Never null as a filter is always needed.
161 public TreeFilter getFilter() {
162 return filter;
166 * Set the tree entry filter for this walker.
167 * <p>
168 * Multiple filters may be combined by constructing an arbitrary tree of
169 * <code>AndTreeFilter</code> or <code>OrTreeFilter</code> instances to
170 * describe the boolean expression required by the application. Custom
171 * filter implementations may also be constructed by applications.
172 * <p>
173 * Note that filters are not thread-safe and may not be shared by concurrent
174 * TreeWalk instances. Every TreeWalk must be supplied its own unique
175 * filter, unless the filter implementation specifically states it is (and
176 * always will be) thread-safe. Callers may use {@link TreeFilter#clone()}
177 * to create a unique filter tree for this TreeWalk instance.
179 * @param newFilter
180 * the new filter. If null the special {@link TreeFilter#ALL}
181 * filter will be used instead, as it matches every entry.
182 * @see org.spearce.jgit.treewalk.filter.AndTreeFilter
183 * @see org.spearce.jgit.treewalk.filter.OrTreeFilter
185 public void setFilter(final TreeFilter newFilter) {
186 filter = newFilter != null ? newFilter : TreeFilter.ALL;
190 * Is this walker automatically entering into subtrees?
191 * <p>
192 * If the walker is recursive then the caller will not see a subtree node
193 * and instead will only receive file nodes in all relevant subtrees.
195 * @return true if automatically entering subtrees is enabled.
197 public boolean isRecursive() {
198 return recursive;
202 * Set the walker to enter (or not enter) subtrees automatically.
203 * <p>
204 * If recursive mode is enabled the walker will hide subtree nodes from the
205 * calling application and will produce only file level nodes. If a tree
206 * (directory) is deleted then all of the file level nodes will appear to be
207 * deleted, recursively, through as many levels as necessary to account for
208 * all entries.
210 * @param b
211 * true to skip subtree nodes and only obtain files nodes.
213 public void setRecursive(final boolean b) {
214 recursive = b;
217 /** Reset this walker so new tree iterators can be added to it. */
218 public void reset() {
219 trees = new AbstractTreeIterator[0];
220 advance = false;
221 depth = 0;
225 * Reset this walker to run over a set of existing trees.
227 * @param ids
228 * the trees we need to parse. The walker will execute over this
229 * many parallel trees if the reset is successful.
230 * @throws MissingObjectException
231 * the given tree object does not exist in this repository.
232 * @throws IncorrectObjectTypeException
233 * the given object id does not denote a tree, but instead names
234 * some other non-tree type of object. Note that commits are not
235 * trees, even if they are sometimes called a "tree-ish".
236 * @throws CorruptObjectException
237 * the object claimed to be a tree, but its contents did not
238 * appear to be a tree. The repository may have data corruption.
239 * @throws IOException
240 * a loose object or pack file could not be read.
242 public void reset(final ObjectId[] ids) throws MissingObjectException,
243 IncorrectObjectTypeException, CorruptObjectException, IOException {
244 final int oldLen = trees.length;
245 final int newLen = ids.length;
246 final AbstractTreeIterator[] r = newLen == oldLen ? trees
247 : new AbstractTreeIterator[newLen];
248 for (int i = 0; i < newLen; i++) {
249 AbstractTreeIterator o;
251 if (i < oldLen) {
252 o = trees[i];
253 while (o.parent != null)
254 o = o.parent;
255 if (o instanceof CanonicalTreeParser) {
256 o.matches = null;
257 ((CanonicalTreeParser) o).reset(db, ids[i]);
258 o.next();
259 r[i] = o;
260 continue;
264 o = parserFor(ids[i]);
265 o.next();
266 r[i] = o;
269 trees = r;
270 advance = false;
271 depth = 0;
275 * Add an already existing tree object for walking.
276 * <p>
277 * The position of this tree is returned to the caller, in case the caller
278 * has lost track of the order they added the trees into the walker.
280 * @param id
281 * identity of the tree object the caller wants walked.
282 * @return position of this tree within the walker.
283 * @throws MissingObjectException
284 * the given tree object does not exist in this repository.
285 * @throws IncorrectObjectTypeException
286 * the given object id does not denote a tree, but instead names
287 * some other non-tree type of object. Note that commits are not
288 * trees, even if they are sometimes called a "tree-ish".
289 * @throws CorruptObjectException
290 * the object claimed to be a tree, but its contents did not
291 * appear to be a tree. The repository may have data corruption.
292 * @throws IOException
293 * a loose object or pack file could not be read.
295 public int addTree(final ObjectId id) throws MissingObjectException,
296 IncorrectObjectTypeException, CorruptObjectException, IOException {
297 return addTree(parserFor(id));
301 * Add an already created tree iterator for walking.
302 * <p>
303 * The position of this tree is returned to the caller, in case the caller
304 * has lost track of the order they added the trees into the walker.
306 * @param p
307 * an iterator to walk over. The iterator should be new, with no
308 * parent, and should still be positioned before the first entry.
309 * @return position of this tree within the walker.
310 * @throws CorruptObjectException
311 * the iterator was unable to obtain its first entry, due to
312 * possible data corruption within the backing data store.
314 public int addTree(final AbstractTreeIterator p)
315 throws CorruptObjectException {
316 final int n = trees.length;
317 final AbstractTreeIterator[] newTrees = new AbstractTreeIterator[n + 1];
319 System.arraycopy(trees, 0, newTrees, 0, n);
320 newTrees[n] = p;
321 p.matches = null;
322 p.next();
324 trees = newTrees;
325 return n;
329 * Get the number of trees known to this walker.
331 * @return the total number of trees this walker is iterating over.
333 public int getTreeCount() {
334 return trees.length;
338 * Advance this walker to the next relevant entry.
340 * @return true if there is an entry available; false if all entries have
341 * been walked and the walk of this set of tree iterators is over.
342 * @throws MissingObjectException
343 * {@link #isRecursive()} was enabled, a subtree was found, but
344 * the subtree object does not exist in this repository. The
345 * repository may be missing objects.
346 * @throws IncorrectObjectTypeException
347 * {@link #isRecursive()} was enabled, a subtree was found, and
348 * the subtree id does not denote a tree, but instead names some
349 * other non-tree type of object. The repository may have data
350 * corruption.
351 * @throws CorruptObjectException
352 * the contents of a tree did not appear to be a tree. The
353 * repository may have data corruption.
354 * @throws IOException
355 * a loose object or pack file could not be read.
357 public boolean next() throws MissingObjectException,
358 IncorrectObjectTypeException, CorruptObjectException, IOException {
359 try {
360 if (advance) {
361 advance = false;
362 popEntriesEqual();
365 for (;;) {
366 final AbstractTreeIterator t = min();
367 if (t.eof()) {
368 if (depth > 0) {
369 exitSubtree();
370 continue;
372 return false;
375 currentHead = t;
376 if (!filter.include(this)) {
377 popEntriesEqual();
378 continue;
381 if (recursive && FileMode.TREE.equals(t.mode)) {
382 enterSubtree();
383 continue;
386 advance = true;
387 return true;
389 } catch (StopWalkException stop) {
390 return false;
395 * Obtain the raw {@link FileMode} bits for the current entry.
396 * <p>
397 * Every added tree supplies mode bits, even if the tree does not contain
398 * the current entry. In the latter case {@link FileMode#MISSING}'s mode
399 * bits (0) are returned.
401 * @param nth
402 * tree to obtain the mode bits from.
403 * @return mode bits for the current entry of the nth tree.
404 * @see FileMode#fromBits(int)
406 public int getRawMode(final int nth) {
407 final AbstractTreeIterator t = trees[nth];
408 return t.matches == currentHead ? t.mode : 0;
412 * Obtain the ObjectId for the current entry.
413 * <p>
414 * Using this method to compare ObjectId values between trees of this walker
415 * is very inefficient. Applications should try to use
416 * {@link #idEqual(int, int)} whenever possible.
417 * <p>
418 * Every tree supplies an object id, even if the tree does not contain the
419 * current entry. In the latter case {@link ObjectId#zeroId()} is returned.
421 * @param nth
422 * tree to obtain the object identifier from.
423 * @return object identifier for the current tree entry.
424 * @see #idEqual(int, int)
426 public ObjectId getObjectId(final int nth) {
427 final AbstractTreeIterator t = trees[nth];
428 return t.matches == currentHead ? t.getEntryObjectId() : ObjectId
429 .zeroId();
433 * Compare two tree's current ObjectId values for equality.
435 * @param nthA
436 * first tree to compare the object id from.
437 * @param nthB
438 * second tree to compare the object id from.
439 * @return result of
440 * <code>getObjectId(nthA).equals(getObjectId(nthB))</code>.
441 * @see #getObjectId(int)
443 public boolean idEqual(final int nthA, final int nthB) {
444 final AbstractTreeIterator ch = currentHead;
445 final AbstractTreeIterator a = trees[nthA];
446 final AbstractTreeIterator b = trees[nthB];
447 return a.matches == ch && b.matches == ch && a.idEqual(b);
451 * Get the current entry's complete path.
452 * <p>
453 * This method is not very efficient and is primarily meant for debugging
454 * and final output generation. Applications should try to avoid calling it,
455 * and if invoked do so only once per interesting entry, where the name is
456 * absolutely required for correct function.
458 * @return complete path of the current entry, from the root of the
459 * repository. If the current entry is in a subtree there will be at
460 * least one '/' in the returned string.
462 public String getPathString() {
463 return pathOf(currentHead);
467 * Test if the supplied path matches the current entry's path.
468 * <p>
469 * This method tests that the supplied path is exactly equal to the current
470 * entry, or is one of its parent directories. It is faster to use this
471 * method then to use {@link #getPathString()} to first create a String
472 * object, then test <code>startsWith</code> or some other type of string
473 * match function.
475 * @param p
476 * path buffer to test. Callers should ensure the path does not
477 * end with '/' prior to invocation.
478 * @param pLen
479 * number of bytes from <code>buf</code> to test.
480 * @return < 0 if p is before the current path; 0 if p matches the current
481 * path; 1 if the current path is past p and p will never match
482 * again on this tree walk.
484 public int isPathPrefix(final byte[] p, final int pLen) {
485 final AbstractTreeIterator t = currentHead;
486 final byte[] c = t.path;
487 final int cLen = t.pathLen;
488 int ci;
490 for (ci = 0; ci < cLen && ci < pLen; ci++) {
491 final int c_value = (c[ci] & 0xff) - (p[ci] & 0xff);
492 if (c_value != 0)
493 return c_value;
496 if (ci < cLen) {
497 // Ran out of pattern but we still had current data.
498 // If c[ci] == '/' then pattern matches the subtree.
499 // Otherwise we cannot be certain so we return -1.
501 return c[ci] == '/' ? 0 : -1;
504 if (ci < pLen) {
505 // Ran out of current, but we still have pattern data.
506 // If p[ci] == '/' then pattern matches this subtree,
507 // otherwise we cannot be certain so we return -1.
509 return p[ci] == '/' ? 0 : -1;
512 // Both strings are identical.
514 return 0;
518 * Is the current entry a subtree?
519 * <p>
520 * This method is faster then testing the raw mode bits of all trees to see
521 * if any of them are a subtree. If at least one is a subtree then this
522 * method will return true.
524 * @return true if {@link #enterSubtree()} will work on the current node.
526 public boolean isSubtree() {
527 return FileMode.TREE.equals(currentHead.mode);
531 * Enter into the current subtree.
532 * <p>
533 * If the current entry is a subtree this method arranges for its children
534 * to be returned before the next sibling following the subtree is returned.
536 * @throws MissingObjectException
537 * a subtree was found, but the subtree object does not exist in
538 * this repository. The repository may be missing objects.
539 * @throws IncorrectObjectTypeException
540 * a subtree was found, and the subtree id does not denote a
541 * tree, but instead names some other non-tree type of object.
542 * The repository may have data corruption.
543 * @throws CorruptObjectException
544 * the contents of a tree did not appear to be a tree. The
545 * repository may have data corruption.
546 * @throws IOException
547 * a loose object or pack file could not be read.
549 public void enterSubtree() throws MissingObjectException,
550 IncorrectObjectTypeException, CorruptObjectException, IOException {
551 final AbstractTreeIterator[] tmp = new AbstractTreeIterator[trees.length];
552 for (int i = 0; i < trees.length; i++) {
553 final AbstractTreeIterator t = trees[i];
554 final AbstractTreeIterator n;
555 if (!t.eof() && FileMode.TREE.equals(t.mode))
556 n = t.createSubtreeIterator(db);
557 else
558 n = new EmptyTreeIterator(t);
559 n.next();
560 tmp[i] = n;
562 depth++;
563 advance = false;
564 System.arraycopy(tmp, 0, trees, 0, trees.length);
567 private AbstractTreeIterator min() {
568 int i = 0;
569 AbstractTreeIterator minRef = trees[i];
570 while (minRef.eof() && ++i < trees.length)
571 minRef = trees[i];
572 if (minRef.eof())
573 return minRef;
575 minRef.matches = minRef;
576 while (++i < trees.length) {
577 final AbstractTreeIterator t = trees[i];
578 if (t.eof())
579 continue;
580 final int cmp = t.pathCompare(minRef);
581 if (cmp < 0) {
582 t.matches = t;
583 minRef = t;
584 } else if (cmp == 0) {
585 t.matches = minRef;
589 return minRef;
592 private void popEntriesEqual() throws CorruptObjectException {
593 final AbstractTreeIterator ch = currentHead;
594 for (int i = 0; i < trees.length; i++) {
595 final AbstractTreeIterator t = trees[i];
596 if (t.matches == ch) {
597 t.next();
598 t.matches = null;
603 private void exitSubtree() throws CorruptObjectException {
604 depth--;
605 for (int i = 0; i < trees.length; i++)
606 trees[i] = trees[i].parent;
607 currentHead = min();
608 popEntriesEqual();
611 private CanonicalTreeParser parserFor(final ObjectId id)
612 throws IncorrectObjectTypeException, IOException {
613 final CanonicalTreeParser p = new CanonicalTreeParser();
614 p.reset(db, id);
615 return p;
618 private static String pathOf(final AbstractTreeIterator t) {
619 try {
620 return new String(t.path, 0, t.pathLen,
621 Constants.CHARACTER_ENCODING);
622 } catch (UnsupportedEncodingException uee) {
623 throw new RuntimeException("JVM doesn't support "
624 + Constants.CHARACTER_ENCODING, uee);