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)
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;
return a;
}
};
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 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;
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);
+ }
+ else
+ is_macro = 0;
for (pass = 0; pass < 2; pass++) {
/* pass 0 counts and allocates, pass 1 fills */
}
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;
{
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);
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");
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);
{
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)
+{
+ fprintf(stderr, "%s: %s => %d (%s)\n",
+ what, attr->name, set, 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;
* .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.
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;
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(const char *path, int pathlen, struct attr_stack *stk, int rem)
{
- int i, j, k;
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->pattern, base, strlen(base))) {
- for (j = 0; j < a->num_attr; j++) {
+ 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;
- for (k = 0; k < num; k++) {
- if (0 <= check[k].isset ||
- check[k].attr != attr)
- continue;
- check[k].isset = set;
+ int *n = &(check[attr->attr_nr].isset);
+
+ if (*n < 0) {
+ debug_set("fill", a->u.pattern, attr, set);
+ *n = set;
rem--;
}
}
return rem;
}
+static int macroexpand(struct attr_stack *stk, int rem)
+{
+ 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 (check[a->u.attr->attr_nr].isset < 0)
+ 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--;
+ }
+ }
+ }
+ return rem;
+}
+
int git_checkattr(const char *path, int num, struct git_attr_check *check)
{
struct attr_stack *stk;
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].isset = -1;
pathlen = strlen(path);
cp = strrchr(path, '/');
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++)
+ check[i].isset = check_all_attr[check[i].attr->attr_nr].isset;
+
return 0;
}