You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
381 lines
8.8 KiB
381 lines
8.8 KiB
18 years ago
|
#include "cache.h"
|
||
|
#include "attr.h"
|
||
|
|
||
|
/*
|
||
|
* The basic design decision here is that we are not going to have
|
||
|
* insanely large number of attributes.
|
||
|
*
|
||
|
* This is a randomly chosen prime.
|
||
|
*/
|
||
|
#define HASHSIZE 257
|
||
|
|
||
|
#ifndef DEBUG_ATTR
|
||
|
#define DEBUG_ATTR 0
|
||
|
#endif
|
||
|
|
||
|
struct git_attr {
|
||
|
struct git_attr *next;
|
||
|
unsigned h;
|
||
|
char name[FLEX_ARRAY];
|
||
|
};
|
||
|
|
||
|
static struct git_attr *(git_attr_hash[HASHSIZE]);
|
||
|
|
||
|
static unsigned hash_name(const char *name, int namelen)
|
||
|
{
|
||
|
unsigned val = 0;
|
||
|
unsigned char c;
|
||
|
|
||
|
while (namelen--) {
|
||
|
c = *name++;
|
||
|
val = ((val << 7) | (val >> 22)) ^ c;
|
||
|
}
|
||
|
return val;
|
||
|
}
|
||
|
|
||
|
struct git_attr *git_attr(const char *name, int len)
|
||
|
{
|
||
|
unsigned hval = hash_name(name, len);
|
||
|
unsigned pos = hval % HASHSIZE;
|
||
|
struct git_attr *a;
|
||
|
|
||
|
for (a = git_attr_hash[pos]; a; a = a->next) {
|
||
|
if (a->h == hval &&
|
||
|
!memcmp(a->name, name, len) && !a->name[len])
|
||
|
return a;
|
||
|
}
|
||
|
|
||
|
a = xmalloc(sizeof(*a) + len + 1);
|
||
|
memcpy(a->name, name, len);
|
||
|
a->name[len] = 0;
|
||
|
a->h = hval;
|
||
|
a->next = git_attr_hash[pos];
|
||
|
git_attr_hash[pos] = a;
|
||
|
return a;
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* .gitattributes file is one line per record, each of which is
|
||
|
*
|
||
|
* (1) glob pattern.
|
||
|
* (2) whitespace
|
||
|
* (3) whitespace separated list of attribute names, each of which
|
||
|
* could be prefixed with '!' to mean "not set".
|
||
|
*/
|
||
|
|
||
|
struct attr_state {
|
||
|
int unset;
|
||
|
struct git_attr *attr;
|
||
|
};
|
||
|
|
||
|
struct match_attr {
|
||
|
char *pattern;
|
||
|
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)
|
||
|
{
|
||
|
int namelen;
|
||
|
int num_attr;
|
||
|
const char *cp, *name;
|
||
|
struct match_attr *res = res;
|
||
|
int pass;
|
||
|
|
||
|
cp = line + strspn(line, blank);
|
||
|
if (!*cp || *cp == '#')
|
||
|
return NULL;
|
||
|
name = cp;
|
||
|
namelen = strcspn(name, blank);
|
||
|
|
||
|
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);
|
||
|
}
|
||
|
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;
|
||
|
res->num_attr = num_attr;
|
||
|
}
|
||
|
return res;
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* Like info/exclude and .gitignore, the attribute information can
|
||
|
* 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.
|
||
|
*
|
||
|
* In the same file, later entries override the earlier match, so in the
|
||
|
* global list, we would have entries from info/attributes the earliest
|
||
|
* (reading the file from top to bottom), .gitattribute of the root
|
||
|
* 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 excluded() does in dir.c to deal with
|
||
|
* .gitignore
|
||
|
*/
|
||
|
|
||
|
static struct attr_stack {
|
||
|
struct attr_stack *prev;
|
||
|
char *origin;
|
||
|
unsigned num_matches;
|
||
|
struct match_attr **attrs;
|
||
|
} *attr_stack;
|
||
|
|
||
|
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]);
|
||
|
free(e);
|
||
|
}
|
||
|
|
||
|
static const char *builtin_attr[] = {
|
||
|
NULL,
|
||
|
};
|
||
|
|
||
|
static struct attr_stack *read_attr_from_array(const char **list)
|
||
|
{
|
||
|
struct attr_stack *res;
|
||
|
const char *line;
|
||
|
|
||
|
res = xcalloc(1, sizeof(*res));
|
||
|
while ((line = *(list++)) != NULL) {
|
||
|
struct match_attr *a = parse_attr_line(line);
|
||
|
if (!a)
|
||
|
continue;
|
||
|
res->attrs = xrealloc(res->attrs, res->num_matches + 1);
|
||
|
res->attrs[res->num_matches++] = a;
|
||
|
}
|
||
|
return res;
|
||
|
}
|
||
|
|
||
|
static struct attr_stack *read_attr_from_file(const char *path)
|
||
|
{
|
||
|
FILE *fp;
|
||
|
struct attr_stack *res;
|
||
|
char buf[2048];
|
||
|
|
||
|
res = xcalloc(1, sizeof(*res));
|
||
|
fp = fopen(path, "r");
|
||
|
if (!fp)
|
||
|
return res;
|
||
|
|
||
|
while (fgets(buf, sizeof(buf), fp)) {
|
||
|
struct match_attr *a = parse_attr_line(buf);
|
||
|
if (!a)
|
||
|
continue;
|
||
|
res->attrs = xrealloc(res->attrs, res->num_matches + 1);
|
||
|
res->attrs[res->num_matches++] = a;
|
||
|
}
|
||
|
fclose(fp);
|
||
|
return res;
|
||
|
}
|
||
|
|
||
|
#if DEBUG_ATTR
|
||
|
static void debug_info(const char *what, struct attr_stack *elem)
|
||
|
{
|
||
|
fprintf(stderr, "%s: %s\n", what, elem->origin ? elem->origin : "()");
|
||
|
}
|
||
|
#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)
|
||
|
#endif
|
||
|
|
||
|
static void prepare_attr_stack(const char *path, int dirlen)
|
||
|
{
|
||
|
struct attr_stack *elem, *info;
|
||
|
int len;
|
||
|
char pathbuf[PATH_MAX];
|
||
|
|
||
|
/*
|
||
|
* At the bottom of the attribute stack is the built-in
|
||
|
* set of attribute definitions. Then, contents from
|
||
|
* .gitattribute files from directories closer to the
|
||
|
* root to the ones in deeper directories are pushed
|
||
|
* to the stack. Finally, at the very top of the stack
|
||
|
* we always keep the contents of $GIT_DIR/info/attributes.
|
||
|
*
|
||
|
* When checking, we use entries from near the top of the
|
||
|
* stack, preferring $GIT_DIR/info/attributes, then
|
||
|
* .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;
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* Pop the "info" one that is always at the top of the stack.
|
||
|
*/
|
||
|
info = attr_stack;
|
||
|
attr_stack = info->prev;
|
||
|
|
||
|
/*
|
||
|
* Pop the ones from directories that are not the prefix of
|
||
|
* the path we are checking.
|
||
|
*/
|
||
|
while (attr_stack && attr_stack->origin) {
|
||
|
int namelen = strlen(attr_stack->origin);
|
||
|
|
||
|
elem = attr_stack;
|
||
|
if (namelen <= dirlen &&
|
||
|
!strncmp(elem->origin, path, namelen))
|
||
|
break;
|
||
|
|
||
|
debug_pop(elem);
|
||
|
attr_stack = elem->prev;
|
||
|
free_attr_elem(elem);
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* Read from parent directories and push them down
|
||
|
*/
|
||
|
while (1) {
|
||
|
char *cp;
|
||
|
|
||
|
len = strlen(attr_stack->origin);
|
||
|
if (dirlen <= len)
|
||
|
break;
|
||
|
memcpy(pathbuf, path, dirlen);
|
||
|
memcpy(pathbuf + dirlen, "/", 2);
|
||
|
cp = strchr(pathbuf + len + 1, '/');
|
||
|
strcpy(cp + 1, GITATTRIBUTES_FILE);
|
||
|
elem = read_attr_from_file(pathbuf);
|
||
|
*cp = '\0';
|
||
|
elem->origin = strdup(pathbuf);
|
||
|
elem->prev = attr_stack;
|
||
|
attr_stack = elem;
|
||
|
debug_push(elem);
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* Finally push the "info" one at the top of the stack.
|
||
|
*/
|
||
|
info->prev = attr_stack;
|
||
|
attr_stack = info;
|
||
|
}
|
||
|
|
||
|
static int path_matches(const char *pathname, int pathlen,
|
||
|
const char *pattern,
|
||
|
const char *base, int baselen)
|
||
|
{
|
||
|
if (!strchr(pattern, '/')) {
|
||
|
/* match basename */
|
||
|
const char *basename = strrchr(pathname, '/');
|
||
|
basename = basename ? basename + 1 : pathname;
|
||
|
return (fnmatch(pattern, basename, 0) == 0);
|
||
|
}
|
||
|
/*
|
||
|
* match with FNM_PATHNAME; the pattern has base implicitly
|
||
|
* in front of it.
|
||
|
*/
|
||
|
if (*pattern == '/')
|
||
|
pattern++;
|
||
|
if (pathlen < baselen ||
|
||
|
(baselen && pathname[baselen - 1] != '/') ||
|
||
|
strncmp(pathname, base, baselen))
|
||
|
return 0;
|
||
|
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)
|
||
|
{
|
||
|
int i, j, k;
|
||
|
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 (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--;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
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;
|
||
|
|
||
|
pathlen = strlen(path);
|
||
|
cp = strrchr(path, '/');
|
||
|
if (!cp)
|
||
|
dirlen = 0;
|
||
|
else
|
||
|
dirlen = cp - path;
|
||
|
prepare_attr_stack(path, dirlen);
|
||
|
rem = num;
|
||
|
for (stk = attr_stack; 0 < rem && stk; stk = stk->prev)
|
||
|
rem = fill(path, pathlen, stk, check, num, rem);
|
||
|
return 0;
|
||
|
}
|