Commit e50d138e898dd034161663873f75716086266f86

Edward Thomson 2019-06-06T09:48:30

Merge pull request #5095 from pks-t/pks/ignore-escaped-trailing-space ignore: handle escaped trailing whitespace

diff --git a/src/attr_file.c b/src/attr_file.c
index 51ecfbb..673f9a4 100644
--- a/src/attr_file.c
+++ b/src/attr_file.c
@@ -560,6 +560,21 @@ void git_attr_path__free(git_attr_path *info)
  */
 
 /*
+ * Determine the length of trailing spaces. Escaped spaces do not count as
+ * trailing whitespace.
+ */
+static size_t trailing_space_length(const char *p, size_t len)
+{
+	size_t n;
+	for (n = len; n; n--) {
+		if ((p[n-1] != ' ' && p[n-1] != '\t') ||
+		    (n > 1 && p[n-2] == '\\'))
+			break;
+	}
+	return len - n;
+}
+
+/*
  * This will return 0 if the spec was filled out,
  * GIT_ENOTFOUND if the fnmatch does not require matching, or
  * another error code there was an actual problem.
@@ -647,9 +662,10 @@ int git_attr_fnmatch__parse(
 			return GIT_ENOTFOUND;
 
 	/* Remove trailing spaces. */
-	while (pattern[spec->length - 1] == ' ' || pattern[spec->length - 1] == '\t')
-		if (--spec->length == 0)
-			return GIT_ENOTFOUND;
+	spec->length -= trailing_space_length(pattern, spec->length);
+
+	if (spec->length == 0)
+		return GIT_ENOTFOUND;
 
 	if (pattern[spec->length - 1] == '/') {
 		spec->length--;
diff --git a/tests/attr/ignore.c b/tests/attr/ignore.c
index 1bf06fc..ea8a141 100644
--- a/tests/attr/ignore.c
+++ b/tests/attr/ignore.c
@@ -61,6 +61,52 @@ void test_attr_ignore__ignore_space(void)
 	assert_is_ignored(true, "NewFolder/NewFolder/File.txt");
 }
 
+void test_attr_ignore__intermittent_space(void)
+{
+	cl_git_rewritefile("attr/.gitignore", "foo bar\n");
+
+	assert_is_ignored(false, "foo");
+	assert_is_ignored(false, "bar");
+	assert_is_ignored(true, "foo bar");
+}
+
+void test_attr_ignore__trailing_space(void)
+{
+	cl_git_rewritefile(
+		"attr/.gitignore",
+		"foo \n"
+		"bar  \n"
+	);
+
+	assert_is_ignored(true, "foo");
+	assert_is_ignored(false, "foo ");
+	assert_is_ignored(true, "bar");
+	assert_is_ignored(false, "bar ");
+	assert_is_ignored(false, "bar  ");
+}
+
+void test_attr_ignore__escaped_trailing_spaces(void)
+{
+	cl_git_rewritefile(
+		"attr/.gitignore",
+		"foo\\ \n"
+		"bar\\ \\ \n"
+		"baz \\ \n"
+		"qux\\  \n"
+	);
+
+	assert_is_ignored(false, "foo");
+	assert_is_ignored(true, "foo ");
+	assert_is_ignored(false, "bar");
+	assert_is_ignored(false, "bar ");
+	assert_is_ignored(true, "bar  ");
+	assert_is_ignored(true, "baz  ");
+	assert_is_ignored(false, "baz ");
+	assert_is_ignored(true, "qux ");
+	assert_is_ignored(false, "qux");
+	assert_is_ignored(false, "qux  ");
+}
+
 void test_attr_ignore__ignore_dir(void)
 {
 	cl_git_rewritefile("attr/.gitignore", "dir/\n");