attr.c: add push_stack() helper
[gitweb.git] / attr.c
diff --git a/attr.c b/attr.c
index 007f1a29954b590c5c3f046006e40d88b731aaa4..8026d68bd1722c4900616592d464e3e248c22d75 100644 (file)
--- a/attr.c
+++ b/attr.c
@@ -13,6 +13,7 @@
 #include "attr.h"
 #include "dir.h"
 #include "utf8.h"
+#include "quote.h"
 
 const char git_attr__true[] = "(builtin)true";
 const char git_attr__false[] = "\0(builtin)false";
@@ -43,7 +44,7 @@ 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;
 }
@@ -183,6 +184,12 @@ static const char *parse_attr(const char *src, int lineno, const char *cp,
                        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 invalid_attr_name()
+                * check here.
+                */
                if (*cp == '-' || *cp == '!') {
                        e->setto = (*cp == '-') ? ATTR__FALSE : ATTR__UNSET;
                        cp++;
@@ -206,18 +213,27 @@ 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);
@@ -227,20 +243,19 @@ static struct match_attr *parse_attr_line(const char *line, const char *src,
                        fprintf(stderr,
                                "%.*s is not a valid attribute name: %s:%d\n",
                                namelen, name, src, lineno);
-                       return NULL;
+                       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,
@@ -261,7 +276,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;
@@ -276,7 +291,13 @@ static struct match_attr *parse_attr_line(const char *line, const char *src,
                        cannot_trust_maybe_real = 1;
        }
 
+       strbuf_release(&pattern);
        return res;
+
+fail_return:
+       strbuf_release(&pattern);
+       free(res);
+       return NULL;
 }
 
 /*
@@ -294,7 +315,7 @@ 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 {
@@ -463,7 +484,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
+#endif /* DEBUG_ATTR */
 
 static void drop_attr_stack(void)
 {
@@ -489,6 +510,18 @@ static int git_attr_system(void)
 
 static GIT_PATH_FUNC(git_path_info_attributes, INFOATTRIBUTES_FILE)
 
+static void push_stack(struct attr_stack **attr_stack_p,
+                      struct attr_stack *elem, char *origin, size_t originlen)
+{
+       if (elem) {
+               elem->origin = origin;
+               if (origin)
+                       elem->originlen = originlen;
+               elem->prev = *attr_stack_p;
+               *attr_stack_p = elem;
+       }
+}
+
 static void bootstrap_attr_stack(void)
 {
        struct attr_stack *elem;
@@ -496,37 +529,23 @@ static void bootstrap_attr_stack(void)
        if (attr_stack)
                return;
 
-       elem = read_attr_from_array(builtin_attr);
-       elem->origin = NULL;
-       elem->prev = attr_stack;
-       attr_stack = elem;
-
-       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;
-               }
-       }
+       push_stack(&attr_stack, read_attr_from_array(builtin_attr), NULL, 0);
+
+       if (git_attr_system())
+               push_stack(&attr_stack,
+                          read_attr_from_file(git_etc_gitattributes(), 1),
+                          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;
-               }
-       }
+       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);
-               elem->origin = xstrdup("");
-               elem->originlen = 0;
-               elem->prev = attr_stack;
-               attr_stack = elem;
+               push_stack(&attr_stack, elem, xstrdup(""), 0);
                debug_push(elem);
        }
 
@@ -537,15 +556,12 @@ static void bootstrap_attr_stack(void)
 
        if (!elem)
                elem = xcalloc(1, sizeof(*elem));
-       elem->origin = NULL;
-       elem->prev = attr_stack;
-       attr_stack = elem;
+       push_stack(&attr_stack, elem, NULL, 0);
 }
 
 static void prepare_attr_stack(const char *path, int dirlen)
 {
        struct attr_stack *elem, *info;
-       int len;
        const char *cp;
 
        /*
@@ -605,20 +621,21 @@ static void prepare_attr_stack(const char *path, int dirlen)
 
                assert(attr_stack->origin);
                while (1) {
-                       len = strlen(attr_stack->origin);
+                       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_add(&pathbuf, path, cp - path);
-                       strbuf_addch(&pathbuf, '/');
-                       strbuf_addstr(&pathbuf, GITATTRIBUTES_FILE);
+                       strbuf_addf(&pathbuf,
+                                   "%.*s/%s", (int)(cp - path), path,
+                                   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;
+                       origin = strbuf_detach(&pathbuf, &len);
+                       push_stack(&attr_stack, elem, origin, len);
                        debug_push(elem);
                }
 
@@ -628,8 +645,7 @@ static void prepare_attr_stack(const char *path, int dirlen)
        /*
         * Finally push the "info" one at the top of the stack.
         */
-       info->prev = attr_stack;
-       attr_stack = info;
+       push_stack(&attr_stack, info, NULL, 0);
 }
 
 static int path_matches(const char *pathname, int pathlen,
@@ -699,24 +715,21 @@ static int fill(const char *path, int pathlen, int basename_offset,
 static int macroexpand_one(int nr, int rem)
 {
        struct attr_stack *stk;
-       struct match_attr *a = NULL;
        int i;
 
        if (check_all_attr[nr].value != ATTR__TRUE ||
            !check_all_attr[nr].attr->maybe_macro)
                return rem;
 
-       for (stk = attr_stack; !a && stk; stk = stk->prev)
-               for (i = stk->num_matches - 1; !a && 0 <= i; i--) {
+       for (stk = attr_stack; stk; stk = stk->prev) {
+               for (i = stk->num_matches - 1; 0 <= i; i--) {
                        struct match_attr *ma = stk->attrs[i];
                        if (!ma->is_macro)
                                continue;
                        if (ma->u.attr->attr_nr == nr)
-                               a = ma;
+                               return fill_one("expand", ma, rem);
                }
-
-       if (a)
-               rem = fill_one("expand", a, rem);
+       }
 
        return rem;
 }