t0302 & t3900: add forgotten quotes
[gitweb.git] / attr.c
diff --git a/attr.c b/attr.c
index 69643ae77d91b2b5a7a5092f04d71e52481a9b4f..7e2134471cb4afc1b891208ebe4bebd6ad55571d 100644 (file)
--- a/attr.c
+++ b/attr.c
@@ -445,17 +445,16 @@ static struct match_attr *parse_attr_line(const char *line, const char *src,
  * .gitignore file and info/excludes file as a fallback.
  */
 
-/* NEEDSWORK: This will become per git_attr_check */
-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);
@@ -478,9 +477,96 @@ 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)
 {
-       return xcalloc(1, sizeof(struct attr_check));
+       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, ...)
@@ -517,6 +603,23 @@ struct attr_check *attr_check_initl(const char *one, ...)
        return check;
 }
 
+struct attr_check *attr_check_dup(const struct attr_check *check)
+{
+       struct attr_check *ret;
+
+       if (!check)
+               return NULL;
+
+       ret = attr_check_alloc();
+
+       ret->nr = check->nr;
+       ret->alloc = check->alloc;
+       ALLOC_ARRAY(ret->items, ret->nr);
+       COPY_ARRAY(ret->items, check->items, ret->nr);
+
+       return ret;
+}
+
 struct attr_check_item *attr_check_append(struct attr_check *check,
                                          const struct git_attr *attr)
 {
@@ -543,12 +646,19 @@ void attr_check_clear(struct attr_check *check)
        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)
 {
-       attr_check_clear(check);
-       free(check);
+       if (check) {
+               /* Remove check from the check vector */
+               check_vector_remove(check);
+
+               attr_check_clear(check);
+               free(check);
+       }
 }
 
 static const char *builtin_attr[] = {
@@ -584,26 +694,30 @@ static struct attr_stack *read_attr_from_array(const char **list)
 }
 
 /*
- * NEEDSWORK: these two are tricky.  The callers assume there is a
- * single, system-wide global state "where we read attributes from?"
- * and when the state is flipped by calling git_attr_set_direction(),
- * attr_stack is discarded so that subsequent attr_check will lazily
- * read from the right place.  And they do not know or care who called
- * by them uses the attribute subsystem, hence have no knowledge of
- * existing git_attr_check instances or future ones that will be
- * created).
- *
- * Probably we need a thread_local that holds these two variables,
- * and a list of git_attr_check instances (which need to be maintained
- * by hooking into git_attr_check_alloc(), git_attr_check_initl(), and
- * git_attr_check_clear().  Then git_attr_set_direction() updates the
- * fields in that thread_local for these two variables, iterate over
- * all the active git_attr_check instances and discard the attr_stack
- * they hold.  Yuck, but it sounds doable.
+ * 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");
@@ -654,25 +768,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;
@@ -705,15 +822,6 @@ static void debug_set(const char *what, const char *match, struct git_attr *attr
 #define debug_set(a,b,c,d) do { ; } while (0)
 #endif /* DEBUG_ATTR */
 
-static void drop_attr_stack(void)
-{
-       while (attr_stack) {
-               struct attr_stack *elem = attr_stack;
-               attr_stack = elem->prev;
-               free_attr_elem(elem);
-       }
-}
-
 static const char *git_etc_gitattributes(void)
 {
        static const char *system_wide;
@@ -722,6 +830,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);
@@ -741,47 +857,48 @@ static void push_stack(struct attr_stack **attr_stack_p,
        }
 }
 
