aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Documentation/gitattributes.txt1
-rw-r--r--attr.c52
-rw-r--r--dir.c22
-rw-r--r--dir.h11
-rwxr-xr-xt/t0003-attributes.sh10
5 files changed, 64 insertions, 32 deletions
diff --git a/Documentation/gitattributes.txt b/Documentation/gitattributes.txt
index 80120ea14..b7c0e6577 100644
--- a/Documentation/gitattributes.txt
+++ b/Documentation/gitattributes.txt
@@ -56,6 +56,7 @@ When more than one pattern matches the path, a later line
overrides an earlier line. This overriding is done per
attribute. The rules how the pattern matches paths are the
same as in `.gitignore` files; see linkgit:gitignore[5].
+Unlike `.gitignore`, negative patterns are forbidden.
When deciding what attributes are assigned to a path, git
consults `$GIT_DIR/info/attributes` file (which has the highest
diff --git a/attr.c b/attr.c
index e7caee4dd..2fc635362 100644
--- a/attr.c
+++ b/attr.c
@@ -115,6 +115,13 @@ struct attr_state {
const char *setto;
};
+struct pattern {
+ const char *pattern;
+ int patternlen;
+ int nowildcardlen;
+ int flags; /* EXC_FLAG_* */
+};
+
/*
* One rule, as from a .gitattributes file.
*
@@ -131,7 +138,7 @@ struct attr_state {
*/
struct match_attr {
union {
- char *pattern;
+ struct pattern pat;
struct git_attr *attr;
} u;
char is_macro;
@@ -241,9 +248,16 @@ static struct match_attr *parse_attr_line(const char *line, const char *src,
if (is_macro)
res->u.attr = git_attr_internal(name, namelen);
else {
- res->u.pattern = (char *)&(res->state[num_attr]);
- memcpy(res->u.pattern, name, namelen);
- res->u.pattern[namelen] = 0;
+ char *p = (char *)&(res->state[num_attr]);
+ memcpy(p, name, namelen);
+ res->u.pat.pattern = p;
+ parse_exclude_pattern(&res->u.pat.pattern,
+ &res->u.pat.patternlen,
+ &res->u.pat.flags,
+ &res->u.pat.nowildcardlen);
+ if (res->u.pat.flags & EXC_FLAG_NEGATIVE)
+ die(_("Negative patterns are forbidden in git attributes\n"
+ "Use '\\!' for literal leading exclamation."));
}
res->is_macro = is_macro;
res->num_attr = num_attr;
@@ -640,25 +654,21 @@ static void prepare_attr_stack(const char *path)
static int path_matches(const char *pathname, int pathlen,
const char *basename,
- const char *pattern,
+ const struct pattern *pat,
const char *base, int baselen)
{
- if (!strchr(pattern, '/')) {
- return (fnmatch_icase(pattern, basename, 0) == 0);
+ const char *pattern = pat->pattern;
+ int prefix = pat->nowildcardlen;
+
+ if (pat->flags & EXC_FLAG_NODIR) {
+ return match_basename(basename,
+ pathlen - (basename - pathname),
+ pattern, prefix,
+ pat->patternlen, pat->flags);
}
- /*
- * match with FNM_PATHNAME; the pattern has base implicitly
- * in front of it.
- */
- if (*pattern == '/')
- pattern++;
- if (pathlen < baselen ||
- (baselen && pathname[baselen] != '/') ||
- strncmp(pathname, base, baselen))
- return 0;
- if (baselen != 0)
- baselen++;
- return fnmatch_icase(pattern, pathname + baselen, FNM_PATHNAME) == 0;
+ return match_pathname(pathname, pathlen,
+ base, baselen,
+ pattern, prefix, pat->patternlen, pat->flags);
}
static int macroexpand_one(int attr_nr, int rem);
@@ -696,7 +706,7 @@ static int fill(const char *path, int pathlen, const char *basename,
if (a->is_macro)
continue;
if (path_matches(path, pathlen, basename,
- a->u.pattern, base, stk->originlen))
+ &a->u.pat, base, stk->originlen))
rem = fill_one("fill", a, rem);
}
return rem;
diff --git a/dir.c b/dir.c
index c4e64a5bc..ee8e7115a 100644
--- a/dir.c
+++ b/dir.c
@@ -308,10 +308,10 @@ static int no_wildcard(const char *string)
return string[simple_length(string)] == '\0';
}
-static void parse_exclude_pattern(const char **pattern,
- int *patternlen,
- int *flags,
- int *nowildcardlen)
+void parse_exclude_pattern(const char **pattern,
+ int *patternlen,
+ int *flags,
+ int *nowildcardlen)
{
const char *p = *pattern;
size_t i, len;
@@ -530,9 +530,9 @@ static void prep_exclude(struct dir_struct *dir, const char *base, int baselen)
dir->basebuf[baselen] = '\0';
}
-static int match_basename(const char *basename, int basenamelen,
- const char *pattern, int prefix, int patternlen,
- int flags)
+int match_basename(const char *basename, int basenamelen,
+ const char *pattern, int prefix, int patternlen,
+ int flags)
{
if (prefix == patternlen) {
if (!strcmp_icase(pattern, basename))
@@ -549,10 +549,10 @@ static int match_basename(const char *basename, int basenamelen,
return 0;
}
-static int match_pathname(const char *pathname, int pathlen,
- const char *base, int baselen,
- const char *pattern, int prefix, int patternlen,
- int flags)
+int match_pathname(const char *pathname, int pathlen,
+ const char *base, int baselen,
+ const char *pattern, int prefix, int patternlen,
+ int flags)
{
const char *name;
int namelen;
diff --git a/dir.h b/dir.h
index 41ea32d95..f5c89e3b8 100644
--- a/dir.h
+++ b/dir.h
@@ -81,6 +81,16 @@ extern int excluded_from_list(const char *pathname, int pathlen, const char *bas
struct dir_entry *dir_add_ignored(struct dir_struct *dir, const char *pathname, int len);
/*
+ * these implement the matching logic for dir.c:excluded_from_list and
+ * attr.c:path_matches()
+ */
+extern int match_basename(const char *, int,
+ const char *, int, int, int);
+extern int match_pathname(const char *, int,
+ const char *, int,
+ const char *, int, int, int);
+
+/*
* The excluded() API is meant for callers that check each level of leading
* directory hierarchies with excluded() to avoid recursing into excluded
* directories. Callers that do not do so should use this API instead.
@@ -97,6 +107,7 @@ extern int path_excluded(struct path_exclude_check *, const char *, int namelen,
extern int add_excludes_from_file_to_list(const char *fname, const char *base, int baselen,
char **buf_p, struct exclude_list *which, int check_index);
extern void add_excludes_from_file(struct dir_struct *, const char *fname);
+extern void parse_exclude_pattern(const char **string, int *patternlen, int *flags, int *nowildcardlen);
extern void add_exclude(const char *string, const char *base,
int baselen, struct exclude_list *which);
extern void free_excludes(struct exclude_list *el);
diff --git a/t/t0003-attributes.sh b/t/t0003-attributes.sh
index 51f3045ba..f6c21ea4e 100755
--- a/t/t0003-attributes.sh
+++ b/t/t0003-attributes.sh
@@ -206,6 +206,16 @@ test_expect_success 'root subdir attribute test' '
attr_check subdir/a/i unspecified
'
+test_expect_success 'negative patterns' '
+ echo "!f test=bar" >.gitattributes &&
+ test_must_fail git check-attr test -- f
+'
+
+test_expect_success 'patterns starting with exclamation' '
+ echo "\!f test=foo" >.gitattributes &&
+ attr_check "!f" foo
+'
+
test_expect_success 'setup bare' '
git clone --bare . bare.git &&
cd bare.git