summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJunio C Hamano <gitster@pobox.com>2017-02-27 21:57:14 (GMT)
committerJunio C Hamano <gitster@pobox.com>2017-02-27 21:57:14 (GMT)
commita04855bae8d75d22b0c909c7d1febedcd05ba9b1 (patch)
treeb6f96ab383fc925c7a4abb9b62076ffbe7382269
parentfdeb89fdeb1db8f8910bc33547dd819065bc4e9d (diff)
parentf0dd042148233ad4681b29f35f3bc3ba3b962474 (diff)
downloadgit-a04855bae8d75d22b0c909c7d1febedcd05ba9b1.zip
git-a04855bae8d75d22b0c909c7d1febedcd05ba9b1.tar.gz
git-a04855bae8d75d22b0c909c7d1febedcd05ba9b1.tar.bz2
Merge branch 'bw/attr'
The gitattributes machinery is being taught to work better in a multi-threaded environment. * bw/attr: (27 commits) attr: reformat git_attr_set_direction() function attr: push the bare repo check into read_attr() attr: store attribute stack in attr_check structure attr: tighten const correctness with git_attr and match_attr attr: remove maybe-real, maybe-macro from git_attr attr: eliminate global check_all_attr array attr: use hashmap for attribute dictionary attr: change validity check for attribute names to use positive logic attr: pass struct attr_check to collect_some_attrs attr: retire git_check_attrs() API attr: convert git_check_attrs() callers to use the new API attr: convert git_all_attrs() to use "struct attr_check" attr: (re)introduce git_check_attr() and struct attr_check attr: rename function and struct related to checking attributes attr.c: outline the future plans by heavily commenting Documentation: fix a typo attr.c: add push_stack() helper attr: support quoting pathname patterns in C style attr.c: plug small leak in parse_attr_line() attr.c: tighten constness around "git_attr" structure ...
-rw-r--r--Documentation/gitattributes.txt10
-rw-r--r--Documentation/technical/api-gitattributes.txt86
-rw-r--r--archive.c24
-rw-r--r--attr.c878
-rw-r--r--attr.h49
-rw-r--r--builtin/check-attr.c66
-rw-r--r--builtin/pack-objects.c19
-rw-r--r--commit.c3
-rw-r--r--common-main.c3
-rw-r--r--convert.c25
-rw-r--r--ll-merge.c33
-rwxr-xr-xt/t0003-attributes.sh26
-rw-r--r--userdiff.c19
-rw-r--r--ws.c19
14 files changed, 816 insertions, 444 deletions
diff --git a/Documentation/gitattributes.txt b/Documentation/gitattributes.txt
index e0b66c1..a53d093 100644
--- a/Documentation/gitattributes.txt
+++ b/Documentation/gitattributes.txt
@@ -21,9 +21,11 @@ Each line in `gitattributes` file is of form:
pattern attr1 attr2 ...
That is, a pattern followed by an attributes list,
-separated by whitespaces. When the pattern matches the
-path in question, the attributes listed on the line are given to
-the path.
+separated by whitespaces. Leading and trailing whitespaces are
+ignored. Lines that begin with '#' are ignored. Patterns
+that begin with a double quote are quoted in C style.
+When the pattern matches the path in question, the attributes
+listed on the line are given to the path.
Each attribute can be in one of these states for a given path:
@@ -86,7 +88,7 @@ is either not set or empty, $HOME/.config/git/attributes is used instead.
Attributes for all users on a system should be placed in the
`$(prefix)/etc/gitattributes` file.
-Sometimes you would need to override an setting of an attribute
+Sometimes you would need to override a setting of an attribute
for a path to `Unspecified` state. This can be done by listing
the name of the attribute prefixed with an exclamation point `!`.
diff --git a/Documentation/technical/api-gitattributes.txt b/Documentation/technical/api-gitattributes.txt
index 2602668..e7cbb7c 100644
--- a/Documentation/technical/api-gitattributes.txt
+++ b/Documentation/technical/api-gitattributes.txt
@@ -16,10 +16,15 @@ Data Structure
of no interest to the calling programs. The name of the
attribute can be retrieved by calling `git_attr_name()`.
-`struct git_attr_check`::
+`struct attr_check_item`::
- This structure represents a set of attributes to check in a call
- to `git_check_attr()` function, and receives the results.
+ This structure represents one attribute and its value.
+
+`struct attr_check`::
+
+ This structure represents a collection of `attr_check_item`.
+ It is passed to `git_check_attr()` function, specifying the
+ attributes to check, and receives their values.
Attribute Values
@@ -27,7 +32,7 @@ Attribute Values
An attribute for a path can be in one of four states: Set, Unset,
Unspecified or set to a string, and `.value` member of `struct
-git_attr_check` records it. There are three macros to check these:
+attr_check_item` records it. There are three macros to check these:
`ATTR_TRUE()`::
@@ -48,49 +53,51 @@ value of the attribute for the path.
Querying Specific Attributes
----------------------------
-* Prepare an array of `struct git_attr_check` to define the list of
- attributes you would want to check. To populate this array, you would
- need to define necessary attributes by calling `git_attr()` function.
+* Prepare `struct attr_check` using attr_check_initl()
+ function, enumerating the names of attributes whose values you are
+ interested in, terminated with a NULL pointer. Alternatively, an
+ empty `struct attr_check` can be prepared by calling
+ `attr_check_alloc()` function and then attributes you want to
+ ask about can be added to it with `attr_check_append()`
+ function.
* Call `git_check_attr()` to check the attributes for the path.
-* Inspect `git_attr_check` structure to see how each of the attribute in
- the array is defined for the path.
+* Inspect `attr_check` structure to see how each of the
+ attribute in the array is defined for the path.
Example
-------
-To see how attributes "crlf" and "indent" are set for different paths.
+To see how attributes "crlf" and "ident" are set for different paths.
-. Prepare an array of `struct git_attr_check` with two elements (because
- we are checking two attributes). Initialize their `attr` member with
- pointers to `struct git_attr` obtained by calling `git_attr()`:
+. Prepare a `struct attr_check` with two elements (because
+ we are checking two attributes):
------------
-static struct git_attr_check check[2];
+static struct attr_check *check;
static void setup_check(void)
{
- if (check[0].attr)
+ if (check)
return; /* already done */
- check[0].attr = git_attr("crlf");
- check[1].attr = git_attr("ident");
+ check = attr_check_initl("crlf", "ident", NULL);
}
------------
-. Call `git_check_attr()` with the prepared array of `struct git_attr_check`:
+. Call `git_check_attr()` with the prepared `struct attr_check`:
------------
const char *path;
setup_check();
- git_check_attr(path, ARRAY_SIZE(check), check);
+ git_check_attr(path, check);
------------
-. Act on `.value` member of the result, left in `check[]`:
+. Act on `.value` member of the result, left in `check->items[]`:
------------
- const char *value = check[0].value;
+ const char *value = check->items[0].value;
if (ATTR_TRUE(value)) {
The attribute is Set, by listing only the name of the
@@ -109,20 +116,39 @@ static void setup_check(void)
}
------------
+To see how attributes in argv[] are set for different paths, only
+the first step in the above would be different.
+
+------------
+static struct attr_check *check;
+static void setup_check(const char **argv)
+{
+ check = attr_check_alloc();
+ while (*argv) {
+ struct git_attr *attr = git_attr(*argv);
+ attr_check_append(check, attr);
+ argv++;
+ }
+}
+------------
+
Querying All Attributes
-----------------------
To get the values of all attributes associated with a file:
-* Call `git_all_attrs()`, which returns an array of `git_attr_check`
- structures.
+* Prepare an empty `attr_check` structure by calling
+ `attr_check_alloc()`.
+
+* Call `git_all_attrs()`, which populates the `attr_check`
+ with the attributes attached to the path.
-* Iterate over the `git_attr_check` array to examine the attribute
- names and values. The name of the attribute described by a
- `git_attr_check` object can be retrieved via
- `git_attr_name(check[i].attr)`. (Please note that no items will be
- returned for unset attributes, so `ATTR_UNSET()` will return false
- for all returned `git_array_check` objects.)
+* Iterate over the `attr_check.items[]` array to examine
+ the attribute names and values. The name of the attribute
+ described by a `attr_check.items[]` object can be retrieved via
+ `git_attr_name(check->items[i].attr)`. (Please note that no items
+ will be returned for unset attributes, so `ATTR_UNSET()` will return
+ false for all returned `attr_check.items[]` objects.)
-* Free the `git_array_check` array.
+* Free the `attr_check` struct by calling `attr_check_free()`.
diff --git a/archive.c b/archive.c
index 01751e5..60b8891 100644
--- a/archive.c
+++ b/archive.c
@@ -87,19 +87,6 @@ void *sha1_file_to_archive(const struct archiver_args *args,
return buffer;
}
-static void setup_archive_check(struct git_attr_check *check)
-{
- static struct git_attr *attr_export_ignore;
- static struct git_attr *attr_export_subst;
-
- if (!attr_export_ignore) {
- attr_export_ignore = git_attr("export-ignore");
- attr_export_subst = git_attr("export-subst");
- }
- check[0].attr = attr_export_ignore;
- check[1].attr = attr_export_subst;
-}
-
struct directory {
struct directory *up;
struct object_id oid;
@@ -120,10 +107,10 @@ static int write_archive_entry(const unsigned char *sha1, const char *base,
void *context)
{
static struct strbuf path = STRBUF_INIT;
+ static struct attr_check *check;
struct archiver_context *c = context;
struct archiver_args *args = c->args;
write_archive_entry_fn_t write_entry = c->write_entry;
- struct git_attr_check check[2];
const char *path_without_prefix;
int err;
@@ -137,11 +124,12 @@ static int write_archive_entry(const unsigned char *sha1, const char *base,
strbuf_addch(&path, '/');
path_without_prefix = path.buf + args->baselen;
- setup_archive_check(check);
- if (!git_check_attr(path_without_prefix, ARRAY_SIZE(check), check)) {
- if (ATTR_TRUE(check[0].value))
+ if (!check)
+ check = attr_check_initl("export-ignore", "export-subst", NULL);
+ if (!git_check_attr(path_without_prefix, check)) {
+ if (ATTR_TRUE(check->items[0].value))
return 0;
- args->convert = ATTR_TRUE(check[1].value);
+ args->convert = ATTR_TRUE(check->items[1].value);
}
if (S_ISDIR(mode) || S_ISGITLINK(mode)) {
diff --git a/attr.c b/attr.c
index 1fcf042..5493bff 100644
--- a/attr.c
+++ b/attr.c
@@ -13,6 +13,8 @@
#include "attr.h"
#include "dir.h"
#include "utf8.h"
+#include "quote.h"
+#include "thread-utils.h"
const char git_attr__true[] = "(builtin)true";
const char git_attr__false[] = "\0(builtin)false";
@@ -22,99 +24,234 @@ static const char git_attr__unknown[] = "(builtin)unknown";
#define ATTR__UNSET NULL
#define ATTR__UNKNOWN git_attr__unknown
-/* 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;
- int attr_nr;
- int maybe_macro;
- int maybe_real;
- char name[FLEX_ARRAY];
+ int attr_nr; /* unique attribute number */
+ char name[FLEX_ARRAY]; /* attribute name */
};
-static int attr_nr;
-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;
}
-static unsigned hash_name(const char *name, int namelen)
+struct attr_hashmap {
+ struct hashmap map;
+#ifndef NO_PTHREADS
+ pthread_mutex_t mutex;
+#endif
+};
+
+static inline void hashmap_lock(struct attr_hashmap *map)
+{
+#ifndef NO_PTHREADS
+ pthread_mutex_lock(&map->mutex);
+#endif
+}
+
+static inline void hashmap_unlock(struct attr_hashmap *map)
{
- unsigned val = 0, c;
+#ifndef NO_PTHREADS
+ pthread_mutex_unlock(&map->mutex);
+#endif
+}
- while (namelen--) {
- c = *name++;
- val = ((val << 7) | (val >> 22)) ^ c;
+/*
+ * The global dictionary of all interned attributes. This
+ * is a singleton object which is shared between threads.
+ * Access to this dictionary must be surrounded with a mutex.
+ */
+static struct attr_hashmap g_attr_hashmap;
+
+/* The container for objects stored in "struct attr_hashmap" */
+struct attr_hash_entry {
+ struct hashmap_entry ent; /* must be the first member! */
+ const char *key; /* the key; memory should be owned by value */
+ size_t keylen; /* length of the key */
+ void *value; /* the stored value */
+};
+
+/* attr_hashmap comparison function */
+static int attr_hash_entry_cmp(const struct attr_hash_entry *a,
+ const struct attr_hash_entry *b,
+ void *unused)
+{
+ return (a->keylen != b->keylen) || strncmp(a->key, b->key, a->keylen);
+}
+
+/* Initialize an 'attr_hashmap' object */
+static void attr_hashmap_init(struct attr_hashmap *map)
+{
+ hashmap_init(&map->map, (hashmap_cmp_fn) attr_hash_entry_cmp, 0);
+}
+
+/*
+ * Retrieve the 'value' stored in a hashmap given the provided 'key'.
+ * If there is no matching entry, return NULL.
+ */
+static void *attr_hashmap_get(struct attr_hashmap *map,
+ const char *key, size_t keylen)
+{
+ struct attr_hash_entry k;
+ struct attr_hash_entry *e;
+
+ if (!map->map.tablesize)
+ attr_hashmap_init(map);
+
+ hashmap_entry_init(&k, memhash(key, keylen));
+ k.key = key;
+ k.keylen = keylen;
+ e = hashmap_get(&map->map, &k, NULL);
+
+ return e ? e->value : NULL;
+}
+
+/* Add 'value' to a hashmap based on the provided 'key'. */
+static void attr_hashmap_add(struct attr_hashmap *map,
+ const char *key, size_t keylen,
+ void *value)
+{
+ struct attr_hash_entry *e;
+
+ if (!map->map.tablesize)
+ attr_hashmap_init(map);
+
+ e = xmalloc(sizeof(struct attr_hash_entry));
+ hashmap_entry_init(e, memhash(key, keylen));
+ e->key = key;
+ e->keylen = keylen;
+ e->value = value;
+
+ hashmap_add(&map->map, e);
+}
+
+struct all_attrs_item {
+ const struct git_attr *attr;
+ const char *value;
+ /*
+ * If 'macro' is non-NULL, indicates that 'attr' is a macro based on
+ * the current attribute stack and contains a pointer to the match_attr
+ * definition of the macro
+ */
+ const struct match_attr *macro;
+};
+
+/*
+ * Reallocate and reinitialize the array of all attributes (which is used in
+ * the attribute collection process) in 'check' based on the global dictionary
+ * of attributes.
+ */
+static void all_attrs_init(struct attr_hashmap *map, struct attr_check *check)
+{
+ int i;
+
+ hashmap_lock(map);
+
+ if (map->map.size < check->all_attrs_nr)
+ die("BUG: interned attributes shouldn't be deleted");
+
+ /*
+ * If the number of attributes in the global dictionary has increased
+ * (or this attr_check instance doesn't have an initialized all_attrs
+ * field), reallocate the provided attr_check instance's all_attrs
+ * field and fill each entry with its corresponding git_attr.
+ */
+ if (map->map.size != check->all_attrs_nr) {
+ struct attr_hash_entry *e;
+ struct hashmap_iter iter;
+ hashmap_iter_init(&map->map, &iter);
+
+ REALLOC_ARRAY(check->all_attrs, map->map.size);
+ check->all_attrs_nr = map->map.size;
+
+ while ((e = hashmap_iter_next(&iter))) {
+ const struct git_attr *a = e->value;
+ check->all_attrs[a->attr_nr].attr = a;
+ }
+ }
+
+ hashmap_unlock(map);
+
+ /*
+ * Re-initialize every entry in check->all_attrs.
+ * This re-initialization can live outside of the locked region since
+ * the attribute dictionary is no longer being accessed.
+ */
+ for (i = 0; i < check->all_attrs_nr; i++) {
+ check->all_attrs[i].value = ATTR__UNKNOWN;
+ check->all_attrs[i].macro = NULL;
}
- return val;
}
-static int invalid_attr_name(const char *name, int namelen)
+static int attr_name_valid(const char *name, size_t namelen)
{
/*
* Attribute name cannot begin with '-' and must consist of
* characters from [-A-Za-z0-9_.].
*/
if (namelen <= 0 || *name == '-')
- return -1;
+ return 0;
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;
}
- return 0;
+ return 1;
}
-static struct git_attr *git_attr_internal(const char *name, int len)
+static void report_invalid_attr(const char *name, size_t len,
+ const char *src, int lineno)
+{
+ struct strbuf err = STRBUF_INIT;
+ strbuf_addf(&err, _("%.*s is not a valid attribute name"),
+ (int) len, name);
+ fprintf(stderr, "%s: %s:%d\n", err.buf, src, lineno);
+ strbuf_release(&err);
+}
+
+/*
+ * Given a 'name', lookup and return the corresponding attribute in the global
+ * dictionary. If no entry is found, create a new attribute and store it in
+ * the dictionary.
+ */
+static const struct git_attr *git_attr_internal(const char *name, int namelen)
{
- 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;
+ if (!attr_name_valid(name, namelen))
+ return NULL;
+
+ hashmap_lock(&g_attr_hashmap);
+
+ a = attr_hashmap_get(&g_attr_hashmap, name, namelen);
+
+ if (!a) {
+ FLEX_ALLOC_MEM(a, name, name, namelen);
+ a->attr_nr = g_attr_hashmap.map.size;
+
+ attr_hashmap_add(&g_attr_hashmap, a->name, namelen, a);
+ assert(a->attr_nr == (g_attr_hashmap.map.size - 1));
}
- if (invalid_attr_name(name, len))
- return NULL;
+ hashmap_unlock(&g_attr_hashmap);
- FLEX_ALLOC_MEM(a, name, name, len);
- a->h = hval;
- a->next = git_attr_hash[pos];
- a->attr_nr = attr_nr++;
- a->maybe_macro = 0;
- a->maybe_real = 0;
- git_attr_hash[pos] = a;
-
- REALLOC_ARRAY(check_all_attr, attr_nr);
- check_all_attr[a->attr_nr].attr = a;
- check_all_attr[a->attr_nr].value = ATTR__UNKNOWN;
return a;
}
-struct git_attr *git_attr(const char *name)
+const struct git_attr *git_attr(const char *name)
{
return git_attr_internal(name, strlen(name));
}
/* What does a matched pattern decide? */
struct attr_state {
- struct git_attr *attr;
+ const struct git_attr *attr;
const char *setto;
};
@@ -131,9 +268,8 @@ struct pattern {
* If is_macro is true, then u.attr is a pointer to the git_attr being
* defined.
*
- * If is_macro is false, then u.pattern points at the filename pattern
- * to which the rule applies. (The memory pointed to is part of the
- * memory block allocated for the match_attr instance.)
+ * If is_macro is false, then u.pat is the filename pattern to which the
+ * rule applies.
*
* In either case, num_attr is the number of attributes affected by
* this rule, and state is an array listing them. The attributes are
@@ -142,7 +278,7 @@ struct pattern {
struct match_attr {
union {
struct pattern pat;
- struct git_attr *attr;
+ const struct git_attr *attr;
} u;
char is_macro;
unsigned num_attr;
@@ -177,13 +313,17 @@ static const char *parse_attr(const char *src, int lineno, const char *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);
+ if (!attr_name_valid(cp, len)) {
+ report_invalid_attr(cp, len, src, lineno);
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 attr_name_valid()
+ * check here.
+ */
if (*cp == '-' || *cp == '!') {
e->setto = (*cp == '-') ? ATTR__FALSE : ATTR__UNSET;
cp++;
@@ -207,41 +347,47 @@ 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);
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;
+ if (!attr_name_valid(name, namelen)) {
+ report_invalid_attr(name, namelen, src, lineno);
+ 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,
@@ -250,7 +396,6 @@ static struct match_attr *parse_attr_line(const char *line, const char *src,
(is_macro ? 0 : namelen + 1));
if (is_macro) {
res->u.attr = git_attr_internal(name, namelen);
- res->u.attr->maybe_macro = 1;
} else {
char *p = (char *)&(res->state[num_attr]);
memcpy(p, name, namelen);
@@ -262,7 +407,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;
@@ -271,13 +416,15 @@ static struct match_attr *parse_attr_line(const char *line, const char *src,
/* Second pass to fill the attr_states */
for (cp = states, i = 0; *cp; i++) {
cp = parse_attr(src, lineno, cp, &(res->state[i]));
- if (!is_macro)
- res->state[i].attr->maybe_real = 1;
- if (res->state[i].attr->maybe_macro)
- cannot_trust_maybe_real = 1;
}
+ strbuf_release(&pattern);
return res;
+
+fail_return:
+ strbuf_release(&pattern);
+ free(res);
+ return NULL;
}
/*
@@ -295,19 +442,19 @@ 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 {
+struct attr_stack {
struct attr_stack *prev;
char *origin;
size_t originlen;
unsigned num_matches;
unsigned alloc;
struct match_attr **attrs;
-} *attr_stack;
+};
-static void free_attr_elem(struct attr_stack *e)
+static void attr_stack_free(struct attr_stack *e)
{
int i;
free(e->origin);
@@ -330,6 +477,173 @@ static void free_attr_elem(struct attr_stack *e)
free(e);
}
+static void drop_attr_stack(struct attr_stack **stack)
+{
+ while (*stack) {
+ struct attr_stack *elem = *stack;
+ *stack = elem->prev;
+ attr_stack_free(elem);
+ }
+}
+
+/* List of all attr_check structs; access should be surrounded by mutex */
+static struct check_vector {
+ size_t nr;
+ size_t alloc;
+ struct attr_check **checks;
+#ifndef NO_PTHREADS
+ pthread_mutex_t mutex;
+#endif
+} check_vector;
+
+static inline void vector_lock(void)
+{
+#ifndef NO_PTHREADS
+ pthread_mutex_lock(&check_vector.mutex);
+#endif
+}
+
+static inline void vector_unlock(void)
+{
+#ifndef NO_PTHREADS
+ pthread_mutex_unlock(&check_vector.mutex);
+#endif
+}
+
+static void check_vector_add(struct attr_check *c)
+{
+ vector_lock();
+
+ ALLOC_GROW(check_vector.checks,
+ check_vector.nr + 1,
+ check_vector.alloc);
+ check_vector.checks[check_vector.nr++] = c;
+
+ vector_unlock();
+}
+
+static void check_vector_remove(struct attr_check *check)
+{
+ int i;
+
+ vector_lock();
+
+ /* Find entry */
+ for (i = 0; i < check_vector.nr; i++)
+ if (check_vector.checks[i] == check)
+ break;
+
+ if (i >= check_vector.nr)
+ die("BUG: no entry found");
+
+ /* shift entries over */
+ for (; i < check_vector.nr - 1; i++)
+ check_vector.checks[i] = check_vector.checks[i + 1];
+
+ check_vector.nr--;
+
+ vector_unlock();
+}
+
+/* Iterate through all attr_check instances and drop their stacks */
+static void drop_all_attr_stacks(void)
+{
+ int i;
+
+ vector_lock();
+
+ for (i = 0; i < check_vector.nr; i++) {
+ drop_attr_stack(&check_vector.checks[i]->stack);
+ }
+
+ vector_unlock();
+}
+
+struct attr_check *attr_check_alloc(void)
+{
+ struct attr_check *c = xcalloc(1, sizeof(struct attr_check));
+
+ /* save pointer to the check struct */
+ check_vector_add(c);
+
+ return c;
+}
+
+struct attr_check *attr_check_initl(const char *one, ...)
+{
+ struct attr_check *check;
+ int cnt;
+ va_list params;
+ const char *param;
+
+ va_start(params, one);
+ for (cnt = 1; (param = va_arg(params, const char *)) != NULL; cnt++)
+ ;
+ va_end(params);
+
+ check = attr_check_alloc();
+ check->nr = cnt;
+ check->alloc = cnt;
+ check->items = xcalloc(cnt, sizeof(struct attr_check_item));
+
+ check->items[0].attr = git_attr(one);
+ va_start(params, one);
+ for (cnt = 1; cnt < check->nr; cnt++) {
+ const struct git_attr *attr;
+ param = va_arg(params, const char *);
+ if (!param)
+ die("BUG: counted %d != ended at %d",
+ check->nr, cnt);
+ attr = git_attr(param);
+ if (!attr)
+ die("BUG: %s: not a valid attribute name", param);
+ check->items[cnt].attr = attr;
+ }
+ va_end(params);
+ return check;
+}
+
+struct attr_check_item *attr_check_append(struct attr_check *check,
+ const struct git_attr *attr)
+{
+ struct attr_check_item *item;
+
+ ALLOC_GROW(check->items, check->nr + 1, check->alloc);
+ item = &check->items[check->nr++];
+ item->attr = attr;
+ return item;
+}
+
+void attr_check_reset(struct attr_check *check)
+{
+ check->nr = 0;
+}
+
+void attr_check_clear(struct attr_check *check)
+{
+ free(check->items);
+ check->items = NULL;
+ check->alloc = 0;
+ check->nr = 0;
+
+ free(check->all_attrs);
+ check->all_attrs = NULL;
+ check->all_attrs_nr = 0;
+
+ drop_attr_stack(&check->stack);
+}
+
+void attr_check_free(struct attr_check *check)
+{
+ if (check) {
+ /* Remove check from the check vector */
+ check_vector_remove(check);
+
+ attr_check_clear(check);
+ free(check);
+ }
+}
+
static const char *builtin_attr[] = {
"[attr]binary -diff -merge -text",
NULL,
@@ -362,9 +676,31 @@ static struct attr_stack *read_attr_from_array(const char **list)
return res;
}
+/*
+ * Callers into the attribute system assume there is a single, system-wide
+ * global state where attributes are read from and when the state is flipped by
+ * calling git_attr_set_direction(), the stack frames that have been
+ * constructed need to be discarded so so that subsequent calls into the
+ * attribute system will lazily read from the right place. Since changing
+ * direction causes a global paradigm shift, it should not ever be called while
+ * another thread could potentially be calling into the attribute system.
+ */
static enum git_attr_direction direction;
static struct index_state *use_index;
+void git_attr_set_direction(enum git_attr_direction new_direction,
+ struct index_state *istate)
+{
+ if (is_bare_repository() && new_direction != GIT_ATTR_INDEX)
+ die("BUG: non-INDEX attr direction in a bare repo");
+
+ if (new_direction != direction)
+ drop_all_attr_stacks();
+
+ direction = new_direction;
+ use_index = istate;
+}
+
static struct attr_stack *read_attr_from_file(const char *path, int macro_ok)
{
FILE *fp = fopen(path, "r");
@@ -402,8 +738,8 @@ static struct attr_stack *read_attr_from_index(const char *path, int macro_ok)
for (sp = buf; *sp; ) {
char *ep;
int more;
- for (ep = sp; *ep && *ep != '\n'; ep++)
- ;
+
+ ep = strchrnul(sp, '\n');
more = (*ep == '\n');
*ep = '\0';
handle_attr_line(res, sp, path, ++lineno, macro_ok);
@@ -415,25 +751,28 @@ static struct attr_stack *read_attr_from_index(const char *path, int macro_ok)
static struct attr_stack *read_attr(const char *path, int macro_ok)
{
- struct attr_stack *res;
+ struct attr_stack *res = NULL;
- if (direction == GIT_ATTR_CHECKOUT) {
+ if (direction == GIT_ATTR_INDEX) {
res = read_attr_from_index(path, macro_ok);
- if (!res)
- res = read_attr_from_file(path, macro_ok);
- }
- else if (direction == GIT_ATTR_CHECKIN) {
- res = read_attr_from_file(path, macro_ok);
- if (!res)
- /*
- * There is no checked out .gitattributes file there, but
- * we might have it in the index. We allow operation in a
- * sparsely checked out work tree, so read from it.
- */
+ } else if (!is_bare_repository()) {
+ if (direction == GIT_ATTR_CHECKOUT) {
res = read_attr_from_index(path, macro_ok);
+ if (!res)
+ res = read_attr_from_file(path, macro_ok);
+ } else if (direction == GIT_ATTR_CHECKIN) {
+ res = read_attr_from_file(path, macro_ok);
+ if (!res)
+ /*
+ * There is no checked out .gitattributes file
+ * there, but we might have it in the index.
+ * We allow operation in a sparsely checked out
+ * work tree, so read from it.
+ */
+ res = read_attr_from_index(path, macro_ok);
+ }
}
- else
- res = read_attr_from_index(path, macro_ok);
+
if (!res)
res = xcalloc(1, sizeof(*res));
return res;
@@ -464,16 +803,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
-
-static void drop_attr_stack(void)
-{
- while (attr_stack) {
- struct attr_stack *elem = attr_stack;
- attr_stack = elem->prev;
- free_attr_elem(elem);
- }
-}
+#endif /* DEBUG_ATTR */
static const char *git_etc_gitattributes(void)
{
@@ -483,6 +813,14 @@ static const char *git_etc_gitattributes(void)
return system_wide;
}
+static const char *get_home_gitattributes(void)
+{
+ if (!git_attributes_file)
+ git_attributes_file = xdg_config_home("attributes");
+
+ return git_attributes_file;
+}
+
static int git_attr_system(void)
{
return !git_env_bool("GIT_ATTR_NOSYSTEM", 0);
@@ -490,64 +828,60 @@ static int git_attr_system(void)
static GIT_PATH_FUNC(git_path_info_attributes, INFOATTRIBUTES_FILE)
-static void bootstrap_attr_stack(void)
+static void push_stack(struct attr_stack **attr_stack_p,
+ struct attr_stack *elem, char *origin, size_t originlen)
{
- struct attr_stack *elem;
+ if (elem) {
+ elem->origin = origin;
+ if (origin)
+ elem->originlen = originlen;
+ elem->prev = *attr_stack_p;
+ *attr_stack_p = elem;
+ }
+}
- if (attr_stack)
+static void bootstrap_attr_stack(struct attr_stack **stack)
+{
+ struct attr_stack *e;
+
+ if (*stack)
return;
- elem = read_attr_from_array(builtin_attr);
- elem->origin = NULL;
- elem->prev = attr_stack;
- attr_stack = elem;
+ /* builtin frame */
+ e = read_attr_from_array(builtin_attr);
+ push_stack(stack, e, NULL, 0);
+ /* system-wide frame */
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;
- }
+ e = read_attr_from_file(git_etc_gitattributes(), 1);
+ push_stack(stack, e, 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;
- }
+ /* home directory */
+ if (get_home_gitattributes()) {
+ e = read_attr_from_file(get_home_gitattributes(), 1);
+ push_stack(stack, e, 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;
- debug_push(elem);
- }
+ /* root directory */
+ e = read_attr(GITATTRIBUTES_FILE, 1);
+ push_stack(stack, e, xstrdup(""), 0);
+ /* info frame */
if (startup_info->have_repository)
- elem = read_attr_from_file(git_path_info_attributes(), 1);
+ e = read_attr_from_file(git_path_info_attributes(), 1);
else
- elem = NULL;
-
- if (!elem)
- elem = xcalloc(1, sizeof(*elem));
- elem->origin = NULL;
- elem->prev = attr_stack;
- attr_stack = elem;
+ e = NULL;
+ if (!e)
+ e = xcalloc(1, sizeof(struct attr_stack));
+ push_stack(stack, e, NULL, 0);
}
-static void prepare_attr_stack(const char *path, int dirlen)
+static void prepare_attr_stack(const char *path, int dirlen,
+ struct attr_stack **stack)
{
- struct attr_stack *elem, *info;
- int len;
- const char *cp;
+ struct attr_stack *info;
+ struct strbuf pathbuf = STRBUF_INIT;
/*
* At the bottom of the attribute stack is the built-in
@@ -564,13 +898,13 @@ 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.
*/
- bootstrap_attr_stack();
+ bootstrap_attr_stack(stack);
/*
* Pop the "info" one that is always at the top of the stack.
*/
- info = attr_stack;
- attr_stack = info->prev;
+ info = *stack;
+ *stack = info->prev;
/*
* Pop the ones from directories that are not the prefix of
@@ -578,59 +912,63 @@ static void prepare_attr_stack(const char *path, int dirlen)
* the root one (whose origin is an empty string "") or the builtin
* one (whose origin is NULL) without popping it.
*/
- while (attr_stack->origin) {
- int namelen = strlen(attr_stack->origin);
+ while ((*stack)->origin) {
+ int namelen = (*stack)->originlen;
+ struct attr_stack *elem;
- elem = attr_stack;
+ elem = *stack;
if (namelen <= dirlen &&
!strncmp(elem->origin, path, namelen) &&
(!namelen || path[namelen] == '/'))
break;
debug_pop(elem);
- attr_stack = elem->prev;
- free_attr_elem(elem);
+ *stack = elem->prev;
+ attr_stack_free(elem);
}
/*
- * Read from parent directories and push them down
+ * bootstrap_attr_stack() should have added, and the
+ * above loop should have stopped before popping, the
+ * root element whose attr_stack->origin is set to an
+ * empty string.
*/
- if (!is_bare_repository() || direction == GIT_ATTR_INDEX) {
- /*
- * bootstrap_attr_stack() should have added, and the
- * above loop should have stopped before popping, the
- * root element whose attr_stack->origin is set to an
- * empty string.
- */
- struct strbuf pathbuf = STRBUF_INIT;
-
- assert(attr_stack->origin);
- while (1) {
- len = strlen(attr_stack->origin);
- if (dirlen <= len)
- break;
- cp = memchr(path + len + 1, '/', dirlen - len - 1);
- if (!cp)
- cp = path + dirlen;
- strbuf_add(&pathbuf, path, cp - path);
+ assert((*stack)->origin);
+
+ strbuf_addstr(&pathbuf, (*stack)->origin);
+ /* Build up to the directory 'path' is in */
+ while (pathbuf.len < dirlen) {
+ size_t len = pathbuf.len;
+ struct attr_stack *next;
+ char *origin;
+
+ /* Skip path-separator */
+ if (len < dirlen && is_dir_sep(path[len]))
+ len++;
+ /* Find the end of the next component */
+ while (len < dirlen && !is_dir_sep(path[len]))
+ len++;
+
+ if (pathbuf.len > 0)
strbuf_addch(&pathbuf, '/');
- strbuf_addstr(&pathbuf, 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;
- debug_push(elem);
- }
+ strbuf_add(&pathbuf, path + pathbuf.len, (len - pathbuf.len));
+ strbuf_addf(&pathbuf, "/%s", GITATTRIBUTES_FILE);
+
+ next = read_attr(pathbuf.buf, 0);
- strbuf_release(&pathbuf);
+ /* reset the pathbuf to not include "/.gitattributes" */
+ strbuf_setlen(&pathbuf, len);
+
+ origin = xstrdup(pathbuf.buf);
+ push_stack(stack, next, origin, len);
}
/*
* Finally push the "info" one at the top of the stack.
*/
- info->prev = attr_stack;
- attr_stack = info;
+ push_stack(stack, info, NULL, 0);
+
+ strbuf_release(&pathbuf);
}
static int path_matches(const char *pathname, int pathlen,
@@ -656,16 +994,16 @@ static int path_matches(const char *pathname, int pathlen,
pattern, prefix, pat->patternlen, pat->flags);
}
-static int macroexpand_one(int attr_nr, int rem);
+static int macroexpand_one(struct all_attrs_item *all_attrs, int nr, int rem);
-static int fill_one(const char *what, struct match_attr *a, int rem)
+static int fill_one(const char *what, struct all_attrs_item *all_attrs,
+ const struct match_attr *a, int rem)
{
- struct git_attr_check *check = check_all_attr;
int i;
- for (i = a->num_attr - 1; 0 < rem && 0 <= i; i--) {
- struct git_attr *attr = a->state[i].attr;
- const char **n = &(check[attr->attr_nr].value);
+ for (i = a->num_attr - 1; rem > 0 && i >= 0; i--) {
+ const struct git_attr *attr = a->state[i].attr;
+ const char **n = &(all_attrs[attr->attr_nr].value);
const char *v = a->state[i].setto;
if (*n == ATTR__UNKNOWN) {
@@ -674,64 +1012,72 @@ static int fill_one(const char *what, struct match_attr *a, int rem)
attr, v);
*n = v;
rem--;
- rem = macroexpand_one(attr->attr_nr, rem);
+ rem = macroexpand_one(all_attrs, attr->attr_nr, rem);
}
}
return rem;
}
static int fill(const char *path, int pathlen, int basename_offset,
- struct attr_stack *stk, int rem)
+ const struct attr_stack *stack,
+ struct all_attrs_item *all_attrs, int rem)
{
- int i;
- const char *base = stk->origin ? stk->origin : "";
+ for (; rem > 0 && stack; stack = stack->prev) {
+ int i;
+ const char *base = stack->origin ? stack->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, basename_offset,
- &a->u.pat, base, stk->originlen))
- rem = fill_one("fill", a, rem);
+ for (i = stack->num_matches - 1; 0 < rem && 0 <= i; i--) {
+ const struct match_attr *a = stack->attrs[i];
+ if (a->is_macro)
+ continue;
+ if (path_matches(path, pathlen, basename_offset,
+ &a->u.pat, base, stack->originlen))
+ rem = fill_one("fill", all_attrs, a, rem);
+ }
}
+
return rem;
}
-static int macroexpand_one(int nr, int rem)
+static int macroexpand_one(struct all_attrs_item *all_attrs, int nr, int rem)
{
- struct attr_stack *stk;
- struct match_attr *a = NULL;
- int i;
+ const struct all_attrs_item *item = &all_attrs[nr];
- if (check_all_attr[nr].value != ATTR__TRUE ||
- !check_all_attr[nr].attr->maybe_macro)
+ if (item->macro && item->value == ATTR__TRUE)
+ return fill_one("expand", all_attrs, item->macro, rem);
+ else
return rem;
+}
- for (stk = attr_stack; !a && stk; stk = stk->prev)
- for (i = stk->num_matches - 1; !a && 0 <= i; i--) {
- struct match_attr *ma = stk->attrs[i];
- if (!ma->is_macro)
- continue;
- if (ma->u.attr->attr_nr == nr)
- a = ma;
+/*
+ * Marks the attributes which are macros based on the attribute stack.
+ * This prevents having to search through the attribute stack each time
+ * a macro needs to be expanded during the fill stage.
+ */
+static void determine_macros(struct all_attrs_item *all_attrs,
+ const struct attr_stack *stack)
+{
+ for (; stack; stack = stack->prev) {
+ int i;
+ for (i = stack->num_matches - 1; i >= 0; i--) {
+ const struct match_attr *ma = stack->attrs[i];
+ if (ma->is_macro) {
+ int n = ma->u.attr->attr_nr;
+ if (!all_attrs[n].macro) {
+ all_attrs[n].macro = ma;
+ }
+ }
}
-
- if (a)
- rem = fill_one("expand", a, rem);
-
- return rem;
+ }
}
/*
- * Collect attributes for path into the array pointed to by
- * check_all_attr. If num is non-zero, only attributes in check[] are
- * collected. Otherwise all attributes are collected.
+ * Collect attributes for path into the array pointed to by check->all_attrs.
+ * If check->check_nr is non-zero, only attributes in check[] are collected.
+ * Otherwise all attributes are collected.
*/
-static void collect_some_attrs(const char *path, int num,
- struct git_attr_check *check)
-
+static void collect_some_attrs(const char *path, struct attr_check *check)
{
- struct attr_stack *stk;
int i, pathlen, rem, dirlen;
const char *cp, *last_slash = NULL;
int basename_offset;
@@ -749,81 +1095,67 @@ static void collect_some_attrs(const char *path, int num,
dirlen = 0;
}
- prepare_attr_stack(path, dirlen);
- for (i = 0; i < attr_nr; i++)
- check_all_attr[i].value = ATTR__UNKNOWN;
- if (num && !cannot_trust_maybe_real) {
+ prepare_attr_stack(path, dirlen, &check->stack);
+ all_attrs_init(&g_attr_hashmap, check);
+ determine_macros(check->all_attrs, check->stack);
+
+ if (check->nr) {
rem = 0;
- for (i = 0; i < num; i++) {
- if (!check[i].attr->maybe_real) {
- struct git_attr_check *c;
- c = check_all_attr + check[i].attr->attr_nr;
- c->value = ATTR__UNSET;
+ for (i = 0; i < check->nr; i++) {
+ int n = check->items[i].attr->attr_nr;
+ struct all_attrs_item *item = &check->all_attrs[n];
+ if (item->macro) {
+ item->value = ATTR__UNSET;
rem++;
}
}
- if (rem == num)
+ if (rem == check->nr)
return;
}
- rem = attr_nr;
- for (stk = attr_stack; 0 < rem && stk; stk = stk->prev)
- rem = fill(path, pathlen, basename_offset, stk, rem);
+ rem = check->all_attrs_nr;
+ fill(path, pathlen, basename_offset, check->stack, check->all_attrs, rem);
}
-int git_check_attr(const char *path, int num, struct git_attr_check *check)
+int git_check_attr(const char *path, struct attr_check *check)
{
int i;
- collect_some_attrs(path, num, check);
+ collect_some_attrs(path, check);
- for (i = 0; i < num; i++) {
- const char *value = check_all_attr[check[i].attr->attr_nr].value;
+ for (i = 0; i < check->nr; i++) {
+ size_t n = check->items[i].attr->attr_nr;
+ const char *value = check->all_attrs[n].value;
if (value == ATTR__UNKNOWN)
value = ATTR__UNSET;
- check[i].value = value;
+ check->items[i].value = value;
}
return 0;
}
-int git_all_attrs(const char *path, int *num, struct git_attr_check **check)
+void git_all_attrs(const char *path, struct attr_check *check)
{
- int i, count, j;
+ int i;
- collect_some_attrs(path, 0, NULL);
+ attr_check_reset(check);
+ collect_some_attrs(path, check);
- /* Count the number of attributes that are set. */
- count = 0;
- for (i = 0; i < attr_nr; i++) {
- const char *value = check_all_attr[i].value;
- if (value != ATTR__UNSET && value != ATTR__UNKNOWN)
- ++count;
- }
- *num = count;
- ALLOC_ARRAY(*check, count);
- j = 0;
- for (i = 0; i < attr_nr; i++) {
- const char *value = check_all_attr[i].value;
- if (value != ATTR__UNSET && value != ATTR__UNKNOWN) {
- (*check)[j].attr = check_all_attr[i].attr;
- (*check)[j].value = value;
- ++j;
- }
+ for (i = 0; i < check->all_attrs_nr; i++) {
+ const char *name = check->all_attrs[i].attr->name;
+ const char *value = check->all_attrs[i].value;
+ struct attr_check_item *item;
+ if (value == ATTR__UNSET || value == ATTR__UNKNOWN)
+ continue;
+ item = attr_check_append(check, git_attr(name));
+ item->value = value;
}
-
- return 0;
}
-void git_attr_set_direction(enum git_attr_direction new, struct index_state *istate)
+void attr_start(void)
{
- enum git_attr_direction old = direction;
-
- if (is_bare_repository() && new != GIT_ATTR_INDEX)
- die("BUG: non-INDEX attr direction in a bare repo");
-
- direction = new;
- if (new != old)
- drop_attr_stack();
- use_index = istate;
+#ifndef NO_PTHREADS
+ pthread_mutex_init(&g_attr_hashmap.mutex, NULL);
+ pthread_mutex_init(&check_vector.mutex, NULL);
+#endif
}
diff --git a/attr.h b/attr.h
index 8b08d33..48ab3e1 100644
--- a/attr.h
+++ b/attr.h
@@ -4,11 +4,15 @@
/* An attribute is a pointer to this opaque structure */
struct git_attr;
+/* opaque structures used internally for attribute collection */
+struct all_attrs_item;
+struct attr_stack;
+
/*
* Given a string, return the gitattribute object that
* corresponds to it.
*/
-struct git_attr *git_attr(const char *);
+const struct git_attr *git_attr(const char *);
/* Internal use */
extern const char git_attr__true[];
@@ -20,38 +24,57 @@ extern const char git_attr__false[];
#define ATTR_UNSET(v) ((v) == NULL)
/*
- * Send one or more git_attr_check to git_check_attr(), and
+ * Send one or more git_attr_check to git_check_attrs(), and
* each 'value' member tells what its value is.
* Unset one is returned as NULL.
*/
-struct git_attr_check {
- struct git_attr *attr;
+struct attr_check_item {
+ const struct git_attr *attr;
const char *value;
};
+struct attr_check {
+ int nr;
+ int alloc;
+ struct attr_check_item *items;
+ int all_attrs_nr;
+ struct all_attrs_item *all_attrs;
+ struct attr_stack *stack;
+};
+
+extern struct attr_check *attr_check_alloc(void);
+extern struct attr_check *attr_check_initl(const char *, ...);
+
+extern struct attr_check_item *attr_check_append(struct attr_check *check,
+ const struct git_attr *attr);
+
+extern void attr_check_reset(struct attr_check *check);
+extern void attr_check_clear(struct attr_check *check);
+extern void attr_check_free(struct attr_check *check);
+
/*
* Return the name of the attribute represented by the argument. The
* return value is a pointer to a null-delimited string that is part
* of the internal data structure; it should not be modified or freed.
*/
-char *git_attr_name(struct git_attr *);
+extern const char *git_attr_name(const struct git_attr *);
-int git_check_attr(const char *path, int, struct git_attr_check *);
+extern int git_check_attr(const char *path, struct attr_check *check);
/*
- * Retrieve all attributes that apply to the specified path. *num
- * will be set to the number of attributes on the path; **check will
- * be set to point at a newly-allocated array of git_attr_check
- * objects describing the attributes and their values. *check must be
- * free()ed by the caller.
+ * Retrieve all attributes that apply to the specified path.
+ * check holds the attributes and their values.
*/
-int git_all_attrs(const char *path, int *num, struct git_attr_check **check);
+extern void git_all_attrs(const char *path, struct attr_check *check);
enum git_attr_direction {
GIT_ATTR_CHECKIN,
GIT_ATTR_CHECKOUT,
GIT_ATTR_INDEX
};
-void git_attr_set_direction(enum git_attr_direction, struct index_state *);
+void git_attr_set_direction(enum git_attr_direction new_direction,
+ struct index_state *istate);
+
+extern void attr_start(void);
#endif /* ATTR_H */
diff --git a/builtin/check-attr.c b/builtin/check-attr.c
index 53a5a18..4d01ca0 100644
--- a/builtin/check-attr.c
+++ b/builtin/check-attr.c
@@ -24,12 +24,13 @@ static const struct option check_attr_options[] = {
OPT_END()
};
-static void output_attr(int cnt, struct git_attr_check *check,
- const char *file)
+static void output_attr(struct attr_check *check, const char *file)
{
int j;
+ int cnt = check->nr;
+
for (j = 0; j < cnt; j++) {
- const char *value = check[j].value;
+ const char *value = check->items[j].value;
if (ATTR_TRUE(value))
value = "set";
@@ -42,35 +43,38 @@ static void output_attr(int cnt, struct git_attr_check *check,
printf("%s%c" /* path */
"%s%c" /* attrname */
"%s%c" /* attrvalue */,
- file, 0, git_attr_name(check[j].attr), 0, value, 0);
+ file, 0,
+ git_attr_name(check->items[j].attr), 0, value, 0);
} else {
quote_c_style(file, NULL, stdout, 0);
- printf(": %s: %s\n", git_attr_name(check[j].attr), value);
+ printf(": %s: %s\n",
+ git_attr_name(check->items[j].attr), value);
}
-
}
}
-static void check_attr(const char *prefix, int cnt,
- struct git_attr_check *check, const char *file)
+static void check_attr(const char *prefix,
+ struct attr_check *check,
+ int collect_all,
+ const char *file)
{
char *full_path =
prefix_path(prefix, prefix ? strlen(prefix) : 0, file);
- if (check != NULL) {
- if (git_check_attr(full_path, cnt, check))
- die("git_check_attr died");
- output_attr(cnt, check, file);
+
+ if (collect_all) {
+ git_all_attrs(full_path, check);
} else {
- if (git_all_attrs(full_path, &cnt, &check))
- die("git_all_attrs died");
- output_attr(cnt, check, file);
- free(check);
+ if (git_check_attr(full_path, check))
+ die("git_check_attr died");
}
+ output_attr(check, file);
+
free(full_path);
}
-static void check_attr_stdin_paths(const char *prefix, int cnt,
- struct git_attr_check *check)
+static void check_attr_stdin_paths(const char *prefix,
+ struct attr_check *check,
+ int collect_all)
{
struct strbuf buf = STRBUF_INIT;
struct strbuf unquoted = STRBUF_INIT;
@@ -84,7 +88,7 @@ static void check_attr_stdin_paths(const char *prefix, int cnt,
die("line is badly quoted");
strbuf_swap(&buf, &unquoted);
}
- check_attr(prefix, cnt, check, buf.buf);
+ check_attr(prefix, check, collect_all, buf.buf);
maybe_flush_or_die(stdout, "attribute to stdout");
}
strbuf_release(&buf);
@@ -99,7 +103,7 @@ static NORETURN void error_with_usage(const char *msg)
int cmd_check_attr(int argc, const char **argv, const char *prefix)
{
- struct git_attr_check *check;
+ struct attr_check *check;
int cnt, i, doubledash, filei;
if (!is_bare_repository())
@@ -159,28 +163,26 @@ int cmd_check_attr(int argc, const char **argv, const char *prefix)
error_with_usage("No file specified");
}
- if (all_attrs) {
- check = NULL;
- } else {
- check = xcalloc(cnt, sizeof(*check));
+ check = attr_check_alloc();
+ if (!all_attrs) {
for (i = 0; i < cnt; i++) {
- const char *name;
- struct git_attr *a;
- name = argv[i];
- a = git_attr(name);
+ const struct git_attr *a = git_attr(argv[i]);
+
if (!a)
return error("%s: not a valid attribute name",
- name);
- check[i].attr = a;
+ argv[i]);
+ attr_check_append(check, a);
}
}
if (stdin_paths)
- check_attr_stdin_paths(prefix, cnt, check);
+ check_attr_stdin_paths(prefix, check, all_attrs);
else {
for (i = filei; i < argc; i++)
- check_attr(prefix, cnt, check, argv[i]);
+ check_attr(prefix, check, all_attrs, argv[i]);
maybe_flush_or_die(stdout, "attribute to stdout");
}
+
+ attr_check_free(check);
return 0;
}
diff --git a/builtin/pack-objects.c b/builtin/pack-objects.c
index c7af475..f294dcf 100644
--- a/builtin/pack-objects.c
+++ b/builtin/pack-objects.c
@@ -894,24 +894,15 @@ static void write_pack_file(void)
written, nr_result);
}
-static void setup_delta_attr_check(struct git_attr_check *check)
-{
- static struct git_attr *attr_delta;
-
- if (!attr_delta)
- attr_delta = git_attr("delta");
-
- check[0].attr = attr_delta;
-}
-
static int no_try_delta(const char *path)
{
- struct git_attr_check check[1];
+ static struct attr_check *check;
- setup_delta_attr_check(check);
- if (git_check_attr(path, ARRAY_SIZE(check), check))
+ if (!check)
+ check = attr_check_initl("delta", NULL);
+ if (git_check_attr(path, check))
return 0;
- if (ATTR_FALSE(check->value))
+ if (ATTR_FALSE(check->items[0].value))
return 1;
return 0;
}
diff --git a/commit.c b/commit.c
index 2cf8515..0c4ee3d 100644
--- a/commit.c
+++ b/commit.c
@@ -415,8 +415,7 @@ int find_commit_subject(const char *commit_buffer, const char **subject)
p++;
if (*p) {
p = skip_blank_lines(p + 2);
- for (eol = p; *eol && *eol != '\n'; eol++)
- ; /* do nothing */
+ eol = strchrnul(p, '\n');
} else
eol = p;
diff --git a/common-main.c b/common-main.c
index c654f95..6a68900 100644
--- a/common-main.c
+++ b/common-main.c
@@ -1,5 +1,6 @@
#include "cache.h"
#include "exec_cmd.h"
+#include "attr.h"
/*
* Many parts of Git have subprograms communicate via pipe, expect the
@@ -33,6 +34,8 @@ int main(int argc, const char **argv)
git_setup_gettext();
+ attr_start();
+
git_extract_argv0_path(argv[0]);
restore_sigpipe_to_default();
diff --git a/convert.c b/convert.c
index 4e17e45..8d652bf 100644
--- a/convert.c
+++ b/convert.c
@@ -1028,7 +1028,7 @@ static int ident_to_worktree(const char *path, const char *src, size_t len,
return 1;
}
-static enum crlf_action git_path_check_crlf(struct git_attr_check *check)
+static enum crlf_action git_path_check_crlf(struct attr_check_item *check)
{
const char *value = check->value;
@@ -1045,7 +1045,7 @@ static enum crlf_action git_path_check_crlf(struct git_attr_check *check)
return CRLF_UNDEFINED;
}
-static enum eol git_path_check_eol(struct git_attr_check *check)
+static enum eol git_path_check_eol(struct attr_check_item *check)
{
const char *value = check->value;
@@ -1058,7 +1058,7 @@ static enum eol git_path_check_eol(struct git_attr_check *check)
return EOL_UNSET;
}
-static struct convert_driver *git_path_check_convert(struct git_attr_check *check)
+static struct convert_driver *git_path_check_convert(struct attr_check_item *check)
{
const char *value = check->value;
struct convert_driver *drv;
@@ -1071,7 +1071,7 @@ static struct convert_driver *git_path_check_convert(struct git_attr_check *chec
return NULL;
}
-static int git_path_check_ident(struct git_attr_check *check)
+static int git_path_check_ident(struct attr_check_item *check)
{
const char *value = check->value;
@@ -1085,24 +1085,19 @@ struct conv_attrs {
int ident;
};
-static const char *conv_attr_name[] = {
- "crlf", "ident", "filter", "eol", "text",
-};
-#define NUM_CONV_ATTRS ARRAY_SIZE(conv_attr_name)
-
static void convert_attrs(struct conv_attrs *ca, const char *path)
{
- int i;
- static struct git_attr_check ccheck[NUM_CONV_ATTRS];
+ static struct attr_check *check;
- if (!ccheck[0].attr) {
- for (i = 0; i < NUM_CONV_ATTRS; i++)
- ccheck[i].attr = git_attr(conv_attr_name[i]);
+ if (!check) {
+ check = attr_check_initl("crlf", "ident", "filter",
+ "eol", "text", NULL);
user_convert_tail = &user_convert;
git_config(read_convert_config, NULL);
}
- if (!git_check_attr(path, NUM_CONV_ATTRS, ccheck)) {
+ if (!git_check_attr(path, check)) {
+ struct attr_check_item *ccheck = check->items;
ca->crlf_action = git_path_check_crlf(ccheck + 4);
if (ca->crlf_action == CRLF_UNDEFINED)
ca->crlf_action = git_path_check_crlf(ccheck + 0);
diff --git a/ll-merge.c b/ll-merge.c
index ad8be42..ac0d4a5 100644
--- a/ll-merge.c
+++ b/ll-merge.c
@@ -336,15 +336,6 @@ static const struct ll_merge_driver *find_ll_merge_driver(const char *merge_attr
return &ll_merge_drv[LL_TEXT_MERGE];
}
-static int git_path_check_merge(const char *path, struct git_attr_check check[2])
-{
- if (!check[0].attr) {
- check[0].attr = git_attr("merge");
- check[1].attr = git_attr("conflict-marker-size");
- }
- return git_check_attr(path, 2, check);
-}
-
static void normalize_file(mmfile_t *mm, const char *path)
{
struct strbuf strbuf = STRBUF_INIT;
@@ -362,7 +353,7 @@ int ll_merge(mmbuffer_t *result_buf,
mmfile_t *theirs, const char *their_label,
const struct ll_merge_options *opts)
{
- static struct git_attr_check check[2];
+ static struct attr_check *check;
static const struct ll_merge_options default_opts;
const char *ll_driver_name = NULL;
int marker_size = DEFAULT_CONFLICT_MARKER_SIZE;
@@ -376,10 +367,14 @@ int ll_merge(mmbuffer_t *result_buf,
normalize_file(ours, path);
normalize_file(theirs, path);
}
- if (!git_path_check_merge(path, check)) {
- ll_driver_name = check[0].value;
- if (check[1].value) {
- marker_size = atoi(check[1].value);
+
+ if (!check)
+ check = attr_check_initl("merge", "conflict-marker-size", NULL);
+
+ if (!git_check_attr(path, check)) {
+ ll_driver_name = check->items[0].value;
+ if (check->items[1].value) {
+ marker_size = atoi(check->items[1].value);
if (marker_size <= 0)
marker_size = DEFAULT_CONFLICT_MARKER_SIZE;
}
@@ -398,13 +393,13 @@ int ll_merge(mmbuffer_t *result_buf,
int ll_merge_marker_size(const char *path)
{
- static struct git_attr_check check;
+ static struct attr_check *check;
int marker_size = DEFAULT_CONFLICT_MARKER_SIZE;
- if (!check.attr)
- check.attr = git_attr("conflict-marker-size");
- if (!git_check_attr(path, 1, &check) && check.value) {
- marker_size = atoi(check.value);
+ if (!check)
+ check = attr_check_initl("conflict-marker-size", NULL);
+ if (!git_check_attr(path, check) && check->items[0].value) {
+ marker_size = atoi(check->items[0].value);
if (marker_size <= 0)
marker_size = DEFAULT_CONFLICT_MARKER_SIZE;
}
diff --git a/t/t0003-attributes.sh b/t/t0003-attributes.sh
index f0fbb42..f19ae4f 100755
--- a/t/t0003-attributes.sh
+++ b/t/t0003-attributes.sh
@@ -13,10 +13,31 @@ attr_check () {
test_line_count = 0 err
}
+attr_check_quote () {
+
+ path="$1"
+ quoted_path="$2"
+ expect="$3"
+
+ git check-attr test -- "$path" >actual &&
+ echo "\"$quoted_path\": test: $expect" >expect &&
+ test_cmp expect actual
+
+}
+
+test_expect_success 'open-quoted pathname' '
+ echo "\"a test=a" >.gitattributes &&
+ test_must_fail attr_check a a
+'
+
+
test_expect_success 'setup' '
mkdir -p a/b/d a/c b &&
(
echo "[attr]notest !test"
+ echo "\" d \" test=d"
+ echo " e test=e"
+ echo " e\" test=e"
echo "f test=f"
echo "a/i test=a/i"
echo "onoff test -test"
@@ -69,6 +90,11 @@ test_expect_success 'command line checks' '
'
test_expect_success 'attribute test' '
+
+ attr_check " d " d &&
+ attr_check e e &&
+ attr_check_quote e\" e\\\" e &&
+
attr_check f f &&
attr_check a/f f &&
attr_check a/c/f f &&
diff --git a/userdiff.c b/userdiff.c
index 2125d6d..8b732e4 100644
--- a/userdiff.c
+++ b/userdiff.c
@@ -262,25 +262,22 @@ struct userdiff_driver *userdiff_find_by_name(const char *name) {
struct userdiff_driver *userdiff_find_by_path(const char *path)
{
- static struct git_attr *attr;
- struct git_attr_check check;
-
- if (!attr)
- attr = git_attr("diff");
- check.attr = attr;
+ static struct attr_check *check;
+ if (!check)
+ check = attr_check_initl("diff", NULL);
if (!path)
return NULL;
- if (git_check_attr(path, 1, &check))
+ if (git_check_attr(path, check))
return NULL;
- if (ATTR_TRUE(check.value))
+ if (ATTR_TRUE(check->items[0].value))
return &driver_true;
- if (ATTR_FALSE(check.value))
+ if (ATTR_FALSE(check->items[0].value))
return &driver_false;
- if (ATTR_UNSET(check.value))
+ if (ATTR_UNSET(check->items[0].value))
return NULL;
- return userdiff_find_by_name(check.value);
+ return userdiff_find_by_name(check->items[0].value);
}
struct userdiff_driver *userdiff_get_textconv(struct userdiff_driver *driver)
diff --git a/ws.c b/ws.c
index ea4b2b1..a07caed 100644
--- a/ws.c
+++ b/ws.c
@@ -71,24 +71,17 @@ unsigned parse_whitespace_rule(const char *string)
return rule;
}
-static void setup_whitespace_attr_check(struct git_attr_check *check)
-{
- static struct git_attr *attr_whitespace;
-
- if (!attr_whitespace)
- attr_whitespace = git_attr("whitespace");
- check[0].attr = attr_whitespace;
-}
-
unsigned whitespace_rule(const char *pathname)
{
- struct git_attr_check attr_whitespace_rule;
+ static struct attr_check *attr_whitespace_rule;
+
+ if (!attr_whitespace_rule)
+ attr_whitespace_rule = attr_check_initl("whitespace", NULL);
- setup_whitespace_attr_check(&attr_whitespace_rule);
- if (!git_check_attr(pathname, 1, &attr_whitespace_rule)) {
+ if (!git_check_attr(pathname, attr_whitespace_rule)) {
const char *value;
- value = attr_whitespace_rule.value;
+ value = attr_whitespace_rule->items[0].value;
if (ATTR_TRUE(value)) {
/* true (whitespace) */
unsigned all_rule = ws_tab_width(whitespace_rule_cfg);