diff --git a/attr.c b/attr.c index 60fe48f3b84d7dfa2d0e3f27852ceae03d7487d4..b3496a6eb529c111ff0378a517b13ca89cb74d5a 100644 --- a/attr.c +++ b/attr.c @@ -1,6 +1,8 @@ #include "cache.h" #include "attr.h" +#define ATTR__UNKNOWN ((void *) -2) + /* * The basic design decision here is that we are not going to have * insanely large number of attributes. @@ -83,6 +85,7 @@ struct git_attr *git_attr(const char *name, int len) 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; } @@ -92,12 +95,14 @@ 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; + void *setto; }; struct match_attr { @@ -112,13 +117,63 @@ struct match_attr { static const char blank[] = " \t\r\n"; +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; @@ -153,42 +208,16 @@ static struct match_attr *parse_attr_line(const char *line, const char *src, num_attr = 0; cp = name + namelen; cp = cp + strspn(cp, blank); - while (*cp) { - const char *ep; - ep = cp + strcspn(cp, blank); - if (!pass) { - if (*cp == '-') - cp++; - if (invalid_attr_name(cp, ep - cp)) { - fprintf(stderr, - "%.*s is not a valid attribute name: %s:%d\n", - (int)(ep - cp), cp, - src, lineno); - return NULL; - } - } else { - 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 + (is_macro ? 0 : namelen + 1)); - if (is_macro) { + 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); @@ -205,9 +234,9 @@ static struct match_attr *parse_attr_line(const char *line, const char *src, * 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 @@ -229,8 +258,21 @@ 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++) { + void *setto = a->state[j].setto; + if (setto == ATTR__TRUE || + setto == ATTR__FALSE || + setto == ATTR__UNSET || + setto == ATTR__UNKNOWN) + ; + else + free(setto); + } + free(a); + } free(e); } @@ -288,10 +330,19 @@ 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, int set) +static void debug_set(const char *what, const char *match, struct git_attr *attr, void *v) { - fprintf(stderr, "%s: %s => %d (%s)\n", - what, attr->name, set, match); + 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)) @@ -420,56 +471,53 @@ static int path_matches(const char *pathname, int pathlen, return fnmatch(pattern, pathname + baselen, FNM_PATHNAME) == 0; } +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; + void **n = &(check[attr->attr_nr].value); + void *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; const char *base = stk->origin ? stk->origin : ""; - int i, j; - 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 (path_matches(path, pathlen, - a->u.pattern, base, strlen(base))) { - for (j = 0; 0 < rem && j < a->num_attr; j++) { - struct git_attr *attr = a->state[j].attr; - int set = !a->state[j].unset; - int *n = &(check[attr->attr_nr].isset); - - if (*n < 0) { - debug_set("fill", a->u.pattern, attr, set); - *n = 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, j; + 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].isset < 0) + if (check[a->u.attr->attr_nr].value != ATTR__TRUE) continue; - for (j = 0; 0 < rem && j < a->num_attr; j++) { - struct git_attr *attr = a->state[j].attr; - int set = !a->state[j].unset; - int *n = &(check[attr->attr_nr].isset); - - if (*n < 0) { - debug_set("expand", a->u.attr->name, attr, set); - *n = set; - rem--; - } - } + rem = fill_one("expand", a, rem); } return rem; } @@ -482,7 +530,7 @@ int git_checkattr(const char *path, int num, struct git_attr_check *check) bootstrap_attr_stack(); for (i = 0; i < attr_nr; i++) - check_all_attr[i].isset = -1; + check_all_attr[i].value = ATTR__UNKNOWN; pathlen = strlen(path); cp = strrchr(path, '/'); @@ -498,8 +546,12 @@ int git_checkattr(const char *path, int num, struct git_attr_check *check) for (stk = attr_stack; 0 < rem && stk; stk = stk->prev) rem = macroexpand(stk, rem); - for (i = 0; i < num; i++) - check[i].isset = check_all_attr[check[i].attr->attr_nr].isset; + for (i = 0; i < num; i++) { + void *value = check_all_attr[check[i].attr->attr_nr].value; + if (value == ATTR__UNKNOWN) + value = ATTR__UNSET; + check[i].value = value; + } return 0; } diff --git a/attr.h b/attr.h index 1e5ab4069417f184e494a30c7cf821acffafc8d5..8ec2d3d35ce02d15adb0c3b4c72ce2d435d5a1ae 100644 --- a/attr.h +++ b/attr.h @@ -6,9 +6,19 @@ struct git_attr; struct git_attr *git_attr(const char *, int); +/* Internal use */ +#define ATTR__TRUE ((void *) 1) +#define ATTR__FALSE ((void *) 0) +#define ATTR__UNSET ((void *) -1) + +/* For public to check git_attr_check results */ +#define ATTR_TRUE(v) ((v) == ATTR__TRUE) +#define ATTR_FALSE(v) ((v) == ATTR__FALSE) +#define ATTR_UNSET(v) ((v) == ATTR__UNSET) + struct git_attr_check { struct git_attr *attr; - int isset; + void *value; }; int git_checkattr(const char *path, int, struct git_attr_check *); diff --git a/builtin-check-attr.c b/builtin-check-attr.c index 634be9ed2e4a28b0c3a53d8724bacbd3094b369d..6983a73c1b387e5eb4a0c8dbc6b9e72b93735245 100644 --- a/builtin-check-attr.c +++ b/builtin-check-attr.c @@ -42,11 +42,17 @@ int cmd_check_attr(int argc, const char **argv, const char *prefix) if (git_checkattr(argv[i], cnt, check)) die("git_checkattr died"); for (j = 0; j < cnt; j++) { + void *value = check[j].value; + + if (ATTR_TRUE(value)) + value = "set"; + else if (ATTR_FALSE(value)) + value = "unset"; + else if (ATTR_UNSET(value)) + value = "unspecified"; + write_name_quoted("", 0, argv[i], 1, stdout); - printf(": %s: %s\n", argv[j+1], - (check[j].isset < 0) ? "unspecified" : - (check[j].isset == 0) ? "unset" : - "set"); + printf(": %s: %s\n", argv[j+1], (char *) value); } } return 0; diff --git a/convert.c b/convert.c index d0d4b8187113ed9bc5c969bdabbfe4a2dcf3ed87..68bb70f12ff49fede13bce01e2d8a2f0d64734d1 100644 --- a/convert.c +++ b/convert.c @@ -225,9 +225,19 @@ static int git_path_check_crlf(const char *path) setup_crlf_check(&attr_crlf_check); - if (git_checkattr(path, 1, &attr_crlf_check)) - return -1; - return attr_crlf_check.isset; + if (!git_checkattr(path, 1, &attr_crlf_check)) { + void *value = attr_crlf_check.value; + if (ATTR_TRUE(value)) + return 1; + else if (ATTR_FALSE(value)) + return 0; + else if (ATTR_UNSET(value)) + ; + else + die("unknown value %s given to 'crlf' attribute", + (char *)value); + } + return -1; } int convert_to_git(const char *path, char **bufp, unsigned long *sizep) diff --git a/diff.c b/diff.c index dcea405ed39199164251f2be0c42984a0c86aea5..a32078ea0177cb2a57e4a470bf6e50c81d6dd70d 100644 --- a/diff.c +++ b/diff.c @@ -1068,9 +1068,18 @@ static int file_is_binary(struct diff_filespec *one) struct git_attr_check attr_diff_check; setup_diff_attr_check(&attr_diff_check); - if (!git_checkattr(one->path, 1, &attr_diff_check) && - (0 <= attr_diff_check.isset)) - return !attr_diff_check.isset; + if (!git_checkattr(one->path, 1, &attr_diff_check)) { + void *value = attr_diff_check.value; + if (ATTR_TRUE(value)) + return 0; + else if (ATTR_FALSE(value)) + return 1; + else if (ATTR_UNSET(value)) + ; + else + die("unknown value %s given to 'diff' attribute", + (char *)value); + } if (!one->data) { if (!DIFF_FILE_VALID(one))