Browse Source

Fix path pattern matching to work also for gitattributes

Path pattern matching for attribute rules is different than matching
for excluded files.

The first difference concerns patterns without slashes. For
gitattributes those must match on the last component only, not on
any earlier segment. This is true also for directory-only patterns.

The second difference concerns directory-only patterns. Those also
must not match on a prefix or segment except the last one. They do
not apply recursively to all files beneath.

And third, matches only on a prefix must match for gitattributes
only if the last matcher was "/**".

Add a new parameter for such path matching to IMatcher.matches() and
pass it through as appropriate (false for gitignore, true for
gitattributes). As far as gitignore is concerned, there is no change.

New tests have been added, and some existing attribute matching tests
have been fixed since they operated on wrong assumptions.

Bug: 508568
Change-Id: Ie825dc2cac8a85a72a7eeb0abb888f3193d21dd2
Signed-off-by: Thomas Wolf <thomas.wolf@paranor.ch>
stable-4.9
Thomas Wolf 7 years ago committed by Matthias Sohn
parent
commit
d80b999c76
  1. 224
      org.eclipse.jgit.test/tst/org/eclipse/jgit/attributes/AttributesHandlerTest.java
  2. 92
      org.eclipse.jgit.test/tst/org/eclipse/jgit/attributes/AttributesMatcherTest.java
  3. 94
      org.eclipse.jgit.test/tst/org/eclipse/jgit/attributes/CGitAttributesTest.java
  4. 3
      org.eclipse.jgit/src/org/eclipse/jgit/api/DescribeCommand.java
  5. 4
      org.eclipse.jgit/src/org/eclipse/jgit/attributes/AttributesRule.java
  6. 2
      org.eclipse.jgit/src/org/eclipse/jgit/ignore/FastIgnoreRule.java
  7. 12
      org.eclipse.jgit/src/org/eclipse/jgit/ignore/internal/IMatcher.java
  8. 78
      org.eclipse.jgit/src/org/eclipse/jgit/ignore/internal/NameMatcher.java
  9. 73
      org.eclipse.jgit/src/org/eclipse/jgit/ignore/internal/PathMatcher.java
  10. 3
      org.eclipse.jgit/src/org/eclipse/jgit/ignore/internal/WildMatcher.java

224
org.eclipse.jgit.test/tst/org/eclipse/jgit/attributes/AttributesHandlerTest.java

