Skip to content

Commit 82dce99

Browse files
pcloudsgitster
authored andcommitted
attr: more matching optimizations from .gitignore
.gitattributes and .gitignore share the same pattern syntax but has separate matching implementation. Over the years, ignore's implementation accumulates more optimizations while attr's stays the same. This patch reuses the core matching functions that are also used by excluded_from_list. excluded_from_list and path_matches can't be merged due to differences in exclude and attr, for example: * "!pattern" syntax is forbidden in .gitattributes. As an attribute can be unset (i.e. set to a special value "false") or made back to unspecified (i.e. not even set to "false"), "!pattern attr" is unclear which one it means. * we support attaching attributes to directories, but git-core internally does not currently make use of attributes on directories. Signed-off-by: Nguyễn Thái Ngọc Duy <[email protected]> Signed-off-by: Junio C Hamano <[email protected]>
1 parent 84460ee commit 82dce99

File tree

5 files changed

+64
-32
lines changed

5 files changed

+64
-32
lines changed

Documentation/gitattributes.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@ When more than one pattern matches the path, a later line
5656
overrides an earlier line. This overriding is done per
5757
attribute. The rules how the pattern matches paths are the
5858
same as in `.gitignore` files; see linkgit:gitignore[5].
59+
Unlike `.gitignore`, negative patterns are forbidden.
5960

