diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/attributes/AttributesHandlerTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/attributes/AttributesHandlerTest.java index 50d020c57..0717379e2 100644 --- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/attributes/AttributesHandlerTest.java +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/attributes/AttributesHandlerTest.java @@ -1,4 +1,7 @@ /* + * Copyright (C) 2015, 2017 Ivan Motsch + * 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(); } diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/attributes/AttributesMatcherTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/attributes/AttributesMatcherTest.java index e8dd95232..23c416a45 100644 --- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/attributes/AttributesMatcherTest.java +++ b/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"); diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/attributes/CGitAttributesTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/attributes/CGitAttributesTest.java index e0a6d1615..c8ff6c875 100644 --- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/attributes/CGitAttributesTest.java +++ b/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(); + } } diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/DescribeCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/DescribeCommand.java index d365171ed..68b1bd9e2 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/api/DescribeCommand.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/DescribeCommand.java @@ -209,7 +209,8 @@ public class DescribeCommand extends GitCommand { // Find the first tag that matches one of the matchers; precedence according to matcher definition order for (IMatcher matcher : matchers) { Optional match = tags.stream() - .filter(tag -> matcher.matches(tag.getName(), false)) + .filter(tag -> matcher.matches(tag.getName(), false, + false)) .findFirst(); if (match.isPresent()) { return match; diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/attributes/AttributesRule.java b/org.eclipse.jgit/src/org/eclipse/jgit/attributes/AttributesRule.java index b88a16e86..3cf5de8be 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/attributes/AttributesRule.java +++ b/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; } diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/ignore/FastIgnoreRule.java b/org.eclipse.jgit/src/org/eclipse/jgit/ignore/FastIgnoreRule.java index ef67d4941..7298a082c 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/ignore/FastIgnoreRule.java +++ b/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; } diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/ignore/internal/IMatcher.java b/org.eclipse.jgit/src/org/eclipse/jgit/ignore/internal/IMatcher.java index 61f7b8340..5b184cb19 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/ignore/internal/IMatcher.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/ignore/internal/IMatcher.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2014, Andrey Loskutov + * Copyright (C) 2014, 2017 Andrey Loskutov * 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 diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/ignore/internal/NameMatcher.java b/org.eclipse.jgit/src/org/eclipse/jgit/ignore/internal/NameMatcher.java index 00651237d..9667837a4 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/ignore/internal/NameMatcher.java +++ b/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; - } - } diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/ignore/internal/PathMatcher.java b/org.eclipse.jgit/src/org/eclipse/jgit/ignore/internal/PathMatcher.java index ce9ad80fb..85073ecfb 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/ignore/internal/PathMatcher.java +++ b/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 createMatchers(List segments, + private static List createMatchers(List segments, Character pathSeparator, boolean dirOnly) throws InvalidPatternException { List 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); diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/ignore/internal/WildMatcher.java b/org.eclipse.jgit/src/org/eclipse/jgit/ignore/internal/WildMatcher.java index 93ea13c72..363b3cee8 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/ignore/internal/WildMatcher.java +++ b/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; }