attr: more matching optimizations from .gitignore
authorNguyễn Thái Ngọc Duy <pclouds@gmail.com>
Mon, 15 Oct 2012 06:24:39 +0000 (13:24 +0700)
committerJunio C Hamano <gitster@pobox.com>
Mon, 15 Oct 2012 21:57:17 +0000 (14:57 -0700)
.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 <pclouds@gmail.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
Documentation/gitattributes.txt
attr.c
dir.c
dir.h
t/t0003-attributes.sh

index 80120ea14f1ccd2784405c2bf2d54863bb52e8d3..b7c0e6577e8d1dcdd7ac6f8f12ba7f6e29497445 100644 (file)
@@ -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 e7caee4ddd94f4c82f136b89d3f66afe328e7497..2fc6353628a2cd7ffe363a7b600dc8f4283d0e1f 100644 (file)
--- 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 c4e64a5bc93176b62979a264fa8a8c54c4349233..ee8e7115a8a37ca5c096dc3548b13e3761e4cac5 100644 (file)
--- 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 41ea32d957c6e4f201a0e15473a2ba1d340be751..f5c89e3b80143f2508ac5b69d432aa82a4254751 100644 (file)
--- a/dir.h
+++ b/dir.h
@@ -80,6 +80,16 @@ extern int excluded_from_list(const char *pathname, int pathlen, const char *bas
                              int *dtype, struct exclude_list *el);
 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
@@ -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);
index 51f3045ba4bde549b06ea669b1877f0e2db7c401..f6c21ea4ea07e7603aee693814d3b5caa143923c 100755 (executable)
@@ -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