Ignore trailing spaces in directory rule patterns

Bug: 500967
Change-Id: I7fabc2654af97011c62f46d5c30ee992341e45e2
Signed-off-by: Andrey Loskutov <loskutov@gmx.de>
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/ignore/BasicRuleTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/ignore/BasicRuleTest.java
index d8a6174..a6e48fe 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/ignore/BasicRuleTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/ignore/BasicRuleTest.java
@@ -47,6 +47,7 @@
 import static org.junit.Assert.assertNotEquals;
 import static org.junit.Assert.assertTrue;
 
+import org.eclipse.jgit.ignore.internal.Strings;
 import org.junit.Test;
 
 public class BasicRuleTest {
@@ -73,4 +74,31 @@
 		assertNotEquals(rule1.toString(), rule3.toString());
 	}
 
+	@Test
+	public void testDirectoryPattern() {
+		assertTrue(Strings.isDirectoryPattern("/"));
+		assertTrue(Strings.isDirectoryPattern("/ "));
+		assertTrue(Strings.isDirectoryPattern("/     "));
+		assertFalse(Strings.isDirectoryPattern("     "));
+		assertFalse(Strings.isDirectoryPattern(""));
+	}
+
+	@Test
+	public void testStripTrailingChar() {
+		assertEquals("", Strings.stripTrailing("/", '/'));
+		assertEquals("", Strings.stripTrailing("///", '/'));
+		assertEquals("a", Strings.stripTrailing("a/", '/'));
+		assertEquals("a", Strings.stripTrailing("a///", '/'));
+		assertEquals("a/ ", Strings.stripTrailing("a/ ", '/'));
+	}
+
+	@Test
+	public void testStripTrailingWhitespace() {
+		assertEquals("", Strings.stripTrailingWhitespace(""));
+		assertEquals("", Strings.stripTrailingWhitespace("   "));
+		assertEquals("a", Strings.stripTrailingWhitespace("a"));
+		assertEquals("a", Strings.stripTrailingWhitespace("a "));
+		assertEquals("a", Strings.stripTrailingWhitespace("a  "));
+		assertEquals("a", Strings.stripTrailingWhitespace("a \t"));
+	}
 }
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/ignore/FastIgnoreRuleTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/ignore/FastIgnoreRuleTest.java
index 480e326..1863b80 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/ignore/FastIgnoreRuleTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/ignore/FastIgnoreRuleTest.java
@@ -123,6 +123,17 @@
 	}
 
 	@Test
+	public void testTrailingSpaces() {
+		assertMatched("a ", "a");
+		assertMatched("a/ ", "a/");
+		assertMatched("a/ ", "a/b");
+		assertMatched("a/\\ ", "a/ ");
+		assertNotMatched("a/\\ ", "a/");
+		assertNotMatched("a/\\ ", "a/b");
+		assertNotMatched("/ ", "a");
+	}
+
+	@Test
 	public void testAsteriskDot() {
 		assertMatched("*.a", ".a");
 		assertMatched("*.a", "/.a");
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/ignore/IgnoreNodeTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/ignore/IgnoreNodeTest.java
index c026efc..4a7dcd5 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/ignore/IgnoreNodeTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/ignore/IgnoreNodeTest.java
@@ -480,8 +480,9 @@
 		writeTrashFile("a/a", "");
 		writeTrashFile("a/a ", "");
 		writeTrashFile("a/a  ", "");
+		writeTrashFile("b/c", "");
 
-		writeIgnoreFile(".gitignore", "a\\ ", "a \\ ");
+		writeIgnoreFile(".gitignore", "a\\ ", "a \\ ", "b/ ");
 
 		beginWalk();
 		assertEntry(F, tracked, ".gitignore");
@@ -497,6 +498,8 @@
 		assertEntry(F, tracked, "a/a");
 		assertEntry(F, ignored, "a/a ");
 		assertEntry(F, ignored, "a/a  ");
+		assertEntry(D, ignored, "b");
+		assertEntry(F, ignored, "b/c");
 		endWalk();
 	}
 
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/ignore/FastIgnoreRule.java b/org.eclipse.jgit/src/org/eclipse/jgit/ignore/FastIgnoreRule.java
index e376cbb..eb081ad 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/ignore/FastIgnoreRule.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/ignore/FastIgnoreRule.java
@@ -43,6 +43,8 @@
 package org.eclipse.jgit.ignore;
 
 import static org.eclipse.jgit.ignore.internal.Strings.stripTrailing;
+import static org.eclipse.jgit.ignore.internal.Strings.stripTrailingWhitespace;
+import static org.eclipse.jgit.ignore.internal.Strings.isDirectoryPattern;
 import static org.eclipse.jgit.ignore.internal.IMatcher.NO_MATCH;
 import org.eclipse.jgit.errors.InvalidPatternException;
 import org.eclipse.jgit.ignore.internal.IMatcher;
@@ -111,8 +113,9 @@
 				pattern = pattern.substring(1);
 			}
 		}
-		dirOnly = pattern.charAt(pattern.length() - 1) == PATH_SEPARATOR;
+		dirOnly = isDirectoryPattern(pattern);
 		if (dirOnly) {
+			pattern = stripTrailingWhitespace(pattern);
 			pattern = stripTrailing(pattern, PATH_SEPARATOR);
 			if (pattern.length() == 0) {
 				this.matcher = NO_MATCH;
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 e354c71..70c5199 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
@@ -76,10 +76,50 @@
 	 * @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;
+		for (int i = pattern.length() - 1; i >= 0; i--) {
+			char charAt = pattern.charAt(i);
+			if (charAt != c) {
+				if (i == pattern.length() - 1) {
+					return pattern;
+				}
+				return pattern.substring(0, i + 1);
+			}
+		}
+		return ""; //$NON-NLS-1$
+	}
+
+	/**
+	 * @param pattern
+	 *            non null
+	 * @return new string with all trailing whitespace removed
+	 */
+	public static String stripTrailingWhitespace(String pattern) {
+		for (int i = pattern.length() - 1; i >= 0; i--) {
+			char charAt = pattern.charAt(i);
+			if (!Character.isWhitespace(charAt)) {
+				if (i == pattern.length() - 1) {
+					return pattern;
+				}
+				return pattern.substring(0, i + 1);
+			}
+		}
+		return ""; //$NON-NLS-1$
+	}
+
+	/**
+	 * @param pattern
+	 *            non null
+	 * @return true if the last character, which is not whitespace, is a path
+	 *         separator
+	 */
+	public static boolean isDirectoryPattern(String pattern) {
+		for (int i = pattern.length() - 1; i >= 0; i--) {
+			char charAt = pattern.charAt(i);
+			if (!Character.isWhitespace(charAt)) {
+				return charAt == FastIgnoreRule.PATH_SEPARATOR;
+			}
+		}
+		return false;
 	}
 
 	static int count(String s, char c, boolean ignoreFirstLast) {