From: Mirko Faina Date: Thu, 08 Oct 2026 12:06:59 GMT Subject: [RFC PATCH 3/6] dir.c: teach parse_path_pattern() precious files Message-ID: <677a1af4c6bacf129450e3c0c150d27bf65baecf.1791460418.git.mroik@delayed.space> In-Reply-To: parse_path_pattern() knows only to recognize patterns that are either directories or normal files. It also knows that there are negated patterns, but it doesn't know about patterns that indicate precious files. Teach about precious files to parse_path_pattern(). We teach it also to detect the invalid patterns starting with '!$' which would mean a negated precious file, that we don't allow. This is reported back to the caller so we change its signature to return an int. We also teach the callers parse_attr_line() and add_pattern() to handle such errors. Signed-off-by: Mirko Faina --- attr.c | 8 ++++++-- attr.h | 1 + dir.c | 26 +++++++++++++++++++++++--- dir.h | 2 +- t/t7508-status.sh | 9 +++++++++ 5 files changed, 40 insertions(+), 6 deletions(-) diff --git a/attr.c b/attr.c index 0e63f1b6de..5c60ca6b23 100644 --- a/attr.c +++ b/attr.c @@ -382,10 +382,14 @@ struct match_attr *parse_attr_line(const char *line, const char *src, char *p = (char *)&(res->state[num_attr]); memcpy(p, name, namelen); res->u.pat.pattern = p; - parse_path_pattern(&res->u.pat.pattern, + if (parse_path_pattern(&res->u.pat.pattern, &res->u.pat.patternlen, &res->u.pat.flags, - &res->u.pat.nowildcardlen); + &res->u.pat.nowildcardlen)) { + warning(_("pattern '%s' is invalid, skipping"), + (char *)&res->u.pat.pattern); + goto fail_return; + } if (res->u.pat.flags & PATTERN_FLAG_NEGATIVE) { warning(_("Negative patterns are ignored in git attributes\n" "Use '\\!' for literal leading exclamation.")); diff --git a/attr.h b/attr.h index c083d47df5..d00939732b 100644 --- a/attr.h +++ b/attr.h @@ -255,6 +255,7 @@ enum pattern_flags { PATTERN_FLAG_ENDSWITH = 4, PATTERN_FLAG_MUSTBEDIR = 8, PATTERN_FLAG_NEGATIVE = 16, + PATTERN_FLAG_PRECIOUS = 32, }; struct pattern { diff --git a/dir.c b/dir.c index c6342c882a..c6f1bed429 100644 --- a/dir.c +++ b/dir.c @@ -700,7 +700,10 @@ int no_wildcard(const char *string) return string[simple_length(string)] == '\0'; } -void parse_path_pattern(const char **pattern, +/* + * Returns 1 if the pattern is problematic, 0 otherwise + */ +int parse_path_pattern(const char **pattern, int *patternlen, enum pattern_flags *flags, int *nowildcardlen) @@ -709,7 +712,12 @@ void parse_path_pattern(const char **pattern, size_t i, len; *flags = 0; - if (*p == '!') { + if (simple_length(p) >= 2 && p[0] == '!' && p[1] == '$') { + return 1; + } else if (*p == '$') { + *flags |= PATTERN_FLAG_PRECIOUS; + p++; + } else if (*p == '!') { *flags |= PATTERN_FLAG_NEGATIVE; p++; } @@ -736,6 +744,7 @@ void parse_path_pattern(const char **pattern, *flags |= PATTERN_FLAG_ENDSWITH; *pattern = p; *patternlen = len; + return 0; } int pl_hashmap_cmp(const void *cmp_data UNUSED, @@ -975,6 +984,14 @@ int hashmap_contains_parent(struct hashmap *map, return 0; } +/* + * Parses the pattern for its type and sets flags accordingly, then adds it to + * the pattern list. If the pattern is invalid the function returns early with a + * warning. + * + * The only problematic patterns at the moment are the one starting with '!$' + * which are negated precious-files. + */ void add_pattern(const char *string, const char *base, int baselen, struct pattern_list *pl, int srcpos) { @@ -983,7 +1000,10 @@ void add_pattern(const char *string, const char *base, enum pattern_flags flags; int nowildcardlen; - parse_path_pattern(&string, &patternlen, &flags, &nowildcardlen); + if (parse_path_pattern(&string, &patternlen, &flags, &nowildcardlen)) { + warning(_("pattern '%s' is problematic, skipping"), string); + return; + } FLEX_ALLOC_MEM(pattern, pattern, string, patternlen); pattern->patternlen = patternlen; pattern->nowildcardlen = nowildcardlen; diff --git a/dir.h b/dir.h index 210ee8a98d..5cda2cdba7 100644 --- a/dir.h +++ b/dir.h @@ -465,7 +465,7 @@ int add_patterns_from_blob_to_list(struct object_id *oid, int add_patterns_from_buffer(char *buf, size_t size, const char *base, int baselen, struct pattern_list *pl); -void parse_path_pattern(const char **string, int *patternlen, enum pattern_flags *flags, int *nowildcardlen); +int parse_path_pattern(const char **string, int *patternlen, enum pattern_flags *flags, int *nowildcardlen); void add_pattern(const char *string, const char *base, int baselen, struct pattern_list *pl, int srcpos); void clear_pattern_list(struct pattern_list *pl); diff --git a/t/t7508-status.sh b/t/t7508-status.sh index 0fd7c79911..aa251c6abd 100755 --- a/t/t7508-status.sh +++ b/t/t7508-status.sh @@ -301,6 +301,15 @@ EOF test_cmp expect output ' +test_expect_success 'parse invalid pattern' " + test_when_finished rm actual gitignore_backup && + test_when_finished cp gitignore_backup .gitignore && + cp .gitignore gitignore_backup && + echo '!$/ciao' >> .gitignore && + git status >actual 2>&1 && + test_grep \"warning: pattern '!$/ciao' is problematic, skipping\" actual +" + test_expect_success 'status with gitignore (nothing untracked)' ' { echo ".gitignore" && -- 2.56.0