2
This code is derived from jgit (http://eclipse.org/jgit).
3
Copyright owners are documented in jgit's IP log.
5
This program and the accompanying materials are made available
6
under the terms of the Eclipse Distribution License v1.0 which
7
accompanies this distribution, is reproduced below, and is
8
available at http://www.eclipse.org/org/documents/edl-v10.php
12
Redistribution and use in source and binary forms, with or
13
without modification, are permitted provided that the following
16
- Redistributions of source code must retain the above copyright
17
notice, this list of conditions and the following disclaimer.
19
- Redistributions in binary form must reproduce the above
20
copyright notice, this list of conditions and the following
21
disclaimer in the documentation and/or other materials provided
22
with the distribution.
24
- Neither the name of the Eclipse Foundation, Inc. nor the
25
names of its contributors may be used to endorse or promote
26
products derived from this software without specific prior
29
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
30
CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
31
INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
32
OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
33
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
34
CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
35
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
36
NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
37
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
38
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
39
STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
40
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
41
ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
48
namespace NGit.Treewalk
50
/// <summary>Specialized TreeWalk to detect directory-file (D/F) name conflicts.</summary>
52
/// Specialized TreeWalk to detect directory-file (D/F) name conflicts.
54
/// Due to the way a Git tree is organized the standard
55
/// <see cref="TreeWalk">TreeWalk</see>
57
/// easily find a D/F conflict when merging two or more trees together. In the
58
/// standard TreeWalk the file will be returned first, and then much later the
59
/// directory will be returned. This makes it impossible for the application to
60
/// efficiently detect and handle the conflict.
62
/// Using this walk implementation causes the directory to report earlier than
63
/// usual, at the same time as the non-directory entry. This permits the
64
/// application to handle the D/F conflict in a single step. The directory is
65
/// returned only once, so it does not get returned later in the iteration.
67
/// When a D/F conflict is detected
68
/// <see cref="TreeWalk.IsSubtree()">TreeWalk.IsSubtree()</see>
71
/// <see cref="TreeWalk.EnterSubtree()">TreeWalk.EnterSubtree()</see>
72
/// will recurse into the subtree, no matter
73
/// which iterator originally supplied the subtree.
75
/// Because conflicted directories report early, using this walk implementation
77
/// <see cref="NGit.Dircache.DirCacheBuilder">NGit.Dircache.DirCacheBuilder</see>
78
/// may cause the automatic resorting to
79
/// run and fix the entry ordering.
81
/// This walk implementation requires more CPU to implement a look-ahead and a
82
/// look-behind to merge a D/F pair together, or to skip a previously reported
83
/// directory. In typical Git repositories the look-ahead cost is 0 and the
84
/// look-behind doesn't trigger, as users tend not to create trees which contain
85
/// both "foo" as a directory and "foo.c" as a file.
87
/// In the worst-case however several thousand look-ahead steps per walk step may
88
/// be necessary, making the overhead quite significant. Since this worst-case
89
/// should never happen this walk implementation has made the time/space tradeoff
90
/// in favor of more-time/less-space, as that better suits the typical case.
92
public class NameConflictTreeWalk : TreeWalk
94
private static readonly int TREE_MODE = FileMode.TREE.GetBits();
96
private bool fastMinHasMatch;
98
private AbstractTreeIterator dfConflict;
100
/// <summary>Create a new tree walker for a given repository.</summary>
101
/// <remarks>Create a new tree walker for a given repository.</remarks>
102
/// <param name="repo">the repository the walker will obtain data from.</param>
103
public NameConflictTreeWalk(Repository repo) : this(repo.NewObjectReader())
107
/// <summary>Create a new tree walker for a given repository.</summary>
108
/// <remarks>Create a new tree walker for a given repository.</remarks>
109
/// <param name="or">the reader the walker will obtain tree data from.</param>
110
public NameConflictTreeWalk(ObjectReader or) : base(or)
114
/// <exception cref="NGit.Errors.CorruptObjectException"></exception>
115
internal override AbstractTreeIterator Min()
119
AbstractTreeIterator minRef = FastMin();
126
if (SkipEntry(minRef))
128
foreach (AbstractTreeIterator t in trees)
130
if (t.matches == minRef)
140
return CombineDF(minRef);
144
private AbstractTreeIterator FastMin()
146
fastMinHasMatch = true;
148
AbstractTreeIterator minRef = trees[i];
149
while (minRef.Eof && ++i < trees.Length)
157
bool hasConflict = false;
158
minRef.matches = minRef;
159
while (++i < trees.Length)
161
AbstractTreeIterator t = trees[i];
166
int cmp = t.PathCompare(minRef);
169
if (fastMinHasMatch && IsTree(minRef) && !IsTree(t) && NameEqual(minRef, t))
171
// We used to be at a tree, but now we are at a file
172
// with the same name. Allow the file to match the
180
fastMinHasMatch = false;
189
// Exact name/mode match is best.
195
if (fastMinHasMatch && IsTree(t) && !IsTree(minRef) && NameEqual(t, minRef))
197
// The minimum is a file (non-tree) but the next entry
198
// of this iterator is a tree whose name matches our file.
199
// This is a classic D/F conflict and commonly occurs like
200
// this, with no gaps in between the file and directory.
202
// Use the tree as the minimum instead (see combineDF).
204
for (int k = 0; k < i; k++)
206
AbstractTreeIterator p = trees[k];
207
if (p.matches == minRef)
218
fastMinHasMatch = false;
223
if (hasConflict && fastMinHasMatch && dfConflict == null)
230
private static bool NameEqual(AbstractTreeIterator a, AbstractTreeIterator b)
232
return a.PathCompare(b, TREE_MODE) == 0;
235
private static bool IsTree(AbstractTreeIterator p)
237
return FileMode.TREE.Equals(p.mode);
240
/// <exception cref="NGit.Errors.CorruptObjectException"></exception>
241
private bool SkipEntry(AbstractTreeIterator minRef)
243
// A tree D/F may have been handled earlier. We need to
244
// not report this path if it has already been reported.
246
foreach (AbstractTreeIterator t in trees)
248
if (t.matches == minRef || t.First)
257
int cmp = t.PathCompare(minRef, 0);
260
// We have already seen this "$path" before. Skip it.
267
if (cmp < 0 || t.First)
269
// We cannot find "$path" in t; it will never appear.
277
// We have never seen the current path before.
282
/// <exception cref="NGit.Errors.CorruptObjectException"></exception>
283
private AbstractTreeIterator CombineDF(AbstractTreeIterator minRef)
285
// Look for a possible D/F conflict forward in the tree(s)
286
// as there may be a "$path/" which matches "$path". Make
287
// such entries match this entry.
289
AbstractTreeIterator treeMatch = null;
290
foreach (AbstractTreeIterator t in trees)
292
if (t.matches == minRef || t.Eof)
298
int cmp = t.PathCompare(minRef, TREE_MODE);
301
// The "$path/" may still appear later.
307
t.Back(t.matchShift);
316
// We have a conflict match here.
324
// A conflict match is not possible.
326
if (t.matchShift != 0)
328
t.Back(t.matchShift);
336
if (treeMatch != null)
338
// If we do have a conflict use one of the directory
339
// matching iterators instead of the file iterator.
340
// This way isSubtree is true and isRecursive works.
342
foreach (AbstractTreeIterator t_1 in trees)
344
if (t_1.matches == minRef)
346
t_1.matches = treeMatch;
349
if (dfConflict == null)
351
dfConflict = treeMatch;
358
/// <exception cref="NGit.Errors.CorruptObjectException"></exception>
359
internal override void PopEntriesEqual()
361
AbstractTreeIterator ch = currentHead;
362
for (int i = 0; i < trees.Length; i++)
364
AbstractTreeIterator t = trees[i];
367
if (t.matchShift == 0)
373
t.Back(t.matchShift);
379
if (ch == dfConflict)
385
/// <exception cref="NGit.Errors.CorruptObjectException"></exception>
386
internal override void SkipEntriesEqual()
388
AbstractTreeIterator ch = currentHead;
389
for (int i = 0; i < trees.Length; i++)
391
AbstractTreeIterator t = trees[i];
394
if (t.matchShift == 0)
400
t.Back(t.matchShift);
406
if (ch == dfConflict)
412
/// <summary>True if the current entry is covered by a directory/file conflict.</summary>
414
/// True if the current entry is covered by a directory/file conflict.
415
/// This means that for some prefix of the current entry's path, this walk
416
/// has detected a directory/file conflict. Also true if the current entry
417
/// itself is a directory/file conflict.
418
/// Example: If this TreeWalk points to foo/bar/a.txt and this method returns
419
/// true then you know that either for path foo or for path foo/bar files and
420
/// folders were detected.
423
/// <code>true</code> if the current entry is covered by a
424
/// directory/file conflict, <code>false</code> otherwise
426
public virtual bool IsDirectoryFileConflict()
428
return dfConflict != null;