Browse Source

Reimplementation of ignore rule parser

The current IgnoreRule/FileNameMatcher implementation scales not well
with huge repositories - it is both slow and memory expensive while
parsing glob expressions (bug 440732). Addtitionally, the "double star"
pattern (/**/) is not understood by the old parser (bug 416348).

The proposed implementation is a complete clean room rewrite of the
gitignore parser, aiming to add missing double star pattern support and
improve the performance and memory consumption.

The glob expressions from .gitignore rules are converted to Java regular
expressions (java.util.regex.Pattern). java.util.regex.Pattern code can
evaluate expression from gitignore rules considerable faster (and with
less memory consumption) as the old FileNameMatcher implementation.

CQ: 8828
Bug: 416348
Bug: 440732
Change-Id: Ibefb930381f2f16eddb9947e592752f8ae2b76e1
Signed-off-by: Andrey Loskutov <loskutov@gmx.de>
Signed-off-by: Matthias Sohn <matthias.sohn@sap.com>
tags/v3.6.0.201411121045-m1
Andrey Loskutov 9 years ago
parent
commit
2f5a08798e

+ 3
- 1
org.eclipse.jgit.test/META-INF/MANIFEST.MF View File

@@ -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)"

+ 74
- 0
org.eclipse.jgit.test/tst/org/eclipse/jgit/ignore/BasicRuleTest.java View File

@@ -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());
}

}

+ 561
- 0
org.eclipse.jgit.test/tst/org/eclipse/jgit/ignore/FastIgnoreRuleTest.java View File

@@ -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;
}
}

+ 371
- 0
org.eclipse.jgit.test/tst/org/eclipse/jgit/ignore/IgnoreMatcherParametrizedTest.java View File

@@ -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);
}
}

+ 5
- 4
org.eclipse.jgit.test/tst/org/eclipse/jgit/ignore/IgnoreMatcherTest.java View File

@@ -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);

+ 904
- 0
org.eclipse.jgit.test/tst/org/eclipse/jgit/ignore/IgnoreRuleSpecialCasesTest.java View File

@@ -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);
}

}

+ 1
- 0
org.eclipse.jgit/META-INF/MANIFEST.MF View File

@@ -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";

+ 229
- 0
org.eclipse.jgit/src/org/eclipse/jgit/ignore/FastIgnoreRule.java View File

@@ -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;
}
}
}

+ 6
- 6
org.eclipse.jgit/src/org/eclipse/jgit/ignore/IgnoreNode.java View File

@@ -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;

+ 8
- 3
org.eclipse.jgit/src/org/eclipse/jgit/ignore/IgnoreRule.java View File

@@ -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;
}
}
}

+ 88
- 0
org.eclipse.jgit/src/org/eclipse/jgit/ignore/internal/AbstractMatcher.java View File

@@ -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);
}
}

+ 80
- 0
org.eclipse.jgit/src/org/eclipse/jgit/ignore/internal/IMatcher.java View File

@@ -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);
}

+ 112
- 0
org.eclipse.jgit/src/org/eclipse/jgit/ignore/internal/NameMatcher.java View File

@@ -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;
}

}

+ 239
- 0
org.eclipse.jgit/src/org/eclipse/jgit/ignore/internal/PathMatcher.java View File

@@ -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);
}
}

+ 373
- 0
org.eclipse.jgit/src/org/eclipse/jgit/ignore/internal/Strings.java View File

@@ -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;
}

}

+ 74
- 0
org.eclipse.jgit/src/org/eclipse/jgit/ignore/internal/WildCardMatcher.java View File

@@ -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();
}
}

+ 75
- 0
org.eclipse.jgit/src/org/eclipse/jgit/ignore/internal/WildMatcher.java View File

@@ -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;
}

}

+ 2
- 2
org.eclipse.jgit/src/org/eclipse/jgit/treewalk/WorkingTreeIterator.java View File

@@ -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;
}


Loading…
Cancel
Save