-static void bootstrap_attr_stack(void)
+static void bootstrap_attr_stack(struct attr_stack **stack)
 {
-       struct attr_stack *elem;
+       struct attr_stack *e;
 
-       if (attr_stack)
+       if (*stack)
                return;
 
-       push_stack(&attr_stack, read_attr_from_array(builtin_attr), NULL, 0);
+       /* builtin frame */
+       e = read_attr_from_array(builtin_attr);
+       push_stack(stack, e, NULL, 0);
 
-       if (git_attr_system())
-               push_stack(&attr_stack,
-                          read_attr_from_file(git_etc_gitattributes(), 1),
-                          NULL, 0);
+       /* system-wide frame */
+       if (git_attr_system()) {
+               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)
-               push_stack(&attr_stack,
-                          read_attr_from_file(git_attributes_file, 1),
-                          NULL, 0);
-
-       if (!is_bare_repository() || direction == GIT_ATTR_INDEX) {
-               elem = read_attr(GITATTRIBUTES_FILE, 1);
-               push_stack(&attr_stack, elem, xstrdup(""), 0);
-               debug_push(elem);
+       /* home directory */
+       if (get_home_gitattributes()) {
+               e = read_attr_from_file(get_home_gitattributes(), 1);
+               push_stack(stack, e, NULL, 0);
        }
 
+       /* 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));
-       push_stack(&attr_stack, elem, NULL, 0);
+               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;
-       const char *cp;
+       struct attr_stack *info;
+       struct strbuf pathbuf = STRBUF_INIT;
 
        /*
         * At the bottom of the attribute stack is the built-in
@@ -798,13 +915,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
@@ -812,59 +929,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) {
-                       size_t len = strlen(attr_stack->origin);
-                       char *origin;
-
-                       if (dirlen <= len)
-                               break;
-                       cp = memchr(path + len + 1, '/', dirlen - len - 1);
-                       if (!cp)
-                               cp = path + dirlen;
-                       strbuf_addf(&pathbuf,
-                                   "%.*s/%s", (int)(cp - path), path,
-                                   GITATTRIBUTES_FILE);
-                       elem = read_attr(pathbuf.buf, 0);
-                       strbuf_setlen(&pathbuf, cp - path);
-                       origin = strbuf_detach(&pathbuf, &len);
-                       push_stack(&attr_stack, elem, origin, len);
-                       debug_push(elem);
-               }
-
-               strbuf_release(&pathbuf);
+       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_add(&pathbuf, path + pathbuf.len, (len - pathbuf.len));
+               strbuf_addf(&pathbuf, "/%s", GITATTRIBUTES_FILE);
+
+               next = read_attr(pathbuf.buf, 0);
+
+               /* 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.
         */
-       push_stack(&attr_stack, info, NULL, 0);
+       push_stack(stack, info, NULL, 0);
+
+       strbuf_release(&pathbuf);
 }
 
 static int path_matches(const char *pathname, int pathlen,
@@ -915,20 +1036,23 @@ static int fill_one(const char *what, struct all_attrs_item *all_attrs,
 }
 
 static int fill(const char *path, int pathlen, int basename_offset,
-               struct attr_stack *stk, struct all_attrs_item *all_attrs,
-               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 (i = stk->num_matches - 1; 0 < rem && 0 <= i; i--) {
-               const 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", all_attrs, a, rem);
+       for (; rem > 0 && stack; stack = stack->prev) {
+               int i;
+               const char *base = stack->origin ? stack->origin : "";
+
+               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;
 }
 
@@ -971,7 +1095,6 @@ static void determine_macros(struct all_attrs_item *all_attrs,
  */
 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;
@@ -989,9 +1112,9 @@ static void collect_some_attrs(const char *path, struct attr_check *check)
                dirlen = 0;
        }
 
-       prepare_attr_stack(path, dirlen);
+       prepare_attr_stack(path, dirlen, &check->stack);
        all_attrs_init(&g_attr_hashmap, check);
-       determine_macros(check->all_attrs, attr_stack);
+       determine_macros(check->all_attrs, check->stack);
 
        if (check->nr) {
                rem = 0;
@@ -1008,8 +1131,7 @@ static void collect_some_attrs(const char *path, struct attr_check *check)
        }
 
        rem = check->all_attrs_nr;
-       for (stk = attr_stack; 0 < rem && stk; stk = stk->prev)
-               rem = fill(path, pathlen, basename_offset, stk, check->all_attrs, rem);
+       fill(path, pathlen, basename_offset, check->stack, check->all_attrs, rem);
 }
 
 int git_check_attr(const char *path, struct attr_check *check)
@@ -1047,22 +1169,10 @@ void git_all_attrs(const char *path, struct attr_check *check)
        }
 }
 
-void git_attr_set_direction(enum git_attr_direction new, struct index_state *istate)
-{
-       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;
-}
-
 void attr_start(void)
 {
 #ifndef NO_PTHREADS
        pthread_mutex_init(&g_attr_hashmap.mutex, NULL);
+       pthread_mutex_init(&check_vector.mutex, NULL);
 #endif
 }