@ -1,4 +1,7 @@
/*
* Copyright (C) 2015, 2017 Ivan Motsch <ivan.motsch@bsiag.com>
* and other copyright owners as documented in the project's IP log.
*
* This program and the accompanying materials are made available
* under the terms of the Eclipse Distribution License v1.0 which
* accompanies this distribution, is reproduced below, and is
@ -259,25 +262,230 @@ public class AttributesHandlerTest extends RepositoryTestCase {
setupRepo("sub/ global", "sub/** init",
"sub/** top_sub\n*.txt top",
"sub/** subsub\nsub/ subsub2\n*.txt foo");
// The last two sub/** and sub/ rules are in sub/.gitattributes. They
// must not apply to any of the files here. They would match for a
// further subdirectory sub/sub.
// The last sub/** is in sub/.gitattributes. It must not
// apply to any of the files here. It would match for a
// further subdirectory sub/sub. The sub/ rules must match
// only for directories.
walk = beginWalk();
assertIteration(F, ".gitattributes");
assertIteration(D, "sub", attrs("global"));
assertIteration(F, "sub/.gitattributes", attrs("init top_sub global"));
assertIteration(F, "sub/a.txt", attrs("init foo top top_sub global"));
assertIteration(F, "sub/.gitattributes", attrs("init top_sub"));
assertIteration(F, "sub/a.txt", attrs("init foo top top_sub"));
endWalk();
// All right, let's see that they *do* apply in sub/sub:
writeTrashFile("sub/sub/b.txt", "b");
walk = beginWalk();
assertIteration(F, ".gitattributes");
assertIteration(D, "sub", attrs("global"));
assertIteration(F, "sub/.gitattributes", attrs("init top_sub global"));
assertIteration(F, "sub/a.txt", attrs("init foo top top_sub global"));
assertIteration(F, "sub/.gitattributes", attrs("init top_sub"));
assertIteration(F, "sub/a.txt", attrs("init foo top top_sub"));
assertIteration(D, "sub/sub", attrs("init subsub2 top_sub global"));
assertIteration(F, "sub/sub/b.txt",
attrs("init foo subsub2 subsub top top_sub global"));
attrs("init foo subsub top top_sub"));
endWalk();
}
@Test
public void testNestedMatchNot() throws Exception {
setupRepo(null, null, "*.xml xml\n*.jar jar", null);
writeTrashFile("foo.xml/bar.jar", "b");
writeTrashFile("foo.xml/bar.xml", "bx");
writeTrashFile("sub/b.jar", "bj");
writeTrashFile("sub/b.xml", "bx");
// On foo.xml/bar.jar we must not have 'xml'
walk = beginWalk();
assertIteration(F, ".gitattributes");
assertIteration(D, "foo.xml", attrs("xml"));
assertIteration(F, "foo.xml/bar.jar", attrs("jar"));
assertIteration(F, "foo.xml/bar.xml", attrs("xml"));
assertIteration(D, "sub");
assertIteration(F, "sub/a.txt");
assertIteration(F, "sub/b.jar", attrs("jar"));
assertIteration(F, "sub/b.xml", attrs("xml"));
endWalk();
}
@Test
public void testNestedMatch() throws Exception {
// See also CGitAttributeTest.testNestedMatch()
setupRepo(null, null, "foo/ xml\nsub/foo/ sub\n*.jar jar", null);
writeTrashFile("foo/bar.jar", "b");
writeTrashFile("foo/bar.xml", "bx");
writeTrashFile("sub/b.jar", "bj");
writeTrashFile("sub/b.xml", "bx");
writeTrashFile("sub/foo/b.jar", "bf");
walk = beginWalk();
assertIteration(F, ".gitattributes");
assertIteration(D, "foo", attrs("xml"));
assertIteration(F, "foo/bar.jar", attrs("jar"));
assertIteration(F, "foo/bar.xml");
assertIteration(D, "sub");
assertIteration(F, "sub/a.txt");
assertIteration(F, "sub/b.jar", attrs("jar"));
assertIteration(F, "sub/b.xml");
assertIteration(D, "sub/foo", attrs("sub xml"));
assertIteration(F, "sub/foo/b.jar", attrs("jar"));
endWalk();
}
@Test
public void testNestedMatchRecursive() throws Exception {
setupRepo(null, null, "foo/** xml\n*.jar jar", null);
writeTrashFile("foo/bar.jar", "b");
writeTrashFile("foo/bar.xml", "bx");
writeTrashFile("sub/b.jar", "bj");
writeTrashFile("sub/b.xml", "bx");
writeTrashFile("sub/foo/b.jar", "bf");
// On foo.xml/bar.jar we must not have 'xml'
walk = beginWalk();
assertIteration(F, ".gitattributes");
assertIteration(D, "foo");
assertIteration(F, "foo/bar.jar", attrs("jar xml"));
assertIteration(F, "foo/bar.xml", attrs("xml"));
assertIteration(D, "sub");
assertIteration(F, "sub/a.txt");
assertIteration(F, "sub/b.jar", attrs("jar"));
assertIteration(F, "sub/b.xml");
assertIteration(D, "sub/foo");
assertIteration(F, "sub/foo/b.jar", attrs("jar"));
endWalk();
}
@Test
public void testStarMatchOnSlashNot() throws Exception {
setupRepo(null, null, "s*xt bar", null);
writeTrashFile("sub/a.txt", "1");
writeTrashFile("foo/sext", "2");
writeTrashFile("foo/s.txt", "3");
walk = beginWalk();
assertIteration(F, ".gitattributes");
assertIteration(D, "foo");
assertIteration(F, "foo/s.txt", attrs("bar"));
assertIteration(F, "foo/sext", attrs("bar"));
assertIteration(D, "sub");
assertIteration(F, "sub/a.txt");
endWalk();
}
@Test
public void testPrefixMatchNot() throws Exception {
setupRepo(null, null, "sub/new bar", null);
writeTrashFile("sub/new/foo.txt", "1");
walk = beginWalk();
assertIteration(F, ".gitattributes");
assertIteration(D, "sub");
assertIteration(F, "sub/a.txt");
assertIteration(D, "sub/new", attrs("bar"));
assertIteration(F, "sub/new/foo.txt");
endWalk();
}
@Test
public void testComplexPathMatch() throws Exception {
setupRepo(null, null, "s[t-v]b/n[de]w bar", null);
writeTrashFile("sub/new/foo.txt", "1");
writeTrashFile("sub/ndw", "2");
walk = beginWalk();
assertIteration(F, ".gitattributes");
assertIteration(D, "sub");
assertIteration(F, "sub/a.txt");
assertIteration(F, "sub/ndw", attrs("bar"));
assertIteration(D, "sub/new", attrs("bar"));
assertIteration(F, "sub/new/foo.txt");
endWalk();
}
@Test
public void testStarPathMatch() throws Exception {
setupRepo(null, null, "sub/new/* bar", null);
writeTrashFile("sub/new/foo.txt", "1");
writeTrashFile("sub/new/lower/foo.txt", "2");
walk = beginWalk();
assertIteration(F, ".gitattributes");
assertIteration(D, "sub");
assertIteration(F, "sub/a.txt");
assertIteration(D, "sub/new");
assertIteration(F, "sub/new/foo.txt", attrs("bar"));
assertIteration(D, "sub/new/lower", attrs("bar"));
assertIteration(F, "sub/new/lower/foo.txt");
endWalk();
}
@Test
public void testDirectoryMatchSubSimple() 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");
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");
assertIteration(F, "sub/sub/new/foo.txt");
endWalk();
}
@Test
public void testDirectoryMatchSubRecursive() throws Exception {
setupRepo(null, null, "**/sub/new/ bar", null);
writeTrashFile("sub/new/foo.txt", "1");
writeTrashFile("foo/sub/new/foo.txt", "2");
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");
endWalk();
}
@Test
public void testDirectoryMatchSubComplex() throws Exception {
setupRepo(null, null, "s[uv]b/n*/ bar", null);
writeTrashFile("sub/new/foo.txt", "1");
writeTrashFile("foo/sub/new/foo.txt", "2");
walk = beginWalk();
assertIteration(F, ".gitattributes");
assertIteration(D, "foo");
assertIteration(D, "foo/sub");
assertIteration(D, "foo/sub/new");
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");
endWalk();
}
@Test
public void testDirectoryMatch() throws Exception {
setupRepo(null, null, "new/ bar", null);
writeTrashFile("sub/new/foo.txt", "1");
writeTrashFile("foo/sub/new/foo.txt", "2");
writeTrashFile("foo/new", "3");
walk = beginWalk();
assertIteration(F, ".gitattributes");
assertIteration(D, "foo");
assertIteration(F, "foo/new");
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");
endWalk();
}

