X-Git-Url: https://asedeno.scripts.mit.edu/gitweb/?a=blobdiff_plain;f=attr.c;h=a0712543b2076a90f38d36c5ed0e8efa2639e316;hb=952c8c56380734d45bddf369fe478895672c5a3a;hp=7435d927a91b363d4cb7762e9af82f7a41a8d508;hpb=d0bfd026a8241d544c339944976927b388d61a5e;p=git.git diff --git a/attr.c b/attr.c index 7435d927a..a0712543b 100644 --- a/attr.c +++ b/attr.c @@ -1,6 +1,14 @@ #include "cache.h" #include "attr.h" +const char git_attr__true[] = "(builtin)true"; +const char git_attr__false[] = "\0(builtin)false"; +static const char git_attr__unknown[] = "(builtin)unknown"; +#define ATTR__TRUE git_attr__true +#define ATTR__FALSE git_attr__false +#define ATTR__UNSET NULL +#define ATTR__UNKNOWN git_attr__unknown + /* * The basic design decision here is that we are not going to have * insanely large number of attributes. @@ -16,9 +24,12 @@ struct git_attr { struct git_attr *next; unsigned h; + int attr_nr; char name[FLEX_ARRAY]; }; +static int attr_nr; +static struct git_attr_check *check_all_attr; static struct git_attr *(git_attr_hash[HASHSIZE]); static unsigned hash_name(const char *name, int namelen) @@ -33,6 +44,27 @@ static unsigned hash_name(const char *name, int namelen) return val; } +static int invalid_attr_name(const char *name, int namelen) +{ + /* + * Attribute name cannot begin with '-' and from + * [-A-Za-z0-9_.]. We'd specifically exclude '=' for now, + * as we might later want to allow non-binary value for + * attributes, e.g. "*.svg merge=special-merge-program-for-svg" + */ + if (*name == '-') + return -1; + 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; +} + struct git_attr *git_attr(const char *name, int len) { unsigned hval = hash_name(name, len); @@ -45,12 +77,21 @@ struct git_attr *git_attr(const char *name, int len) return a; } + if (invalid_attr_name(name, len)) + return NULL; + a = xmalloc(sizeof(*a) + len + 1); memcpy(a->name, name, len); a->name[len] = 0; a->h = hval; a->next = git_attr_hash[pos]; + a->attr_nr = attr_nr++; git_attr_hash[pos] = a; + + check_all_attr = xrealloc(check_all_attr, + sizeof(*check_all_attr) * attr_nr); + check_all_attr[a->attr_nr].attr = a; + check_all_attr[a->attr_nr].value = ATTR__UNKNOWN; return a; } @@ -60,66 +101,135 @@ struct git_attr *git_attr(const char *name, int len) * (1) glob pattern. * (2) whitespace * (3) whitespace separated list of attribute names, each of which - * could be prefixed with '!' to mean "not set". + * could be prefixed with '-' to mean "set to false", '!' to mean + * "unset". */ +/* What does a matched pattern decide? */ struct attr_state { - int unset; struct git_attr *attr; + const char *setto; }; struct match_attr { - char *pattern; + union { + char *pattern; + struct git_attr *attr; + } u; + char is_macro; unsigned num_attr; struct attr_state state[FLEX_ARRAY]; }; static const char blank[] = " \t\r\n"; -static struct match_attr *parse_attr_line(const char *line) +static const char *parse_attr(const char *src, int lineno, const char *cp, + int *num_attr, struct match_attr *res) +{ + const char *ep, *equals; + int len; + + ep = cp + strcspn(cp, blank); + equals = strchr(cp, '='); + if (equals && ep < equals) + equals = NULL; + if (equals) + len = equals - cp; + else + len = ep - cp; + if (!res) { + if (*cp == '-' || *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); + return NULL; + } + } else { + struct attr_state *e; + + e = &(res->state[*num_attr]); + if (*cp == '-' || *cp == '!') { + e->setto = (*cp == '-') ? ATTR__FALSE : ATTR__UNSET; + cp++; + len--; + } + else if (!equals) + e->setto = ATTR__TRUE; + else { + char *value; + int vallen = ep - equals; + value = xmalloc(vallen); + memcpy(value, equals+1, vallen-1); + value[vallen-1] = 0; + e->setto = value; + } + e->attr = git_attr(cp, len); + } + (*num_attr)++; + return ep + strspn(ep, blank); +} + +static struct match_attr *parse_attr_line(const char *line, const char *src, + int lineno, int macro_ok) { int namelen; int num_attr; const char *cp, *name; - struct match_attr *res = res; + struct match_attr *res = NULL; int pass; + int is_macro; cp = line + strspn(line, blank); if (!*cp || *cp == '#') return NULL; name = cp; namelen = strcspn(name, blank); + if (strlen(ATTRIBUTE_MACRO_PREFIX) < namelen && + !prefixcmp(name, ATTRIBUTE_MACRO_PREFIX)) { + if (!macro_ok) { + fprintf(stderr, "%s not allowed: %s:%d\n", + name, src, lineno); + return NULL; + } + 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; + } + } + else + is_macro = 0; for (pass = 0; pass < 2; pass++) { /* pass 0 counts and allocates, pass 1 fills */ num_attr = 0; cp = name + namelen; cp = cp + strspn(cp, blank); - while (*cp) { - const char *ep; - ep = cp + strcspn(cp, blank); - if (pass) { - struct attr_state *e; - - e = &(res->state[num_attr]); - if (*cp == '!') { - e->unset = 1; - cp++; - } - e->attr = git_attr(cp, ep - cp); - } - num_attr++; - cp = ep + strspn(ep, blank); - } + while (*cp) + cp = parse_attr(src, lineno, cp, &num_attr, res); if (pass) break; res = xcalloc(1, sizeof(*res) + sizeof(struct attr_state) * num_attr + - namelen + 1); - res->pattern = (char*)&(res->state[num_attr]); - memcpy(res->pattern, name, namelen); - res->pattern[namelen] = 0; + (is_macro ? 0 : namelen + 1)); + if (is_macro) + res->u.attr = git_attr(name, namelen); + else { + res->u.pattern = (char*)&(res->state[num_attr]); + memcpy(res->u.pattern, name, namelen); + res->u.pattern[namelen] = 0; + } + res->is_macro = is_macro; res->num_attr = num_attr; } return res; @@ -130,9 +240,9 @@ static struct match_attr *parse_attr_line(const char *line) * come from many places. * * (1) .gitattribute file of the same directory; - * (2) .gitattribute file of the parent directory if (1) does not have any match; - * this goes recursively upwards, just like .gitignore - * (3) perhaps $GIT_DIR/info/attributes, as the final fallback. + * (2) .gitattribute file of the parent directory if (1) does not have + * any match; this goes recursively upwards, just like .gitignore. + * (3) $GIT_DIR/info/attributes, which overrides both of the above. * * In the same file, later entries override the earlier match, so in the * global list, we would have entries from info/attributes the earliest @@ -154,12 +264,26 @@ static void free_attr_elem(struct attr_stack *e) { int i; free(e->origin); - for (i = 0; i < e->num_matches; i++) - free(e->attrs[i]); + for (i = 0; i < e->num_matches; i++) { + struct match_attr *a = e->attrs[i]; + int j; + for (j = 0; j < a->num_attr; j++) { + const char *setto = a->state[j].setto; + if (setto == ATTR__TRUE || + setto == ATTR__FALSE || + setto == ATTR__UNSET || + setto == ATTR__UNKNOWN) + ; + else + free((char*) setto); + } + free(a); + } free(e); } static const char *builtin_attr[] = { + "[attr]binary -diff -crlf", NULL, }; @@ -167,23 +291,28 @@ static struct attr_stack *read_attr_from_array(const char **list) { struct attr_stack *res; const char *line; + int lineno = 0; res = xcalloc(1, sizeof(*res)); while ((line = *(list++)) != NULL) { - struct match_attr *a = parse_attr_line(line); + struct match_attr *a; + + a = parse_attr_line(line, "[builtin]", ++lineno, 1); if (!a) continue; - res->attrs = xrealloc(res->attrs, res->num_matches + 1); + res->attrs = xrealloc(res->attrs, + sizeof(struct match_attr *) * (res->num_matches + 1)); res->attrs[res->num_matches++] = a; } return res; } -static struct attr_stack *read_attr_from_file(const char *path) +static struct attr_stack *read_attr_from_file(const char *path, int macro_ok) { FILE *fp; struct attr_stack *res; char buf[2048]; + int lineno = 0; res = xcalloc(1, sizeof(*res)); fp = fopen(path, "r"); @@ -191,10 +320,13 @@ static struct attr_stack *read_attr_from_file(const char *path) return res; while (fgets(buf, sizeof(buf), fp)) { - struct match_attr *a = parse_attr_line(buf); + struct match_attr *a; + + a = parse_attr_line(buf, path, ++lineno, macro_ok); if (!a) continue; - res->attrs = xrealloc(res->attrs, res->num_matches + 1); + res->attrs = xrealloc(res->attrs, + sizeof(struct match_attr *) * (res->num_matches + 1)); res->attrs[res->num_matches++] = a; } fclose(fp); @@ -206,13 +338,51 @@ static void debug_info(const char *what, struct attr_stack *elem) { fprintf(stderr, "%s: %s\n", what, elem->origin ? elem->origin : "()"); } +static void debug_set(const char *what, const char *match, struct git_attr *attr, void *v) +{ + const char *value = v; + + if (ATTR_TRUE(value)) + value = "set"; + else if (ATTR_FALSE(value)) + value = "unset"; + else if (ATTR_UNSET(value)) + value = "unspecified"; + + fprintf(stderr, "%s: %s => %s (%s)\n", + what, attr->name, (char *) value, match); +} #define debug_push(a) debug_info("push", (a)) #define debug_pop(a) debug_info("pop", (a)) #else #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 bootstrap_attr_stack(void) +{ + if (!attr_stack) { + struct attr_stack *elem; + + elem = read_attr_from_array(builtin_attr); + elem->origin = NULL; + elem->prev = attr_stack; + attr_stack = elem; + + elem = read_attr_from_file(GITATTRIBUTES_FILE, 1); + elem->origin = strdup(""); + elem->prev = attr_stack; + attr_stack = elem; + debug_push(elem); + + elem = read_attr_from_file(git_path(INFOATTRIBUTES_FILE), 1); + elem->origin = NULL; + elem->prev = attr_stack; + attr_stack = elem; + } +} + static void prepare_attr_stack(const char *path, int dirlen) { struct attr_stack *elem, *info; @@ -232,23 +402,8 @@ 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. */ - if (!attr_stack) { - elem = read_attr_from_array(builtin_attr); - elem->origin = NULL; - elem->prev = attr_stack; - attr_stack = elem; - - elem = read_attr_from_file(GITATTRIBUTES_FILE); - elem->origin = strdup(""); - elem->prev = attr_stack; - attr_stack = elem; - debug_push(elem); - - elem = read_attr_from_file(git_path(INFOATTRIBUTES_FILE)); - elem->origin = NULL; - elem->prev = attr_stack; - attr_stack = elem; - } + if (!attr_stack) + bootstrap_attr_stack(); /* * Pop the "info" one that is always at the top of the stack. @@ -286,7 +441,7 @@ static void prepare_attr_stack(const char *path, int dirlen) memcpy(pathbuf + dirlen, "/", 2); cp = strchr(pathbuf + len + 1, '/'); strcpy(cp + 1, GITATTRIBUTES_FILE); - elem = read_attr_from_file(pathbuf); + elem = read_attr_from_file(pathbuf, 0); *cp = '\0'; elem->origin = strdup(pathbuf); elem->prev = attr_stack; @@ -324,35 +479,53 @@ static int path_matches(const char *pathname, int pathlen, return fnmatch(pattern, pathname + baselen, FNM_PATHNAME) == 0; } -/* - * I do not like this at all. Only because we allow individual - * attribute to be set or unset incrementally by individual - * lines in .gitattribute files, we need to do this triple - * loop which looks quite wasteful. - */ -static int fill(const char *path, int pathlen, - struct attr_stack *stk, struct git_attr_check *check, - int num, int rem) +static int fill_one(const char *what, struct match_attr *a, int rem) +{ + struct git_attr_check *check = check_all_attr; + int i; + + for (i = 0; 0 < rem && i < a->num_attr; i++) { + struct git_attr *attr = a->state[i].attr; + const char **n = &(check[attr->attr_nr].value); + const char *v = a->state[i].setto; + + if (*n == ATTR__UNKNOWN) { + debug_set(what, a->u.pattern, attr, v); + *n = v; + rem--; + } + } + return rem; +} + +static int fill(const char *path, int pathlen, struct attr_stack *stk, int rem) { - int i, j, k; + int i; const char *base = stk->origin ? stk->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, - a->pattern, base, strlen(base))) { - for (j = 0; j < a->num_attr; j++) { - struct git_attr *attr = a->state[j].attr; - int set = !a->state[j].unset; - for (k = 0; k < num; k++) { - if (0 <= check[k].isset || - check[k].attr != attr) - continue; - check[k].isset = set; - rem--; - } - } - } + a->u.pattern, base, strlen(base))) + rem = fill_one("fill", a, rem); + } + return rem; +} + +static int macroexpand(struct attr_stack *stk, int rem) +{ + int i; + struct git_attr_check *check = check_all_attr; + + for (i = stk->num_matches - 1; 0 < rem && 0 <= i; i--) { + struct match_attr *a = stk->attrs[i]; + if (!a->is_macro) + continue; + if (check[a->u.attr->attr_nr].value != ATTR__TRUE) + continue; + rem = fill_one("expand", a, rem); } return rem; } @@ -363,8 +536,9 @@ int git_checkattr(const char *path, int num, struct git_attr_check *check) const char *cp; int dirlen, pathlen, i, rem; - for (i = 0; i < num; i++) - check[i].isset = -1; + bootstrap_attr_stack(); + for (i = 0; i < attr_nr; i++) + check_all_attr[i].value = ATTR__UNKNOWN; pathlen = strlen(path); cp = strrchr(path, '/'); @@ -373,8 +547,19 @@ int git_checkattr(const char *path, int num, struct git_attr_check *check) else dirlen = cp - path; prepare_attr_stack(path, dirlen); - rem = num; + rem = attr_nr; + for (stk = attr_stack; 0 < rem && stk; stk = stk->prev) + rem = fill(path, pathlen, stk, rem); + for (stk = attr_stack; 0 < rem && stk; stk = stk->prev) - rem = fill(path, pathlen, stk, check, num, rem); + rem = macroexpand(stk, rem); + + for (i = 0; i < num; i++) { + const char *value = check_all_attr[check[i].attr->attr_nr].value; + if (value == ATTR__UNKNOWN) + value = ATTR__UNSET; + check[i].value = value; + } + return 0; }