diff options
18 files changed, 3205 insertions, 16 deletions
diff --git a/org.eclipse.jgit.test/META-INF/MANIFEST.MF b/org.eclipse.jgit.test/META-INF/MANIFEST.MF index d493ae4e10..7b9bd64bc1 100644 --- a/org.eclipse.jgit.test/META-INF/MANIFEST.MF +++ b/org.eclipse.jgit.test/META-INF/MANIFEST.MF @@ -20,6 +20,7 @@ Import-Package: com.googlecode.javaewah;version="[0.7.9,0.8.0)", org.eclipse.jgit.fnmatch;version="[3.6.0,3.7.0)", org.eclipse.jgit.gitrepo;version="[3.6.0,3.7.0)", org.eclipse.jgit.ignore;version="[3.6.0,3.7.0)", + org.eclipse.jgit.ignore.internal;version="[3.6.0,3.7.0)", org.eclipse.jgit.internal;version="[3.6.0,3.7.0)", org.eclipse.jgit.internal.storage.dfs;version="[3.6.0,3.7.0)", org.eclipse.jgit.internal.storage.file;version="[3.6.0,3.7.0)", @@ -47,5 +48,6 @@ Import-Package: com.googlecode.javaewah;version="[0.7.9,0.8.0)", org.hamcrest;version="[1.1.0,2.0.0)", org.junit;version="[4.4.0,5.0.0)", org.junit.experimental.theories;version="[4.4.0,5.0.0)", - org.junit.runner;version="[4.4.0,5.0.0)" + org.junit.runner;version="[4.4.0,5.0.0)", + org.junit.runners;version="[4.11.0,5.0.0)" Require-Bundle: org.hamcrest.core;bundle-version="[1.1.0,2.0.0)" diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/ignore/BasicRuleTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/ignore/BasicRuleTest.java new file mode 100644 index 0000000000..2349b55c64 --- /dev/null +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/ignore/BasicRuleTest.java @@ -0,0 +1,74 @@ +/* + * Copyright (C) 2014, 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 + * under the terms of the Eclipse Distribution License v1.0 which + * accompanies this distribution, is reproduced below, and is + * available at http://www.eclipse.org/org/documents/edl-v10.php + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * - Neither the name of the Eclipse Foundation, Inc. nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package org.eclipse.jgit.ignore; + +import static org.junit.Assert.*; + +import org.eclipse.jgit.ignore.FastIgnoreRule; +import org.junit.Test; + +public class BasicRuleTest { + + @Test + public void test() { + FastIgnoreRule rule1 = new FastIgnoreRule("/hello/[a]/"); + FastIgnoreRule rule2 = new FastIgnoreRule("/hello/[a]/"); + FastIgnoreRule rule3 = new FastIgnoreRule("!/hello/[a]/"); + FastIgnoreRule rule4 = new FastIgnoreRule("/hello/[a]"); + assertTrue(rule1.dirOnly()); + assertTrue(rule3.dirOnly()); + assertFalse(rule4.dirOnly()); + assertFalse(rule1.getNegation()); + assertTrue(rule3.getNegation()); + assertNotEquals(rule1, null); + assertEquals(rule1, rule1); + assertEquals(rule1, rule2); + assertNotEquals(rule1, rule3); + assertNotEquals(rule1, rule4); + assertEquals(rule1.hashCode(), rule2.hashCode()); + assertNotEquals(rule1.hashCode(), rule3.hashCode()); + assertEquals(rule1.toString(), rule2.toString()); + assertNotEquals(rule1.toString(), rule3.toString()); + } + +} diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/ignore/FastIgnoreRuleTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/ignore/FastIgnoreRuleTest.java new file mode 100644 index 0000000000..dab4e351a3 --- /dev/null +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/ignore/FastIgnoreRuleTest.java @@ -0,0 +1,561 @@ +/* + * Copyright (C) 2014, 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 + * under the terms of the Eclipse Distribution License v1.0 which + * accompanies this distribution, is reproduced below, and is + * available at http://www.eclipse.org/org/documents/edl-v10.php + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * - Neither the name of the Eclipse Foundation, Inc. nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package org.eclipse.jgit.ignore; + +import static org.eclipse.jgit.ignore.internal.Strings.split; +import static org.junit.Assert.*; +import static org.junit.Assume.*; + +import java.util.Arrays; + +import org.eclipse.jgit.ignore.FastIgnoreRule; +import org.eclipse.jgit.ignore.IgnoreRule; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.*; +import org.junit.runners.Parameterized.Parameter; +import org.junit.runners.Parameterized.Parameters; + +@SuppressWarnings("deprecation") +@RunWith(Parameterized.class) +public class FastIgnoreRuleTest { + + @Parameters(name = "JGit? {0}") + public static Iterable<Boolean[]> data() { + return Arrays.asList(new Boolean[][] { { Boolean.FALSE }, + { Boolean.TRUE } }); + } + + @Parameter + public Boolean useJGitRule; + + @Test + public void testSimpleCharClass() { + assertMatched("[a]", "a"); + assertMatched("[a]", "a/"); + assertMatched("[a]", "a/b"); + + assertMatched("[a]", "b/a"); + assertMatched("[a]", "b/a/"); + assertMatched("[a]", "b/a/b"); + + assertMatched("[a]", "/a/"); + assertMatched("[a]", "/a/b"); + + assertMatched("[a]", "c/a/b"); + assertMatched("[a]", "c/b/a"); + + assertMatched("/[a]", "a"); + assertMatched("/[a]", "a/"); + assertMatched("/[a]", "a/b"); + assertMatched("/[a]", "/a"); + assertMatched("/[a]", "/a/"); + assertMatched("/[a]", "/a/b"); + + assertMatched("[a]/", "a/"); + assertMatched("[a]/", "a/b"); + assertMatched("[a]/", "/a/"); + assertMatched("[a]/", "/a/b"); + + assertMatched("/[a]/", "a/"); + assertMatched("/[a]/", "a/b"); + assertMatched("/[a]/", "/a/"); + assertMatched("/[a]/", "/a/b"); + } + + @Test + public void testCharClass() { + assertMatched("[v-z]", "x"); + assertMatched("[v-z]", "x/"); + assertMatched("[v-z]", "x/b"); + + assertMatched("[v-z]", "b/x"); + assertMatched("[v-z]", "b/x/"); + assertMatched("[v-z]", "b/x/b"); + + assertMatched("[v-z]", "/x/"); + assertMatched("[v-z]", "/x/b"); + + assertMatched("[v-z]", "c/x/b"); + assertMatched("[v-z]", "c/b/x"); + + assertMatched("/[v-z]", "x"); + assertMatched("/[v-z]", "x/"); + assertMatched("/[v-z]", "x/b"); + assertMatched("/[v-z]", "/x"); + assertMatched("/[v-z]", "/x/"); + assertMatched("/[v-z]", "/x/b"); + + assertMatched("[v-z]/", "x/"); + assertMatched("[v-z]/", "x/b"); + assertMatched("[v-z]/", "/x/"); + assertMatched("[v-z]/", "/x/b"); + + assertMatched("/[v-z]/", "x/"); + assertMatched("/[v-z]/", "x/b"); + assertMatched("/[v-z]/", "/x/"); + assertMatched("/[v-z]/", "/x/b"); + } + + @Test + public void testAsteriskDot() { + assertMatched("*.a", ".a"); + assertMatched("*.a", "/.a"); + assertMatched("*.a", "a.a"); + assertMatched("*.a", "/b.a"); + assertMatched("*.a", "b.a"); + assertMatched("*.a", "/a/b.a"); + assertMatched("*.a", "/b/.a"); + } + + @Test + public void testAsteriskDotDoNotMatch() { + assertNotMatched("*.a", ".ab"); + assertNotMatched("*.a", "/.ab"); + assertNotMatched("*.a", "/b.ba"); + assertNotMatched("*.a", "a.ab"); + assertNotMatched("*.a", "/b.ab"); + assertNotMatched("*.a", "b.ab"); + assertNotMatched("*.a", "/a/b.ab"); + assertNotMatched("*.a", "/b/.ab"); + } + + @Test + public void testDotAsteriskMatch() { + assertMatched("a.*", "a."); + assertMatched("a.*", "a./"); + assertMatched("a.*", "a.b"); + + assertMatched("a.*", "b/a.b"); + assertMatched("a.*", "b/a.b/"); + assertMatched("a.*", "b/a.b/b"); + + assertMatched("a.*", "/a.b/"); + assertMatched("a.*", "/a.b/b"); + + assertMatched("a.*", "c/a.b/b"); + assertMatched("a.*", "c/b/a.b"); + + assertMatched("/a.*", "a.b"); + assertMatched("/a.*", "a.b/"); + assertMatched("/a.*", "a.b/b"); + assertMatched("/a.*", "/a.b"); + assertMatched("/a.*", "/a.b/"); + assertMatched("/a.*", "/a.b/b"); + + assertMatched("/a.*/b", "a.b/b"); + assertMatched("/a.*/b", "/a.b/b"); + assertMatched("/a.*/b", "/a.bc/b"); + assertMatched("/a.*/b", "/a./b"); + } + + @Test + public void testAsterisk() { + assertMatched("a*", "a"); + assertMatched("a*", "a/"); + assertMatched("a*", "ab"); + + assertMatched("a*", "b/ab"); + assertMatched("a*", "b/ab/"); + assertMatched("a*", "b/ab/b"); + + assertMatched("a*", "b/abc"); + assertMatched("a*", "b/abc/"); + assertMatched("a*", "b/abc/b"); + + assertMatched("a*", "/abc/"); + assertMatched("a*", "/abc/b"); + + assertMatched("a*", "c/abc/b"); + assertMatched("a*", "c/b/abc"); + + assertMatched("/a*", "abc"); + assertMatched("/a*", "abc/"); + assertMatched("/a*", "abc/b"); + assertMatched("/a*", "/abc"); + assertMatched("/a*", "/abc/"); + assertMatched("/a*", "/abc/b"); + + assertMatched("/a*/b", "abc/b"); + assertMatched("/a*/b", "/abc/b"); + assertMatched("/a*/b", "/abcd/b"); + assertMatched("/a*/b", "/a/b"); + } + + @Test + public void testQuestionmark() { + assertMatched("a?", "ab"); + assertMatched("a?", "ab/"); + + assertMatched("a?", "b/ab"); + assertMatched("a?", "b/ab/"); + assertMatched("a?", "b/ab/b"); + + assertMatched("a?", "/ab/"); + assertMatched("a?", "/ab/b"); + + assertMatched("a?", "c/ab/b"); + assertMatched("a?", "c/b/ab"); + + assertMatched("/a?", "ab"); + assertMatched("/a?", "ab/"); + assertMatched("/a?", "ab/b"); + assertMatched("/a?", "/ab"); + assertMatched("/a?", "/ab/"); + assertMatched("/a?", "/ab/b"); + + assertMatched("/a?/b", "ab/b"); + assertMatched("/a?/b", "/ab/b"); + } + + @Test + public void testQuestionmarkDoNotMatch() { + assertNotMatched("a?", "a/"); + assertNotMatched("a?", "abc"); + assertNotMatched("a?", "abc/"); + + assertNotMatched("a?", "b/abc"); + assertNotMatched("a?", "b/abc/"); + + assertNotMatched("a?", "/abc/"); + assertNotMatched("a?", "/abc/b"); + + assertNotMatched("a?", "c/abc/b"); + assertNotMatched("a?", "c/b/abc"); + + assertNotMatched("/a?", "abc"); + assertNotMatched("/a?", "abc/"); + assertNotMatched("/a?", "abc/b"); + assertNotMatched("/a?", "/abc"); + assertNotMatched("/a?", "/abc/"); + assertNotMatched("/a?", "/abc/b"); + + assertNotMatched("/a?/b", "abc/b"); + assertNotMatched("/a?/b", "/abc/b"); + assertNotMatched("/a?/b", "/a/b"); + } + + @Test + public void testSimplePatterns() { + assertMatched("a", "a"); + assertMatched("a", "a/"); + assertMatched("a", "a/b"); + + assertMatched("a", "b/a"); + assertMatched("a", "b/a/"); + assertMatched("a", "b/a/b"); + + assertMatched("a", "/a/"); + assertMatched("a", "/a/b"); + + assertMatched("a", "c/a/b"); + assertMatched("a", "c/b/a"); + + assertMatched("/a", "a"); + assertMatched("/a", "a/"); + assertMatched("/a", "a/b"); + assertMatched("/a", "/a"); + assertMatched("/a", "/a/"); + assertMatched("/a", "/a/b"); + + assertMatched("a/", "a/"); + assertMatched("a/", "a/b"); + assertMatched("a/", "/a/"); + assertMatched("a/", "/a/b"); + + assertMatched("/a/", "a/"); + assertMatched("/a/", "a/b"); + assertMatched("/a/", "/a/"); + assertMatched("/a/", "/a/b"); + + } + + @Test + public void testSimplePatternsDoNotMatch() { + assertNotMatched("ab", "a"); + assertNotMatched("abc", "a/"); + assertNotMatched("abc", "a/b"); + + assertNotMatched("a", "ab"); + assertNotMatched("a", "ba"); + assertNotMatched("a", "aa"); + + assertNotMatched("a", "b/ab"); + assertNotMatched("a", "b/ba"); + + assertNotMatched("a", "b/ba"); + assertNotMatched("a", "b/ab"); + + assertNotMatched("a", "b/ba/"); + assertNotMatched("a", "b/ba/b"); + + assertNotMatched("a", "/aa"); + assertNotMatched("a", "aa/"); + assertNotMatched("a", "/aa/"); + + assertNotMatched("/a", "b/a"); + assertNotMatched("/a", "/b/a/"); + + assertNotMatched("a/", "a"); + assertNotMatched("a/", "b/a"); + + assertNotMatched("/a/", "a"); + assertNotMatched("/a/", "/a"); + assertNotMatched("/a/", "b/a"); + } + + @Test + public void testSegments() { + assertMatched("/a/b", "a/b"); + assertMatched("/a/b", "/a/b"); + assertMatched("/a/b", "/a/b/"); + assertMatched("/a/b", "/a/b/c"); + + assertMatched("a/b", "a/b"); + assertMatched("a/b", "/a/b"); + assertMatched("a/b", "/a/b/"); + assertMatched("a/b", "/a/b/c"); + + assertMatched("a/b/", "a/b/"); + assertMatched("a/b/", "/a/b/"); + assertMatched("a/b/", "/a/b/c"); + } + + @Test + public void testSegmentsDoNotMatch() { + assertNotMatched("a/b", "/a/bb"); + assertNotMatched("a/b", "/aa/b"); + assertNotMatched("a/b", "a/bb"); + assertNotMatched("a/b", "aa/b"); + assertNotMatched("a/b", "c/aa/b"); + assertNotMatched("a/b", "c/a/bb"); + assertNotMatched("a/b/", "/a/b"); + assertNotMatched("/a/b/", "/a/b"); + assertNotMatched("/a/b", "c/a/b"); + assertNotMatched("/a/b/", "c/a/b"); + assertNotMatched("/a/b/", "c/a/b/"); + + // XXX why is it like this???? + assertNotMatched("a/b", "c/a/b"); + assertNotMatched("a/b", "c/a/b/"); + assertNotMatched("a/b", "c/a/b/c"); + assertNotMatched("a/b/", "c/a/b/"); + assertNotMatched("a/b/", "c/a/b/c"); + } + + @SuppressWarnings("boxing") + @Test + public void testWildmatch() { + if (useJGitRule) + System.err + .println("IgnoreRule can't understand wildmatch rules, skipping testWildmatch!"); + + Boolean assume = useJGitRule; + assertMatched("**/a/b", "a/b", assume); + assertMatched("**/a/b", "c/a/b", assume); + assertMatched("**/a/b", "c/d/a/b", assume); + assertMatched("**/**/a/b", "c/d/a/b", assume); + + assertMatched("/**/a/b", "a/b", assume); + assertMatched("/**/a/b", "c/a/b", assume); + assertMatched("/**/a/b", "c/d/a/b", assume); + assertMatched("/**/**/a/b", "c/d/a/b", assume); + + assertMatched("a/b/**", "a/b", assume); + assertMatched("a/b/**", "a/b/c", assume); + assertMatched("a/b/**", "a/b/c/d/", assume); + assertMatched("a/b/**/**", "a/b/c/d", assume); + + assertMatched("**/a/**/b", "c/d/a/b", assume); + assertMatched("**/a/**/b", "c/d/a/e/b", assume); + assertMatched("**/**/a/**/**/b", "c/d/a/e/b", assume); + + assertMatched("/**/a/**/b", "c/d/a/b", assume); + assertMatched("/**/a/**/b", "c/d/a/e/b", assume); + assertMatched("/**/**/a/**/**/b", "c/d/a/e/b", assume); + + assertMatched("a/**/b", "a/b", assume); + assertMatched("a/**/b", "a/c/b", assume); + assertMatched("a/**/b", "a/c/d/b", assume); + assertMatched("a/**/**/b", "a/c/d/b", assume); + + assertMatched("a/**/b/**/c", "a/c/b/d/c", assume); + assertMatched("a/**/**/b/**/**/c", "a/c/b/d/c", assume); + } + + @SuppressWarnings("boxing") + @Test + public void testWildmatchDoNotMatch() { + if (useJGitRule) + System.err + .println("IgnoreRule can't understand wildmatch rules, skipping testWildmatchDoNotMatch!"); + + Boolean assume = useJGitRule; + assertNotMatched("**/a/b", "a/c/b", assume); + assertNotMatched("!/**/*.zip", "c/a/b.zip", assume); + assertNotMatched("!**/*.zip", "c/a/b.zip", assume); + assertNotMatched("a/**/b", "a/c/bb", assume); + } + + @SuppressWarnings("unused") + @Test + public void testSimpleRules() { + try { + new FastIgnoreRule(null); + fail("Illegal input allowed!"); + } catch (IllegalArgumentException e) { + // expected + } + assertFalse(new FastIgnoreRule("/").isMatch("/", false)); + assertFalse(new FastIgnoreRule("//").isMatch("//", false)); + assertFalse(new FastIgnoreRule("#").isMatch("#", false)); + assertFalse(new FastIgnoreRule("").isMatch("", false)); + assertFalse(new FastIgnoreRule(" ").isMatch(" ", false)); + } + + @Test + public void testSplit() { + try { + split("/", '/').toArray(); + fail("should not allow single slash"); + } catch (IllegalStateException e) { + // expected + } + + assertArrayEquals(new String[] { "a", "b" }, split("a/b", '/') + .toArray()); + assertArrayEquals(new String[] { "a", "b/" }, split("a/b/", '/') + .toArray()); + assertArrayEquals(new String[] { "/a", "b" }, split("/a/b", '/') + .toArray()); + assertArrayEquals(new String[] { "/a", "b/" }, split("/a/b/", '/') + .toArray()); + assertArrayEquals(new String[] { "/a", "b", "c" }, split("/a/b/c", '/') + .toArray()); + assertArrayEquals(new String[] { "/a", "b", "c/" }, + split("/a/b/c/", '/').toArray()); + } + + public void assertMatched(String pattern, String path, Boolean... assume) { + boolean match = match(pattern, path); + String result = path + " is " + (match ? "ignored" : "not ignored") + + " via '" + pattern + "' rule"; + if (!match) + System.err.println(result); + if (assume.length == 0 || !assume[0].booleanValue()) + assertTrue("Expected a match for: " + pattern + " with: " + path, + match); + else + assumeTrue("Expected a match for: " + pattern + " with: " + path, + match); + + if (pattern.startsWith("!")) + pattern = pattern.substring(1); + else + pattern = "!" + pattern; + match = match(pattern, path); + if (assume.length == 0 || !assume[0].booleanValue()) + assertFalse("Expected no match for: " + pattern + " with: " + path, + match); + else + assumeFalse("Expected no match for: " + pattern + " with: " + path, + match); + } + + public void assertNotMatched(String pattern, String path, Boolean... assume) { + boolean match = match(pattern, path); + String result = path + " is " + (match ? "ignored" : "not ignored") + + " via '" + pattern + "' rule"; + if (match) + System.err.println(result); + if (assume.length == 0 || !assume[0].booleanValue()) + assertFalse("Expected no match for: " + pattern + " with: " + path, + match); + else + assumeFalse("Expected no match for: " + pattern + " with: " + path, + match); + + if (pattern.startsWith("!")) + pattern = pattern.substring(1); + else + pattern = "!" + pattern; + match = match(pattern, path); + if (assume.length == 0 || !assume[0].booleanValue()) + assertTrue("Expected a match for: " + pattern + " with: " + path, + match); + else + assumeTrue("Expected a match for: " + pattern + " with: " + path, + match); + } + + /** + * Check for a match. If target ends with "/", match will assume that the + * target is meant to be a directory. + * + * @param pattern + * Pattern as it would appear in a .gitignore file + * @param target + * Target file path relative to repository's GIT_DIR + * @return Result of {@link FastIgnoreRule#isMatch(String, boolean)} + */ + private boolean match(String pattern, String target) { + boolean isDirectory = target.endsWith("/"); + if (useJGitRule.booleanValue()) { + IgnoreRule r = new IgnoreRule(pattern); + // If speed of this test is ever an issue, we can use a presetRule + // field + // to avoid recompiling a pattern each time. + boolean match = r.isMatch(target, isDirectory); + if (r.getNegation()) + match = !match; + return match; + } + FastIgnoreRule r = new FastIgnoreRule(pattern); + // If speed of this test is ever an issue, we can use a presetRule field + // to avoid recompiling a pattern each time. + boolean match = r.isMatch(target, isDirectory); + if (r.getNegation()) + match = !match; + return match; + } +} diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/ignore/IgnoreMatcherParametrizedTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/ignore/IgnoreMatcherParametrizedTest.java new file mode 100644 index 0000000000..a51582ab3d --- /dev/null +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/ignore/IgnoreMatcherParametrizedTest.java @@ -0,0 +1,371 @@ +/* + * Copyright (C) 2010, Red Hat Inc. + * 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 + * available at http://www.eclipse.org/org/documents/edl-v10.php + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * - Neither the name of the Eclipse Foundation, Inc. nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package org.eclipse.jgit.ignore; + +import static org.junit.Assert.*; + +import java.util.Arrays; + +import org.eclipse.jgit.ignore.FastIgnoreRule; +import org.eclipse.jgit.ignore.IgnoreRule; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.*; +import org.junit.runners.Parameterized.Parameter; +import org.junit.runners.Parameterized.Parameters; + +/** + * Tests ignore pattern matches + */ +@SuppressWarnings("deprecation") +@RunWith(Parameterized.class) +public class IgnoreMatcherParametrizedTest { + + @Parameters(name = "JGit? {0}") + public static Iterable<Boolean[]> data() { + return Arrays.asList(new Boolean[][] { { Boolean.FALSE }, + { Boolean.TRUE } }); + } + + @Parameter + public Boolean useJGitRule; + + @Test + public void testBasic() { + String pattern = "/test.stp"; + assertMatched(pattern, "/test.stp"); + + pattern = "#/test.stp"; + assertNotMatched(pattern, "/test.stp"); + } + + @Test + public void testFileNameWildcards() { + // Test basic * and ? for any pattern + any character + String pattern = "*.st?"; + assertMatched(pattern, "/test.stp"); + assertMatched(pattern, "/anothertest.stg"); + assertMatched(pattern, "/anothertest.st0"); + assertNotMatched(pattern, "/anothertest.sta1"); + // Check that asterisk does not expand to "/" + assertNotMatched(pattern, "/another/test.sta1"); + + // Same as above, with a leading slash to ensure that doesn't cause + // problems + pattern = "/*.st?"; + assertMatched(pattern, "/test.stp"); + assertMatched(pattern, "/anothertest.stg"); + assertMatched(pattern, "/anothertest.st0"); + assertNotMatched(pattern, "/anothertest.sta1"); + // Check that asterisk does not expand to "/" + assertNotMatched(pattern, "/another/test.sta1"); + + // Test for numbers + pattern = "*.sta[0-5]"; + assertMatched(pattern, "/test.sta5"); + assertMatched(pattern, "/test.sta4"); + assertMatched(pattern, "/test.sta3"); + assertMatched(pattern, "/test.sta2"); + assertMatched(pattern, "/test.sta1"); + assertMatched(pattern, "/test.sta0"); + assertMatched(pattern, "/anothertest.sta2"); + assertNotMatched(pattern, "test.stag"); + assertNotMatched(pattern, "test.sta6"); + + // Test for letters + pattern = "/[tv]est.sta[a-d]"; + assertMatched(pattern, "/test.staa"); + assertMatched(pattern, "/test.stab"); + assertMatched(pattern, "/test.stac"); + assertMatched(pattern, "/test.stad"); + assertMatched(pattern, "/vest.stac"); + assertNotMatched(pattern, "test.stae"); + assertNotMatched(pattern, "test.sta9"); + + // Test child directory/file is matched + 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.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"); + assertMatched(pattern, "/neb"); + assertNotMatched(pattern, "/src/new.c"); + } + + @Test + public void testTargetWithoutLeadingSlash() { + // Test basic * and ? for any pattern + any character + String pattern = "/*.st?"; + assertMatched(pattern, "test.stp"); + assertMatched(pattern, "anothertest.stg"); + assertMatched(pattern, "anothertest.st0"); + assertNotMatched(pattern, "anothertest.sta1"); + // Check that asterisk does not expand to "" + assertNotMatched(pattern, "another/test.sta1"); + + // Same as above, with a leading slash to ensure that doesn't cause + // problems + pattern = "/*.st?"; + assertMatched(pattern, "test.stp"); + assertMatched(pattern, "anothertest.stg"); + assertMatched(pattern, "anothertest.st0"); + assertNotMatched(pattern, "anothertest.sta1"); + // Check that asterisk does not expand to "" + assertNotMatched(pattern, "another/test.sta1"); + + // Test for numbers + pattern = "/*.sta[0-5]"; + assertMatched(pattern, "test.sta5"); + assertMatched(pattern, "test.sta4"); + assertMatched(pattern, "test.sta3"); + assertMatched(pattern, "test.sta2"); + assertMatched(pattern, "test.sta1"); + assertMatched(pattern, "test.sta0"); + assertMatched(pattern, "anothertest.sta2"); + assertNotMatched(pattern, "test.stag"); + assertNotMatched(pattern, "test.sta6"); + + // Test for letters + pattern = "/[tv]est.sta[a-d]"; + assertMatched(pattern, "test.staa"); + assertMatched(pattern, "test.stab"); + assertMatched(pattern, "test.stac"); + assertMatched(pattern, "test.stad"); + assertMatched(pattern, "vest.stac"); + assertNotMatched(pattern, "test.stae"); + assertNotMatched(pattern, "test.sta9"); + + // Test child directory/file is matched + 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.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"); + assertMatched(pattern, "neb"); + assertNotMatched(pattern, "src/new.c"); + } + + @Test + public void testParentDirectoryGitIgnores() { + // Contains git ignore patterns such as might be seen in a parent + // directory + + // Test for wildcards + String pattern = "/*/*.c"; + assertMatched(pattern, "/file/a.c"); + assertMatched(pattern, "/src/a.c"); + assertNotMatched(pattern, "/src/new/a.c"); + + // Test child directory/file is matched + 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.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"); + 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/b2/file.c"); + assertNotMatched(pattern, "/src/new/a/bb1/file.c"); + assertNotMatched(pattern, "/src/new/a/file.c"); + } + + @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"); + assertNotMatched(pattern, "/srcA/"); + } + + @Test + public void testNameOnlyMatches() { + /* + * Name-only matches do not contain any path separators + */ + // Test matches for file extension + String pattern = "*.stp"; + assertMatched(pattern, "/test.stp"); + assertMatched(pattern, "/src/test.stp"); + assertNotMatched(pattern, "/test.stp1"); + assertNotMatched(pattern, "/test.astp"); + + // 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"); + 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"); + assertNotMatched(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"); + 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"); + assertMatched(pattern, "/file/src"); + assertMatched(pattern, "/src/"); + assertMatched(pattern, "/srb/a.c"); + assertMatched(pattern, "/grb/new/a.c"); + assertMatched(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"); + assertMatched(pattern, "/file/dra"); + assertMatched(pattern, "/era/"); + assertNotMatched(pattern, "/crg"); + assertNotMatched(pattern, "/cr3"); + } + + @Test + public void testNegation() { + String pattern = "!/test.stp"; + assertMatched(pattern, "/test.stp"); + } + + /** + * Check for a match. If target ends with "/", match will assume that the + * target is meant to be a directory. + * + * @param pattern + * Pattern as it would appear in a .gitignore file + * @param target + * Target file path relative to repository's GIT_DIR + */ + public void assertMatched(String pattern, String target) { + boolean value = match(pattern, target); + assertTrue("Expected a match for: " + pattern + " with: " + target, + value); + } + + /** + * Check for a match. If target ends with "/", match will assume that the + * target is meant to be a directory. + * + * @param pattern + * Pattern as it would appear in a .gitignore file + * @param target + * Target file path relative to repository's GIT_DIR + */ + public void assertNotMatched(String pattern, String target) { + boolean value = match(pattern, target); + assertFalse("Expected no match for: " + pattern + " with: " + target, + value); + } + + /** + * Check for a match. If target ends with "/", match will assume that the + * target is meant to be a directory. + * + * @param pattern + * Pattern as it would appear in a .gitignore file + * @param target + * Target file path relative to repository's GIT_DIR + * @return Result of {@link FastIgnoreRule#isMatch(String, boolean)} + */ + private boolean match(String pattern, String target) { + boolean isDirectory = target.endsWith("/"); + if (useJGitRule.booleanValue()) { + IgnoreRule r = new IgnoreRule(pattern); + // If speed of this test is ever an issue, we can use a presetRule + // field + // to avoid recompiling a pattern each time. + return r.isMatch(target, isDirectory); + } + FastIgnoreRule r = new FastIgnoreRule(pattern); + // If speed of this test is ever an issue, we can use a presetRule field + // to avoid recompiling a pattern each time. + return r.isMatch(target, isDirectory); + } +} diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/ignore/IgnoreMatcherTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/ignore/IgnoreMatcherTest.java index aa98696b24..0713b1ad16 100644 --- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/ignore/IgnoreMatcherTest.java +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/ignore/IgnoreMatcherTest.java @@ -52,6 +52,7 @@ import org.junit.Test; /** * Tests ignore pattern matches */ +@SuppressWarnings("deprecation") public class IgnoreMatcherTest { @Test @@ -383,12 +384,12 @@ public class IgnoreMatcherTest { /** * Check for a match. If target ends with "/", match will assume that the * target is meant to be a directory. + * * @param pattern - * Pattern as it would appear in a .gitignore file + * Pattern as it would appear in a .gitignore file * @param target - * Target file path relative to repository's GIT_DIR - * @return - * Result of {@link IgnoreRule#isMatch(String, boolean)} + * Target file path relative to repository's GIT_DIR + * @return Result of IgnoreRule.isMatch(String, boolean) */ private static boolean match(String pattern, String target) { IgnoreRule r = new IgnoreRule(pattern); diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/ignore/IgnoreRuleSpecialCasesTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/ignore/IgnoreRuleSpecialCasesTest.java new file mode 100644 index 0000000000..41180e70c2 --- /dev/null +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/ignore/IgnoreRuleSpecialCasesTest.java @@ -0,0 +1,904 @@ +/* + * Copyright (C) 2008, Florian Koeberle <florianskarten@web.de> + * Copyright (C) 2008, Florian Köberle <florianskarten@web.de> + * 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 + * available at http://www.eclipse.org/org/documents/edl-v10.php + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * - Neither the name of the Eclipse Foundation, Inc. nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package org.eclipse.jgit.ignore; + +import static org.junit.Assert.*; +import static org.junit.Assume.*; + +import java.util.Arrays; + +import org.eclipse.jgit.ignore.FastIgnoreRule; +import org.eclipse.jgit.ignore.IgnoreRule; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.*; +import org.junit.runners.Parameterized.Parameter; +import org.junit.runners.Parameterized.Parameters; + +@RunWith(Parameterized.class) +@SuppressWarnings({ "deprecation", "boxing" }) +public class IgnoreRuleSpecialCasesTest { + + @Parameters(name = "JGit? {0}") + public static Iterable<Boolean[]> data() { + return Arrays.asList(new Boolean[][] { { Boolean.FALSE }, + { Boolean.TRUE } }); + } + + @Parameter + public Boolean useJGitRule; + + private void assertMatch(final String pattern, final String input, + final boolean matchExpected, Boolean... assume) { + boolean assumeDir = input.endsWith("/"); + if (useJGitRule.booleanValue()) { + final IgnoreRule matcher = new IgnoreRule(pattern); + if (assume.length == 0 || !assume[0].booleanValue()) + assertEquals(matchExpected, matcher.isMatch(input, assumeDir)); + else + assumeTrue(matchExpected == matcher.isMatch(input, assumeDir)); + } else { + FastIgnoreRule matcher = new FastIgnoreRule(pattern); + if (assume.length == 0 || !assume[0].booleanValue()) + assertEquals(matchExpected, matcher.isMatch(input, assumeDir)); + else + assumeTrue(matchExpected == matcher.isMatch(input, assumeDir)); + } + } + + private void assertFileNameMatch(final String pattern, final String input, + final boolean matchExpected) { + boolean assumeDir = input.endsWith("/"); + if (useJGitRule.booleanValue()) { + final IgnoreRule matcher = new IgnoreRule(pattern); + assertEquals(matchExpected, matcher.isMatch(input, assumeDir)); + } else { + FastIgnoreRule matcher = new FastIgnoreRule(pattern); + assertEquals(matchExpected, matcher.isMatch(input, assumeDir)); + } + } + + @Test + public void testVerySimplePatternCase0() throws Exception { + if (useJGitRule) + System.err + .println("IgnoreRule can't understand blank lines, skipping"); + Boolean assume = useJGitRule; + assertMatch("", "", false, assume); + } + + @Test + public void testVerySimplePatternCase1() throws Exception { + assertMatch("ab", "a", false); + } + + @Test + public void testVerySimplePatternCase2() throws Exception { + assertMatch("ab", "ab", true); + } + + @Test + public void testVerySimplePatternCase3() throws Exception { + assertMatch("ab", "ac", false); + } + + @Test + public void testVerySimplePatternCase4() throws Exception { + assertMatch("ab", "abc", false); + } + + @Test + public void testVerySimpleWildcardCase0() throws Exception { + assertMatch("?", "a", true); + } + + @Test + public void testVerySimpleWildCardCase1() throws Exception { + assertMatch("??", "a", false); + } + + @Test + public void testVerySimpleWildCardCase2() throws Exception { + assertMatch("??", "ab", true); + } + + @Test + public void testVerySimpleWildCardCase3() throws Exception { + assertMatch("??", "abc", false); + } + + @Test + public void testVerySimpleStarCase0() throws Exception { + // can't happen, but blank lines should never match + assertMatch("*", "", false); + } + + @Test + public void testVerySimpleStarCase1() throws Exception { + assertMatch("*", "a", true); + } + + @Test + public void testVerySimpleStarCase2() throws Exception { + assertMatch("*", "ab", true); + } + + @Test + public void testSimpleStarCase0() throws Exception { + assertMatch("a*b", "a", false); + } + + @Test + public void testSimpleStarCase1() throws Exception { + assertMatch("a*c", "ac", true); + } + + @Test + public void testSimpleStarCase2() throws Exception { + assertMatch("a*c", "ab", false); + } + + @Test + public void testSimpleStarCase3() throws Exception { + assertMatch("a*c", "abc", true); + } + + @Test + public void testManySolutionsCase0() throws Exception { + assertMatch("a*a*a", "aaa", true); + } + + @Test + public void testManySolutionsCase1() throws Exception { + assertMatch("a*a*a", "aaaa", true); + } + + @Test + public void testManySolutionsCase2() throws Exception { + assertMatch("a*a*a", "ababa", true); + } + + @Test + public void testManySolutionsCase3() throws Exception { + assertMatch("a*a*a", "aaaaaaaa", true); + } + + @Test + public void testManySolutionsCase4() throws Exception { + assertMatch("a*a*a", "aaaaaaab", false); + } + + @Test + public void testVerySimpleGroupCase0() throws Exception { + assertMatch("[ab]", "a", true); + } + + @Test + public void testVerySimpleGroupCase1() throws Exception { + assertMatch("[ab]", "b", true); + } + + @Test + public void testVerySimpleGroupCase2() throws Exception { + assertMatch("[ab]", "ab", false); + } + + @Test + public void testVerySimpleGroupRangeCase0() throws Exception { + assertMatch("[b-d]", "a", false); + } + + @Test + public void testVerySimpleGroupRangeCase1() throws Exception { + assertMatch("[b-d]", "b", true); + } + + @Test + public void testVerySimpleGroupRangeCase2() throws Exception { + assertMatch("[b-d]", "c", true); + } + + @Test + public void testVerySimpleGroupRangeCase3() throws Exception { + assertMatch("[b-d]", "d", true); + } + + @Test + public void testVerySimpleGroupRangeCase4() throws Exception { + assertMatch("[b-d]", "e", false); + } + + @Test + public void testVerySimpleGroupRangeCase5() throws Exception { + assertMatch("[b-d]", "-", false); + } + + @Test + public void testTwoGroupsCase0() throws Exception { + assertMatch("[b-d][ab]", "bb", true); + } + + @Test + public void testTwoGroupsCase1() throws Exception { + assertMatch("[b-d][ab]", "ca", true); + } + + @Test + public void testTwoGroupsCase2() throws Exception { + assertMatch("[b-d][ab]", "fa", false); + } + + @Test + public void testTwoGroupsCase3() throws Exception { + assertMatch("[b-d][ab]", "bc", false); + } + + @Test + public void testTwoRangesInOneGroupCase0() throws Exception { + assertMatch("[b-ce-e]", "a", false); + } + + @Test + public void testTwoRangesInOneGroupCase1() throws Exception { + assertMatch("[b-ce-e]", "b", true); + } + + @Test + public void testTwoRangesInOneGroupCase2() throws Exception { + assertMatch("[b-ce-e]", "c", true); + } + + @Test + public void testTwoRangesInOneGroupCase3() throws Exception { + assertMatch("[b-ce-e]", "d", false); + } + + @Test + public void testTwoRangesInOneGroupCase4() throws Exception { + assertMatch("[b-ce-e]", "e", true); + } + + @Test + public void testTwoRangesInOneGroupCase5() throws Exception { + assertMatch("[b-ce-e]", "f", false); + } + + @Test + public void testIncompleteRangesInOneGroupCase0() throws Exception { + assertMatch("a[b-]", "ab", true); + } + + @Test + public void testIncompleteRangesInOneGroupCase1() throws Exception { + assertMatch("a[b-]", "ac", false); + } + + @Test + public void testIncompleteRangesInOneGroupCase2() throws Exception { + assertMatch("a[b-]", "a-", true); + } + + @Test + public void testCombinedRangesInOneGroupCase0() throws Exception { + assertMatch("[a-c-e]", "b", true); + } + + /** + * The c belongs to the range a-c. "-e" is no valid range so d should not + * match. + * + * @throws Exception + * for some reasons + */ + @Test + public void testCombinedRangesInOneGroupCase1() throws Exception { + assertMatch("[a-c-e]", "d", false); + } + + @Test + public void testCombinedRangesInOneGroupCase2() throws Exception { + assertMatch("[a-c-e]", "e", true); + } + + @Test + public void testInversedGroupCase0() throws Exception { + assertMatch("[!b-c]", "a", true); + } + + @Test + public void testInversedGroupCase1() throws Exception { + assertMatch("[!b-c]", "b", false); + } + + @Test + public void testInversedGroupCase2() throws Exception { + assertMatch("[!b-c]", "c", false); + } + + @Test + public void testInversedGroupCase3() throws Exception { + assertMatch("[!b-c]", "d", true); + } + + @Test + public void testAlphaGroupCase0() throws Exception { + assertMatch("[[:alpha:]]", "d", true); + } + + @Test + public void testAlphaGroupCase1() throws Exception { + assertMatch("[[:alpha:]]", ":", false); + } + + @Test + public void testAlphaGroupCase2() throws Exception { + // \u00f6 = 'o' with dots on it + assertMatch("[[:alpha:]]", "\u00f6", true); + } + + @Test + public void test2AlphaGroupsCase0() throws Exception { + // \u00f6 = 'o' with dots on it + assertMatch("[[:alpha:]][[:alpha:]]", "a\u00f6", true); + assertMatch("[[:alpha:]][[:alpha:]]", "a1", false); + } + + @Test + public void testAlnumGroupCase0() throws Exception { + assertMatch("[[:alnum:]]", "a", true); + } + + @Test + public void testAlnumGroupCase1() throws Exception { + assertMatch("[[:alnum:]]", "1", true); + } + + @Test + public void testAlnumGroupCase2() throws Exception { + assertMatch("[[:alnum:]]", ":", false); + } + + @Test + public void testBlankGroupCase0() throws Exception { + assertMatch("[[:blank:]]", " ", true); + } + + @Test + public void testBlankGroupCase1() throws Exception { + assertMatch("[[:blank:]]", "\t", true); + } + + @Test + public void testBlankGroupCase2() throws Exception { + assertMatch("[[:blank:]]", "\r", false); + } + + @Test + public void testBlankGroupCase3() throws Exception { + assertMatch("[[:blank:]]", "\n", false); + } + + @Test + public void testBlankGroupCase4() throws Exception { + assertMatch("[[:blank:]]", "a", false); + } + + @Test + public void testCntrlGroupCase0() throws Exception { + assertMatch("[[:cntrl:]]", "a", false); + } + + @Test + public void testCntrlGroupCase1() throws Exception { + assertMatch("[[:cntrl:]]", String.valueOf((char) 7), true); + } + + @Test + public void testDigitGroupCase0() throws Exception { + assertMatch("[[:digit:]]", "0", true); + } + + @Test + public void testDigitGroupCase1() throws Exception { + assertMatch("[[:digit:]]", "5", true); + } + + @Test + public void testDigitGroupCase2() throws Exception { + assertMatch("[[:digit:]]", "9", true); + } + + @Test + public void testDigitGroupCase3() throws Exception { + // \u06f9 = EXTENDED ARABIC-INDIC DIGIT NINE + assertMatch("[[:digit:]]", "\u06f9", true); + } + + @Test + public void testDigitGroupCase4() throws Exception { + assertMatch("[[:digit:]]", "a", false); + } + + @Test + public void testDigitGroupCase5() throws Exception { + assertMatch("[[:digit:]]", "]", false); + } + + @Test + public void testGraphGroupCase0() throws Exception { + assertMatch("[[:graph:]]", "]", true); + } + + @Test + public void testGraphGroupCase1() throws Exception { + assertMatch("[[:graph:]]", "a", true); + } + + @Test + public void testGraphGroupCase2() throws Exception { + assertMatch("[[:graph:]]", ".", true); + } + + @Test + public void testGraphGroupCase3() throws Exception { + assertMatch("[[:graph:]]", "0", true); + } + + @Test + public void testGraphGroupCase4() throws Exception { + assertMatch("[[:graph:]]", " ", false); + } + + @Test + public void testGraphGroupCase5() throws Exception { + // \u00f6 = 'o' with dots on it + assertMatch("[[:graph:]]", "\u00f6", true); + } + + @Test + public void testLowerGroupCase0() throws Exception { + assertMatch("[[:lower:]]", "a", true); + } + + @Test + public void testLowerGroupCase1() throws Exception { + assertMatch("[[:lower:]]", "h", true); + } + + @Test + public void testLowerGroupCase2() throws Exception { + assertMatch("[[:lower:]]", "A", false); + } + + @Test + public void testLowerGroupCase3() throws Exception { + assertMatch("[[:lower:]]", "H", false); + } + + @Test + public void testLowerGroupCase4() throws Exception { + // \u00e4 = small 'a' with dots on it + assertMatch("[[:lower:]]", "\u00e4", true); + } + + @Test + public void testLowerGroupCase5() throws Exception { + assertMatch("[[:lower:]]", ".", false); + } + + @Test + public void testPrintGroupCase0() throws Exception { + assertMatch("[[:print:]]", "]", true); + } + + @Test + public void testPrintGroupCase1() throws Exception { + assertMatch("[[:print:]]", "a", true); + } + + @Test + public void testPrintGroupCase2() throws Exception { + assertMatch("[[:print:]]", ".", true); + } + + @Test + public void testPrintGroupCase3() throws Exception { + assertMatch("[[:print:]]", "0", true); + } + + @Test + public void testPrintGroupCase4() throws Exception { + assertMatch("[[:print:]]", " ", true); + } + + @Test + public void testPrintGroupCase5() throws Exception { + // \u00f6 = 'o' with dots on it + assertMatch("[[:print:]]", "\u00f6", true); + } + + @Test + public void testPunctGroupCase0() throws Exception { + assertMatch("[[:punct:]]", ".", true); + } + + @Test + public void testPunctGroupCase1() throws Exception { + assertMatch("[[:punct:]]", "@", true); + } + + @Test + public void testPunctGroupCase2() throws Exception { + assertMatch("[[:punct:]]", " ", false); + } + + @Test + public void testPunctGroupCase3() throws Exception { + assertMatch("[[:punct:]]", "a", false); + } + + @Test + public void testSpaceGroupCase0() throws Exception { + assertMatch("[[:space:]]", " ", true); + } + + @Test + public void testSpaceGroupCase1() throws Exception { + assertMatch("[[:space:]]", "\t", true); + } + + @Test + public void testSpaceGroupCase2() throws Exception { + assertMatch("[[:space:]]", "\r", true); + } + + @Test + public void testSpaceGroupCase3() throws Exception { + assertMatch("[[:space:]]", "\n", true); + } + + @Test + public void testSpaceGroupCase4() throws Exception { + assertMatch("[[:space:]]", "a", false); + } + + @Test + public void testUpperGroupCase0() throws Exception { + assertMatch("[[:upper:]]", "a", false); + } + + @Test + public void testUpperGroupCase1() throws Exception { + assertMatch("[[:upper:]]", "h", false); + } + + @Test + public void testUpperGroupCase2() throws Exception { + assertMatch("[[:upper:]]", "A", true); + } + + @Test + public void testUpperGroupCase3() throws Exception { + assertMatch("[[:upper:]]", "H", true); + } + + @Test + public void testUpperGroupCase4() throws Exception { + // \u00c4 = 'A' with dots on it + assertMatch("[[:upper:]]", "\u00c4", true); + } + + @Test + public void testUpperGroupCase5() throws Exception { + assertMatch("[[:upper:]]", ".", false); + } + + @Test + public void testXDigitGroupCase0() throws Exception { + assertMatch("[[:xdigit:]]", "a", true); + } + + @Test + public void testXDigitGroupCase1() throws Exception { + assertMatch("[[:xdigit:]]", "d", true); + } + + @Test + public void testXDigitGroupCase2() throws Exception { + assertMatch("[[:xdigit:]]", "f", true); + } + + @Test + public void testXDigitGroupCase3() throws Exception { + assertMatch("[[:xdigit:]]", "0", true); + } + + @Test + public void testXDigitGroupCase4() throws Exception { + assertMatch("[[:xdigit:]]", "5", true); + } + + @Test + public void testXDigitGroupCase5() throws Exception { + assertMatch("[[:xdigit:]]", "9", true); + } + + @Test + public void testXDigitGroupCase6() throws Exception { + assertMatch("[[:xdigit:]]", "۹", false); + } + + @Test + public void testXDigitGroupCase7() throws Exception { + assertMatch("[[:xdigit:]]", ".", false); + } + + @Test + public void testWordGroupCase0() throws Exception { + assertMatch("[[:word:]]", "g", true); + } + + @Test + public void testWordGroupCase1() throws Exception { + // \u00f6 = 'o' with dots on it + assertMatch("[[:word:]]", "\u00f6", true); + } + + @Test + public void testWordGroupCase2() throws Exception { + assertMatch("[[:word:]]", "5", true); + } + + @Test + public void testWordGroupCase3() throws Exception { + assertMatch("[[:word:]]", "_", true); + } + + @Test + public void testWordGroupCase4() throws Exception { + assertMatch("[[:word:]]", " ", false); + } + + @Test + public void testWordGroupCase5() throws Exception { + assertMatch("[[:word:]]", ".", false); + } + + @Test + public void testMixedGroupCase0() throws Exception { + assertMatch("[A[:lower:]C3-5]", "A", true); + } + + @Test + public void testMixedGroupCase1() throws Exception { + assertMatch("[A[:lower:]C3-5]", "C", true); + } + + @Test + public void testMixedGroupCase2() throws Exception { + assertMatch("[A[:lower:]C3-5]", "e", true); + } + + @Test + public void testMixedGroupCase3() throws Exception { + assertMatch("[A[:lower:]C3-5]", "3", true); + } + + @Test + public void testMixedGroupCase4() throws Exception { + assertMatch("[A[:lower:]C3-5]", "4", true); + } + + @Test + public void testMixedGroupCase5() throws Exception { + assertMatch("[A[:lower:]C3-5]", "5", true); + } + + @Test + public void testMixedGroupCase6() throws Exception { + assertMatch("[A[:lower:]C3-5]", "B", false); + } + + @Test + public void testMixedGroupCase7() throws Exception { + assertMatch("[A[:lower:]C3-5]", "2", false); + } + + @Test + public void testMixedGroupCase8() throws Exception { + assertMatch("[A[:lower:]C3-5]", "6", false); + } + + @Test + public void testMixedGroupCase9() throws Exception { + assertMatch("[A[:lower:]C3-5]", ".", false); + } + + @Test + public void testSpecialGroupCase0() throws Exception { + assertMatch("[[]", "[", true); + } + + @Test + public void testSpecialGroupCase1() throws Exception { + assertMatch("[]]", "]", true); + } + + @Test + public void testSpecialGroupCase2() throws Exception { + assertMatch("[]a]", "]", true); + } + + @Test + public void testSpecialGroupCase3() throws Exception { + assertMatch("[a[]", "[", true); + } + + @Test + public void testSpecialGroupCase4() throws Exception { + assertMatch("[a[]", "a", true); + } + + @Test + public void testSpecialGroupCase5() throws Exception { + assertMatch("[!]]", "]", false); + } + + @Test + public void testSpecialGroupCase6() throws Exception { + assertMatch("[!]]", "x", true); + } + + @Test + public void testSpecialGroupCase7() throws Exception { + assertMatch("[:]]", ":]", true); + } + + @Test + public void testSpecialGroupCase8() throws Exception { + assertMatch("[:]]", ":", false); + } + + @Test + public void testSpecialGroupCase9() throws Exception { + if (useJGitRule) + System.err.println("IgnoreRule can't understand [[:], skipping"); + Boolean assume = useJGitRule; + // Second bracket is threated literally, so both [ and : should match + assertMatch("[[:]", ":", true, assume); + assertMatch("[[:]", "[", true, assume); + } + + @Test + public void testUnsupportedGroupCase0() throws Exception { + assertMatch("[[=a=]]", "a", false); + assertMatch("[[=a=]]", "=", false); + assertMatch("[=a=]", "a", true); + assertMatch("[=a=]", "=", true); + } + + @Test + public void testUnsupportedGroupCase01() throws Exception { + assertMatch("[.a.]*[.a.]", "aha", true); + } + + @Test + public void testUnsupportedGroupCase1() throws Exception { + assertMatch("[[.a.]]", "a", false); + assertMatch("[[.a.]]", ".", false); + assertMatch("[.a.]", "a", true); + assertMatch("[.a.]", ".", true); + } + + @Test + public void testEscapedBracket1() throws Exception { + assertMatch("\\[", "[", true); + } + + @Test + public void testEscapedBracket2() throws Exception { + assertMatch("\\[[a]", "[", false); + } + + @Test + public void testEscapedBracket3() throws Exception { + assertMatch("\\[[a]", "a", false); + } + + @Test + public void testEscapedBracket4() throws Exception { + assertMatch("\\[[a]", "[a", true); + } + + @Test + public void testEscapedBracket5() throws Exception { + assertMatch("[a\\]]", "]", true); + } + + @Test + public void testEscapedBracket6() throws Exception { + assertMatch("[a\\]]", "a", true); + } + + @Test + public void testEscapedBackslash() throws Exception { + if (useJGitRule) + System.err + .println("IgnoreRule can't understand escaped backslashes, skipping"); + Boolean assume = useJGitRule; + // In Git CLI a\\b matches a\b file + assertMatch("a\\\\b", "a\\b", true, assume); + } + + @Test + public void testMultipleEscapedCharacters1() throws Exception { + assertMatch("\\]a?c\\*\\[d\\?\\]", "]abc*[d?]", true); + } + + @Test + public void testFilePathSimpleCase() throws Exception { + assertFileNameMatch("a/b", "a/b", true); + } + + @Test + public void testFilePathCase0() throws Exception { + assertFileNameMatch("a*b", "a/b", false); + } + + @Test + public void testFilePathCase1() throws Exception { + assertFileNameMatch("a?b", "a/b", false); + } + + @Test + public void testFilePathCase2() throws Exception { + assertFileNameMatch("a*b", "a\\b", true); + } + + @Test + public void testFilePathCase3() throws Exception { + assertFileNameMatch("a?b", "a\\b", true); + } + +}
\ No newline at end of file diff --git a/org.eclipse.jgit/META-INF/MANIFEST.MF b/org.eclipse.jgit/META-INF/MANIFEST.MF index 9cbb9955f2..80399f6008 100644 --- a/org.eclipse.jgit/META-INF/MANIFEST.MF +++ b/org.eclipse.jgit/META-INF/MANIFEST.MF @@ -50,6 +50,7 @@ Export-Package: org.eclipse.jgit.api;version="3.6.0"; org.eclipse.jgit.lib", org.eclipse.jgit.gitrepo.internal;version="3.6.0";x-internal:=true, org.eclipse.jgit.ignore;version="3.6.0", + org.eclipse.jgit.ignore.internal;version="3.6.0";x-friends:="org.eclipse.jgit.test", org.eclipse.jgit.internal;version="3.6.0";x-friends:="org.eclipse.jgit.test,org.eclipse.jgit.http.test", org.eclipse.jgit.internal.storage.dfs;version="3.6.0";x-friends:="org.eclipse.jgit.test", org.eclipse.jgit.internal.storage.file;version="3.6.0"; diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/ignore/FastIgnoreRule.java b/org.eclipse.jgit/src/org/eclipse/jgit/ignore/FastIgnoreRule.java new file mode 100644 index 0000000000..02863bd16a --- /dev/null +++ b/org.eclipse.jgit/src/org/eclipse/jgit/ignore/FastIgnoreRule.java @@ -0,0 +1,229 @@ +/* + * Copyright (C) 2014, 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 + * under the terms of the Eclipse Distribution License v1.0 which + * accompanies this distribution, is reproduced below, and is + * available at http://www.eclipse.org/org/documents/edl-v10.php + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * - Neither the name of the Eclipse Foundation, Inc. nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package org.eclipse.jgit.ignore; + +import static org.eclipse.jgit.ignore.internal.Strings.stripTrailing; + +import org.eclipse.jgit.errors.InvalidPatternException; +import org.eclipse.jgit.ignore.internal.IMatcher; +import org.eclipse.jgit.ignore.internal.PathMatcher; + +/** + * "Fast" (compared with IgnoreRule) git ignore rule implementation supporting + * also double star <code>**<code> pattern. + * <p> + * This class is immutable and thread safe. + * + * @since 3.6 + */ +public class FastIgnoreRule { + + /** + * Character used as default path separator for ignore entries + */ + public static final char PATH_SEPARATOR = '/'; + + private static final NoResultMatcher NO_MATCH = new NoResultMatcher(); + + private final IMatcher matcher; + + private final boolean inverse; + + private final boolean dirOnly; + + /** + * + * @param pattern + * ignore pattern as described in <a href= + * "https://www.kernel.org/pub/software/scm/git/docs/gitignore.html" + * >git manual</a>. If pattern is invalid or is not a pattern + * (comment), this rule doesn't match anything. + */ + public FastIgnoreRule(String pattern) { + if (pattern == null) + throw new IllegalArgumentException("Pattern must not be null!"); //$NON-NLS-1$ + if (pattern.length() == 0) { + dirOnly = false; + inverse = false; + this.matcher = NO_MATCH; + return; + } + inverse = pattern.charAt(0) == '!'; + if (inverse) { + pattern = pattern.substring(1); + if (pattern.length() == 0) { + dirOnly = false; + this.matcher = NO_MATCH; + return; + } + } + if (pattern.charAt(0) == '#') { + this.matcher = NO_MATCH; + dirOnly = false; + } else { + dirOnly = pattern.charAt(pattern.length() - 1) == PATH_SEPARATOR; + if (dirOnly) { + pattern = stripTrailing(pattern, PATH_SEPARATOR); + if (pattern.length() == 0) { + this.matcher = NO_MATCH; + return; + } + } + IMatcher m; + try { + m = PathMatcher.createPathMatcher(pattern, + Character.valueOf(PATH_SEPARATOR), dirOnly); + } catch (InvalidPatternException e) { + m = NO_MATCH; + } + this.matcher = m; + } + } + + /** + * Returns true if a match was made. <br> + * This function does NOT return the actual ignore status of the target! + * Please consult {@link #getResult()} for the negation status. The actual + * ignore status may be true or false depending on whether this rule is an + * ignore rule or a negation rule. + * + * @param path + * Name pattern of the file, relative to the base directory of + * this rule + * @param directory + * Whether the target file is a directory or not + * @return True if a match was made. This does not necessarily mean that the + * target is ignored. Call {@link #getResult() getResult()} for the + * result. + */ + public boolean isMatch(String path, boolean directory) { + if (path == null) + return false; + if (path.length() == 0) + return false; + boolean match = matcher.matches(path, directory); + return match; + } + + /** + * @return True if the pattern is just a file name and not a path + */ + public boolean getNameOnly() { + return !(matcher instanceof PathMatcher); + } + + /** + * + * @return True if the pattern should match directories only + */ + public boolean dirOnly() { + return dirOnly; + } + + /** + * Indicates whether the rule is non-negation or negation. + * + * @return True if the pattern had a "!" in front of it + */ + public boolean getNegation() { + return inverse; + } + + /** + * Indicates whether the rule is non-negation or negation. + * + * @return True if the target is to be ignored, false otherwise. + */ + public boolean getResult() { + return !inverse; + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + if (inverse) + sb.append('!'); + sb.append(matcher); + if (dirOnly) + sb.append(PATH_SEPARATOR); + return sb.toString(); + + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + (inverse ? 1231 : 1237); + result = prime * result + (dirOnly ? 1231 : 1237); + result = prime * result + ((matcher == null) ? 0 : matcher.hashCode()); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (!(obj instanceof FastIgnoreRule)) + return false; + + FastIgnoreRule other = (FastIgnoreRule) obj; + if (inverse != other.inverse) + return false; + if (dirOnly != other.dirOnly) + return false; + return matcher.equals(other.matcher); + } + + static final class NoResultMatcher implements IMatcher { + + public boolean matches(String path, boolean assumeDirectory) { + return false; + } + + public boolean matches(String segment, int startIncl, int endExcl, + boolean assumeDirectory) { + return false; + } + } +} diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/ignore/IgnoreNode.java b/org.eclipse.jgit/src/org/eclipse/jgit/ignore/IgnoreNode.java index 2cddddbe11..ff74f7cabf 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/ignore/IgnoreNode.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/ignore/IgnoreNode.java @@ -71,11 +71,11 @@ public class IgnoreNode { } /** The rules that have been parsed into this node. */ - private final List<IgnoreRule> rules; + private final List<FastIgnoreRule> rules; /** Create an empty ignore node with no rules. */ public IgnoreNode() { - rules = new ArrayList<IgnoreRule>(); + rules = new ArrayList<FastIgnoreRule>(); } /** @@ -84,7 +84,7 @@ public class IgnoreNode { * @param rules * list of rules. **/ - public IgnoreNode(List<IgnoreRule> rules) { + public IgnoreNode(List<FastIgnoreRule> rules) { this.rules = rules; } @@ -103,7 +103,7 @@ public class IgnoreNode { while ((txt = br.readLine()) != null) { txt = txt.trim(); if (txt.length() > 0 && !txt.startsWith("#") && !txt.equals("/")) //$NON-NLS-1$ //$NON-NLS-2$ - rules.add(new IgnoreRule(txt)); + rules.add(new FastIgnoreRule(txt)); } } @@ -112,7 +112,7 @@ public class IgnoreNode { } /** @return list of all ignore rules held by this node. */ - public List<IgnoreRule> getRules() { + public List<FastIgnoreRule> getRules() { return Collections.unmodifiableList(rules); } @@ -133,7 +133,7 @@ public class IgnoreNode { // Parse rules in the reverse order that they were read for (int i = rules.size() - 1; i > -1; i--) { - IgnoreRule rule = rules.get(i); + FastIgnoreRule rule = rules.get(i); if (rule.isMatch(entryPath, isDirectory)) { if (rule.getResult()) return MatchResult.IGNORED; diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/ignore/IgnoreRule.java b/org.eclipse.jgit/src/org/eclipse/jgit/ignore/IgnoreRule.java index 42bbd9e9b8..f14d1bd0b2 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/ignore/IgnoreRule.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/ignore/IgnoreRule.java @@ -46,11 +46,16 @@ import org.eclipse.jgit.errors.InvalidPatternException; import org.eclipse.jgit.fnmatch.FileNameMatcher; /** - * A single ignore rule corresponding to one line in a .gitignore or - * ignore file. Parses the ignore pattern + * A single ignore rule corresponding to one line in a .gitignore or ignore + * file. Parses the ignore pattern * * Inspiration from: Ferry Huberts + * + * @deprecated this rule does not support double star pattern and is slow + * parsing glob expressions. Consider to use {@link FastIgnoreRule} + * instead. See https://bugs.eclipse.org/bugs/show_bug.cgi?id=440732 */ +@Deprecated public class IgnoreRule { private String pattern; private boolean negation; @@ -270,4 +275,4 @@ public class IgnoreRule { public String toString() { return pattern; } -}
\ No newline at end of file +} diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/ignore/internal/AbstractMatcher.java b/org.eclipse.jgit/src/org/eclipse/jgit/ignore/internal/AbstractMatcher.java new file mode 100644 index 0000000000..4e90d8c3cb --- /dev/null +++ b/org.eclipse.jgit/src/org/eclipse/jgit/ignore/internal/AbstractMatcher.java @@ -0,0 +1,88 @@ +/* + * Copyright (C) 2014, 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 + * under the terms of the Eclipse Distribution License v1.0 which + * accompanies this distribution, is reproduced below, and is + * available at http://www.eclipse.org/org/documents/edl-v10.php + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * - Neither the name of the Eclipse Foundation, Inc. nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package org.eclipse.jgit.ignore.internal; + +/** + * Base class for default methods as {@link #toString()} and such. + * <p> + * This class is immutable and thread safe. + * + * @since 3.6 + */ +public abstract class AbstractMatcher implements IMatcher { + + final boolean dirOnly; + + final String pattern; + + /** + * @param pattern + * string to parse + * @param dirOnly + * true if this matcher should match only directories + */ + AbstractMatcher(String pattern, boolean dirOnly) { + this.pattern = pattern; + this.dirOnly = dirOnly; + } + + @Override + public String toString() { + return pattern; + } + + @Override + public int hashCode() { + return pattern.hashCode(); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (!(obj instanceof AbstractMatcher)) + return false; + AbstractMatcher other = (AbstractMatcher) obj; + return dirOnly == other.dirOnly && pattern.equals(other.pattern); + } +} 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 new file mode 100644 index 0000000000..10b5e49e1f --- /dev/null +++ b/org.eclipse.jgit/src/org/eclipse/jgit/ignore/internal/IMatcher.java @@ -0,0 +1,80 @@ +/* + * Copyright (C) 2014, 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 + * under the terms of the Eclipse Distribution License v1.0 which + * accompanies this distribution, is reproduced below, and is + * available at http://www.eclipse.org/org/documents/edl-v10.php + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * - Neither the name of the Eclipse Foundation, Inc. nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package org.eclipse.jgit.ignore.internal; + +/** + * Generic string matcher + * + * @since 3.6 + */ +public interface IMatcher { + + /** + * Matches entire given string + * + * @param path + * string which is not null, but might be empty + * @param assumeDirectory + * true to assume this path as directory (even if it doesn't end + * with a slash) + * @return true if this matcher pattern matches given string + */ + boolean matches(String path, boolean assumeDirectory); + + /** + * Matches only part of given string + * + * @param segment + * string which is not null, but might be empty + * @param startIncl + * start index, inclusive + * @param endExcl + * end index, exclusive + * @param assumeDirectory + * true to assume this path as directory (even if it doesn't end + * with a slash) + * @return true if this matcher pattern matches given string + */ + boolean matches(String segment, int startIncl, int endExcl, + boolean assumeDirectory); +} 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 new file mode 100644 index 0000000000..6c4c8092fe --- /dev/null +++ b/org.eclipse.jgit/src/org/eclipse/jgit/ignore/internal/NameMatcher.java @@ -0,0 +1,112 @@ +/* + * Copyright (C) 2014, 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 + * under the terms of the Eclipse Distribution License v1.0 which + * accompanies this distribution, is reproduced below, and is + * available at http://www.eclipse.org/org/documents/edl-v10.php + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * - Neither the name of the Eclipse Foundation, Inc. nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package org.eclipse.jgit.ignore.internal; + +import static org.eclipse.jgit.ignore.internal.Strings.getPathSeparator; + +/** + * Matcher built from patterns for file names (single path segments). This class + * is immutable and thread safe. + * + * @since 3.6 + */ +public class NameMatcher extends AbstractMatcher { + + final boolean beginning; + + final char slash; + + final String subPattern; + + NameMatcher(String pattern, Character pathSeparator, boolean dirOnly) { + super(pattern, dirOnly); + slash = getPathSeparator(pathSeparator); + beginning = pattern.length() == 0 ? false : pattern.charAt(0) == slash; + if (!beginning) + this.subPattern = pattern; + else + this.subPattern = pattern.substring(1); + } + + 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) + // 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 false; + } + + public boolean matches(String segment, int startIncl, int endExcl, + boolean assumeDirectory) { + // faster local access, same as in string.indexOf() + String s = subPattern; + if (s.length() != (endExcl - startIncl)) + return false; + for (int i = 0; i < s.length(); i++) { + char c1 = s.charAt(i); + char c2 = segment.charAt(i + startIncl); + 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 new file mode 100644 index 0000000000..753971ed40 --- /dev/null +++ b/org.eclipse.jgit/src/org/eclipse/jgit/ignore/internal/PathMatcher.java @@ -0,0 +1,239 @@ +/* + * Copyright (C) 2014, 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 + * under the terms of the Eclipse Distribution License v1.0 which + * accompanies this distribution, is reproduced below, and is + * available at http://www.eclipse.org/org/documents/edl-v10.php + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * - Neither the name of the Eclipse Foundation, Inc. nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package org.eclipse.jgit.ignore.internal; + +import static org.eclipse.jgit.ignore.internal.Strings.*; + +import java.util.*; + +import org.eclipse.jgit.errors.InvalidPatternException; +import org.eclipse.jgit.ignore.FastIgnoreRule; + +/** + * Matcher built by patterns consists of multiple path segments. + * <p> + * This class is immutable and thread safe. + * + * @since 3.6 + */ +public class PathMatcher extends AbstractMatcher { + + private static final WildMatcher WILD = WildMatcher.INSTANCE; + + private final List<IMatcher> matchers; + + private final char slash; + + private boolean beginning; + + PathMatcher(String pattern, Character pathSeparator, boolean dirOnly) + throws InvalidPatternException { + super(pattern, dirOnly); + slash = getPathSeparator(pathSeparator); + beginning = pattern.indexOf(slash) == 0; + if (isSimplePathWithSegments(pattern)) + matchers = null; + else + matchers = createMatchers(split(pattern, slash), pathSeparator, + dirOnly); + } + + private boolean isSimplePathWithSegments(String path) { + return !isWildCard(path) && count(path, slash, true) > 0; + } + + static private List<IMatcher> createMatchers(List<String> segments, + Character pathSeparator, boolean dirOnly) + throws InvalidPatternException { + List<IMatcher> matchers = new ArrayList<IMatcher>(segments.size()); + for (int i = 0; i < segments.size(); i++) { + String segment = segments.get(i); + IMatcher matcher = createNameMatcher0(segment, pathSeparator, + dirOnly); + if (matcher == WILD && i > 0 + && matchers.get(matchers.size() - 1) == WILD) + // collapse wildmatchers **/** is same as ** + continue; + matchers.add(matcher); + } + return matchers; + } + + /** + * + * @param pattern + * @param pathSeparator + * if this parameter isn't null then this character will not + * match at wildcards(* and ? are wildcards). + * @param dirOnly + * @return never null + * @throws InvalidPatternException + */ + public static IMatcher createPathMatcher(String pattern, + Character pathSeparator, boolean dirOnly) + throws InvalidPatternException { + pattern = pattern.trim(); + char slash = Strings.getPathSeparator(pathSeparator); + // ignore possible leading and trailing slash + int slashIdx = pattern.indexOf(slash, 1); + if (slashIdx > 0 && slashIdx < pattern.length() - 1) + return new PathMatcher(pattern, pathSeparator, dirOnly); + return createNameMatcher0(pattern, pathSeparator, dirOnly); + } + + private static IMatcher createNameMatcher0(String segment, + Character pathSeparator, boolean dirOnly) + throws InvalidPatternException { + // check if we see /** or ** segments => double star pattern + if (WildMatcher.WILDMATCH.equals(segment) + || WildMatcher.WILDMATCH2.equals(segment)) + return WILD; + if (isWildCard(segment)) + return new WildCardMatcher(segment, pathSeparator, dirOnly); + return new NameMatcher(segment, pathSeparator, dirOnly); + } + + public boolean matches(String path, boolean assumeDirectory) { + if (matchers == null) + return simpleMatch(path, assumeDirectory); + return iterate(path, 0, path.length(), assumeDirectory); + } + + /* + * Stupid but fast string comparison: the case where we don't have to match + * 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) { + boolean hasSlash = path.indexOf(slash) == 0; + if (beginning && !hasSlash) + path = slash + path; + + 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; + + /* + * 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)) + return true; + + return false; + } + + public boolean matches(String segment, int startIncl, int endExcl, + boolean assumeDirectory) { + throw new UnsupportedOperationException( + "Path matcher works only on entire paths"); //$NON-NLS-1$ + } + + boolean iterate(final String path, final int startIncl, final int endExcl, + boolean assumeDirectory) { + int matcher = 0; + int right = startIncl; + boolean match = false; + int lastWildmatch = -1; + while (true) { + int left = right; + right = path.indexOf(slash, right); + if (right == -1) { + if (left < endExcl) + match = matches(matcher, path, left, endExcl, + assumeDirectory); + if (match) { + if (matcher == matchers.size() - 2 + && matchers.get(matcher + 1) == WILD) + // ** can match *nothing*: a/b/** match also a/b + return true; + if (matcher < matchers.size() - 1 + && matchers.get(matcher) == WILD) { + // ** can match *nothing*: a/**/b match also a/b + matcher++; + match = matches(matcher, path, left, endExcl, + assumeDirectory); + } else if (dirOnly) + return false; + } + return match && matcher + 1 == matchers.size(); + } + if (right - left > 0) + match = matches(matcher, path, left, right, assumeDirectory); + else { + // path starts with slash??? + right++; + continue; + } + if (match) { + if (matchers.get(matcher) == WILD) { + lastWildmatch = matcher; + // ** can match *nothing*: a/**/b match also a/b + right = left - 1; + } + matcher++; + if (matcher == matchers.size()) + return true; + } else if (lastWildmatch != -1) + matcher = lastWildmatch + 1; + else + return false; + right++; + } + } + + 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/Strings.java b/org.eclipse.jgit/src/org/eclipse/jgit/ignore/internal/Strings.java new file mode 100644 index 0000000000..e9b300760a --- /dev/null +++ b/org.eclipse.jgit/src/org/eclipse/jgit/ignore/internal/Strings.java @@ -0,0 +1,373 @@ +/* + * Copyright (C) 2014, 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 + * under the terms of the Eclipse Distribution License v1.0 which + * accompanies this distribution, is reproduced below, and is + * available at http://www.eclipse.org/org/documents/edl-v10.php + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * - Neither the name of the Eclipse Foundation, Inc. nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package org.eclipse.jgit.ignore.internal; + +import static java.lang.Character.isLetter; + +import java.util.*; +import java.util.regex.Pattern; + +import org.eclipse.jgit.errors.InvalidPatternException; +import org.eclipse.jgit.ignore.FastIgnoreRule; + +/** + * Various {@link String} related utility methods, written mostly to avoid + * generation of new String objects (e.g. via splitting Strings etc). + * + * @since 3.6 + */ +public class Strings { + + static char getPathSeparator(Character pathSeparator) { + return pathSeparator == null ? FastIgnoreRule.PATH_SEPARATOR + : pathSeparator.charValue(); + } + + /** + * @param pattern + * non null + * @param c + * character to remove + * @return new string with all trailing characters removed + */ + public static String stripTrailing(String pattern, char c) { + while (pattern.length() > 0 + && pattern.charAt(pattern.length() - 1) == c) + pattern = pattern.substring(0, pattern.length() - 1); + return pattern; + } + + static int count(String s, char c, boolean ignoreFirstLast) { + int start = 0; + int count = 0; + while (true) { + start = s.indexOf(c, start); + if (start == -1) + break; + if (!ignoreFirstLast || (start != 0 && start != s.length())) + count++; + start++; + } + return count; + } + + /** + * Splits given string to substrings by given separator + * + * @param pattern + * non null + * @param slash + * separator char + * @return list of substrings + */ + public static List<String> split(String pattern, char slash) { + int count = count(pattern, slash, true); + if (count < 1) + throw new IllegalStateException( + "Pattern must have at least two segments: " + pattern); //$NON-NLS-1$ + List<String> segments = new ArrayList<String>(count); + int right = 0; + while (true) { + int left = right; + right = pattern.indexOf(slash, right); + if (right == -1) { + if (left < pattern.length()) + segments.add(pattern.substring(left)); + break; + } + if (right - left > 0) + if (left == 1) + // leading slash should remain by the first pattern + segments.add(pattern.substring(left - 1, right)); + else if (right == pattern.length() - 1) + // trailing slash should remain too + segments.add(pattern.substring(left, right + 1)); + else + segments.add(pattern.substring(left, right)); + right++; + } + return segments; + } + + static boolean isWildCard(String pattern) { + return pattern.indexOf('*') != -1 || pattern.indexOf('?') != -1 + || pattern.indexOf('[') != -1 + // required to match escaped backslashes '\\\\' + || pattern.indexOf('\\') != -1 || pattern.indexOf(']') != -1; + } + + final static List<String> POSIX_CHAR_CLASSES = Arrays.asList( + "alnum", "alpha", "blank", "cntrl", //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$ + // [:alnum:] [:alpha:] [:blank:] [:cntrl:] + "digit", "graph", "lower", "print", //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$ + // [:digit:] [:graph:] [:lower:] [:print:] + "punct", "space", "upper", "xdigit", //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$ + // [:punct:] [:space:] [:upper:] [:xdigit:] + "word" //$NON-NLS-1$ + // [:word:] XXX I don't see it in + // http://man7.org/linux/man-pages/man7/glob.7.html + // but this was in org.eclipse.jgit.fnmatch.GroupHead.java ??? + ); + + private static final String DL = "\\p{javaDigit}\\p{javaLetter}"; //$NON-NLS-1$ + + final static List<String> JAVA_CHAR_CLASSES = Arrays + .asList("\\p{Alnum}", "\\p{javaLetter}", "\\p{Blank}", "\\p{Cntrl}", //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$ + // [:alnum:] [:alpha:] [:blank:] [:cntrl:] + "\\p{javaDigit}", "[\\p{Graph}" + DL + "]", "\\p{Ll}", "[\\p{Print}" + DL + "]", //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$ //$NON-NLS-5$ //$NON-NLS-6$ + // [:digit:] [:graph:] [:lower:] [:print:] + "\\p{Punct}", "\\p{Space}", "\\p{Lu}", "\\p{XDigit}", //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$ + // [:punct:] [:space:] [:upper:] [:xdigit:] + "[" + DL + "_]" //$NON-NLS-1$ //$NON-NLS-2$ + // [:word:] + ); + + // Collating symbols [[.a.]] or equivalence class expressions [[=a=]] are + // not supported by CLI git (at least not by 1.9.1) + final static Pattern UNSUPPORTED = Pattern + .compile("\\[\\[[.=]\\w+[.=]\\]\\]"); //$NON-NLS-1$ + + /** + * Conversion from glob to Java regex following two sources: <li> + * http://man7.org/linux/man-pages/man7/glob.7.html <li> + * org.eclipse.jgit.fnmatch.FileNameMatcher.java Seems that there are + * various ways to define what "glob" can be. + * + * @param pattern + * non null pattern + * + * @return Java regex pattern corresponding to given glob pattern + * @throws InvalidPatternException + */ + static Pattern convertGlob(String pattern) throws InvalidPatternException { + if (UNSUPPORTED.matcher(pattern).find()) + throw new InvalidPatternException( + "Collating symbols [[.a.]] or equivalence class expressions [[=a=]] are not supported", //$NON-NLS-1$ + pattern); + + StringBuilder sb = new StringBuilder(pattern.length()); + + int in_brackets = 0; + boolean seenEscape = false; + boolean ignoreLastBracket = false; + boolean in_char_class = false; + // 6 is the length of the longest posix char class "xdigit" + char[] charClass = new char[6]; + + for (int i = 0; i < pattern.length(); i++) { + char c = pattern.charAt(i); + switch (c) { + + case '*': + if (seenEscape || in_brackets > 0) + sb.append(c); + else + sb.append('.').append(c); + break; + + case '.': + if (seenEscape) + sb.append(c); + else + sb.append('\\').append('.'); + break; + + case '?': + if (seenEscape || in_brackets > 0) + sb.append(c); + else + sb.append('.'); + break; + + case ':': + if (in_brackets > 0) + if (lookBehind(sb) == '[' + && isLetter(lookAhead(pattern, i))) + in_char_class = true; + sb.append(':'); + break; + + case '-': + if (in_brackets > 0) { + if (lookAhead(pattern, i) == ']') + sb.append('\\').append(c); + else + sb.append(c); + } else + sb.append('-'); + break; + + case '\\': + if (in_brackets > 0) { + char lookAhead = lookAhead(pattern, i); + if (lookAhead == ']' || lookAhead == '[') + ignoreLastBracket = true; + } + sb.append(c); + break; + + case '[': + if (in_brackets > 0) { + sb.append('\\').append('['); + ignoreLastBracket = true; + } else { + if (!seenEscape) { + in_brackets++; + ignoreLastBracket = false; + } + sb.append('['); + } + break; + + case ']': + if (seenEscape) { + sb.append(']'); + ignoreLastBracket = true; + break; + } + if (in_brackets <= 0) { + sb.append('\\').append(']'); + ignoreLastBracket = true; + break; + } + char lookBehind = lookBehind(sb); + if ((lookBehind == '[' && !ignoreLastBracket) + || lookBehind == '^') { + sb.append('\\'); + sb.append(']'); + ignoreLastBracket = true; + } else { + ignoreLastBracket = false; + if (!in_char_class) { + in_brackets--; + sb.append(']'); + } else { + in_char_class = false; + String charCl = checkPosixCharClass(charClass); + // delete last \[:: chars and set the pattern + if (charCl != null) { + sb.setLength(sb.length() - 4); + sb.append(charCl); + } + reset(charClass); + } + } + break; + + case '!': + if (in_brackets > 0) { + if (lookBehind(sb) == '[') + sb.append('^'); + else + sb.append(c); + } else + sb.append(c); + break; + + default: + if (in_char_class) + setNext(charClass, c); + else + sb.append(c); + break; + } // end switch + + seenEscape = c == '\\'; + + } // end for + + if (in_brackets > 0) + throw new InvalidPatternException("Not closed bracket?", pattern); //$NON-NLS-1$ + return Pattern.compile(sb.toString()); + } + + /** + * @param buffer + * @return zero of the buffer is empty, otherwise the last character from + * buffer + */ + private static char lookBehind(StringBuilder buffer) { + return buffer.length() > 0 ? buffer.charAt(buffer.length() - 1) : 0; + } + + /** + * @param pattern + * @param i + * current pointer in the pattern + * @return zero of the index is out of range, otherwise the next character + * from given position + */ + private static char lookAhead(String pattern, int i) { + int idx = i + 1; + return idx >= pattern.length() ? 0 : pattern.charAt(idx); + } + + private static void setNext(char[] buffer, char c) { + for (int i = 0; i < buffer.length; i++) + if (buffer[i] == 0) { + buffer[i] = c; + break; + } + } + + private static void reset(char[] buffer) { + for (int i = 0; i < buffer.length; i++) + buffer[i] = 0; + } + + private static String checkPosixCharClass(char[] buffer) { + for (int i = 0; i < POSIX_CHAR_CLASSES.size(); i++) { + String clazz = POSIX_CHAR_CLASSES.get(i); + boolean match = true; + for (int j = 0; j < clazz.length(); j++) + if (buffer[j] != clazz.charAt(j)) { + match = false; + break; + } + if (match) + return JAVA_CHAR_CLASSES.get(i); + } + return null; + } + +} diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/ignore/internal/WildCardMatcher.java b/org.eclipse.jgit/src/org/eclipse/jgit/ignore/internal/WildCardMatcher.java new file mode 100644 index 0000000000..7d12b0ddce --- /dev/null +++ b/org.eclipse.jgit/src/org/eclipse/jgit/ignore/internal/WildCardMatcher.java @@ -0,0 +1,74 @@ +/* + * Copyright (C) 2014, 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 + * under the terms of the Eclipse Distribution License v1.0 which + * accompanies this distribution, is reproduced below, and is + * available at http://www.eclipse.org/org/documents/edl-v10.php + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * - Neither the name of the Eclipse Foundation, Inc. nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package org.eclipse.jgit.ignore.internal; + +import static org.eclipse.jgit.ignore.internal.Strings.convertGlob; + +import java.util.regex.Pattern; + +import org.eclipse.jgit.errors.InvalidPatternException; + +/** + * Matcher built from path segments containing wildcards. This matcher converts + * glob wildcards to Java {@link Pattern}'s. + * <p> + * This class is immutable and thread safe. + * + * @since 3.6 + */ +public class WildCardMatcher extends NameMatcher { + + final Pattern p; + + WildCardMatcher(String pattern, Character pathSeparator, boolean dirOnly) + throws InvalidPatternException { + super(pattern, pathSeparator, dirOnly); + p = convertGlob(subPattern); + } + + @Override + public boolean matches(String segment, int startIncl, int endExcl, + boolean assumeDirectory) { + return p.matcher(segment.substring(startIncl, endExcl)).matches(); + } +} 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 new file mode 100644 index 0000000000..d578654375 --- /dev/null +++ b/org.eclipse.jgit/src/org/eclipse/jgit/ignore/internal/WildMatcher.java @@ -0,0 +1,75 @@ +/* + * Copyright (C) 2014, 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 + * under the terms of the Eclipse Distribution License v1.0 which + * accompanies this distribution, is reproduced below, and is + * available at http://www.eclipse.org/org/documents/edl-v10.php + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * - Neither the name of the Eclipse Foundation, Inc. nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package org.eclipse.jgit.ignore.internal; + +/** + * Wildmatch matcher for "double star" (<code>**</code>) pattern only. This + * matcher matches any path. + * <p> + * This class is immutable and thread safe. + * + * @since 3.6 + */ +public final class WildMatcher extends AbstractMatcher { + + static final String WILDMATCH = "**"; //$NON-NLS-1$ + + // double star for the beginning of pattern + static final String WILDMATCH2 = "/**"; //$NON-NLS-1$ + + static final WildMatcher INSTANCE = new WildMatcher(); + + private WildMatcher() { + super(WILDMATCH, false); + } + + public final boolean matches(String path, boolean assumeDirectory) { + return true; + } + + public final boolean matches(String segment, int startIncl, int endExcl, + boolean assumeDirectory) { + return true; + } + +} diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/WorkingTreeIterator.java b/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/WorkingTreeIterator.java index 3d63d92542..cc5ef18074 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/WorkingTreeIterator.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/WorkingTreeIterator.java @@ -70,8 +70,8 @@ import org.eclipse.jgit.dircache.DirCacheIterator; import org.eclipse.jgit.errors.CorruptObjectException; import org.eclipse.jgit.errors.MissingObjectException; import org.eclipse.jgit.errors.NoWorkTreeException; +import org.eclipse.jgit.ignore.FastIgnoreRule; import org.eclipse.jgit.ignore.IgnoreNode; -import org.eclipse.jgit.ignore.IgnoreRule; import org.eclipse.jgit.internal.JGitText; import org.eclipse.jgit.lib.Constants; import org.eclipse.jgit.lib.CoreConfig; @@ -1132,7 +1132,7 @@ public abstract class WorkingTreeIterator extends AbstractTreeIterator { final Entry entry; PerDirectoryIgnoreNode(Entry entry) { - super(Collections.<IgnoreRule> emptyList()); + super(Collections.<FastIgnoreRule> emptyList()); this.entry = entry; } |