Merge branch 'bw/attr'
authorJunio C Hamano <gitster@pobox.com>
Mon, 27 Feb 2017 21:57:14 +0000 (13:57 -0800)
committerJunio C Hamano <gitster@pobox.com>
Mon, 27 Feb 2017 21:57:14 +0000 (13:57 -0800)
The gitattributes machinery is being taught to work better in a
multi-threaded environment.

* bw/attr: (27 commits)
attr: reformat git_attr_set_direction() function
attr: push the bare repo check into read_attr()
attr: store attribute stack in attr_check structure
attr: tighten const correctness with git_attr and match_attr
attr: remove maybe-real, maybe-macro from git_attr
attr: eliminate global check_all_attr array
attr: use hashmap for attribute dictionary
attr: change validity check for attribute names to use positive logic
attr: pass struct attr_check to collect_some_attrs
attr: retire git_check_attrs() API
attr: convert git_check_attrs() callers to use the new API
attr: convert git_all_attrs() to use "struct attr_check"
attr: (re)introduce git_check_attr() and struct attr_check
attr: rename function and struct related to checking attributes
attr.c: outline the future plans by heavily commenting
Documentation: fix a typo
attr.c: add push_stack() helper
attr: support quoting pathname patterns in C style
attr.c: plug small leak in parse_attr_line()
attr.c: tighten constness around "git_attr" structure
...

14 files changed:
Documentation/gitattributes.txt
Documentation/technical/api-gitattributes.txt
archive.c
attr.c
attr.h
builtin/check-attr.c
builtin/pack-objects.c
commit.c
common-main.c
convert.c
ll-merge.c
t/t0003-attributes.sh
userdiff.c
ws.c
index e0b66c1220a7d22ef5b8eb960a4e2e389a71f884..a53d093ca1666e6f84f229f94a3e4b7b1ed6c899 100644 (file)
@@ -21,9 +21,11 @@ Each line in `gitattributes` file is of form:
        pattern attr1 attr2 ...
 
 That is, a pattern followed by an attributes list,
-separated by whitespaces.  When the pattern matches the
-path in question, the attributes listed on the line are given to
-the path.
+separated by whitespaces. Leading and trailing whitespaces are
+ignored. Lines that begin with '#' are ignored. Patterns
+that begin with a double quote are quoted in C style.
+When the pattern matches the path in question, the attributes
+listed on the line are given to the path.
 
 Each attribute can be in one of these states for a given path:
 
@@ -86,7 +88,7 @@ is either not set or empty, $HOME/.config/git/attributes is used instead.
 Attributes for all users on a system should be placed in the
 `$(prefix)/etc/gitattributes` file.
 
-Sometimes you would need to override an setting of an attribute
+Sometimes you would need to override a setting of an attribute
 for a path to `Unspecified` state.  This can be done by listing
 the name of the attribute prefixed with an exclamation point `!`.
 
index 260266867768a549e58e540eaa95d3bbe422e5c0..e7cbb7c13adf2f7ea3b15279a17f1a06da859cbc 100644 (file)
@@ -16,10 +16,15 @@ Data Structure
        of no interest to the calling programs.  The name of the
        attribute can be retrieved by calling `git_attr_name()`.
 
-`struct git_attr_check`::
+`struct attr_check_item`::
 
-       This structure represents a set of attributes to check in a call
-       to `git_check_attr()` function, and receives the results.
+       This structure represents one attribute and its value.
+
+`struct attr_check`::
+
+       This structure represents a collection of `attr_check_item`.
+       It is passed to `git_check_attr()` function, specifying the
+       attributes to check, and receives their values.
 
 
 Attribute Values
