summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorAndrey Loskutov <loskutov@gmx.de>2015-09-25 00:37:50 +0200
committerAndrey Loskutov <loskutov@gmx.de>2015-09-26 18:16:42 +0200
commit1abd51d95373fcf7450463478b45aa7da8012cef (patch)
tree4cd9a68fe658e9b985559443b17fb5b99856da62
parent4b7daecf4a827b9cf69a76e5cbd8559e8079f4c8 (diff)
downloadjgit-1abd51d95373fcf7450463478b45aa7da8012cef.tar.gz
jgit-1abd51d95373fcf7450463478b45aa7da8012cef.zip
[ignore rules] fix for backslash handling
An attempt to re-implement not well documented Git CLI behavior for patterns with backslashes. It looks like Git silently ignores all \ characters in ignore rules, if they are NOT covered by 3 cases described in [1]: {quote} 1) ... Put a backslash ("\") in front of the first hash for patterns that begin with a hash. ... 2) Trailing spaces are ignored unless they are quoted with backslash ("\"). ... 3) Put a backslash ("\") in front of the first "!" for patterns that begin with a literal "!", for example, "\!important!.txt". {quote} Undocumented also is the fact that backslash itself can be escaped by backslash. So \h\e\l\l\o\.t\x\t rule matches hello.txt and a\\\\b a\b in Git CLI. Additionally, the glob parser [2] knows special meaning of backslash: {quote} One can remove the special meaning of '?', '*' and '[' by preceding them by a backslash, or, in case this is part of a shell command line, enclosing them in quotes. Between brackets these characters stand for themselves. Thus, "[[?*\]" matches the four characters '[', '?', '*' and '\'. {quote} [1] https://www.kernel.org/pub/software/scm/git/docs/gitignore.html [2] http://man7.org/linux/man-pages/man7/glob.7.html Bug: 478065 Change-Id: I3dc973475d1943c5622103701fa8cb3ea0684e3e Signed-off-by: Andrey Loskutov <loskutov@gmx.de>
-rw-r--r--org.eclipse.jgit.test/tst/org/eclipse/jgit/ignore/IgnoreRuleSpecialCasesTest.java40
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/ignore/internal/LeadingAsteriskMatcher.java2
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/ignore/internal/NameMatcher.java6
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/ignore/internal/PathMatcher.java5
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/ignore/internal/Strings.java42
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/ignore/internal/TrailingAsteriskMatcher.java2
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/ignore/internal/WildCardMatcher.java2
7 files changed, 82 insertions, 17 deletions
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/ignore/IgnoreRuleSpecialCasesTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/ignore/IgnoreRuleSpecialCasesTest.java
index ec4a1f1c67..f8eb126826 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/ignore/IgnoreRuleSpecialCasesTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/ignore/IgnoreRuleSpecialCasesTest.java
@@ -830,21 +830,29 @@ public class IgnoreRuleSpecialCasesTest {
}
@Test
+ public void testIgnoredBackslash() throws Exception {
+ // In Git CLI a\b\c is equal to abc
+ assertMatch("a\\b\\c", "abc", true);
+ }
+
+ @Test
public void testEscapedBackslash() throws Exception {
// In Git CLI a\\b matches a\b file
assertMatch("a\\\\b", "a\\b", true);
+ assertMatch("a\\\\b\\c", "a\\bc", true);
+
}
@Test
public void testEscapedExclamationMark() throws Exception {
assertMatch("\\!b!.txt", "!b!.txt", true);
- assertMatch("a\\!b!.txt", "a\\!b!.txt", true);
+ assertMatch("a\\!b!.txt", "a!b!.txt", true);
}
@Test
public void testEscapedHash() throws Exception {
assertMatch("\\#b", "#b", true);
- assertMatch("a\\#", "a\\#", true);
+ assertMatch("a\\#", "a#", true);
}
@Test
@@ -855,12 +863,12 @@ public class IgnoreRuleSpecialCasesTest {
@Test
public void testNotEscapingBackslash() throws Exception {
- assertMatch("\\out", "\\out", true);
- assertMatch("\\out", "a/\\out", true);
- assertMatch("c:\\/", "c:\\/", true);
- assertMatch("c:\\/", "a/c:\\/", true);
- assertMatch("c:\\tmp", "c:\\tmp", true);
- assertMatch("c:\\tmp", "a/c:\\tmp", true);
+ assertMatch("\\out", "out", true);
+ assertMatch("\\out", "a/out", true);
+ assertMatch("c:\\/", "c:/", true);
+ assertMatch("c:\\/", "a/c:/", true);
+ assertMatch("c:\\tmp", "c:tmp", true);
+ assertMatch("c:\\tmp", "a/c:tmp", true);
}
@Test
@@ -869,6 +877,22 @@ public class IgnoreRuleSpecialCasesTest {
}
@Test
+ public void testBackslash() throws Exception {
+ assertMatch("a\\", "a", true);
+ assertMatch("\\a", "a", true);
+ assertMatch("a/\\", "a/", true);
+ assertMatch("a/b\\", "a/b", true);
+ assertMatch("\\a/b", "a/b", true);
+ assertMatch("/\\a", "/a", true);
+ assertMatch("\\a\\b\\c\\", "abc", true);
+ assertMatch("/\\a/\\b/\\c\\", "a/b/c", true);
+
+ // empty path segment doesn't match
+ assertMatch("\\/a", "/a", false);
+ assertMatch("\\/a", "a", false);
+ }
+
+ @Test
public void testDollar() throws Exception {
assertMatch("$", "$", true);
assertMatch("$x", "$x", true);
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/ignore/internal/LeadingAsteriskMatcher.java b/org.eclipse.jgit/src/org/eclipse/jgit/ignore/internal/LeadingAsteriskMatcher.java
index f1153d9c69..3d0ad09124 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/ignore/internal/LeadingAsteriskMatcher.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/ignore/internal/LeadingAsteriskMatcher.java
@@ -50,7 +50,7 @@ package org.eclipse.jgit.ignore.internal;
public class LeadingAsteriskMatcher extends NameMatcher {
LeadingAsteriskMatcher(String pattern, Character pathSeparator, boolean dirOnly) {
- super(pattern, pathSeparator, dirOnly);
+ super(pattern, pathSeparator, dirOnly, true);
if (subPattern.charAt(0) != '*')
throw new IllegalArgumentException(
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/ignore/internal/NameMatcher.java b/org.eclipse.jgit/src/org/eclipse/jgit/ignore/internal/NameMatcher.java
index 6c4c8092fe..8beae8379e 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/ignore/internal/NameMatcher.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/ignore/internal/NameMatcher.java
@@ -58,9 +58,13 @@ public class NameMatcher extends AbstractMatcher {
final String subPattern;
- NameMatcher(String pattern, Character pathSeparator, boolean dirOnly) {
+ NameMatcher(String pattern, Character pathSeparator, boolean dirOnly,
+ boolean deleteBackslash) {
super(pattern, dirOnly);
slash = getPathSeparator(pathSeparator);
+ if (deleteBackslash) {
+ pattern = Strings.deleteBackslash(pattern);
+ }
beginning = pattern.length() == 0 ? false : pattern.charAt(0) == slash;
if (!beginning)
this.subPattern = pattern;
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/ignore/internal/PathMatcher.java b/org.eclipse.jgit/src/org/eclipse/jgit/ignore/internal/PathMatcher.java
index 6b9b5a806b..c3f6694a7a 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/ignore/internal/PathMatcher.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/ignore/internal/PathMatcher.java
@@ -85,7 +85,8 @@ public class PathMatcher extends AbstractMatcher {
}
private boolean isSimplePathWithSegments(String path) {
- return !isWildCard(path) && count(path, slash, true) > 0;
+ return !isWildCard(path) && path.indexOf('\\') < 0
+ && count(path, slash, true) > 0;
}
static private List<IMatcher> createMatchers(List<String> segments,
@@ -167,7 +168,7 @@ public class PathMatcher extends AbstractMatcher {
case COMPLEX:
return new WildCardMatcher(segment, pathSeparator, dirOnly);
default:
- return new NameMatcher(segment, pathSeparator, dirOnly);
+ return new NameMatcher(segment, pathSeparator, dirOnly, true);
}
}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/ignore/internal/Strings.java b/org.eclipse.jgit/src/org/eclipse/jgit/ignore/internal/Strings.java
index 2e955d3124..f972828bc6 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/ignore/internal/Strings.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/ignore/internal/Strings.java
@@ -157,9 +157,7 @@ public class Strings {
return false;
}
char nextChar = pattern.charAt(nextIdx);
- if (nextChar == '?' || nextChar == '*' || nextChar == '['
- // required to match escaped backslashes '\\\\'
- || nextChar == '\\') {
+ if (escapedByBackslash(nextChar)) {
return true;
} else {
return false;
@@ -169,6 +167,10 @@ public class Strings {
return false;
}
+ private static boolean escapedByBackslash(char nextChar) {
+ return nextChar == '?' || nextChar == '*' || nextChar == '[';
+ }
+
static PatternState checkWildCards(String pattern) {
if (isComplexWildcard(pattern))
return PatternState.COMPLEX;
@@ -308,6 +310,14 @@ public class Strings {
char lookAhead = lookAhead(pattern, i);
if (lookAhead == ']' || lookAhead == '[')
ignoreLastBracket = true;
+ } else {
+ //
+ char lookAhead = lookAhead(pattern, i);
+ if (lookAhead != '\\' && lookAhead != '['
+ && lookAhead != '?' && lookAhead != '*'
+ && lookAhead != ' ' && lookBehind(sb) != '\\') {
+ break;
+ }
}
sb.append(c);
break;
@@ -445,4 +455,30 @@ public class Strings {
return null;
}
+ static String deleteBackslash(String s) {
+ if (s.indexOf('\\') < 0) {
+ return s;
+ }
+ StringBuilder sb = new StringBuilder(s.length());
+ for (int i = 0; i < s.length(); i++) {
+ char ch = s.charAt(i);
+ if (ch == '\\') {
+ if (i + 1 == s.length()) {
+ continue;
+ }
+ char next = s.charAt(i + 1);
+ if (next == '\\') {
+ sb.append(ch);
+ i++;
+ continue;
+ }
+ if (!escapedByBackslash(next)) {
+ continue;
+ }
+ }
+ sb.append(ch);
+ }
+ return sb.toString();
+ }
+
}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/ignore/internal/TrailingAsteriskMatcher.java b/org.eclipse.jgit/src/org/eclipse/jgit/ignore/internal/TrailingAsteriskMatcher.java
index 4a1c780d99..b927d27dbe 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/ignore/internal/TrailingAsteriskMatcher.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/ignore/internal/TrailingAsteriskMatcher.java
@@ -50,7 +50,7 @@ package org.eclipse.jgit.ignore.internal;
public class TrailingAsteriskMatcher extends NameMatcher {
TrailingAsteriskMatcher(String pattern, Character pathSeparator, boolean dirOnly) {
- super(pattern, pathSeparator, dirOnly);
+ super(pattern, pathSeparator, dirOnly, true);
if (subPattern.charAt(subPattern.length() - 1) != '*')
throw new IllegalArgumentException(
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/ignore/internal/WildCardMatcher.java b/org.eclipse.jgit/src/org/eclipse/jgit/ignore/internal/WildCardMatcher.java
index 7d12b0ddce..8f9815283d 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/ignore/internal/WildCardMatcher.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/ignore/internal/WildCardMatcher.java
@@ -62,7 +62,7 @@ public class WildCardMatcher extends NameMatcher {
WildCardMatcher(String pattern, Character pathSeparator, boolean dirOnly)
throws InvalidPatternException {
- super(pattern, pathSeparator, dirOnly);
+ super(pattern, pathSeparator, dirOnly, false);
p = convertGlob(subPattern);
}