From b6967c393aaa9bc8fcb1f248f94a4deb897248cb Mon Sep 17 00:00:00 2001 From: Patrick Steinhardt Date: Thu, 6 Jun 2019 14:02:17 +0200 Subject: attr_file: refactor stripping of trailing spaces The stripping of trailing spaces currently happens as part of `git_attr_fnmatch__parse`. As we aren't currently parsing trailing whitespaces correct in case they're escaped, we'll have to change that code, though. To make actual behavioural change easier to review, refactor the code up-front by pulling it out into its own function that is expected to retain the exact same functionality as before. Like this, the fix will be trivial to apply. --- src/attr_file.c | 20 +++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/src/attr_file.c b/src/attr_file.c index aef3e64af..b2c60f204 100644 --- a/src/attr_file.c +++ b/src/attr_file.c @@ -559,6 +559,19 @@ void git_attr_path__free(git_attr_path *info) * "cat-file.c" but not "mozilla-sha1/sha1.c". */ +/* + * Determine the length of trailing spaces. + */ +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') + break; + } + return len - n; +} + /* * This will return 0 if the spec was filled out, * GIT_ENOTFOUND if the fnmatch does not require matching, or @@ -646,9 +659,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--; -- cgit v1.2.1 From d81e7866aba52625aa3100764d77c73adba58c8e Mon Sep 17 00:00:00 2001 From: Patrick Steinhardt Date: Thu, 6 Jun 2019 14:11:44 +0200 Subject: 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. --- src/attr_file.c | 6 ++++-- tests/attr/ignore.c | 46 ++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 50 insertions(+), 2 deletions(-) diff --git a/src/attr_file.c b/src/attr_file.c index b2c60f204..40262eca6 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 1bf06fc1f..ea8a14192 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"); -- cgit v1.2.1