6061
When deciding what attributes are assigned to a path, git
6162
consults `$GIT_DIR/info/attributes` file (which has the highest

attr.c

Lines changed: 31 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,13 @@ struct attr_state {
115115
const char *setto;
116116
};
117117

118+
struct pattern {
119+
const char *pattern;
120+
int patternlen;
121+
int nowildcardlen;
122+
int flags; /* EXC_FLAG_* */
123+
};
124+
118125
/*
119126
* One rule, as from a .gitattributes file.
120127
*
@@ -131,7 +138,7 @@ struct attr_state {
131138
*/
132139
struct match_attr {
133140
union {
134-
char *pattern;
141+
struct pattern pat;
135142
struct git_attr *attr;
136143
} u;
137144
char is_macro;
@@ -241,9 +248,16 @@ static struct match_attr *parse_attr_line(const char *line, const char *src,
241248
if (is_macro)
242249
res->u.attr = git_attr_internal(name, namelen);
243250
else {
244-
res->u.pattern = (char *)&(res->state[num_attr]);
245-
memcpy(res->u.pattern, name, namelen);
246-
res->u.pattern[namelen] = 0;
251+
char *p = (char *)&(res->state[num_attr]);
252+
memcpy(p, name, namelen);
253+
res->u.pat.pattern = p;
254+
parse_exclude_pattern(&res->u.pat.pattern,
255+
&res->u.pat.patternlen,
256+
&res->u.pat.flags,
257+
&res->u.pat.nowildcardlen);
258+
if (res->u.pat.flags & EXC_FLAG_NEGATIVE)
259+
die(_("Negative patterns are forbidden in git attributes\n"
260+
"Use '\\!' for literal leading exclamation."));
247261
}
248262
res->is_macro = is_macro;
249263
res->num_attr = num_attr;
@@ -640,25 +654,21 @@ static void prepare_attr_stack(const char *path)
640654

641655
static int path_matches(const char *pathname, int pathlen,
642656
const char *basename,
643-
const char *pattern,
657+
const struct pattern *pat,
644658
const char *base, int baselen)
645659
{
646-
if (!strchr(pattern, '/')) {
647-
return (fnmatch_icase(pattern, basename, 0) == 0);
660+
const char *pattern = pat->pattern;
661+
int prefix = pat->nowildcardlen;
662+
663+
if (pat->flags & EXC_FLAG_NODIR) {
664+
return match_basename(basename,
665+
pathlen - (basename - pathname),
666+
pattern, prefix,
667+
pat->patternlen, pat->flags);
648668
}
649-
/*
650-
* match with FNM_PATHNAME; the pattern has base implicitly
651-
* in front of it.
652-
*/
653-
if (*pattern == '/')
654-
pattern++;
655-
if (pathlen < baselen ||
656-
(baselen && pathname[baselen] != '/') ||
657-
strncmp(pathname, base, baselen))
658-
return 0;
659-
if (baselen != 0)
660-
baselen++;
661-
return fnmatch_icase(pattern, pathname + baselen, FNM_PATHNAME) == 0;
669+
return match_pathname(pathname, pathlen,
670+
base, baselen,
671+
pattern, prefix, pat->patternlen, pat->flags);
662672
}
663673

664674
static int macroexpand_one(int attr_nr, int rem);
@@ -696,7 +706,7 @@ static int fill(const char *path, int pathlen, const char *basename,
696706
if (a->is_macro)
697707
continue;
698708
if (path_matches(path, pathlen, basename,
699-
a->u.pattern, base, stk->originlen))
709+
&a->u.pat, base, stk->originlen))
700710
rem = fill_one("fill", a, rem);
701711
}
702712
return rem;

dir.c

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -308,10 +308,10 @@ static int no_wildcard(const char *string)
308308
return string[simple_length(string)] == '\0';
309309
}
310310

311-
static void parse_exclude_pattern(const char **pattern,
312-
int *patternlen,
313-
int *flags,
314-
int *nowildcardlen)
311+
void parse_exclude_pattern(const char **pattern,
312+
int *patternlen,
313+
int *flags,
314+
int *nowildcardlen)
315315
{
316316
const char *p = *pattern;
317317
size_t i, len;
@@ -530,9 +530,9 @@ static void prep_exclude(struct dir_struct *dir, const char *base, int baselen)
530530
dir->basebuf[baselen] = '\0';
531531
}
532532

533-
static int match_basename(const char *basename, int basenamelen,
534-
const char *pattern, int prefix, int patternlen,
535-
int flags)
533+
int match_basename(const char *basename, int basenamelen,
534+
const char *pattern, int prefix, int patternlen,
535+
int flags)
536536
{
537537
if (prefix == patternlen) {
538538
if (!strcmp_icase(pattern, basename))
@@ -549,10 +549,10 @@ static int match_basename(const char *basename, int basenamelen,
549549
return 0;
550550
}
551551

552-
static int match_pathname(const char *pathname, int pathlen,
553-
const char *base, int baselen,
554-
const char *pattern, int prefix, int patternlen,
555-
int flags)
552+
int match_pathname(const char *pathname, int pathlen,
553+
const char *base, int baselen,
554+
const char *pattern, int prefix, int patternlen,
555+
int flags)
556556
{
557557
const char *name;
558558
int namelen;

dir.h

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,16 @@ extern int excluded_from_list(const char *pathname, int pathlen, const char *bas
8080
int *dtype, struct exclude_list *el);
8181
struct dir_entry *dir_add_ignored(struct dir_struct *dir, const char *pathname, int len);
8282

83+
/*
84+
* these implement the matching logic for dir.c:excluded_from_list and
85+
* attr.c:path_matches()
86+
*/
87+
extern int match_basename(const char *, int,
88+
const char *, int, int, int);
89+
extern int match_pathname(const char *, int,
90+
const char *, int,
91+
const char *, int, int, int);
92+
8393
/*
8494
* The excluded() API is meant for callers that check each level of leading
8595
* directory hierarchies with excluded() to avoid recursing into excluded
@@ -97,6 +107,7 @@ extern int path_excluded(struct path_exclude_check *, const char *, int namelen,
97107
extern int add_excludes_from_file_to_list(const char *fname, const char *base, int baselen,
98108
char **buf_p, struct exclude_list *which, int check_index);
99109
extern void add_excludes_from_file(struct dir_struct *, const char *fname);
110+
extern void parse_exclude_pattern(const char **string, int *patternlen, int *flags, int *nowildcardlen);
100111
extern void add_exclude(const char *string, const char *base,
101112
int baselen, struct exclude_list *which);
102113
extern void free_excludes(struct exclude_list *el);

t/t0003-attributes.sh

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -206,6 +206,16 @@ test_expect_success 'root subdir attribute test' '
206206
attr_check subdir/a/i unspecified
207207
'
208208

209+
test_expect_success 'negative patterns' '
210+
echo "!f test=bar" >.gitattributes &&
211+
test_must_fail git check-attr test -- f
212+
'
213+
214+
test_expect_success 'patterns starting with exclamation' '
215+
echo "\!f test=foo" >.gitattributes &&
216+
attr_check "!f" foo
217+
'
218+
209219
test_expect_success 'setup bare' '
210220
git clone --bare . bare.git &&
211221
cd bare.git

0 commit comments

Comments
 (0)