@@ -27,7 +32,7 @@ Attribute Values
 
 An attribute for a path can be in one of four states: Set, Unset,
 Unspecified or set to a string, and `.value` member of `struct
-git_attr_check` records it.  There are three macros to check these:
+attr_check_item` records it.  There are three macros to check these:
 
 `ATTR_TRUE()`::
 
@@ -48,49 +53,51 @@ value of the attribute for the path.
 Querying Specific Attributes
 ----------------------------
 
-* Prepare an array of `struct git_attr_check` to define the list of
-  attributes you would want to check.  To populate this array, you would
-  need to define necessary attributes by calling `git_attr()` function.
+* Prepare `struct attr_check` using attr_check_initl()
+  function, enumerating the names of attributes whose values you are
+  interested in, terminated with a NULL pointer.  Alternatively, an
+  empty `struct attr_check` can be prepared by calling
+  `attr_check_alloc()` function and then attributes you want to
+  ask about can be added to it with `attr_check_append()`
+  function.
 
 * Call `git_check_attr()` to check the attributes for the path.
 
-* Inspect `git_attr_check` structure to see how each of the attribute in
-  the array is defined for the path.
+* Inspect `attr_check` structure to see how each of the
+  attribute in the array is defined for the path.
 
 
 Example
 -------
 
-To see how attributes "crlf" and "indent" are set for different paths.
+To see how attributes "crlf" and "ident" are set for different paths.
 
-. Prepare an array of `struct git_attr_check` with two elements (because
-  we are checking two attributes).  Initialize their `attr` member with
-  pointers to `struct git_attr` obtained by calling `git_attr()`:
+. Prepare a `struct attr_check` with two elements (because
+  we are checking two attributes):
 
 ------------
-static struct git_attr_check check[2];
+static struct attr_check *check;
 static void setup_check(void)
 {
-       if (check[0].attr)
+       if (check)
                return; /* already done */
-       check[0].attr = git_attr("crlf");
-       check[1].attr = git_attr("ident");
+       check = attr_check_initl("crlf", "ident", NULL);
 }
 ------------
 
-. Call `git_check_attr()` with the prepared array of `struct git_attr_check`:
+. Call `git_check_attr()` with the prepared `struct attr_check`:
 
 ------------
        const char *path;
 
        setup_check();
-       git_check_attr(path, ARRAY_SIZE(check), check);
+       git_check_attr(path, check);
 ------------
 
-. Act on `.value` member of the result, left in `check[]`:
+. Act on `.value` member of the result, left in `check->items[]`:
 
 ------------
-       const char *value = check[0].value;
+       const char *value = check->items[0].value;
 
        if (ATTR_TRUE(value)) {
                The attribute is Set, by listing only the name of the
@@ -109,20 +116,39 @@ static void setup_check(void)
        }
 ------------
 
+To see how attributes in argv[] are set for different paths, only
+the first step in the above would be different.
+
+------------
+static struct attr_check *check;
+static void setup_check(const char **argv)
+{
+       check = attr_check_alloc();
+       while (*argv) {
+               struct git_attr *attr = git_attr(*argv);
+               attr_check_append(check, attr);
+               argv++;
+       }
+}
+------------
+
 
 Querying All Attributes
 -----------------------
 
 To get the values of all attributes associated with a file:
 
-* Call `git_all_attrs()`, which returns an array of `git_attr_check`
-  structures.
+* Prepare an empty `attr_check` structure by calling
+  `attr_check_alloc()`.
+
+* Call `git_all_attrs()`, which populates the `attr_check`
+  with the attributes attached to the path.
 
-* Iterate over the `git_attr_check` array to examine the attribute
-  names and values.  The name of the attribute described by a
-  `git_attr_check` object can be retrieved via
-  `git_attr_name(check[i].attr)`.  (Please note that no items will be
-  returned for unset attributes, so `ATTR_UNSET()` will return false
-  for all returned `git_array_check` objects.)
+* Iterate over the `attr_check.items[]` array to examine
+  the attribute names and values.  The name of the attribute
+  described by a  `attr_check.items[]` object can be retrieved via
+  `git_attr_name(check->items[i].attr)`.  (Please note that no items
+  will be returned for unset attributes, so `ATTR_UNSET()` will return
+  false for all returned `attr_check.items[]` objects.)
 
-* Free the `git_array_check` array.
+* Free the `attr_check` struct by calling `attr_check_free()`.
index 01751e574bc10c1211d0253d215917ecc4ce82dd..60b889198656f42a1d073503c4da5886bf0a0526 100644 (file)
--- a/archive.c
+++ b/archive.c
@@ -87,19 +87,6 @@ void *sha1_file_to_archive(const struct archiver_args *args,
        return buffer;
 }
 
-static void setup_archive_check(struct git_attr_check *check)
-{
-       static struct git_attr *attr_export_ignore;
-       static struct git_attr *attr_export_subst;
-
-       if (!attr_export_ignore) {
-               attr_export_ignore = git_attr("export-ignore");
-               attr_export_subst = git_attr("export-subst");
-       }
-       check[0].attr = attr_export_ignore;
-       check[1].attr = attr_export_subst;
-}
-
 struct directory {
        struct directory *up;
        struct object_id oid;
@@ -120,10 +107,10 @@ static int write_archive_entry(const unsigned char *sha1, const char *base,
                void *context)
 {
        static struct strbuf path = STRBUF_INIT;
+       static struct attr_check *check;
        struct archiver_context *c = context;
        struct archiver_args *args = c->args;
        write_archive_entry_fn_t write_entry = c->write_entry;
-       struct git_attr_check check[2];
        const char *path_without_prefix;
        int err;
 
@@ -137,11 +124,12 @@ static int write_archive_entry(const unsigned char *sha1, const char *base,
                strbuf_addch(&path, '/');
        path_without_prefix = path.buf + args->baselen;
 
-       setup_archive_check(check);
-       if (!git_check_attr(path_without_prefix, ARRAY_SIZE(check), check)) {
-               if (ATTR_TRUE(check[0].value))
+       if (!check)
+               check = attr_check_initl("export-ignore", "export-subst", NULL);
+       if (!git_check_attr(path_without_prefix, check)) {
+               if (ATTR_TRUE(check->items[0].value))
                        return 0;
-               args->convert = ATTR_TRUE(check[1].value);
+               args->convert = ATTR_TRUE(check->items[1].value);
        }
 
        if (S_ISDIR(mode) || S_ISGITLINK(mode)) {
diff --git a/attr.c b/attr.c
index 1fcf042b87c8a39fb967e07c5acb81c0c364d136..5493bff224a98361811c1e9fa88bb16efbf85d22 100644 (file)
--- a/attr.c
+++ b/attr.c
@@ -13,6 +13,8 @@
 #include "attr.h"
 #include "dir.h"
 #include "utf8.h"
+#include "quote.h"
+#include "thread-utils.h"
 
 const char git_attr__true[] = "(builtin)true";
 const char git_attr__false[] = "\0(builtin)false";
@@ -22,99 +24,234 @@ static const char git_attr__unknown[] = "(builtin)unknown";
 #define ATTR__UNSET NULL
 #define ATTR__UNKNOWN git_attr__unknown
 
-/* This is a randomly chosen prime. */
-#define HASHSIZE 257
-
 #ifndef DEBUG_ATTR
 #define DEBUG_ATTR 0
 #endif
 
 struct git_attr {
-       struct git_attr *next;
-       unsigned h;
-       int attr_nr;
-       int maybe_macro;
-       int maybe_real;
-       char name[FLEX_ARRAY];
+       int attr_nr; /* unique attribute number */
+       char name[FLEX_ARRAY]; /* attribute name */
 };
-static int attr_nr;
-static int cannot_trust_maybe_real;
-
-static struct git_attr_check *check_all_attr;
-static struct git_attr *(git_attr_hash[HASHSIZE]);
 
-char *git_attr_name(struct git_attr *attr)
+const char *git_attr_name(const struct git_attr *attr)
 {
        return attr->name;
 }
 
-static unsigned hash_name(const char *name, int namelen)
+struct attr_hashmap {
+       struct hashmap map;
+#ifndef NO_PTHREADS
+       pthread_mutex_t mutex;
+#endif
+};
+
+static inline void hashmap_lock(struct attr_hashmap *map)
+{
+#ifndef NO_PTHREADS
+       pthread_mutex_lock(&map->mutex);
+#endif
+}
+
+static inline void hashmap_unlock(struct attr_hashmap *map)
 {
-       unsigned val = 0, c;
+#ifndef NO_PTHREADS
+       pthread_mutex_unlock(&map->mutex);
+#endif
+}
 
-       while (namelen--) {
-               c = *name++;
-               val = ((val << 7) | (val >> 22)) ^ c;
+/*
+ * The global dictionary of all interned attributes.  This
+ * is a singleton object which is shared between threads.
+ * Access to this dictionary must be surrounded with a mutex.
+ */
+static struct attr_hashmap g_attr_hashmap;
+
+/* The container for objects stored in "struct attr_hashmap" */
+struct attr_hash_entry {
+       struct hashmap_entry ent; /* must be the first member! */
+       const char *key; /* the key; memory should be owned by value */
+       size_t keylen; /* length of the key */
+       void *value; /* the stored value */
+};
+
+/* attr_hashmap comparison function */
+static int attr_hash_entry_cmp(const struct attr_hash_entry *a,
+                              const struct attr_hash_entry *b,
+                              void *unused)
+{
+       return (a->keylen != b->keylen) || strncmp(a->key, b->key, a->keylen);
+}
+
+/* Initialize an 'attr_hashmap' object */
+static void attr_hashmap_init(struct attr_hashmap *map)
+{
+       hashmap_init(&map->map, (hashmap_cmp_fn) attr_hash_entry_cmp, 0);
+}
+
+/*
+ * Retrieve the 'value' stored in a hashmap given the provided 'key'.
+ * If there is no matching entry, return NULL.
+ */
+static void *attr_hashmap_get(struct attr_hashmap *map,
+                             const char *key, size_t keylen)
+{
+       struct attr_hash_entry k;
+       struct attr_hash_entry *e;
+
+       if (!map->map.tablesize)
+               attr_hashmap_init(map);
+
+       hashmap_entry_init(&k, memhash(key, keylen));
+       k.key = key;
+       k.keylen = keylen;
+       e = hashmap_get(&map->map, &k, NULL);
+
+       return e ? e->value : NULL;
+}
+
+/* Add 'value' to a hashmap based on the provided 'key'. */
+static void attr_hashmap_add(struct attr_hashmap *map,
+                            const char *key, size_t keylen,
+                            void *value)
+{
+       struct attr_hash_entry *e;
+
+       if (!map->map.tablesize)
+               attr_hashmap_init(map);
+
+       e = xmalloc(sizeof(struct attr_hash_entry));
+       hashmap_entry_init(e, memhash(key, keylen));
+       e->key = key;
+       e->keylen = keylen;
+       e->value = value;
+
+       hashmap_add(&map->map, e);
+}
+
+struct all_attrs_item {
+       const struct git_attr *attr;
+       const char *value;
+       /*
+        * If 'macro' is non-NULL, indicates that 'attr' is a macro based on
+        * the current attribute stack and contains a pointer to the match_attr
+        * definition of the macro
+        */
+       const struct match_attr *macro;
+};
+
+/*
+ * Reallocate and reinitialize the array of all attributes (which is used in
+ * the attribute collection process) in 'check' based on the global dictionary
+ * of attributes.
+ */
+static void all_attrs_init(struct attr_hashmap *map, struct attr_check *check)
+{
+       int i;
+
+       hashmap_lock(map);
+
+       if (map->map.size < check->all_attrs_nr)
+               die("BUG: interned attributes shouldn't be deleted");
+
+       /*
+        * If the number of attributes in the global dictionary has increased
+        * (or this attr_check instance doesn't have an initialized all_attrs
+        * field), reallocate the provided attr_check instance's all_attrs
+        * field and fill each entry with its corresponding git_attr.
+        */
+       if (map->map.size != check->all_attrs_nr) {
+               struct attr_hash_entry *e;
+               struct hashmap_iter iter;
+               hashmap_iter_init(&map->map, &iter);
+
+               REALLOC_ARRAY(check->all_attrs, map->map.size);
+               check->all_attrs_nr = map->map.size;
+
+               while ((e = hashmap_iter_next(&iter))) {
+                       const struct git_attr *a = e->value;
+                       check->all_attrs[a->attr_nr].attr = a;
+               }
+       }
+
+       hashmap_unlock(map);
+
+       /*
+        * Re-initialize every entry in check->all_attrs.
+        * This re-initialization can live outside of the locked region since
+        * the attribute dictionary is no longer being accessed.
+        */
+       for (i = 0; i < check->all_attrs_nr; i++) {
+               check->all_attrs[i].value = ATTR__UNKNOWN;
+               check->all_attrs[i].macro = NULL;
        }
-       return val;
 }
 
-static int invalid_attr_name(const char *name, int namelen)
+static int attr_name_valid(const char *name, size_t namelen)
 {
        /*
         * Attribute name cannot begin with '-' and must consist of
         * characters from [-A-Za-z0-9_.].
         */
        if (namelen <= 0 || *name == '-')
-               return -1;
+               return 0;
        while (namelen--) {
                char ch = *name++;
                if (! (ch == '-' || ch == '.' || ch == '_' ||
                       ('0' <= ch && ch <= '9') ||
                       ('a' <= ch && ch <= 'z') ||
                       ('A' <= ch && ch <= 'Z')) )
-                       return -1;
+                       return 0;
        }
-       return 0;
+       return 1;
 }
 
-static struct git_attr *git_attr_internal(const char *name, int len)
+static void report_invalid_attr(const char *name, size_t len,
+                               const char *src, int lineno)
+{
+       struct strbuf err = STRBUF_INIT;
+       strbuf_addf(&err, _("%.*s is not a valid attribute name"),
+                   (int) len, name);
+       fprintf(stderr, "%s: %s:%d\n", err.buf, src, lineno);
+       strbuf_release(&err);
+}
+
+/*
+ * Given a 'name', lookup and return the corresponding attribute in the global
+ * dictionary.  If no entry is found, create a new attribute and store it in
+ * the dictionary.
+ */
+static const struct git_attr *git_attr_internal(const char *name, int namelen)
 {
-       unsigned hval = hash_name(name, len);
-       unsigned pos = hval % HASHSIZE;
        struct git_attr *a;
 
-       for (a = git_attr_hash[pos]; a; a = a->next) {
-               if (a->h == hval &&
-                   !memcmp(a->name, name, len) && !a->name[len])
-                       return a;
+       if (!attr_name_valid(name, namelen))
+               return NULL;
+
+       hashmap_lock(&g_attr_hashmap);
+
+       a = attr_hashmap_get(&g_attr_hashmap, name, namelen);
+
+       if (!a) {
+               FLEX_ALLOC_MEM(a, name, name, namelen);
+               a->attr_nr = g_attr_hashmap.map.size;
+
+               attr_hashmap_add(&g_attr_hashmap, a->name, namelen, a);
+               assert(a->attr_nr == (g_attr_hashmap.map.size - 1));
        }
 
-       if (invalid_attr_name(name, len))
-               return NULL;
+       hashmap_unlock(&g_attr_hashmap);
 
-       FLEX_ALLOC_MEM(a, name, name, len);
-       a->h = hval;
-       a->next = git_attr_hash[pos];
-       a->attr_nr = attr_nr++;
-       a->maybe_macro = 0;
-       a->maybe_real = 0;
-       git_attr_hash[pos] = a;
-
-       REALLOC_ARRAY(check_all_attr, attr_nr);
-       check_all_attr[a->attr_nr].attr = a;
-       check_all_attr[a->attr_nr].value = ATTR__UNKNOWN;
        return a;
 }
 
-struct git_attr *git_attr(const char *name)
+const struct git_attr *git_attr(const char *name)
 {
        return git_attr_internal(name, strlen(name));
 }
 
 /* What does a matched pattern decide? */
 struct attr_state {
-       struct git_attr *attr;
+       const struct git_attr *attr;
        const char *setto;
 };
 
@@ -131,9 +268,8 @@ struct pattern {
  * If is_macro is true, then u.attr is a pointer to the git_attr being
  * defined.
  *
- * If is_macro is false, then u.pattern points at the filename pattern
- * to which the rule applies.  (The memory pointed to is part of the
- * memory block allocated for the match_attr instance.)
+ * If is_macro is false, then u.pat is the filename pattern to which the
+ * rule applies.
  *
  * In either case, num_attr is the number of attributes affected by
  * this rule, and state is an array listing them.  The attributes are
@@ -142,7 +278,7 @@ struct pattern {
 struct match_attr {
        union {
                struct pattern pat;
-               struct git_attr *attr;
+               const struct git_attr *attr;
        } u;
        char is_macro;
        unsigned num_attr;
@@ -177,13 +313,17 @@ static const char *parse_attr(const char *src, int lineno, const char *cp,
                        cp++;
                        len--;
                }
-               if (invalid_attr_name(cp, len)) {
-                       fprintf(stderr,
-                               "%.*s is not a valid attribute name: %s:%d\n",
-                               len, cp, src, lineno);
+               if (!attr_name_valid(cp, len)) {
+                       report_invalid_attr(cp, len, src, lineno);
                        return NULL;
                }
        } else {
+               /*
+                * As this function is always called twice, once with
+                * e == NULL in the first pass and then e != NULL in
+                * the second pass, no need for attr_name_valid()
+                * check here.
+                */
                if (*cp == '-' || *cp == '!') {
                        e->setto = (*cp == '-') ? ATTR__FALSE : ATTR__UNSET;
                        cp++;
@@ -207,41 +347,47 @@ static struct match_attr *parse_attr_line(const char *line, const char *src,
        const char *cp, *name, *states;
        struct match_attr *res = NULL;
        int is_macro;
+       struct strbuf pattern = STRBUF_INIT;
 
        cp = line + strspn(line, blank);
        if (!*cp || *cp == '#')
                return NULL;
        name = cp;
-       namelen = strcspn(name, blank);
+
+       if (*cp == '"' && !unquote_c_style(&pattern, name, &states)) {
+               name = pattern.buf;
+               namelen = pattern.len;
+       } else {
+               namelen = strcspn(name, blank);
+               states = name + namelen;
+       }
+
        if (strlen(ATTRIBUTE_MACRO_PREFIX) < namelen &&
            starts_with(name, ATTRIBUTE_MACRO_PREFIX)) {
                if (!macro_ok) {
                        fprintf(stderr, "%s not allowed: %s:%d\n",
                                name, src, lineno);
-                       return NULL;
+                       goto fail_return;
                }
                is_macro = 1;
                name += strlen(ATTRIBUTE_MACRO_PREFIX);
                name += strspn(name, blank);
                namelen = strcspn(name, blank);
-               if (invalid_attr_name(name, namelen)) {
-                       fprintf(stderr,
-                               "%.*s is not a valid attribute name: %s:%d\n",
-                               namelen, name, src, lineno);
-                       return NULL;
+               if (!attr_name_valid(name, namelen)) {
+                       report_invalid_attr(name, namelen, src, lineno);
+                       goto fail_return;
                }
        }
        else
                is_macro = 0;
 
-       states = name + namelen;
        states += strspn(states, blank);
 
        /* First pass to count the attr_states */
        for (cp = states, num_attr = 0; *cp; num_attr++) {
                cp = parse_attr(src, lineno, cp, NULL);
                if (!cp)
-                       return NULL;
+                       goto fail_return;
        }
 
        res = xcalloc(1,
@@ -250,7 +396,6 @@ static struct match_attr *parse_attr_line(const char *line, const char *src,
                      (is_macro ? 0 : namelen + 1));
        if (is_macro) {
                res->u.attr = git_attr_internal(name, namelen);
-               res->u.attr->maybe_macro = 1;
        } else {
                char *p = (char *)&(res->state[num_attr]);
                memcpy(p, name, namelen);
@@ -262,7 +407,7 @@ static struct match_attr *parse_attr_line(const char *line, const char *src,
                if (res->u.pat.flags & EXC_FLAG_NEGATIVE) {
                        warning(_("Negative patterns are ignored in git attributes\n"
                                  "Use '\\!' for literal leading exclamation."));
-                       return NULL;
+                       goto fail_return;
                }
        }
        res->is_macro = is_macro;
@@ -271,13 +416,15 @@ static struct match_attr *parse_attr_line(const char *line, const char *src,
        /* Second pass to fill the attr_states */
        for (cp = states, i = 0; *cp; i++) {
                cp = parse_attr(src, lineno, cp, &(res->state[i]));
-               if (!is_macro)
-                       res->state[i].attr->maybe_real = 1;
-               if (res->state[i].attr->maybe_macro)
-                       cannot_trust_maybe_real = 1;
        }
 
+       strbuf_release(&pattern);
        return res;
+
+fail_return:
+       strbuf_release(&pattern);
+       free(res);
+       return NULL;
 }
 
 /*
@@ -295,19 +442,19 @@ static struct match_attr *parse_attr_line(const char *line, const char *src,
  * directory (again, reading the file from top to bottom) down to the
  * current directory, and then scan the list backwards to find the first match.
  * This is exactly the same as what is_excluded() does in dir.c to deal with
- * .gitignore
+ * .gitignore file and info/excludes file as a fallback.
  */
 
-static struct attr_stack {
+struct attr_stack {
        struct attr_stack *prev;
        char *origin;
        size_t originlen;
        unsigned num_matches;
        unsigned alloc;
        struct match_attr **attrs;
-} *attr_stack;
+};
 
-static void free_attr_elem(struct attr_stack *e)
+static void attr_stack_free(struct attr_stack *e)
 {
        int i;
        free(e->origin);
@@ -330,6 +477,173 @@ static void free_attr_elem(struct attr_stack *e)
        free(e);
 }
 
+static void drop_attr_stack(struct attr_stack **stack)
+{
+       while (*stack) {
+               struct attr_stack *elem = *stack;
+               *stack = elem->prev;
+               attr_stack_free(elem);
+       }
+}
+
+/* List of all attr_check structs; access should be surrounded by mutex */
+static struct check_vector {
+       size_t nr;
+       size_t alloc;
+       struct attr_check **checks;
+#ifndef NO_PTHREADS
+       pthread_mutex_t mutex;
+#endif
+} check_vector;
+
+static inline void vector_lock(void)
+{
+#ifndef NO_PTHREADS
+       pthread_mutex_lock(&check_vector.mutex);
+#endif
+}
+
+static inline void vector_unlock(void)
+{
+#ifndef NO_PTHREADS
+       pthread_mutex_unlock(&check_vector.mutex);
+#endif
+}
+
+static void check_vector_add(struct attr_check *c)
+{
+       vector_lock();
+
+       ALLOC_GROW(check_vector.checks,
+                  check_vector.nr + 1,
+                  check_vector.alloc);
+       check_vector.checks[check_vector.nr++] = c;
+
+       vector_unlock();
+}
+
+static void check_vector_remove(struct attr_check *check)
+{
+       int i;
+
+       vector_lock();
+
+       /* Find entry */
+       for (i = 0; i < check_vector.nr; i++)
+               if (check_vector.checks[i] == check)
+                       break;
+
+       if (i >= check_vector.nr)
+               die("BUG: no entry found");
+
+       /* shift entries over */
+       for (; i < check_vector.nr - 1; i++)
+               check_vector.checks[i] = check_vector.checks[i + 1];
+
+       check_vector.nr--;
+
+       vector_unlock();
+}
+
+/* Iterate through all attr_check instances and drop their stacks */
+static void drop_all_attr_stacks(void)
+{
+       int i;
+
+       vector_lock();
+
+       for (i = 0; i < check_vector.nr; i++) {
+               drop_attr_stack(&check_vector.checks[i]->stack);
+       }
+
+       vector_unlock();
+}
+
+struct attr_check *attr_check_alloc(void)
+{
+       struct attr_check *c = xcalloc(1, sizeof(struct attr_check));
+
+       /* save pointer to the check struct */
+       check_vector_add(c);
+
+       return c;
+}
+
+struct attr_check *attr_check_initl(const char *one, ...)
+{
+       struct attr_check *check;
+       int cnt;
+       va_list params;
+       const char *param;
+
+       va_start(params, one);
+       for (cnt = 1; (param = va_arg(params, const char *)) != NULL; cnt++)
+               ;
+       va_end(params);
+
+       check = attr_check_alloc();
+       check->nr = cnt;
+       check->alloc = cnt;
+       check->items = xcalloc(cnt, sizeof(struct attr_check_item));
+
+       check->items[0].attr = git_attr(one);
+       va_start(params, one);
+       for (cnt = 1; cnt < check->nr; cnt++) {
+               const struct git_attr *attr;
+               param = va_arg(params, const char *);
+               if (!param)
+                       die("BUG: counted %d != ended at %d",
+                           check->nr, cnt);
+               attr = git_attr(param);
+               if (!attr)
+                       die("BUG: %s: not a valid attribute name", param);
+               check->items[cnt].attr = attr;
+       }
+       va_end(params);
+       return check;
+}
+
+struct attr_check_item *attr_check_append(struct attr_check *check,
+                                         const struct git_attr *attr)
+{
+       struct attr_check_item *item;
+
+       ALLOC_GROW(check->items, check->nr + 1, check->alloc);
+       item = &check->items[check->nr++];
+       item->attr = attr;
+       return item;
+}
+
+void attr_check_reset(struct attr_check *check)
+{
+       check->nr = 0;
+}
+
+void attr_check_clear(struct attr_check *check)
+{
+       free(check->items);
+       check->items = NULL;
+       check->alloc = 0;
+       check->nr = 0;
+
+       free(check->all_attrs);
+       check->all_attrs = NULL;
+       check->all_attrs_nr = 0;
+
+       drop_attr_stack(&check->stack);
+}
+
+void attr_check_free(struct attr_check *check)
+{
+       if (check) {
+               /* Remove check from the check vector */
+               check_vector_remove(check);
+
+               attr_check_clear(check);
+               free(check);
+       }
+}
+
 static const char *builtin_attr[] = {
        "[attr]binary -diff -merge -text",
        NULL,
@@ -362,9 +676,31 @@ static struct attr_stack *read_attr_from_array(const char **list)
        return res;
 }
 
+/*
+ * Callers into the attribute system assume there is a single, system-wide
+ * global state where attributes are read from and when the state is flipped by
+ * calling git_attr_set_direction(), the stack frames that have been
+ * constructed need to be discarded so so that subsequent calls into the
+ * attribute system will lazily read from the right place.  Since changing
+ * direction causes a global paradigm shift, it should not ever be called while
+ * another thread could potentially be calling into the attribute system.
+ */
 static enum git_attr_direction direction;
 static struct index_state *use_index;
 
+void git_attr_set_direction(enum git_attr_direction new_direction,
+                           struct index_state *istate)
+{
+       if (is_bare_repository() && new_direction != GIT_ATTR_INDEX)
+               die("BUG: non-INDEX attr direction in a bare repo");
+
+       if (new_direction != direction)
+               drop_all_attr_stacks();
+
+       direction = new_direction;
+       use_index = istate;
+}
+
 static struct attr_stack *read_attr_from_file(const char *path, int macro_ok)
 {
        FILE *fp = fopen(path, "r");
@@ -402,8 +738,8 @@ static struct attr_stack *read_attr_from_index(const char *path, int macro_ok)
        for (sp = buf; *sp; ) {
                char *ep;
                int more;
-               for (ep = sp; *ep && *ep != '\n'; ep++)
-                       ;
+
+               ep = strchrnul(sp, '\n');
                more = (*ep == '\n');
                *ep = '\0';
                handle_attr_line(res, sp, path, ++lineno, macro_ok);
@@ -415,25 +751,28 @@ static struct attr_stack *read_attr_from_index(const char *path, int macro_ok)
 
 static struct attr_stack *read_attr(const char *path, int macro_ok)
 {
-       struct attr_stack *res;
+       struct attr_stack *res = NULL;
 
-       if (direction == GIT_ATTR_CHECKOUT) {
+       if (direction == GIT_ATTR_INDEX) {
                res = read_attr_from_index(path, macro_ok);
-               if (!res)
-                       res = read_attr_from_file(path, macro_ok);
-       }
-       else if (direction == GIT_ATTR_CHECKIN) {
-               res = read_attr_from_file(path, macro_ok);
-               if (!res)
-                       /*
-                        * There is no checked out .gitattributes file there, but
-                        * we might have it in the index.  We allow operation in a
-                        * sparsely checked out work tree, so read from it.
-                        */
+       } else if (!is_bare_repository()) {
+               if (direction == GIT_ATTR_CHECKOUT) {
                        res = read_attr_from_index(path, macro_ok);
+                       if (!res)
+                               res = read_attr_from_file(path, macro_ok);
+               } else if (direction == GIT_ATTR_CHECKIN) {
+                       res = read_attr_from_file(path, macro_ok);
+                       if (!res)
+                               /*
+                                * There is no checked out .gitattributes file
+                                * there, but we might have it in the index.
+                                * We allow operation in a sparsely checked out
+                                * work tree, so read from it.
+                                */
+                               res = read_attr_from_index(path, macro_ok);
+               }
        }
-       else
-               res = read_attr_from_index(path, macro_ok);
+
        if (!res)
                res = xcalloc(1, sizeof(*res));
        return res;
@@ -464,16 +803,7 @@ static void debug_set(const char *what, const char *match, struct git_attr *attr
 #define debug_push(a) do { ; } while (0)
 #define debug_pop(a) do { ; } while (0)
 #define debug_set(a,b,c,d) do { ; } while (0)
-#endif
-
-static void drop_attr_stack(void)
-{
-       while (attr_stack) {
-               struct attr_stack *elem = attr_stack;
-               attr_stack = elem->prev;
-               free_attr_elem(elem);
-       }
-}
+#endif /* DEBUG_ATTR */
 
 static const char *git_etc_gitattributes(void)
 {
@@ -483,6 +813,14 @@ static const char *git_etc_gitattributes(void)
        return system_wide;
 }
 
+static const char *get_home_gitattributes(void)
+{
+       if (!git_attributes_file)
+               git_attributes_file = xdg_config_home("attributes");
+
+       return git_attributes_file;
+}
+
 static int git_attr_system(void)
 {
        return !git_env_bool("GIT_ATTR_NOSYSTEM", 0);
@@ -490,64 +828,60 @@ static int git_attr_system(void)
 
 static GIT_PATH_FUNC(git_path_info_attributes, INFOATTRIBUTES_FILE)
 
-static void bootstrap_attr_stack(void)
+static void push_stack(struct attr_stack **attr_stack_p,
+                      struct attr_stack *elem, char *origin, size_t originlen)
 {
-       struct attr_stack *elem;
+       if (elem) {
+               elem->origin = origin;
+               if (origin)
+                       elem->originlen = originlen;
+               elem->prev = *attr_stack_p;
+               *attr_stack_p = elem;
+       }
+}
 
-       if (attr_stack)
+static void bootstrap_attr_stack(struct attr_stack **stack)
+{
+       struct attr_stack *e;
+
+       if (*stack)
                return;
 
-       elem = read_attr_from_array(builtin_attr);
-       elem->origin = NULL;
-       elem->prev = attr_stack;
-       attr_stack = elem;
+       /* builtin frame */
+       e = read_attr_from_array(builtin_attr);
+       push_stack(stack, e, NULL, 0);
 
+       /* system-wide frame */
        if (git_attr_system()) {
-               elem = read_attr_from_file(git_etc_gitattributes(), 1);
-               if (elem) {
-                       elem->origin = NULL;
-                       elem->prev = attr_stack;
-                       attr_stack = elem;
-               }
+               e = read_attr_from_file(git_etc_gitattributes(), 1);
+               push_stack(stack, e, NULL, 0);
        }
 
-       if (!git_attributes_file)
-               git_attributes_file = xdg_config_home("attributes");
-       if (git_attributes_file) {
-               elem = read_attr_from_file(git_attributes_file, 1);
-               if (elem) {
-                       elem->origin = NULL;
-                       elem->prev = attr_stack;
-                       attr_stack = elem;
-               }
+       /* home directory */
+       if (get_home_gitattributes()) {
+               e = read_attr_from_file(get_home_gitattributes(), 1);
+               push_stack(stack, e, NULL, 0);
        }
 
-       if (!is_bare_repository() || direction == GIT_ATTR_INDEX) {
-               elem = read_attr(GITATTRIBUTES_FILE, 1);
-               elem->origin = xstrdup("");
-               elem->originlen = 0;
-               elem->prev = attr_stack;
-               attr_stack = elem;
-               debug_push(elem);
-       }
+       /* root directory */
+       e = read_attr(GITATTRIBUTES_FILE, 1);
+       push_stack(stack, e, xstrdup(""), 0);
 
+       /* info frame */
        if (startup_info->have_repository)
-               elem = read_attr_from_file(git_path_info_attributes(), 1);
+               e = read_attr_from_file(git_path_info_attributes(), 1);
        else
-               elem = NULL;
-
-       if (!elem)
-               elem = xcalloc(1, sizeof(*elem));
-       elem->origin = NULL;
-       elem->prev = attr_stack;
-       attr_stack = elem;
+               e = NULL;
+       if (!e)
+               e = xcalloc(1, sizeof(struct attr_stack));
+       push_stack(stack, e, NULL, 0);
 }
 
-static void prepare_attr_stack(const char *path, int dirlen)
+static void prepare_attr_stack(const char *path, int dirlen,
+                              struct attr_stack **stack)
 {
-       struct attr_stack *elem, *info;
-       int len;
-       const char *cp;
+       struct attr_stack *info;
+       struct strbuf pathbuf = STRBUF_INIT;
 
        /*
         * At the bottom of the attribute stack is the built-in
@@ -564,13 +898,13 @@ static void prepare_attr_stack(const char *path, int dirlen)
         * .gitattributes in deeper directories to shallower ones,
         * and finally use the built-in set as the default.
         */
-       bootstrap_attr_stack();
+       bootstrap_attr_stack(stack);
 
        /*
         * Pop the "info" one that is always at the top of the stack.
         */
-       info = attr_stack;
-       attr_stack = info->prev;
+       info = *stack;
+       *stack = info->prev;
 
        /*
         * Pop the ones from directories that are not the prefix of
@@ -578,59 +912,63 @@ static void prepare_attr_stack(const char *path, int dirlen)
         * the root one (whose origin is an empty string "") or the builtin
         * one (whose origin is NULL) without popping it.
         */
-       while (attr_stack->origin) {
-               int namelen = strlen(attr_stack->origin);
+       while ((*stack)->origin) {
+               int namelen = (*stack)->originlen;
+               struct attr_stack *elem;
 
-               elem = attr_stack;
+               elem = *stack;
                if (namelen <= dirlen &&
                    !strncmp(elem->origin, path, namelen) &&
                    (!namelen || path[namelen] == '/'))
                        break;
 
                debug_pop(elem);
-               attr_stack = elem->prev;
-               free_attr_elem(elem);
+               *stack = elem->prev;
+               attr_stack_free(elem);
        }
 
        /*
-        * Read from parent directories and push them down
+        * bootstrap_attr_stack() should have added, and the
+        * above loop should have stopped before popping, the
+        * root element whose attr_stack->origin is set to an
+        * empty string.
         */
-       if (!is_bare_repository() || direction == GIT_ATTR_INDEX) {
-               /*
-                * bootstrap_attr_stack() should have added, and the
-                * above loop should have stopped before popping, the
-                * root element whose attr_stack->origin is set to an
-                * empty string.
-                */
-               struct strbuf pathbuf = STRBUF_INIT;
-
-               assert(attr_stack->origin);
-               while (1) {
-                       len = strlen(attr_stack->origin);
-                       if (dirlen <= len)
-                               break;
-                       cp = memchr(path + len + 1, '/', dirlen - len - 1);
-                       if (!cp)
-                               cp = path + dirlen;
-                       strbuf_add(&pathbuf, path, cp - path);
+       assert((*stack)->origin);
+
+       strbuf_addstr(&pathbuf, (*stack)->origin);
+       /* Build up to the directory 'path' is in */
+       while (pathbuf.len < dirlen) {
+               size_t len = pathbuf.len;
+               struct attr_stack *next;
+               char *origin;
+
+               /* Skip path-separator */
+               if (len < dirlen && is_dir_sep(path[len]))
+                       len++;
+               /* Find the end of the next component */
+               while (len < dirlen && !is_dir_sep(path[len]))
+                       len++;
+
+               if (pathbuf.len > 0)
                        strbuf_addch(&pathbuf, '/');
-                       strbuf_addstr(&pathbuf, GITATTRIBUTES_FILE);
-                       elem = read_attr(pathbuf.buf, 0);
-                       strbuf_setlen(&pathbuf, cp - path);
-                       elem->origin = strbuf_detach(&pathbuf, &elem->originlen);
-                       elem->prev = attr_stack;
-                       attr_stack = elem;
-                       debug_push(elem);
-               }
+               strbuf_add(&pathbuf, path + pathbuf.len, (len - pathbuf.len));
+               strbuf_addf(&pathbuf, "/%s", GITATTRIBUTES_FILE);
+
+               next = read_attr(pathbuf.buf, 0);
 
-               strbuf_release(&pathbuf);
+               /* reset the pathbuf to not include "/.gitattributes" */
+               strbuf_setlen(&pathbuf, len);
+
+               origin = xstrdup(pathbuf.buf);
+               push_stack(stack, next, origin, len);
        }
 
        /*
         * Finally push the "info" one at the top of the stack.
         */
-       info->prev = attr_stack;
-       attr_stack = info;
+       push_stack(stack, info, NULL, 0);
+
+       strbuf_release(&pathbuf);
 }
 
 static int path_matches(const char *pathname, int pathlen,
@@ -656,16 +994,16 @@ static int path_matches(const char *pathname, int pathlen,
                              pattern, prefix, pat->patternlen, pat->flags);
 }
 
-static int macroexpand_one(int attr_nr, int rem);
+static int macroexpand_one(struct all_attrs_item *all_attrs, int nr, int rem);
 
-static int fill_one(const char *what, struct match_attr *a, int rem)
+static int fill_one(const char *what, struct all_attrs_item *all_attrs,
+                   const struct match_attr *a, int rem)
 {
-       struct git_attr_check *check = check_all_attr;
        int i;
 
-       for (i = a->num_attr - 1; 0 < rem && 0 <= i; i--) {
-               struct git_attr *attr = a->state[i].attr;
-               const char **n = &(check[attr->attr_nr].value);
+       for (i = a->num_attr - 1; rem > 0 && i >= 0; i--) {
+               const struct git_attr *attr = a->state[i].attr;
+               const char **n = &(all_attrs[attr->attr_nr].value);
                const char *v = a->state[i].setto;
 
                if (*n == ATTR__UNKNOWN) {
@@ -674,64 +1012,72 @@ static int fill_one(const char *what, struct match_attr *a, int rem)
                                  attr, v);
                        *n = v;
                        rem--;
-                       rem = macroexpand_one(attr->attr_nr, rem);
+                       rem = macroexpand_one(all_attrs, attr->attr_nr, rem);
                }
        }
        return rem;
 }
 
 static int fill(const char *path, int pathlen, int basename_offset,
-               struct attr_stack *stk, int rem)
+               const struct attr_stack *stack,
+               struct all_attrs_item *all_attrs, int rem)
 {
-       int i;
-       const char *base = stk->origin ? stk->origin : "";
+       for (; rem > 0 && stack; stack = stack->prev) {
+               int i;
+               const char *base = stack->origin ? stack->origin : "";
 
-       for (i = stk->num_matches - 1; 0 < rem && 0 <= i; i--) {
-               struct match_attr *a = stk->attrs[i];
-               if (a->is_macro)
-                       continue;
-               if (path_matches(path, pathlen, basename_offset,
-                                &a->u.pat, base, stk->originlen))
-                       rem = fill_one("fill", a, rem);
+               for (i = stack->num_matches - 1; 0 < rem && 0 <= i; i--) {
+                       const struct match_attr *a = stack->attrs[i];
+                       if (a->is_macro)
+                               continue;
+                       if (path_matches(path, pathlen, basename_offset,
+                                        &a->u.pat, base, stack->originlen))
+                               rem = fill_one("fill", all_attrs, a, rem);
+               }
        }
+
        return rem;
 }
 
-static int macroexpand_one(int nr, int rem)
+static int macroexpand_one(struct all_attrs_item *all_attrs, int nr, int rem)
 {
-       struct attr_stack *stk;
-       struct match_attr *a = NULL;
-       int i;
+       const struct all_attrs_item *item = &all_attrs[nr];
 
-       if (check_all_attr[nr].value != ATTR__TRUE ||
-           !check_all_attr[nr].attr->maybe_macro)
+       if (item->macro && item->value == ATTR__TRUE)
+               return fill_one("expand", all_attrs, item->macro, rem);
+       else
                return rem;
+}
 
-       for (stk = attr_stack; !a && stk; stk = stk->prev)
-               for (i = stk->num_matches - 1; !a && 0 <= i; i--) {
-                       struct match_attr *ma = stk->attrs[i];
-                       if (!ma->is_macro)
-                               continue;
-                       if (ma->u.attr->attr_nr == nr)
-                               a = ma;
+/*
+ * Marks the attributes which are macros based on the attribute stack.
+ * This prevents having to search through the attribute stack each time
+ * a macro needs to be expanded during the fill stage.
+ */
+static void determine_macros(struct all_attrs_item *all_attrs,
+                            const struct attr_stack *stack)
+{
+       for (; stack; stack = stack->prev) {
+               int i;
+               for (i = stack->num_matches - 1; i >= 0; i--) {
+                       const struct match_attr *ma = stack->attrs[i];
+                       if (ma->is_macro) {
+                               int n = ma->u.attr->attr_nr;
+                               if (!all_attrs[n].macro) {
+                                       all_attrs[n].macro = ma;
+                               }
+                       }
                }
-
-       if (a)
-               rem = fill_one("expand", a, rem);
-
-       return rem;
+       }
 }
 
 /*
- * Collect attributes for path into the array pointed to by
- * check_all_attr. If num is non-zero, only attributes in check[] are
- * collected. Otherwise all attributes are collected.
+ * Collect attributes for path into the array pointed to by check->all_attrs.
+ * If check->check_nr is non-zero, only attributes in check[] are collected.
+ * Otherwise all attributes are collected.
  */
-static void collect_some_attrs(const char *path, int num,
-                              struct git_attr_check *check)
-
+static void collect_some_attrs(const char *path, struct attr_check *check)
 {
-       struct attr_stack *stk;
        int i, pathlen, rem, dirlen;
        const char *cp, *last_slash = NULL;
        int basename_offset;
@@ -749,81 +1095,67 @@ static void collect_some_attrs(const char *path, int num,
                dirlen = 0;
        }
 
-       prepare_attr_stack(path, dirlen);
-       for (i = 0; i < attr_nr; i++)
-               check_all_attr[i].value = ATTR__UNKNOWN;
-       if (num && !cannot_trust_maybe_real) {
+       prepare_attr_stack(path, dirlen, &check->stack);
+       all_attrs_init(&g_attr_hashmap, check);
+       determine_macros(check->all_attrs, check->stack);
+
+       if (check->nr) {
                rem = 0;
-               for (i = 0; i < num; i++) {
-                       if (!check[i].attr->maybe_real) {
-                               struct git_attr_check *c;
-                               c = check_all_attr + check[i].attr->attr_nr;
-                               c->value = ATTR__UNSET;
+               for (i = 0; i < check->nr; i++) {
+                       int n = check->items[i].attr->attr_nr;
+                       struct all_attrs_item *item = &check->all_attrs[n];
+                       if (item->macro) {
+                               item->value = ATTR__UNSET;
                                rem++;
                        }
                }
-               if (rem == num)
+               if (rem == check->nr)
                        return;
        }
 
-       rem = attr_nr;
-       for (stk = attr_stack; 0 < rem && stk; stk = stk->prev)
-               rem = fill(path, pathlen, basename_offset, stk, rem);
+       rem = check->all_attrs_nr;
+       fill(path, pathlen, basename_offset, check->stack, check->all_attrs, rem);
 }
 
-int git_check_attr(const char *path, int num, struct git_attr_check *check)
+int git_check_attr(const char *path, struct attr_check *check)
 {
        int i;
 
-       collect_some_attrs(path, num, check);
+       collect_some_attrs(path, check);
 
-       for (i = 0; i < num; i++) {
-               const char *value = check_all_attr[check[i].attr->attr_nr].value;
+       for (i = 0; i < check->nr; i++) {
+               size_t n = check->items[i].attr->attr_nr;
+               const char *value = check->all_attrs[n].value;
                if (value == ATTR__UNKNOWN)
                        value = ATTR__UNSET;
-               check[i].value = value;
+               check->items[i].value = value;
        }
 
        return 0;
 }
 
-int git_all_attrs(const char *path, int *num, struct git_attr_check **check)
+void git_all_attrs(const char *path, struct attr_check *check)
 {
-       int i, count, j;
+       int i;
 
-       collect_some_attrs(path, 0, NULL);
+       attr_check_reset(check);
+       collect_some_attrs(path, check);
 
-       /* Count the number of attributes that are set. */
-       count = 0;
-       for (i = 0; i < attr_nr; i++) {
-               const char *value = check_all_attr[i].value;
-               if (value != ATTR__UNSET && value != ATTR__UNKNOWN)
-                       ++count;
-       }
-       *num = count;
-       ALLOC_ARRAY(*check, count);
-       j = 0;
-       for (i = 0; i < attr_nr; i++) {
-               const char *value = check_all_attr[i].value;
-               if (value != ATTR__UNSET && value != ATTR__UNKNOWN) {
-                       (*check)[j].attr = check_all_attr[i].attr;
-                       (*check)[j].value = value;
-                       ++j;
-               }
+       for (i = 0; i < check->all_attrs_nr; i++) {
+               const char *name = check->all_attrs[i].attr->name;
+               const char *value = check->all_attrs[i].value;
+               struct attr_check_item *item;
+               if (value == ATTR__UNSET || value == ATTR__UNKNOWN)
+                       continue;
+               item = attr_check_append(check, git_attr(name));
+               item->value = value;
        }
-
-       return 0;
 }
 
-void git_attr_set_direction(enum git_attr_direction new, struct index_state *istate)
+void attr_start(void)
 {
-       enum git_attr_direction old = direction;
-
-       if (is_bare_repository() && new != GIT_ATTR_INDEX)
-               die("BUG: non-INDEX attr direction in a bare repo");
-
-       direction = new;
-       if (new != old)
-               drop_attr_stack();
-       use_index = istate;
+#ifndef NO_PTHREADS
+       pthread_mutex_init(&g_attr_hashmap.mutex, NULL);
+       pthread_mutex_init(&check_vector.mutex, NULL);
+#endif
 }
diff --git a/attr.h b/attr.h
index 8b08d33af84ebbb376a69d85f3db6c03eeb78a63..48ab3e1c2f1c3eaf11e32f345fc833fc160c5b02 100644 (file)
--- a/attr.h
+++ b/attr.h
@@ -4,11 +4,15 @@
 /* An attribute is a pointer to this opaque structure */
 struct git_attr;
 
+/* opaque structures used internally for attribute collection */
+struct all_attrs_item;
+struct attr_stack;
+
 /*
  * Given a string, return the gitattribute object that
  * corresponds to it.
  */
-struct git_attr *git_attr(const char *);
+const struct git_attr *git_attr(const char *);
 
 /* Internal use */
 extern const char git_attr__true[];
@@ -20,38 +24,57 @@ extern const char git_attr__false[];
 #define ATTR_UNSET(v) ((v) == NULL)
 
 /*
- * Send one or more git_attr_check to git_check_attr(), and
+ * Send one or more git_attr_check to git_check_attrs(), and
  * each 'value' member tells what its value is.
  * Unset one is returned as NULL.
  */
-struct git_attr_check {
-       struct git_attr *attr;
+struct attr_check_item {
+       const struct git_attr *attr;
        const char *value;
 };
 
+struct attr_check {
+       int nr;
+       int alloc;
+       struct attr_check_item *items;
+       int all_attrs_nr;
+       struct all_attrs_item *all_attrs;
+       struct attr_stack *stack;
+};
+
+extern struct attr_check *attr_check_alloc(void);
+extern struct attr_check *attr_check_initl(const char *, ...);
+
+extern struct attr_check_item *attr_check_append(struct attr_check *check,
+                                                const struct git_attr *attr);
+
+extern void attr_check_reset(struct attr_check *check);
+extern void attr_check_clear(struct attr_check *check);
+extern void attr_check_free(struct attr_check *check);
+
 /*
  * Return the name of the attribute represented by the argument.  The
  * return value is a pointer to a null-delimited string that is part
  * of the internal data structure; it should not be modified or freed.
  */
-char *git_attr_name(struct git_attr *);
+extern const char *git_attr_name(const struct git_attr *);
 
-int git_check_attr(const char *path, int, struct git_attr_check *);
+extern int git_check_attr(const char *path, struct attr_check *check);
 
 /*
- * Retrieve all attributes that apply to the specified path.  *num
- * will be set to the number of attributes on the path; **check will
- * be set to point at a newly-allocated array of git_attr_check
- * objects describing the attributes and their values.  *check must be
- * free()ed by the caller.
+ * Retrieve all attributes that apply to the specified path.
+ * check holds the attributes and their values.
  */
-int git_all_attrs(const char *path, int *num, struct git_attr_check **check);
+extern void git_all_attrs(const char *path, struct attr_check *check);
 
 enum git_attr_direction {
        GIT_ATTR_CHECKIN,
        GIT_ATTR_CHECKOUT,
        GIT_ATTR_INDEX
 };
-void git_attr_set_direction(enum git_attr_direction, struct index_state *);
+void git_attr_set_direction(enum git_attr_direction new_direction,
+                           struct index_state *istate);
+
+extern void attr_start(void);
 
 #endif /* ATTR_H */
index 53a5a18c1681f5f13f6ddd4fd4d7d30c0ea14ce6..4d01ca0c8ba0ba2a1d2c209b766e19d9ca9c8ac0 100644 (file)
@@ -24,12 +24,13 @@ static const struct option check_attr_options[] = {
        OPT_END()
 };
 
-static void output_attr(int cnt, struct git_attr_check *check,
-       const char *file)
+static void output_attr(struct attr_check *check, const char *file)
 {
        int j;
+       int cnt = check->nr;
+
        for (j = 0; j < cnt; j++) {
-               const char *value = check[j].value;
+               const char *value = check->items[j].value;
 
                if (ATTR_TRUE(value))
                        value = "set";
@@ -42,35 +43,38 @@ static void output_attr(int cnt, struct git_attr_check *check,
                        printf("%s%c" /* path */
                               "%s%c" /* attrname */
                               "%s%c" /* attrvalue */,
-                              file, 0, git_attr_name(check[j].attr), 0, value, 0);
+                              file, 0,
+                              git_attr_name(check->items[j].attr), 0, value, 0);
                } else {
                        quote_c_style(file, NULL, stdout, 0);
-                       printf(": %s: %s\n", git_attr_name(check[j].attr), value);
+                       printf(": %s: %s\n",
+                              git_attr_name(check->items[j].attr), value);
                }
-
        }
 }
 
-static void check_attr(const char *prefix, int cnt,
-       struct git_attr_check *check, const char *file)
+static void check_attr(const char *prefix,
+                      struct attr_check *check,
+                      int collect_all,
+                      const char *file)
 {
        char *full_path =
                prefix_path(prefix, prefix ? strlen(prefix) : 0, file);
-       if (check != NULL) {
-               if (git_check_attr(full_path, cnt, check))
-                       die("git_check_attr died");
-               output_attr(cnt, check, file);
+
+       if (collect_all) {
+               git_all_attrs(full_path, check);
        } else {
-               if (git_all_attrs(full_path, &cnt, &check))
-                       die("git_all_attrs died");
-               output_attr(cnt, check, file);
-               free(check);
+               if (git_check_attr(full_path, check))
+                       die("git_check_attr died");
        }
+       output_attr(check, file);
+
        free(full_path);
 }
 
-static void check_attr_stdin_paths(const char *prefix, int cnt,
-       struct git_attr_check *check)
+static void check_attr_stdin_paths(const char *prefix,
+                                  struct attr_check *check,
+                                  int collect_all)
 {
        struct strbuf buf = STRBUF_INIT;
        struct strbuf unquoted = STRBUF_INIT;
@@ -84,7 +88,7 @@ static void check_attr_stdin_paths(const char *prefix, int cnt,
                                die("line is badly quoted");
                        strbuf_swap(&buf, &unquoted);
                }
-               check_attr(prefix, cnt, check, buf.buf);
+               check_attr(prefix, check, collect_all, buf.buf);
                maybe_flush_or_die(stdout, "attribute to stdout");
        }
        strbuf_release(&buf);
@@ -99,7 +103,7 @@ static NORETURN void error_with_usage(const char *msg)
 
 int cmd_check_attr(int argc, const char **argv, const char *prefix)
 {
-       struct git_attr_check *check;
+       struct attr_check *check;
        int cnt, i, doubledash, filei;
 
        if (!is_bare_repository())
@@ -159,28 +163,26 @@ int cmd_check_attr(int argc, const char **argv, const char *prefix)
                        error_with_usage("No file specified");
        }
 
-       if (all_attrs) {
-               check = NULL;
-       } else {
-               check = xcalloc(cnt, sizeof(*check));
+       check = attr_check_alloc();
+       if (!all_attrs) {
                for (i = 0; i < cnt; i++) {
-                       const char *name;
-                       struct git_attr *a;
-                       name = argv[i];
-                       a = git_attr(name);
+                       const struct git_attr *a = git_attr(argv[i]);
+
                        if (!a)
                                return error("%s: not a valid attribute name",
-                                       name);
-                       check[i].attr = a;
+                                            argv[i]);
+                       attr_check_append(check, a);
                }
        }
 
        if (stdin_paths)
-               check_attr_stdin_paths(prefix, cnt, check);
+               check_attr_stdin_paths(prefix, check, all_attrs);
        else {
                for (i = filei; i < argc; i++)
-                       check_attr(prefix, cnt, check, argv[i]);
+                       check_attr(prefix, check, all_attrs, argv[i]);
                maybe_flush_or_die(stdout, "attribute to stdout");
        }
+
+       attr_check_free(check);
        return 0;
 }
index c7af4754857f373df37d2f15dcd6c4e4384ec570..f294dcffa90aa3d8c916e448a4de905fa6363d26 100644 (file)
@@ -894,24 +894,15 @@ static void write_pack_file(void)
                        written, nr_result);
 }
 
-static void setup_delta_attr_check(struct git_attr_check *check)
-{
-       static struct git_attr *attr_delta;
-
-       if (!attr_delta)
-               attr_delta = git_attr("delta");
-
-       check[0].attr = attr_delta;
-}
-
 static int no_try_delta(const char *path)
 {
-       struct git_attr_check check[1];
+       static struct attr_check *check;
 
-       setup_delta_attr_check(check);
-       if (git_check_attr(path, ARRAY_SIZE(check), check))
+       if (!check)
+               check = attr_check_initl("delta", NULL);
+       if (git_check_attr(path, check))
                return 0;
-       if (ATTR_FALSE(check->value))
+       if (ATTR_FALSE(check->items[0].value))
                return 1;
        return 0;
 }
index 2cf85158b4899b664a3cbae8d0777f5a9e473318..0c4ee3de4228384d752a429f85b739864c26f457 100644 (file)
--- a/commit.c
+++ b/commit.c
@@ -415,8 +415,7 @@ int find_commit_subject(const char *commit_buffer, const char **subject)
                p++;
        if (*p) {
                p = skip_blank_lines(p + 2);
-               for (eol = p; *eol && *eol != '\n'; eol++)
-                       ; /* do nothing */
+               eol = strchrnul(p, '\n');
        } else
                eol = p;
 
index c654f95551c33d6b346ae7fe43a6cac987dd6b75..6a689007e7ce3fe08f148e8b82c0a1c618c513a5 100644 (file)
@@ -1,5 +1,6 @@
 #include "cache.h"
 #include "exec_cmd.h"
+#include "attr.h"
 
 /*
  * Many parts of Git have subprograms communicate via pipe, expect the
@@ -33,6 +34,8 @@ int main(int argc, const char **argv)
 
        git_setup_gettext();
 
+       attr_start();
+
        git_extract_argv0_path(argv[0]);
 
        restore_sigpipe_to_default();
index 4e17e45ed265b3f4861d1b7a1e86edaa4df3b1ac..8d652bf27c9444d3696c4b942dc85f062e2bf53e 100644 (file)
--- a/convert.c
+++ b/convert.c
@@ -1028,7 +1028,7 @@ static int ident_to_worktree(const char *path, const char *src, size_t len,
        return 1;
 }
 
-static enum crlf_action git_path_check_crlf(struct git_attr_check *check)
+static enum crlf_action git_path_check_crlf(struct attr_check_item *check)
 {
        const char *value = check->value;
 
@@ -1045,7 +1045,7 @@ static enum crlf_action git_path_check_crlf(struct git_attr_check *check)
        return CRLF_UNDEFINED;
 }
 
-static enum eol git_path_check_eol(struct git_attr_check *check)
+static enum eol git_path_check_eol(struct attr_check_item *check)
 {
        const char *value = check->value;
 
@@ -1058,7 +1058,7 @@ static enum eol git_path_check_eol(struct git_attr_check *check)
        return EOL_UNSET;
 }
 
-static struct convert_driver *git_path_check_convert(struct git_attr_check *check)
+static struct convert_driver *git_path_check_convert(struct attr_check_item *check)
 {
        const char *value = check->value;
        struct convert_driver *drv;
@@ -1071,7 +1071,7 @@ static struct convert_driver *git_path_check_convert(struct git_attr_check *chec
        return NULL;
 }
 
-static int git_path_check_ident(struct git_attr_check *check)
+static int git_path_check_ident(struct attr_check_item *check)
 {
        const char *value = check->value;
 
@@ -1085,24 +1085,19 @@ struct conv_attrs {
        int ident;
 };
 
-static const char *conv_attr_name[] = {
-       "crlf", "ident", "filter", "eol", "text",
-};
-#define NUM_CONV_ATTRS ARRAY_SIZE(conv_attr_name)
-
 static void convert_attrs(struct conv_attrs *ca, const char *path)
 {
-       int i;
-       static struct git_attr_check ccheck[NUM_CONV_ATTRS];
+       static struct attr_check *check;
 
-       if (!ccheck[0].attr) {
-               for (i = 0; i < NUM_CONV_ATTRS; i++)
-                       ccheck[i].attr = git_attr(conv_attr_name[i]);
+       if (!check) {
+               check = attr_check_initl("crlf", "ident", "filter",
+                                        "eol", "text", NULL);
                user_convert_tail = &user_convert;
                git_config(read_convert_config, NULL);
        }
 
-       if (!git_check_attr(path, NUM_CONV_ATTRS, ccheck)) {
+       if (!git_check_attr(path, check)) {
+               struct attr_check_item *ccheck = check->items;
                ca->crlf_action = git_path_check_crlf(ccheck + 4);
                if (ca->crlf_action == CRLF_UNDEFINED)
                        ca->crlf_action = git_path_check_crlf(ccheck + 0);
index ad8be42f912b456492ca1704ce067fea4713401a..ac0d4a5d78d956f09aa0082b286babc36f04b593 100644 (file)
@@ -336,15 +336,6 @@ static const struct ll_merge_driver *find_ll_merge_driver(const char *merge_attr
        return &ll_merge_drv[LL_TEXT_MERGE];
 }
 
-static int git_path_check_merge(const char *path, struct git_attr_check check[2])
-{
-       if (!check[0].attr) {
-               check[0].attr = git_attr("merge");
-               check[1].attr = git_attr("conflict-marker-size");
-       }
-       return git_check_attr(path, 2, check);
-}
-
 static void normalize_file(mmfile_t *mm, const char *path)
 {
        struct strbuf strbuf = STRBUF_INIT;
@@ -362,7 +353,7 @@ int ll_merge(mmbuffer_t *result_buf,
             mmfile_t *theirs, const char *their_label,
             const struct ll_merge_options *opts)
 {
-       static struct git_attr_check check[2];
+       static struct attr_check *check;
        static const struct ll_merge_options default_opts;
        const char *ll_driver_name = NULL;
        int marker_size = DEFAULT_CONFLICT_MARKER_SIZE;
@@ -376,10 +367,14 @@ int ll_merge(mmbuffer_t *result_buf,
                normalize_file(ours, path);
                normalize_file(theirs, path);
        }
-       if (!git_path_check_merge(path, check)) {
-               ll_driver_name = check[0].value;
-               if (check[1].value) {
-                       marker_size = atoi(check[1].value);
+
+       if (!check)
+               check = attr_check_initl("merge", "conflict-marker-size", NULL);
+
+       if (!git_check_attr(path, check)) {
+               ll_driver_name = check->items[0].value;
+               if (check->items[1].value) {
+                       marker_size = atoi(check->items[1].value);
                        if (marker_size <= 0)
                                marker_size = DEFAULT_CONFLICT_MARKER_SIZE;
                }
@@ -398,13 +393,13 @@ int ll_merge(mmbuffer_t *result_buf,
 
 int ll_merge_marker_size(const char *path)
 {
-       static struct git_attr_check check;
+       static struct attr_check *check;
        int marker_size = DEFAULT_CONFLICT_MARKER_SIZE;
 
-       if (!check.attr)
-               check.attr = git_attr("conflict-marker-size");
-       if (!git_check_attr(path, 1, &check) && check.value) {
-               marker_size = atoi(check.value);
+       if (!check)
+               check = attr_check_initl("conflict-marker-size", NULL);
+       if (!git_check_attr(path, check) && check->items[0].value) {
+               marker_size = atoi(check->items[0].value);
                if (marker_size <= 0)
                        marker_size = DEFAULT_CONFLICT_MARKER_SIZE;
        }
index f0fbb425545019a6275a347a5826f6ba2977d394..f19ae4f8ccddacee84e04fa50663672f5f6174c1 100755 (executable)
@@ -13,10 +13,31 @@ attr_check () {
        test_line_count = 0 err
 }
 
+attr_check_quote () {
+
+       path="$1"
+       quoted_path="$2"
+       expect="$3"
+
+       git check-attr test -- "$path" >actual &&
+       echo "\"$quoted_path\": test: $expect" >expect &&
+       test_cmp expect actual
+
+}
+
+test_expect_success 'open-quoted pathname' '
+       echo "\"a test=a" >.gitattributes &&
+       test_must_fail attr_check a a
+'
+
+
 test_expect_success 'setup' '
        mkdir -p a/b/d a/c b &&
        (
                echo "[attr]notest !test"
+               echo "\" d \"   test=d"
+               echo " e        test=e"
+               echo " e\"      test=e"
                echo "f test=f"
                echo "a/i test=a/i"
                echo "onoff test -test"
@@ -69,6 +90,11 @@ test_expect_success 'command line checks' '
 '
 
 test_expect_success 'attribute test' '
+
+       attr_check " d " d &&
+       attr_check e e &&
+       attr_check_quote e\" e\\\" e &&
+
        attr_check f f &&
        attr_check a/f f &&
        attr_check a/c/f f &&
index 2125d6da26dbdc01f7397415ef1f67616e6f2a71..8b732e40bce4968257c2918219f7dfdb0b9ed3a0 100644 (file)
@@ -262,25 +262,22 @@ struct userdiff_driver *userdiff_find_by_name(const char *name) {
 
 struct userdiff_driver *userdiff_find_by_path(const char *path)
 {
-       static struct git_attr *attr;
-       struct git_attr_check check;
-
-       if (!attr)
-               attr = git_attr("diff");
-       check.attr = attr;
+       static struct attr_check *check;
 
+       if (!check)
+               check = attr_check_initl("diff", NULL);
        if (!path)
                return NULL;
-       if (git_check_attr(path, 1, &check))
+       if (git_check_attr(path, check))
                return NULL;
 
-       if (ATTR_TRUE(check.value))
+       if (ATTR_TRUE(check->items[0].value))
                return &driver_true;
-       if (ATTR_FALSE(check.value))
+       if (ATTR_FALSE(check->items[0].value))
                return &driver_false;
-       if (ATTR_UNSET(check.value))
+       if (ATTR_UNSET(check->items[0].value))
                return NULL;
-       return userdiff_find_by_name(check.value);
+       return userdiff_find_by_name(check->items[0].value);
 }
 
 struct userdiff_driver *userdiff_get_textconv(struct userdiff_driver *driver)
diff --git a/ws.c b/ws.c
index ea4b2b1dfd64ce41f4752a93f4d5478c4e53deda..a07caedd5a565bbf29e4f9b3036ac60e6e9f716e 100644 (file)
--- a/ws.c
+++ b/ws.c
@@ -71,24 +71,17 @@ unsigned parse_whitespace_rule(const char *string)
        return rule;
 }
 
-static void setup_whitespace_attr_check(struct git_attr_check *check)
-{
-       static struct git_attr *attr_whitespace;
-
-       if (!attr_whitespace)
-               attr_whitespace = git_attr("whitespace");
-       check[0].attr = attr_whitespace;
-}
-
 unsigned whitespace_rule(const char *pathname)
 {
-       struct git_attr_check attr_whitespace_rule;
+       static struct attr_check *attr_whitespace_rule;
+
+       if (!attr_whitespace_rule)
+               attr_whitespace_rule = attr_check_initl("whitespace", NULL);
 
-       setup_whitespace_attr_check(&attr_whitespace_rule);
-       if (!git_check_attr(pathname, 1, &attr_whitespace_rule)) {
+       if (!git_check_attr(pathname, attr_whitespace_rule)) {
                const char *value;
 
-               value = attr_whitespace_rule.value;
+               value = attr_whitespace_rule->items[0].value;
                if (ATTR_TRUE(value)) {
                        /* true (whitespace) */
                        unsigned all_rule = ws_tab_width(whitespace_rule_cfg);