Commit d81e7866aba52625aa3100764d77c73adba58c8e

Patrick Steinhardt 2019-06-06T14:11:44

ignore: handle escaped trailing whitespace The gitignore's pattern format specifies that "Trailing spaces are ignored unless they are quoted with backslash ("\")". We do not honor this currently and will treat a pattern "foo\ " as if it was "foo\" only and a pattern "foo\ \ " as "foo\ \". Fix our code to handle those special cases and add tests to avoid regressions.

diff --git a/src/attr_file.c b/src/attr_file.c
index b2c60f2..40262ec 100644
--- a/src/attr_file.c
+++ b/src/attr_file.c
@@ -560,13 +560,15 @@ void git_attr_path__free(git_attr_path *info)
  */
 
 /*
- * Determine the length of trailing spaces.
+ * 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')
+		if ((p[n-1] != ' ' && p[n-1] != '\t') ||
+		    (n > 1 && p[n-2] == '\\'))
 			break;
 	}
 	return len - n;
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");