Exclude file matching: fix backtracking on match failures after **

** matching always tries the empty match first. If a mismatch occurs
later, the ** must be extended by exactly one segment and matching must
resume with the matcher following the ** matcher.

Bug: 520920
Change-Id: Id019ad1c773bd645ae92e398021952f8e961f45c
Signed-off-by: Thomas Wolf <thomas.wolf@paranor.ch>
This commit is contained in:
Thomas Wolf 2017-08-13 15:08:16 +02:00 committed by Matthias Sohn
parent d80b999c76
commit d031b64667
4 changed files with 150 additions and 1 deletions

View File

@ -451,6 +451,50 @@ public void testDirectoryMatchSubRecursive() throws Exception {
endWalk();
}
@Test
public void testDirectoryMatchSubRecursiveBacktrack() throws Exception {
setupRepo(null, null, "**/sub/new/ bar", null);
writeTrashFile("sub/new/foo.txt", "1");
writeTrashFile("foo/sub/new/foo.txt", "2");
writeTrashFile("sub/sub/new/foo.txt", "3");
walk = beginWalk();
assertIteration(F, ".gitattributes");
assertIteration(D, "foo");
assertIteration(D, "foo/sub");
assertIteration(D, "foo/sub/new", attrs("bar"));
assertIteration(F, "foo/sub/new/foo.txt");
assertIteration(D, "sub");
assertIteration(F, "sub/a.txt");
assertIteration(D, "sub/new", attrs("bar"));
assertIteration(F, "sub/new/foo.txt");
assertIteration(D, "sub/sub");
assertIteration(D, "sub/sub/new", attrs("bar"));
assertIteration(F, "sub/sub/new/foo.txt");
endWalk();
}
@Test
public void testDirectoryMatchSubRecursiveBacktrack2() throws Exception {
setupRepo(null, null, "**/**/sub/new/ bar", null);
writeTrashFile("sub/new/foo.txt", "1");
writeTrashFile("foo/sub/new/foo.txt", "2");
writeTrashFile("sub/sub/new/foo.txt", "3");
walk = beginWalk();
assertIteration(F, ".gitattributes");
assertIteration(D, "foo");
assertIteration(D, "foo/sub");
assertIteration(D, "foo/sub/new", attrs("bar"));
assertIteration(F, "foo/sub/new/foo.txt");
assertIteration(D, "sub");
assertIteration(F, "sub/a.txt");
assertIteration(D, "sub/new", attrs("bar"));
assertIteration(F, "sub/new/foo.txt");
assertIteration(D, "sub/sub");
assertIteration(D, "sub/sub/new", attrs("bar"));
assertIteration(F, "sub/sub/new/foo.txt");
endWalk();
}
@Test
public void testDirectoryMatchSubComplex() throws Exception {
setupRepo(null, null, "s[uv]b/n*/ bar", null);

View File

@ -319,6 +319,51 @@ public void testDirectoryMatchSubRecursive() throws Exception {
assertSameAsCGit();
}
@Test
public void testDirectoryMatchSubRecursiveBacktrack() throws Exception {
createFiles("src/new/foo.txt", "src/src/new/foo.txt");
writeTrashFile(".gitattributes", "**/src/new/ bar\n");
assertSameAsCGit();
}
@Test
public void testDirectoryMatchSubRecursiveBacktrack2() throws Exception {
createFiles("src/new/foo.txt", "src/src/new/foo.txt");
writeTrashFile(".gitattributes", "**/**/src/new/ bar\n");
assertSameAsCGit();
}
@Test
public void testDirectoryMatchSubRecursiveBacktrack3() throws Exception {
createFiles("src/new/src/new/foo.txt",
"foo/src/new/bar/src/new/foo.txt");
writeTrashFile(".gitattributes", "**/src/new/ bar\n");
assertSameAsCGit();
}
@Test
public void testDirectoryMatchSubRecursiveBacktrack4() throws Exception {
createFiles("src/src/src/new/foo.txt",
"foo/src/src/bar/src/new/foo.txt");
writeTrashFile(".gitattributes", "**/src/ bar\n");
assertSameAsCGit();
}
@Test
public void testDirectoryMatchSubRecursiveBacktrack5() throws Exception {
createFiles("x/a/a/b/foo.txt", "x/y/z/b/a/b/foo.txt",
"x/y/a/a/a/a/b/foo.txt", "x/y/a/a/a/a/b/a/b/foo.txt");
writeTrashFile(".gitattributes", "**/*/a/b bar\n");
assertSameAsCGit();
}
@Test
public void testDirectoryMatchSubRecursiveBacktrack6() throws Exception {
createFiles("x/a/a/b/foo.txt", "x/y/a/b/a/b/foo.txt");
writeTrashFile(".gitattributes", "**/*/**/a/b bar\n");
assertSameAsCGit();
}
@Test
public void testDirectoryMatchSubComplex() throws Exception {
createFiles("src/new/foo.txt", "foo/src/new/foo.txt", "sub/src/new");

View File

@ -203,4 +203,41 @@ public void testDirectoryMatchSubRecursive() throws Exception {
writeTrashFile(".gitignore", "**/src/new/");
assertSameAsCGit();
}
@Test
public void testDirectoryMatchSubRecursiveBacktrack() throws Exception {
createFiles("src/new/foo.txt", "src/src/new/foo.txt");
writeTrashFile(".gitignore", "**/src/new/");
assertSameAsCGit();
}
@Test
public void testDirectoryMatchSubRecursiveBacktrack2() throws Exception {
createFiles("src/new/foo.txt", "src/src/new/foo.txt");
writeTrashFile(".gitignore", "**/**/src/new/");
assertSameAsCGit();
}
@Test
public void testDirectoryMatchSubRecursiveBacktrack3() throws Exception {
createFiles("x/a/a/b/foo.txt");
writeTrashFile(".gitignore", "**/*/a/b/");
assertSameAsCGit();
}
@Test
public void testDirectoryMatchSubRecursiveBacktrack4() throws Exception {
createFiles("x/a/a/b/foo.txt", "x/y/z/b/a/b/foo.txt",
"x/y/a/a/a/a/b/foo.txt", "x/y/a/a/a/a/b/a/b/foo.txt");
writeTrashFile(".gitignore", "**/*/a/b bar\n");
assertSameAsCGit();
}
@Test
public void testDirectoryMatchSubRecursiveBacktrack5() throws Exception {
createFiles("x/a/a/b/foo.txt", "x/y/a/b/a/b/foo.txt");
writeTrashFile(".gitignore", "**/*/**/a/b bar\n");
assertSameAsCGit();
}
}

View File

@ -225,6 +225,11 @@ private boolean iterate(final String path, final int startIncl,
int right = startIncl;
boolean match = false;
int lastWildmatch = -1;
// ** matches may get extended if a later match fails. When that
// happens, we must extend the ** by exactly one segment.
// wildmatchBacktrackPos records the end of the segment after a **
// match, so that we can reset correctly.
int wildmatchBacktrackPos = -1;
while (true) {
int left = right;
right = path.indexOf(slash, right);
@ -250,6 +255,9 @@ private boolean iterate(final String path, final int startIncl,
}
return match && matcher + 1 == matchers.size();
}
if (wildmatchBacktrackPos < 0) {
wildmatchBacktrackPos = right;
}
if (right - left > 0) {
match = matches(matcher, path, left, right, assumeDirectory);
} else {
@ -261,6 +269,7 @@ private boolean iterate(final String path, final int startIncl,
boolean wasWild = matchers.get(matcher) == WILD;
if (wasWild) {
lastWildmatch = matcher;
wildmatchBacktrackPos = -1;
// ** can match *nothing*: a/**/b match also a/b
right = left - 1;
}
@ -276,11 +285,25 @@ private boolean iterate(final String path, final int startIncl,
return !dirOnly || assumeDirectory;
}
// Prefix matches only if pattern ended with /**
return wasWild;
if (wasWild) {
return true;
}
if (lastWildmatch >= 0) {
// Consider pattern **/x and input x/x.
// We've matched the prefix x/ so far: we
// must try to extend the **!
matcher = lastWildmatch + 1;
right = wildmatchBacktrackPos;
wildmatchBacktrackPos = -1;
} else {
return false;
}
}
}
} else if (lastWildmatch != -1) {
matcher = lastWildmatch + 1;
right = wildmatchBacktrackPos;
wildmatchBacktrackPos = -1;
} else {
return false;
}