2 * Copyright (C) 2008, Google Inc.
6 * Redistribution and use in source and binary forms, with or
7 * without modification, are permitted provided that the following
10 * - Redistributions of source code must retain the above copyright
11 * notice, this list of conditions and the following disclaimer.
13 * - Redistributions in binary form must reproduce the above
14 * copyright notice, this list of conditions and the following
15 * disclaimer in the documentation and/or other materials provided
16 * with the distribution.
18 * - Neither the name of the Git Development Community nor the
19 * names of its contributors may be used to endorse or promote
20 * products derived from this software without specific prior
23 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
24 * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
25 * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
26 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
27 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
28 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
29 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
30 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
31 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
32 * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
33 * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
34 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
35 * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
38 package org
.spearce
.jgit
.treewalk
;
40 import org
.spearce
.jgit
.dircache
.DirCacheBuilder
;
41 import org
.spearce
.jgit
.errors
.CorruptObjectException
;
42 import org
.spearce
.jgit
.lib
.FileMode
;
43 import org
.spearce
.jgit
.lib
.Repository
;
46 * Specialized TreeWalk to detect directory-file (D/F) name conflicts.
48 * Due to the way a Git tree is organized the standard {@link TreeWalk} won't
49 * easily find a D/F conflict when merging two or more trees together. In the
50 * standard TreeWalk the file will be returned first, and then much later the
51 * directory will be returned. This makes it impossible for the application to
52 * efficiently detect and handle the conflict.
54 * Using this walk implementation causes the directory to report earlier than
55 * usual, at the same time as the non-directory entry. This permits the
56 * application to handle the D/F conflict in a single step. The directory is
57 * returned only once, so it does not get returned later in the iteration.
59 * When a D/F conflict is detected {@link TreeWalk#isSubtree()} will return true
60 * and {@link TreeWalk#enterSubtree()} will recurse into the subtree, no matter
61 * which iterator originally supplied the subtree.
63 * Because conflicted directories report early, using this walk implementation
64 * to populate a {@link DirCacheBuilder} may cause the automatic resorting to
65 * run and fix the entry ordering.
67 * This walk implementation requires more CPU to implement a look-ahead and a
68 * look-behind to merge a D/F pair together, or to skip a previously reported
69 * directory. In typical Git repositories the look-ahead cost is 0 and the
70 * look-behind doesn't trigger, as users tend not to create trees which contain
71 * both "foo" as a directory and "foo.c" as a file.
73 * In the worst-case however several thousand look-ahead steps per walk step may
74 * be necessary, making the overhead quite significant. Since this worst-case
75 * should never happen this walk implementation has made the time/space tradeoff
76 * in favor of more-time/less-space, as that better suits the typical case.
78 public class NameConflictTreeWalk
extends TreeWalk
{
79 private static final int TREE_MODE
= FileMode
.TREE
.getBits();
81 private boolean fastMinHasMatch
;
84 * Create a new tree walker for a given repository.
87 * the repository the walker will obtain data from.
89 public NameConflictTreeWalk(final Repository repo
) {
94 AbstractTreeIterator
min() throws CorruptObjectException
{
96 final AbstractTreeIterator minRef
= fastMin();
100 if (isTree(minRef
)) {
101 if (skipEntry(minRef
)) {
102 for (final AbstractTreeIterator t
: trees
) {
103 if (t
.matches
== minRef
) {
113 return combineDF(minRef
);
117 private AbstractTreeIterator
fastMin() {
118 fastMinHasMatch
= true;
121 AbstractTreeIterator minRef
= trees
[i
];
122 while (minRef
.eof() && ++i
< trees
.length
)
127 minRef
.matches
= minRef
;
128 while (++i
< trees
.length
) {
129 final AbstractTreeIterator t
= trees
[i
];
133 final int cmp
= t
.pathCompare(minRef
);
135 if (fastMinHasMatch
&& isTree(minRef
) && !isTree(t
)
136 && nameEqual(minRef
, t
)) {
137 // We used to be at a tree, but now we are at a file
138 // with the same name. Allow the file to match the
143 fastMinHasMatch
= false;
147 } else if (cmp
== 0) {
148 // Exact name/mode match is best.
151 } else if (fastMinHasMatch
&& isTree(t
) && !isTree(minRef
)
152 && nameEqual(t
, minRef
)) {
153 // The minimum is a file (non-tree) but the next entry
154 // of this iterator is a tree whose name matches our file.
155 // This is a classic D/F conflict and commonly occurs like
156 // this, with no gaps in between the file and directory.
158 // Use the tree as the minimum instead (see combineDF).
161 for (int k
= 0; k
< i
; k
++) {
162 final AbstractTreeIterator p
= trees
[k
];
163 if (p
.matches
== minRef
)
169 fastMinHasMatch
= false;
175 private static boolean nameEqual(final AbstractTreeIterator a
,
176 final AbstractTreeIterator b
) {
177 return a
.pathCompare(b
, TREE_MODE
) == 0;
180 private static boolean isTree(final AbstractTreeIterator p
) {
181 return FileMode
.TREE
.equals(p
.mode
);
184 private boolean skipEntry(final AbstractTreeIterator minRef
)
185 throws CorruptObjectException
{
186 // A tree D/F may have been handled earlier. We need to
187 // not report this path if it has already been reported.
189 for (final AbstractTreeIterator t
: trees
) {
190 if (t
.matches
== minRef
|| t
.first())
198 final int cmp
= t
.pathCompare(minRef
, 0);
200 // We have already seen this "$path" before. Skip it.
204 } else if (cmp
< 0 || t
.first()) {
205 // We cannot find "$path" in t; it will never appear.
213 // We have never seen the current path before.
218 private AbstractTreeIterator
combineDF(final AbstractTreeIterator minRef
)
219 throws CorruptObjectException
{
220 // Look for a possible D/F conflict forward in the tree(s)
221 // as there may be a "$path/" which matches "$path". Make
222 // such entries match this entry.
224 AbstractTreeIterator treeMatch
= null;
225 for (final AbstractTreeIterator t
: trees
) {
226 if (t
.matches
== minRef
|| t
.eof())
230 final int cmp
= t
.pathCompare(minRef
, TREE_MODE
);
232 // The "$path/" may still appear later.
237 t
.back(t
.matchShift
);
241 } else if (cmp
== 0) {
242 // We have a conflict match here.
248 // A conflict match is not possible.
250 if (t
.matchShift
!= 0) {
251 t
.back(t
.matchShift
);
259 if (treeMatch
!= null) {
260 // If we do have a conflict use one of the directory
261 // matching iterators instead of the file iterator.
262 // This way isSubtree is true and isRecursive works.
264 for (final AbstractTreeIterator t
: trees
)
265 if (t
.matches
== minRef
)
266 t
.matches
= treeMatch
;
274 void popEntriesEqual() throws CorruptObjectException
{
275 final AbstractTreeIterator ch
= currentHead
;
276 for (int i
= 0; i
< trees
.length
; i
++) {
277 final AbstractTreeIterator t
= trees
[i
];
278 if (t
.matches
== ch
) {
279 if (t
.matchShift
== 0)
282 t
.back(t
.matchShift
);
291 void skipEntriesEqual() throws CorruptObjectException
{
292 final AbstractTreeIterator ch
= currentHead
;
293 for (int i
= 0; i
< trees
.length
; i
++) {
294 final AbstractTreeIterator t
= trees
[i
];
295 if (t
.matches
== ch
) {
296 if (t
.matchShift
== 0)
299 t
.back(t
.matchShift
);