92
org.eclipse.jgit.test/tst/org/eclipse/jgit/attributes/AttributesMatcherTest.java

@ -109,16 +109,16 @@ public class AttributesMatcherTest {
pattern = "/src/ne?";
assertMatched(pattern, "/src/new/");
assertMatched(pattern, "/src/new");
assertMatched(pattern, "/src/new/a.c");
assertMatched(pattern, "/src/new/a/a.c");
assertNotMatched(pattern, "/src/new/a.c");
assertNotMatched(pattern, "/src/new/a/a.c");
assertNotMatched(pattern, "/src/new.c");
//Test name-only fnmatcher matches
pattern = "ne?";
assertMatched(pattern, "/src/new/");
assertMatched(pattern, "/src/new");
assertMatched(pattern, "/src/new/a.c");
assertMatched(pattern, "/src/new/a/a.c");
assertNotMatched(pattern, "/src/new/a.c");
assertNotMatched(pattern, "/src/new/a/a.c");
assertMatched(pattern, "/neb");
assertNotMatched(pattern, "/src/new.c");
}
@ -169,16 +169,16 @@ public class AttributesMatcherTest {
pattern = "/src/ne?";
assertMatched(pattern, "src/new/");
assertMatched(pattern, "src/new");
assertMatched(pattern, "src/new/a.c");
assertMatched(pattern, "src/new/a/a.c");
assertNotMatched(pattern, "src/new/a.c");
assertNotMatched(pattern, "src/new/a/a.c");
assertNotMatched(pattern, "src/new.c");
//Test name-only fnmatcher matches
pattern = "ne?";
assertMatched(pattern, "src/new/");
assertMatched(pattern, "src/new");
assertMatched(pattern, "src/new/a.c");
assertMatched(pattern, "src/new/a/a.c");
assertNotMatched(pattern, "src/new/a.c");
assertNotMatched(pattern, "src/new/a/a.c");
assertMatched(pattern, "neb");
assertNotMatched(pattern, "src/new.c");
}
@ -197,35 +197,50 @@ public class AttributesMatcherTest {
pattern = "/src/new";
assertMatched(pattern, "/src/new/");
assertMatched(pattern, "/src/new");
assertMatched(pattern, "/src/new/a.c");
assertMatched(pattern, "/src/new/a/a.c");
assertNotMatched(pattern, "/src/new/a.c");
assertNotMatched(pattern, "/src/new/a/a.c");
assertNotMatched(pattern, "/src/new.c");
//Test child directory is matched, slash after name
pattern = "/src/new/";
assertMatched(pattern, "/src/new/");
assertMatched(pattern, "/src/new/a.c");
assertMatched(pattern, "/src/new/a/a.c");
assertNotMatched(pattern, "/src/new/a.c");
assertNotMatched(pattern, "/src/new/a/a.c");
assertNotMatched(pattern, "/src/new");
assertNotMatched(pattern, "/src/new.c");
//Test directory is matched by name only
pattern = "b1";
assertMatched(pattern, "/src/new/a/b1/a.c");
assertNotMatched(pattern, "/src/new/a/b1/a.c");
assertNotMatched(pattern, "/src/new/a/b2/file.c");
assertNotMatched(pattern, "/src/new/a/bb1/file.c");
assertNotMatched(pattern, "/src/new/a/file.c");
assertNotMatched(pattern, "/src/new/a/bb1");
assertMatched(pattern, "/src/new/a/b1");
}
@Test
public void testTrailingSlash() {
String pattern = "/src/";
assertMatched(pattern, "/src/");
assertMatched(pattern, "/src/new");
assertMatched(pattern, "/src/new/a.c");
assertMatched(pattern, "/src/a.c");
assertNotMatched(pattern, "/src/new");
assertNotMatched(pattern, "/src/new/a.c");
assertNotMatched(pattern, "/src/a.c");
assertNotMatched(pattern, "/src");
assertNotMatched(pattern, "/srcA/");
pattern = "src/";
assertMatched(pattern, "src/");
assertMatched(pattern, "/src/");
assertNotMatched(pattern, "src");
assertNotMatched(pattern, "/src/new");
assertNotMatched(pattern, "/src/new/a.c");
assertNotMatched(pattern, "/src/a.c");
assertNotMatched(pattern, "foo/src/a.c");
assertNotMatched(pattern, "foo/src/bar/a.c");
assertNotMatched(pattern, "foo/src/bar/src");
assertMatched(pattern, "foo/src/");
assertMatched(pattern, "foo/src/bar/src/");
}
@Test
@ -239,51 +254,58 @@ public class AttributesMatcherTest {
assertMatched(pattern, "/src/test.stp");
assertNotMatched(pattern, "/test.stp1");
assertNotMatched(pattern, "/test.astp");
assertNotMatched(pattern, "test.stp/foo.bar");
assertMatched(pattern, "test.stp");
assertMatched(pattern, "test.stp/");
assertMatched(pattern, "test.stp/test.stp");
//Test matches for name-only, applies to file name or folder name
pattern = "src";
assertMatched(pattern, "/src");
assertMatched(pattern, "/src/");
assertMatched(pattern, "/src/a.c");
assertMatched(pattern, "/src/new/a.c");
assertMatched(pattern, "/new/src/a.c");
assertNotMatched(pattern, "/src/a.c");
assertNotMatched(pattern, "/src/new/a.c");
assertNotMatched(pattern, "/new/src/a.c");
assertMatched(pattern, "/file/src");
//Test matches for name-only, applies only to folder names
pattern = "src/";
assertMatched(pattern, "/src/");
assertMatched(pattern, "/src/a.c");
assertMatched(pattern, "/src/new/a.c");
assertMatched(pattern, "/new/src/a.c");
assertNotMatched(pattern, "/src/a.c");
assertNotMatched(pattern, "/src/new/a.c");
assertNotMatched(pattern, "/new/src/a.c");
assertNotMatched(pattern, "/src");
assertNotMatched(pattern, "/file/src");
assertMatched(pattern, "/file/src/");
//Test matches for name-only, applies to file name or folder name
//With a small wildcard
pattern = "?rc";
assertMatched(pattern, "/src/a.c");
assertMatched(pattern, "/src/new/a.c");
assertMatched(pattern, "/new/src/a.c");
assertNotMatched(pattern, "/src/a.c");
assertNotMatched(pattern, "/src/new/a.c");
assertNotMatched(pattern, "/new/src/a.c");
assertMatched(pattern, "/new/src/");
assertMatched(pattern, "/file/src");
assertMatched(pattern, "/src/");
//Test matches for name-only, applies to file name or folder name
//With a small wildcard
pattern = "?r[a-c]";
assertMatched(pattern, "/src/a.c");
assertMatched(pattern, "/src/new/a.c");
assertMatched(pattern, "/new/src/a.c");
assertNotMatched(pattern, "/src/a.c");
assertNotMatched(pattern, "/src/new/a.c");
assertNotMatched(pattern, "/new/src/a.c");
assertMatched(pattern, "/file/src");
assertMatched(pattern, "/src/");
assertMatched(pattern, "/srb/a.c");
assertMatched(pattern, "/grb/new/a.c");
assertMatched(pattern, "/new/crb/a.c");
assertNotMatched(pattern, "/srb/a.c");
assertNotMatched(pattern, "/grb/new/a.c");
assertNotMatched(pattern, "/new/crb/a.c");
assertMatched(pattern, "/file/3rb");
assertMatched(pattern, "/xrb/");
assertMatched(pattern, "/3ra/a.c");
assertMatched(pattern, "/5ra/new/a.c");
assertMatched(pattern, "/new/1ra/a.c");
assertNotMatched(pattern, "/3ra/a.c");
assertNotMatched(pattern, "/5ra/new/a.c");
assertNotMatched(pattern, "/new/1ra/a.c");
assertNotMatched(pattern, "/new/1ra/a.c/");
assertMatched(pattern, "/file/dra");
assertMatched(pattern, "/file/dra/");
assertMatched(pattern, "/era/");
assertNotMatched(pattern, "/crg");
assertNotMatched(pattern, "/cr3");

94
org.eclipse.jgit.test/tst/org/eclipse/jgit/attributes/CGitAttributesTest.java

@ -201,6 +201,13 @@ public class CGitAttributesTest extends RepositoryTestCase {
cgit.entrySet().toArray(), jgit.entrySet().toArray());
}
@Test
public void testBug508568() throws Exception {
createFiles("foo.xml/bar.jar", "sub/foo.xml/bar.jar");
writeTrashFile(".gitattributes", "*.xml xml\n" + "*.jar jar\n");
assertSameAsCGit();
}
@Test
public void testRelativePath() throws Exception {
createFiles("sub/foo.txt");
@ -224,6 +231,44 @@ public class CGitAttributesTest extends RepositoryTestCase {
assertSameAsCGit();
}
@Test
public void testNestedMatch() throws Exception {
// This is an interesting test. At the time of this writing, the
// gitignore documentation says: "In other words, foo/ will match a
// directory foo AND PATHS UNDERNEATH IT, but will not match a regular
// file or a symbolic link foo". (Emphasis added.) And gitattributes is
// supposed to follow the same rules. But the documentation appears to
// lie: C-git will *not* apply the attribute "xml" to *any* files in
// any subfolder "foo" here. It will only apply the "jar" attribute
// to the three *.jar files.
//
// The point is probably that ignores are handled top-down, and once a
// directory "foo" is matched (here: on paths "foo" and "sub/foo" by
// pattern "foo/"), the directory is excluded and the gitignore
// documentation also says: "It is not possible to re-include a file if
// a parent directory of that file is excluded." So once the pattern
// "foo/" has matched, it appears as if everything beneath would also be
// matched.
//
// But not so for gitattributes! The foo/ rule only matches the
// directory itself, but not anything beneath.
createFiles("foo/bar.jar", "foo/bar.xml", "sub/b.jar", "sub/b.xml",
"sub/foo/b.jar");
writeTrashFile(".gitattributes",
"foo/ xml\n" + "sub/foo/ sub\n" + "*.jar jar\n");
assertSameAsCGit();
}
@Test
public void testNestedMatchWithWildcard() throws Exception {
// See above.
createFiles("foo/bar.jar", "foo/bar.xml", "sub/b.jar", "sub/b.xml",
"sub/foo/b.jar");
writeTrashFile(".gitattributes",
"**/foo/ xml\n" + "*/foo/ sub\n" + "*.jar jar\n");
assertSameAsCGit();
}
@Test
public void testNestedMatchRecursive() throws Exception {
createFiles("foo/bar.jar", "foo/bar.xml", "sub/b.jar", "sub/b.xml",
@ -238,4 +283,53 @@ public class CGitAttributesTest extends RepositoryTestCase {
writeTrashFile(".gitattributes", "s*xt bar");
assertSameAsCGit();
}
@Test
public void testPrefixMatchNot() throws Exception {
createFiles("src/new/foo.txt");
writeTrashFile(".gitattributes", "src/new bar\n");
assertSameAsCGit();
}
@Test
public void testComplexPathMatchNot() throws Exception {
createFiles("src/new/foo.txt", "src/ndw");
writeTrashFile(".gitattributes", "s[p-s]c/n[de]w bar\n");
assertSameAsCGit();
}
@Test
public void testStarPathMatchNot() throws Exception {
createFiles("src/new/foo.txt", "src/ndw");
writeTrashFile(".gitattributes", "src/* bar\n");
assertSameAsCGit();
}
@Test
public void testDirectoryMatchSubSimple() throws Exception {
createFiles("src/new/foo.txt", "foo/src/new/foo.txt", "sub/src/new");
writeTrashFile(".gitattributes", "src/new/ bar\n");
assertSameAsCGit();
}
@Test
public void testDirectoryMatchSubRecursive() throws Exception {
createFiles("src/new/foo.txt", "foo/src/new/foo.txt", "sub/src/new");
writeTrashFile(".gitattributes", "**/src/new/ bar\n");
assertSameAsCGit();
}
@Test
public void testDirectoryMatchSubComplex() throws Exception {
createFiles("src/new/foo.txt", "foo/src/new/foo.txt", "sub/src/new");
writeTrashFile(".gitattributes", "s[rs]c/n*/ bar\n");
assertSameAsCGit();
}
@Test
public void testDirectoryMatch() throws Exception {
createFiles("src/new/foo.txt", "foo/src/new/foo.txt", "sub/src/new");
writeTrashFile(".gitattributes", "new/ bar\n");
assertSameAsCGit();
}
}

3
org.eclipse.jgit/src/org/eclipse/jgit/api/DescribeCommand.java

@ -209,7 +209,8 @@ public class DescribeCommand extends GitCommand<String> {
// Find the first tag that matches one of the matchers; precedence according to matcher definition order
for (IMatcher matcher : matchers) {
Optional<Ref> match = tags.stream()
.filter(tag -> matcher.matches(tag.getName(), false))
.filter(tag -> matcher.matches(tag.getName(), false,
false))
.findFirst();
if (match.isPresent()) {
return match;

4
org.eclipse.jgit/src/org/eclipse/jgit/attributes/AttributesRule.java

@ -1,5 +1,5 @@
/*
* Copyright (C) 2010, Red Hat Inc.
* Copyright (C) 2010, 2017 Red Hat Inc.
* and other copyright owners as documented in the project's IP log.
*
* This program and the accompanying materials are made available
@ -210,7 +210,7 @@ public class AttributesRule {
return false;
if (relativeTarget.length() == 0)
return false;
boolean match = matcher.matches(relativeTarget, isDirectory);
boolean match = matcher.matches(relativeTarget, isDirectory, true);
return match;
}

2
org.eclipse.jgit/src/org/eclipse/jgit/ignore/FastIgnoreRule.java

@ -155,7 +155,7 @@ public class FastIgnoreRule {
return false;
if (path.length() == 0)
return false;
boolean match = matcher.matches(path, directory);
boolean match = matcher.matches(path, directory, false);
return match;
}

12
org.eclipse.jgit/src/org/eclipse/jgit/ignore/internal/IMatcher.java

@ -1,5 +1,5 @@
/*
* Copyright (C) 2014, Andrey Loskutov <loskutov@gmx.de>
* Copyright (C) 2014, 2017 Andrey Loskutov <loskutov@gmx.de>
* and other copyright owners as documented in the project's IP log.
*
* This program and the accompanying materials are made available
@ -52,7 +52,8 @@ public interface IMatcher {
*/
public static final IMatcher NO_MATCH = new IMatcher() {
@Override
public boolean matches(String path, boolean assumeDirectory) {
public boolean matches(String path, boolean assumeDirectory,
boolean pathMatch) {
return false;
}
@ -71,9 +72,14 @@ public interface IMatcher {
* @param assumeDirectory
* true to assume this path as directory (even if it doesn't end
* with a slash)
* @param pathMatch
* {@code true} if the match is for the full path: prefix-only
* matches are not allowed, and {@link NameMatcher}s must match
* only the last component (if they can -- they may not, if they
* are anchored at the beginning)
* @return true if this matcher pattern matches given string
*/
boolean matches(String path, boolean assumeDirectory);
boolean matches(String path, boolean assumeDirectory, boolean pathMatch);
/**
* Matches only part of given string

78
org.eclipse.jgit/src/org/eclipse/jgit/ignore/internal/NameMatcher.java

@ -64,26 +64,59 @@ public class NameMatcher extends AbstractMatcher {
pattern = Strings.deleteBackslash(pattern);
}
beginning = pattern.length() == 0 ? false : pattern.charAt(0) == slash;
if (!beginning)
if (!beginning) {
this.subPattern = pattern;
else
} else {
this.subPattern = pattern.substring(1);
}
}
@Override
public boolean matches(String path, boolean assumeDirectory) {
int end = 0;
int firstChar = 0;
do {
firstChar = getFirstNotSlash(path, end);
end = getFirstSlash(path, firstChar);
boolean match = matches(path, firstChar, end, assumeDirectory);
if (match)
public boolean matches(String path, boolean assumeDirectory,
boolean pathMatch) {
// A NameMatcher's pattern does not contain a slash.
int start = 0;
int stop = path.length();
if (stop > 0 && path.charAt(0) == slash) {
start++;
}
if (pathMatch) {
// Can match only after the last slash
int lastSlash = path.lastIndexOf(slash, stop - 1);
if (lastSlash == stop - 1) {
// Skip trailing slash
lastSlash = path.lastIndexOf(slash, lastSlash - 1);
stop--;
}
boolean match;
if (lastSlash < start) {
match = matches(path, start, stop, assumeDirectory);
} else {
// Can't match if the path contains a slash if the pattern is
// anchored at the beginning
match = !beginning
&& matches(path, lastSlash + 1, stop, assumeDirectory);
}
if (match && dirOnly) {
match = assumeDirectory;
}
return match;
}
while (start < stop) {
int end = path.indexOf(slash, start);
if (end < 0) {
end = stop;
}
if (end > start && matches(path, start, end, assumeDirectory)) {
// make sure the directory matches: either if we are done with
// segment and there is next one, or if the directory is assumed
return !dirOnly ? true : (end > 0 && end != path.length())
|| assumeDirectory;
} while (!beginning && end != path.length());
return !dirOnly || assumeDirectory || end < stop;
}
if (beginning) {
break;
}
start = end + 1;
}
return false;
}
@ -92,25 +125,18 @@ public class NameMatcher extends AbstractMatcher {
boolean assumeDirectory) {
// faster local access, same as in string.indexOf()
String s = subPattern;
if (s.length() != (endExcl - startIncl))
int length = s.length();
if (length != (endExcl - startIncl)) {
return false;
for (int i = 0; i < s.length(); i++) {
}
for (int i = 0; i < length; i++) {
char c1 = s.charAt(i);
char c2 = segment.charAt(i + startIncl);
if (c1 != c2)
if (c1 != c2) {
return false;
}
}
return true;
}
private int getFirstNotSlash(String s, int start) {
int slashIdx = s.indexOf(slash, start);
return slashIdx == start ? start + 1 : start;
}
private int getFirstSlash(String s, int start) {
int slashIdx = s.indexOf(slash, start);
return slashIdx == -1 ? s.length() : slashIdx;
}
}

73
org.eclipse.jgit/src/org/eclipse/jgit/ignore/internal/PathMatcher.java

@ -52,7 +52,6 @@ import java.util.ArrayList;
import java.util.List;
import org.eclipse.jgit.errors.InvalidPatternException;
import org.eclipse.jgit.ignore.FastIgnoreRule;
import org.eclipse.jgit.ignore.internal.Strings.PatternState;
/**
@ -68,9 +67,10 @@ public class PathMatcher extends AbstractMatcher {
private final char slash;
private boolean beginning;
private final boolean beginning;
PathMatcher(String pattern, Character pathSeparator, boolean dirOnly)
private PathMatcher(String pattern, Character pathSeparator,
boolean dirOnly)
throws InvalidPatternException {
super(pattern, dirOnly);
slash = getPathSeparator(pathSeparator);
@ -87,7 +87,7 @@ public class PathMatcher extends AbstractMatcher {
&& count(path, slash, true) > 0;
}
static private List<IMatcher> createMatchers(List<String> segments,
private static List<IMatcher> createMatchers(List<String> segments,
Character pathSeparator, boolean dirOnly)
throws InvalidPatternException {
List<IMatcher> matchers = new ArrayList<>(segments.size());
@ -171,10 +171,12 @@ public class PathMatcher extends AbstractMatcher {
}
@Override
public boolean matches(String path, boolean assumeDirectory) {
if (matchers == null)
return simpleMatch(path, assumeDirectory);
return iterate(path, 0, path.length(), assumeDirectory);
public boolean matches(String path, boolean assumeDirectory,
boolean pathMatch) {
if (matchers == null) {
return simpleMatch(path, assumeDirectory, pathMatch);
}
return iterate(path, 0, path.length(), assumeDirectory, pathMatch);
}
/*
@ -182,31 +184,31 @@ public class PathMatcher extends AbstractMatcher {
* wildcards or single segments (mean: this is multi-segment path which must
* be at the beginning of the another string)
*/
private boolean simpleMatch(String path, boolean assumeDirectory) {
private boolean simpleMatch(String path, boolean assumeDirectory,
boolean pathMatch) {
boolean hasSlash = path.indexOf(slash) == 0;
if (beginning && !hasSlash)
if (beginning && !hasSlash) {
path = slash + path;
if (!beginning && hasSlash)
}
if (!beginning && hasSlash) {
path = path.substring(1);
if (path.equals(pattern))
// Exact match
if (dirOnly && !assumeDirectory)
// Directory expectations not met
return false;
else
// Directory expectations met
return true;
}
if (path.equals(pattern)) {
// Exact match: must meet directory expectations
return !dirOnly || assumeDirectory;
}
/*
* Add slashes for startsWith check. This avoids matching e.g.
* "/src/new" to /src/newfile" but allows "/src/new" to match
* "/src/new/newfile", as is the git standard
*/
if (path.startsWith(pattern + FastIgnoreRule.PATH_SEPARATOR))
String prefix = pattern + slash;
if (pathMatch) {
return path.equals(prefix) && (!dirOnly || assumeDirectory);
}
if (path.startsWith(prefix)) {
return true;
}
return false;
}
@ -217,8 +219,8 @@ public class PathMatcher extends AbstractMatcher {
"Path matcher works only on entire paths"); //$NON-NLS-1$
}
boolean iterate(final String path, final int startIncl, final int endExcl,
boolean assumeDirectory) {
private boolean iterate(final String path, final int startIncl,
final int endExcl, boolean assumeDirectory, boolean pathMatch) {
int matcher = 0;
int right = startIncl;
boolean match = false;
@ -256,14 +258,26 @@ public class PathMatcher extends AbstractMatcher {
continue;
}
if (match) {
if (matchers.get(matcher) == WILD) {
boolean wasWild = matchers.get(matcher) == WILD;
if (wasWild) {
lastWildmatch = matcher;
// ** can match *nothing*: a/**/b match also a/b
right = left - 1;
}
matcher++;
if (matcher == matchers.size()) {
return true;
// We had a prefix match here.
if (!pathMatch) {
return true;
} else {
if (right == endExcl - 1) {
// Extra slash at the end: actually a full match.
// Must meet directory expectations
return !dirOnly || assumeDirectory;
}
// Prefix matches only if pattern ended with /**
return wasWild;
}
}
} else if (lastWildmatch != -1) {
matcher = lastWildmatch + 1;
@ -274,7 +288,8 @@ public class PathMatcher extends AbstractMatcher {
}
}
boolean matches(int matcherIdx, String path, int startIncl, int endExcl,
private boolean matches(int matcherIdx, String path, int startIncl,
int endExcl,
boolean assumeDirectory) {
IMatcher matcher = matchers.get(matcherIdx);
return matcher.matches(path, startIncl, endExcl, assumeDirectory);

3
org.eclipse.jgit/src/org/eclipse/jgit/ignore/internal/WildMatcher.java

@ -62,7 +62,8 @@ public final class WildMatcher extends AbstractMatcher {
}
@Override
public final boolean matches(String path, boolean assumeDirectory) {
public final boolean matches(String path, boolean assumeDirectory,
boolean pathMatch) {
return true;
}

Loading…
Cancel
Save