{"thread":{"id":"44358","subject":"[PATCHv2 00/36] Revamping the attr subsystem!","startedAt":"2016-10-22T23:32:37Z","lastAt":"2016-11-09T22:25:21Z","messageCount":81,"participants":["Stefan Beller","Ramsay Jones","Junio C Hamano","Johannes Schindelin","Simon Ruderich","Jeff King","Duy Nguyen","Johannes Sixt"],"isPatch":false,"patchVersion":null,"patchTotal":null},"messages":[{"id":"304685","messageId":"20161022233225.8883-1-sbeller@google.com","threadId":"44358","inReplyTo":null,"subject":"[PATCHv2 00/36] Revamping the attr subsystem!","fromName":"Stefan Beller","fromEmail":"sbeller@google.com","sentAt":"2016-10-22T23:31:49Z","receivedAt":"2016-10-22T23:32:37Z","isPatch":false,"sender":{"key":"stefanbeller@gmail.com","avatar":"https://avatars.githubusercontent.com/u/455868?v=4"},"body":"previous discussion:\nhttp://public-inbox.org/git/20161012224109.23410-1-sbeller@google.com\nhttp://public-inbox.org/git/20161011002115.23312-1-sbeller@google.com/\n\nThis implements the discarded series':\njc/attr\njc/attr-more\nsb/pathspec-label\nsb/submodule-default-paths\n\n* I rebase to origin master (no merge conflicts)\n* I implemented the thread safe attr API in patch 27 (attr: convert to new threadsafe API)\n* patch 28 (attr: keep attr stack for each check) makes it actually possible\n  to run in a multithreaded environment.\n* I added a test for the multithreaded when it is introduced in patch 32\n  (pathspec: allow querying for attributes)\n\nThanks,\nStefan\n\nJunio C Hamano (24):\n  commit.c: use strchrnul() to scan for one line\n  attr.c: use strchrnul() to scan for one line\n  attr.c: update a stale comment on \"struct match_attr\"\n  attr.c: explain the lack of attr-name syntax check in parse_attr()\n  attr.c: complete a sentence in a comment\n  attr.c: mark where #if DEBUG ends more clearly\n  attr.c: simplify macroexpand_one()\n  attr.c: tighten constness around \"git_attr\" structure\n  attr.c: plug small leak in parse_attr_line()\n  attr: rename function and struct related to checking attributes\n  attr: (re)introduce git_check_attr() and struct git_attr_check\n  attr: convert git_all_attrs() to use \"struct git_attr_check\"\n  attr: convert git_check_attrs() callers to use the new API\n  attr: retire git_check_attrs() API\n  attr: add counted string version of git_check_attr()\n  attr: add counted string version of git_attr()\n  attr: expose validity check for attribute names\n  attr.c: add push_stack() helper\n  attr.c: pass struct git_attr_check down the callchain\n  attr.c: rename a local variable check\n  attr.c: correct ugly hack for git_all_attrs()\n  attr.c: introduce empty_attr_check_elems()\n  attr.c: always pass check[] to collect_some_attrs()\n  attr.c: outline the future plans by heavily commenting\n\nNguyễn Thái Ngọc Duy (1):\n  attr: support quoting pathname patterns in C style\n\nStefan Beller (11):\n  attr: make git_check_attr_counted static\n  attr: convert to new threadsafe API\n  attr: keep attr stack for each check\n  Documentation: fix a typo\n  pathspec: move long magic parsing out of prefix_pathspec\n  pathspec: move prefix check out of the inner loop\n  pathspec: allow querying for attributes\n  pathspec: allow escaped query values\n  submodule update: add `--init-default-path` switch\n  clone: add --init-submodule=<pathspec> switch\n  completion: clone can initialize specific submodules\n\n Documentation/config.txt                      |   5 +\n Documentation/git-clone.txt                   |  23 +-\n Documentation/git-submodule.txt               |  17 +-\n Documentation/gitattributes.txt               |  10 +-\n Documentation/glossary-content.txt            |  20 +\n Documentation/technical/api-gitattributes.txt | 117 ++++--\n archive.c                                     |  26 +-\n attr.c                                        | 530 ++++++++++++++++++--------\n attr.h                                        |  74 +++-\n builtin/check-attr.c                          |  65 ++--\n builtin/clone.c                               |  36 +-\n builtin/pack-objects.c                        |  27 +-\n commit.c                                      |   3 +-\n contrib/completion/git-completion.bash        |   1 +\n convert.c                                     |  45 +--\n dir.c                                         |  35 ++\n git-submodule.sh                              |  21 +-\n hashmap.h                                     |   2 +\n ll-merge.c                                    |  36 +-\n pathspec.c                                    | 225 +++++++++--\n pathspec.h                                    |  16 +\n t/t0003-attributes.sh                         |  26 ++\n t/t6134-pathspec-with-labels.sh               | 180 +++++++++\n t/t7400-submodule-basic.sh                    | 134 +++++++\n userdiff.c                                    |  21 +-\n ws.c                                          |  26 +-\n 26 files changed, 1313 insertions(+), 408 deletions(-)\n create mode 100755 t/t6134-pathspec-with-labels.sh\n\n-- \n2.10.1.508.g6572022\n\n"},{"id":"304686","messageId":"20161022233225.8883-2-sbeller@google.com","threadId":"44358","inReplyTo":"20161022233225.8883-1-sbeller@google.com","subject":"[PATCH 01/36] commit.c: use strchrnul() to scan for one line","fromName":"Stefan Beller","fromEmail":"sbeller@google.com","sentAt":"2016-10-22T23:31:50Z","receivedAt":"2016-10-22T23:32:42Z","isPatch":true,"sender":{"key":"stefanbeller@gmail.com","avatar":"https://avatars.githubusercontent.com/u/455868?v=4"},"body":"From: Junio C Hamano <gitster@pobox.com>\n\nSigned-off-by: Junio C Hamano <gitster@pobox.com>\nSigned-off-by: Stefan Beller <sbeller@google.com>\n---\n commit.c | 3 +--\n 1 file changed, 1 insertion(+), 2 deletions(-)\n\ndiff --git a/commit.c b/commit.c\nindex 856fd4a..41b2fdd 100644\n--- a/commit.c\n+++ b/commit.c\n@@ -415,8 +415,7 @@ int find_commit_subject(const char *commit_buffer, const char **subject)\n \t\tp++;\n \tif (*p) {\n \t\tp = skip_blank_lines(p + 2);\n-\t\tfor (eol = p; *eol && *eol != '\\n'; eol++)\n-\t\t\t; /* do nothing */\n+\t\teol = strchrnul(p, '\\n');\n \t} else\n \t\teol = p;\n \n-- \n2.10.1.508.g6572022\n\n"},{"id":"304687","messageId":"20161022233225.8883-3-sbeller@google.com","threadId":"44358","inReplyTo":"20161022233225.8883-1-sbeller@google.com","subject":"[PATCH 02/36] attr.c: use strchrnul() to scan for one line","fromName":"Stefan Beller","fromEmail":"sbeller@google.com","sentAt":"2016-10-22T23:31:51Z","receivedAt":"2016-10-22T23:32:45Z","isPatch":true,"sender":{"key":"stefanbeller@gmail.com","avatar":"https://avatars.githubusercontent.com/u/455868?v=4"},"body":"From: Junio C Hamano <gitster@pobox.com>\n\nSigned-off-by: Junio C Hamano <gitster@pobox.com>\nSigned-off-by: Stefan Beller <sbeller@google.com>\n---\n attr.c | 4 ++--\n 1 file changed, 2 insertions(+), 2 deletions(-)\n\ndiff --git a/attr.c b/attr.c\nindex eec5d7d..45aec1b 100644\n--- a/attr.c\n+++ b/attr.c\n@@ -402,8 +402,8 @@ static struct attr_stack *read_attr_from_index(const char *path, int macro_ok)\n \tfor (sp = buf; *sp; ) {\n \t\tchar *ep;\n \t\tint more;\n-\t\tfor (ep = sp; *ep && *ep != '\\n'; ep++)\n-\t\t\t;\n+\n+\t\tep = strchrnul(sp, '\\n');\n \t\tmore = (*ep == '\\n');\n \t\t*ep = '\\0';\n \t\thandle_attr_line(res, sp, path, ++lineno, macro_ok);\n-- \n2.10.1.508.g6572022\n\n"},{"id":"304688","messageId":"20161022233225.8883-5-sbeller@google.com","threadId":"44358","inReplyTo":"20161022233225.8883-1-sbeller@google.com","subject":"[PATCH 04/36] attr.c: explain the lack of attr-name syntax check in parse_attr()","fromName":"Stefan Beller","fromEmail":"sbeller@google.com","sentAt":"2016-10-22T23:31:53Z","receivedAt":"2016-10-22T23:32:51Z","isPatch":true,"sender":{"key":"stefanbeller@gmail.com","avatar":"https://avatars.githubusercontent.com/u/455868?v=4"},"body":"From: Junio C Hamano <gitster@pobox.com>\n\nSigned-off-by: Junio C Hamano <gitster@pobox.com>\nSigned-off-by: Stefan Beller <sbeller@google.com>\n---\n attr.c | 6 ++++++\n 1 file changed, 6 insertions(+)\n\ndiff --git a/attr.c b/attr.c\nindex 4ae7801..05db667 100644\n--- a/attr.c\n+++ b/attr.c\n@@ -183,6 +183,12 @@ static const char *parse_attr(const char *src, int lineno, const char *cp,\n \t\t\treturn NULL;\n \t\t}\n \t} else {\n+\t\t/*\n+\t\t * As this function is always called twice, once with\n+\t\t * e == NULL in the first pass and then e != NULL in\n+\t\t * the second pass, no need for invalid_attr_name()\n+\t\t * check here.\n+\t\t */\n \t\tif (*cp == '-' || *cp == '!') {\n \t\t\te->setto = (*cp == '-') ? ATTR__FALSE : ATTR__UNSET;\n \t\t\tcp++;\n-- \n2.10.1.508.g6572022\n\n"},{"id":"304689","messageId":"20161022233225.8883-7-sbeller@google.com","threadId":"44358","inReplyTo":"20161022233225.8883-1-sbeller@google.com","subject":"[PATCH 06/36] attr.c: mark where #if DEBUG ends more clearly","fromName":"Stefan Beller","fromEmail":"sbeller@google.com","sentAt":"2016-10-22T23:31:55Z","receivedAt":"2016-10-22T23:32:54Z","isPatch":true,"sender":{"key":"stefanbeller@gmail.com","avatar":"https://avatars.githubusercontent.com/u/455868?v=4"},"body":"From: Junio C Hamano <gitster@pobox.com>\n\nSigned-off-by: Junio C Hamano <gitster@pobox.com>\nSigned-off-by: Stefan Beller <sbeller@google.com>\n---\n attr.c | 2 +-\n 1 file changed, 1 insertion(+), 1 deletion(-)\n\ndiff --git a/attr.c b/attr.c\nindex a7f2c3f..95416d3 100644\n--- a/attr.c\n+++ b/attr.c\n@@ -469,7 +469,7 @@ static void debug_set(const char *what, const char *match, struct git_attr *attr\n #define debug_push(a) do { ; } while (0)\n #define debug_pop(a) do { ; } while (0)\n #define debug_set(a,b,c,d) do { ; } while (0)\n-#endif\n+#endif /* DEBUG_ATTR */\n \n static void drop_attr_stack(void)\n {\n-- \n2.10.1.508.g6572022\n\n"},{"id":"304690","messageId":"20161022233225.8883-6-sbeller@google.com","threadId":"44358","inReplyTo":"20161022233225.8883-1-sbeller@google.com","subject":"[PATCH 05/36] attr.c: complete a sentence in a comment","fromName":"Stefan Beller","fromEmail":"sbeller@google.com","sentAt":"2016-10-22T23:31:54Z","receivedAt":"2016-10-22T23:32:55Z","isPatch":true,"sender":{"key":"stefanbeller@gmail.com","avatar":"https://avatars.githubusercontent.com/u/455868?v=4"},"body":"From: Junio C Hamano <gitster@pobox.com>\n\nSigned-off-by: Junio C Hamano <gitster@pobox.com>\nSigned-off-by: Stefan Beller <sbeller@google.com>\n---\n attr.c | 2 +-\n 1 file changed, 1 insertion(+), 1 deletion(-)\n\ndiff --git a/attr.c b/attr.c\nindex 05db667..a7f2c3f 100644\n--- a/attr.c\n+++ b/attr.c\n@@ -300,7 +300,7 @@ static struct match_attr *parse_attr_line(const char *line, const char *src,\n  * directory (again, reading the file from top to bottom) down to the\n  * current directory, and then scan the list backwards to find the first match.\n  * This is exactly the same as what is_excluded() does in dir.c to deal with\n- * .gitignore\n+ * .gitignore file and info/excludes file as a fallback.\n  */\n \n static struct attr_stack {\n-- \n2.10.1.508.g6572022\n\n"},{"id":"304691","messageId":"20161022233225.8883-4-sbeller@google.com","threadId":"44358","inReplyTo":"20161022233225.8883-1-sbeller@google.com","subject":"[PATCH 03/36] attr.c: update a stale comment on \"struct match_attr\"","fromName":"Stefan Beller","fromEmail":"sbeller@google.com","sentAt":"2016-10-22T23:31:52Z","receivedAt":"2016-10-22T23:32:56Z","isPatch":true,"sender":{"key":"stefanbeller@gmail.com","avatar":"https://avatars.githubusercontent.com/u/455868?v=4"},"body":"From: Junio C Hamano <gitster@pobox.com>\n\nWhen 82dce998 (attr: more matching optimizations from .gitignore,\n2012-10-15) changed a pointer to a string \"*pattern\" into an\nembedded \"struct pattern\" in struct match_attr, it forgot to update\nthe comment that describes the structure.\n\nSigned-off-by: Junio C Hamano <gitster@pobox.com>\nSigned-off-by: Stefan Beller <sbeller@google.com>\n---\n attr.c | 5 ++---\n 1 file changed, 2 insertions(+), 3 deletions(-)\n\ndiff --git a/attr.c b/attr.c\nindex 45aec1b..4ae7801 100644\n--- a/attr.c\n+++ b/attr.c\n@@ -131,9 +131,8 @@ struct pattern {\n  * If is_macro is true, then u.attr is a pointer to the git_attr being\n  * defined.\n  *\n- * If is_macro is false, then u.pattern points at the filename pattern\n- * to which the rule applies.  (The memory pointed to is part of the\n- * memory block allocated for the match_attr instance.)\n+ * If is_macro is false, then u.pat is the filename pattern to which the\n+ * rule applies.\n  *\n  * In either case, num_attr is the number of attributes affected by\n  * this rule, and state is an array listing them.  The attributes are\n-- \n2.10.1.508.g6572022\n\n"},{"id":"304692","messageId":"20161022233225.8883-10-sbeller@google.com","threadId":"44358","inReplyTo":"20161022233225.8883-1-sbeller@google.com","subject":"[PATCH 09/36] attr.c: plug small leak in parse_attr_line()","fromName":"Stefan Beller","fromEmail":"sbeller@google.com","sentAt":"2016-10-22T23:31:58Z","receivedAt":"2016-10-22T23:32:59Z","isPatch":true,"sender":{"key":"stefanbeller@gmail.com","avatar":"https://avatars.githubusercontent.com/u/455868?v=4"},"body":"From: Junio C Hamano <gitster@pobox.com>\n\nIf any error is noticed after the match_attr structure is allocated,\nwe shouldn't just return NULL from this function.\n\nAdd a fail_return label that frees the allocated structure and\nreturns NULL, and consistently jump there when we want to return\nNULL after cleaning up.\n\nSigned-off-by: Junio C Hamano <gitster@pobox.com>\nSigned-off-by: Stefan Beller <sbeller@google.com>\n---\n attr.c | 12 ++++++++----\n 1 file changed, 8 insertions(+), 4 deletions(-)\n\ndiff --git a/attr.c b/attr.c\nindex 5c35d42..1877f7a 100644\n--- a/attr.c\n+++ b/attr.c\n@@ -223,7 +223,7 @@ static struct match_attr *parse_attr_line(const char *line, const char *src,\n \t\tif (!macro_ok) {\n \t\t\tfprintf(stderr, \"%s not allowed: %s:%d\\n\",\n \t\t\t\tname, src, lineno);\n-\t\t\treturn NULL;\n+\t\t\tgoto fail_return;\n \t\t}\n \t\tis_macro = 1;\n \t\tname += strlen(ATTRIBUTE_MACRO_PREFIX);\n@@ -233,7 +233,7 @@ static struct match_attr *parse_attr_line(const char *line, const char *src,\n \t\t\tfprintf(stderr,\n \t\t\t\t\"%.*s is not a valid attribute name: %s:%d\\n\",\n \t\t\t\tnamelen, name, src, lineno);\n-\t\t\treturn NULL;\n+\t\t\tgoto fail_return;\n \t\t}\n \t}\n \telse\n@@ -246,7 +246,7 @@ static struct match_attr *parse_attr_line(const char *line, const char *src,\n \tfor (cp = states, num_attr = 0; *cp; num_attr++) {\n \t\tcp = parse_attr(src, lineno, cp, NULL);\n \t\tif (!cp)\n-\t\t\treturn NULL;\n+\t\t\tgoto fail_return;\n \t}\n \n \tres = xcalloc(1,\n@@ -267,7 +267,7 @@ static struct match_attr *parse_attr_line(const char *line, const char *src,\n \t\tif (res->u.pat.flags & EXC_FLAG_NEGATIVE) {\n \t\t\twarning(_(\"Negative patterns are ignored in git attributes\\n\"\n \t\t\t\t  \"Use '\\\\!' for literal leading exclamation.\"));\n-\t\t\treturn NULL;\n+\t\t\tgoto fail_return;\n \t\t}\n \t}\n \tres->is_macro = is_macro;\n@@ -283,6 +283,10 @@ static struct match_attr *parse_attr_line(const char *line, const char *src,\n \t}\n \n \treturn res;\n+\n+fail_return:\n+\tfree(res);\n+\treturn NULL;\n }\n \n /*\n-- \n2.10.1.508.g6572022\n\n"},{"id":"304693","messageId":"20161022233225.8883-8-sbeller@google.com","threadId":"44358","inReplyTo":"20161022233225.8883-1-sbeller@google.com","subject":"[PATCH 07/36] attr.c: simplify macroexpand_one()","fromName":"Stefan Beller","fromEmail":"sbeller@google.com","sentAt":"2016-10-22T23:31:56Z","receivedAt":"2016-10-22T23:33:00Z","isPatch":true,"sender":{"key":"stefanbeller@gmail.com","avatar":"https://avatars.githubusercontent.com/u/455868?v=4"},"body":"From: Junio C Hamano <gitster@pobox.com>\n\nThe double-loop wants to do an early return immediately when one\nmatching macro is found.  Eliminate the extra variable 'a' used for\nthat purpose and rewrite the \"assign the found item to 'a' to make\nit non-NULL and force the loop(s) to terminate\" with a direct return\nfrom there.\n\nSigned-off-by: Junio C Hamano <gitster@pobox.com>\nSigned-off-by: Stefan Beller <sbeller@google.com>\n---\n attr.c | 11 ++++-------\n 1 file changed, 4 insertions(+), 7 deletions(-)\n\ndiff --git a/attr.c b/attr.c\nindex 95416d3..7bfeef3 100644\n--- a/attr.c\n+++ b/attr.c\n@@ -701,24 +701,21 @@ static int fill(const char *path, int pathlen, int basename_offset,\n static int macroexpand_one(int nr, int rem)\n {\n \tstruct attr_stack *stk;\n-\tstruct match_attr *a = NULL;\n \tint i;\n \n \tif (check_all_attr[nr].value != ATTR__TRUE ||\n \t    !check_all_attr[nr].attr->maybe_macro)\n \t\treturn rem;\n \n-\tfor (stk = attr_stack; !a && stk; stk = stk->prev)\n-\t\tfor (i = stk->num_matches - 1; !a && 0 <= i; i--) {\n+\tfor (stk = attr_stack; stk; stk = stk->prev) {\n+\t\tfor (i = stk->num_matches - 1; 0 <= i; i--) {\n \t\t\tstruct match_attr *ma = stk->attrs[i];\n \t\t\tif (!ma->is_macro)\n \t\t\t\tcontinue;\n \t\t\tif (ma->u.attr->attr_nr == nr)\n-\t\t\t\ta = ma;\n+\t\t\t\treturn fill_one(\"expand\", ma, rem);\n \t\t}\n-\n-\tif (a)\n-\t\trem = fill_one(\"expand\", a, rem);\n+\t}\n \n \treturn rem;\n }\n-- \n2.10.1.508.g6572022\n\n"},{"id":"304694","messageId":"20161022233225.8883-11-sbeller@google.com","threadId":"44358","inReplyTo":"20161022233225.8883-1-sbeller@google.com","subject":"[PATCH 10/36] attr: rename function and struct related to checking attributes","fromName":"Stefan Beller","fromEmail":"sbeller@google.com","sentAt":"2016-10-22T23:31:59Z","receivedAt":"2016-10-22T23:33:04Z","isPatch":true,"sender":{"key":"stefanbeller@gmail.com","avatar":"https://avatars.githubusercontent.com/u/455868?v=4"},"body":"From: Junio C Hamano <gitster@pobox.com>\n\nThe traditional API to check attributes is to prepare an N-element\narray of \"struct git_attr_check\" and pass N and the array to the\nfunction \"git_check_attr()\" as arguments.\n\nIn preparation to revamp the API to pass a single structure, in\nwhich these N elements are held, rename the type used for these\nindividual array elements to \"struct git_attr_check_elem\" and rename\nthe function to \"git_check_attrs()\".\n\nSigned-off-by: Junio C Hamano <gitster@pobox.com>\nSigned-off-by: Stefan Beller <sbeller@google.com>\n---\n archive.c              |  6 +++---\n attr.c                 | 12 ++++++------\n attr.h                 |  8 ++++----\n builtin/check-attr.c   | 19 ++++++++++---------\n builtin/pack-objects.c |  6 +++---\n convert.c              | 12 ++++++------\n ll-merge.c             | 10 +++++-----\n userdiff.c             |  4 ++--\n ws.c                   |  6 +++---\n 9 files changed, 42 insertions(+), 41 deletions(-)\n\ndiff --git a/archive.c b/archive.c\nindex dde1ab4..2dc8d6c 100644\n--- a/archive.c\n+++ b/archive.c\n@@ -87,7 +87,7 @@ void *sha1_file_to_archive(const struct archiver_args *args,\n \treturn buffer;\n }\n \n-static void setup_archive_check(struct git_attr_check *check)\n+static void setup_archive_check(struct git_attr_check_elem *check)\n {\n \tstatic struct git_attr *attr_export_ignore;\n \tstatic struct git_attr *attr_export_subst;\n@@ -123,7 +123,7 @@ static int write_archive_entry(const unsigned char *sha1, const char *base,\n \tstruct archiver_context *c = context;\n \tstruct archiver_args *args = c->args;\n \twrite_archive_entry_fn_t write_entry = c->write_entry;\n-\tstruct git_attr_check check[2];\n+\tstruct git_attr_check_elem check[2];\n \tconst char *path_without_prefix;\n \tint err;\n \n@@ -138,7 +138,7 @@ static int write_archive_entry(const unsigned char *sha1, const char *base,\n \tpath_without_prefix = path.buf + args->baselen;\n \n \tsetup_archive_check(check);\n-\tif (!git_check_attr(path_without_prefix, ARRAY_SIZE(check), check)) {\n+\tif (!git_check_attrs(path_without_prefix, ARRAY_SIZE(check), check)) {\n \t\tif (ATTR_TRUE(check[0].value))\n \t\t\treturn 0;\n \t\targs->convert = ATTR_TRUE(check[1].value);\ndiff --git a/attr.c b/attr.c\nindex 1877f7a..c99e23a 100644\n--- a/attr.c\n+++ b/attr.c\n@@ -40,7 +40,7 @@ struct git_attr {\n static int attr_nr;\n static int cannot_trust_maybe_real;\n \n-static struct git_attr_check *check_all_attr;\n+static struct git_attr_check_elem *check_all_attr;\n static struct git_attr *(git_attr_hash[HASHSIZE]);\n \n const char *git_attr_name(const struct git_attr *attr)\n@@ -665,7 +665,7 @@ static int macroexpand_one(int attr_nr, int rem);\n \n static int fill_one(const char *what, struct match_attr *a, int rem)\n {\n-\tstruct git_attr_check *check = check_all_attr;\n+\tstruct git_attr_check_elem *check = check_all_attr;\n \tint i;\n \n \tfor (i = a->num_attr - 1; 0 < rem && 0 <= i; i--) {\n@@ -730,7 +730,7 @@ static int macroexpand_one(int nr, int rem)\n  * collected. Otherwise all attributes are collected.\n  */\n static void collect_some_attrs(const char *path, int num,\n-\t\t\t       struct git_attr_check *check)\n+\t\t\t       struct git_attr_check_elem *check)\n \n {\n \tstruct attr_stack *stk;\n@@ -758,7 +758,7 @@ static void collect_some_attrs(const char *path, int num,\n \t\trem = 0;\n \t\tfor (i = 0; i < num; i++) {\n \t\t\tif (!check[i].attr->maybe_real) {\n-\t\t\t\tstruct git_attr_check *c;\n+\t\t\t\tstruct git_attr_check_elem *c;\n \t\t\t\tc = check_all_attr + check[i].attr->attr_nr;\n \t\t\t\tc->value = ATTR__UNSET;\n \t\t\t\trem++;\n@@ -773,7 +773,7 @@ static void collect_some_attrs(const char *path, int num,\n \t\trem = fill(path, pathlen, basename_offset, stk, rem);\n }\n \n-int git_check_attr(const char *path, int num, struct git_attr_check *check)\n+int git_check_attrs(const char *path, int num, struct git_attr_check_elem *check)\n {\n \tint i;\n \n@@ -789,7 +789,7 @@ int git_check_attr(const char *path, int num, struct git_attr_check *check)\n \treturn 0;\n }\n \n-int git_all_attrs(const char *path, int *num, struct git_attr_check **check)\n+int git_all_attrs(const char *path, int *num, struct git_attr_check_elem **check)\n {\n \tint i, count, j;\n \ndiff --git a/attr.h b/attr.h\nindex 00d7a66..dd3c4a3 100644\n--- a/attr.h\n+++ b/attr.h\n@@ -20,11 +20,11 @@ extern const char git_attr__false[];\n #define ATTR_UNSET(v) ((v) == NULL)\n \n /*\n- * Send one or more git_attr_check to git_check_attr(), and\n+ * Send one or more git_attr_check to git_check_attrs(), and\n  * each 'value' member tells what its value is.\n  * Unset one is returned as NULL.\n  */\n-struct git_attr_check {\n+struct git_attr_check_elem {\n \tconst struct git_attr *attr;\n \tconst char *value;\n };\n@@ -36,7 +36,7 @@ struct git_attr_check {\n  */\n extern const char *git_attr_name(const struct git_attr *);\n \n-int git_check_attr(const char *path, int, struct git_attr_check *);\n+int git_check_attrs(const char *path, int, struct git_attr_check_elem *);\n \n /*\n  * Retrieve all attributes that apply to the specified path.  *num\n@@ -45,7 +45,7 @@ int git_check_attr(const char *path, int, struct git_attr_check *);\n  * objects describing the attributes and their values.  *check must be\n  * free()ed by the caller.\n  */\n-int git_all_attrs(const char *path, int *num, struct git_attr_check **check);\n+int git_all_attrs(const char *path, int *num, struct git_attr_check_elem **check);\n \n enum git_attr_direction {\n \tGIT_ATTR_CHECKIN,\ndiff --git a/builtin/check-attr.c b/builtin/check-attr.c\nindex 53a5a18..97e3837 100644\n--- a/builtin/check-attr.c\n+++ b/builtin/check-attr.c\n@@ -24,8 +24,8 @@ static const struct option check_attr_options[] = {\n \tOPT_END()\n };\n \n-static void output_attr(int cnt, struct git_attr_check *check,\n-\tconst char *file)\n+static void output_attr(int cnt, struct git_attr_check_elem *check,\n+\t\t\tconst char *file)\n {\n \tint j;\n \tfor (j = 0; j < cnt; j++) {\n@@ -51,14 +51,15 @@ static void output_attr(int cnt, struct git_attr_check *check,\n \t}\n }\n \n-static void check_attr(const char *prefix, int cnt,\n-\tstruct git_attr_check *check, const char *file)\n+static void check_attr(const char *prefix,\n+\t\t       int cnt, struct git_attr_check_elem *check,\n+\t\t       const char *file)\n {\n \tchar *full_path =\n \t\tprefix_path(prefix, prefix ? strlen(prefix) : 0, file);\n \tif (check != NULL) {\n-\t\tif (git_check_attr(full_path, cnt, check))\n-\t\t\tdie(\"git_check_attr died\");\n+\t\tif (git_check_attrs(full_path, cnt, check))\n+\t\t\tdie(\"git_check_attrs died\");\n \t\toutput_attr(cnt, check, file);\n \t} else {\n \t\tif (git_all_attrs(full_path, &cnt, &check))\n@@ -69,8 +70,8 @@ static void check_attr(const char *prefix, int cnt,\n \tfree(full_path);\n }\n \n-static void check_attr_stdin_paths(const char *prefix, int cnt,\n-\tstruct git_attr_check *check)\n+static void check_attr_stdin_paths(const char *prefix,\n+\t\t\t\t   int cnt, struct git_attr_check_elem *check)\n {\n \tstruct strbuf buf = STRBUF_INIT;\n \tstruct strbuf unquoted = STRBUF_INIT;\n@@ -99,7 +100,7 @@ static NORETURN void error_with_usage(const char *msg)\n \n int cmd_check_attr(int argc, const char **argv, const char *prefix)\n {\n-\tstruct git_attr_check *check;\n+\tstruct git_attr_check_elem *check;\n \tint cnt, i, doubledash, filei;\n \n \tif (!is_bare_repository())\ndiff --git a/builtin/pack-objects.c b/builtin/pack-objects.c\nindex 1e7c2a9..3cb38ed 100644\n--- a/builtin/pack-objects.c\n+++ b/builtin/pack-objects.c\n@@ -896,7 +896,7 @@ static void write_pack_file(void)\n \t\t\twritten, nr_result);\n }\n \n-static void setup_delta_attr_check(struct git_attr_check *check)\n+static void setup_delta_attr_check(struct git_attr_check_elem *check)\n {\n \tstatic struct git_attr *attr_delta;\n \n@@ -908,10 +908,10 @@ static void setup_delta_attr_check(struct git_attr_check *check)\n \n static int no_try_delta(const char *path)\n {\n-\tstruct git_attr_check check[1];\n+\tstruct git_attr_check_elem check[1];\n \n \tsetup_delta_attr_check(check);\n-\tif (git_check_attr(path, ARRAY_SIZE(check), check))\n+\tif (git_check_attrs(path, ARRAY_SIZE(check), check))\n \t\treturn 0;\n \tif (ATTR_FALSE(check->value))\n \t\treturn 1;\ndiff --git a/convert.c b/convert.c\nindex 077f5e6..c95ae71 100644\n--- a/convert.c\n+++ b/convert.c\n@@ -718,7 +718,7 @@ static int ident_to_worktree(const char *path, const char *src, size_t len,\n \treturn 1;\n }\n \n-static enum crlf_action git_path_check_crlf(struct git_attr_check *check)\n+static enum crlf_action git_path_check_crlf(struct git_attr_check_elem *check)\n {\n \tconst char *value = check->value;\n \n@@ -735,7 +735,7 @@ static enum crlf_action git_path_check_crlf(struct git_attr_check *check)\n \treturn CRLF_UNDEFINED;\n }\n \n-static enum eol git_path_check_eol(struct git_attr_check *check)\n+static enum eol git_path_check_eol(struct git_attr_check_elem *check)\n {\n \tconst char *value = check->value;\n \n@@ -748,7 +748,7 @@ static enum eol git_path_check_eol(struct git_attr_check *check)\n \treturn EOL_UNSET;\n }\n \n-static struct convert_driver *git_path_check_convert(struct git_attr_check *check)\n+static struct convert_driver *git_path_check_convert(struct git_attr_check_elem *check)\n {\n \tconst char *value = check->value;\n \tstruct convert_driver *drv;\n@@ -761,7 +761,7 @@ static struct convert_driver *git_path_check_convert(struct git_attr_check *chec\n \treturn NULL;\n }\n \n-static int git_path_check_ident(struct git_attr_check *check)\n+static int git_path_check_ident(struct git_attr_check_elem *check)\n {\n \tconst char *value = check->value;\n \n@@ -783,7 +783,7 @@ static const char *conv_attr_name[] = {\n static void convert_attrs(struct conv_attrs *ca, const char *path)\n {\n \tint i;\n-\tstatic struct git_attr_check ccheck[NUM_CONV_ATTRS];\n+\tstatic struct git_attr_check_elem ccheck[NUM_CONV_ATTRS];\n \n \tif (!ccheck[0].attr) {\n \t\tfor (i = 0; i < NUM_CONV_ATTRS; i++)\n@@ -792,7 +792,7 @@ static void convert_attrs(struct conv_attrs *ca, const char *path)\n \t\tgit_config(read_convert_config, NULL);\n \t}\n \n-\tif (!git_check_attr(path, NUM_CONV_ATTRS, ccheck)) {\n+\tif (!git_check_attrs(path, NUM_CONV_ATTRS, ccheck)) {\n \t\tca->crlf_action = git_path_check_crlf(ccheck + 4);\n \t\tif (ca->crlf_action == CRLF_UNDEFINED)\n \t\t\tca->crlf_action = git_path_check_crlf(ccheck + 0);\ndiff --git a/ll-merge.c b/ll-merge.c\nindex ad8be42..eb2c37e 100644\n--- a/ll-merge.c\n+++ b/ll-merge.c\n@@ -336,13 +336,13 @@ static const struct ll_merge_driver *find_ll_merge_driver(const char *merge_attr\n \treturn &ll_merge_drv[LL_TEXT_MERGE];\n }\n \n-static int git_path_check_merge(const char *path, struct git_attr_check check[2])\n+static int git_path_check_merge(const char *path, struct git_attr_check_elem check[2])\n {\n \tif (!check[0].attr) {\n \t\tcheck[0].attr = git_attr(\"merge\");\n \t\tcheck[1].attr = git_attr(\"conflict-marker-size\");\n \t}\n-\treturn git_check_attr(path, 2, check);\n+\treturn git_check_attrs(path, 2, check);\n }\n \n static void normalize_file(mmfile_t *mm, const char *path)\n@@ -362,7 +362,7 @@ int ll_merge(mmbuffer_t *result_buf,\n \t     mmfile_t *theirs, const char *their_label,\n \t     const struct ll_merge_options *opts)\n {\n-\tstatic struct git_attr_check check[2];\n+\tstatic struct git_attr_check_elem check[2];\n \tstatic const struct ll_merge_options default_opts;\n \tconst char *ll_driver_name = NULL;\n \tint marker_size = DEFAULT_CONFLICT_MARKER_SIZE;\n@@ -398,12 +398,12 @@ int ll_merge(mmbuffer_t *result_buf,\n \n int ll_merge_marker_size(const char *path)\n {\n-\tstatic struct git_attr_check check;\n+\tstatic struct git_attr_check_elem check;\n \tint marker_size = DEFAULT_CONFLICT_MARKER_SIZE;\n \n \tif (!check.attr)\n \t\tcheck.attr = git_attr(\"conflict-marker-size\");\n-\tif (!git_check_attr(path, 1, &check) && check.value) {\n+\tif (!git_check_attrs(path, 1, &check) && check.value) {\n \t\tmarker_size = atoi(check.value);\n \t\tif (marker_size <= 0)\n \t\t\tmarker_size = DEFAULT_CONFLICT_MARKER_SIZE;\ndiff --git a/userdiff.c b/userdiff.c\nindex 2125d6d..4de3289 100644\n--- a/userdiff.c\n+++ b/userdiff.c\n@@ -263,7 +263,7 @@ struct userdiff_driver *userdiff_find_by_name(const char *name) {\n struct userdiff_driver *userdiff_find_by_path(const char *path)\n {\n \tstatic struct git_attr *attr;\n-\tstruct git_attr_check check;\n+\tstruct git_attr_check_elem check;\n \n \tif (!attr)\n \t\tattr = git_attr(\"diff\");\n@@ -271,7 +271,7 @@ struct userdiff_driver *userdiff_find_by_path(const char *path)\n \n \tif (!path)\n \t\treturn NULL;\n-\tif (git_check_attr(path, 1, &check))\n+\tif (git_check_attrs(path, 1, &check))\n \t\treturn NULL;\n \n \tif (ATTR_TRUE(check.value))\ndiff --git a/ws.c b/ws.c\nindex ea4b2b1..7350905 100644\n--- a/ws.c\n+++ b/ws.c\n@@ -71,7 +71,7 @@ unsigned parse_whitespace_rule(const char *string)\n \treturn rule;\n }\n \n-static void setup_whitespace_attr_check(struct git_attr_check *check)\n+static void setup_whitespace_attr_check(struct git_attr_check_elem *check)\n {\n \tstatic struct git_attr *attr_whitespace;\n \n@@ -82,10 +82,10 @@ static void setup_whitespace_attr_check(struct git_attr_check *check)\n \n unsigned whitespace_rule(const char *pathname)\n {\n-\tstruct git_attr_check attr_whitespace_rule;\n+\tstruct git_attr_check_elem attr_whitespace_rule;\n \n \tsetup_whitespace_attr_check(&attr_whitespace_rule);\n-\tif (!git_check_attr(pathname, 1, &attr_whitespace_rule)) {\n+\tif (!git_check_attrs(pathname, 1, &attr_whitespace_rule)) {\n \t\tconst char *value;\n \n \t\tvalue = attr_whitespace_rule.value;\n-- \n2.10.1.508.g6572022\n\n"},{"id":"304695","messageId":"20161022233225.8883-9-sbeller@google.com","threadId":"44358","inReplyTo":"20161022233225.8883-1-sbeller@google.com","subject":"[PATCH 08/36] attr.c: tighten constness around \"git_attr\" structure","fromName":"Stefan Beller","fromEmail":"sbeller@google.com","sentAt":"2016-10-22T23:31:57Z","receivedAt":"2016-10-22T23:33:05Z","isPatch":true,"sender":{"key":"stefanbeller@gmail.com","avatar":"https://avatars.githubusercontent.com/u/455868?v=4"},"body":"From: Junio C Hamano <gitster@pobox.com>\n\nIt holds an interned string, and git_attr_name() is a way to peek\ninto it.  Make sure the involved pointer types are pointer-to-const.\n\nSigned-off-by: Junio C Hamano <gitster@pobox.com>\nSigned-off-by: Stefan Beller <sbeller@google.com>\n---\n attr.c | 2 +-\n attr.h | 4 ++--\n 2 files changed, 3 insertions(+), 3 deletions(-)\n\ndiff --git a/attr.c b/attr.c\nindex 7bfeef3..5c35d42 100644\n--- a/attr.c\n+++ b/attr.c\n@@ -43,7 +43,7 @@ static int cannot_trust_maybe_real;\n static struct git_attr_check *check_all_attr;\n static struct git_attr *(git_attr_hash[HASHSIZE]);\n \n-char *git_attr_name(struct git_attr *attr)\n+const char *git_attr_name(const struct git_attr *attr)\n {\n \treturn attr->name;\n }\ndiff --git a/attr.h b/attr.h\nindex 8b08d33..00d7a66 100644\n--- a/attr.h\n+++ b/attr.h\n@@ -25,7 +25,7 @@ extern const char git_attr__false[];\n  * Unset one is returned as NULL.\n  */\n struct git_attr_check {\n-\tstruct git_attr *attr;\n+\tconst struct git_attr *attr;\n \tconst char *value;\n };\n \n@@ -34,7 +34,7 @@ struct git_attr_check {\n  * return value is a pointer to a null-delimited string that is part\n  * of the internal data structure; it should not be modified or freed.\n  */\n-char *git_attr_name(struct git_attr *);\n+extern const char *git_attr_name(const struct git_attr *);\n \n int git_check_attr(const char *path, int, struct git_attr_check *);\n \n-- \n2.10.1.508.g6572022\n\n"},{"id":"304696","messageId":"20161022233225.8883-13-sbeller@google.com","threadId":"44358","inReplyTo":"20161022233225.8883-1-sbeller@google.com","subject":"[PATCH 12/36] attr: convert git_all_attrs() to use \"struct git_attr_check\"","fromName":"Stefan Beller","fromEmail":"sbeller@google.com","sentAt":"2016-10-22T23:32:01Z","receivedAt":"2016-10-22T23:33:08Z","isPatch":true,"sender":{"key":"stefanbeller@gmail.com","avatar":"https://avatars.githubusercontent.com/u/455868?v=4"},"body":"From: Junio C Hamano <gitster@pobox.com>\n\nThis updates the other two ways the attribute check is done via an\narray of \"struct git_attr_check_elem\" elements.  These two niches\nappear only in \"git check-attr\".\n\n * The caller does not know offhand what attributes it wants to ask\n   about and cannot use git_attr_check_initl() to prepare the\n   git_attr_check structure.\n\n * The caller may not know what attributes it wants to ask at all,\n   and instead wants to learn everything that the given path has.\n\nSuch a caller can call git_attr_check_alloc() to allocate an empty\ngit_attr_check, and then call git_attr_check_append() to add\nattribute names one by one.  A new attribute can be appended until\ngit_attr_check structure is \"finalized\", which happens when it is\nused to ask for attributes for any path by calling git_check_attr()\nor git_all_attrs().  A git_attr_check structure that is initialized\nby git_attr_check_initl() is already finalized when it is returned.\n\nI am not at all happy with the way git_all_attrs() API turned out to\nbe, but it is only to support one niche caller (\"check-attr --all\"),\nso I'll stop here for now.\n\nSigned-off-by: Junio C Hamano <gitster@pobox.com>\nSigned-off-by: Stefan Beller <sbeller@google.com>\n---\n attr.c               | 75 ++++++++++++++++++++++++++++++++++++++--------------\n attr.h               | 16 ++++++-----\n builtin/check-attr.c | 51 ++++++++++++++++++-----------------\n 3 files changed, 90 insertions(+), 52 deletions(-)\n\ndiff --git a/attr.c b/attr.c\nindex 861e1a2..76f0d6b 100644\n--- a/attr.c\n+++ b/attr.c\n@@ -724,6 +724,11 @@ static int macroexpand_one(int nr, int rem)\n \treturn rem;\n }\n \n+static int attr_check_is_dynamic(const struct git_attr_check *check)\n+{\n+\treturn (void *)(check->check) != (void *)(check + 1);\n+}\n+\n /*\n  * Collect attributes for path into the array pointed to by\n  * check_all_attr. If num is non-zero, only attributes in check[] are\n@@ -789,32 +794,21 @@ int git_check_attrs(const char *path, int num, struct git_attr_check_elem *check\n \treturn 0;\n }\n \n-int git_all_attrs(const char *path, int *num, struct git_attr_check_elem **check)\n+void git_all_attrs(const char *path, struct git_attr_check *check)\n {\n-\tint i, count, j;\n+\tint i;\n \n+\tgit_attr_check_clear(check);\n \tcollect_some_attrs(path, 0, NULL);\n \n-\t/* Count the number of attributes that are set. */\n-\tcount = 0;\n-\tfor (i = 0; i < attr_nr; i++) {\n-\t\tconst char *value = check_all_attr[i].value;\n-\t\tif (value != ATTR__UNSET && value != ATTR__UNKNOWN)\n-\t\t\t++count;\n-\t}\n-\t*num = count;\n-\tALLOC_ARRAY(*check, count);\n-\tj = 0;\n \tfor (i = 0; i < attr_nr; i++) {\n+\t\tconst char *name = check_all_attr[i].attr->name;\n \t\tconst char *value = check_all_attr[i].value;\n-\t\tif (value != ATTR__UNSET && value != ATTR__UNKNOWN) {\n-\t\t\t(*check)[j].attr = check_all_attr[i].attr;\n-\t\t\t(*check)[j].value = value;\n-\t\t\t++j;\n-\t\t}\n+\t\tif (value == ATTR__UNSET || value == ATTR__UNKNOWN)\n+\t\t\tcontinue;\n+\t\tgit_attr_check_append(check, git_attr(name));\n+\t\tcheck->check[check->check_nr - 1].value = value;\n \t}\n-\n-\treturn 0;\n }\n \n void git_attr_set_direction(enum git_attr_direction new, struct index_state *istate)\n@@ -832,6 +826,7 @@ void git_attr_set_direction(enum git_attr_direction new, struct index_state *ist\n \n int git_check_attr(const char *path, struct git_attr_check *check)\n {\n+\tcheck->finalized = 1;\n \treturn git_check_attrs(path, check->check_nr, check->check);\n }\n \n@@ -849,17 +844,57 @@ struct git_attr_check *git_attr_check_initl(const char *one, ...)\n \tcheck = xcalloc(1,\n \t\t\tsizeof(*check) + cnt * sizeof(*(check->check)));\n \tcheck->check_nr = cnt;\n+\tcheck->finalized = 1;\n \tcheck->check = (struct git_attr_check_elem *)(check + 1);\n \n \tcheck->check[0].attr = git_attr(one);\n \tva_start(params, one);\n \tfor (cnt = 1; cnt < check->check_nr; cnt++) {\n+\t\tstruct git_attr *attr;\n \t\tparam = va_arg(params, const char *);\n \t\tif (!param)\n \t\t\tdie(\"BUG: counted %d != ended at %d\",\n \t\t\t    check->check_nr, cnt);\n-\t\tcheck->check[cnt].attr = git_attr(param);\n+\t\tattr = git_attr(param);\n+\t\tif (!attr)\n+\t\t\tdie(\"BUG: %s: not a valid attribute name\", param);\n+\t\tcheck->check[cnt].attr = attr;\n \t}\n \tva_end(params);\n \treturn check;\n }\n+\n+struct git_attr_check *git_attr_check_alloc(void)\n+{\n+\treturn xcalloc(1, sizeof(struct git_attr_check));\n+}\n+\n+struct git_attr_check_elem *git_attr_check_append(struct git_attr_check *check,\n+\t\t\t\t\t\t  const struct git_attr *attr)\n+{\n+\tstruct git_attr_check_elem *elem;\n+\tif (check->finalized)\n+\t\tdie(\"BUG: append after git_attr_check structure is finalized\");\n+\tif (!attr_check_is_dynamic(check))\n+\t\tdie(\"BUG: appending to a statically initialized git_attr_check\");\n+\tALLOC_GROW(check->check, check->check_nr + 1, check->check_alloc);\n+\telem = &check->check[check->check_nr++];\n+\telem->attr = attr;\n+\treturn elem;\n+}\n+\n+void git_attr_check_clear(struct git_attr_check *check)\n+{\n+\tif (!attr_check_is_dynamic(check))\n+\t\tdie(\"BUG: clearing a statically initialized git_attr_check\");\n+\tfree(check->check);\n+\tcheck->check_nr = 0;\n+\tcheck->check_alloc = 0;\n+\tcheck->finalized = 0;\n+}\n+\n+void git_attr_check_free(struct git_attr_check *check)\n+{\n+\tgit_attr_check_clear(check);\n+\tfree(check);\n+}\ndiff --git a/attr.h b/attr.h\nindex 3fd8690..0d94077 100644\n--- a/attr.h\n+++ b/attr.h\n@@ -30,6 +30,7 @@ struct git_attr_check_elem {\n };\n \n struct git_attr_check {\n+\tint finalized;\n \tint check_nr;\n \tint check_alloc;\n \tstruct git_attr_check_elem *check;\n@@ -38,6 +39,12 @@ struct git_attr_check {\n extern struct git_attr_check *git_attr_check_initl(const char *, ...);\n extern int git_check_attr(const char *path, struct git_attr_check *);\n \n+extern struct git_attr_check *git_attr_check_alloc(void);\n+extern struct git_attr_check_elem *git_attr_check_append(struct git_attr_check *, const struct git_attr *);\n+\n+extern void git_attr_check_clear(struct git_attr_check *);\n+extern void git_attr_check_free(struct git_attr_check *);\n+\n /*\n  * Return the name of the attribute represented by the argument.  The\n  * return value is a pointer to a null-delimited string that is part\n@@ -48,13 +55,10 @@ extern const char *git_attr_name(const struct git_attr *);\n int git_check_attrs(const char *path, int, struct git_attr_check_elem *);\n \n /*\n- * Retrieve all attributes that apply to the specified path.  *num\n- * will be set to the number of attributes on the path; **check will\n- * be set to point at a newly-allocated array of git_attr_check\n- * objects describing the attributes and their values.  *check must be\n- * free()ed by the caller.\n+ * Retrieve all attributes that apply to the specified path.\n+ * check holds the attributes and their values.\n  */\n-int git_all_attrs(const char *path, int *num, struct git_attr_check_elem **check);\n+void git_all_attrs(const char *path, struct git_attr_check *check);\n \n enum git_attr_direction {\n \tGIT_ATTR_CHECKIN,\ndiff --git a/builtin/check-attr.c b/builtin/check-attr.c\nindex 97e3837..ec61476 100644\n--- a/builtin/check-attr.c\n+++ b/builtin/check-attr.c\n@@ -24,12 +24,13 @@ static const struct option check_attr_options[] = {\n \tOPT_END()\n };\n \n-static void output_attr(int cnt, struct git_attr_check_elem *check,\n-\t\t\tconst char *file)\n+static void output_attr(struct git_attr_check *check, const char *file)\n {\n \tint j;\n+\tint cnt = check->check_nr;\n+\n \tfor (j = 0; j < cnt; j++) {\n-\t\tconst char *value = check[j].value;\n+\t\tconst char *value = check->check[j].value;\n \n \t\tif (ATTR_TRUE(value))\n \t\t\tvalue = \"set\";\n@@ -42,36 +43,37 @@ static void output_attr(int cnt, struct git_attr_check_elem *check,\n \t\t\tprintf(\"%s%c\" /* path */\n \t\t\t       \"%s%c\" /* attrname */\n \t\t\t       \"%s%c\" /* attrvalue */,\n-\t\t\t       file, 0, git_attr_name(check[j].attr), 0, value, 0);\n+\t\t\t       file, 0,\n+\t\t\t       git_attr_name(check->check[j].attr), 0, value, 0);\n \t\t} else {\n \t\t\tquote_c_style(file, NULL, stdout, 0);\n-\t\t\tprintf(\": %s: %s\\n\", git_attr_name(check[j].attr), value);\n+\t\t\tprintf(\": %s: %s\\n\",\n+\t\t\t       git_attr_name(check->check[j].attr), value);\n \t\t}\n-\n \t}\n }\n \n static void check_attr(const char *prefix,\n-\t\t       int cnt, struct git_attr_check_elem *check,\n+\t\t       struct git_attr_check *check,\n \t\t       const char *file)\n {\n \tchar *full_path =\n \t\tprefix_path(prefix, prefix ? strlen(prefix) : 0, file);\n \tif (check != NULL) {\n-\t\tif (git_check_attrs(full_path, cnt, check))\n-\t\t\tdie(\"git_check_attrs died\");\n-\t\toutput_attr(cnt, check, file);\n+\t\tif (git_check_attr(full_path, check))\n+\t\t\tdie(\"git_check_attr died\");\n+\t\toutput_attr(check, file);\n \t} else {\n-\t\tif (git_all_attrs(full_path, &cnt, &check))\n-\t\t\tdie(\"git_all_attrs died\");\n-\t\toutput_attr(cnt, check, file);\n-\t\tfree(check);\n+\t\tcheck = git_attr_check_alloc();\n+\t\tgit_all_attrs(full_path, check);\n+\t\toutput_attr(check, file);\n+\t\tgit_attr_check_free(check);\n \t}\n \tfree(full_path);\n }\n \n static void check_attr_stdin_paths(const char *prefix,\n-\t\t\t\t   int cnt, struct git_attr_check_elem *check)\n+\t\t\t\t   struct git_attr_check *check)\n {\n \tstruct strbuf buf = STRBUF_INIT;\n \tstruct strbuf unquoted = STRBUF_INIT;\n@@ -85,7 +87,7 @@ static void check_attr_stdin_paths(const char *prefix,\n \t\t\t\tdie(\"line is badly quoted\");\n \t\t\tstrbuf_swap(&buf, &unquoted);\n \t\t}\n-\t\tcheck_attr(prefix, cnt, check, buf.buf);\n+\t\tcheck_attr(prefix, check, buf.buf);\n \t\tmaybe_flush_or_die(stdout, \"attribute to stdout\");\n \t}\n \tstrbuf_release(&buf);\n@@ -100,7 +102,7 @@ static NORETURN void error_with_usage(const char *msg)\n \n int cmd_check_attr(int argc, const char **argv, const char *prefix)\n {\n-\tstruct git_attr_check_elem *check;\n+\tstruct git_attr_check *check;\n \tint cnt, i, doubledash, filei;\n \n \tif (!is_bare_repository())\n@@ -163,24 +165,21 @@ int cmd_check_attr(int argc, const char **argv, const char *prefix)\n \tif (all_attrs) {\n \t\tcheck = NULL;\n \t} else {\n-\t\tcheck = xcalloc(cnt, sizeof(*check));\n+\t\tcheck = git_attr_check_alloc();\n \t\tfor (i = 0; i < cnt; i++) {\n-\t\t\tconst char *name;\n-\t\t\tstruct git_attr *a;\n-\t\t\tname = argv[i];\n-\t\t\ta = git_attr(name);\n+\t\t\tstruct git_attr *a = git_attr(argv[i]);\n \t\t\tif (!a)\n \t\t\t\treturn error(\"%s: not a valid attribute name\",\n-\t\t\t\t\tname);\n-\t\t\tcheck[i].attr = a;\n+\t\t\t\t\t     argv[i]);\n+\t\t\tgit_attr_check_append(check, a);\n \t\t}\n \t}\n \n \tif (stdin_paths)\n-\t\tcheck_attr_stdin_paths(prefix, cnt, check);\n+\t\tcheck_attr_stdin_paths(prefix, check);\n \telse {\n \t\tfor (i = filei; i < argc; i++)\n-\t\t\tcheck_attr(prefix, cnt, check, argv[i]);\n+\t\t\tcheck_attr(prefix, check, argv[i]);\n \t\tmaybe_flush_or_die(stdout, \"attribute to stdout\");\n \t}\n \treturn 0;\n-- \n2.10.1.508.g6572022\n\n"},{"id":"304697","messageId":"20161022233225.8883-15-sbeller@google.com","threadId":"44358","inReplyTo":"20161022233225.8883-1-sbeller@google.com","subject":"[PATCH 14/36] attr: retire git_check_attrs() API","fromName":"Stefan Beller","fromEmail":"sbeller@google.com","sentAt":"2016-10-22T23:32:03Z","receivedAt":"2016-10-22T23:33:11Z","isPatch":true,"sender":{"key":"stefanbeller@gmail.com","avatar":"https://avatars.githubusercontent.com/u/455868?v=4"},"body":"From: Junio C Hamano <gitster@pobox.com>\n\nSince nobody uses the old API, make it file-scope static, and update\nthe documentation to describe the new API.\n\nSigned-off-by: Junio C Hamano <gitster@pobox.com>\nSigned-off-by: Stefan Beller <sbeller@google.com>\n---\n Documentation/technical/api-gitattributes.txt | 82 ++++++++++++++++++---------\n attr.c                                        |  3 +-\n attr.h                                        |  2 -\n 3 files changed, 56 insertions(+), 31 deletions(-)\n\ndiff --git a/Documentation/technical/api-gitattributes.txt b/Documentation/technical/api-gitattributes.txt\nindex 2602668..92fc32a 100644\n--- a/Documentation/technical/api-gitattributes.txt\n+++ b/Documentation/technical/api-gitattributes.txt\n@@ -16,10 +16,15 @@ Data Structure\n \tof no interest to the calling programs.  The name of the\n \tattribute can be retrieved by calling `git_attr_name()`.\n \n+`struct git_attr_check_elem`::\n+\n+\tThis structure represents one attribute and its value.\n+\n `struct git_attr_check`::\n \n-\tThis structure represents a set of attributes to check in a call\n-\tto `git_check_attr()` function, and receives the results.\n+\tThis structure represents a collection of `git_attr_check_elem`.\n+\tIt is passed to `git_check_attr()` function, specifying the\n+\tattributes to check, and receives their values.\n \n \n Attribute Values\n@@ -48,49 +53,51 @@ value of the attribute for the path.\n Querying Specific Attributes\n ----------------------------\n \n-* Prepare an array of `struct git_attr_check` to define the list of\n-  attributes you would want to check.  To populate this array, you would\n-  need to define necessary attributes by calling `git_attr()` function.\n+* Prepare `struct git_attr_check` using git_attr_check_initl()\n+  function, enumerating the names of attributes whose values you are\n+  interested in, terminated with a NULL pointer.  Alternatively, an\n+  empty `struct git_attr_check` can be prepared by calling\n+  `git_attr_check_alloc()` function and then attributes you want to\n+  ask about can be added to it with `git_attr_check_append()`\n+  function.\n \n * Call `git_check_attr()` to check the attributes for the path.\n \n-* Inspect `git_attr_check` structure to see how each of the attribute in\n-  the array is defined for the path.\n+* Inspect `git_attr_check` structure to see how each of the\n+  attribute in the array is defined for the path.\n \n \n Example\n -------\n \n-To see how attributes \"crlf\" and \"indent\" are set for different paths.\n+To see how attributes \"crlf\" and \"ident\" are set for different paths.\n \n-. Prepare an array of `struct git_attr_check` with two elements (because\n-  we are checking two attributes).  Initialize their `attr` member with\n-  pointers to `struct git_attr` obtained by calling `git_attr()`:\n+. Prepare a `struct git_attr_check` with two elements (because\n+  we are checking two attributes):\n \n ------------\n-static struct git_attr_check check[2];\n+static struct git_attr_check *check;\n static void setup_check(void)\n {\n-\tif (check[0].attr)\n+\tif (check)\n \t\treturn; /* already done */\n-\tcheck[0].attr = git_attr(\"crlf\");\n-\tcheck[1].attr = git_attr(\"ident\");\n+\tcheck = git_attr_check_initl(\"crlf\", \"ident\", NULL);\n }\n ------------\n \n-. Call `git_check_attr()` with the prepared array of `struct git_attr_check`:\n+. Call `git_check_attr()` with the prepared `struct git_attr_check`:\n \n ------------\n \tconst char *path;\n \n \tsetup_check();\n-\tgit_check_attr(path, ARRAY_SIZE(check), check);\n+\tgit_check_attr(path, check);\n ------------\n \n-. Act on `.value` member of the result, left in `check[]`:\n+. Act on `.value` member of the result, left in `check->check[]`:\n \n ------------\n-\tconst char *value = check[0].value;\n+\tconst char *value = check->check[0].value;\n \n \tif (ATTR_TRUE(value)) {\n \t\tThe attribute is Set, by listing only the name of the\n@@ -109,20 +116,39 @@ static void setup_check(void)\n \t}\n ------------\n \n+To see how attributes in argv[] are set for different paths, only\n+the first step in the above would be different.\n+\n+------------\n+static struct git_attr_check *check;\n+static void setup_check(const char **argv)\n+{\n+\tcheck = git_attr_check_alloc();\n+\twhile (*argv) {\n+\t\tstruct git_attr *attr = git_attr(*argv);\n+\t\tgit_attr_check_append(check, attr);\n+\t\targv++;\n+\t}\n+}\n+------------\n+\n \n Querying All Attributes\n -----------------------\n \n To get the values of all attributes associated with a file:\n \n-* Call `git_all_attrs()`, which returns an array of `git_attr_check`\n-  structures.\n+* Prepare an empty `git_attr_check` structure by calling\n+  `git_attr_check_alloc()`.\n+\n+* Call `git_all_attrs()`, which populates the `git_attr_check`\n+  with the attributes attached to the path.\n \n-* Iterate over the `git_attr_check` array to examine the attribute\n-  names and values.  The name of the attribute described by a\n-  `git_attr_check` object can be retrieved via\n-  `git_attr_name(check[i].attr)`.  (Please note that no items will be\n-  returned for unset attributes, so `ATTR_UNSET()` will return false\n-  for all returned `git_array_check` objects.)\n+* Iterate over the `git_attr_check.check[]` array to examine\n+  the attribute names and values.  The name of the attribute\n+  described by a  `git_attr_check.check[]` object can be retrieved via\n+  `git_attr_name(check->check[i].attr)`.  (Please note that no items\n+  will be returned for unset attributes, so `ATTR_UNSET()` will return\n+  false for all returned `git_array_check` objects.)\n \n-* Free the `git_array_check` array.\n+* Free the `git_array_check` by calling `git_attr_check_free()`.\ndiff --git a/attr.c b/attr.c\nindex 76f0d6b..d427798 100644\n--- a/attr.c\n+++ b/attr.c\n@@ -778,7 +778,8 @@ static void collect_some_attrs(const char *path, int num,\n \t\trem = fill(path, pathlen, basename_offset, stk, rem);\n }\n \n-int git_check_attrs(const char *path, int num, struct git_attr_check_elem *check)\n+static int git_check_attrs(const char *path, int num,\n+\t\t\t   struct git_attr_check_elem *check)\n {\n \tint i;\n \ndiff --git a/attr.h b/attr.h\nindex 0d94077..506db0c 100644\n--- a/attr.h\n+++ b/attr.h\n@@ -52,8 +52,6 @@ extern void git_attr_check_free(struct git_attr_check *);\n  */\n extern const char *git_attr_name(const struct git_attr *);\n \n-int git_check_attrs(const char *path, int, struct git_attr_check_elem *);\n-\n /*\n  * Retrieve all attributes that apply to the specified path.\n  * check holds the attributes and their values.\n-- \n2.10.1.508.g6572022\n\n"},{"id":"304698","messageId":"20161022233225.8883-12-sbeller@google.com","threadId":"44358","inReplyTo":"20161022233225.8883-1-sbeller@google.com","subject":"[PATCH 11/36] attr: (re)introduce git_check_attr() and struct git_attr_check","fromName":"Stefan Beller","fromEmail":"sbeller@google.com","sentAt":"2016-10-22T23:32:00Z","receivedAt":"2016-10-22T23:33:12Z","isPatch":true,"sender":{"key":"stefanbeller@gmail.com","avatar":"https://avatars.githubusercontent.com/u/455868?v=4"},"body":"From: Junio C Hamano <gitster@pobox.com>\n\nA common pattern to check N attributes for many paths is to\n\n (1) prepare an array A of N git_attr_check_elem items;\n (2) call git_attr() to intern the N attribute names and fill A;\n (3) repeatedly call git_check_attrs() for path with N and A;\n\nA look-up for these N attributes for a single path P scans the\nentire attr_stack, starting from the .git/info/attributes file and\nthen .gitattributes file in the directory the path P is in, going\nupwards to find .gitattributes file found in parent directories.\n\nAn earlier commit 06a604e6 (attr: avoid heavy work when we know the\nspecified attr is not defined, 2014-12-28) tried to optimize out\nthis scanning for one trivial special case: when the attribute being\nsought is known not to exist, we do not have to scan for it.  While\nthis may be a cheap and effective heuristic, it would not work well\nwhen N is (much) more than 1.\n\nWhat we would want is a more customized way to skip irrelevant\nentries in the attribute stack, and the definition of irrelevance\nis tied to the set of attributes passed to git_check_attrs() call,\ni.e. the set of attributes being sought.  The data necessary for\nthis optimization needs to live alongside the set of attributes, but\na simple array of git_attr_check_elem simply does not have any place\nfor that.\n\nIntroduce \"struct git_attr_check\" that contains N, the number of\nattributes being sought, and A, the array that holds N\ngit_attr_check_elem items, and a function git_check_attr() that\ntakes a path P and this structure as its parameters.  This structure\ncan later be extended to hold extra data necessary for optimization.\n\nAlso, to make it easier to write the first two steps in common\ncases, introduce git_attr_check_initl() helper function, which takes\na NULL-terminated list of attribute names and initialize this\nstructure.\n\nAs an illustration of this new API, convert archive.c that asks for\nexport-subst and export-ignore attributes for each paths.\n\nSigned-off-by: Junio C Hamano <gitster@pobox.com>\nSigned-off-by: Stefan Beller <sbeller@google.com>\n---\n archive.c | 24 ++++++------------------\n attr.c    | 34 ++++++++++++++++++++++++++++++++++\n attr.h    |  9 +++++++++\n 3 files changed, 49 insertions(+), 18 deletions(-)\n\ndiff --git a/archive.c b/archive.c\nindex 2dc8d6c..11e3951 100644\n--- a/archive.c\n+++ b/archive.c\n@@ -87,19 +87,6 @@ void *sha1_file_to_archive(const struct archiver_args *args,\n \treturn buffer;\n }\n \n-static void setup_archive_check(struct git_attr_check_elem *check)\n-{\n-\tstatic struct git_attr *attr_export_ignore;\n-\tstatic struct git_attr *attr_export_subst;\n-\n-\tif (!attr_export_ignore) {\n-\t\tattr_export_ignore = git_attr(\"export-ignore\");\n-\t\tattr_export_subst = git_attr(\"export-subst\");\n-\t}\n-\tcheck[0].attr = attr_export_ignore;\n-\tcheck[1].attr = attr_export_subst;\n-}\n-\n struct directory {\n \tstruct directory *up;\n \tstruct object_id oid;\n@@ -123,7 +110,7 @@ static int write_archive_entry(const unsigned char *sha1, const char *base,\n \tstruct archiver_context *c = context;\n \tstruct archiver_args *args = c->args;\n \twrite_archive_entry_fn_t write_entry = c->write_entry;\n-\tstruct git_attr_check_elem check[2];\n+\tstatic struct git_attr_check *check;\n \tconst char *path_without_prefix;\n \tint err;\n \n@@ -137,11 +124,12 @@ static int write_archive_entry(const unsigned char *sha1, const char *base,\n \t\tstrbuf_addch(&path, '/');\n \tpath_without_prefix = path.buf + args->baselen;\n \n-\tsetup_archive_check(check);\n-\tif (!git_check_attrs(path_without_prefix, ARRAY_SIZE(check), check)) {\n-\t\tif (ATTR_TRUE(check[0].value))\n+\tif (!check)\n+\t\tcheck = git_attr_check_initl(\"export-ignore\", \"export-subst\", NULL);\n+\tif (!git_check_attr(path_without_prefix, check)) {\n+\t\tif (ATTR_TRUE(check->check[0].value))\n \t\t\treturn 0;\n-\t\targs->convert = ATTR_TRUE(check[1].value);\n+\t\targs->convert = ATTR_TRUE(check->check[1].value);\n \t}\n \n \tif (S_ISDIR(mode) || S_ISGITLINK(mode)) {\ndiff --git a/attr.c b/attr.c\nindex c99e23a..861e1a2 100644\n--- a/attr.c\n+++ b/attr.c\n@@ -829,3 +829,37 @@ void git_attr_set_direction(enum git_attr_direction new, struct index_state *ist\n \t\tdrop_attr_stack();\n \tuse_index = istate;\n }\n+\n+int git_check_attr(const char *path, struct git_attr_check *check)\n+{\n+\treturn git_check_attrs(path, check->check_nr, check->check);\n+}\n+\n+struct git_attr_check *git_attr_check_initl(const char *one, ...)\n+{\n+\tstruct git_attr_check *check;\n+\tint cnt;\n+\tva_list params;\n+\tconst char *param;\n+\n+\tva_start(params, one);\n+\tfor (cnt = 1; (param = va_arg(params, const char *)) != NULL; cnt++)\n+\t\t;\n+\tva_end(params);\n+\tcheck = xcalloc(1,\n+\t\t\tsizeof(*check) + cnt * sizeof(*(check->check)));\n+\tcheck->check_nr = cnt;\n+\tcheck->check = (struct git_attr_check_elem *)(check + 1);\n+\n+\tcheck->check[0].attr = git_attr(one);\n+\tva_start(params, one);\n+\tfor (cnt = 1; cnt < check->check_nr; cnt++) {\n+\t\tparam = va_arg(params, const char *);\n+\t\tif (!param)\n+\t\t\tdie(\"BUG: counted %d != ended at %d\",\n+\t\t\t    check->check_nr, cnt);\n+\t\tcheck->check[cnt].attr = git_attr(param);\n+\t}\n+\tva_end(params);\n+\treturn check;\n+}\ndiff --git a/attr.h b/attr.h\nindex dd3c4a3..3fd8690 100644\n--- a/attr.h\n+++ b/attr.h\n@@ -29,6 +29,15 @@ struct git_attr_check_elem {\n \tconst char *value;\n };\n \n+struct git_attr_check {\n+\tint check_nr;\n+\tint check_alloc;\n+\tstruct git_attr_check_elem *check;\n+};\n+\n+extern struct git_attr_check *git_attr_check_initl(const char *, ...);\n+extern int git_check_attr(const char *path, struct git_attr_check *);\n+\n /*\n  * Return the name of the attribute represented by the argument.  The\n  * return value is a pointer to a null-delimited string that is part\n-- \n2.10.1.508.g6572022\n\n"},{"id":"304699","messageId":"20161022233225.8883-17-sbeller@google.com","threadId":"44358","inReplyTo":"20161022233225.8883-1-sbeller@google.com","subject":"[PATCH 16/36] attr: add counted string version of git_attr()","fromName":"Stefan Beller","fromEmail":"sbeller@google.com","sentAt":"2016-10-22T23:32:05Z","receivedAt":"2016-10-22T23:33:15Z","isPatch":true,"sender":{"key":"stefanbeller@gmail.com","avatar":"https://avatars.githubusercontent.com/u/455868?v=4"},"body":"From: Junio C Hamano <gitster@pobox.com>\n\nOften a potential caller has <name, namelen> pair that\nrepresents the name it wants to create an attribute out of.\n\nWhen name[namelen] is not NUL, the caller has to xmemdupz()\nonly to call git_attr().\n\nAdd git_attr_counted() that takes such a counted string instead of\n\"const char *name\".\n\nSigned-off-by: Junio C Hamano <gitster@pobox.com>\nSigned-off-by: Stefan Beller <sbeller@google.com>\n---\n attr.c | 8 ++++----\n attr.h | 5 ++++-\n 2 files changed, 8 insertions(+), 5 deletions(-)\n\ndiff --git a/attr.c b/attr.c\nindex 9bec243..90dbacd 100644\n--- a/attr.c\n+++ b/attr.c\n@@ -78,7 +78,7 @@ static int invalid_attr_name(const char *name, int namelen)\n \treturn 0;\n }\n \n-static struct git_attr *git_attr_internal(const char *name, int len)\n+struct git_attr *git_attr_counted(const char *name, size_t len)\n {\n \tunsigned hval = hash_name(name, len);\n \tunsigned pos = hval % HASHSIZE;\n@@ -109,7 +109,7 @@ static struct git_attr *git_attr_internal(const char *name, int len)\n \n struct git_attr *git_attr(const char *name)\n {\n-\treturn git_attr_internal(name, strlen(name));\n+\treturn git_attr_counted(name, strlen(name));\n }\n \n /* What does a matched pattern decide? */\n@@ -199,7 +199,7 @@ static const char *parse_attr(const char *src, int lineno, const char *cp,\n \t\telse {\n \t\t\te->setto = xmemdupz(equals + 1, ep - equals - 1);\n \t\t}\n-\t\te->attr = git_attr_internal(cp, len);\n+\t\te->attr = git_attr_counted(cp, len);\n \t}\n \treturn ep + strspn(ep, blank);\n }\n@@ -254,7 +254,7 @@ static struct match_attr *parse_attr_line(const char *line, const char *src,\n \t\t      sizeof(struct attr_state) * num_attr +\n \t\t      (is_macro ? 0 : namelen + 1));\n \tif (is_macro) {\n-\t\tres->u.attr = git_attr_internal(name, namelen);\n+\t\tres->u.attr = git_attr_counted(name, namelen);\n \t\tres->u.attr->maybe_macro = 1;\n \t} else {\n \t\tchar *p = (char *)&(res->state[num_attr]);\ndiff --git a/attr.h b/attr.h\nindex c84f164..bcedf92 100644\n--- a/attr.h\n+++ b/attr.h\n@@ -8,7 +8,10 @@ struct git_attr;\n  * Given a string, return the gitattribute object that\n  * corresponds to it.\n  */\n-struct git_attr *git_attr(const char *);\n+extern struct git_attr *git_attr(const char *);\n+\n+/* The same, but with counted string */\n+extern struct git_attr *git_attr_counted(const char *, size_t);\n \n /* Internal use */\n extern const char git_attr__true[];\n-- \n2.10.1.508.g6572022\n\n"},{"id":"304700","messageId":"20161022233225.8883-18-sbeller@google.com","threadId":"44358","inReplyTo":"20161022233225.8883-1-sbeller@google.com","subject":"[PATCH 17/36] attr: expose validity check for attribute names","fromName":"Stefan Beller","fromEmail":"sbeller@google.com","sentAt":"2016-10-22T23:32:06Z","receivedAt":"2016-10-22T23:33:18Z","isPatch":true,"sender":{"key":"stefanbeller@gmail.com","avatar":"https://avatars.githubusercontent.com/u/455868?v=4"},"body":"From: Junio C Hamano <gitster@pobox.com>\n\nExport attr_name_valid() function, and a helper function that\nreturns the message to be given when a given <name, len> pair\nis not a good name for an attribute.\n\nWe could later update the message to exactly spell out what the\nrules for a good attribute name are, etc.\n\nSigned-off-by: Junio C Hamano <gitster@pobox.com>\nSigned-off-by: Stefan Beller <sbeller@google.com>\n---\n attr.c | 39 +++++++++++++++++++++++++--------------\n attr.h |  3 +++\n 2 files changed, 28 insertions(+), 14 deletions(-)\n\ndiff --git a/attr.c b/attr.c\nindex 90dbacd..659dc41 100644\n--- a/attr.c\n+++ b/attr.c\n@@ -59,23 +59,38 @@ static unsigned hash_name(const char *name, int namelen)\n \treturn val;\n }\n \n-static int invalid_attr_name(const char *name, int namelen)\n+int attr_name_valid(const char *name, size_t namelen)\n {\n \t/*\n \t * Attribute name cannot begin with '-' and must consist of\n \t * characters from [-A-Za-z0-9_.].\n \t */\n \tif (namelen <= 0 || *name == '-')\n-\t\treturn -1;\n+\t\treturn 0;\n \twhile (namelen--) {\n \t\tchar ch = *name++;\n \t\tif (! (ch == '-' || ch == '.' || ch == '_' ||\n \t\t       ('0' <= ch && ch <= '9') ||\n \t\t       ('a' <= ch && ch <= 'z') ||\n \t\t       ('A' <= ch && ch <= 'Z')) )\n-\t\t\treturn -1;\n+\t\t\treturn 0;\n \t}\n-\treturn 0;\n+\treturn 1;\n+}\n+\n+void invalid_attr_name_message(struct strbuf *err, const char *name, int len)\n+{\n+\tstrbuf_addf(err, _(\"%.*s is not a valid attribute name\"),\n+\t\t    len, name);\n+}\n+\n+static void report_invalid_attr(const char *name, size_t len,\n+\t\t\t\tconst char *src, int lineno)\n+{\n+\tstruct strbuf err = STRBUF_INIT;\n+\tinvalid_attr_name_message(&err, name, len);\n+\tfprintf(stderr, \"%s: %s:%d\\n\", err.buf, src, lineno);\n+\tstrbuf_release(&err);\n }\n \n struct git_attr *git_attr_counted(const char *name, size_t len)\n@@ -90,7 +105,7 @@ struct git_attr *git_attr_counted(const char *name, size_t len)\n \t\t\treturn a;\n \t}\n \n-\tif (invalid_attr_name(name, len))\n+\tif (!attr_name_valid(name, len))\n \t\treturn NULL;\n \n \tFLEX_ALLOC_MEM(a, name, name, len);\n@@ -176,17 +191,15 @@ static const char *parse_attr(const char *src, int lineno, const char *cp,\n \t\t\tcp++;\n \t\t\tlen--;\n \t\t}\n-\t\tif (invalid_attr_name(cp, len)) {\n-\t\t\tfprintf(stderr,\n-\t\t\t\t\"%.*s is not a valid attribute name: %s:%d\\n\",\n-\t\t\t\tlen, cp, src, lineno);\n+\t\tif (!attr_name_valid(cp, len)) {\n+\t\t\treport_invalid_attr(cp, len, src, lineno);\n \t\t\treturn NULL;\n \t\t}\n \t} else {\n \t\t/*\n \t\t * As this function is always called twice, once with\n \t\t * e == NULL in the first pass and then e != NULL in\n-\t\t * the second pass, no need for invalid_attr_name()\n+\t\t * the second pass, no need for attr_name_valid()\n \t\t * check here.\n \t\t */\n \t\tif (*cp == '-' || *cp == '!') {\n@@ -229,10 +242,8 @@ static struct match_attr *parse_attr_line(const char *line, const char *src,\n \t\tname += strlen(ATTRIBUTE_MACRO_PREFIX);\n \t\tname += strspn(name, blank);\n \t\tnamelen = strcspn(name, blank);\n-\t\tif (invalid_attr_name(name, namelen)) {\n-\t\t\tfprintf(stderr,\n-\t\t\t\t\"%.*s is not a valid attribute name: %s:%d\\n\",\n-\t\t\t\tnamelen, name, src, lineno);\n+\t\tif (!attr_name_valid(name, namelen)) {\n+\t\t\treport_invalid_attr(name, namelen, src, lineno);\n \t\t\tgoto fail_return;\n \t\t}\n \t}\ndiff --git a/attr.h b/attr.h\nindex bcedf92..40abc16 100644\n--- a/attr.h\n+++ b/attr.h\n@@ -13,6 +13,9 @@ extern struct git_attr *git_attr(const char *);\n /* The same, but with counted string */\n extern struct git_attr *git_attr_counted(const char *, size_t);\n \n+extern int attr_name_valid(const char *name, size_t namelen);\n+extern void invalid_attr_name_message(struct strbuf *, const char *, int);\n+\n /* Internal use */\n extern const char git_attr__true[];\n extern const char git_attr__false[];\n-- \n2.10.1.508.g6572022\n\n"},{"id":"304701","messageId":"20161022233225.8883-19-sbeller@google.com","threadId":"44358","inReplyTo":"20161022233225.8883-1-sbeller@google.com","subject":"[PATCH 18/36] attr: support quoting pathname patterns in C style","fromName":"Stefan Beller","fromEmail":"sbeller@google.com","sentAt":"2016-10-22T23:32:07Z","receivedAt":"2016-10-22T23:33:20Z","isPatch":true,"sender":{"key":"stefanbeller@gmail.com","avatar":"https://avatars.githubusercontent.com/u/455868?v=4"},"body":"From: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>\n\nFull pattern must be quoted. So 'pat\"t\"ern attr' will give exactly\n'pat\"t\"ern', not 'pattern'. Also clarify that leading whitespaces are\nnot part of the pattern and document comment syntax.\n\nSigned-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>\nSigned-off-by: Junio C Hamano <gitster@pobox.com>\nSigned-off-by: Stefan Beller <sbeller@google.com>\n---\n Documentation/gitattributes.txt |  8 +++++---\n attr.c                          | 15 +++++++++++++--\n t/t0003-attributes.sh           | 26 ++++++++++++++++++++++++++\n 3 files changed, 44 insertions(+), 5 deletions(-)\n\ndiff --git a/Documentation/gitattributes.txt b/Documentation/gitattributes.txt\nindex 7aff940..8a061af 100644\n--- a/Documentation/gitattributes.txt\n+++ b/Documentation/gitattributes.txt\n@@ -21,9 +21,11 @@ Each line in `gitattributes` file is of form:\n \tpattern\tattr1 attr2 ...\n \n That is, a pattern followed by an attributes list,\n-separated by whitespaces.  When the pattern matches the\n-path in question, the attributes listed on the line are given to\n-the path.\n+separated by whitespaces. Leading and trailing whitespaces are\n+ignored. Lines that begin with '#' are ignored. Patterns\n+that begin with a double quote are quoted in C style.\n+When the pattern matches the path in question, the attributes\n+listed on the line are given to the path.\n \n Each attribute can be in one of these states for a given path:\n \ndiff --git a/attr.c b/attr.c\nindex 659dc41..eba582b 100644\n--- a/attr.c\n+++ b/attr.c\n@@ -13,6 +13,7 @@\n #include \"attr.h\"\n #include \"dir.h\"\n #include \"utf8.h\"\n+#include \"quote.h\"\n \n const char git_attr__true[] = \"(builtin)true\";\n const char git_attr__false[] = \"\\0(builtin)false\";\n@@ -225,12 +226,21 @@ static struct match_attr *parse_attr_line(const char *line, const char *src,\n \tconst char *cp, *name, *states;\n \tstruct match_attr *res = NULL;\n \tint is_macro;\n+\tstruct strbuf pattern = STRBUF_INIT;\n \n \tcp = line + strspn(line, blank);\n \tif (!*cp || *cp == '#')\n \t\treturn NULL;\n \tname = cp;\n-\tnamelen = strcspn(name, blank);\n+\n+\tif (*cp == '\"' && !unquote_c_style(&pattern, name, &states)) {\n+\t\tname = pattern.buf;\n+\t\tnamelen = pattern.len;\n+\t} else {\n+\t\tnamelen = strcspn(name, blank);\n+\t\tstates = name + namelen;\n+\t}\n+\n \tif (strlen(ATTRIBUTE_MACRO_PREFIX) < namelen &&\n \t    starts_with(name, ATTRIBUTE_MACRO_PREFIX)) {\n \t\tif (!macro_ok) {\n@@ -250,7 +260,6 @@ static struct match_attr *parse_attr_line(const char *line, const char *src,\n \telse\n \t\tis_macro = 0;\n \n-\tstates = name + namelen;\n \tstates += strspn(states, blank);\n \n \t/* First pass to count the attr_states */\n@@ -293,9 +302,11 @@ static struct match_attr *parse_attr_line(const char *line, const char *src,\n \t\t\tcannot_trust_maybe_real = 1;\n \t}\n \n+\tstrbuf_release(&pattern);\n \treturn res;\n \n fail_return:\n+\tstrbuf_release(&pattern);\n \tfree(res);\n \treturn NULL;\n }\ndiff --git a/t/t0003-attributes.sh b/t/t0003-attributes.sh\nindex f0fbb42..f19ae4f 100755\n--- a/t/t0003-attributes.sh\n+++ b/t/t0003-attributes.sh\n@@ -13,10 +13,31 @@ attr_check () {\n \ttest_line_count = 0 err\n }\n \n+attr_check_quote () {\n+\n+\tpath=\"$1\"\n+\tquoted_path=\"$2\"\n+\texpect=\"$3\"\n+\n+\tgit check-attr test -- \"$path\" >actual &&\n+\techo \"\\\"$quoted_path\\\": test: $expect\" >expect &&\n+\ttest_cmp expect actual\n+\n+}\n+\n+test_expect_success 'open-quoted pathname' '\n+\techo \"\\\"a test=a\" >.gitattributes &&\n+\ttest_must_fail attr_check a a\n+'\n+\n+\n test_expect_success 'setup' '\n \tmkdir -p a/b/d a/c b &&\n \t(\n \t\techo \"[attr]notest !test\"\n+\t\techo \"\\\" d \\\"\ttest=d\"\n+\t\techo \" e\ttest=e\"\n+\t\techo \" e\\\"\ttest=e\"\n \t\techo \"f\ttest=f\"\n \t\techo \"a/i test=a/i\"\n \t\techo \"onoff test -test\"\n@@ -69,6 +90,11 @@ test_expect_success 'command line checks' '\n '\n \n test_expect_success 'attribute test' '\n+\n+\tattr_check \" d \" d &&\n+\tattr_check e e &&\n+\tattr_check_quote e\\\" e\\\\\\\" e &&\n+\n \tattr_check f f &&\n \tattr_check a/f f &&\n \tattr_check a/c/f f &&\n-- \n2.10.1.508.g6572022\n\n"},{"id":"304702","messageId":"20161022233225.8883-20-sbeller@google.com","threadId":"44358","inReplyTo":"20161022233225.8883-1-sbeller@google.com","subject":"[PATCH 19/36] attr.c: add push_stack() helper","fromName":"Stefan Beller","fromEmail":"sbeller@google.com","sentAt":"2016-10-22T23:32:08Z","receivedAt":"2016-10-22T23:33:23Z","isPatch":true,"sender":{"key":"stefanbeller@gmail.com","avatar":"https://avatars.githubusercontent.com/u/455868?v=4"},"body":"From: Junio C Hamano <gitster@pobox.com>\n\nThere are too many repetitious \"I have this new attr_stack element;\npush it at the top of the stack\" sequence.  The new helper function\npush_stack() gives us a way to express what is going on at these\nplaces, and as a side effect, halves the number of times we mention\nthe attr_stack global variable.\n\nSigned-off-by: Junio C Hamano <gitster@pobox.com>\nSigned-off-by: Stefan Beller <sbeller@google.com>\n---\n attr.c | 71 +++++++++++++++++++++++++++++++-----------------------------------\n 1 file changed, 33 insertions(+), 38 deletions(-)\n\ndiff --git a/attr.c b/attr.c\nindex eba582b..34c297d 100644\n--- a/attr.c\n+++ b/attr.c\n@@ -521,6 +521,18 @@ static int git_attr_system(void)\n \n static GIT_PATH_FUNC(git_path_info_attributes, INFOATTRIBUTES_FILE)\n \n+static void push_stack(struct attr_stack **attr_stack_p,\n+\t\t       struct attr_stack *elem, char *origin, size_t originlen)\n+{\n+\tif (elem) {\n+\t\telem->origin = origin;\n+\t\tif (origin)\n+\t\t\telem->originlen = originlen;\n+\t\telem->prev = *attr_stack_p;\n+\t\t*attr_stack_p = elem;\n+\t}\n+}\n+\n static void bootstrap_attr_stack(void)\n {\n \tstruct attr_stack *elem;\n@@ -528,52 +540,35 @@ static void bootstrap_attr_stack(void)\n \tif (attr_stack)\n \t\treturn;\n \n-\telem = read_attr_from_array(builtin_attr);\n-\telem->origin = NULL;\n-\telem->prev = attr_stack;\n-\tattr_stack = elem;\n-\n-\tif (git_attr_system()) {\n-\t\telem = read_attr_from_file(git_etc_gitattributes(), 1);\n-\t\tif (elem) {\n-\t\t\telem->origin = NULL;\n-\t\t\telem->prev = attr_stack;\n-\t\t\tattr_stack = elem;\n-\t\t}\n-\t}\n+\tpush_stack(&attr_stack, read_attr_from_array(builtin_attr), NULL, 0);\n+\n+\tif (git_attr_system())\n+\t\tpush_stack(&attr_stack,\n+\t\t\t   read_attr_from_file(git_etc_gitattributes(), 1),\n+\t\t\t   NULL, 0);\n \n \tif (!git_attributes_file)\n \t\tgit_attributes_file = xdg_config_home(\"attributes\");\n-\tif (git_attributes_file) {\n-\t\telem = read_attr_from_file(git_attributes_file, 1);\n-\t\tif (elem) {\n-\t\t\telem->origin = NULL;\n-\t\t\telem->prev = attr_stack;\n-\t\t\tattr_stack = elem;\n-\t\t}\n-\t}\n+\tif (git_attributes_file)\n+\t\tpush_stack(&attr_stack,\n+\t\t\t   read_attr_from_file(git_attributes_file, 1),\n+\t\t\t   NULL, 0);\n \n \tif (!is_bare_repository() || direction == GIT_ATTR_INDEX) {\n \t\telem = read_attr(GITATTRIBUTES_FILE, 1);\n-\t\telem->origin = xstrdup(\"\");\n-\t\telem->originlen = 0;\n-\t\telem->prev = attr_stack;\n-\t\tattr_stack = elem;\n+\t\tpush_stack(&attr_stack, elem, xstrdup(\"\"), 0);\n \t\tdebug_push(elem);\n \t}\n \n \telem = read_attr_from_file(git_path_info_attributes(), 1);\n \tif (!elem)\n \t\telem = xcalloc(1, sizeof(*elem));\n-\telem->origin = NULL;\n-\telem->prev = attr_stack;\n-\tattr_stack = elem;\n+\tpush_stack(&attr_stack, elem, NULL, 0);\n }\n \n static void prepare_attr_stack(const char *path, int dirlen)\n {\n \tstruct attr_stack *elem, *info;\n-\tint len;\n \tconst char *cp;\n \n \t/*\n@@ -633,20 +628,21 @@ static void prepare_attr_stack(const char *path, int dirlen)\n \n \t\tassert(attr_stack->origin);\n \t\twhile (1) {\n-\t\t\tlen = strlen(attr_stack->origin);\n+\t\t\tsize_t len = strlen(attr_stack->origin);\n+\t\t\tchar *origin;\n+\n \t\t\tif (dirlen <= len)\n \t\t\t\tbreak;\n \t\t\tcp = memchr(path + len + 1, '/', dirlen - len - 1);\n \t\t\tif (!cp)\n \t\t\t\tcp = path + dirlen;\n-\t\t\tstrbuf_add(&pathbuf, path, cp - path);\n-\t\t\tstrbuf_addch(&pathbuf, '/');\n-\t\t\tstrbuf_addstr(&pathbuf, GITATTRIBUTES_FILE);\n+\t\t\tstrbuf_addf(&pathbuf,\n+\t\t\t\t    \"%.*s/%s\", (int)(cp - path), path,\n+\t\t\t\t    GITATTRIBUTES_FILE);\n \t\t\telem = read_attr(pathbuf.buf, 0);\n \t\t\tstrbuf_setlen(&pathbuf, cp - path);\n-\t\t\telem->origin = strbuf_detach(&pathbuf, &elem->originlen);\n-\t\t\telem->prev = attr_stack;\n-\t\t\tattr_stack = elem;\n+\t\t\torigin = strbuf_detach(&pathbuf, &len);\n+\t\t\tpush_stack(&attr_stack, elem, origin, len);\n \t\t\tdebug_push(elem);\n \t\t}\n \n@@ -656,8 +652,7 @@ static void prepare_attr_stack(const char *path, int dirlen)\n \t/*\n \t * Finally push the \"info\" one at the top of the stack.\n \t */\n-\tinfo->prev = attr_stack;\n-\tattr_stack = info;\n+\tpush_stack(&attr_stack, info, NULL, 0);\n }\n \n static int path_matches(const char *pathname, int pathlen,\n-- \n2.10.1.508.g6572022\n\n"},{"id":"304703","messageId":"20161022233225.8883-22-sbeller@google.com","threadId":"44358","inReplyTo":"20161022233225.8883-1-sbeller@google.com","subject":"[PATCH 21/36] attr.c: rename a local variable check","fromName":"Stefan Beller","fromEmail":"sbeller@google.com","sentAt":"2016-10-22T23:32:10Z","receivedAt":"2016-10-22T23:33:26Z","isPatch":true,"sender":{"key":"stefanbeller@gmail.com","avatar":"https://avatars.githubusercontent.com/u/455868?v=4"},"body":"From: Junio C Hamano <gitster@pobox.com>\n\nThroughout this series, we are trying to use \"check\" to name an\ninstance of \"git_attr_check\" structure; let's rename a \"check\" that\nrefers to an array whose elements are git_attr_check_elem to avoid\nconfusion.\n\nSigned-off-by: Junio C Hamano <gitster@pobox.com>\nSigned-off-by: Stefan Beller <sbeller@google.com>\n---\n attr.c | 4 ++--\n 1 file changed, 2 insertions(+), 2 deletions(-)\n\ndiff --git a/attr.c b/attr.c\nindex 9ed4825..7869277 100644\n--- a/attr.c\n+++ b/attr.c\n@@ -682,12 +682,12 @@ static int macroexpand_one(int attr_nr, int rem);\n \n static int fill_one(const char *what, struct match_attr *a, int rem)\n {\n-\tstruct git_attr_check_elem *check = check_all_attr;\n+\tstruct git_attr_check_elem *celem = check_all_attr;\n \tint i;\n \n \tfor (i = a->num_attr - 1; 0 < rem && 0 <= i; i--) {\n \t\tstruct git_attr *attr = a->state[i].attr;\n-\t\tconst char **n = &(check[attr->attr_nr].value);\n+\t\tconst char **n = &(celem[attr->attr_nr].value);\n \t\tconst char *v = a->state[i].setto;\n \n \t\tif (*n == ATTR__UNKNOWN) {\n-- \n2.10.1.508.g6572022\n\n"},{"id":"304704","messageId":"20161022233225.8883-16-sbeller@google.com","threadId":"44358","inReplyTo":"20161022233225.8883-1-sbeller@google.com","subject":"[PATCH 15/36] attr: add counted string version of git_check_attr()","fromName":"Stefan Beller","fromEmail":"sbeller@google.com","sentAt":"2016-10-22T23:32:04Z","receivedAt":"2016-10-22T23:33:30Z","isPatch":true,"sender":{"key":"stefanbeller@gmail.com","avatar":"https://avatars.githubusercontent.com/u/455868?v=4"},"body":"From: Junio C Hamano <gitster@pobox.com>\n\nOften a potential caller has <path, pathlen> pair that\nrepresents the path it wants to ask attributes for; when\npath[pathlen] is not NUL, the caller has to xmemdupz()\nonly to call git_check_attr().\n\nAdd git_check_attr_counted() that takes such a counted\nstring instead of \"const char *path\".\n\nSigned-off-by: Junio C Hamano <gitster@pobox.com>\nSigned-off-by: Stefan Beller <sbeller@google.com>\n---\n attr.c | 23 ++++++++++++++---------\n attr.h |  1 +\n 2 files changed, 15 insertions(+), 9 deletions(-)\n\ndiff --git a/attr.c b/attr.c\nindex d427798..9bec243 100644\n--- a/attr.c\n+++ b/attr.c\n@@ -734,20 +734,19 @@ static int attr_check_is_dynamic(const struct git_attr_check *check)\n  * check_all_attr. If num is non-zero, only attributes in check[] are\n  * collected. Otherwise all attributes are collected.\n  */\n-static void collect_some_attrs(const char *path, int num,\n+static void collect_some_attrs(const char *path, int pathlen, int num,\n \t\t\t       struct git_attr_check_elem *check)\n \n {\n \tstruct attr_stack *stk;\n-\tint i, pathlen, rem, dirlen;\n+\tint i, rem, dirlen;\n \tconst char *cp, *last_slash = NULL;\n \tint basename_offset;\n \n-\tfor (cp = path; *cp; cp++) {\n+\tfor (cp = path; cp < path + pathlen; cp++) {\n \t\tif (*cp == '/' && cp[1])\n \t\t\tlast_slash = cp;\n \t}\n-\tpathlen = cp - path;\n \tif (last_slash) {\n \t\tbasename_offset = last_slash + 1 - path;\n \t\tdirlen = last_slash - path;\n@@ -778,12 +777,12 @@ static void collect_some_attrs(const char *path, int num,\n \t\trem = fill(path, pathlen, basename_offset, stk, rem);\n }\n \n-static int git_check_attrs(const char *path, int num,\n+static int git_check_attrs(const char *path, int pathlen, int num,\n \t\t\t   struct git_attr_check_elem *check)\n {\n \tint i;\n \n-\tcollect_some_attrs(path, num, check);\n+\tcollect_some_attrs(path, pathlen, num, check);\n \n \tfor (i = 0; i < num; i++) {\n \t\tconst char *value = check_all_attr[check[i].attr->attr_nr].value;\n@@ -800,7 +799,7 @@ void git_all_attrs(const char *path, struct git_attr_check *check)\n \tint i;\n \n \tgit_attr_check_clear(check);\n-\tcollect_some_attrs(path, 0, NULL);\n+\tcollect_some_attrs(path, strlen(path), 0, NULL);\n \n \tfor (i = 0; i < attr_nr; i++) {\n \t\tconst char *name = check_all_attr[i].attr->name;\n@@ -825,10 +824,16 @@ void git_attr_set_direction(enum git_attr_direction new, struct index_state *ist\n \tuse_index = istate;\n }\n \n-int git_check_attr(const char *path, struct git_attr_check *check)\n+int git_check_attr_counted(const char *path, int pathlen,\n+\t\t\t   struct git_attr_check *check)\n {\n \tcheck->finalized = 1;\n-\treturn git_check_attrs(path, check->check_nr, check->check);\n+\treturn git_check_attrs(path, pathlen, check->check_nr, check->check);\n+}\n+\n+int git_check_attr(const char *path, struct git_attr_check *check)\n+{\n+\treturn git_check_attr_counted(path, strlen(path), check);\n }\n \n struct git_attr_check *git_attr_check_initl(const char *one, ...)\ndiff --git a/attr.h b/attr.h\nindex 506db0c..c84f164 100644\n--- a/attr.h\n+++ b/attr.h\n@@ -38,6 +38,7 @@ struct git_attr_check {\n \n extern struct git_attr_check *git_attr_check_initl(const char *, ...);\n extern int git_check_attr(const char *path, struct git_attr_check *);\n+extern int git_check_attr_counted(const char *, int, struct git_attr_check *);\n \n extern struct git_attr_check *git_attr_check_alloc(void);\n extern struct git_attr_check_elem *git_attr_check_append(struct git_attr_check *, const struct git_attr *);\n-- \n2.10.1.508.g6572022\n\n"},{"id":"304705","messageId":"20161022233225.8883-23-sbeller@google.com","threadId":"44358","inReplyTo":"20161022233225.8883-1-sbeller@google.com","subject":"[PATCH 22/36] attr.c: correct ugly hack for git_all_attrs()","fromName":"Stefan Beller","fromEmail":"sbeller@google.com","sentAt":"2016-10-22T23:32:11Z","receivedAt":"2016-10-22T23:33:32Z","isPatch":true,"sender":{"key":"stefanbeller@gmail.com","avatar":"https://avatars.githubusercontent.com/u/455868?v=4"},"body":"From: Junio C Hamano <gitster@pobox.com>\n\nThe collect_some_attrs() function has an ugly hack since\n\n06a604e6 (attr: avoid heavy work when we know the specified attr is\nnot defined, 2014-12-28) added an optimization that relies on the\nfact that the caller knows what attributes it is interested in, so\nthat we can leave once we know the final answer for all the\nattributes the caller asked.\n\ngit_all_attrs() that asks \"what attributes are on this path?\"\nhowever does not know what attributes it is interested in, other\nthan the vague \"we are interested in all of them\", which is not a\nvery useful thing to say.  As a way to disable this optimization\nfor this caller, the said commit added a code to skip it when\nthe caller passes a NULL for the check structure.\n\nHowever, it skipped the optimization not when check is NULL, but\nwhen the number of attributes being checked is 0, which is\nunnecessarily pessimistic.\n\nSigned-off-by: Junio C Hamano <gitster@pobox.com>\nSigned-off-by: Stefan Beller <sbeller@google.com>\n---\n attr.c | 24 ++++++++----------------\n 1 file changed, 8 insertions(+), 16 deletions(-)\n\ndiff --git a/attr.c b/attr.c\nindex 7869277..2d13441 100644\n--- a/attr.c\n+++ b/attr.c\n@@ -748,8 +748,8 @@ static int attr_check_is_dynamic(const struct git_attr_check *check)\n \n /*\n  * Collect attributes for path into the array pointed to by\n- * check_all_attr. If num is non-zero, only attributes in check[] are\n- * collected. Otherwise all attributes are collected.\n+ * check_all_attr.  If check is not NULL, only attributes in\n+ * check[] are collected. Otherwise all attributes are collected.\n  */\n static void collect_some_attrs(const char *path, int pathlen,\n \t\t\t       struct git_attr_check *check)\n@@ -759,17 +759,6 @@ static void collect_some_attrs(const char *path, int pathlen,\n \tint i, rem, dirlen;\n \tconst char *cp, *last_slash = NULL;\n \tint basename_offset;\n-\tint num;\n-\tstruct git_attr_check_elem *celem;\n-\n-\tif (!check) {\n-\t\t/* Yuck - ugly git_all_attrs() hack! */\n-\t\tcelem = NULL;\n-\t\tnum = 0;\n-\t} else {\n-\t\tcelem = check->check;\n-\t\tnum = check->check_nr;\n-\t}\n \n \tfor (cp = path; cp < path + pathlen; cp++) {\n \t\tif (*cp == '/' && cp[1])\n@@ -786,9 +775,12 @@ static void collect_some_attrs(const char *path, int pathlen,\n \tprepare_attr_stack(path, dirlen);\n \tfor (i = 0; i < attr_nr; i++)\n \t\tcheck_all_attr[i].value = ATTR__UNKNOWN;\n-\tif (num && !cannot_trust_maybe_real) {\n+\n+\tif (check && !cannot_trust_maybe_real) {\n+\t\tstruct git_attr_check_elem *celem = check->check;\n+\n \t\trem = 0;\n-\t\tfor (i = 0; i < num; i++) {\n+\t\tfor (i = 0; i < check->check_nr; i++) {\n \t\t\tif (!celem[i].attr->maybe_real) {\n \t\t\t\tstruct git_attr_check_elem *c;\n \t\t\t\tc = check_all_attr + celem[i].attr->attr_nr;\n@@ -796,7 +788,7 @@ static void collect_some_attrs(const char *path, int pathlen,\n \t\t\t\trem++;\n \t\t\t}\n \t\t}\n-\t\tif (rem == num)\n+\t\tif (rem == check->check_nr)\n \t\t\treturn;\n \t}\n \n-- \n2.10.1.508.g6572022\n\n"},{"id":"304706","messageId":"20161022233225.8883-24-sbeller@google.com","threadId":"44358","inReplyTo":"20161022233225.8883-1-sbeller@google.com","subject":"[PATCH 23/36] attr.c: introduce empty_attr_check_elems()","fromName":"Stefan Beller","fromEmail":"sbeller@google.com","sentAt":"2016-10-22T23:32:12Z","receivedAt":"2016-10-22T23:33:33Z","isPatch":true,"sender":{"key":"stefanbeller@gmail.com","avatar":"https://avatars.githubusercontent.com/u/455868?v=4"},"body":"From: Junio C Hamano <gitster@pobox.com>\n\nOne codepath needs to just empty the git_attr_check_elem array in\nthe git_attr_check structure, without releasing the entire resource.\nIntroduce a helper to do so and rewrite git_attr_check_clear() using\nit.\n\nSigned-off-by: Junio C Hamano <gitster@pobox.com>\nSigned-off-by: Stefan Beller <sbeller@google.com>\n---\n attr.c | 11 +++++++++--\n 1 file changed, 9 insertions(+), 2 deletions(-)\n\ndiff --git a/attr.c b/attr.c\nindex 2d13441..9f58cc0 100644\n--- a/attr.c\n+++ b/attr.c\n@@ -746,6 +746,14 @@ static int attr_check_is_dynamic(const struct git_attr_check *check)\n \treturn (void *)(check->check) != (void *)(check + 1);\n }\n \n+static void empty_attr_check_elems(struct git_attr_check *check)\n+{\n+\tif (!attr_check_is_dynamic(check))\n+\t\tdie(\"BUG: emptying a statically initialized git_attr_check\");\n+\tcheck->check_nr = 0;\n+\tcheck->finalized = 0;\n+}\n+\n /*\n  * Collect attributes for path into the array pointed to by\n  * check_all_attr.  If check is not NULL, only attributes in\n@@ -912,12 +920,11 @@ struct git_attr_check_elem *git_attr_check_append(struct git_attr_check *check,\n \n void git_attr_check_clear(struct git_attr_check *check)\n {\n+\tempty_attr_check_elems(check);\n \tif (!attr_check_is_dynamic(check))\n \t\tdie(\"BUG: clearing a statically initialized git_attr_check\");\n \tfree(check->check);\n-\tcheck->check_nr = 0;\n \tcheck->check_alloc = 0;\n-\tcheck->finalized = 0;\n }\n \n void git_attr_check_free(struct git_attr_check *check)\n-- \n2.10.1.508.g6572022\n\n"},{"id":"304707","messageId":"20161022233225.8883-21-sbeller@google.com","threadId":"44358","inReplyTo":"20161022233225.8883-1-sbeller@google.com","subject":"[PATCH 20/36] attr.c: pass struct git_attr_check down the callchain","fromName":"Stefan Beller","fromEmail":"sbeller@google.com","sentAt":"2016-10-22T23:32:09Z","receivedAt":"2016-10-22T23:33:37Z","isPatch":true,"sender":{"key":"stefanbeller@gmail.com","avatar":"https://avatars.githubusercontent.com/u/455868?v=4"},"body":"From: Junio C Hamano <gitster@pobox.com>\n\nThe callchain that starts from git_check_attrs() down to\ncollect_some_attrs() used to take an array of git_attr_check_elem\nas their parameters.  Pass the enclosing git_attr_check instance\ninstead, so that they will have access to new fields we will add to\nthe data structure.\n\nSigned-off-by: Junio C Hamano <gitster@pobox.com>\nSigned-off-by: Stefan Beller <sbeller@google.com>\n---\n attr.c | 36 ++++++++++++++++++++++++------------\n 1 file changed, 24 insertions(+), 12 deletions(-)\n\ndiff --git a/attr.c b/attr.c\nindex 34c297d..9ed4825 100644\n--- a/attr.c\n+++ b/attr.c\n@@ -751,14 +751,25 @@ static int attr_check_is_dynamic(const struct git_attr_check *check)\n  * check_all_attr. If num is non-zero, only attributes in check[] are\n  * collected. Otherwise all attributes are collected.\n  */\n-static void collect_some_attrs(const char *path, int pathlen, int num,\n-\t\t\t       struct git_attr_check_elem *check)\n+static void collect_some_attrs(const char *path, int pathlen,\n+\t\t\t       struct git_attr_check *check)\n \n {\n \tstruct attr_stack *stk;\n \tint i, rem, dirlen;\n \tconst char *cp, *last_slash = NULL;\n \tint basename_offset;\n+\tint num;\n+\tstruct git_attr_check_elem *celem;\n+\n+\tif (!check) {\n+\t\t/* Yuck - ugly git_all_attrs() hack! */\n+\t\tcelem = NULL;\n+\t\tnum = 0;\n+\t} else {\n+\t\tcelem = check->check;\n+\t\tnum = check->check_nr;\n+\t}\n \n \tfor (cp = path; cp < path + pathlen; cp++) {\n \t\tif (*cp == '/' && cp[1])\n@@ -778,9 +789,9 @@ static void collect_some_attrs(const char *path, int pathlen, int num,\n \tif (num && !cannot_trust_maybe_real) {\n \t\trem = 0;\n \t\tfor (i = 0; i < num; i++) {\n-\t\t\tif (!check[i].attr->maybe_real) {\n+\t\t\tif (!celem[i].attr->maybe_real) {\n \t\t\t\tstruct git_attr_check_elem *c;\n-\t\t\t\tc = check_all_attr + check[i].attr->attr_nr;\n+\t\t\t\tc = check_all_attr + celem[i].attr->attr_nr;\n \t\t\t\tc->value = ATTR__UNSET;\n \t\t\t\trem++;\n \t\t\t}\n@@ -794,18 +805,19 @@ static void collect_some_attrs(const char *path, int pathlen, int num,\n \t\trem = fill(path, pathlen, basename_offset, stk, rem);\n }\n \n-static int git_check_attrs(const char *path, int pathlen, int num,\n-\t\t\t   struct git_attr_check_elem *check)\n+static int git_check_attrs(const char *path, int pathlen,\n+\t\t\t   struct git_attr_check *check)\n {\n \tint i;\n+\tstruct git_attr_check_elem *celem = check->check;\n \n-\tcollect_some_attrs(path, pathlen, num, check);\n+\tcollect_some_attrs(path, pathlen, check);\n \n-\tfor (i = 0; i < num; i++) {\n-\t\tconst char *value = check_all_attr[check[i].attr->attr_nr].value;\n+\tfor (i = 0; i < check->check_nr; i++) {\n+\t\tconst char *value = check_all_attr[celem[i].attr->attr_nr].value;\n \t\tif (value == ATTR__UNKNOWN)\n \t\t\tvalue = ATTR__UNSET;\n-\t\tcheck[i].value = value;\n+\t\tcelem[i].value = value;\n \t}\n \n \treturn 0;\n@@ -816,7 +828,7 @@ void git_all_attrs(const char *path, struct git_attr_check *check)\n \tint i;\n \n \tgit_attr_check_clear(check);\n-\tcollect_some_attrs(path, strlen(path), 0, NULL);\n+\tcollect_some_attrs(path, strlen(path), NULL);\n \n \tfor (i = 0; i < attr_nr; i++) {\n \t\tconst char *name = check_all_attr[i].attr->name;\n@@ -845,7 +857,7 @@ int git_check_attr_counted(const char *path, int pathlen,\n \t\t\t   struct git_attr_check *check)\n {\n \tcheck->finalized = 1;\n-\treturn git_check_attrs(path, pathlen, check->check_nr, check->check);\n+\treturn git_check_attrs(path, pathlen, check);\n }\n \n int git_check_attr(const char *path, struct git_attr_check *check)\n-- \n2.10.1.508.g6572022\n\n"},{"id":"304708","messageId":"20161022233225.8883-28-sbeller@google.com","threadId":"44358","inReplyTo":"20161022233225.8883-1-sbeller@google.com","subject":"[PATCH 27/36] attr: convert to new threadsafe API","fromName":"Stefan Beller","fromEmail":"sbeller@google.com","sentAt":"2016-10-22T23:32:16Z","receivedAt":"2016-10-22T23:33:40Z","isPatch":true,"sender":{"key":"stefanbeller@gmail.com","avatar":"https://avatars.githubusercontent.com/u/455868?v=4"},"body":"This revamps the API of the attr subsystem to be thread safe.\nBefore we had the question and its results in one struct type.\nThe typical usage of the API was\n\n    static struct git_attr_check *check;\n\n    if (!check)\n        check = git_attr_check_initl(\"text\", NULL);\n\n    git_check_attr(path, check);\n    act_on(check->value[0]);\n\nThis has a couple of issues when it comes to thread safety:\n\n* the initialization is racy in this implementation. To make it\n  thread safe, we need to acquire a mutex, such that only one\n  thread is executing the code in git_attr_check_initl.\n  As we do not want to introduce a mutex at each call site,\n  this is best done in the attr code. However to do so, we need\n  to have access to the `check` variable, i.e. the API has to\n  look like\n    git_attr_check_initl(struct git_attr_check*, ...);\n  Then one of the threads calling git_attr_check_initl will\n  acquire the mutex and init the `check`, while all other threads\n  will wait on the mutex just to realize they're late to the\n  party and they'll return with no work done.\n\n* While the check for attributes to be questioned only need to\n  be initalized once as that part will be read only after its\n  initialisation, the answer may be different for each path.\n  Because of that we need to decouple the check and the answer,\n  such that each thread can obtain an answer for the path it\n  is currently processing.\n\nThis commit changes the API and adds locking in\ngit_attr_check_initl that provides the thread safety for constructing\n`struct git_attr_check`.\n\nThe usage of the new API will be:\n\n    /*\n     * The initl call will thread-safely check whether the\n     * struct git_attr_check has been initialized. We only\n     * want to do the initialization work once, hence we do\n     * that work inside a thread safe environment.\n     */\n    static struct git_attr_check *check;\n    git_attr_check_initl(&check, \"text\", NULL);\n\n    /*\n     * Obtain a pointer to a correctly sized result\n     * statically allocated on the stack; this macro:\n     */\n    GIT_ATTR_RESULT_INIT_FOR(myresult, 1);\n\n    /* Perform the check and act on it: */\n    git_check_attr(path, check, myresult);\n    act_on(myresult->value[0]);\n\n    /*\n     * No need to free the check as it is static, hence doesn't leak\n     * memory. The result is also static, so no need to free there either.\n     */\n\nSigned-off-by: Stefan Beller <sbeller@google.com>\n---\n Documentation/technical/api-gitattributes.txt |  91 +++++++++-------\n archive.c                                     |  14 +--\n attr.c                                        | 143 ++++++++++++++++++--------\n attr.h                                        |  71 ++++++++-----\n builtin/check-attr.c                          |  35 ++++---\n builtin/pack-objects.c                        |  16 +--\n convert.c                                     |  39 +++----\n ll-merge.c                                    |  25 +++--\n userdiff.c                                    |  16 +--\n ws.c                                          |  17 ++-\n 10 files changed, 279 insertions(+), 188 deletions(-)\n\ndiff --git a/Documentation/technical/api-gitattributes.txt b/Documentation/technical/api-gitattributes.txt\nindex 92fc32a..f3fc7bd 100644\n--- a/Documentation/technical/api-gitattributes.txt\n+++ b/Documentation/technical/api-gitattributes.txt\n@@ -16,15 +16,17 @@ Data Structure\n \tof no interest to the calling programs.  The name of the\n \tattribute can be retrieved by calling `git_attr_name()`.\n \n-`struct git_attr_check_elem`::\n-\n-\tThis structure represents one attribute and its value.\n-\n `struct git_attr_check`::\n \n-\tThis structure represents a collection of `git_attr_check_elem`.\n+\tThis structure represents a collection of `struct git_attrs`.\n \tIt is passed to `git_check_attr()` function, specifying the\n-\tattributes to check, and receives their values.\n+\tattributes to check, and receives their values into a corresponding\n+\t`struct git_attr_result`.\n+\n+`struct git_attr_result`::\n+\n+\tThis structure represents a collection of results to its\n+\tcorresponding `struct git_attr_check`, that has the same order.\n \n \n Attribute Values\n@@ -32,7 +34,7 @@ Attribute Values\n \n An attribute for a path can be in one of four states: Set, Unset,\n Unspecified or set to a string, and `.value` member of `struct\n-git_attr_check` records it.  There are three macros to check these:\n+git_attr_result` records it.  There are three macros to check these:\n \n `ATTR_TRUE()`::\n \n@@ -53,19 +55,31 @@ value of the attribute for the path.\n Querying Specific Attributes\n ----------------------------\n \n-* Prepare `struct git_attr_check` using git_attr_check_initl()\n+* Prepare a `struct git_attr_check` using `git_attr_check_initl()`\n   function, enumerating the names of attributes whose values you are\n   interested in, terminated with a NULL pointer.  Alternatively, an\n-  empty `struct git_attr_check` can be prepared by calling\n-  `git_attr_check_alloc()` function and then attributes you want to\n-  ask about can be added to it with `git_attr_check_append()`\n-  function.\n-\n-* Call `git_check_attr()` to check the attributes for the path.\n-\n-* Inspect `git_attr_check` structure to see how each of the\n-  attribute in the array is defined for the path.\n-\n+  empty `struct git_attr_check` as allocated by git_attr_check_alloc()\n+  can be prepared by calling `git_attr_check_alloc()` function and\n+  then attributes you want to ask about can be added to it with\n+  `git_attr_check_append()` function.\n+  `git_attr_check_initl()` is thread safe, i.e. you can call it\n+  from different threads at the same time; when check determines\n+  the initialzisation is still needed, the threads will use a\n+  single global mutex to perform the initialization just once, the\n+  others will wait on the the thread to actually perform the\n+  initialization.\n+\n+* Prepare a `struct git_attr_result` using `GIT_ATTR_RESULT_INIT_FOR()`\n+  for the result for static allocations. When the result size is not known\n+  at compile time, use `git_attr_result_alloc`. The call to initialize\n+  the result is not thread safe, because different threads need their\n+  own thread local result anyway.\n+\n+* Call `git_check_attr()` to check the attributes for the path,\n+  the given `git_attr_result` will be filled with the result.\n+\n+* Inspect the returned `git_attr_result` structure to see how\n+  each of the attribute in the array is defined for the path.\n \n Example\n -------\n@@ -76,28 +90,23 @@ To see how attributes \"crlf\" and \"ident\" are set for different paths.\n   we are checking two attributes):\n \n ------------\n-static struct git_attr_check *check;\n-static void setup_check(void)\n-{\n-\tif (check)\n-\t\treturn; /* already done */\n-\tcheck = git_attr_check_initl(\"crlf\", \"ident\", NULL);\n-}\n+\tstatic struct git_attr_check *check;\n+\tgit_attr_check_initl(check, \"crlf\", \"ident\", NULL);\n ------------\n \n . Call `git_check_attr()` with the prepared `struct git_attr_check`:\n \n ------------\n \tconst char *path;\n+\tGIT_ATTR_RESULT_INIT_FOR(result, 2);\n \n-\tsetup_check();\n-\tgit_check_attr(path, check);\n+\tgit_check_attr(path, check, result);\n ------------\n \n-. Act on `.value` member of the result, left in `check->check[]`:\n+. Act on `result->value[]`:\n \n ------------\n-\tconst char *value = check->check[0].value;\n+\tconst char *value = result->value[0];\n \n \tif (ATTR_TRUE(value)) {\n \t\tThe attribute is Set, by listing only the name of the\n@@ -123,12 +132,15 @@ the first step in the above would be different.\n static struct git_attr_check *check;\n static void setup_check(const char **argv)\n {\n+\tif (check)\n+\t\treturn; /* already done */\n \tcheck = git_attr_check_alloc();\n \twhile (*argv) {\n \t\tstruct git_attr *attr = git_attr(*argv);\n \t\tgit_attr_check_append(check, attr);\n \t\targv++;\n \t}\n+\tstruct git_attr_result *result = git_attr_result_alloc(check);\n }\n ------------\n \n@@ -138,17 +150,20 @@ Querying All Attributes\n \n To get the values of all attributes associated with a file:\n \n-* Prepare an empty `git_attr_check` structure by calling\n-  `git_attr_check_alloc()`.\n+* Setup a local variables for the question\n+  `struct git_attr_check` as well as a pointer where the result\n+  `struct git_attr_result` will be stored.\n \n-* Call `git_all_attrs()`, which populates the `git_attr_check`\n-  with the attributes attached to the path.\n+* Call `git_all_attrs()`.\n \n-* Iterate over the `git_attr_check.check[]` array to examine\n-  the attribute names and values.  The name of the attribute\n-  described by a  `git_attr_check.check[]` object can be retrieved via\n-  `git_attr_name(check->check[i].attr)`.  (Please note that no items\n+* Iterate over the `git_attr_check.attr[]` array to examine the\n+  attribute names.  The name of the attribute described by a\n+  `git_attr_check.attr[]` object can be retrieved via\n+  `git_attr_name(check->attr[i])`.  (Please note that no items\n   will be returned for unset attributes, so `ATTR_UNSET()` will return\n   false for all returned `git_array_check` objects.)\n+  The respective value for an attribute can be found in the same\n+  index position in of `git_attr_result`.\n \n-* Free the `git_array_check` by calling `git_attr_check_free()`.\n+* Clear the variables by calling `git_attr_check_clear()` and\n+  `git_attr_result_free()`.\ndiff --git a/archive.c b/archive.c\nindex 11e3951..e027b3c 100644\n--- a/archive.c\n+++ b/archive.c\n@@ -107,12 +107,14 @@ static int write_archive_entry(const unsigned char *sha1, const char *base,\n \t\tvoid *context)\n {\n \tstatic struct strbuf path = STRBUF_INIT;\n+\tstatic struct git_attr_check *check;\n+\n \tstruct archiver_context *c = context;\n \tstruct archiver_args *args = c->args;\n \twrite_archive_entry_fn_t write_entry = c->write_entry;\n-\tstatic struct git_attr_check *check;\n \tconst char *path_without_prefix;\n \tint err;\n+\tstruct git_attr_result result[2];\n \n \targs->convert = 0;\n \tstrbuf_reset(&path);\n@@ -124,12 +126,12 @@ static int write_archive_entry(const unsigned char *sha1, const char *base,\n \t\tstrbuf_addch(&path, '/');\n \tpath_without_prefix = path.buf + args->baselen;\n \n-\tif (!check)\n-\t\tcheck = git_attr_check_initl(\"export-ignore\", \"export-subst\", NULL);\n-\tif (!git_check_attr(path_without_prefix, check)) {\n-\t\tif (ATTR_TRUE(check->check[0].value))\n+\tgit_attr_check_initl(&check, \"export-ignore\", \"export-subst\", NULL);\n+\n+\tif (!git_check_attr(path_without_prefix, check, result)) {\n+\t\tif (ATTR_TRUE(result[0].value))\n \t\t\treturn 0;\n-\t\targs->convert = ATTR_TRUE(check->check[1].value);\n+\t\targs->convert = ATTR_TRUE(result[1].value);\n \t}\n \n \tif (S_ISDIR(mode) || S_ISGITLINK(mode)) {\ndiff --git a/attr.c b/attr.c\nindex 881bdfa..89ae155 100644\n--- a/attr.c\n+++ b/attr.c\n@@ -14,6 +14,7 @@\n #include \"dir.h\"\n #include \"utf8.h\"\n #include \"quote.h\"\n+#include \"thread-utils.h\"\n \n const char git_attr__true[] = \"(builtin)true\";\n const char git_attr__false[] = \"\\0(builtin)false\";\n@@ -46,6 +47,19 @@ struct git_attr {\n static int attr_nr;\n static struct git_attr *(git_attr_hash[HASHSIZE]);\n \n+#ifndef NO_PTHREADS\n+\n+static pthread_mutex_t attr_mutex;\n+#define attr_lock()\t\tpthread_mutex_lock(&attr_mutex)\n+#define attr_unlock()\t\tpthread_mutex_unlock(&attr_mutex)\n+\n+#else\n+\n+#define attr_lock()\t\t(void)0\n+#define attr_unlock()\t\t(void)0\n+\n+#endif /* NO_PTHREADS */\n+\n /*\n  * NEEDSWORK: maybe-real, maybe-macro are not property of\n  * an attribute, as it depends on what .gitattributes are\n@@ -55,6 +69,16 @@ static struct git_attr *(git_attr_hash[HASHSIZE]);\n  */\n static int cannot_trust_maybe_real;\n \n+/*\n+ * Send one or more git_attr_check to git_check_attrs(), and\n+ * each 'value' member tells what its value is.\n+ * Unset one is returned as NULL.\n+ */\n+struct git_attr_check_elem {\n+\tconst struct git_attr *attr;\n+\tconst char *value;\n+};\n+\n /* NEEDSWORK: This will become per git_attr_check */\n static struct git_attr_check_elem *check_all_attr;\n \n@@ -781,7 +805,7 @@ static int macroexpand_one(int nr, int rem)\n \n static int attr_check_is_dynamic(const struct git_attr_check *check)\n {\n-\treturn (void *)(check->check) != (void *)(check + 1);\n+\treturn (void *)(check->attr) != (void *)(check + 1);\n }\n \n static void empty_attr_check_elems(struct git_attr_check *check)\n@@ -799,7 +823,9 @@ static void empty_attr_check_elems(struct git_attr_check *check)\n  * any and all attributes that are visible are collected in it.\n  */\n static void collect_some_attrs(const char *path, int pathlen,\n-\t\t\t       struct git_attr_check *check, int collect_all)\n+\t\t\t       struct git_attr_check *check,\n+\t\t\t       struct git_attr_result **result,\n+\t\t\t       int collect_all)\n \n {\n \tstruct attr_stack *stk;\n@@ -825,13 +851,11 @@ static void collect_some_attrs(const char *path, int pathlen,\n \t\tcheck_all_attr[i].value = ATTR__UNKNOWN;\n \n \tif (!collect_all && !cannot_trust_maybe_real) {\n-\t\tstruct git_attr_check_elem *celem = check->check;\n-\n \t\trem = 0;\n \t\tfor (i = 0; i < check->check_nr; i++) {\n-\t\t\tif (!celem[i].attr->maybe_real) {\n+\t\t\tif (!check->attr[i]->maybe_real) {\n \t\t\t\tstruct git_attr_check_elem *c;\n-\t\t\t\tc = check_all_attr + celem[i].attr->attr_nr;\n+\t\t\t\tc = check_all_attr + check->attr[i]->attr_nr;\n \t\t\t\tc->value = ATTR__UNSET;\n \t\t\t\trem++;\n \t\t\t}\n@@ -845,38 +869,52 @@ static void collect_some_attrs(const char *path, int pathlen,\n \t\trem = fill(path, pathlen, basename_offset, stk, rem);\n \n \tif (collect_all) {\n-\t\tempty_attr_check_elems(check);\n+\t\tint check_nr = 0, check_alloc = 0;\n+\t\tconst char **res = NULL;\n+\n \t\tfor (i = 0; i < attr_nr; i++) {\n \t\t\tconst struct git_attr *attr = check_all_attr[i].attr;\n \t\t\tconst char *value = check_all_attr[i].value;\n \t\t\tif (value == ATTR__UNSET || value == ATTR__UNKNOWN)\n \t\t\t\tcontinue;\n-\t\t\tgit_attr_check_append(check, attr)->value = value;\n+\n+\t\t\tgit_attr_check_append(check, attr);\n+\n+\t\t\tALLOC_GROW(res, check_nr + 1, check_alloc);\n+\t\t\tres[check_nr++] = value;\n \t\t}\n+\n+\t\t*result = git_attr_result_alloc(check);\n+\t\tfor (i = 0; i < check->check_nr; i++)\n+\t\t\t(*result)[i].value = res[i];\n+\n+\t\tfree(res);\n \t}\n }\n \n static int git_check_attrs(const char *path, int pathlen,\n-\t\t\t   struct git_attr_check *check)\n+\t\t\t   struct git_attr_check *check,\n+\t\t\t   struct git_attr_result *result)\n {\n \tint i;\n-\tstruct git_attr_check_elem *celem = check->check;\n \n-\tcollect_some_attrs(path, pathlen, check, 0);\n+\tcollect_some_attrs(path, pathlen, check, &result, 0);\n \n \tfor (i = 0; i < check->check_nr; i++) {\n-\t\tconst char *value = check_all_attr[celem[i].attr->attr_nr].value;\n+\t\tconst char *value = check_all_attr[check->attr[i]->attr_nr].value;\n \t\tif (value == ATTR__UNKNOWN)\n \t\t\tvalue = ATTR__UNSET;\n-\t\tcelem[i].value = value;\n+\t\tresult[i].value = value;\n \t}\n \n \treturn 0;\n }\n \n-void git_all_attrs(const char *path, struct git_attr_check *check)\n+void git_all_attrs(const char *path,\n+\t\t   struct git_attr_check *check,\n+\t\t   struct git_attr_result **result)\n {\n-\tcollect_some_attrs(path, strlen(path), check, 1);\n+\tcollect_some_attrs(path, strlen(path), check, result, 1);\n }\n \n void git_attr_set_direction(enum git_attr_direction new, struct index_state *istate)\n@@ -892,36 +930,40 @@ void git_attr_set_direction(enum git_attr_direction new, struct index_state *ist\n \tuse_index = istate;\n }\n \n-static int git_check_attr_counted(const char *path, int pathlen,\n-\t\t\t\t  struct git_attr_check *check)\n+int git_check_attr(const char *path,\n+\t\t    struct git_attr_check *check,\n+\t\t    struct git_attr_result *result)\n {\n \tcheck->finalized = 1;\n-\treturn git_check_attrs(path, pathlen, check);\n+\treturn git_check_attrs(path, strlen(path), check, result);\n }\n \n-int git_check_attr(const char *path, struct git_attr_check *check)\n+void git_attr_check_initl(struct git_attr_check **check_,\n+\t\t\t  const char *one, ...)\n {\n-\treturn git_check_attr_counted(path, strlen(path), check);\n-}\n-\n-struct git_attr_check *git_attr_check_initl(const char *one, ...)\n-{\n-\tstruct git_attr_check *check;\n \tint cnt;\n \tva_list params;\n \tconst char *param;\n+\tstruct git_attr_check *check;\n+\n+\tattr_lock();\n+\tif (*check_) {\n+\t\tattr_unlock();\n+\t\treturn;\n+\t}\n \n \tva_start(params, one);\n \tfor (cnt = 1; (param = va_arg(params, const char *)) != NULL; cnt++)\n \t\t;\n \tva_end(params);\n+\n \tcheck = xcalloc(1,\n-\t\t\tsizeof(*check) + cnt * sizeof(*(check->check)));\n+\t\t\tsizeof(*check) + cnt * sizeof(*(check->attr)));\n \tcheck->check_nr = cnt;\n \tcheck->finalized = 1;\n-\tcheck->check = (struct git_attr_check_elem *)(check + 1);\n+\tcheck->attr = (const struct git_attr **)(check + 1);\n \n-\tcheck->check[0].attr = git_attr(one);\n+\tcheck->attr[0] = git_attr(one);\n \tva_start(params, one);\n \tfor (cnt = 1; cnt < check->check_nr; cnt++) {\n \t\tstruct git_attr *attr;\n@@ -932,29 +974,44 @@ struct git_attr_check *git_attr_check_initl(const char *one, ...)\n \t\tattr = git_attr(param);\n \t\tif (!attr)\n \t\t\tdie(\"BUG: %s: not a valid attribute name\", param);\n-\t\tcheck->check[cnt].attr = attr;\n+\t\tcheck->attr[cnt] = attr;\n \t}\n \tva_end(params);\n-\treturn check;\n+\t*check_ = check;\n+\tattr_unlock();\n+}\n+\n+void git_attr_check_alloc(struct git_attr_check **check)\n+{\n+\tattr_lock();\n+\tif (!*check)\n+\t\t*check = xcalloc(1, sizeof(struct git_attr_check));\n+\n+\tattr_unlock();\n }\n \n-struct git_attr_check *git_attr_check_alloc(void)\n+struct git_attr_result *git_attr_result_alloc(struct git_attr_check *check)\n {\n-\treturn xcalloc(1, sizeof(struct git_attr_check));\n+\treturn xcalloc(1, sizeof(struct git_attr_result) * check->check_nr);\n }\n \n-struct git_attr_check_elem *git_attr_check_append(struct git_attr_check *check,\n-\t\t\t\t\t\t  const struct git_attr *attr)\n+void git_attr_check_append(struct git_attr_check *check,\n+\t\t\t   const struct git_attr *attr)\n {\n-\tstruct git_attr_check_elem *elem;\n+\tint i;\n \tif (check->finalized)\n \t\tdie(\"BUG: append after git_attr_check structure is finalized\");\n \tif (!attr_check_is_dynamic(check))\n \t\tdie(\"BUG: appending to a statically initialized git_attr_check\");\n-\tALLOC_GROW(check->check, check->check_nr + 1, check->check_alloc);\n-\telem = &check->check[check->check_nr++];\n-\telem->attr = attr;\n-\treturn elem;\n+\tattr_lock();\n+\tfor (i = 0; i < check->check_nr; i++)\n+\t\tif (check->attr[i] == attr)\n+\t\t\tbreak;\n+\tif (i == check->check_nr) {\n+\t\tALLOC_GROW(check->attr, check->check_nr + 1, check->check_alloc);\n+\t\tcheck->attr[check->check_nr++] = attr;\n+\t}\n+\tattr_unlock();\n }\n \n void git_attr_check_clear(struct git_attr_check *check)\n@@ -962,12 +1019,12 @@ void git_attr_check_clear(struct git_attr_check *check)\n \tempty_attr_check_elems(check);\n \tif (!attr_check_is_dynamic(check))\n \t\tdie(\"BUG: clearing a statically initialized git_attr_check\");\n-\tfree(check->check);\n+\tfree(check->attr);\n \tcheck->check_alloc = 0;\n }\n \n-void git_attr_check_free(struct git_attr_check *check)\n+void git_attr_result_free(struct git_attr_result *result)\n {\n-\tgit_attr_check_clear(check);\n-\tfree(check);\n+\t/* No need to free values as they are interned. */\n+\tfree(result);\n }\ndiff --git a/attr.h b/attr.h\nindex 06ac93b..219b8c7 100644\n--- a/attr.h\n+++ b/attr.h\n@@ -9,10 +9,16 @@ struct git_attr;\n  * corresponds to it.\n  */\n extern struct git_attr *git_attr(const char *);\n-\n /* The same, but with counted string */\n extern struct git_attr *git_attr_counted(const char *, size_t);\n \n+/*\n+ * Return the name of the attribute represented by the argument.  The\n+ * return value is a pointer to a null-delimited string that is part\n+ * of the internal data structure; it should not be modified or freed.\n+ */\n+extern const char *git_attr_name(const struct git_attr *);\n+\n extern int attr_name_valid(const char *name, size_t namelen);\n extern void invalid_attr_name_message(struct strbuf *, const char *, int);\n \n@@ -25,44 +31,53 @@ extern const char git_attr__false[];\n #define ATTR_FALSE(v) ((v) == git_attr__false)\n #define ATTR_UNSET(v) ((v) == NULL)\n \n-/*\n- * Send one or more git_attr_check to git_check_attrs(), and\n- * each 'value' member tells what its value is.\n- * Unset one is returned as NULL.\n- */\n-struct git_attr_check_elem {\n-\tconst struct git_attr *attr;\n-\tconst char *value;\n-};\n-\n struct git_attr_check {\n \tint finalized;\n \tint check_nr;\n \tint check_alloc;\n-\tstruct git_attr_check_elem *check;\n+\tconst struct git_attr **attr;\n+};\n+#define GIT_ATTR_CHECK_INIT {0, 0, 0, NULL}\n+\n+struct git_attr_result {\n+\tconst char *value;\n };\n \n-extern struct git_attr_check *git_attr_check_initl(const char *, ...);\n-extern int git_check_attr(const char *path, struct git_attr_check *);\n+/*\n+ * Initialize the `git_attr_check` via one of the following three functions:\n+ *\n+ * git_attr_check_alloc  allocates an empty check,\n+ * git_attr_check_append add an attribute to the given git_attr_check\n+ *\n+ * git_all_attrs         allocates a check and fills in all attributes that\n+ *                       are set for the given path.\n+ * git_attr_check_initl  takes a pointer to where the check will be initialized,\n+ *                       followed by all attributes that are to be checked.\n+ *                       This makes it potentially thread safe as it could\n+ *                       internally have a mutex for that memory location.\n+ *                       Currently it is not thread safe!\n+ */\n+extern void git_attr_check_alloc(struct git_attr_check **);\n+extern struct git_attr_result *git_attr_result_alloc(struct git_attr_check *check);\n \n-extern struct git_attr_check *git_attr_check_alloc(void);\n-extern struct git_attr_check_elem *git_attr_check_append(struct git_attr_check *, const struct git_attr *);\n+extern void git_attr_check_append(struct git_attr_check *,\n+\t\t\t\t  const struct git_attr *);\n+extern void git_attr_check_initl(struct git_attr_check **,\n+\t\t\t\t const char *, ...);\n+\n+extern void git_all_attrs(const char *path,\n+\t\t\t  struct git_attr_check *,\n+\t\t\t  struct git_attr_result **);\n+\n+/* Query a path for its attributes */\n+extern int git_check_attr(const char *path,\n+\t\t\t  struct git_attr_check *,\n+\t\t\t  struct git_attr_result *result);\n \n extern void git_attr_check_clear(struct git_attr_check *);\n-extern void git_attr_check_free(struct git_attr_check *);\n \n-/*\n- * Return the name of the attribute represented by the argument.  The\n- * return value is a pointer to a null-delimited string that is part\n- * of the internal data structure; it should not be modified or freed.\n- */\n-extern const char *git_attr_name(const struct git_attr *);\n+extern void git_attr_result_free(struct git_attr_result *);\n \n-/*\n- * Retrieve all attributes that apply to the specified path.\n- * check holds the attributes and their values.\n- */\n-void git_all_attrs(const char *path, struct git_attr_check *check);\n \n enum git_attr_direction {\n \tGIT_ATTR_CHECKIN,\ndiff --git a/builtin/check-attr.c b/builtin/check-attr.c\nindex ec61476..c7c6c22 100644\n--- a/builtin/check-attr.c\n+++ b/builtin/check-attr.c\n@@ -24,13 +24,14 @@ static const struct option check_attr_options[] = {\n \tOPT_END()\n };\n \n-static void output_attr(struct git_attr_check *check, const char *file)\n+static void output_attr(struct git_attr_check *check,\n+\t\t\tstruct git_attr_result *result, const char *file)\n {\n \tint j;\n \tint cnt = check->check_nr;\n \n \tfor (j = 0; j < cnt; j++) {\n-\t\tconst char *value = check->check[j].value;\n+\t\tconst char *value = result[j].value;\n \n \t\tif (ATTR_TRUE(value))\n \t\t\tvalue = \"set\";\n@@ -44,11 +45,11 @@ static void output_attr(struct git_attr_check *check, const char *file)\n \t\t\t       \"%s%c\" /* attrname */\n \t\t\t       \"%s%c\" /* attrvalue */,\n \t\t\t       file, 0,\n-\t\t\t       git_attr_name(check->check[j].attr), 0, value, 0);\n+\t\t\t       git_attr_name(check->attr[j]), 0, value, 0);\n \t\t} else {\n \t\t\tquote_c_style(file, NULL, stdout, 0);\n \t\t\tprintf(\": %s: %s\\n\",\n-\t\t\t       git_attr_name(check->check[j].attr), value);\n+\t\t\t       git_attr_name(check->attr[j]), value);\n \t\t}\n \t}\n }\n@@ -59,16 +60,20 @@ static void check_attr(const char *prefix,\n {\n \tchar *full_path =\n \t\tprefix_path(prefix, prefix ? strlen(prefix) : 0, file);\n+\tstruct git_attr_check local_check = GIT_ATTR_CHECK_INIT;\n+\tstruct git_attr_result *result = NULL;\n+\n \tif (check != NULL) {\n-\t\tif (git_check_attr(full_path, check))\n-\t\t\tdie(\"git_check_attr died\");\n-\t\toutput_attr(check, file);\n+\t\tresult = git_attr_result_alloc(check);\n+\t\tgit_check_attr(full_path, check, result);\n \t} else {\n-\t\tcheck = git_attr_check_alloc();\n-\t\tgit_all_attrs(full_path, check);\n-\t\toutput_attr(check, file);\n-\t\tgit_attr_check_free(check);\n+\t\tgit_all_attrs(full_path, &local_check, &result);\n+\t\tcheck = &local_check;\n \t}\n+\toutput_attr(check, result, file);\n+\tgit_attr_check_clear(&local_check);\n+\n+\tgit_attr_result_free(result);\n \tfree(full_path);\n }\n \n@@ -102,7 +107,7 @@ static NORETURN void error_with_usage(const char *msg)\n \n int cmd_check_attr(int argc, const char **argv, const char *prefix)\n {\n-\tstruct git_attr_check *check;\n+\tstruct git_attr_check *check = NULL;\n \tint cnt, i, doubledash, filei;\n \n \tif (!is_bare_repository())\n@@ -162,10 +167,8 @@ int cmd_check_attr(int argc, const char **argv, const char *prefix)\n \t\t\terror_with_usage(\"No file specified\");\n \t}\n \n-\tif (all_attrs) {\n-\t\tcheck = NULL;\n-\t} else {\n-\t\tcheck = git_attr_check_alloc();\n+\tif (!all_attrs) {\n+\t\tgit_attr_check_alloc(&check);\n \t\tfor (i = 0; i < cnt; i++) {\n \t\t\tstruct git_attr *a = git_attr(argv[i]);\n \t\t\tif (!a)\ndiff --git a/builtin/pack-objects.c b/builtin/pack-objects.c\nindex 3918c07..3751836 100644\n--- a/builtin/pack-objects.c\n+++ b/builtin/pack-objects.c\n@@ -899,14 +899,16 @@ static void write_pack_file(void)\n static int no_try_delta(const char *path)\n {\n \tstatic struct git_attr_check *check;\n+\tint ret = 0;\n+\tstruct git_attr_result result[1];\n \n-\tif (!check)\n-\t\tcheck = git_attr_check_initl(\"delta\", NULL);\n-\tif (git_check_attr(path, check))\n-\t\treturn 0;\n-\tif (ATTR_FALSE(check->check[0].value))\n-\t\treturn 1;\n-\treturn 0;\n+\tgit_attr_check_initl(&check, \"delta\", NULL);\n+\n+\tif (!git_check_attr(path, check, result)) {\n+\t\tif (ATTR_FALSE(result[0].value))\n+\t\t\tret = 1;\n+\t}\n+\treturn ret;\n }\n \n /*\ndiff --git a/convert.c b/convert.c\nindex bb2435a..0d55742 100644\n--- a/convert.c\n+++ b/convert.c\n@@ -718,10 +718,8 @@ static int ident_to_worktree(const char *path, const char *src, size_t len,\n \treturn 1;\n }\n \n-static enum crlf_action git_path_check_crlf(struct git_attr_check_elem *check)\n+static enum crlf_action git_path_check_crlf(const char *value)\n {\n-\tconst char *value = check->value;\n-\n \tif (ATTR_TRUE(value))\n \t\treturn CRLF_TEXT;\n \telse if (ATTR_FALSE(value))\n@@ -735,10 +733,8 @@ static enum crlf_action git_path_check_crlf(struct git_attr_check_elem *check)\n \treturn CRLF_UNDEFINED;\n }\n \n-static enum eol git_path_check_eol(struct git_attr_check_elem *check)\n+static enum eol git_path_check_eol(const char *value)\n {\n-\tconst char *value = check->value;\n-\n \tif (ATTR_UNSET(value))\n \t\t;\n \telse if (!strcmp(value, \"lf\"))\n@@ -748,9 +744,8 @@ static enum eol git_path_check_eol(struct git_attr_check_elem *check)\n \treturn EOL_UNSET;\n }\n \n-static struct convert_driver *git_path_check_convert(struct git_attr_check_elem *check)\n+static struct convert_driver *git_path_check_convert(const char *value)\n {\n-\tconst char *value = check->value;\n \tstruct convert_driver *drv;\n \n \tif (ATTR_TRUE(value) || ATTR_FALSE(value) || ATTR_UNSET(value))\n@@ -761,10 +756,8 @@ static struct convert_driver *git_path_check_convert(struct git_attr_check_elem\n \treturn NULL;\n }\n \n-static int git_path_check_ident(struct git_attr_check_elem *check)\n+static int git_path_check_ident(const char *value)\n {\n-\tconst char *value = check->value;\n-\n \treturn !!ATTR_TRUE(value);\n }\n \n@@ -778,25 +771,27 @@ struct conv_attrs {\n static void convert_attrs(struct conv_attrs *ca, const char *path)\n {\n \tstatic struct git_attr_check *check;\n+\tstatic int init_user_convert_tail;\n+\tstruct git_attr_result result[5];\n+\n+\tgit_attr_check_initl(&check, \"crlf\", \"ident\", \"filter\",\n+\t\t\t     \"eol\", \"text\", NULL);\n \n-\tif (!check) {\n-\t\tcheck = git_attr_check_initl(\"crlf\", \"ident\",\n-\t\t\t\t\t     \"filter\", \"eol\", \"text\",\n-\t\t\t\t\t     NULL);\n+\tif (!init_user_convert_tail) {\n \t\tuser_convert_tail = &user_convert;\n \t\tgit_config(read_convert_config, NULL);\n+\t\tinit_user_convert_tail = 1;\n \t}\n \n-\tif (!git_check_attr(path, check)) {\n-\t\tstruct git_attr_check_elem *ccheck = check->check;\n-\t\tca->crlf_action = git_path_check_crlf(ccheck + 4);\n+\tif (!git_check_attr(path, check, result)) {\n+\t\tca->crlf_action = git_path_check_crlf(result[4].value);\n \t\tif (ca->crlf_action == CRLF_UNDEFINED)\n-\t\t\tca->crlf_action = git_path_check_crlf(ccheck + 0);\n+\t\t\tca->crlf_action = git_path_check_crlf(result[0].value);\n \t\tca->attr_action = ca->crlf_action;\n-\t\tca->ident = git_path_check_ident(ccheck + 1);\n-\t\tca->drv = git_path_check_convert(ccheck + 2);\n+\t\tca->ident = git_path_check_ident(result[1].value);\n+\t\tca->drv = git_path_check_convert(result[2].value);\n \t\tif (ca->crlf_action != CRLF_BINARY) {\n-\t\t\tenum eol eol_attr = git_path_check_eol(ccheck + 3);\n+\t\t\tenum eol eol_attr = git_path_check_eol(result[3].value);\n \t\t\tif (ca->crlf_action == CRLF_AUTO && eol_attr == EOL_LF)\n \t\t\t\tca->crlf_action = CRLF_AUTO_INPUT;\n \t\t\telse if (ca->crlf_action == CRLF_AUTO && eol_attr == EOL_CRLF)\ndiff --git a/ll-merge.c b/ll-merge.c\nindex bc6479c..2c9b684 100644\n--- a/ll-merge.c\n+++ b/ll-merge.c\n@@ -353,12 +353,14 @@ int ll_merge(mmbuffer_t *result_buf,\n \t     mmfile_t *theirs, const char *their_label,\n \t     const struct ll_merge_options *opts)\n {\n-\tstatic struct git_attr_check *check;\n \tstatic const struct ll_merge_options default_opts;\n \tconst char *ll_driver_name = NULL;\n \tint marker_size = DEFAULT_CONFLICT_MARKER_SIZE;\n \tconst struct ll_merge_driver *driver;\n \n+\tstatic struct git_attr_check *check;\n+\tstruct git_attr_result result[2];\n+\n \tif (!opts)\n \t\topts = &default_opts;\n \n@@ -368,13 +370,12 @@ int ll_merge(mmbuffer_t *result_buf,\n \t\tnormalize_file(theirs, path);\n \t}\n \n-\tif (!check)\n-\t\tcheck = git_attr_check_initl(\"merge\", \"conflict-marker-size\", NULL);\n+\tgit_attr_check_initl(&check, \"merge\", \"conflict-marker-size\", NULL);\n \n-\tif (!git_check_attr(path, check)) {\n-\t\tll_driver_name = check->check[0].value;\n-\t\tif (check->check[1].value) {\n-\t\t\tmarker_size = atoi(check->check[1].value);\n+\tif (!git_check_attr(path, check, result)) {\n+\t\tll_driver_name = result[0].value;\n+\t\tif (result[1].value) {\n+\t\t\tmarker_size = atoi(result[1].value);\n \t\t\tif (marker_size <= 0)\n \t\t\t\tmarker_size = DEFAULT_CONFLICT_MARKER_SIZE;\n \t\t}\n@@ -395,13 +396,15 @@ int ll_merge_marker_size(const char *path)\n {\n \tstatic struct git_attr_check *check;\n \tint marker_size = DEFAULT_CONFLICT_MARKER_SIZE;\n+\tstruct git_attr_result result[1];\n \n-\tif (!check)\n-\t\tcheck = git_attr_check_initl(\"conflict-marker-size\", NULL);\n-\tif (!git_check_attr(path, check) && check->check[0].value) {\n-\t\tmarker_size = atoi(check->check[0].value);\n+\tgit_attr_check_initl(&check, \"conflict-marker-size\", NULL);\n+\n+\tif (!git_check_attr(path, check, result) && !ATTR_UNSET(result[0].value)) {\n+\t\tmarker_size = atoi(result[0].value);\n \t\tif (marker_size <= 0)\n \t\t\tmarker_size = DEFAULT_CONFLICT_MARKER_SIZE;\n \t}\n+\n \treturn marker_size;\n }\ndiff --git a/userdiff.c b/userdiff.c\nindex 46dfd32..1d6d363 100644\n--- a/userdiff.c\n+++ b/userdiff.c\n@@ -263,21 +263,23 @@ struct userdiff_driver *userdiff_find_by_name(const char *name) {\n struct userdiff_driver *userdiff_find_by_path(const char *path)\n {\n \tstatic struct git_attr_check *check;\n+\tstruct git_attr_result result[1];\n \n-\tif (!check)\n-\t\tcheck = git_attr_check_initl(\"diff\", NULL);\n \tif (!path)\n \t\treturn NULL;\n-\tif (git_check_attr(path, check))\n+\n+\tgit_attr_check_initl(&check, \"diff\", NULL);\n+\n+\tif (git_check_attr(path, check, result))\n \t\treturn NULL;\n \n-\tif (ATTR_TRUE(check->check[0].value))\n+\tif (ATTR_TRUE(result[0].value))\n \t\treturn &driver_true;\n-\tif (ATTR_FALSE(check->check[0].value))\n+\tif (ATTR_FALSE(result[0].value))\n \t\treturn &driver_false;\n-\tif (ATTR_UNSET(check->check[0].value))\n+\tif (ATTR_UNSET(result[0].value))\n \t\treturn NULL;\n-\treturn userdiff_find_by_name(check->check[0].value);\n+\treturn userdiff_find_by_name(result[0].value);\n }\n \n struct userdiff_driver *userdiff_get_textconv(struct userdiff_driver *driver)\ndiff --git a/ws.c b/ws.c\nindex bb3270c..59fb501 100644\n--- a/ws.c\n+++ b/ws.c\n@@ -74,15 +74,12 @@ unsigned parse_whitespace_rule(const char *string)\n unsigned whitespace_rule(const char *pathname)\n {\n \tstatic struct git_attr_check *attr_whitespace_rule;\n+\tstruct git_attr_result result[1];\n \n-\tif (!attr_whitespace_rule)\n-\t\tattr_whitespace_rule = git_attr_check_initl(\"whitespace\", NULL);\n+\tgit_attr_check_initl(&attr_whitespace_rule, \"whitespace\", NULL);\n \n-\tif (!git_check_attr(pathname, attr_whitespace_rule)) {\n-\t\tconst char *value;\n-\n-\t\tvalue = attr_whitespace_rule->check[0].value;\n-\t\tif (ATTR_TRUE(value)) {\n+\tif (!git_check_attr(pathname, attr_whitespace_rule, result)) {\n+\t\tif (ATTR_TRUE(result[0].value)) {\n \t\t\t/* true (whitespace) */\n \t\t\tunsigned all_rule = ws_tab_width(whitespace_rule_cfg);\n \t\t\tint i;\n@@ -91,15 +88,15 @@ unsigned whitespace_rule(const char *pathname)\n \t\t\t\t    !whitespace_rule_names[i].exclude_default)\n \t\t\t\t\tall_rule |= whitespace_rule_names[i].rule_bits;\n \t\t\treturn all_rule;\n-\t\t} else if (ATTR_FALSE(value)) {\n+\t\t} else if (ATTR_FALSE(result[0].value)) {\n \t\t\t/* false (-whitespace) */\n \t\t\treturn ws_tab_width(whitespace_rule_cfg);\n-\t\t} else if (ATTR_UNSET(value)) {\n+\t\t} else if (ATTR_UNSET(result[0].value)) {\n \t\t\t/* reset to default (!whitespace) */\n \t\t\treturn whitespace_rule_cfg;\n \t\t} else {\n \t\t\t/* string */\n-\t\t\treturn parse_whitespace_rule(value);\n+\t\t\treturn parse_whitespace_rule(result[0].value);\n \t\t}\n \t} else {\n \t\treturn whitespace_rule_cfg;\n-- \n2.10.1.508.g6572022\n\n"},{"id":"304709","messageId":"20161022233225.8883-27-sbeller@google.com","threadId":"44358","inReplyTo":"20161022233225.8883-1-sbeller@google.com","subject":"[PATCH 26/36] attr: make git_check_attr_counted static","fromName":"Stefan Beller","fromEmail":"sbeller@google.com","sentAt":"2016-10-22T23:32:15Z","receivedAt":"2016-10-22T23:33:41Z","isPatch":true,"sender":{"key":"stefanbeller@gmail.com","avatar":"https://avatars.githubusercontent.com/u/455868?v=4"},"body":"It's not used outside the attr code, so let's keep it private.\n\nSigned-off-by: Stefan Beller <sbeller@google.com>\n---\n attr.c | 4 ++--\n attr.h | 1 -\n 2 files changed, 2 insertions(+), 3 deletions(-)\n\ndiff --git a/attr.c b/attr.c\nindex 0f08ee6..881bdfa 100644\n--- a/attr.c\n+++ b/attr.c\n@@ -892,8 +892,8 @@ void git_attr_set_direction(enum git_attr_direction new, struct index_state *ist\n \tuse_index = istate;\n }\n \n-int git_check_attr_counted(const char *path, int pathlen,\n-\t\t\t   struct git_attr_check *check)\n+static int git_check_attr_counted(const char *path, int pathlen,\n+\t\t\t\t  struct git_attr_check *check)\n {\n \tcheck->finalized = 1;\n \treturn git_check_attrs(path, pathlen, check);\ndiff --git a/attr.h b/attr.h\nindex 40abc16..06ac93b 100644\n--- a/attr.h\n+++ b/attr.h\n@@ -44,7 +44,6 @@ struct git_attr_check {\n \n extern struct git_attr_check *git_attr_check_initl(const char *, ...);\n extern int git_check_attr(const char *path, struct git_attr_check *);\n-extern int git_check_attr_counted(const char *, int, struct git_attr_check *);\n \n extern struct git_attr_check *git_attr_check_alloc(void);\n extern struct git_attr_check_elem *git_attr_check_append(struct git_attr_check *, const struct git_attr *);\n-- \n2.10.1.508.g6572022\n\n"},{"id":"304710","messageId":"20161022233225.8883-31-sbeller@google.com","threadId":"44358","inReplyTo":"20161022233225.8883-1-sbeller@google.com","subject":"[PATCH 30/36] pathspec: move long magic parsing out of prefix_pathspec","fromName":"Stefan Beller","fromEmail":"sbeller@google.com","sentAt":"2016-10-22T23:32:19Z","receivedAt":"2016-10-22T23:33:44Z","isPatch":true,"sender":{"key":"stefanbeller@gmail.com","avatar":"https://avatars.githubusercontent.com/u/455868?v=4"},"body":"`prefix_pathspec` is quite a lengthy function and we plan on adding more.\nSplit it up for better readability. As we want to add code into the\ninner loop of the long magic parsing, we also benefit from lower\nindentation.\n\nSigned-off-by: Stefan Beller <sbeller@google.com>\nSigned-off-by: Junio C Hamano <gitster@pobox.com>\n---\n pathspec.c | 84 +++++++++++++++++++++++++++++++++++---------------------------\n 1 file changed, 47 insertions(+), 37 deletions(-)\n\ndiff --git a/pathspec.c b/pathspec.c\nindex 86f2b44..67678fc 100644\n--- a/pathspec.c\n+++ b/pathspec.c\n@@ -88,6 +88,52 @@ static void prefix_short_magic(struct strbuf *sb, int prefixlen,\n \tstrbuf_addf(sb, \",prefix:%d)\", prefixlen);\n }\n \n+static void eat_long_magic(struct pathspec_item *item, const char *elt,\n+\t\tunsigned *magic, int *pathspec_prefix,\n+\t\tconst char **copyfrom_, const char **long_magic_end)\n+{\n+\tint i;\n+\tconst char *copyfrom = *copyfrom_;\n+\t/* longhand */\n+\tconst char *nextat;\n+\tfor (copyfrom = elt + 2;\n+\t     *copyfrom && *copyfrom != ')';\n+\t     copyfrom = nextat) {\n+\t\tsize_t len = strcspn(copyfrom, \",)\");\n+\t\tif (copyfrom[len] == ',')\n+\t\t\tnextat = copyfrom + len + 1;\n+\t\telse\n+\t\t\t/* handle ')' and '\\0' */\n+\t\t\tnextat = copyfrom + len;\n+\t\tif (!len)\n+\t\t\tcontinue;\n+\t\tfor (i = 0; i < ARRAY_SIZE(pathspec_magic); i++) {\n+\t\t\tif (strlen(pathspec_magic[i].name) == len &&\n+\t\t\t    !strncmp(pathspec_magic[i].name, copyfrom, len)) {\n+\t\t\t\t*magic |= pathspec_magic[i].bit;\n+\t\t\t\tbreak;\n+\t\t\t}\n+\t\t\tif (starts_with(copyfrom, \"prefix:\")) {\n+\t\t\t\tchar *endptr;\n+\t\t\t\t*pathspec_prefix = strtol(copyfrom + 7,\n+\t\t\t\t\t\t\t  &endptr, 10);\n+\t\t\t\tif (endptr - copyfrom != len)\n+\t\t\t\t\tdie(_(\"invalid parameter for pathspec magic 'prefix'\"));\n+\t\t\t\t/* \"i\" would be wrong, but it does not matter */\n+\t\t\t\tbreak;\n+\t\t\t}\n+\t\t}\n+\t\tif (ARRAY_SIZE(pathspec_magic) <= i)\n+\t\t\tdie(_(\"Invalid pathspec magic '%.*s' in '%s'\"),\n+\t\t\t    (int) len, copyfrom, elt);\n+\t}\n+\tif (*copyfrom != ')')\n+\t\tdie(_(\"Missing ')' at the end of pathspec magic in '%s'\"), elt);\n+\t*long_magic_end = copyfrom;\n+\tcopyfrom++;\n+\t*copyfrom_ = copyfrom;\n+}\n+\n /*\n  * Take an element of a pathspec and check for magic signatures.\n  * Append the result to the prefix. Return the magic bitmap.\n@@ -150,43 +196,7 @@ static unsigned prefix_pathspec(struct pathspec_item *item,\n \t    (flags & PATHSPEC_LITERAL_PATH)) {\n \t\t; /* nothing to do */\n \t} else if (elt[1] == '(') {\n-\t\t/* longhand */\n-\t\tconst char *nextat;\n-\t\tfor (copyfrom = elt + 2;\n-\t\t     *copyfrom && *copyfrom != ')';\n-\t\t     copyfrom = nextat) {\n-\t\t\tsize_t len = strcspn(copyfrom, \",)\");\n-\t\t\tif (copyfrom[len] == ',')\n-\t\t\t\tnextat = copyfrom + len + 1;\n-\t\t\telse\n-\t\t\t\t/* handle ')' and '\\0' */\n-\t\t\t\tnextat = copyfrom + len;\n-\t\t\tif (!len)\n-\t\t\t\tcontinue;\n-\t\t\tfor (i = 0; i < ARRAY_SIZE(pathspec_magic); i++) {\n-\t\t\t\tif (strlen(pathspec_magic[i].name) == len &&\n-\t\t\t\t    !strncmp(pathspec_magic[i].name, copyfrom, len)) {\n-\t\t\t\t\tmagic |= pathspec_magic[i].bit;\n-\t\t\t\t\tbreak;\n-\t\t\t\t}\n-\t\t\t\tif (starts_with(copyfrom, \"prefix:\")) {\n-\t\t\t\t\tchar *endptr;\n-\t\t\t\t\tpathspec_prefix = strtol(copyfrom + 7,\n-\t\t\t\t\t\t\t\t &endptr, 10);\n-\t\t\t\t\tif (endptr - copyfrom != len)\n-\t\t\t\t\t\tdie(_(\"invalid parameter for pathspec magic 'prefix'\"));\n-\t\t\t\t\t/* \"i\" would be wrong, but it does not matter */\n-\t\t\t\t\tbreak;\n-\t\t\t\t}\n-\t\t\t}\n-\t\t\tif (ARRAY_SIZE(pathspec_magic) <= i)\n-\t\t\t\tdie(_(\"Invalid pathspec magic '%.*s' in '%s'\"),\n-\t\t\t\t    (int) len, copyfrom, elt);\n-\t\t}\n-\t\tif (*copyfrom != ')')\n-\t\t\tdie(_(\"Missing ')' at the end of pathspec magic in '%s'\"), elt);\n-\t\tlong_magic_end = copyfrom;\n-\t\tcopyfrom++;\n+\t\teat_long_magic(item, elt, &magic, &pathspec_prefix, &copyfrom, &long_magic_end);\n \t} else {\n \t\t/* shorthand */\n \t\tfor (copyfrom = elt + 1;\n-- \n2.10.1.508.g6572022\n\n"},{"id":"304711","messageId":"20161022233225.8883-32-sbeller@google.com","threadId":"44358","inReplyTo":"20161022233225.8883-1-sbeller@google.com","subject":"[PATCH 31/36] pathspec: move prefix check out of the inner loop","fromName":"Stefan Beller","fromEmail":"sbeller@google.com","sentAt":"2016-10-22T23:32:20Z","receivedAt":"2016-10-22T23:33:46Z","isPatch":true,"sender":{"key":"stefanbeller@gmail.com","avatar":"https://avatars.githubusercontent.com/u/455868?v=4"},"body":"The prefix check is not related the check of pathspec magic; also there\nis no code that is relevant after we'd break the loop on a match for\n\"prefix:\". So move the check before the loop and shortcircuit the outer\nloop.\n\nSigned-off-by: Stefan Beller <sbeller@google.com>\nSigned-off-by: Junio C Hamano <gitster@pobox.com>\n---\n pathspec.c | 19 ++++++++++---------\n 1 file changed, 10 insertions(+), 9 deletions(-)\n\ndiff --git a/pathspec.c b/pathspec.c\nindex 67678fc..d44f8e7 100644\n--- a/pathspec.c\n+++ b/pathspec.c\n@@ -107,21 +107,22 @@ static void eat_long_magic(struct pathspec_item *item, const char *elt,\n \t\t\tnextat = copyfrom + len;\n \t\tif (!len)\n \t\t\tcontinue;\n+\n+\t\tif (starts_with(copyfrom, \"prefix:\")) {\n+\t\t\tchar *endptr;\n+\t\t\t*pathspec_prefix = strtol(copyfrom + 7,\n+\t\t\t\t\t\t  &endptr, 10);\n+\t\t\tif (endptr - copyfrom != len)\n+\t\t\t\tdie(_(\"invalid parameter for pathspec magic 'prefix'\"));\n+\t\t\tcontinue;\n+\t\t}\n+\n \t\tfor (i = 0; i < ARRAY_SIZE(pathspec_magic); i++) {\n \t\t\tif (strlen(pathspec_magic[i].name) == len &&\n \t\t\t    !strncmp(pathspec_magic[i].name, copyfrom, len)) {\n \t\t\t\t*magic |= pathspec_magic[i].bit;\n \t\t\t\tbreak;\n \t\t\t}\n-\t\t\tif (starts_with(copyfrom, \"prefix:\")) {\n-\t\t\t\tchar *endptr;\n-\t\t\t\t*pathspec_prefix = strtol(copyfrom + 7,\n-\t\t\t\t\t\t\t  &endptr, 10);\n-\t\t\t\tif (endptr - copyfrom != len)\n-\t\t\t\t\tdie(_(\"invalid parameter for pathspec magic 'prefix'\"));\n-\t\t\t\t/* \"i\" would be wrong, but it does not matter */\n-\t\t\t\tbreak;\n-\t\t\t}\n \t\t}\n \t\tif (ARRAY_SIZE(pathspec_magic) <= i)\n \t\t\tdie(_(\"Invalid pathspec magic '%.*s' in '%s'\"),\n-- \n2.10.1.508.g6572022\n\n"},{"id":"304712","messageId":"20161022233225.8883-30-sbeller@google.com","threadId":"44358","inReplyTo":"20161022233225.8883-1-sbeller@google.com","subject":"[PATCH 29/36] Documentation: fix a typo","fromName":"Stefan Beller","fromEmail":"sbeller@google.com","sentAt":"2016-10-22T23:32:18Z","receivedAt":"2016-10-22T23:33:48Z","isPatch":true,"sender":{"key":"stefanbeller@gmail.com","avatar":"https://avatars.githubusercontent.com/u/455868?v=4"},"body":"Signed-off-by: Stefan Beller <sbeller@google.com>\nSigned-off-by: Junio C Hamano <gitster@pobox.com>\n---\n Documentation/gitattributes.txt | 2 +-\n 1 file changed, 1 insertion(+), 1 deletion(-)\n\ndiff --git a/Documentation/gitattributes.txt b/Documentation/gitattributes.txt\nindex 8a061af..5b31797 100644\n--- a/Documentation/gitattributes.txt\n+++ b/Documentation/gitattributes.txt\n@@ -88,7 +88,7 @@ is either not set or empty, $HOME/.config/git/attributes is used instead.\n Attributes for all users on a system should be placed in the\n `$(prefix)/etc/gitattributes` file.\n \n-Sometimes you would need to override an setting of an attribute\n+Sometimes you would need to override a setting of an attribute\n for a path to `Unspecified` state.  This can be done by listing\n the name of the attribute prefixed with an exclamation point `!`.\n \n-- \n2.10.1.508.g6572022\n\n"},{"id":"304713","messageId":"20161022233225.8883-29-sbeller@google.com","threadId":"44358","inReplyTo":"20161022233225.8883-1-sbeller@google.com","subject":"[PATCH 28/36] attr: keep attr stack for each check","fromName":"Stefan Beller","fromEmail":"sbeller@google.com","sentAt":"2016-10-22T23:32:17Z","receivedAt":"2016-10-22T23:33:54Z","isPatch":true,"sender":{"key":"stefanbeller@gmail.com","avatar":"https://avatars.githubusercontent.com/u/455868?v=4"},"body":"Instead of having a global attr stack, attach the stack to each check.\nThis allows to use the attr in a multithreaded way.\n\n\n\nSigned-off-by: Stefan Beller <sbeller@google.com>\n---\n attr.c    | 101 +++++++++++++++++++++++++++++++++++++++-----------------------\n attr.h    |   4 ++-\n hashmap.h |   2 ++\n 3 files changed, 69 insertions(+), 38 deletions(-)\n\ndiff --git a/attr.c b/attr.c\nindex 89ae155..b65437d 100644\n--- a/attr.c\n+++ b/attr.c\n@@ -372,15 +372,17 @@ static struct match_attr *parse_attr_line(const char *line, const char *src,\n  * .gitignore file and info/excludes file as a fallback.\n  */\n \n-/* NEEDSWORK: This will become per git_attr_check */\n-static struct attr_stack {\n+struct attr_stack {\n \tstruct attr_stack *prev;\n \tchar *origin;\n \tsize_t originlen;\n \tunsigned num_matches;\n \tunsigned alloc;\n \tstruct match_attr **attrs;\n-} *attr_stack;\n+};\n+\n+struct hashmap all_attr_stacks;\n+int all_attr_stacks_init;\n \n static void free_attr_elem(struct attr_stack *e)\n {\n@@ -561,11 +563,23 @@ static void debug_set(const char *what, const char *match, struct git_attr *attr\n \n static void drop_attr_stack(void)\n {\n-\twhile (attr_stack) {\n-\t\tstruct attr_stack *elem = attr_stack;\n-\t\tattr_stack = elem->prev;\n-\t\tfree_attr_elem(elem);\n+\tstruct hashmap_iter iter;\n+\tstruct git_attr_check *check;\n+\n+\tattr_lock();\n+\tif (!all_attr_stacks_init) {\n+\t\tattr_unlock();\n+\t\treturn;\n \t}\n+\thashmap_iter_init(&all_attr_stacks, &iter);\n+\twhile ((check = hashmap_iter_next(&iter))) {\n+\t\twhile (check->attr_stack) {\n+\t\t\tstruct attr_stack *elem = check->attr_stack;\n+\t\t\tcheck->attr_stack = elem->prev;\n+\t\t\tfree_attr_elem(elem);\n+\t\t}\n+\t}\n+\tattr_unlock();\n }\n \n static const char *git_etc_gitattributes(void)\n@@ -595,40 +609,42 @@ static void push_stack(struct attr_stack **attr_stack_p,\n \t}\n }\n \n-static void bootstrap_attr_stack(void)\n+static void bootstrap_attr_stack(struct git_attr_check *check)\n {\n \tstruct attr_stack *elem;\n \n-\tif (attr_stack)\n+\tif (check->attr_stack)\n \t\treturn;\n \n-\tpush_stack(&attr_stack, read_attr_from_array(builtin_attr), NULL, 0);\n+\tpush_stack(&check->attr_stack,\n+\t\t   read_attr_from_array(builtin_attr), NULL, 0);\n \n \tif (git_attr_system())\n-\t\tpush_stack(&attr_stack,\n+\t\tpush_stack(&check->attr_stack,\n \t\t\t   read_attr_from_file(git_etc_gitattributes(), 1),\n \t\t\t   NULL, 0);\n \n \tif (!git_attributes_file)\n \t\tgit_attributes_file = xdg_config_home(\"attributes\");\n \tif (git_attributes_file)\n-\t\tpush_stack(&attr_stack,\n+\t\tpush_stack(&check->attr_stack,\n \t\t\t   read_attr_from_file(git_attributes_file, 1),\n \t\t\t   NULL, 0);\n \n \tif (!is_bare_repository() || direction == GIT_ATTR_INDEX) {\n \t\telem = read_attr(GITATTRIBUTES_FILE, 1);\n-\t\tpush_stack(&attr_stack, elem, xstrdup(\"\"), 0);\n+\t\tpush_stack(&check->attr_stack, elem, xstrdup(\"\"), 0);\n \t\tdebug_push(elem);\n \t}\n \n \telem = read_attr_from_file(git_path_info_attributes(), 1);\n \tif (!elem)\n \t\telem = xcalloc(1, sizeof(*elem));\n-\tpush_stack(&attr_stack, elem, NULL, 0);\n+\tpush_stack(&check->attr_stack, elem, NULL, 0);\n }\n \n-static void prepare_attr_stack(const char *path, int dirlen)\n+static void prepare_attr_stack(const char *path, int dirlen,\n+\t\t\t       struct git_attr_check *check)\n {\n \tstruct attr_stack *elem, *info;\n \tconst char *cp;\n@@ -648,13 +664,13 @@ static void prepare_attr_stack(const char *path, int dirlen)\n \t * .gitattributes in deeper directories to shallower ones,\n \t * and finally use the built-in set as the default.\n \t */\n-\tbootstrap_attr_stack();\n+\tbootstrap_attr_stack(check);\n \n \t/*\n \t * Pop the \"info\" one that is always at the top of the stack.\n \t */\n-\tinfo = attr_stack;\n-\tattr_stack = info->prev;\n+\tinfo = check->attr_stack;\n+\tcheck->attr_stack = info->prev;\n \n \t/*\n \t * Pop the ones from directories that are not the prefix of\n@@ -662,17 +678,17 @@ static void prepare_attr_stack(const char *path, int dirlen)\n \t * the root one (whose origin is an empty string \"\") or the builtin\n \t * one (whose origin is NULL) without popping it.\n \t */\n-\twhile (attr_stack->origin) {\n-\t\tint namelen = strlen(attr_stack->origin);\n+\twhile (check->attr_stack->origin) {\n+\t\tint namelen = strlen(check->attr_stack->origin);\n \n-\t\telem = attr_stack;\n+\t\telem = check->attr_stack;\n \t\tif (namelen <= dirlen &&\n \t\t    !strncmp(elem->origin, path, namelen) &&\n \t\t    (!namelen || path[namelen] == '/'))\n \t\t\tbreak;\n \n \t\tdebug_pop(elem);\n-\t\tattr_stack = elem->prev;\n+\t\tcheck->attr_stack = elem->prev;\n \t\tfree_attr_elem(elem);\n \t}\n \n@@ -688,9 +704,9 @@ static void prepare_attr_stack(const char *path, int dirlen)\n \t\t */\n \t\tstruct strbuf pathbuf = STRBUF_INIT;\n \n-\t\tassert(attr_stack->origin);\n+\t\tassert(check->attr_stack->origin);\n \t\twhile (1) {\n-\t\t\tsize_t len = strlen(attr_stack->origin);\n+\t\t\tsize_t len = strlen(check->attr_stack->origin);\n \t\t\tchar *origin;\n \n \t\t\tif (dirlen <= len)\n@@ -704,7 +720,7 @@ static void prepare_attr_stack(const char *path, int dirlen)\n \t\t\telem = read_attr(pathbuf.buf, 0);\n \t\t\tstrbuf_setlen(&pathbuf, cp - path);\n \t\t\torigin = strbuf_detach(&pathbuf, &len);\n-\t\t\tpush_stack(&attr_stack, elem, origin, len);\n+\t\t\tpush_stack(&check->attr_stack, elem, origin, len);\n \t\t\tdebug_push(elem);\n \t\t}\n \n@@ -714,7 +730,13 @@ static void prepare_attr_stack(const char *path, int dirlen)\n \t/*\n \t * Finally push the \"info\" one at the top of the stack.\n \t */\n-\tpush_stack(&attr_stack, info, NULL, 0);\n+\tpush_stack(&check->attr_stack, info, NULL, 0);\n+\tif (!all_attr_stacks_init) {\n+\t\thashmap_init(&all_attr_stacks, NULL, 0);\n+\t\tall_attr_stacks_init = 1;\n+\t}\n+\tif (!hashmap_get(&all_attr_stacks, check, NULL))\n+\t\thashmap_put(&all_attr_stacks, check);\n }\n \n static int path_matches(const char *pathname, int pathlen,\n@@ -740,9 +762,10 @@ static int path_matches(const char *pathname, int pathlen,\n \t\t\t      pattern, prefix, pat->patternlen, pat->flags);\n }\n \n-static int macroexpand_one(int attr_nr, int rem);\n+static int macroexpand_one(int attr_nr, int rem, struct git_attr_check *check);\n \n-static int fill_one(const char *what, struct match_attr *a, int rem)\n+static int fill_one(const char *what, struct match_attr *a, int rem,\n+\t\t    struct git_attr_check *check)\n {\n \tstruct git_attr_check_elem *celem = check_all_attr;\n \tint i;\n@@ -758,14 +781,14 @@ static int fill_one(const char *what, struct match_attr *a, int rem)\n \t\t\t\t  attr, v);\n \t\t\t*n = v;\n \t\t\trem--;\n-\t\t\trem = macroexpand_one(attr->attr_nr, rem);\n+\t\t\trem = macroexpand_one(attr->attr_nr, rem, check);\n \t\t}\n \t}\n \treturn rem;\n }\n \n static int fill(const char *path, int pathlen, int basename_offset,\n-\t\tstruct attr_stack *stk, int rem)\n+\t\tstruct attr_stack *stk, int rem, struct git_attr_check *check)\n {\n \tint i;\n \tconst char *base = stk->origin ? stk->origin : \"\";\n@@ -776,12 +799,12 @@ static int fill(const char *path, int pathlen, int basename_offset,\n \t\t\tcontinue;\n \t\tif (path_matches(path, pathlen, basename_offset,\n \t\t\t\t &a->u.pat, base, stk->originlen))\n-\t\t\trem = fill_one(\"fill\", a, rem);\n+\t\t\trem = fill_one(\"fill\", a, rem, check);\n \t}\n \treturn rem;\n }\n \n-static int macroexpand_one(int nr, int rem)\n+static int macroexpand_one(int nr, int rem, struct git_attr_check *check)\n {\n \tstruct attr_stack *stk;\n \tint i;\n@@ -790,13 +813,13 @@ static int macroexpand_one(int nr, int rem)\n \t    !check_all_attr[nr].attr->maybe_macro)\n \t\treturn rem;\n \n-\tfor (stk = attr_stack; stk; stk = stk->prev) {\n+\tfor (stk = check->attr_stack; stk; stk = stk->prev) {\n \t\tfor (i = stk->num_matches - 1; 0 <= i; i--) {\n \t\t\tstruct match_attr *ma = stk->attrs[i];\n \t\t\tif (!ma->is_macro)\n \t\t\t\tcontinue;\n \t\t\tif (ma->u.attr->attr_nr == nr)\n-\t\t\t\treturn fill_one(\"expand\", ma, rem);\n+\t\t\t\treturn fill_one(\"expand\", ma, rem, check);\n \t\t}\n \t}\n \n@@ -845,7 +868,7 @@ static void collect_some_attrs(const char *path, int pathlen,\n \t\tdirlen = 0;\n \t}\n \n-\tprepare_attr_stack(path, dirlen);\n+\tprepare_attr_stack(path, dirlen, check);\n \n \tfor (i = 0; i < attr_nr; i++)\n \t\tcheck_all_attr[i].value = ATTR__UNKNOWN;\n@@ -865,8 +888,8 @@ static void collect_some_attrs(const char *path, int pathlen,\n \t}\n \n \trem = attr_nr;\n-\tfor (stk = attr_stack; 0 < rem && stk; stk = stk->prev)\n-\t\trem = fill(path, pathlen, basename_offset, stk, rem);\n+\tfor (stk = check->attr_stack; 0 < rem && stk; stk = stk->prev)\n+\t\trem = fill(path, pathlen, basename_offset, stk, rem, check);\n \n \tif (collect_all) {\n \t\tint check_nr = 0, check_alloc = 0;\n@@ -898,6 +921,8 @@ static int git_check_attrs(const char *path, int pathlen,\n {\n \tint i;\n \n+\tattr_lock();\n+\n \tcollect_some_attrs(path, pathlen, check, &result, 0);\n \n \tfor (i = 0; i < check->check_nr; i++) {\n@@ -907,6 +932,8 @@ static int git_check_attrs(const char *path, int pathlen,\n \t\tresult[i].value = value;\n \t}\n \n+\tattr_unlock();\n+\n \treturn 0;\n }\n \ndiff --git a/attr.h b/attr.h\nindex 219b8c7..60d90f2 100644\n--- a/attr.h\n+++ b/attr.h\n@@ -32,12 +32,14 @@ extern const char git_attr__false[];\n #define ATTR_UNSET(v) ((v) == NULL)\n \n struct git_attr_check {\n+\tstruct hashmap_entry entry;\n \tint finalized;\n \tint check_nr;\n \tint check_alloc;\n \tconst struct git_attr **attr;\n+\tstruct attr_stack *attr_stack;\n };\n-#define GIT_ATTR_CHECK_INIT {0, 0, 0, NULL}\n+#define GIT_ATTR_CHECK_INIT {HASHMAP_ENTRY_INIT, 0, 0, 0, NULL, NULL}\n \n struct git_attr_result {\n \tconst char *value;\ndiff --git a/hashmap.h b/hashmap.h\nindex ab7958a..d247c62 100644\n--- a/hashmap.h\n+++ b/hashmap.h\n@@ -31,6 +31,8 @@ struct hashmap_entry {\n \tunsigned int hash;\n };\n \n+#define HASHMAP_ENTRY_INIT {NULL, 0}\n+\n typedef int (*hashmap_cmp_fn)(const void *entry, const void *entry_or_key,\n \t\tconst void *keydata);\n \n-- \n2.10.1.508.g6572022\n\n"},{"id":"304714","messageId":"20161022233225.8883-26-sbeller@google.com","threadId":"44358","inReplyTo":"20161022233225.8883-1-sbeller@google.com","subject":"[PATCH 25/36] attr.c: outline the future plans by heavily commenting","fromName":"Stefan Beller","fromEmail":"sbeller@google.com","sentAt":"2016-10-22T23:32:14Z","receivedAt":"2016-10-22T23:33:55Z","isPatch":true,"sender":{"key":"stefanbeller@gmail.com","avatar":"https://avatars.githubusercontent.com/u/455868?v=4"},"body":"From: Junio C Hamano <gitster@pobox.com>\n\nSigned-off-by: Junio C Hamano <gitster@pobox.com>\nSigned-off-by: Stefan Beller <sbeller@google.com>\n---\n attr.c | 40 +++++++++++++++++++++++++++++++++++++++-\n 1 file changed, 39 insertions(+), 1 deletion(-)\n\ndiff --git a/attr.c b/attr.c\nindex 673dc7a..0f08ee6 100644\n--- a/attr.c\n+++ b/attr.c\n@@ -30,6 +30,11 @@ static const char git_attr__unknown[] = \"(builtin)unknown\";\n #define DEBUG_ATTR 0\n #endif\n \n+/*\n+ * NEEDSWORK: the global dictionary of the interned attributes\n+ * must stay a singleton even after we become thread-ready.\n+ * Access to these must be surrounded with mutex when it happens.\n+ */\n struct git_attr {\n \tstruct git_attr *next;\n \tunsigned h;\n@@ -39,10 +44,19 @@ struct git_attr {\n \tchar name[FLEX_ARRAY];\n };\n static int attr_nr;\n+static struct git_attr *(git_attr_hash[HASHSIZE]);\n+\n+/*\n+ * NEEDSWORK: maybe-real, maybe-macro are not property of\n+ * an attribute, as it depends on what .gitattributes are\n+ * read.  Once we introduce per git_attr_check attr_stack\n+ * and check_all_attr, the optimization based on them will\n+ * become unnecessary and can go away.  So is this variable.\n+ */\n static int cannot_trust_maybe_real;\n \n+/* NEEDSWORK: This will become per git_attr_check */\n static struct git_attr_check_elem *check_all_attr;\n-static struct git_attr *(git_attr_hash[HASHSIZE]);\n \n const char *git_attr_name(const struct git_attr *attr)\n {\n@@ -117,6 +131,11 @@ struct git_attr *git_attr_counted(const char *name, size_t len)\n \ta->maybe_real = 0;\n \tgit_attr_hash[pos] = a;\n \n+\t/*\n+\t * NEEDSWORK: per git_attr_check check_all_attr\n+\t * will be initialized a lot more lazily, not\n+\t * like this, and not here.\n+\t */\n \tREALLOC_ARRAY(check_all_attr, attr_nr);\n \tcheck_all_attr[a->attr_nr].attr = a;\n \tcheck_all_attr[a->attr_nr].value = ATTR__UNKNOWN;\n@@ -329,6 +348,7 @@ static struct match_attr *parse_attr_line(const char *line, const char *src,\n  * .gitignore file and info/excludes file as a fallback.\n  */\n \n+/* NEEDSWORK: This will become per git_attr_check */\n static struct attr_stack {\n \tstruct attr_stack *prev;\n \tchar *origin;\n@@ -393,6 +413,24 @@ static struct attr_stack *read_attr_from_array(const char **list)\n \treturn res;\n }\n \n+/*\n+ * NEEDSWORK: these two are tricky.  The callers assume there is a\n+ * single, system-wide global state \"where we read attributes from?\"\n+ * and when the state is flipped by calling git_attr_set_direction(),\n+ * attr_stack is discarded so that subsequent attr_check will lazily\n+ * read from the right place.  And they do not know or care who called\n+ * by them uses the attribute subsystem, hence have no knowledge of\n+ * existing git_attr_check instances or future ones that will be\n+ * created).\n+ *\n+ * Probably we need a thread_local that holds these two variables,\n+ * and a list of git_attr_check instances (which need to be maintained\n+ * by hooking into git_attr_check_alloc(), git_attr_check_initl(), and\n+ * git_attr_check_clear().  Then git_attr_set_direction() updates the\n+ * fields in that thread_local for these two variables, iterate over\n+ * all the active git_attr_check instances and discard the attr_stack\n+ * they hold.  Yuck, but it sounds doable.\n+ */\n static enum git_attr_direction direction;\n static struct index_state *use_index;\n \n-- \n2.10.1.508.g6572022\n\n"},{"id":"304715","messageId":"20161022233225.8883-25-sbeller@google.com","threadId":"44358","inReplyTo":"20161022233225.8883-1-sbeller@google.com","subject":"[PATCH 24/36] attr.c: always pass check[] to collect_some_attrs()","fromName":"Stefan Beller","fromEmail":"sbeller@google.com","sentAt":"2016-10-22T23:32:13Z","receivedAt":"2016-10-22T23:33:58Z","isPatch":true,"sender":{"key":"stefanbeller@gmail.com","avatar":"https://avatars.githubusercontent.com/u/455868?v=4"},"body":"From: Junio C Hamano <gitster@pobox.com>\n\nThis function used to be called with check=NULL to signal it to\ncollect all attributes in the global check_all_attr[] array.\n\nBecause the longer term plan is to allocate check_all_attr[] and\nattr_stack data structures per git_attr_check instance (i.e. \"check\"\nhere) to make the attr subsystem thread-safe, it is unacceptable.\n\nPass \"Are we grabbing all attributes defined in the system?\" bit as\na separate argument and pass it from the callers.\n\nSigned-off-by: Junio C Hamano <gitster@pobox.com>\nSigned-off-by: Stefan Beller <sbeller@google.com>\n---\n attr.c | 37 +++++++++++++++++++------------------\n 1 file changed, 19 insertions(+), 18 deletions(-)\n\ndiff --git a/attr.c b/attr.c\nindex 9f58cc0..673dc7a 100644\n--- a/attr.c\n+++ b/attr.c\n@@ -756,11 +756,12 @@ static void empty_attr_check_elems(struct git_attr_check *check)\n \n /*\n  * Collect attributes for path into the array pointed to by\n- * check_all_attr.  If check is not NULL, only attributes in\n- * check[] are collected. Otherwise all attributes are collected.\n+ * check_all_attr.  If collect_all is zero, only attributes in\n+ * check[] are collected.  Otherwise, check[] is cleared and\n+ * any and all attributes that are visible are collected in it.\n  */\n static void collect_some_attrs(const char *path, int pathlen,\n-\t\t\t       struct git_attr_check *check)\n+\t\t\t       struct git_attr_check *check, int collect_all)\n \n {\n \tstruct attr_stack *stk;\n@@ -781,10 +782,11 @@ static void collect_some_attrs(const char *path, int pathlen,\n \t}\n \n \tprepare_attr_stack(path, dirlen);\n+\n \tfor (i = 0; i < attr_nr; i++)\n \t\tcheck_all_attr[i].value = ATTR__UNKNOWN;\n \n-\tif (check && !cannot_trust_maybe_real) {\n+\tif (!collect_all && !cannot_trust_maybe_real) {\n \t\tstruct git_attr_check_elem *celem = check->check;\n \n \t\trem = 0;\n@@ -803,6 +805,17 @@ static void collect_some_attrs(const char *path, int pathlen,\n \trem = attr_nr;\n \tfor (stk = attr_stack; 0 < rem && stk; stk = stk->prev)\n \t\trem = fill(path, pathlen, basename_offset, stk, rem);\n+\n+\tif (collect_all) {\n+\t\tempty_attr_check_elems(check);\n+\t\tfor (i = 0; i < attr_nr; i++) {\n+\t\t\tconst struct git_attr *attr = check_all_attr[i].attr;\n+\t\t\tconst char *value = check_all_attr[i].value;\n+\t\t\tif (value == ATTR__UNSET || value == ATTR__UNKNOWN)\n+\t\t\t\tcontinue;\n+\t\t\tgit_attr_check_append(check, attr)->value = value;\n+\t\t}\n+\t}\n }\n \n static int git_check_attrs(const char *path, int pathlen,\n@@ -811,7 +824,7 @@ static int git_check_attrs(const char *path, int pathlen,\n \tint i;\n \tstruct git_attr_check_elem *celem = check->check;\n \n-\tcollect_some_attrs(path, pathlen, check);\n+\tcollect_some_attrs(path, pathlen, check, 0);\n \n \tfor (i = 0; i < check->check_nr; i++) {\n \t\tconst char *value = check_all_attr[celem[i].attr->attr_nr].value;\n@@ -825,19 +838,7 @@ static int git_check_attrs(const char *path, int pathlen,\n \n void git_all_attrs(const char *path, struct git_attr_check *check)\n {\n-\tint i;\n-\n-\tgit_attr_check_clear(check);\n-\tcollect_some_attrs(path, strlen(path), NULL);\n-\n-\tfor (i = 0; i < attr_nr; i++) {\n-\t\tconst char *name = check_all_attr[i].attr->name;\n-\t\tconst char *value = check_all_attr[i].value;\n-\t\tif (value == ATTR__UNSET || value == ATTR__UNKNOWN)\n-\t\t\tcontinue;\n-\t\tgit_attr_check_append(check, git_attr(name));\n-\t\tcheck->check[check->check_nr - 1].value = value;\n-\t}\n+\tcollect_some_attrs(path, strlen(path), check, 1);\n }\n \n void git_attr_set_direction(enum git_attr_direction new, struct index_state *istate)\n-- \n2.10.1.508.g6572022\n\n"},{"id":"304716","messageId":"20161022233225.8883-37-sbeller@google.com","threadId":"44358","inReplyTo":"20161022233225.8883-1-sbeller@google.com","subject":"[PATCH 36/36] completion: clone can initialize specific submodules","fromName":"Stefan Beller","fromEmail":"sbeller@google.com","sentAt":"2016-10-22T23:32:25Z","receivedAt":"2016-10-22T23:33:59Z","isPatch":true,"sender":{"key":"stefanbeller@gmail.com","avatar":"https://avatars.githubusercontent.com/u/455868?v=4"},"body":"Signed-off-by: Stefan Beller <sbeller@google.com>\n---\n contrib/completion/git-completion.bash | 1 +\n 1 file changed, 1 insertion(+)\n\ndiff --git a/contrib/completion/git-completion.bash b/contrib/completion/git-completion.bash\nindex 21016bf..90eb772 100644\n--- a/contrib/completion/git-completion.bash\n+++ b/contrib/completion/git-completion.bash\n@@ -1138,6 +1138,7 @@ _git_clone ()\n \t\t\t--single-branch\n \t\t\t--branch\n \t\t\t--recurse-submodules\n+\t\t\t--init-submodule\n \t\t\t\"\n \t\treturn\n \t\t;;\n-- \n2.10.1.508.g6572022\n\n"},{"id":"304717","messageId":"20161022233225.8883-36-sbeller@google.com","threadId":"44358","inReplyTo":"20161022233225.8883-1-sbeller@google.com","subject":"[PATCH 35/36] clone: add --init-submodule=<pathspec> switch","fromName":"Stefan Beller","fromEmail":"sbeller@google.com","sentAt":"2016-10-22T23:32:24Z","receivedAt":"2016-10-22T23:34:02Z","isPatch":true,"sender":{"key":"stefanbeller@gmail.com","avatar":"https://avatars.githubusercontent.com/u/455868?v=4"},"body":"The new switch passes the pathspec to `git submodule update --init`\nwhich is called after the actual clone is done.\n\nAdditionally this configures the submodule.defaultUpdatePath to\nbe the given pathspec, such that any future invocation of\n`git submodule update --init-default-paths` will keep up\nwith the pathspec.\n\nSigned-off-by: Stefan Beller <sbeller@google.com>\nSigned-off-by: Junio C Hamano <gitster@pobox.com>\n---\n Documentation/git-clone.txt | 23 +++++++++----\n builtin/clone.c             | 36 ++++++++++++++++++--\n t/t7400-submodule-basic.sh  | 81 +++++++++++++++++++++++++++++++++++++++++++++\n 3 files changed, 131 insertions(+), 9 deletions(-)\n\ndiff --git a/Documentation/git-clone.txt b/Documentation/git-clone.txt\nindex 35cc34b..1089f38 100644\n--- a/Documentation/git-clone.txt\n+++ b/Documentation/git-clone.txt\n@@ -15,7 +15,8 @@ SYNOPSIS\n \t  [--dissociate] [--separate-git-dir <git dir>]\n \t  [--depth <depth>] [--[no-]single-branch]\n \t  [--recursive | --recurse-submodules] [--[no-]shallow-submodules]\n-\t  [--jobs <n>] [--] <repository> [<directory>]\n+\t  [--init-submodule <pathspec>] [--jobs <n>] [--]\n+\t  <repository> [<directory>]\n \n DESCRIPTION\n -----------\n@@ -217,12 +218,20 @@ objects from the source repository into a pack in the cloned repository.\n \n --recursive::\n --recurse-submodules::\n-\tAfter the clone is created, initialize all submodules within,\n-\tusing their default settings. This is equivalent to running\n-\t`git submodule update --init --recursive` immediately after\n-\tthe clone is finished. This option is ignored if the cloned\n-\trepository does not have a worktree/checkout (i.e. if any of\n-\t`--no-checkout`/`-n`, `--bare`, or `--mirror` is given)\n+\tAfter the clone is created, initialize and clone all submodules\n+\twithin, using their default settings. This is equivalent to\n+\trunning `git submodule update --recursive --init `\n+\timmediately after the clone is finished. This option is ignored\n+\tif the cloned repository does not have a worktree/checkout (i.e.\n+\tif any of `--no-checkout`/`-n`, `--bare`, or `--mirror` is given)\n+\n+--init-submodule::\n+\tAfter the clone is created, initialize and clone specified\n+\tsubmodules within, using their default settings. It is possible\n+\tto give multiple specifications by giving this argument multiple\n+\ttimes. This is equivalent to configure `submodule.defaultUpdateGroup`\n+\tand then running `git submodule update --init-default-path`\n+\timmediately after the clone is finished.\n \n --[no-]shallow-submodules::\n \tAll submodules which are cloned will be shallow with a depth of 1.\ndiff --git a/builtin/clone.c b/builtin/clone.c\nindex 6c76a6e..748e7c0 100644\n--- a/builtin/clone.c\n+++ b/builtin/clone.c\n@@ -56,6 +56,16 @@ static struct string_list option_required_reference = STRING_LIST_INIT_NODUP;\n static struct string_list option_optional_reference = STRING_LIST_INIT_NODUP;\n static int option_dissociate;\n static int max_jobs = -1;\n+static struct string_list init_submodules;\n+\n+static int init_submodules_cb(const struct option *opt, const char *arg, int unset)\n+{\n+\tif (unset)\n+\t\treturn -1;\n+\n+\tstring_list_append((struct string_list *)opt->value, arg);\n+\treturn 0;\n+}\n \n static struct option builtin_clone_options[] = {\n \tOPT__VERBOSITY(&option_verbosity),\n@@ -112,6 +122,9 @@ static struct option builtin_clone_options[] = {\n \t\t\tTRANSPORT_FAMILY_IPV4),\n \tOPT_SET_INT('6', \"ipv6\", &family, N_(\"use IPv6 addresses only\"),\n \t\t\tTRANSPORT_FAMILY_IPV6),\n+\tOPT_CALLBACK(0, \"init-submodule\", &init_submodules, N_(\"<pathspec>\"),\n+\t\t\tN_(\"clone specific submodules. Pass multiple times for complex pathspecs\"),\n+\t\t\tinit_submodules_cb),\n \tOPT_END()\n };\n \n@@ -733,13 +746,21 @@ static int checkout(int submodule_progress)\n \terr |= run_hook_le(NULL, \"post-checkout\", sha1_to_hex(null_sha1),\n \t\t\t   sha1_to_hex(sha1), \"1\", NULL);\n \n-\tif (!err && option_recursive) {\n+\tif (!err && (option_recursive || init_submodules.nr > 0)) {\n \t\tstruct argv_array args = ARGV_ARRAY_INIT;\n-\t\targv_array_pushl(&args, \"submodule\", \"update\", \"--init\", \"--recursive\", NULL);\n+\t\targv_array_pushl(&args, \"submodule\", \"update\", NULL);\n+\n+\t\tif (init_submodules.nr > 0)\n+\t\t\targv_array_pushf(&args, \"--init-default-path\");\n+\t\telse\n+\t\t\targv_array_pushf(&args, \"--init\");\n \n \t\tif (option_shallow_submodules == 1)\n \t\t\targv_array_push(&args, \"--depth=1\");\n \n+\t\tif (option_recursive)\n+\t\t\targv_array_pushf(&args, \"--recursive\");\n+\n \t\tif (max_jobs != -1)\n \t\t\targv_array_pushf(&args, \"--jobs=%d\", max_jobs);\n \n@@ -887,6 +908,17 @@ int cmd_clone(int argc, const char **argv, const char *prefix)\n \t\toption_no_checkout = 1;\n \t}\n \n+\tif (init_submodules.nr > 0) {\n+\t\tstruct string_list_item *item;\n+\t\tstruct strbuf sb = STRBUF_INIT;\n+\t\tfor_each_string_list_item(item, &init_submodules) {\n+\t\t\tstrbuf_addf(&sb, \"submodule.defaultUpdatePath=%s\",\n+\t\t\t\t    item->string);\n+\t\t\tstring_list_append(&option_config,\n+\t\t\t\t\t   strbuf_detach(&sb, NULL));\n+\t\t}\n+\t}\n+\n \tif (!option_origin)\n \t\toption_origin = \"origin\";\n \ndiff --git a/t/t7400-submodule-basic.sh b/t/t7400-submodule-basic.sh\nindex 4699b1c..90f9030 100755\n--- a/t/t7400-submodule-basic.sh\n+++ b/t/t7400-submodule-basic.sh\n@@ -1170,4 +1170,85 @@ test_expect_success 'submodule update --init-default-path' '\n \ttest_cmp expect actual\n '\n \n+cat <<EOF > expected\n+ sub0 (test2)\n+-sub1\n+-sub2\n+-sub3\n+EOF\n+\n+test_expect_success 'clone --init-submodule works' '\n+\ttest_when_finished \"rm -rf multisuper_clone\" &&\n+\tgit clone --recurse-submodules --init-submodule=\"sub0\" multisuper multisuper_clone &&\n+\t(\n+\t\tcd multisuper_clone &&\n+\t\tgit submodule status |cut -c 1,43- >../actual\n+\t) &&\n+\ttest_cmp actual expected\n+'\n+\n+cat <<EOF > expect\n+-sub0\n+ sub1 (test2)\n+-sub2\n+ sub3 (test2)\n+EOF\n+test_expect_success 'clone with multiple --init-submodule options' '\n+\ttest_when_finished \"rm -rf multisuper_clone\" &&\n+\tgit clone --recurse-submodules \\\n+\t\t  --init-submodule=\".\" \\\n+\t\t  --init-submodule \":(exclude)sub0\" \\\n+\t\t  --init-submodule \":(exclude)sub2\" \\\n+\t\t\tmultisuper multisuper_clone &&\n+\t(\n+\t\tcd multisuper_clone &&\n+\t\tgit submodule status |cut -c1,43- >../actual\n+\t) &&\n+\ttest_cmp expect actual\n+'\n+\n+cat <<EOF > expect\n+-sub0\n+ sub1 (test2)\n+-sub2\n+ sub3 (test2)\n+EOF\n+\n+cat <<EOF > expect2\n+-sub0\n+ sub1 (test2)\n+-sub2\n+ sub3 (test2)\n+-sub4\n+ sub5 (test2)\n+EOF\n+\n+test_expect_success 'clone and subsequent updates correctly auto-initialize submodules' '\n+\ttest_when_finished \"rm -rf multisuper_clone\" &&\n+\tgit clone --recurse-submodules --init-submodule=\".\" \\\n+\t\t\t\t       --init-submodule \":(exclude)sub0\" \\\n+\t\t\t\t       --init-submodule \":(exclude)sub2\" \\\n+\t\t\t\t       --init-submodule \":(exclude)sub4\" \\\n+\t\t\t\t       multisuper multisuper_clone &&\n+\t(\n+\t\tcd multisuper_clone &&\n+\t\tgit submodule status |cut -c1,43- >../actual\n+\t) &&\n+\ttest_cmp expect actual &&\n+\t(\n+\t\tcd multisuper &&\n+\t\tgit submodule add ../sub1 sub4 &&\n+\t\tgit submodule add ../sub1 sub5 &&\n+\t\tgit commit -m \"add more submodules\"\n+\t) &&\n+\t(\n+\t\tcd multisuper_clone &&\n+\t\t# obtain the new superproject\n+\t\tgit pull &&\n+\t\tgit submodule update --init-default-path &&\n+\t\tgit submodule status |cut -c1,43- >../actual\n+\t) &&\n+\ttest_cmp expect2 actual\n+'\n+\n test_done\n-- \n2.10.1.508.g6572022\n\n"},{"id":"304718","messageId":"20161022233225.8883-35-sbeller@google.com","threadId":"44358","inReplyTo":"20161022233225.8883-1-sbeller@google.com","subject":"[PATCH 34/36] submodule update: add `--init-default-path` switch","fromName":"Stefan Beller","fromEmail":"sbeller@google.com","sentAt":"2016-10-22T23:32:23Z","receivedAt":"2016-10-22T23:34:05Z","isPatch":true,"sender":{"key":"stefanbeller@gmail.com","avatar":"https://avatars.githubusercontent.com/u/455868?v=4"},"body":"The new switch `--init-default-path` initializes the submodules which are\nconfigured in `submodule.defaultUpdatePath` instead of those given as\ncommand line arguments before updating. In the first implementation this\nis made incompatible with further command line arguments as it is\nunclear what the user means by\n\n    git submodule update --init --init-default-path <paths>\n\nThis new switch allows to record more complex patterns as it saves\nretyping them whenever you invoke update.\n\nSigned-off-by: Stefan Beller <sbeller@google.com>\nSigned-off-by: Junio C Hamano <gitster@pobox.com>\n---\n Documentation/config.txt        |  5 ++++\n Documentation/git-submodule.txt | 17 +++++++++----\n git-submodule.sh                | 21 +++++++++++++---\n t/t7400-submodule-basic.sh      | 53 +++++++++++++++++++++++++++++++++++++++++\n 4 files changed, 89 insertions(+), 7 deletions(-)\n\ndiff --git a/Documentation/config.txt b/Documentation/config.txt\nindex 27069ac..72901ef 100644\n--- a/Documentation/config.txt\n+++ b/Documentation/config.txt\n@@ -2886,6 +2886,11 @@ submodule.alternateErrorStrategy\n \tas computed via `submodule.alternateLocation`. Possible values are\n \t`ignore`, `info`, `die`. Default is `die`.\n \n+submodule.defaultUpdatePath::\n+\tSpecifies a set of submodules to initialize when calling\n+\t`git submodule --init-default-group` by using the pathspec\n+\tsyntax.\n+\n tag.forceSignAnnotated::\n \tA boolean to specify whether annotated tags created should be GPG signed.\n \tIf `--annotate` is specified on the command line, it takes\ndiff --git a/Documentation/git-submodule.txt b/Documentation/git-submodule.txt\nindex bf3bb37..503fec8 100644\n--- a/Documentation/git-submodule.txt\n+++ b/Documentation/git-submodule.txt\n@@ -14,10 +14,10 @@ SYNOPSIS\n 'git submodule' [--quiet] status [--cached] [--recursive] [--] [<path>...]\n 'git submodule' [--quiet] init [--] [<path>...]\n 'git submodule' [--quiet] deinit [-f|--force] (--all|[--] <path>...)\n-'git submodule' [--quiet] update [--init] [--remote] [-N|--no-fetch]\n-\t      [--[no-]recommend-shallow] [-f|--force] [--rebase|--merge]\n-\t      [--reference <repository>] [--depth <depth>] [--recursive]\n-\t      [--jobs <n>] [--] [<path>...]\n+'git submodule' [--quiet] update [--init[-default-path]] [--remote] [-N|--no-fetch]\n+\t      [--[no-]recommend-shallow]\n+\t      [-f|--force] [--rebase|--merge] [--reference <repository>]\n+\t      [--depth <depth>] [--recursive] [--jobs <n>] [--] [<path>...]\n 'git submodule' [--quiet] summary [--cached|--files] [(-n|--summary-limit) <n>]\n \t      [commit] [--] [<path>...]\n 'git submodule' [--quiet] foreach [--recursive] <command>\n@@ -194,6 +194,10 @@ If the submodule is not yet initialized, and you just want to use the\n setting as stored in .gitmodules, you can automatically initialize the\n submodule with the `--init` option.\n \n+You can configure a set of submodules using pathspec syntax in\n+submodule.defaultUpdatePath you can use `--init-default-path` to initialize\n+those before updating.\n+\n If `--recursive` is specified, this command will recurse into the\n registered submodules, and update any nested submodules within.\n --\n@@ -361,6 +365,11 @@ the submodule itself.\n \tInitialize all submodules for which \"git submodule init\" has not been\n \tcalled so far before updating.\n \n+--init-default-path::\n+\tThis option is only valid for the update command.\n+\tInitialize all submodules configured in \"`submodule.defaultUpdatePath`\"\n+\tthat have not been updated before.\n+\n --name::\n \tThis option is only valid for the add command. It sets the submodule's\n \tname to the given string instead of defaulting to its path. The name\ndiff --git a/git-submodule.sh b/git-submodule.sh\nindex a024a13..334cecc 100755\n--- a/git-submodule.sh\n+++ b/git-submodule.sh\n@@ -9,7 +9,7 @@ USAGE=\"[--quiet] add [-b <branch>] [-f|--force] [--name <name>] [--reference <re\n    or: $dashless [--quiet] status [--cached] [--recursive] [--] [<path>...]\n    or: $dashless [--quiet] init [--] [<path>...]\n    or: $dashless [--quiet] deinit [-f|--force] (--all| [--] <path>...)\n-   or: $dashless [--quiet] update [--init] [--remote] [-N|--no-fetch] [-f|--force] [--checkout|--merge|--rebase] [--[no-]recommend-shallow] [--reference <repository>] [--recursive] [--] [<path>...]\n+   or: $dashless [--quiet] update [--init[-default-path]] [--remote] [-N|--no-fetch] [-f|--force] [--checkout|--merge|--rebase] [--[no-]recommend-shallow] [--reference <repository>] [--recursive] [--] [<path>...]\n    or: $dashless [--quiet] summary [--cached|--files] [--summary-limit <n>] [commit] [--] [<path>...]\n    or: $dashless [--quiet] foreach [--recursive] <command>\n    or: $dashless [--quiet] sync [--recursive] [--] [<path>...]\"\n@@ -503,7 +503,12 @@ cmd_update()\n \t\t\tprogress=\"--progress\"\n \t\t\t;;\n \t\t-i|--init)\n-\t\t\tinit=1\n+\t\t\ttest -z $init || test $init = by_args || die \"$(gettext \"Only one of --init or --init-default-path may be used.\")\"\n+\t\t\tinit=by_args\n+\t\t\t;;\n+\t\t--init-default-path)\n+\t\t\ttest -z $init || test $init = by_config || die \"$(gettext \"Only one of --init or --init-default-path may be used.\")\"\n+\t\t\tinit=by_config\n \t\t\t;;\n \t\t--remote)\n \t\t\tremote=1\n@@ -572,7 +577,17 @@ cmd_update()\n \n \tif test -n \"$init\"\n \tthen\n-\t\tcmd_init \"--\" \"$@\" || return\n+\t\tif test \"$init\" = \"by_config\"\n+\t\tthen\n+\t\t\tif test $# -gt 0\n+\t\t\tthen\n+\t\t\t\tdie \"$(gettext \"path arguments are incompatible with --init-default-path\")\"\n+\t\t\tfi\n+\t\t\tcmd_init \"--\" $(git config --get-all submodule.defaultUpdatePath) || return\n+\t\telse\n+\t\t\tcmd_init \"--\" \"$@\" || return\n+\t\tfi\n+\n \tfi\n \n \t{\ndiff --git a/t/t7400-submodule-basic.sh b/t/t7400-submodule-basic.sh\nindex b77cce8..4699b1c 100755\n--- a/t/t7400-submodule-basic.sh\n+++ b/t/t7400-submodule-basic.sh\n@@ -1116,5 +1116,58 @@ test_expect_success 'submodule helper list is not confused by common prefixes' '\n \ttest_cmp expect actual\n '\n \n+test_expect_success 'setup superproject with submodules' '\n+\tmkdir sub1 &&\n+\t(\n+\t\tcd sub1 &&\n+\t\tgit init &&\n+\t\ttest_commit test &&\n+\t\ttest_commit test2\n+\t) &&\n+\tmkdir multisuper &&\n+\t(\n+\t\tcd multisuper &&\n+\t\tgit init &&\n+\t\tgit submodule add ../sub1 sub0 &&\n+\t\tgit submodule add ../sub1 sub1 &&\n+\t\tgit submodule add ../sub1 sub2 &&\n+\t\tgit submodule add ../sub1 sub3 &&\n+\t\tgit commit -m \"add some submodules\"\n+\t)\n+'\n+\n+cat >expect <<-EOF\n+-sub0\n+ sub1 (test2)\n+ sub2 (test2)\n+ sub3 (test2)\n+EOF\n+\n+test_expect_success 'submodule update --init with a specification' '\n+\ttest_when_finished \"rm -rf multisuper_clone\" &&\n+\tpwd=$(pwd) &&\n+\tgit clone file://\"$pwd\"/multisuper multisuper_clone &&\n+\t(\n+\t\tcd multisuper_clone &&\n+\t\tgit submodule update --init . \":(exclude)sub0\" &&\n+\t\tgit submodule status |cut -c 1,43- >../actual\n+\t) &&\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'submodule update --init-default-path' '\n+\ttest_when_finished \"rm -rf multisuper_clone\" &&\n+\tpwd=$(pwd) &&\n+\tgit clone file://\"$pwd\"/multisuper multisuper_clone &&\n+\t(\n+\t\tcd multisuper_clone &&\n+\t\tgit config submodule.defaultUpdatePath \".\" &&\n+\t\tgit config --add submodule.defaultUpdatePath \":(exclude)sub0\" &&\n+\t\tgit submodule update --init-default-path &&\n+\t\tgit submodule status |cut -c 1,43- >../actual &&\n+\t\ttest_must_fail git submodule update --init-default-path sub0\n+\t) &&\n+\ttest_cmp expect actual\n+'\n \n test_done\n-- \n2.10.1.508.g6572022\n\n"},{"id":"304719","messageId":"20161022233225.8883-34-sbeller@google.com","threadId":"44358","inReplyTo":"20161022233225.8883-1-sbeller@google.com","subject":"[PATCH 33/36] pathspec: allow escaped query values","fromName":"Stefan Beller","fromEmail":"sbeller@google.com","sentAt":"2016-10-22T23:32:22Z","receivedAt":"2016-10-22T23:34:08Z","isPatch":true,"sender":{"key":"stefanbeller@gmail.com","avatar":"https://avatars.githubusercontent.com/u/455868?v=4"},"body":"In our own .gitattributes file we have attributes such as:\n\n    *.[ch] whitespace=indent,trail,space\n\nWhen querying for attributes we want to be able to ask for the exact\nvalue, i.e.\n\n    git ls-files :(attr:whitespace=indent,trail,space)\n\nshould work, but the commas are used in the attr magic to introduce\nthe next attr, such that this query currently fails with\n\nfatal: Invalid pathspec magic 'trail' in ':(attr:whitespace=indent,trail,space)'\n\nThis change allows escaping characters by a backslash, such that the query\n\n    git ls-files :(attr:whitespace=indent\\,trail\\,space)\n\nwill match all path that have the value \"indent,trail,space\" for the\nwhitespace attribute. To accomplish this, we need to modify two places.\nFirst `eat_long_magic` needs to not stop early upon seeing a comma or\nclosing paren that is escaped. As a second step we need to remove any\nescaping from the attr value.\n\nHelped-by: Junio C Hamano <gitster@pobox.com>\nSigned-off-by: Stefan Beller <sbeller@google.com>\nSigned-off-by: Junio C Hamano <gitster@pobox.com>\n---\n pathspec.c                      | 53 +++++++++++++++++++++++++++++++++++++----\n t/t6134-pathspec-with-labels.sh | 10 ++++++++\n 2 files changed, 58 insertions(+), 5 deletions(-)\n\ndiff --git a/pathspec.c b/pathspec.c\nindex 0eee177..3832e03 100644\n--- a/pathspec.c\n+++ b/pathspec.c\n@@ -89,12 +89,56 @@ static void prefix_short_magic(struct strbuf *sb, int prefixlen,\n \tstrbuf_addf(sb, \",prefix:%d)\", prefixlen);\n }\n \n+static size_t strcspn_escaped(const char *s, const char *stop)\n+{\n+\tconst char *i;\n+\n+\tfor (i = s; *i; i++) {\n+\t\t/* skip the escaped character */\n+\t\tif (i[0] == '\\\\' && i[1]) {\n+\t\t\ti++;\n+\t\t\tcontinue;\n+\t\t}\n+\n+\t\tif (strchr(stop, *i))\n+\t\t\tbreak;\n+\t}\n+\treturn i - s;\n+}\n+\n+static inline int invalid_value_char(const char ch)\n+{\n+\tif (isalnum(ch) || strchr(\",-_\", ch))\n+\t\treturn 0;\n+\treturn -1;\n+}\n+\n+static char *attr_value_unescape(const char *value)\n+{\n+\tconst char *src;\n+\tchar *dst, *ret;\n+\n+\tret = xmallocz(strlen(value));\n+\tfor (src = value, dst = ret; *src; src++, dst++) {\n+\t\tif (*src == '\\\\') {\n+\t\t\tif (!src[1])\n+\t\t\t\tdie(_(\"Escape character '\\\\' not allowed as \"\n+\t\t\t\t      \"last character in attr value\"));\n+\t\t\tsrc++;\n+\t\t}\n+\t\tif (invalid_value_char(*src))\n+\t\t\tdie(\"cannot use '%c' for value matching\", *src);\n+\t\t*dst = *src;\n+\t}\n+\t*dst = '\\0';\n+\treturn ret;\n+}\n+\n static void parse_pathspec_attr_match(struct pathspec_item *item, const char *value)\n {\n \tstruct string_list_item *si;\n \tstruct string_list list = STRING_LIST_INIT_DUP;\n \n-\n \tif (!value || !strlen(value))\n \t\tdie(_(\"attr spec must not be empty\"));\n \n@@ -131,10 +175,9 @@ static void parse_pathspec_attr_match(struct pathspec_item *item, const char *va\n \t\t\tif (attr[attr_len] != '=')\n \t\t\t\tam->match_mode = MATCH_SET;\n \t\t\telse {\n+\t\t\t\tconst char *v = &attr[attr_len + 1];\n \t\t\t\tam->match_mode = MATCH_VALUE;\n-\t\t\t\tam->value = xstrdup(&attr[attr_len + 1]);\n-\t\t\t\tif (strchr(am->value, '\\\\'))\n-\t\t\t\t\tdie(_(\"attr spec values must not contain backslashes\"));\n+\t\t\t\tam->value = attr_value_unescape(v);\n \t\t\t}\n \t\t\tbreak;\n \t\t}\n@@ -166,7 +209,7 @@ static void eat_long_magic(struct pathspec_item *item, const char *elt,\n \tfor (copyfrom = elt + 2;\n \t     *copyfrom && *copyfrom != ')';\n \t     copyfrom = nextat) {\n-\t\tsize_t len = strcspn(copyfrom, \",)\");\n+\t\tsize_t len = strcspn_escaped(copyfrom, \",)\");\n \t\tif (copyfrom[len] == ',')\n \t\t\tnextat = copyfrom + len + 1;\n \t\telse\ndiff --git a/t/t6134-pathspec-with-labels.sh b/t/t6134-pathspec-with-labels.sh\nindex 1c9323c..f5f8413 100755\n--- a/t/t6134-pathspec-with-labels.sh\n+++ b/t/t6134-pathspec-with-labels.sh\n@@ -167,4 +167,14 @@ test_expect_success 'abort on asking for wrong magic' '\n \ttest_must_fail git ls-files . \":(attr:!label=foo)\"\n '\n \n+test_expect_success 'check attribute list' '\n+\tcat <<-EOF >>.gitattributes &&\n+\t* whitespace=indent,trail,space\n+\tEOF\n+\tcat .gitattributes &&\n+\tgit ls-files \":(attr:whitespace=indent\\,trail\\,space)\" >actual &&\n+\tgit ls-files >expect &&\n+\ttest_cmp expect actual\n+'\n+\n test_done\n-- \n2.10.1.508.g6572022\n\n"},{"id":"304720","messageId":"20161022233225.8883-33-sbeller@google.com","threadId":"44358","inReplyTo":"20161022233225.8883-1-sbeller@google.com","subject":"[PATCH 32/36] pathspec: allow querying for attributes","fromName":"Stefan Beller","fromEmail":"sbeller@google.com","sentAt":"2016-10-22T23:32:21Z","receivedAt":"2016-10-22T23:34:11Z","isPatch":true,"sender":{"key":"stefanbeller@gmail.com","avatar":"https://avatars.githubusercontent.com/u/455868?v=4"},"body":"The pathspec mechanism is extended via the new\n\":(attr:eol=input)pattern/to/match\" syntax to filter paths so that it\nrequires paths to not just match the given pattern but also have the\nspecified attrs attached for them to be chosen.\n\nSigned-off-by: Stefan Beller <sbeller@google.com>\nSigned-off-by: Junio C Hamano <gitster@pobox.com>\n---\n Documentation/glossary-content.txt |  20 +++++\n dir.c                              |  35 ++++++++\n pathspec.c                         | 103 +++++++++++++++++++++-\n pathspec.h                         |  16 ++++\n t/t6134-pathspec-with-labels.sh    | 170 +++++++++++++++++++++++++++++++++++++\n 5 files changed, 340 insertions(+), 4 deletions(-)\n create mode 100755 t/t6134-pathspec-with-labels.sh\n\ndiff --git a/Documentation/glossary-content.txt b/Documentation/glossary-content.txt\nindex 8ad29e6..f90bd45 100644\n--- a/Documentation/glossary-content.txt\n+++ b/Documentation/glossary-content.txt\n@@ -384,6 +384,26 @@ full pathname may have special meaning:\n +\n Glob magic is incompatible with literal magic.\n \n+attr;;\n+After `attr:` comes a space separated list of \"attribute\n+requirements\", all of which must be met in order for the\n+path to be considered a match; this is in addition to the\n+usual non-magic pathspec pattern matching.\n++\n+Each of the attribute requirements for the path takes one of\n+these forms:\n+\n+- \"`ATTR`\" requires that the attribute `ATTR` must be set.\n+\n+- \"`-ATTR`\" requires that the attribute `ATTR` must be unset.\n+\n+- \"`ATTR=VALUE`\" requires that the attribute `ATTR` must be\n+  set to the string `VALUE`.\n+\n+- \"`!ATTR`\" requires that the attribute `ATTR` must be\n+  unspecified.\n++\n+\n exclude;;\n \tAfter a path matches any non-exclude pathspec, it will be run\n \tthrough all exclude pathspec (magic signature: `!`). If it\ndiff --git a/dir.c b/dir.c\nindex 3bad1ad..3ec9117 100644\n--- a/dir.c\n+++ b/dir.c\n@@ -9,6 +9,7 @@\n  */\n #include \"cache.h\"\n #include \"dir.h\"\n+#include \"attr.h\"\n #include \"refs.h\"\n #include \"wildmatch.h\"\n #include \"pathspec.h\"\n@@ -207,6 +208,37 @@ int within_depth(const char *name, int namelen,\n \treturn 1;\n }\n \n+static int match_attrs(const char *name, int namelen,\n+\t\t       const struct pathspec_item *item)\n+{\n+\tint i;\n+\tstruct git_attr_result *res = git_attr_result_alloc(item->attr_check);\n+\n+\tgit_check_attr(name, item->attr_check, res);\n+\tfor (i = 0; i < item->attr_match_nr; i++) {\n+\t\tconst char *value;\n+\t\tint matched;\n+\t\tenum attr_match_mode match_mode;\n+\n+\t\tvalue = res[i].value;\n+\t\tmatch_mode = item->attr_match[i].match_mode;\n+\n+\t\tif (ATTR_TRUE(value))\n+\t\t\tmatched = (match_mode == MATCH_SET);\n+\t\telse if (ATTR_FALSE(value))\n+\t\t\tmatched = (match_mode == MATCH_UNSET);\n+\t\telse if (ATTR_UNSET(value))\n+\t\t\tmatched = (match_mode == MATCH_UNSPECIFIED);\n+\t\telse\n+\t\t\tmatched = (match_mode == MATCH_VALUE &&\n+\t\t\t\t   !strcmp(item->attr_match[i].value, value));\n+\t\tif (!matched)\n+\t\t\treturn 0;\n+\t}\n+\n+\treturn 1;\n+}\n+\n #define DO_MATCH_EXCLUDE   1\n #define DO_MATCH_DIRECTORY 2\n \n@@ -262,6 +294,9 @@ static int match_pathspec_item(const struct pathspec_item *item, int prefix,\n \t    strncmp(item->match, name - prefix, item->prefix))\n \t\treturn 0;\n \n+\tif (item->attr_match_nr && !match_attrs(name, namelen, item))\n+\t\treturn 0;\n+\n \t/* If the match was just the prefix, we matched */\n \tif (!*match)\n \t\treturn MATCHED_RECURSIVELY;\ndiff --git a/pathspec.c b/pathspec.c\nindex d44f8e7..0eee177 100644\n--- a/pathspec.c\n+++ b/pathspec.c\n@@ -1,6 +1,7 @@\n #include \"cache.h\"\n #include \"dir.h\"\n #include \"pathspec.h\"\n+#include \"attr.h\"\n \n /*\n  * Finds which of the given pathspecs match items in the index.\n@@ -88,12 +89,78 @@ static void prefix_short_magic(struct strbuf *sb, int prefixlen,\n \tstrbuf_addf(sb, \",prefix:%d)\", prefixlen);\n }\n \n+static void parse_pathspec_attr_match(struct pathspec_item *item, const char *value)\n+{\n+\tstruct string_list_item *si;\n+\tstruct string_list list = STRING_LIST_INIT_DUP;\n+\n+\n+\tif (!value || !strlen(value))\n+\t\tdie(_(\"attr spec must not be empty\"));\n+\n+\tstring_list_split(&list, value, ' ', -1);\n+\tstring_list_remove_empty_items(&list, 0);\n+\n+\tif (!item->attr_check)\n+\t\tgit_attr_check_alloc(&item->attr_check);\n+\telse\n+\t\tdie(_(\"Only one 'attr:' specification is allowed.\"));\n+\n+\tALLOC_GROW(item->attr_match, item->attr_match_nr + list.nr, item->attr_match_alloc);\n+\n+\tfor_each_string_list_item(si, &list) {\n+\t\tsize_t attr_len;\n+\n+\t\tint j = item->attr_match_nr++;\n+\t\tconst char *attr = si->string;\n+\t\tstruct attr_match *am = &item->attr_match[j];\n+\n+\t\tswitch (*attr) {\n+\t\tcase '!':\n+\t\t\tam->match_mode = MATCH_UNSPECIFIED;\n+\t\t\tattr++;\n+\t\t\tattr_len = strlen(attr);\n+\t\t\tbreak;\n+\t\tcase '-':\n+\t\t\tam->match_mode = MATCH_UNSET;\n+\t\t\tattr++;\n+\t\t\tattr_len = strlen(attr);\n+\t\t\tbreak;\n+\t\tdefault:\n+\t\t\tattr_len = strcspn(attr, \"=\");\n+\t\t\tif (attr[attr_len] != '=')\n+\t\t\t\tam->match_mode = MATCH_SET;\n+\t\t\telse {\n+\t\t\t\tam->match_mode = MATCH_VALUE;\n+\t\t\t\tam->value = xstrdup(&attr[attr_len + 1]);\n+\t\t\t\tif (strchr(am->value, '\\\\'))\n+\t\t\t\t\tdie(_(\"attr spec values must not contain backslashes\"));\n+\t\t\t}\n+\t\t\tbreak;\n+\t\t}\n+\n+\t\tam->attr = git_attr_counted(attr, attr_len);\n+\t\tif (!am->attr) {\n+\t\t\tstruct strbuf sb = STRBUF_INIT;\n+\t\t\tam->match_mode = INVALID_ATTR;\n+\t\t\tinvalid_attr_name_message(&sb, attr, attr_len);\n+\t\t\tdie(_(\"invalid attribute in '%s': '%s'\"), value, sb.buf);\n+\t\t}\n+\n+\t\tgit_attr_check_append(item->attr_check, am->attr);\n+\t}\n+\n+\tstring_list_clear(&list, 0);\n+\treturn;\n+}\n+\n static void eat_long_magic(struct pathspec_item *item, const char *elt,\n \t\tunsigned *magic, int *pathspec_prefix,\n \t\tconst char **copyfrom_, const char **long_magic_end)\n {\n \tint i;\n \tconst char *copyfrom = *copyfrom_;\n+\tconst char *body;\n \t/* longhand */\n \tconst char *nextat;\n \tfor (copyfrom = elt + 2;\n@@ -108,15 +175,21 @@ static void eat_long_magic(struct pathspec_item *item, const char *elt,\n \t\tif (!len)\n \t\t\tcontinue;\n \n-\t\tif (starts_with(copyfrom, \"prefix:\")) {\n+\t\tif (skip_prefix(copyfrom, \"prefix:\", &body)) {\n \t\t\tchar *endptr;\n-\t\t\t*pathspec_prefix = strtol(copyfrom + 7,\n-\t\t\t\t\t\t  &endptr, 10);\n+\t\t\t*pathspec_prefix = strtol(body, &endptr, 10);\n \t\t\tif (endptr - copyfrom != len)\n \t\t\t\tdie(_(\"invalid parameter for pathspec magic 'prefix'\"));\n \t\t\tcontinue;\n \t\t}\n \n+\t\tif (skip_prefix(copyfrom, \"attr:\", &body)) {\n+\t\t\tchar *attr_body = xmemdupz(body, len - strlen(\"attr:\"));\n+\t\t\tparse_pathspec_attr_match(item, attr_body);\n+\t\t\tfree(attr_body);\n+\t\t\tcontinue;\n+\t\t}\n+\n \t\tfor (i = 0; i < ARRAY_SIZE(pathspec_magic); i++) {\n \t\t\tif (strlen(pathspec_magic[i].name) == len &&\n \t\t\t    !strncmp(pathspec_magic[i].name, copyfrom, len)) {\n@@ -425,7 +498,10 @@ void parse_pathspec(struct pathspec *pathspec,\n \tfor (i = 0; i < n; i++) {\n \t\tunsigned short_magic;\n \t\tentry = argv[i];\n-\n+\t\titem[i].attr_check = NULL;\n+\t\titem[i].attr_match = NULL;\n+\t\titem[i].attr_match_nr = 0;\n+\t\titem[i].attr_match_alloc = 0;\n \t\titem[i].magic = prefix_pathspec(item + i, &short_magic,\n \t\t\t\t\t\targv + i, flags,\n \t\t\t\t\t\tprefix, prefixlen, entry);\n@@ -447,6 +523,13 @@ void parse_pathspec(struct pathspec *pathspec,\n \t\tif (item[i].nowildcard_len < item[i].len)\n \t\t\tpathspec->has_wildcard = 1;\n \t\tpathspec->magic |= item[i].magic;\n+\n+\t\tif (item[i].attr_match_nr) {\n+\t\t\tint j;\n+\t\t\tfor (j = 0; j < item[i].attr_match_nr; j++)\n+\t\t\t\tif (item[i].attr_match[j].match_mode == INVALID_ATTR)\n+\t\t\t\t\tdie(_(\"attribute spec in the wrong syntax are prohibited.\"));\n+\t\t}\n \t}\n \n \tif (nr_exclude == n)\n@@ -500,6 +583,18 @@ void copy_pathspec(struct pathspec *dst, const struct pathspec *src)\n \n void clear_pathspec(struct pathspec *pathspec)\n {\n+\tint i, j;\n+\tfor (i = 0; i < pathspec->nr; i++) {\n+\t\tif (!pathspec->items[i].attr_match_nr)\n+\t\t\tcontinue;\n+\t\tfor (j = 0; j < pathspec->items[j].attr_match_nr; j++)\n+\t\t\tfree(pathspec->items[i].attr_match[j].value);\n+\t\tfree(pathspec->items[i].attr_match);\n+\t\tif (pathspec->items[i].attr_check)\n+\t\t\tgit_attr_check_clear(pathspec->items[i].attr_check);\n+\t\tfree(pathspec->items[i].attr_check);\n+\t}\n+\n \tfree(pathspec->items);\n \tpathspec->items = NULL;\n }\ndiff --git a/pathspec.h b/pathspec.h\nindex 59809e4..aebe6ea 100644\n--- a/pathspec.h\n+++ b/pathspec.h\n@@ -32,6 +32,22 @@ struct pathspec {\n \t\tint len, prefix;\n \t\tint nowildcard_len;\n \t\tint flags;\n+\t\tint attr_match_nr;\n+\t\tint attr_match_alloc;\n+\t\tstruct attr_match {\n+\t\t\tstruct git_attr *attr;\n+\t\t\tchar *value;\n+\t\t\tenum attr_match_mode {\n+\t\t\t\tMATCH_SET,\n+\t\t\t\tMATCH_UNSET,\n+\t\t\t\tMATCH_VALUE,\n+\t\t\t\tMATCH_UNSPECIFIED,\n+\t\t\t\tMATCH_NOT_UNSPECIFIED,\n+\t\t\t\tMATCH_SET_OR_VALUE,\n+\t\t\t\tINVALID_ATTR\n+\t\t\t} match_mode;\n+\t\t} *attr_match;\n+\t\tstruct git_attr_check *attr_check;\n \t} *items;\n };\n \ndiff --git a/t/t6134-pathspec-with-labels.sh b/t/t6134-pathspec-with-labels.sh\nnew file mode 100755\nindex 0000000..1c9323c\n--- /dev/null\n+++ b/t/t6134-pathspec-with-labels.sh\n@@ -0,0 +1,170 @@\n+#!/bin/sh\n+\n+test_description='test labels in pathspecs'\n+. ./test-lib.sh\n+\n+test_expect_success 'setup a tree' '\n+\tcat <<-EOF >expect &&\n+\tfileA\n+\tfileAB\n+\tfileAC\n+\tfileB\n+\tfileBC\n+\tfileC\n+\tfileNoLabel\n+\tfileSetLabel\n+\tfileUnsetLabel\n+\tfileValue\n+\tfileWrongLabel\n+\tsub/fileA\n+\tsub/fileAB\n+\tsub/fileAC\n+\tsub/fileB\n+\tsub/fileBC\n+\tsub/fileC\n+\tsub/fileNoLabel\n+\tsub/fileSetLabel\n+\tsub/fileUnsetLabel\n+\tsub/fileValue\n+\tsub/fileWrongLabel\n+\tEOF\n+\tmkdir sub &&\n+\twhile read path\n+\tdo\n+\t\t: >$path &&\n+\t\tgit add $path || return 1\n+\tdone <expect &&\n+\tgit commit -m \"initial commit\" &&\n+\tgit ls-files >actual &&\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'pathspec with no attr' '\n+\ttest_must_fail git ls-files \":(attr:)\"\n+'\n+\n+test_expect_success 'pathspec with labels and non existent .gitattributes' '\n+\tgit ls-files \":(attr:label)\" >actual &&\n+\ttest_must_be_empty actual\n+'\n+\n+test_expect_success 'setup .gitattributes' '\n+\tcat <<-EOF >.gitattributes &&\n+\tfileA labelA\n+\tfileB labelB\n+\tfileC labelC\n+\tfileAB labelA labelB\n+\tfileAC labelA labelC\n+\tfileBC labelB labelC\n+\tfileUnsetLabel -label\n+\tfileSetLabel label\n+\tfileValue label=foo\n+\tfileWrongLabel label☺\n+\tEOF\n+\tgit add .gitattributes &&\n+\tgit commit -m \"add attributes\"\n+'\n+\n+test_expect_success 'check specific set attr' '\n+\tcat <<-EOF >expect &&\n+\tfileSetLabel\n+\tsub/fileSetLabel\n+\tEOF\n+\tgit ls-files \":(attr:label)\" >actual &&\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'check specific unset attr' '\n+\tcat <<-EOF >expect &&\n+\tfileUnsetLabel\n+\tsub/fileUnsetLabel\n+\tEOF\n+\tgit ls-files \":(attr:-label)\" >actual &&\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'check specific value attr' '\n+\tcat <<-EOF >expect &&\n+\tfileValue\n+\tsub/fileValue\n+\tEOF\n+\tgit ls-files \":(attr:label=foo)\" >actual &&\n+\ttest_cmp expect actual &&\n+\tgit ls-files \":(attr:label=bar)\" >actual &&\n+\ttest_must_be_empty actual\n+'\n+\n+test_expect_success 'check unspecified attr' '\n+\tcat <<-EOF >expect &&\n+\t.gitattributes\n+\tfileA\n+\tfileAB\n+\tfileAC\n+\tfileB\n+\tfileBC\n+\tfileC\n+\tfileNoLabel\n+\tfileWrongLabel\n+\tsub/fileA\n+\tsub/fileAB\n+\tsub/fileAC\n+\tsub/fileB\n+\tsub/fileBC\n+\tsub/fileC\n+\tsub/fileNoLabel\n+\tsub/fileWrongLabel\n+\tEOF\n+\tgit ls-files \":(attr:!label)\" >actual &&\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'check multiple unspecified attr' '\n+\tcat <<-EOF >expect &&\n+\t.gitattributes\n+\tfileC\n+\tfileNoLabel\n+\tfileWrongLabel\n+\tsub/fileC\n+\tsub/fileNoLabel\n+\tsub/fileWrongLabel\n+\tEOF\n+\tgit ls-files \":(attr:!labelB !labelA !label)\" >actual &&\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'check label with more labels but excluded path' '\n+\tcat <<-EOF >expect &&\n+\tfileAB\n+\tfileB\n+\tfileBC\n+\tEOF\n+\tgit ls-files \":(attr:labelB)\" \":(exclude)sub/\" >actual &&\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'check label excluding other labels' '\n+\tcat <<-EOF >expect &&\n+\tfileAB\n+\tfileB\n+\tfileBC\n+\tsub/fileAB\n+\tsub/fileB\n+\tEOF\n+\tgit ls-files \":(attr:labelB)\" \":(exclude,attr:labelC)sub/\" >actual &&\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'checking attributes in a multithreaded process' '\n+\tgit status \":(attr:labelB)\"\n+'\n+\n+test_expect_success 'abort on giving invalid label on the command line' '\n+\ttest_must_fail git ls-files . \":(attr:☺)\"\n+'\n+\n+test_expect_success 'abort on asking for wrong magic' '\n+\ttest_must_fail git ls-files . \":(attr:-label=foo)\" &&\n+\ttest_must_fail git ls-files . \":(attr:!label=foo)\"\n+'\n+\n+test_done\n-- \n2.10.1.508.g6572022\n\n"},{"id":"304721","messageId":"20161022233225.8883-14-sbeller@google.com","threadId":"44358","inReplyTo":"20161022233225.8883-1-sbeller@google.com","subject":"[PATCH 13/36] attr: convert git_check_attrs() callers to use the new API","fromName":"Stefan Beller","fromEmail":"sbeller@google.com","sentAt":"2016-10-22T23:32:02Z","receivedAt":"2016-10-22T23:34:14Z","isPatch":true,"sender":{"key":"stefanbeller@gmail.com","avatar":"https://avatars.githubusercontent.com/u/455868?v=4"},"body":"From: Junio C Hamano <gitster@pobox.com>\n\nThe remaining callers are all simple \"I have N attributes I am\ninterested in.  I'll ask about them with various paths one by one\".\n\nAfter this step, no caller to git_check_attrs() remains.  After\nremoving it, we can extend \"struct git_attr_check\" struct with data\nthat can be used in optimizing the query for the specific N\nattributes it contains.\n\nSigned-off-by: Junio C Hamano <gitster@pobox.com>\nSigned-off-by: Stefan Beller <sbeller@google.com>\n---\n builtin/pack-objects.c | 19 +++++--------------\n convert.c              | 18 +++++++-----------\n ll-merge.c             | 33 ++++++++++++++-------------------\n userdiff.c             | 19 ++++++++-----------\n ws.c                   | 19 ++++++-------------\n 5 files changed, 40 insertions(+), 68 deletions(-)\n\ndiff --git a/builtin/pack-objects.c b/builtin/pack-objects.c\nindex 3cb38ed..3918c07 100644\n--- a/builtin/pack-objects.c\n+++ b/builtin/pack-objects.c\n@@ -896,24 +896,15 @@ static void write_pack_file(void)\n \t\t\twritten, nr_result);\n }\n \n-static void setup_delta_attr_check(struct git_attr_check_elem *check)\n-{\n-\tstatic struct git_attr *attr_delta;\n-\n-\tif (!attr_delta)\n-\t\tattr_delta = git_attr(\"delta\");\n-\n-\tcheck[0].attr = attr_delta;\n-}\n-\n static int no_try_delta(const char *path)\n {\n-\tstruct git_attr_check_elem check[1];\n+\tstatic struct git_attr_check *check;\n \n-\tsetup_delta_attr_check(check);\n-\tif (git_check_attrs(path, ARRAY_SIZE(check), check))\n+\tif (!check)\n+\t\tcheck = git_attr_check_initl(\"delta\", NULL);\n+\tif (git_check_attr(path, check))\n \t\treturn 0;\n-\tif (ATTR_FALSE(check->value))\n+\tif (ATTR_FALSE(check->check[0].value))\n \t\treturn 1;\n \treturn 0;\n }\ndiff --git a/convert.c b/convert.c\nindex c95ae71..bb2435a 100644\n--- a/convert.c\n+++ b/convert.c\n@@ -775,24 +775,20 @@ struct conv_attrs {\n \tint ident;\n };\n \n-static const char *conv_attr_name[] = {\n-\t\"crlf\", \"ident\", \"filter\", \"eol\", \"text\",\n-};\n-#define NUM_CONV_ATTRS ARRAY_SIZE(conv_attr_name)\n-\n static void convert_attrs(struct conv_attrs *ca, const char *path)\n {\n-\tint i;\n-\tstatic struct git_attr_check_elem ccheck[NUM_CONV_ATTRS];\n+\tstatic struct git_attr_check *check;\n \n-\tif (!ccheck[0].attr) {\n-\t\tfor (i = 0; i < NUM_CONV_ATTRS; i++)\n-\t\t\tccheck[i].attr = git_attr(conv_attr_name[i]);\n+\tif (!check) {\n+\t\tcheck = git_attr_check_initl(\"crlf\", \"ident\",\n+\t\t\t\t\t     \"filter\", \"eol\", \"text\",\n+\t\t\t\t\t     NULL);\n \t\tuser_convert_tail = &user_convert;\n \t\tgit_config(read_convert_config, NULL);\n \t}\n \n-\tif (!git_check_attrs(path, NUM_CONV_ATTRS, ccheck)) {\n+\tif (!git_check_attr(path, check)) {\n+\t\tstruct git_attr_check_elem *ccheck = check->check;\n \t\tca->crlf_action = git_path_check_crlf(ccheck + 4);\n \t\tif (ca->crlf_action == CRLF_UNDEFINED)\n \t\t\tca->crlf_action = git_path_check_crlf(ccheck + 0);\ndiff --git a/ll-merge.c b/ll-merge.c\nindex eb2c37e..bc6479c 100644\n--- a/ll-merge.c\n+++ b/ll-merge.c\n@@ -336,15 +336,6 @@ static const struct ll_merge_driver *find_ll_merge_driver(const char *merge_attr\n \treturn &ll_merge_drv[LL_TEXT_MERGE];\n }\n \n-static int git_path_check_merge(const char *path, struct git_attr_check_elem check[2])\n-{\n-\tif (!check[0].attr) {\n-\t\tcheck[0].attr = git_attr(\"merge\");\n-\t\tcheck[1].attr = git_attr(\"conflict-marker-size\");\n-\t}\n-\treturn git_check_attrs(path, 2, check);\n-}\n-\n static void normalize_file(mmfile_t *mm, const char *path)\n {\n \tstruct strbuf strbuf = STRBUF_INIT;\n@@ -362,7 +353,7 @@ int ll_merge(mmbuffer_t *result_buf,\n \t     mmfile_t *theirs, const char *their_label,\n \t     const struct ll_merge_options *opts)\n {\n-\tstatic struct git_attr_check_elem check[2];\n+\tstatic struct git_attr_check *check;\n \tstatic const struct ll_merge_options default_opts;\n \tconst char *ll_driver_name = NULL;\n \tint marker_size = DEFAULT_CONFLICT_MARKER_SIZE;\n@@ -376,10 +367,14 @@ int ll_merge(mmbuffer_t *result_buf,\n \t\tnormalize_file(ours, path);\n \t\tnormalize_file(theirs, path);\n \t}\n-\tif (!git_path_check_merge(path, check)) {\n-\t\tll_driver_name = check[0].value;\n-\t\tif (check[1].value) {\n-\t\t\tmarker_size = atoi(check[1].value);\n+\n+\tif (!check)\n+\t\tcheck = git_attr_check_initl(\"merge\", \"conflict-marker-size\", NULL);\n+\n+\tif (!git_check_attr(path, check)) {\n+\t\tll_driver_name = check->check[0].value;\n+\t\tif (check->check[1].value) {\n+\t\t\tmarker_size = atoi(check->check[1].value);\n \t\t\tif (marker_size <= 0)\n \t\t\t\tmarker_size = DEFAULT_CONFLICT_MARKER_SIZE;\n \t\t}\n@@ -398,13 +393,13 @@ int ll_merge(mmbuffer_t *result_buf,\n \n int ll_merge_marker_size(const char *path)\n {\n-\tstatic struct git_attr_check_elem check;\n+\tstatic struct git_attr_check *check;\n \tint marker_size = DEFAULT_CONFLICT_MARKER_SIZE;\n \n-\tif (!check.attr)\n-\t\tcheck.attr = git_attr(\"conflict-marker-size\");\n-\tif (!git_check_attrs(path, 1, &check) && check.value) {\n-\t\tmarker_size = atoi(check.value);\n+\tif (!check)\n+\t\tcheck = git_attr_check_initl(\"conflict-marker-size\", NULL);\n+\tif (!git_check_attr(path, check) && check->check[0].value) {\n+\t\tmarker_size = atoi(check->check[0].value);\n \t\tif (marker_size <= 0)\n \t\t\tmarker_size = DEFAULT_CONFLICT_MARKER_SIZE;\n \t}\ndiff --git a/userdiff.c b/userdiff.c\nindex 4de3289..46dfd32 100644\n--- a/userdiff.c\n+++ b/userdiff.c\n@@ -262,25 +262,22 @@ struct userdiff_driver *userdiff_find_by_name(const char *name) {\n \n struct userdiff_driver *userdiff_find_by_path(const char *path)\n {\n-\tstatic struct git_attr *attr;\n-\tstruct git_attr_check_elem check;\n-\n-\tif (!attr)\n-\t\tattr = git_attr(\"diff\");\n-\tcheck.attr = attr;\n+\tstatic struct git_attr_check *check;\n \n+\tif (!check)\n+\t\tcheck = git_attr_check_initl(\"diff\", NULL);\n \tif (!path)\n \t\treturn NULL;\n-\tif (git_check_attrs(path, 1, &check))\n+\tif (git_check_attr(path, check))\n \t\treturn NULL;\n \n-\tif (ATTR_TRUE(check.value))\n+\tif (ATTR_TRUE(check->check[0].value))\n \t\treturn &driver_true;\n-\tif (ATTR_FALSE(check.value))\n+\tif (ATTR_FALSE(check->check[0].value))\n \t\treturn &driver_false;\n-\tif (ATTR_UNSET(check.value))\n+\tif (ATTR_UNSET(check->check[0].value))\n \t\treturn NULL;\n-\treturn userdiff_find_by_name(check.value);\n+\treturn userdiff_find_by_name(check->check[0].value);\n }\n \n struct userdiff_driver *userdiff_get_textconv(struct userdiff_driver *driver)\ndiff --git a/ws.c b/ws.c\nindex 7350905..bb3270c 100644\n--- a/ws.c\n+++ b/ws.c\n@@ -71,24 +71,17 @@ unsigned parse_whitespace_rule(const char *string)\n \treturn rule;\n }\n \n-static void setup_whitespace_attr_check(struct git_attr_check_elem *check)\n-{\n-\tstatic struct git_attr *attr_whitespace;\n-\n-\tif (!attr_whitespace)\n-\t\tattr_whitespace = git_attr(\"whitespace\");\n-\tcheck[0].attr = attr_whitespace;\n-}\n-\n unsigned whitespace_rule(const char *pathname)\n {\n-\tstruct git_attr_check_elem attr_whitespace_rule;\n+\tstatic struct git_attr_check *attr_whitespace_rule;\n+\n+\tif (!attr_whitespace_rule)\n+\t\tattr_whitespace_rule = git_attr_check_initl(\"whitespace\", NULL);\n \n-\tsetup_whitespace_attr_check(&attr_whitespace_rule);\n-\tif (!git_check_attrs(pathname, 1, &attr_whitespace_rule)) {\n+\tif (!git_check_attr(pathname, attr_whitespace_rule)) {\n \t\tconst char *value;\n \n-\t\tvalue = attr_whitespace_rule.value;\n+\t\tvalue = attr_whitespace_rule->check[0].value;\n \t\tif (ATTR_TRUE(value)) {\n \t\t\t/* true (whitespace) */\n \t\t\tunsigned all_rule = ws_tab_width(whitespace_rule_cfg);\n-- \n2.10.1.508.g6572022\n\n"},{"id":"304760","messageId":"0425fea3-3419-c265-b964-f5a309b867fa@ramsayjones.plus.com","threadId":"44358","inReplyTo":"20161022233225.8883-18-sbeller@google.com","subject":"Re: [PATCH 17/36] attr: expose validity check for attribute names","fromName":"Ramsay Jones","fromEmail":"ramsay@ramsayjones.plus.com","sentAt":"2016-10-23T15:07:41Z","receivedAt":"2016-10-23T15:07:52Z","isPatch":true,"sender":{"key":"ramsay@ramsayjones.plus.com","avatar":"https://avatars.githubusercontent.com/u/33702710?v=4"},"body":"\n\nOn 23/10/16 00:32, Stefan Beller wrote:\n> From: Junio C Hamano <gitster@pobox.com>\n> \n> Export attr_name_valid() function, and a helper function that\n> returns the message to be given when a given <name, len> pair\n> is not a good name for an attribute.\n> \n> We could later update the message to exactly spell out what the\n> rules for a good attribute name are, etc.\n> \n> Signed-off-by: Junio C Hamano <gitster@pobox.com>\n> Signed-off-by: Stefan Beller <sbeller@google.com>\n> ---\n\n[snip]\n\n> +extern int attr_name_valid(const char *name, size_t namelen);\n> +extern void invalid_attr_name_message(struct strbuf *, const char *, int);\n> +\n\nThe symbol 'attr_name_valid()' is not used outside of attr.c, even\nby the end of this series. Do you expect this function to be used\nin any future series? (The export is deliberate and it certainly\nseems like it should be part of the public interface, but ...)\n\nIn contrast, the 'invalid_attr_name_message()' function is called\nfrom code in pathspec.c, which relies on 'git_attr_counted()' to\ncall 'attr_name_valid()' internally to check for validity. :-D\n\nATB,\nRamsay Jones\n\n\n\n"},{"id":"304761","messageId":"8a91d287-5413-3f8f-9d0e-edd67fb36557@ramsayjones.plus.com","threadId":"44358","inReplyTo":"20161022233225.8883-29-sbeller@google.com","subject":"Re: [PATCH 28/36] attr: keep attr stack for each check","fromName":"Ramsay Jones","fromEmail":"ramsay@ramsayjones.plus.com","sentAt":"2016-10-23T15:10:38Z","receivedAt":"2016-10-23T15:10:45Z","isPatch":true,"sender":{"key":"ramsay@ramsayjones.plus.com","avatar":"https://avatars.githubusercontent.com/u/33702710?v=4"},"body":"\n\nOn 23/10/16 00:32, Stefan Beller wrote:\n> Instead of having a global attr stack, attach the stack to each check.\n> This allows to use the attr in a multithreaded way.\n> \n> \n> \n> Signed-off-by: Stefan Beller <sbeller@google.com>\n> ---\n>  attr.c    | 101 +++++++++++++++++++++++++++++++++++++++-----------------------\n>  attr.h    |   4 ++-\n>  hashmap.h |   2 ++\n>  3 files changed, 69 insertions(+), 38 deletions(-)\n> \n> diff --git a/attr.c b/attr.c\n> index 89ae155..b65437d 100644\n> --- a/attr.c\n> +++ b/attr.c\n> @@ -372,15 +372,17 @@ static struct match_attr *parse_attr_line(const char *line, const char *src,\n>   * .gitignore file and info/excludes file as a fallback.\n>   */\n>  \n> -/* NEEDSWORK: This will become per git_attr_check */\n> -static struct attr_stack {\n> +struct attr_stack {\n>  \tstruct attr_stack *prev;\n>  \tchar *origin;\n>  \tsize_t originlen;\n>  \tunsigned num_matches;\n>  \tunsigned alloc;\n>  \tstruct match_attr **attrs;\n> -} *attr_stack;\n> +};\n> +\n> +struct hashmap all_attr_stacks;\n> +int all_attr_stacks_init;\n\nMark symbols 'all_attr_stacks' and 'all_attr_stacks_init' with\nthe static keyword. (ie. these are file-local symbols).\n\nATB,\nRamsay Jones\n\n"},{"id":"304791","messageId":"xmqqr37560gv.fsf@gitster.mtv.corp.google.com","threadId":"44358","inReplyTo":"20161022233225.8883-28-sbeller@google.com","subject":"Re: [PATCH 27/36] attr: convert to new threadsafe API","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2016-10-24T18:55:28Z","receivedAt":"2016-10-24T18:55:37Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Stefan Beller <sbeller@google.com> writes:\n\n> This revamps the API of the attr subsystem to be thread safe.\n> Before we had the question and its results in one struct type.\n> The typical usage of the API was\n>\n>     static struct git_attr_check *check;\n>\n>     if (!check)\n>         check = git_attr_check_initl(\"text\", NULL);\n>\n>     git_check_attr(path, check);\n>     act_on(check->value[0]);\n>\n> This has a couple of issues when it comes to thread safety:\n>\n> * the initialization is racy in this implementation. To make it\n>   thread safe, we need to acquire a mutex, such that only one\n>   thread is executing the code in git_attr_check_initl.\n>   As we do not want to introduce a mutex at each call site,\n>   this is best done in the attr code. However to do so, we need\n>   to have access to the `check` variable, i.e. the API has to\n>   look like\n>     git_attr_check_initl(struct git_attr_check*, ...);\n\nMake that a double-asterisk.  The same problem appears in an updated\nexample in technical/api-gitattributes.txt doc, but the example in\nthe commit log message (below) is correct.\n\n> The usage of the new API will be:\n>\n>     /*\n>      * The initl call will thread-safely check whether the\n>      * struct git_attr_check has been initialized. We only\n>      * want to do the initialization work once, hence we do\n>      * that work inside a thread safe environment.\n>      */\n>     static struct git_attr_check *check;\n>     git_attr_check_initl(&check, \"text\", NULL);\n>\n>     /*\n>      * Obtain a pointer to a correctly sized result\n>      * statically allocated on the stack; this macro:\n>      */\n>     GIT_ATTR_RESULT_INIT_FOR(myresult, 1);\n\nAre you sure about this?  We've called attr_check_initl() already so\nif this is declaring myresult, it would be decl-after-stmt.\n\n>     /* Perform the check and act on it: */\n>     git_check_attr(path, check, myresult);\n>     act_on(myresult->value[0]);\n>\n>     /*\n>      * No need to free the check as it is static, hence doesn't leak\n>      * memory. The result is also static, so no need to free there either.\n>      */\n\nThe latter half is questionable.  If it is \"static\" it wouldn't be\nthread safe, no?  I think the diff in this patch for archive.c shows\nthat we only expect\n\n\tstruct git_attr_result result[2];\n\nupfront without RESULT_INIT_FOR(), and the reason why there is no\nneed to free the result[] is because it is on the stack.  And each\nelement in result[] may point at a string, but the string belongs to\nthe attr subsystem and must not be freed.\n\n"},{"id":"304793","messageId":"xmqqmvht5zwx.fsf@gitster.mtv.corp.google.com","threadId":"44358","inReplyTo":"20161022233225.8883-29-sbeller@google.com","subject":"Re: [PATCH 28/36] attr: keep attr stack for each check","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2016-10-24T19:07:26Z","receivedAt":"2016-10-24T19:10:39Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Stefan Beller <sbeller@google.com> writes:\n\n> Instead of having a global attr stack, attach the stack to each check.\n\nTwo threads may be working on \"git checkout\", one \"git_attr_check\"\nin convert.c may be used by them to learn the EOL conversion for\neach path, and these threads are working in different parts of\nworktree in parallel.  The set of .gitattributes files each of these\nthreads wants to be cached at one time is tied to where in the\ndirectory hierarchy the thread is working in.\n\nThe view by API users would not have to change from the point on\nsince 27/36 or so, but I think attr_stack needs to become per\n<check, thread> tuple when we are fully thread-ready for the above\nreason.\n\nBut we need to start somewhere to move away from the current \"one\nsingle attr stack\" to \"there are multiple attr stacks\", and this\n\"two checks may and do use different attr stacks\" is probably a\nreasonable first step.  It may give a single-threaded API users\nimmediate benefit if the \"read and keep only the entries relevant\nto the query\" optimization is done with this step alone, without\nmaking the cache per <check, thread> pair.\n\n> This allows to use the attr in a multithreaded way.\n\nWith manipulation of attr stack protected with a single Big\nAttributes Lock, this should be safe.  It may not perform very well\nwhen used by multiple threads, though ;-)\n\n"},{"id":"304794","messageId":"CAGZ79kb7PaqnyXZ7u0z8Q__ahTLKX8RQwV=dw7vbD4C9LnjOtw@mail.gmail.com","threadId":"44358","inReplyTo":"xmqqr37560gv.fsf@gitster.mtv.corp.google.com","subject":"Re: [PATCH 27/36] attr: convert to new threadsafe API","fromName":"Stefan Beller","fromEmail":"sbeller@google.com","sentAt":"2016-10-24T19:18:04Z","receivedAt":"2016-10-24T19:18:24Z","isPatch":true,"sender":{"key":"stefanbeller@gmail.com","avatar":"https://avatars.githubusercontent.com/u/455868?v=4"},"body":"On Mon, Oct 24, 2016 at 11:55 AM, Junio C Hamano <gitster@pobox.com> wrote:\n\n>\n> Make that a double-asterisk.  The same problem appears in an updated\n> example in technical/api-gitattributes.txt doc, but the example in\n> the commit log message (below) is correct.\n\nThe implementation is actually using a double pointer, see below,\nI forgot commit message and documentation\n\n>>     GIT_ATTR_RESULT_INIT_FOR(myresult, 1);\n>\n> Are you sure about this?  We've called attr_check_initl() already so\n> if this is declaring myresult, it would be decl-after-stmt.\n\nI forgot to update the commit message and Documentation.\nGIT_ATTR_RESULT_INIT_FOR is gone in the header\nand in the implementation.  I'll update that patch\nto be consistent throughout all of {Documentation,\ncommit message, implementation}.\n\n>\n> The latter half is questionable.  If it is \"static\" it wouldn't be\n> thread safe, no?  I think the diff in this patch for archive.c shows\n> that we only expect\n>\n>         struct git_attr_result result[2];\n>\n> upfront without RESULT_INIT_FOR(), and the reason why there is no\n> need to free the result[] is because it is on the stack.  And each\n> element in result[] may point at a string, but the string belongs to\n> the attr subsystem and must not be freed.\n>\n\nSame as above, it's bogus.\n\nThanks,\nStefan\n"},{"id":"304796","messageId":"CAGZ79kZ_7eG4uCXwyg0F=-hjmuT1dCAATSRnY293qkNC6siM5Q@mail.gmail.com","threadId":"44358","inReplyTo":"xmqqmvht5zwx.fsf@gitster.mtv.corp.google.com","subject":"Re: [PATCH 28/36] attr: keep attr stack for each check","fromName":"Stefan Beller","fromEmail":"sbeller@google.com","sentAt":"2016-10-24T19:32:32Z","receivedAt":"2016-10-24T19:32:40Z","isPatch":true,"sender":{"key":"stefanbeller@gmail.com","avatar":"https://avatars.githubusercontent.com/u/455868?v=4"},"body":"On Mon, Oct 24, 2016 at 12:07 PM, Junio C Hamano <gitster@pobox.com> wrote:\n> Stefan Beller <sbeller@google.com> writes:\n>\n>> Instead of having a global attr stack, attach the stack to each check.\n>\n> Two threads may be working on \"git checkout\", one \"git_attr_check\"\n> in convert.c may be used by them to learn the EOL conversion for\n> each path, and these threads are working in different parts of\n> worktree in parallel.  The set of .gitattributes files each of these\n> threads wants to be cached at one time is tied to where in the\n> directory hierarchy the thread is working in.\n>\n> The view by API users would not have to change from the point on\n> since 27/36 or so, but I think attr_stack needs to become per\n> <check, thread> tuple when we are fully thread-ready for the above\n> reason.\n\nI looked for a platform independent way to get a thread id as a natural\nnumber, i.e. I want to get 1,2,3,... such that I could have just added\nlist/array of attr stacks to each check, which would be the\n<check, thread> tuple you envision.\n\nHowever I think we do not really need it to be per check.  If we had\nan easy portable way of getting such a thread id, I would have implemented\na list of stacks per thread first. (Because each thread only looks at one\ncheck at a time.)\n\nSo this is not a baby step because I did not want to do it all at once, but\nbecause I did not find a suitable API to use.\n\n>\n> But we need to start somewhere to move away from the current \"one\n> single attr stack\" to \"there are multiple attr stacks\", and this\n> \"two checks may and do use different attr stacks\" is probably a\n> reasonable first step.  It may give a single-threaded API users\n> immediate benefit if the \"read and keep only the entries relevant\n> to the query\" optimization is done with this step alone, without\n> making the cache per <check, thread> pair.\n>\n>> This allows to use the attr in a multithreaded way.\n>\n> With manipulation of attr stack protected with a single Big\n> Attributes Lock, this should be safe.  It may not perform very well\n> when used by multiple threads, though ;-)\n\nI agree. So maybe it is not really a good fit for general consumption yet.\n"},{"id":"304804","messageId":"xmqqlgxd32yn.fsf@gitster.mtv.corp.google.com","threadId":"44358","inReplyTo":"CAGZ79kZ_7eG4uCXwyg0F=-hjmuT1dCAATSRnY293qkNC6siM5Q@mail.gmail.com","subject":"Re: [PATCH 28/36] attr: keep attr stack for each check","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2016-10-24T20:29:52Z","receivedAt":"2016-10-24T20:30:00Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Stefan Beller <sbeller@google.com> writes:\n\n> I looked for a platform independent way to get a thread id as a natural\n> number, i.e. I want to get 1,2,3,... such that I could have just added\n> list/array of attr stacks to each check, which would be the\n> <check, thread> tuple you envision.\n>\n> However I think we do not really need it to be per check.  If we had\n> an easy portable way of getting such a thread id, I would have implemented\n> a list of stacks per thread first. (Because each thread only looks at one\n> check at a time.)\n\nIt seems that by \"list of stacks per thread\", you mean \"there is a\nlist of stacks, each thread uses one and only element of that list\",\nbut I do not think it would be desirable.\n\n\"Each thread only looks at one check at a time\" is false.  For\nexample, \"write_archive_entry()\" would use one check that is\nspecific to \"git archive\" to learn about \"export-ignore\" and\n\"export-subst\" attributes, while letting convert_to_write_tree()\ncalled via sha1_file_to_archive() called via write_entry() method\n(i.e. write_tar_entry() or write_zip_entry()) to use a separate\ncheck that is specific to the convert.c API.\n\n>> With manipulation of attr stack protected with a single Big\n>> Attributes Lock, this should be safe.  It may not perform very well\n>> when used by multiple threads, though ;-)\n>\n> I agree. So maybe it is not really a good fit for general consumption yet.\n\nI would still think it is a good first step.  It may already be\nthread-safe, but may not be thread-ready from performance point of\nview.  IOW, this would not yet help an attempt to make the callers\nfaster by making them multi-threaded.\n\n"},{"id":"304805","messageId":"CAGZ79ka_zr_NXKoxC45swFrj168fP6S7_nQ1jjcfPOtTN4Jd1A@mail.gmail.com","threadId":"44358","inReplyTo":"0425fea3-3419-c265-b964-f5a309b867fa@ramsayjones.plus.com","subject":"Re: [PATCH 17/36] attr: expose validity check for attribute names","fromName":"Stefan Beller","fromEmail":"sbeller@google.com","sentAt":"2016-10-24T21:07:09Z","receivedAt":"2016-10-24T21:07:16Z","isPatch":true,"sender":{"key":"stefanbeller@gmail.com","avatar":"https://avatars.githubusercontent.com/u/455868?v=4"},"body":"On Sun, Oct 23, 2016 at 8:07 AM, Ramsay Jones\n<ramsay@ramsayjones.plus.com> wrote:\n>\n>\n> On 23/10/16 00:32, Stefan Beller wrote:\n>> From: Junio C Hamano <gitster@pobox.com>\n>>\n>> Export attr_name_valid() function, and a helper function that\n>> returns the message to be given when a given <name, len> pair\n>> is not a good name for an attribute.\n>>\n>> We could later update the message to exactly spell out what the\n>> rules for a good attribute name are, etc.\n>>\n>> Signed-off-by: Junio C Hamano <gitster@pobox.com>\n>> Signed-off-by: Stefan Beller <sbeller@google.com>\n>> ---\n>\n> [snip]\n>\n>> +extern int attr_name_valid(const char *name, size_t namelen);\n>> +extern void invalid_attr_name_message(struct strbuf *, const char *, int);\n>> +\n>\n> The symbol 'attr_name_valid()' is not used outside of attr.c, even\n> by the end of this series. Do you expect this function to be used\n> in any future series? (The export is deliberate and it certainly\n> seems like it should be part of the public interface, but ...)\n>\n> In contrast, the 'invalid_attr_name_message()' function is called\n> from code in pathspec.c, which relies on 'git_attr_counted()' to\n> call 'attr_name_valid()' internally to check for validity. :-D\n\nYeah, I am taking over Junios patches and do not quite implement\nwhat Junio thought I would. ;) So I guess it is a communication mismatch.\n\ngit_attr_counted is a wrapper around attr_name_valid in the way that\nit either returns NULL when the attr name is invalid or it does extra work\nand returns a pointer to an attr.\n\nSo I think for API completeness we'd want to keep attr_name_valid around,\nas otherwise the API looks strange. But that doesn't seem like a compelling\nreason, so I'll drop it from the header file and make it static.\n\nThanks,\nStefan\n"},{"id":"304880","messageId":"alpine.DEB.2.20.1610261049470.3264@virtualbox","threadId":"44358","inReplyTo":"20161022233225.8883-28-sbeller@google.com","subject":"Re: [PATCH 27/36] attr: convert to new threadsafe API","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2016-10-26T08:52:23Z","receivedAt":"2016-10-26T08:52:44Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Hi Stefan,\n\nOn Sat, 22 Oct 2016, Stefan Beller wrote:\n\n> @@ -46,6 +47,19 @@ struct git_attr {\n>  static int attr_nr;\n>  static struct git_attr *(git_attr_hash[HASHSIZE]);\n>  \n> +#ifndef NO_PTHREADS\n> +\n> +static pthread_mutex_t attr_mutex;\n> +#define attr_lock()\t\tpthread_mutex_lock(&attr_mutex)\n> +#define attr_unlock()\t\tpthread_mutex_unlock(&attr_mutex)\n\nThis mutex is never initialized. That may work on the system you tested,\nbut it is incorrect, and it does segfault on Windows. A lot.\n\nI need *at least* something like this to make it stop crashing all over\nthe test suite:\n\n-- snipsnap --\ndiff --git a/attr.c b/attr.c\nindex d5a6aa9..6933504 100644\n--- a/attr.c\n+++ b/attr.c\n@@ -50,7 +50,16 @@ static struct git_attr *(git_attr_hash[HASHSIZE]);\n #ifndef NO_PTHREADS\n \n static pthread_mutex_t attr_mutex;\n-#define attr_lock()pthread_mutex_lock(&attr_mutex)\n+static inline void attr_lock(void)\n+{\n+\tstatic int initialized;\n+\n+\tif (!initialized) {\n+\t\tpthread_mutex_init(&attr_mutex, NULL);\n+\t\tinitialized = 1;\n+\t}\n+\tpthread_mutex_lock(&attr_mutex);\n+}\n #define attr_unlock()pthread_mutex_unlock(&attr_mutex)\n \n #else\n\n"},{"id":"304884","messageId":"20161026093558.drsdwn2hm7bbcktt@ruderich.org","threadId":"44358","inReplyTo":"alpine.DEB.2.20.1610261049470.3264@virtualbox","subject":"Re: [PATCH 27/36] attr: convert to new threadsafe API","fromName":"Simon Ruderich","fromEmail":"simon@ruderich.org","sentAt":"2016-10-26T09:35:58Z","receivedAt":"2016-10-26T09:41:10Z","isPatch":true,"sender":{"key":"simon@ruderich.org","avatar":"https://avatars.githubusercontent.com/u/390994?v=4"},"body":"On Wed, Oct 26, 2016 at 10:52:23AM +0200, Johannes Schindelin wrote:\n> diff --git a/attr.c b/attr.c\n> index d5a6aa9..6933504 100644\n> --- a/attr.c\n> +++ b/attr.c\n> @@ -50,7 +50,16 @@ static struct git_attr *(git_attr_hash[HASHSIZE]);\n>  #ifndef NO_PTHREADS\n>\n>  static pthread_mutex_t attr_mutex;\n> -#define attr_lock()pthread_mutex_lock(&attr_mutex)\n> +static inline void attr_lock(void)\n> +{\n> +\tstatic int initialized;\n> +\n> +\tif (!initialized) {\n> +\t\tpthread_mutex_init(&attr_mutex, NULL);\n> +\t\tinitialized = 1;\n> +\t}\n> +\tpthread_mutex_lock(&attr_mutex);\n> +}\n\nThis may initialize the mutex multiple times during the first\nlock (which may happen in parallel).\n\npthread provides static initializers. To quote the man page:\n\n    Variables of type pthread_mutex_t can also be initialized\n    statically, using the constants PTHREAD_MUTEX_INITIALIZER\n    (for fast mutexes), PTHREAD_RECURSIVE_MUTEX_INITIALIZER_NP\n    (for recursive mutexes), and\n    PTHREAD_ERRORCHECK_MUTEX_INITIALIZER_NP (for error checking\n    mutexes).\n\nRegards\nSimon\n-- \n+ Privatsphäre ist notwendig\n+ Ich verwende GnuPG http://gnupg.org\n+ Öffentlicher Schlüssel: 0x92FEFDB7E44C32F9\n"},{"id":"304888","messageId":"20161026121525.twgiavpgfbr2ahvn@sigill.intra.peff.net","threadId":"44358","inReplyTo":"20161026093558.drsdwn2hm7bbcktt@ruderich.org","subject":"Re: [PATCH 27/36] attr: convert to new threadsafe API","fromName":"Jeff King","fromEmail":"peff@peff.net","sentAt":"2016-10-26T12:15:26Z","receivedAt":"2016-10-26T12:16:08Z","isPatch":true,"sender":{"key":"peff@peff.net","avatar":"https://avatars.githubusercontent.com/u/45925?v=4"},"body":"On Wed, Oct 26, 2016 at 11:35:58AM +0200, Simon Ruderich wrote:\n\n> >  static pthread_mutex_t attr_mutex;\n> > -#define attr_lock()pthread_mutex_lock(&attr_mutex)\n> > +static inline void attr_lock(void)\n> > +{\n> > +\tstatic int initialized;\n> > +\n> > +\tif (!initialized) {\n> > +\t\tpthread_mutex_init(&attr_mutex, NULL);\n> > +\t\tinitialized = 1;\n> > +\t}\n> > +\tpthread_mutex_lock(&attr_mutex);\n> > +}\n> \n> This may initialize the mutex multiple times during the first\n> lock (which may happen in parallel).\n> \n> pthread provides static initializers. To quote the man page:\n> \n>     Variables of type pthread_mutex_t can also be initialized\n>     statically, using the constants PTHREAD_MUTEX_INITIALIZER\n>     (for fast mutexes), PTHREAD_RECURSIVE_MUTEX_INITIALIZER_NP\n>     (for recursive mutexes), and\n>     PTHREAD_ERRORCHECK_MUTEX_INITIALIZER_NP (for error checking\n>     mutexes).\n\nI seem to recall this does not work on Windows, where the pthread\nfunctions are thin wrappers over CRITICAL_SECTION. Other threaded code\nin git does an explicit setup step before entering threaded sections.\nE.g., see start_threads() in builtin/grep.c.\n\n-Peff\n"},{"id":"304893","messageId":"CACsJy8DhsaTY1w_e-0O5d4KLxr4Gmo3g9rDagQq1ooSHywcRoQ@mail.gmail.com","threadId":"44358","inReplyTo":"20161022233225.8883-33-sbeller@google.com","subject":"Re: [PATCH 32/36] pathspec: allow querying for attributes","fromName":"Duy Nguyen","fromEmail":"pclouds@gmail.com","sentAt":"2016-10-26T13:33:57Z","receivedAt":"2016-10-26T13:34:40Z","isPatch":true,"sender":{"key":"pclouds@gmail.com","avatar":"https://avatars.githubusercontent.com/u/720?v=4"},"body":"(sorry if this should have been answered if I went through the series\npatch by patch, I wanted to do a proper review but finally have to\nadmit to myself I won't, so I just skim through a single giant diff\ninstead)\n\nOn Sun, Oct 23, 2016 at 6:32 AM, Stefan Beller <sbeller@google.com> wrote:\n> +attr;;\n> +After `attr:` comes a space separated list of \"attribute\n> +requirements\", all of which must be met in order for the\n> +path to be considered a match;\n\nWhat about (attr=abc def,attr=ghi lkj)? Does it mean (abc && def) ||\n(ghi && lkj), or abc && def && ghi && lkj? Or is it forbidden to have\nmultiple 'attr' attribute in the same pathspec?\n-- \nDuy\n"},{"id":"304894","messageId":"CACsJy8AB-24dDEs6Pa_6pB16T2ZAgw2faOQDEtZ2vU2uuAepHg@mail.gmail.com","threadId":"44358","inReplyTo":"20161022233225.8883-28-sbeller@google.com","subject":"Re: [PATCH 27/36] attr: convert to new threadsafe API","fromName":"Duy Nguyen","fromEmail":"pclouds@gmail.com","sentAt":"2016-10-26T13:52:41Z","receivedAt":"2016-10-26T13:53:20Z","isPatch":true,"sender":{"key":"pclouds@gmail.com","avatar":"https://avatars.githubusercontent.com/u/720?v=4"},"body":"On Sun, Oct 23, 2016 at 6:32 AM, Stefan Beller <sbeller@google.com> wrote:\n> This revamps the API of the attr subsystem to be thread safe.\n> Before we had the question and its results in one struct type.\n> The typical usage of the API was\n>\n>     static struct git_attr_check *check;\n>\n>     if (!check)\n>         check = git_attr_check_initl(\"text\", NULL);\n\n\nTwo cents. I read the .txt first and my first thought was \"is _initl a\ntypo, shouldn't it be jsut _init\"? I know we have this 'l' variant at\nleast in argv-array, but there we have many ways of adding arguments.\nAnd here it's just \"initl\", not \"init\" nor other \"initX\" variants,\nwhich looks odd. I wonder if the name git_attr_check_init() would do\nthe job fine, since we don't have different init variants and the\nnaming convention is not strong enough to tell me \"it's multiple\narguments ended with a NULL one\" right away. If you're worried about\npeople forgetting NULL at the end, how about passing an array of\nstrings, terminated by NULL, instead?\n\nJust thinking out loud. Maybe _initl _is_ a better name.\n-- \nDuy\n"},{"id":"304895","messageId":"CACsJy8BY9QLb=xkGywss+FBo+EBw7O7Kc3P1Kwkxnp0_4ZsnpA@mail.gmail.com","threadId":"44358","inReplyTo":"CAGZ79kb7PaqnyXZ7u0z8Q__ahTLKX8RQwV=dw7vbD4C9LnjOtw@mail.gmail.com","subject":"Re: [PATCH 27/36] attr: convert to new threadsafe API","fromName":"Duy Nguyen","fromEmail":"pclouds@gmail.com","sentAt":"2016-10-26T14:06:59Z","receivedAt":"2016-10-26T14:07:41Z","isPatch":true,"sender":{"key":"pclouds@gmail.com","avatar":"https://avatars.githubusercontent.com/u/720?v=4"},"body":"On Tue, Oct 25, 2016 at 2:18 AM, Stefan Beller <sbeller@google.com> wrote:\n> On Mon, Oct 24, 2016 at 11:55 AM, Junio C Hamano <gitster@pobox.com> wrote:\n>\n>>\n>> Make that a double-asterisk.  The same problem appears in an updated\n>> example in technical/api-gitattributes.txt doc, but the example in\n>> the commit log message (below) is correct.\n>\n> The implementation is actually using a double pointer, see below,\n> I forgot commit message and documentation\n>\n>>>     GIT_ATTR_RESULT_INIT_FOR(myresult, 1);\n>>\n>> Are you sure about this?  We've called attr_check_initl() already so\n>> if this is declaring myresult, it would be decl-after-stmt.\n>\n> I forgot to update the commit message and Documentation.\n> GIT_ATTR_RESULT_INIT_FOR is gone\n\nI was asking whether this function/macro was not thread-safe and found\nout it didn't exist as well, and it's bed time so I'm stopping. Will\ncontinue my skimming on the next re-roll :)\n-- \nDuy\n"},{"id":"304907","messageId":"CAGZ79kYgk9rQDju0MT2uniaxhAWpzJ9f1T9czgNnxfq+Wz6m+A@mail.gmail.com","threadId":"44358","inReplyTo":"20161026121525.twgiavpgfbr2ahvn@sigill.intra.peff.net","subject":"Re: [PATCH 27/36] attr: convert to new threadsafe API","fromName":"Stefan Beller","fromEmail":"sbeller@google.com","sentAt":"2016-10-26T19:51:02Z","receivedAt":"2016-10-26T19:59:38Z","isPatch":true,"sender":{"key":"stefanbeller@gmail.com","avatar":"https://avatars.githubusercontent.com/u/455868?v=4"},"body":"On Wed, Oct 26, 2016 at 5:15 AM, Jeff King <peff@peff.net> wrote:\n> On Wed, Oct 26, 2016 at 11:35:58AM +0200, Simon Ruderich wrote:\n>\n>> >  static pthread_mutex_t attr_mutex;\n>> > -#define attr_lock()pthread_mutex_lock(&attr_mutex)\n>> > +static inline void attr_lock(void)\n>> > +{\n>> > +   static int initialized;\n>> > +\n>> > +   if (!initialized) {\n>> > +           pthread_mutex_init(&attr_mutex, NULL);\n>> > +           initialized = 1;\n>> > +   }\n>> > +   pthread_mutex_lock(&attr_mutex);\n>> > +}\n>>\n>> This may initialize the mutex multiple times during the first\n>> lock (which may happen in parallel).\n>>\n>> pthread provides static initializers. To quote the man page:\n>>\n>>     Variables of type pthread_mutex_t can also be initialized\n>>     statically, using the constants PTHREAD_MUTEX_INITIALIZER\n>>     (for fast mutexes), PTHREAD_RECURSIVE_MUTEX_INITIALIZER_NP\n>>     (for recursive mutexes), and\n>>     PTHREAD_ERRORCHECK_MUTEX_INITIALIZER_NP (for error checking\n>>     mutexes).\n>\n> I seem to recall this does not work on Windows, where the pthread\n> functions are thin wrappers over CRITICAL_SECTION. Other threaded code\n> in git does an explicit setup step before entering threaded sections.\n> E.g., see start_threads() in builtin/grep.c.\n>\n\nI wonder if we can have a similar thing as\nhttp://stackoverflow.com/a/9490113 in compat/win32/pthread.{h.c} as it is\nvery convenient to not have to explicitly initialize mutexes?\n"},{"id":"304910","messageId":"20161026202027.ct6elljwzp46p2vc@sigill.intra.peff.net","threadId":"44358","inReplyTo":"CAGZ79kYgk9rQDju0MT2uniaxhAWpzJ9f1T9czgNnxfq+Wz6m+A@mail.gmail.com","subject":"Re: [PATCH 27/36] attr: convert to new threadsafe API","fromName":"Jeff King","fromEmail":"peff@peff.net","sentAt":"2016-10-26T20:20:28Z","receivedAt":"2016-10-26T20:20:35Z","isPatch":true,"sender":{"key":"peff@peff.net","avatar":"https://avatars.githubusercontent.com/u/45925?v=4"},"body":"On Wed, Oct 26, 2016 at 12:51:02PM -0700, Stefan Beller wrote:\n\n> > I seem to recall this does not work on Windows, where the pthread\n> > functions are thin wrappers over CRITICAL_SECTION. Other threaded code\n> > in git does an explicit setup step before entering threaded sections.\n> > E.g., see start_threads() in builtin/grep.c.\n> >\n> \n> I wonder if we can have a similar thing as\n> http://stackoverflow.com/a/9490113 in compat/win32/pthread.{h.c} as it is\n> very convenient to not have to explicitly initialize mutexes?\n\nI agree it would be much more convenient and get rid of some repetitive\nboilerplate code. I'll leave it to Windows folks to decide if they are\nOK with that approach or not (I do not offhand know of any reason it\nwould not work).\n\n-Peff\n"},{"id":"304911","messageId":"e1f760f5-27a7-8266-5d6c-d61fab7e194d@kdbg.org","threadId":"44358","inReplyTo":"CAGZ79kYgk9rQDju0MT2uniaxhAWpzJ9f1T9czgNnxfq+Wz6m+A@mail.gmail.com","subject":"Re: [PATCH 27/36] attr: convert to new threadsafe API","fromName":"Johannes Sixt","fromEmail":"j6t@kdbg.org","sentAt":"2016-10-26T20:25:38Z","receivedAt":"2016-10-26T20:25:48Z","isPatch":true,"sender":{"key":"j6t@kdbg.org","avatar":"https://avatars.githubusercontent.com/u/14810926?v=4"},"body":"Am 26.10.2016 um 21:51 schrieb Stefan Beller:\n> it is\n> very convenient to not have to explicitly initialize mutexes?\n\nNot to initialize a mutex is still wrong for pthreads.\n\n-- Hannes\n\n"},{"id":"304912","messageId":"20161026202652.kz63mxqqjan7swvl@sigill.intra.peff.net","threadId":"44358","inReplyTo":"e1f760f5-27a7-8266-5d6c-d61fab7e194d@kdbg.org","subject":"Re: [PATCH 27/36] attr: convert to new threadsafe API","fromName":"Jeff King","fromEmail":"peff@peff.net","sentAt":"2016-10-26T20:26:52Z","receivedAt":"2016-10-26T20:26:59Z","isPatch":true,"sender":{"key":"peff@peff.net","avatar":"https://avatars.githubusercontent.com/u/45925?v=4"},"body":"On Wed, Oct 26, 2016 at 10:25:38PM +0200, Johannes Sixt wrote:\n\n> Am 26.10.2016 um 21:51 schrieb Stefan Beller:\n> > it is\n> > very convenient to not have to explicitly initialize mutexes?\n> \n> Not to initialize a mutex is still wrong for pthreads.\n\nI think Stefan was being loose with his wording. There would still be an\ninitializer, but it would be a constant (and in the case of pthread\nemulation on Windows, would just be NULL).\n\n-Peff\n"},{"id":"304914","messageId":"6f231a78-5d74-b93f-a644-a4700c9dfbe7@kdbg.org","threadId":"44358","inReplyTo":"20161026202652.kz63mxqqjan7swvl@sigill.intra.peff.net","subject":"Re: [PATCH 27/36] attr: convert to new threadsafe API","fromName":"Johannes Sixt","fromEmail":"j6t@kdbg.org","sentAt":"2016-10-26T20:40:39Z","receivedAt":"2016-10-26T20:42:01Z","isPatch":true,"sender":{"key":"j6t@kdbg.org","avatar":"https://avatars.githubusercontent.com/u/14810926?v=4"},"body":"Am 26.10.2016 um 22:26 schrieb Jeff King:\n> On Wed, Oct 26, 2016 at 10:25:38PM +0200, Johannes Sixt wrote:\n>\n>> Am 26.10.2016 um 21:51 schrieb Stefan Beller:\n>>> it is\n>>> very convenient to not have to explicitly initialize mutexes?\n>>\n>> Not to initialize a mutex is still wrong for pthreads.\n>\n> I think Stefan was being loose with his wording. There would still be an\n> initializer, but it would be a constant (and in the case of pthread\n> emulation on Windows, would just be NULL).\n\nAnd I was loose, too: Not to initialize a mutex with at least \nPTHREAD_MUTEX_INITILIZER (if not pthread_mutex_init) is still wrong.\n\n-- Hannes\n\n"},{"id":"304915","messageId":"CAGZ79kYNLrkTMg97QU6xwC+zh=hW4LHuyPxwQkkLLaKyYO5sOA@mail.gmail.com","threadId":"44358","inReplyTo":"20161026202652.kz63mxqqjan7swvl@sigill.intra.peff.net","subject":"Re: [PATCH 27/36] attr: convert to new threadsafe API","fromName":"Stefan Beller","fromEmail":"sbeller@google.com","sentAt":"2016-10-26T20:43:25Z","receivedAt":"2016-10-26T20:43:31Z","isPatch":true,"sender":{"key":"stefanbeller@gmail.com","avatar":"https://avatars.githubusercontent.com/u/455868?v=4"},"body":"On Wed, Oct 26, 2016 at 1:26 PM, Jeff King <peff@peff.net> wrote:\n> On Wed, Oct 26, 2016 at 10:25:38PM +0200, Johannes Sixt wrote:\n>\n>> Am 26.10.2016 um 21:51 schrieb Stefan Beller:\n>> > it is\n>> > very convenient to not have to explicitly initialize mutexes?\n>>\n>> Not to initialize a mutex is still wrong for pthreads.\n>\n> I think Stefan was being loose with his wording. There would still be an\n> initializer, but it would be a constant (and in the case of pthread\n> emulation on Windows, would just be NULL).\n\nExactly, so we would do\n\n/* as per the man page of pthread_mutexes: */\npthread_mutex_t mymutex = PTHREAD_MUTEX_INITIALIZER;\n\nint somefunction()\n{\n    pthread_mutex_lock(&mymutex); /* threadsafely initialised on first use */\n    ...\n    pthread_unlock(&mymutex);\n}\n\nand for the Windows compat we'd do\n\n\n#define PTHREAD_MUTEX_INITIALIZER NULL\n#define pthread_mutex_lock emulate_pthread_mutex_lock\n\nint emulate_pthread_mutex_lock(volatile MUTEX_TYPE *mx)\n{\n    if (*mx == NULL) /* static initializer? */\n    { /* this stackoverflow magic to initialize threadsafely if not init'd */}\n\n    EnterCriticalSection(mx) /* as it currently is in compat/win32/pthread.h */\n    return 0;\n}\n\n\n\n\n\n\n>\n> -Peff\n"},{"id":"304916","messageId":"CAGZ79kYhMVrKHhNGYcf_D9kWEYp+sC+tMGbuE+gnD8AU27dh8g@mail.gmail.com","threadId":"44358","inReplyTo":"6f231a78-5d74-b93f-a644-a4700c9dfbe7@kdbg.org","subject":"Re: [PATCH 27/36] attr: convert to new threadsafe API","fromName":"Stefan Beller","fromEmail":"sbeller@google.com","sentAt":"2016-10-26T20:46:15Z","receivedAt":"2016-10-26T20:46:23Z","isPatch":true,"sender":{"key":"stefanbeller@gmail.com","avatar":"https://avatars.githubusercontent.com/u/455868?v=4"},"body":"On Wed, Oct 26, 2016 at 1:40 PM, Johannes Sixt <j6t@kdbg.org> wrote:\n> Am 26.10.2016 um 22:26 schrieb Jeff King:\n>>\n>> On Wed, Oct 26, 2016 at 10:25:38PM +0200, Johannes Sixt wrote:\n>>\n>>> Am 26.10.2016 um 21:51 schrieb Stefan Beller:\n>>>>\n>>>> it is\n>>>> very convenient to not have to explicitly initialize mutexes?\n>>>\n>>>\n>>> Not to initialize a mutex is still wrong for pthreads.\n>>\n>>\n>> I think Stefan was being loose with his wording. There would still be an\n>> initializer, but it would be a constant (and in the case of pthread\n>> emulation on Windows, would just be NULL).\n>\n>\n> And I was loose, too: Not to initialize a mutex with at least\n> PTHREAD_MUTEX_INITILIZER (if not pthread_mutex_init) is still wrong.\n>\n\nMy words were wrong, I meant statically initialized instead of the need to\ncall a function to initialize a mutex. (For the attribute subsystem, where would\nthat function go? We use attrs all over the place. My current thinking would\nbe in git.c to initialize the Big Single Attr Lock. I feel like that\nis not very well\nmaintainable though).\n\nSorry for the confusion,\nStefan\n"},{"id":"304920","messageId":"20161026212059.11840-1-sbeller@google.com","threadId":"44358","inReplyTo":"0425fea3-3419-c265-b964-f5a309b867fa@ramsayjones.plus.com","subject":"[PATCH] attr: expose error reporting function for invalid attribute names","fromName":"Stefan Beller","fromEmail":"sbeller@google.com","sentAt":"2016-10-26T21:20:59Z","receivedAt":"2016-10-26T21:21:08Z","isPatch":true,"sender":{"key":"stefanbeller@gmail.com","avatar":"https://avatars.githubusercontent.com/u/455868?v=4"},"body":"From: Junio C Hamano <gitster@pobox.com>\n\nExport invalid_attr_name_message() function that returns the\nmessage to be given when a given <name, len> pair\nis not a good name for an attribute.\n\nWe could later update the message to exactly spell out what the\nrules for a good attribute name are, etc.\n\nWe do not need to export the validity check 'attr_name_valid()' itself\nas we will learn about the validity indirectly in a later patch\nvia calling 'git_attr_counted()'.\n\nSigned-off-by: Junio C Hamano <gitster@pobox.com>\nSigned-off-by: Stefan Beller <sbeller@google.com>\n---\n\n Ramsay,\n I intend to replace the previous\n \n     [PATCH 17/36] attr: expose validity check for attribute names\n     \n by this one in a reroll.\n \n Thanks,\n Stefan\n\n attr.c | 39 +++++++++++++++++++++++++--------------\n attr.h |  2 ++\n 2 files changed, 27 insertions(+), 14 deletions(-)\n\ndiff --git a/attr.c b/attr.c\nindex 90dbacd..ec878c3 100644\n--- a/attr.c\n+++ b/attr.c\n@@ -59,23 +59,38 @@ static unsigned hash_name(const char *name, int namelen)\n \treturn val;\n }\n \n-static int invalid_attr_name(const char *name, int namelen)\n+static int attr_name_valid(const char *name, size_t namelen)\n {\n \t/*\n \t * Attribute name cannot begin with '-' and must consist of\n \t * characters from [-A-Za-z0-9_.].\n \t */\n \tif (namelen <= 0 || *name == '-')\n-\t\treturn -1;\n+\t\treturn 0;\n \twhile (namelen--) {\n \t\tchar ch = *name++;\n \t\tif (! (ch == '-' || ch == '.' || ch == '_' ||\n \t\t       ('0' <= ch && ch <= '9') ||\n \t\t       ('a' <= ch && ch <= 'z') ||\n \t\t       ('A' <= ch && ch <= 'Z')) )\n-\t\t\treturn -1;\n+\t\t\treturn 0;\n \t}\n-\treturn 0;\n+\treturn 1;\n+}\n+\n+void invalid_attr_name_message(struct strbuf *err, const char *name, int len)\n+{\n+\tstrbuf_addf(err, _(\"%.*s is not a valid attribute name\"),\n+\t\t    len, name);\n+}\n+\n+static void report_invalid_attr(const char *name, size_t len,\n+\t\t\t\tconst char *src, int lineno)\n+{\n+\tstruct strbuf err = STRBUF_INIT;\n+\tinvalid_attr_name_message(&err, name, len);\n+\tfprintf(stderr, \"%s: %s:%d\\n\", err.buf, src, lineno);\n+\tstrbuf_release(&err);\n }\n \n struct git_attr *git_attr_counted(const char *name, size_t len)\n@@ -90,7 +105,7 @@ struct git_attr *git_attr_counted(const char *name, size_t len)\n \t\t\treturn a;\n \t}\n \n-\tif (invalid_attr_name(name, len))\n+\tif (!attr_name_valid(name, len))\n \t\treturn NULL;\n \n \tFLEX_ALLOC_MEM(a, name, name, len);\n@@ -176,17 +191,15 @@ static const char *parse_attr(const char *src, int lineno, const char *cp,\n \t\t\tcp++;\n \t\t\tlen--;\n \t\t}\n-\t\tif (invalid_attr_name(cp, len)) {\n-\t\t\tfprintf(stderr,\n-\t\t\t\t\"%.*s is not a valid attribute name: %s:%d\\n\",\n-\t\t\t\tlen, cp, src, lineno);\n+\t\tif (!attr_name_valid(cp, len)) {\n+\t\t\treport_invalid_attr(cp, len, src, lineno);\n \t\t\treturn NULL;\n \t\t}\n \t} else {\n \t\t/*\n \t\t * As this function is always called twice, once with\n \t\t * e == NULL in the first pass and then e != NULL in\n-\t\t * the second pass, no need for invalid_attr_name()\n+\t\t * the second pass, no need for attr_name_valid()\n \t\t * check here.\n \t\t */\n \t\tif (*cp == '-' || *cp == '!') {\n@@ -229,10 +242,8 @@ static struct match_attr *parse_attr_line(const char *line, const char *src,\n \t\tname += strlen(ATTRIBUTE_MACRO_PREFIX);\n \t\tname += strspn(name, blank);\n \t\tnamelen = strcspn(name, blank);\n-\t\tif (invalid_attr_name(name, namelen)) {\n-\t\t\tfprintf(stderr,\n-\t\t\t\t\"%.*s is not a valid attribute name: %s:%d\\n\",\n-\t\t\t\tnamelen, name, src, lineno);\n+\t\tif (!attr_name_valid(name, namelen)) {\n+\t\t\treport_invalid_attr(name, namelen, src, lineno);\n \t\t\tgoto fail_return;\n \t\t}\n \t}\ndiff --git a/attr.h b/attr.h\nindex bcedf92..d39e327 100644\n--- a/attr.h\n+++ b/attr.h\n@@ -13,6 +13,8 @@ extern struct git_attr *git_attr(const char *);\n /* The same, but with counted string */\n extern struct git_attr *git_attr_counted(const char *, size_t);\n \n+extern void invalid_attr_name_message(struct strbuf *, const char *, int);\n+\n /* Internal use */\n extern const char git_attr__true[];\n extern const char git_attr__false[];\n-- \n2.10.1.508.g6572022\n\n"},{"id":"304924","messageId":"20161026224104.31844-1-sbeller@google.com","threadId":"44358","inReplyTo":"CAGZ79kYhMVrKHhNGYcf_D9kWEYp+sC+tMGbuE+gnD8AU27dh8g@mail.gmail.com","subject":"[PATCHv2 1/2] attr: convert to new threadsafe API","fromName":"Stefan Beller","fromEmail":"sbeller@google.com","sentAt":"2016-10-26T22:41:04Z","receivedAt":"2016-10-26T22:44:25Z","isPatch":false,"sender":{"key":"stefanbeller@gmail.com","avatar":"https://avatars.githubusercontent.com/u/455868?v=4"},"body":"This revamps the API of the attr subsystem to be thread safe.\nBefore we had the question and its results in one struct type.\nThe typical usage of the API was\n\n    static struct git_attr_check *check;\n\n    if (!check)\n        check = git_attr_check_initl(\"text\", NULL);\n\n    git_check_attr(path, check);\n    act_on(check->value[0]);\n\nThis has a couple of issues when it comes to thread safety:\n\n* the initialization is racy in this implementation. To make it\n  thread safe, we need to acquire a mutex, such that only one\n  thread is executing the code in git_attr_check_initl.\n  As we do not want to introduce a mutex at each call site,\n  this is best done in the attr code. However to do so, we need\n  to have access to the `check` variable, i.e. the API has to\n  look like\n    git_attr_check_initl(struct git_attr_check*, ...);\n  Then one of the threads calling git_attr_check_initl will\n  acquire the mutex and init the `check`, while all other threads\n  will wait on the mutex just to realize they're late to the\n  party and they'll return with no work done.\n\n* While the check for attributes to be questioned only need to\n  be initalized once as that part will be read only after its\n  initialisation, the answer may be different for each path.\n  Because of that we need to decouple the check and the answer,\n  such that each thread can obtain an answer for the path it\n  is currently processing.\n\nThis commit changes the API and adds locking in\ngit_attr_check_initl that provides the thread safety for constructing\n`struct git_attr_check`.\n\nThe usage of the new API will be:\n\n    /*\n     * The initl call will thread-safely check whether the\n     * struct git_attr_check has been initialized. We only\n     * want to do the initialization work once, hence we do\n     * that work inside a thread safe environment.\n     */\n    static struct git_attr_check *check;\n    git_attr_check_initl(&check, \"text\", NULL);\n\n    /* We're just asking for one attribute \"text\". */\n    git_attr_result myresult[1];\n\n    /* Perform the check and act on it: */\n    git_check_attr(path, check, myresult);\n    act_on(myresult[0].value);\n\n    /*\n     * No need to free the check as it is static, hence doesn't leak\n     * memory. The result is also static, so no need to free there either.\n     */\n\nSigned-off-by: Stefan Beller <sbeller@google.com>\n---\n\nIn the reroll of this series, I plan to use the patch as-is below:\n\n* Documentation/commit message matches actual implementation\n* initialize the attr lock statically. It was uninitialized in the first\n  version, which works for me on Linux, but not so in Windows; I split off\n  the static initialized mutexes on Windows as a separate patch, see\n  https://public-inbox.org/git/CAGZ79kZryb-5jGif04BtK1V9tgFj-tnahqUk+1Lb7XeecU7cMQ@mail.gmail.com\n  which would be a requirement for this patch.\n\n Documentation/technical/api-gitattributes.txt |  94 ++++++++++-------\n archive.c                                     |  11 +-\n attr.c                                        | 143 ++++++++++++++++++--------\n attr.h                                        |  71 ++++++++-----\n builtin/check-attr.c                          |  35 ++++---\n builtin/pack-objects.c                        |  16 +--\n convert.c                                     |  40 +++----\n ll-merge.c                                    |  24 +++--\n userdiff.c                                    |  16 +--\n ws.c                                          |   8 +-\n 10 files changed, 280 insertions(+), 178 deletions(-)\n\ndiff --git a/Documentation/technical/api-gitattributes.txt b/Documentation/technical/api-gitattributes.txt\nindex 92fc32a..4d8ef93 100644\n--- a/Documentation/technical/api-gitattributes.txt\n+++ b/Documentation/technical/api-gitattributes.txt\n@@ -16,15 +16,19 @@ Data Structure\n \tof no interest to the calling programs.  The name of the\n \tattribute can be retrieved by calling `git_attr_name()`.\n \n-`struct git_attr_check_elem`::\n-\n-\tThis structure represents one attribute and its value.\n-\n `struct git_attr_check`::\n \n-\tThis structure represents a collection of `git_attr_check_elem`.\n+\tThis structure represents a collection of `struct git_attrs`.\n \tIt is passed to `git_check_attr()` function, specifying the\n-\tattributes to check, and receives their values.\n+\tattributes to check, and receives their values into a corresponding\n+\t`struct git_attr_result`.\n+\n+`struct git_attr_result`::\n+\n+\tThis structure represents one results for a check, such that an\n+\tarray of `struct git_attr_results` corresponds to a\n+\t`struct git_attr_check`. The answers given in that array are in\n+\tthe the same order as the check struct.\n \n \n Attribute Values\n@@ -32,7 +36,7 @@ Attribute Values\n \n An attribute for a path can be in one of four states: Set, Unset,\n Unspecified or set to a string, and `.value` member of `struct\n-git_attr_check` records it.  There are three macros to check these:\n+git_attr_result` records it.  There are three macros to check these:\n \n `ATTR_TRUE()`::\n \n@@ -53,19 +57,32 @@ value of the attribute for the path.\n Querying Specific Attributes\n ----------------------------\n \n-* Prepare `struct git_attr_check` using git_attr_check_initl()\n+* Prepare a `struct git_attr_check` using `git_attr_check_initl()`\n   function, enumerating the names of attributes whose values you are\n   interested in, terminated with a NULL pointer.  Alternatively, an\n-  empty `struct git_attr_check` can be prepared by calling\n-  `git_attr_check_alloc()` function and then attributes you want to\n-  ask about can be added to it with `git_attr_check_append()`\n-  function.\n-\n-* Call `git_check_attr()` to check the attributes for the path.\n-\n-* Inspect `git_attr_check` structure to see how each of the\n-  attribute in the array is defined for the path.\n-\n+  empty `struct git_attr_check` as allocated by git_attr_check_alloc()\n+  can be prepared by calling `git_attr_check_alloc()` function and\n+  then attributes you want to ask about can be added to it with\n+  `git_attr_check_append()` function.\n+  Both ways with `git_attr_check_initl()` as well as the\n+  alloc and append route are thread safe, i.e. you can call it\n+  from different threads at the same time; when check determines\n+  the initialzisation is still needed, the threads will use a\n+  single global mutex to perform the initialization just once, the\n+  others will wait on the the thread to actually perform the\n+  initialization.\n+\n+* Allocate an array of `struct git_attr_result` either statically on the\n+  as a variable on the stack or dynamically via `git_attr_result_alloc`\n+  when the result size is not known at compile time. The call to initialize\n+  the result is not thread safe, because different threads need their\n+  own thread local result anyway.\n+\n+* Call `git_check_attr()` to check the attributes for the path,\n+  the given `git_attr_result` will be filled with the result.\n+\n+* Inspect each `git_attr_result` structure to see how\n+  each of the attribute in the array is defined for the path.\n \n Example\n -------\n@@ -76,28 +93,23 @@ To see how attributes \"crlf\" and \"ident\" are set for different paths.\n   we are checking two attributes):\n \n ------------\n-static struct git_attr_check *check;\n-static void setup_check(void)\n-{\n-\tif (check)\n-\t\treturn; /* already done */\n-\tcheck = git_attr_check_initl(\"crlf\", \"ident\", NULL);\n-}\n+\tstatic struct git_attr_check *check;\n+\tgit_attr_check_initl(check, \"crlf\", \"ident\", NULL);\n ------------\n \n . Call `git_check_attr()` with the prepared `struct git_attr_check`:\n \n ------------\n \tconst char *path;\n+\tstruct git_attr_result result[2];\n \n-\tsetup_check();\n-\tgit_check_attr(path, check);\n+\tgit_check_attr(path, check, result);\n ------------\n \n-. Act on `.value` member of the result, left in `check->check[]`:\n+. Act on `result.value[]`:\n \n ------------\n-\tconst char *value = check->check[0].value;\n+\tconst char *value = result.value[0];\n \n \tif (ATTR_TRUE(value)) {\n \t\tThe attribute is Set, by listing only the name of the\n@@ -123,12 +135,15 @@ the first step in the above would be different.\n static struct git_attr_check *check;\n static void setup_check(const char **argv)\n {\n+\tif (check)\n+\t\treturn; /* already done */\n \tcheck = git_attr_check_alloc();\n \twhile (*argv) {\n \t\tstruct git_attr *attr = git_attr(*argv);\n \t\tgit_attr_check_append(check, attr);\n \t\targv++;\n \t}\n+\tstruct git_attr_result *result = git_attr_result_alloc(check);\n }\n ------------\n \n@@ -138,17 +153,20 @@ Querying All Attributes\n \n To get the values of all attributes associated with a file:\n \n-* Prepare an empty `git_attr_check` structure by calling\n-  `git_attr_check_alloc()`.\n+* Setup a local variables for the question\n+  `struct git_attr_check` as well as a pointer where the result\n+  `struct git_attr_result` will be stored.\n \n-* Call `git_all_attrs()`, which populates the `git_attr_check`\n-  with the attributes attached to the path.\n+* Call `git_all_attrs()`.\n \n-* Iterate over the `git_attr_check.check[]` array to examine\n-  the attribute names and values.  The name of the attribute\n-  described by a  `git_attr_check.check[]` object can be retrieved via\n-  `git_attr_name(check->check[i].attr)`.  (Please note that no items\n+* Iterate over the `git_attr_check.attr[]` array to examine the\n+  attribute names.  The name of the attribute described by a\n+  `git_attr_check.attr[]` object can be retrieved via\n+  `git_attr_name(check->attr[i])`.  (Please note that no items\n   will be returned for unset attributes, so `ATTR_UNSET()` will return\n   false for all returned `git_array_check` objects.)\n+  The respective value for an attribute can be found in the same\n+  index position in of `git_attr_result`.\n \n-* Free the `git_array_check` by calling `git_attr_check_free()`.\n+* Clear the variables by calling `git_attr_check_clear()` and\n+  `git_attr_result_free()`.\ndiff --git a/archive.c b/archive.c\nindex 11e3951..65bc6b7 100644\n--- a/archive.c\n+++ b/archive.c\n@@ -111,6 +111,7 @@ static int write_archive_entry(const unsigned char *sha1, const char *base,\n \tstruct archiver_args *args = c->args;\n \twrite_archive_entry_fn_t write_entry = c->write_entry;\n \tstatic struct git_attr_check *check;\n+\tstruct git_attr_result result[2];\n \tconst char *path_without_prefix;\n \tint err;\n \n@@ -124,12 +125,12 @@ static int write_archive_entry(const unsigned char *sha1, const char *base,\n \t\tstrbuf_addch(&path, '/');\n \tpath_without_prefix = path.buf + args->baselen;\n \n-\tif (!check)\n-\t\tcheck = git_attr_check_initl(\"export-ignore\", \"export-subst\", NULL);\n-\tif (!git_check_attr(path_without_prefix, check)) {\n-\t\tif (ATTR_TRUE(check->check[0].value))\n+\tgit_attr_check_initl(&check, \"export-ignore\", \"export-subst\", NULL);\n+\n+\tif (!git_check_attr(path_without_prefix, check, result)) {\n+\t\tif (ATTR_TRUE(result[0].value))\n \t\t\treturn 0;\n-\t\targs->convert = ATTR_TRUE(check->check[1].value);\n+\t\targs->convert = ATTR_TRUE(result[1].value);\n \t}\n \n \tif (S_ISDIR(mode) || S_ISGITLINK(mode)) {\ndiff --git a/attr.c b/attr.c\nindex abf734e..7ee9078 100644\n--- a/attr.c\n+++ b/attr.c\n@@ -14,6 +14,7 @@\n #include \"dir.h\"\n #include \"utf8.h\"\n #include \"quote.h\"\n+#include \"thread-utils.h\"\n \n const char git_attr__true[] = \"(builtin)true\";\n const char git_attr__false[] = \"\\0(builtin)false\";\n@@ -46,6 +47,19 @@ struct git_attr {\n static int attr_nr;\n static struct git_attr *(git_attr_hash[HASHSIZE]);\n \n+#ifndef NO_PTHREADS\n+\n+static pthread_mutex_t attr_mutex = PTHREAD_MUTEX_INITIALIZER;\n+#define attr_lock()\t\tpthread_mutex_lock(&attr_mutex)\n+#define attr_unlock()\t\tpthread_mutex_unlock(&attr_mutex)\n+\n+#else\n+\n+#define attr_lock()\t\t(void)0\n+#define attr_unlock()\t\t(void)0\n+\n+#endif /* NO_PTHREADS */\n+\n /*\n  * NEEDSWORK: maybe-real, maybe-macro are not property of\n  * an attribute, as it depends on what .gitattributes are\n@@ -55,6 +69,16 @@ static struct git_attr *(git_attr_hash[HASHSIZE]);\n  */\n static int cannot_trust_maybe_real;\n \n+/*\n+ * Send one or more git_attr_check to git_check_attrs(), and\n+ * each 'value' member tells what its value is.\n+ * Unset one is returned as NULL.\n+ */\n+struct git_attr_check_elem {\n+\tconst struct git_attr *attr;\n+\tconst char *value;\n+};\n+\n /* NEEDSWORK: This will become per git_attr_check */\n static struct git_attr_check_elem *check_all_attr;\n \n@@ -781,7 +805,7 @@ static int macroexpand_one(int nr, int rem)\n \n static int attr_check_is_dynamic(const struct git_attr_check *check)\n {\n-\treturn (void *)(check->check) != (void *)(check + 1);\n+\treturn (void *)(check->attr) != (void *)(check + 1);\n }\n \n static void empty_attr_check_elems(struct git_attr_check *check)\n@@ -799,7 +823,9 @@ static void empty_attr_check_elems(struct git_attr_check *check)\n  * any and all attributes that are visible are collected in it.\n  */\n static void collect_some_attrs(const char *path, int pathlen,\n-\t\t\t       struct git_attr_check *check, int collect_all)\n+\t\t\t       struct git_attr_check *check,\n+\t\t\t       struct git_attr_result **result,\n+\t\t\t       int collect_all)\n \n {\n \tstruct attr_stack *stk;\n@@ -825,13 +851,11 @@ static void collect_some_attrs(const char *path, int pathlen,\n \t\tcheck_all_attr[i].value = ATTR__UNKNOWN;\n \n \tif (!collect_all && !cannot_trust_maybe_real) {\n-\t\tstruct git_attr_check_elem *celem = check->check;\n-\n \t\trem = 0;\n \t\tfor (i = 0; i < check->check_nr; i++) {\n-\t\t\tif (!celem[i].attr->maybe_real) {\n+\t\t\tif (!check->attr[i]->maybe_real) {\n \t\t\t\tstruct git_attr_check_elem *c;\n-\t\t\t\tc = check_all_attr + celem[i].attr->attr_nr;\n+\t\t\t\tc = check_all_attr + check->attr[i]->attr_nr;\n \t\t\t\tc->value = ATTR__UNSET;\n \t\t\t\trem++;\n \t\t\t}\n@@ -845,38 +869,52 @@ static void collect_some_attrs(const char *path, int pathlen,\n \t\trem = fill(path, pathlen, basename_offset, stk, rem);\n \n \tif (collect_all) {\n-\t\tempty_attr_check_elems(check);\n+\t\tint check_nr = 0, check_alloc = 0;\n+\t\tconst char **res = NULL;\n+\n \t\tfor (i = 0; i < attr_nr; i++) {\n \t\t\tconst struct git_attr *attr = check_all_attr[i].attr;\n \t\t\tconst char *value = check_all_attr[i].value;\n \t\t\tif (value == ATTR__UNSET || value == ATTR__UNKNOWN)\n \t\t\t\tcontinue;\n-\t\t\tgit_attr_check_append(check, attr)->value = value;\n+\n+\t\t\tgit_attr_check_append(check, attr);\n+\n+\t\t\tALLOC_GROW(res, check_nr + 1, check_alloc);\n+\t\t\tres[check_nr++] = value;\n \t\t}\n+\n+\t\t*result = git_attr_result_alloc(check);\n+\t\tfor (i = 0; i < check->check_nr; i++)\n+\t\t\t(*result)[i].value = res[i];\n+\n+\t\tfree(res);\n \t}\n }\n \n static int git_check_attrs(const char *path, int pathlen,\n-\t\t\t   struct git_attr_check *check)\n+\t\t\t   struct git_attr_check *check,\n+\t\t\t   struct git_attr_result *result)\n {\n \tint i;\n-\tstruct git_attr_check_elem *celem = check->check;\n \n-\tcollect_some_attrs(path, pathlen, check, 0);\n+\tcollect_some_attrs(path, pathlen, check, &result, 0);\n \n \tfor (i = 0; i < check->check_nr; i++) {\n-\t\tconst char *value = check_all_attr[celem[i].attr->attr_nr].value;\n+\t\tconst char *value = check_all_attr[check->attr[i]->attr_nr].value;\n \t\tif (value == ATTR__UNKNOWN)\n \t\t\tvalue = ATTR__UNSET;\n-\t\tcelem[i].value = value;\n+\t\tresult[i].value = value;\n \t}\n \n \treturn 0;\n }\n \n-void git_all_attrs(const char *path, struct git_attr_check *check)\n+void git_all_attrs(const char *path,\n+\t\t   struct git_attr_check *check,\n+\t\t   struct git_attr_result **result)\n {\n-\tcollect_some_attrs(path, strlen(path), check, 1);\n+\tcollect_some_attrs(path, strlen(path), check, result, 1);\n }\n \n void git_attr_set_direction(enum git_attr_direction new, struct index_state *istate)\n@@ -892,36 +930,40 @@ void git_attr_set_direction(enum git_attr_direction new, struct index_state *ist\n \tuse_index = istate;\n }\n \n-static int git_check_attr_counted(const char *path, int pathlen,\n-\t\t\t\t  struct git_attr_check *check)\n+int git_check_attr(const char *path,\n+\t\t    struct git_attr_check *check,\n+\t\t    struct git_attr_result *result)\n {\n \tcheck->finalized = 1;\n-\treturn git_check_attrs(path, pathlen, check);\n+\treturn git_check_attrs(path, strlen(path), check, result);\n }\n \n-int git_check_attr(const char *path, struct git_attr_check *check)\n+void git_attr_check_initl(struct git_attr_check **check_,\n+\t\t\t  const char *one, ...)\n {\n-\treturn git_check_attr_counted(path, strlen(path), check);\n-}\n-\n-struct git_attr_check *git_attr_check_initl(const char *one, ...)\n-{\n-\tstruct git_attr_check *check;\n \tint cnt;\n \tva_list params;\n \tconst char *param;\n+\tstruct git_attr_check *check;\n+\n+\tattr_lock();\n+\tif (*check_) {\n+\t\tattr_unlock();\n+\t\treturn;\n+\t}\n \n \tva_start(params, one);\n \tfor (cnt = 1; (param = va_arg(params, const char *)) != NULL; cnt++)\n \t\t;\n \tva_end(params);\n+\n \tcheck = xcalloc(1,\n-\t\t\tsizeof(*check) + cnt * sizeof(*(check->check)));\n+\t\t\tsizeof(*check) + cnt * sizeof(*(check->attr)));\n \tcheck->check_nr = cnt;\n \tcheck->finalized = 1;\n-\tcheck->check = (struct git_attr_check_elem *)(check + 1);\n+\tcheck->attr = (const struct git_attr **)(check + 1);\n \n-\tcheck->check[0].attr = git_attr(one);\n+\tcheck->attr[0] = git_attr(one);\n \tva_start(params, one);\n \tfor (cnt = 1; cnt < check->check_nr; cnt++) {\n \t\tstruct git_attr *attr;\n@@ -932,29 +974,44 @@ struct git_attr_check *git_attr_check_initl(const char *one, ...)\n \t\tattr = git_attr(param);\n \t\tif (!attr)\n \t\t\tdie(\"BUG: %s: not a valid attribute name\", param);\n-\t\tcheck->check[cnt].attr = attr;\n+\t\tcheck->attr[cnt] = attr;\n \t}\n \tva_end(params);\n-\treturn check;\n+\t*check_ = check;\n+\tattr_unlock();\n+}\n+\n+void git_attr_check_alloc(struct git_attr_check **check)\n+{\n+\tattr_lock();\n+\tif (!*check)\n+\t\t*check = xcalloc(1, sizeof(struct git_attr_check));\n+\n+\tattr_unlock();\n }\n \n-struct git_attr_check *git_attr_check_alloc(void)\n+struct git_attr_result *git_attr_result_alloc(struct git_attr_check *check)\n {\n-\treturn xcalloc(1, sizeof(struct git_attr_check));\n+\treturn xcalloc(1, sizeof(struct git_attr_result) * check->check_nr);\n }\n \n-struct git_attr_check_elem *git_attr_check_append(struct git_attr_check *check,\n-\t\t\t\t\t\t  const struct git_attr *attr)\n+void git_attr_check_append(struct git_attr_check *check,\n+\t\t\t   const struct git_attr *attr)\n {\n-\tstruct git_attr_check_elem *elem;\n+\tint i;\n \tif (check->finalized)\n \t\tdie(\"BUG: append after git_attr_check structure is finalized\");\n \tif (!attr_check_is_dynamic(check))\n \t\tdie(\"BUG: appending to a statically initialized git_attr_check\");\n-\tALLOC_GROW(check->check, check->check_nr + 1, check->check_alloc);\n-\telem = &check->check[check->check_nr++];\n-\telem->attr = attr;\n-\treturn elem;\n+\tattr_lock();\n+\tfor (i = 0; i < check->check_nr; i++)\n+\t\tif (check->attr[i] == attr)\n+\t\t\tbreak;\n+\tif (i == check->check_nr) {\n+\t\tALLOC_GROW(check->attr, check->check_nr + 1, check->check_alloc);\n+\t\tcheck->attr[check->check_nr++] = attr;\n+\t}\n+\tattr_unlock();\n }\n \n void git_attr_check_clear(struct git_attr_check *check)\n@@ -962,12 +1019,12 @@ void git_attr_check_clear(struct git_attr_check *check)\n \tempty_attr_check_elems(check);\n \tif (!attr_check_is_dynamic(check))\n \t\tdie(\"BUG: clearing a statically initialized git_attr_check\");\n-\tfree(check->check);\n+\tfree(check->attr);\n \tcheck->check_alloc = 0;\n }\n \n-void git_attr_check_free(struct git_attr_check *check)\n+void git_attr_result_free(struct git_attr_result *result)\n {\n-\tgit_attr_check_clear(check);\n-\tfree(check);\n+\t/* No need to free values as they are interned. */\n+\tfree(result);\n }\ndiff --git a/attr.h b/attr.h\nindex bb40967..607d44c 100644\n--- a/attr.h\n+++ b/attr.h\n@@ -9,10 +9,16 @@ struct git_attr;\n  * corresponds to it.\n  */\n extern struct git_attr *git_attr(const char *);\n-\n /* The same, but with counted string */\n extern struct git_attr *git_attr_counted(const char *, size_t);\n \n+/*\n+ * Return the name of the attribute represented by the argument.  The\n+ * return value is a pointer to a null-delimited string that is part\n+ * of the internal data structure; it should not be modified or freed.\n+ */\n+extern const char *git_attr_name(const struct git_attr *);\n+\n extern void invalid_attr_name_message(struct strbuf *, const char *, int);\n \n /* Internal use */\n@@ -24,44 +30,53 @@ extern const char git_attr__false[];\n #define ATTR_FALSE(v) ((v) == git_attr__false)\n #define ATTR_UNSET(v) ((v) == NULL)\n \n-/*\n- * Send one or more git_attr_check to git_check_attrs(), and\n- * each 'value' member tells what its value is.\n- * Unset one is returned as NULL.\n- */\n-struct git_attr_check_elem {\n-\tconst struct git_attr *attr;\n-\tconst char *value;\n-};\n-\n struct git_attr_check {\n \tint finalized;\n \tint check_nr;\n \tint check_alloc;\n-\tstruct git_attr_check_elem *check;\n+\tconst struct git_attr **attr;\n+};\n+#define GIT_ATTR_CHECK_INIT {0, 0, 0, NULL}\n+\n+struct git_attr_result {\n+\tconst char *value;\n };\n \n-extern struct git_attr_check *git_attr_check_initl(const char *, ...);\n-extern int git_check_attr(const char *path, struct git_attr_check *);\n+/*\n+ * Initialize the `git_attr_check` via one of the following three functions:\n+ *\n+ * git_attr_check_alloc  allocates an empty check,\n+ * git_attr_check_append add an attribute to the given git_attr_check\n+ *\n+ * git_all_attrs         allocates a check and fills in all attributes that\n+ *                       are set for the given path.\n+ * git_attr_check_initl  takes a pointer to where the check will be initialized,\n+ *                       followed by all attributes that are to be checked.\n+ *                       This makes it potentially thread safe as it could\n+ *                       internally have a mutex for that memory location.\n+ *                       Currently it is not thread safe!\n+ */\n+extern void git_attr_check_alloc(struct git_attr_check **);\n+extern struct git_attr_result *git_attr_result_alloc(struct git_attr_check *check);\n \n-extern struct git_attr_check *git_attr_check_alloc(void);\n-extern struct git_attr_check_elem *git_attr_check_append(struct git_attr_check *, const struct git_attr *);\n+extern void git_attr_check_append(struct git_attr_check *,\n+\t\t\t\t  const struct git_attr *);\n+extern void git_attr_check_initl(struct git_attr_check **,\n+\t\t\t\t const char *, ...);\n+\n+extern void git_all_attrs(const char *path,\n+\t\t\t  struct git_attr_check *,\n+\t\t\t  struct git_attr_result **);\n+\n+/* Query a path for its attributes */\n+extern int git_check_attr(const char *path,\n+\t\t\t  struct git_attr_check *,\n+\t\t\t  struct git_attr_result *result);\n \n extern void git_attr_check_clear(struct git_attr_check *);\n-extern void git_attr_check_free(struct git_attr_check *);\n \n-/*\n- * Return the name of the attribute represented by the argument.  The\n- * return value is a pointer to a null-delimited string that is part\n- * of the internal data structure; it should not be modified or freed.\n- */\n-extern const char *git_attr_name(const struct git_attr *);\n+extern void git_attr_result_free(struct git_attr_result *);\n \n-/*\n- * Retrieve all attributes that apply to the specified path.\n- * check holds the attributes and their values.\n- */\n-void git_all_attrs(const char *path, struct git_attr_check *check);\n \n enum git_attr_direction {\n \tGIT_ATTR_CHECKIN,\ndiff --git a/builtin/check-attr.c b/builtin/check-attr.c\nindex ec61476..c7c6c22 100644\n--- a/builtin/check-attr.c\n+++ b/builtin/check-attr.c\n@@ -24,13 +24,14 @@ static const struct option check_attr_options[] = {\n \tOPT_END()\n };\n \n-static void output_attr(struct git_attr_check *check, const char *file)\n+static void output_attr(struct git_attr_check *check,\n+\t\t\tstruct git_attr_result *result, const char *file)\n {\n \tint j;\n \tint cnt = check->check_nr;\n \n \tfor (j = 0; j < cnt; j++) {\n-\t\tconst char *value = check->check[j].value;\n+\t\tconst char *value = result[j].value;\n \n \t\tif (ATTR_TRUE(value))\n \t\t\tvalue = \"set\";\n@@ -44,11 +45,11 @@ static void output_attr(struct git_attr_check *check, const char *file)\n \t\t\t       \"%s%c\" /* attrname */\n \t\t\t       \"%s%c\" /* attrvalue */,\n \t\t\t       file, 0,\n-\t\t\t       git_attr_name(check->check[j].attr), 0, value, 0);\n+\t\t\t       git_attr_name(check->attr[j]), 0, value, 0);\n \t\t} else {\n \t\t\tquote_c_style(file, NULL, stdout, 0);\n \t\t\tprintf(\": %s: %s\\n\",\n-\t\t\t       git_attr_name(check->check[j].attr), value);\n+\t\t\t       git_attr_name(check->attr[j]), value);\n \t\t}\n \t}\n }\n@@ -59,16 +60,20 @@ static void check_attr(const char *prefix,\n {\n \tchar *full_path =\n \t\tprefix_path(prefix, prefix ? strlen(prefix) : 0, file);\n+\tstruct git_attr_check local_check = GIT_ATTR_CHECK_INIT;\n+\tstruct git_attr_result *result = NULL;\n+\n \tif (check != NULL) {\n-\t\tif (git_check_attr(full_path, check))\n-\t\t\tdie(\"git_check_attr died\");\n-\t\toutput_attr(check, file);\n+\t\tresult = git_attr_result_alloc(check);\n+\t\tgit_check_attr(full_path, check, result);\n \t} else {\n-\t\tcheck = git_attr_check_alloc();\n-\t\tgit_all_attrs(full_path, check);\n-\t\toutput_attr(check, file);\n-\t\tgit_attr_check_free(check);\n+\t\tgit_all_attrs(full_path, &local_check, &result);\n+\t\tcheck = &local_check;\n \t}\n+\toutput_attr(check, result, file);\n+\tgit_attr_check_clear(&local_check);\n+\n+\tgit_attr_result_free(result);\n \tfree(full_path);\n }\n \n@@ -102,7 +107,7 @@ static NORETURN void error_with_usage(const char *msg)\n \n int cmd_check_attr(int argc, const char **argv, const char *prefix)\n {\n-\tstruct git_attr_check *check;\n+\tstruct git_attr_check *check = NULL;\n \tint cnt, i, doubledash, filei;\n \n \tif (!is_bare_repository())\n@@ -162,10 +167,8 @@ int cmd_check_attr(int argc, const char **argv, const char *prefix)\n \t\t\terror_with_usage(\"No file specified\");\n \t}\n \n-\tif (all_attrs) {\n-\t\tcheck = NULL;\n-\t} else {\n-\t\tcheck = git_attr_check_alloc();\n+\tif (!all_attrs) {\n+\t\tgit_attr_check_alloc(&check);\n \t\tfor (i = 0; i < cnt; i++) {\n \t\t\tstruct git_attr *a = git_attr(argv[i]);\n \t\t\tif (!a)\ndiff --git a/builtin/pack-objects.c b/builtin/pack-objects.c\nindex 3918c07..3751836 100644\n--- a/builtin/pack-objects.c\n+++ b/builtin/pack-objects.c\n@@ -899,14 +899,16 @@ static void write_pack_file(void)\n static int no_try_delta(const char *path)\n {\n \tstatic struct git_attr_check *check;\n+\tint ret = 0;\n+\tstruct git_attr_result result[1];\n \n-\tif (!check)\n-\t\tcheck = git_attr_check_initl(\"delta\", NULL);\n-\tif (git_check_attr(path, check))\n-\t\treturn 0;\n-\tif (ATTR_FALSE(check->check[0].value))\n-\t\treturn 1;\n-\treturn 0;\n+\tgit_attr_check_initl(&check, \"delta\", NULL);\n+\n+\tif (!git_check_attr(path, check, result)) {\n+\t\tif (ATTR_FALSE(result[0].value))\n+\t\t\tret = 1;\n+\t}\n+\treturn ret;\n }\n \n /*\ndiff --git a/convert.c b/convert.c\nindex bb2435a..fe861ce 100644\n--- a/convert.c\n+++ b/convert.c\n@@ -718,9 +718,9 @@ static int ident_to_worktree(const char *path, const char *src, size_t len,\n \treturn 1;\n }\n \n-static enum crlf_action git_path_check_crlf(struct git_attr_check_elem *check)\n+static enum crlf_action git_path_check_crlf(struct git_attr_result *result)\n {\n-\tconst char *value = check->value;\n+\tconst char *value = result->value;\n \n \tif (ATTR_TRUE(value))\n \t\treturn CRLF_TEXT;\n@@ -735,9 +735,9 @@ static enum crlf_action git_path_check_crlf(struct git_attr_check_elem *check)\n \treturn CRLF_UNDEFINED;\n }\n \n-static enum eol git_path_check_eol(struct git_attr_check_elem *check)\n+static enum eol git_path_check_eol(struct git_attr_result *result)\n {\n-\tconst char *value = check->value;\n+\tconst char *value = result->value;\n \n \tif (ATTR_UNSET(value))\n \t\t;\n@@ -748,9 +748,9 @@ static enum eol git_path_check_eol(struct git_attr_check_elem *check)\n \treturn EOL_UNSET;\n }\n \n-static struct convert_driver *git_path_check_convert(struct git_attr_check_elem *check)\n+static struct convert_driver *git_path_check_convert(struct git_attr_result *result)\n {\n-\tconst char *value = check->value;\n+\tconst char *value = result->value;\n \tstruct convert_driver *drv;\n \n \tif (ATTR_TRUE(value) || ATTR_FALSE(value) || ATTR_UNSET(value))\n@@ -761,9 +761,9 @@ static struct convert_driver *git_path_check_convert(struct git_attr_check_elem\n \treturn NULL;\n }\n \n-static int git_path_check_ident(struct git_attr_check_elem *check)\n+static int git_path_check_ident(struct git_attr_result *result)\n {\n-\tconst char *value = check->value;\n+\tconst char *value = result->value;\n \n \treturn !!ATTR_TRUE(value);\n }\n@@ -778,25 +778,27 @@ struct conv_attrs {\n static void convert_attrs(struct conv_attrs *ca, const char *path)\n {\n \tstatic struct git_attr_check *check;\n+\tstatic int init_user_convert_tail;\n+\tstruct git_attr_result result[5];\n \n-\tif (!check) {\n-\t\tcheck = git_attr_check_initl(\"crlf\", \"ident\",\n-\t\t\t\t\t     \"filter\", \"eol\", \"text\",\n-\t\t\t\t\t     NULL);\n+\tgit_attr_check_initl(&check, \"crlf\", \"ident\", \"filter\",\n+\t\t\t     \"eol\", \"text\", NULL);\n+\n+\tif (!init_user_convert_tail) {\n \t\tuser_convert_tail = &user_convert;\n \t\tgit_config(read_convert_config, NULL);\n+\t\tinit_user_convert_tail = 1;\n \t}\n \n-\tif (!git_check_attr(path, check)) {\n-\t\tstruct git_attr_check_elem *ccheck = check->check;\n-\t\tca->crlf_action = git_path_check_crlf(ccheck + 4);\n+\tif (!git_check_attr(path, check, result)) {\n+\t\tca->crlf_action = git_path_check_crlf(&result[4]);\n \t\tif (ca->crlf_action == CRLF_UNDEFINED)\n-\t\t\tca->crlf_action = git_path_check_crlf(ccheck + 0);\n+\t\t\tca->crlf_action = git_path_check_crlf(&result[0]);\n \t\tca->attr_action = ca->crlf_action;\n-\t\tca->ident = git_path_check_ident(ccheck + 1);\n-\t\tca->drv = git_path_check_convert(ccheck + 2);\n+\t\tca->ident = git_path_check_ident(&result[1]);\n+\t\tca->drv = git_path_check_convert(&result[2]);\n \t\tif (ca->crlf_action != CRLF_BINARY) {\n-\t\t\tenum eol eol_attr = git_path_check_eol(ccheck + 3);\n+\t\t\tenum eol eol_attr = git_path_check_eol(&result[3]);\n \t\t\tif (ca->crlf_action == CRLF_AUTO && eol_attr == EOL_LF)\n \t\t\t\tca->crlf_action = CRLF_AUTO_INPUT;\n \t\t\telse if (ca->crlf_action == CRLF_AUTO && eol_attr == EOL_CRLF)\ndiff --git a/ll-merge.c b/ll-merge.c\nindex bc6479c..d71354a 100644\n--- a/ll-merge.c\n+++ b/ll-merge.c\n@@ -353,12 +353,14 @@ int ll_merge(mmbuffer_t *result_buf,\n \t     mmfile_t *theirs, const char *their_label,\n \t     const struct ll_merge_options *opts)\n {\n-\tstatic struct git_attr_check *check;\n \tstatic const struct ll_merge_options default_opts;\n \tconst char *ll_driver_name = NULL;\n \tint marker_size = DEFAULT_CONFLICT_MARKER_SIZE;\n \tconst struct ll_merge_driver *driver;\n \n+\tstatic struct git_attr_check *check;\n+\tstruct git_attr_result result[2];\n+\n \tif (!opts)\n \t\topts = &default_opts;\n \n@@ -368,13 +370,12 @@ int ll_merge(mmbuffer_t *result_buf,\n \t\tnormalize_file(theirs, path);\n \t}\n \n-\tif (!check)\n-\t\tcheck = git_attr_check_initl(\"merge\", \"conflict-marker-size\", NULL);\n+\tgit_attr_check_initl(&check, \"merge\", \"conflict-marker-size\", NULL);\n \n-\tif (!git_check_attr(path, check)) {\n-\t\tll_driver_name = check->check[0].value;\n-\t\tif (check->check[1].value) {\n-\t\t\tmarker_size = atoi(check->check[1].value);\n+\tif (!git_check_attr(path, check, result)) {\n+\t\tll_driver_name = result[0].value;\n+\t\tif (result[1].value) {\n+\t\t\tmarker_size = atoi(result[1].value);\n \t\t\tif (marker_size <= 0)\n \t\t\t\tmarker_size = DEFAULT_CONFLICT_MARKER_SIZE;\n \t\t}\n@@ -395,11 +396,12 @@ int ll_merge_marker_size(const char *path)\n {\n \tstatic struct git_attr_check *check;\n \tint marker_size = DEFAULT_CONFLICT_MARKER_SIZE;\n+\tstruct git_attr_result result[1];\n+\n+\tgit_attr_check_initl(&check, \"conflict-marker-size\", NULL);\n \n-\tif (!check)\n-\t\tcheck = git_attr_check_initl(\"conflict-marker-size\", NULL);\n-\tif (!git_check_attr(path, check) && check->check[0].value) {\n-\t\tmarker_size = atoi(check->check[0].value);\n+\tif (!git_check_attr(path, check, result) && !ATTR_UNSET(result[0].value)) {\n+\t\tmarker_size = atoi(result[0].value);\n \t\tif (marker_size <= 0)\n \t\t\tmarker_size = DEFAULT_CONFLICT_MARKER_SIZE;\n \t}\ndiff --git a/userdiff.c b/userdiff.c\nindex 46dfd32..1d6d363 100644\n--- a/userdiff.c\n+++ b/userdiff.c\n@@ -263,21 +263,23 @@ struct userdiff_driver *userdiff_find_by_name(const char *name) {\n struct userdiff_driver *userdiff_find_by_path(const char *path)\n {\n \tstatic struct git_attr_check *check;\n+\tstruct git_attr_result result[1];\n \n-\tif (!check)\n-\t\tcheck = git_attr_check_initl(\"diff\", NULL);\n \tif (!path)\n \t\treturn NULL;\n-\tif (git_check_attr(path, check))\n+\n+\tgit_attr_check_initl(&check, \"diff\", NULL);\n+\n+\tif (git_check_attr(path, check, result))\n \t\treturn NULL;\n \n-\tif (ATTR_TRUE(check->check[0].value))\n+\tif (ATTR_TRUE(result[0].value))\n \t\treturn &driver_true;\n-\tif (ATTR_FALSE(check->check[0].value))\n+\tif (ATTR_FALSE(result[0].value))\n \t\treturn &driver_false;\n-\tif (ATTR_UNSET(check->check[0].value))\n+\tif (ATTR_UNSET(result[0].value))\n \t\treturn NULL;\n-\treturn userdiff_find_by_name(check->check[0].value);\n+\treturn userdiff_find_by_name(result[0].value);\n }\n \n struct userdiff_driver *userdiff_get_textconv(struct userdiff_driver *driver)\ndiff --git a/ws.c b/ws.c\nindex bb3270c..7e355c4 100644\n--- a/ws.c\n+++ b/ws.c\n@@ -74,14 +74,14 @@ unsigned parse_whitespace_rule(const char *string)\n unsigned whitespace_rule(const char *pathname)\n {\n \tstatic struct git_attr_check *attr_whitespace_rule;\n+\tstruct git_attr_result result[1];\n \n-\tif (!attr_whitespace_rule)\n-\t\tattr_whitespace_rule = git_attr_check_initl(\"whitespace\", NULL);\n+\tgit_attr_check_initl(&attr_whitespace_rule, \"whitespace\", NULL);\n \n-\tif (!git_check_attr(pathname, attr_whitespace_rule)) {\n+\tif (!git_check_attr(pathname, attr_whitespace_rule, result)) {\n \t\tconst char *value;\n \n-\t\tvalue = attr_whitespace_rule->check[0].value;\n+\t\tvalue = result[0].value;\n \t\tif (ATTR_TRUE(value)) {\n \t\t\t/* true (whitespace) */\n \t\t\tunsigned all_rule = ws_tab_width(whitespace_rule_cfg);\n-- \n2.10.1.508.g6572022\n\n"},{"id":"304928","messageId":"CAGZ79kZ6ENdDPZ79+tyVoXiyyN+o7fwkd8MeHDuvvK7yJqf_Dg@mail.gmail.com","threadId":"44358","inReplyTo":"8a91d287-5413-3f8f-9d0e-edd67fb36557@ramsayjones.plus.com","subject":"Re: [PATCH 28/36] attr: keep attr stack for each check","fromName":"Stefan Beller","fromEmail":"sbeller@google.com","sentAt":"2016-10-26T23:10:03Z","receivedAt":"2016-10-26T23:10:09Z","isPatch":true,"sender":{"key":"stefanbeller@gmail.com","avatar":"https://avatars.githubusercontent.com/u/455868?v=4"},"body":"On Sun, Oct 23, 2016 at 8:10 AM, Ramsay Jones\n<ramsay@ramsayjones.plus.com> wrote:\n>> +\n>> +struct hashmap all_attr_stacks;\n>> +int all_attr_stacks_init;\n>\n> Mark symbols 'all_attr_stacks' and 'all_attr_stacks_init' with\n> the static keyword. (ie. these are file-local symbols).\n>\n> ATB,\n> Ramsay Jones\n>\n\nThis fix will appear in a resend of the series.\n\nThanks,\nStefan\n"},{"id":"304929","messageId":"xmqqoa26aek6.fsf@gitster.mtv.corp.google.com","threadId":"44358","inReplyTo":"20161026224104.31844-1-sbeller@google.com","subject":"Re: [PATCHv2 1/2] attr: convert to new threadsafe API","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2016-10-26T23:14:17Z","receivedAt":"2016-10-26T23:14:28Z","isPatch":false,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Stefan Beller <sbeller@google.com> writes:\n\n> @@ -53,19 +57,32 @@ value of the attribute for the path.\n>  Querying Specific Attributes\n>  ----------------------------\n>  \n> -* Prepare `struct git_attr_check` using git_attr_check_initl()\n> +* Prepare a `struct git_attr_check` using `git_attr_check_initl()`\n>    function, enumerating the names of attributes whose values you are\n>    interested in, terminated with a NULL pointer.  Alternatively, an\n> -  empty `struct git_attr_check` can be prepared by calling\n> -  `git_attr_check_alloc()` function and then attributes you want to\n> -  ask about can be added to it with `git_attr_check_append()`\n> -  function.\n> -\n> -* Call `git_check_attr()` to check the attributes for the path.\n> -\n> -* Inspect `git_attr_check` structure to see how each of the\n> -  attribute in the array is defined for the path.\n> -\n> +  empty `struct git_attr_check` as allocated by git_attr_check_alloc()\n> +  can be prepared by calling `git_attr_check_alloc()` function and\n> +  then attributes you want to ask about can be added to it with\n> +  `git_attr_check_append()` function.\n\nI think that my version that was discarded forbade appending once\nyou started to use the check for querying, because the check was\nmeant to be used as a key for an attr-stack and the check-specific\nattr-stack was planned to keep only the attrs the check is\ninterested in (and appending is to change the set of attrs the check\nis interested in, invalidating the attr-stack at that point).  \n\nIf you lost that restriction, that is good (I didn't check the\nimplementation, though).  Otherwise we'd need to say something here.\n\n> +  Both ways with `git_attr_check_initl()` as well as the\n> +  alloc and append route are thread safe, i.e. you can call it\n> +  from different threads at the same time; when check determines\n> +  the initialzisation is still needed, the threads will use a\n\ninitialization?\n\n> +  single global mutex to perform the initialization just once, the\n> +  others will wait on the the thread to actually perform the\n> +  initialization.\n> +\n> +* Allocate an array of `struct git_attr_result` either statically on the\n> +  as a variable on the stack or dynamically via `git_attr_result_alloc`\n\nGrammo?  \"either on the stack, or dynamically in the heap\"?\n\n> +  when the result size is not known at compile time. The call to initialize\n> +  the result is not thread safe, because different threads need their\n> +  own thread local result anyway.\n\nHaving result defined statically is not thread safe for that\nreason.  It is not clear what you mean by \"The call to initialize\nthe result\"; having it on the stack or have one dynamically on the\nheap ought to be thread safe.\n\n> +* Call `git_check_attr()` to check the attributes for the path,\n> +  the given `git_attr_result` will be filled with the result.\n> +\n> +* Inspect each `git_attr_result` structure to see how\n> +  each of the attribute in the array is defined for the path.\n>  \n>  Example\n>  -------\n> @@ -76,28 +93,23 @@ To see how attributes \"crlf\" and \"ident\" are set for different paths.\n>    we are checking two attributes):\n>  \n>  ------------\n> -static struct git_attr_check *check;\n> -static void setup_check(void)\n> -{\n> -\tif (check)\n> -\t\treturn; /* already done */\n> -\tcheck = git_attr_check_initl(\"crlf\", \"ident\", NULL);\n> -}\n> +\tstatic struct git_attr_check *check;\n> +\tgit_attr_check_initl(check, \"crlf\", \"ident\", NULL);\n\nI think you are still missing \"&\" here.\n\n>  ------------\n>  \n>  . Call `git_check_attr()` with the prepared `struct git_attr_check`:\n>  \n>  ------------\n>  \tconst char *path;\n> +\tstruct git_attr_result result[2];\n>  \n> -\tsetup_check();\n> -\tgit_check_attr(path, check);\n> +\tgit_check_attr(path, check, result);\n>  ------------\n>  \n> -. Act on `.value` member of the result, left in `check->check[]`:\n> +. Act on `result.value[]`:\n>  \n>  ------------\n> -\tconst char *value = check->check[0].value;\n> +\tconst char *value = result.value[0];\n>  \n>  \tif (ATTR_TRUE(value)) {\n>  \t\tThe attribute is Set, by listing only the name of the\n> @@ -123,12 +135,15 @@ the first step in the above would be different.\n>  static struct git_attr_check *check;\n>  static void setup_check(const char **argv)\n>  {\n> +\tif (check)\n> +\t\treturn; /* already done */\n>  \tcheck = git_attr_check_alloc();\n\nYou may want to say that this is thread-unsafe.\n\n>  \twhile (*argv) {\n>  \t\tstruct git_attr *attr = git_attr(*argv);\n>  \t\tgit_attr_check_append(check, attr);\n>  \t\targv++;\n>  \t}\n> +\tstruct git_attr_result *result = git_attr_result_alloc(check);\n>  }\n>  ------------\n>  \n> @@ -138,17 +153,20 @@ Querying All Attributes\n>  \n>  To get the values of all attributes associated with a file:\n>  \n> +* Setup a local variables for the question\n> +  `struct git_attr_check` as well as a pointer where the result\n> +  `struct git_attr_result` will be stored.\n> +* Call `git_all_attrs()`.\n\nHmph, the caller does not know what attribute it is interested in,\nand it is unclear \"how\" the former needs to be set up from this\ndescription.  Should it prepare an empty one that can be appended?\n\nFor the same reason, the size of the result is also unknown at the\ncallsite.\n\nFor these reasons, I thought git-all-attrs would need to use a\ndifferent calling convention from the usual \"query for these\nattributes -- get result for them\" API.  Even if the same attr_check\nand attr_result structures are used, they need to be initialized in\nvery different way from the normal calls, and this part of the doc\nneeds to explain how.  Perhaps it is sufficient to say \"just declare\ntwo pointer variables that point at these structures and a pair of\nnewly allocated structures of these types will be stored there\" or\nsomething.\n\nIt does not even have to use the usual \"struct attr_check\" \"struct\nattr_result\" pair (I am not suggesting to use different convention\nonly to be different; I am merely pointing out that you have that\nlattitude, as the usage pattern for all-attrs is so different from\nthe usual query).\n\n"},{"id":"304932","messageId":"CAGZ79kaR4DddoHQNUUvRAY=_PK5qqS=ws_Wkfa-EXT2seN5b=A@mail.gmail.com","threadId":"44358","inReplyTo":"xmqqoa26aek6.fsf@gitster.mtv.corp.google.com","subject":"Re: [PATCHv2 1/2] attr: convert to new threadsafe API","fromName":"Stefan Beller","fromEmail":"sbeller@google.com","sentAt":"2016-10-27T00:08:14Z","receivedAt":"2016-10-27T00:08:23Z","isPatch":false,"sender":{"key":"stefanbeller@gmail.com","avatar":"https://avatars.githubusercontent.com/u/455868?v=4"},"body":"On Wed, Oct 26, 2016 at 4:14 PM, Junio C Hamano <gitster@pobox.com> wrote:\n> Stefan Beller <sbeller@google.com> writes:\n>\n>> @@ -53,19 +57,32 @@ value of the attribute for the path.\n>>  Querying Specific Attributes\n>>  ----------------------------\n>>\n>> -* Prepare `struct git_attr_check` using git_attr_check_initl()\n>> +* Prepare a `struct git_attr_check` using `git_attr_check_initl()`\n>>    function, enumerating the names of attributes whose values you are\n>>    interested in, terminated with a NULL pointer.  Alternatively, an\n>> -  empty `struct git_attr_check` can be prepared by calling\n>> -  `git_attr_check_alloc()` function and then attributes you want to\n>> -  ask about can be added to it with `git_attr_check_append()`\n>> -  function.\n>> -\n>> -* Call `git_check_attr()` to check the attributes for the path.\n>> -\n>> -* Inspect `git_attr_check` structure to see how each of the\n>> -  attribute in the array is defined for the path.\n>> -\n>> +  empty `struct git_attr_check` as allocated by git_attr_check_alloc()\n>> +  can be prepared by calling `git_attr_check_alloc()` function and\n>> +  then attributes you want to ask about can be added to it with\n>> +  `git_attr_check_append()` function.\n>\n> I think that my version that was discarded forbade appending once\n> you started to use the check for querying, because the check was\n> meant to be used as a key for an attr-stack and the check-specific\n> attr-stack was planned to keep only the attrs the check is\n> interested in (and appending is to change the set of attrs the check\n> is interested in, invalidating the attr-stack at that point).\n>\n> If you lost that restriction, that is good (I didn't check the\n> implementation, though).  Otherwise we'd need to say something here.\n\nThat restriction still applies. Though I think mentioning it in the\nparagraph where we describe querying makes more sense.\n\n> initialization?\n\ndone\n\n\n>\n> Grammo?  \"either on the stack, or dynamically in the heap\"?\n\ndone\n\n>\n> Having result defined statically is not thread safe for that\n> reason.  It is not clear what you mean by \"The call to initialize\n> the result\"; having it on the stack or have one dynamically on the\n> heap ought to be thread safe.\n\ndone\n\n>> -}\n>> +     static struct git_attr_check *check;\n>> +     git_attr_check_initl(check, \"crlf\", \"ident\", NULL);\n>\n> I think you are still missing \"&\" here.\n\ndone\n\n>> +     if (check)\n>> +             return; /* already done */\n>>       check = git_attr_check_alloc();\n>\n> You may want to say that this is thread-unsafe.\n\nIt is not; see the implementation:\n\nvoid git_attr_check_append(struct git_attr_check *check,\n               const struct git_attr *attr)\n{\n    int i;\n    if (check->finalized)\n        die(\"BUG: append after git_attr_check structure is finalized\");\n    if (!attr_check_is_dynamic(check))\n        die(\"BUG: appending to a statically initialized git_attr_check\");\n    attr_lock();\n    for (i = 0; i < check->check_nr; i++)\n        if (check->attr[i] == attr)\n            break;\n    if (i == check->check_nr) {\n        ALLOC_GROW(check->attr, check->check_nr + 1, check->check_alloc);\n        check->attr[check->check_nr++] = attr;\n    }\n    attr_unlock();\n}\n\n>> +* Call `git_all_attrs()`.\n>\n> Hmph, the caller does not know what attribute it is interested in,\n> and it is unclear \"how\" the former needs to be set up from this\n> description.  Should it prepare an empty one that can be appended?\n>\n\nGood point on clarifying this one. It is just needed to have\nNULL pointers around:\n\n    struct git_attr_check *check = NULL;\n    struct git_attr_result *result = NULL;\n    git_all_attrs(full_path, &local_check, &result);\n    // proceed as in the case above\n\nAll comments from above yield the following diff:\n\ndiff --git a/Documentation/technical/api-gitattributes.txt\nb/Documentation/technical/api-gitattributes.txt\nindex 4d8ef93..d221736 100644\n--- a/Documentation/technical/api-gitattributes.txt\n+++ b/Documentation/technical/api-gitattributes.txt\n@@ -67,19 +67,21 @@ Querying Specific Attributes\n   Both ways with `git_attr_check_initl()` as well as the\n   alloc and append route are thread safe, i.e. you can call it\n   from different threads at the same time; when check determines\n-  the initialzisation is still needed, the threads will use a\n+  the initialization is still needed, the threads will use a\n   single global mutex to perform the initialization just once, the\n   others will wait on the the thread to actually perform the\n   initialization.\n\n-* Allocate an array of `struct git_attr_result` either statically on the\n-  as a variable on the stack or dynamically via `git_attr_result_alloc`\n-  when the result size is not known at compile time. The call to initialize\n+* Allocate an array of `struct git_attr_result` either on the stack\n+  or via `git_attr_result_alloc` on the heap when the result size\n+  is not known at compile time. The call to initialize\n   the result is not thread safe, because different threads need their\n   own thread local result anyway.\n\n * Call `git_check_attr()` to check the attributes for the path,\n   the given `git_attr_result` will be filled with the result.\n+  You must not change the `struct git_attr_check` after calling\n+  `git_check_attr()`.\n\n * Inspect each `git_attr_result` structure to see how\n   each of the attribute in the array is defined for the path.\n@@ -103,7 +105,7 @@ To see how attributes \"crlf\" and \"ident\" are set\nfor different paths.\n         const char *path;\n         struct git_attr_result result[2];\n\n-        git_check_attr(path, check, result);\n+        git_check_attr(path, &check, result);\n ------------\n\n . Act on `result.value[]`:\n@@ -155,10 +157,20 @@ To get the values of all attributes associated\nwith a file:\n\n * Setup a local variables for the question\n   `struct git_attr_check` as well as a pointer where the result\n-  `struct git_attr_result` will be stored.\n+  `struct git_attr_result` will be stored. Both should be initialized\n+  to NULL.\n+\n+------------\n+  struct git_attr_check *check = NULL;\n+  struct git_attr_result *result = NULL;\n+------------\n\n * Call `git_all_attrs()`.\n\n+------------\n+  git_all_attrs(full_path, &check, &result);\n+------------\n+\n * Iterate over the `git_attr_check.attr[]` array to examine the\n   attribute names.  The name of the attribute described by a\n   `git_attr_check.attr[]` object can be retrieved via\n"},{"id":"304933","messageId":"xmqqfuniabo6.fsf@gitster.mtv.corp.google.com","threadId":"44358","inReplyTo":"CAGZ79kaR4DddoHQNUUvRAY=_PK5qqS=ws_Wkfa-EXT2seN5b=A@mail.gmail.com","subject":"Re: [PATCHv2 1/2] attr: convert to new threadsafe API","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2016-10-27T00:16:41Z","receivedAt":"2016-10-27T00:16:50Z","isPatch":false,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Stefan Beller <sbeller@google.com> writes:\n\n> +* Allocate an array of `struct git_attr_result` either on the stack\n> +  or via `git_attr_result_alloc` on the heap when the result size\n> +  is not known at compile time. The call to initialize\n>    the result is not thread safe, because different threads need their\n>    own thread local result anyway.\n\nDo you want to keep the last sentence?  \"The call to initialize the\nresult is not thread safe...\"?  Is that true?\n\n> @@ -103,7 +105,7 @@ To see how attributes \"crlf\" and \"ident\" are set\n> for different paths.\n>          const char *path;\n>          struct git_attr_result result[2];\n>\n> -        git_check_attr(path, check, result);\n> +        git_check_attr(path, &check, result);\n\nWhat's the point of this change?  Isn't check typically a pointer\nalready?\n\n"},{"id":"304934","messageId":"CAGZ79kY00mtzwL4kSz6oXSAb-39=axeTP-ax_FxWcL7Z-sAA4w@mail.gmail.com","threadId":"44358","inReplyTo":"xmqqfuniabo6.fsf@gitster.mtv.corp.google.com","subject":"Re: [PATCHv2 1/2] attr: convert to new threadsafe API","fromName":"Stefan Beller","fromEmail":"sbeller@google.com","sentAt":"2016-10-27T00:22:05Z","receivedAt":"2016-10-27T00:22:11Z","isPatch":false,"sender":{"key":"stefanbeller@gmail.com","avatar":"https://avatars.githubusercontent.com/u/455868?v=4"},"body":"On Wed, Oct 26, 2016 at 5:16 PM, Junio C Hamano <gitster@pobox.com> wrote:\n> Stefan Beller <sbeller@google.com> writes:\n>\n>> +* Allocate an array of `struct git_attr_result` either on the stack\n>> +  or via `git_attr_result_alloc` on the heap when the result size\n>> +  is not known at compile time. The call to initialize\n>>    the result is not thread safe, because different threads need their\n>>    own thread local result anyway.\n>\n> Do you want to keep the last sentence?  \"The call to initialize the\n> result is not thread safe...\"?  Is that true?\n\nI'll drop that sentence, as it overstates the situation.\n\nTo explain, you can either have:\n    struct git_attr_result result[2];\nor\n    struct git_attr_result *result = git_attr_result_alloc(check);\nand both are running just fine in a thread. However you should not\nmake that variable static. But maybe that is too much common sense\nand hence confusing.\n\n>\n>> @@ -103,7 +105,7 @@ To see how attributes \"crlf\" and \"ident\" are set\n>> for different paths.\n>>          const char *path;\n>>          struct git_attr_result result[2];\n>>\n>> -        git_check_attr(path, check, result);\n>> +        git_check_attr(path, &check, result);\n>\n> What's the point of this change?  Isn't check typically a pointer\n> already?\n\nThis ought to go to\n\n    git_attr_check_initl(&check, \"crlf\", \"ident\", NULL);\n\ninstead.\n\n>\n"},{"id":"304936","messageId":"xmqqbmy6aa6b.fsf@gitster.mtv.corp.google.com","threadId":"44358","inReplyTo":"20161026224104.31844-1-sbeller@google.com","subject":"Re: [PATCHv2 1/2] attr: convert to new threadsafe API","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2016-10-27T00:49:00Z","receivedAt":"2016-10-27T00:49:10Z","isPatch":false,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Stefan Beller <sbeller@google.com> writes:\n\n>  extern struct git_attr *git_attr(const char *);\n> ...\n> +extern void git_attr_check_append(struct git_attr_check *,\n> +\t\t\t\t  const struct git_attr *);\n\nAnother thing.  Do we still need to expose git_attr() to the outside\ncallers?  If we change git_attr_check_append() to take \"const char *\"\nas its second parameter, can we retire it from the public interface?\n\nIt being an \"intern\" function, by definition it is not thread-safe.\nIts use from prepare_attr_stack() inside git_check_attr() that takes\nthe Big Attribute Subsystem Lock is safe, but the callers that do\n\n\tstruct git_attr_check *check = ...;\n\tstruct git_attr *text_attr = git_attr(\"text\");\n\n\tgit_attr_check_append(check, text_attr);\n\nwould risk racing to register the same entry to the \"all the\nattributes in the known universe\" table.  \n\nIf the attribute API does not have to expose git_attr(const char *)\nto the external callers at all, that would be ideal.  Otherwise, we\nwould need to rename the current git_attr() that is used internally\nunder the Big Lock to \n\n    static struct git_attr *git_attr_locked(const char*)\n\nthat is defined inside attr.c, and then provide the external version\nas a thin wrapper that calls it under the Big Lock.\n\n\n"},{"id":"304937","messageId":"xmqq7f8uaa4g.fsf@gitster.mtv.corp.google.com","threadId":"44358","inReplyTo":"CAGZ79kY00mtzwL4kSz6oXSAb-39=axeTP-ax_FxWcL7Z-sAA4w@mail.gmail.com","subject":"Re: [PATCHv2 1/2] attr: convert to new threadsafe API","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2016-10-27T00:50:07Z","receivedAt":"2016-10-27T00:50:16Z","isPatch":false,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Stefan Beller <sbeller@google.com> writes:\n\n> To explain, you can either have:\n>     struct git_attr_result result[2];\n> or\n>     struct git_attr_result *result = git_attr_result_alloc(check);\n> and both are running just fine in a thread. However you should not\n> make that variable static. But maybe that is too much common sense\n> and hence confusing.\n\nYup, if you spelled it out that does make sense, but the description\ngiven in the patch was just misleading.\n\nThanks.\n"},{"id":"304940","messageId":"CAGZ79kYiVxJwtAYsgks8LVNWvawWxdy=8Xpdrki-C_u15C3Z9w@mail.gmail.com","threadId":"44358","inReplyTo":"xmqqbmy6aa6b.fsf@gitster.mtv.corp.google.com","subject":"Re: [PATCHv2 1/2] attr: convert to new threadsafe API","fromName":"Stefan Beller","fromEmail":"sbeller@google.com","sentAt":"2016-10-27T02:19:54Z","receivedAt":"2016-10-27T02:20:03Z","isPatch":false,"sender":{"key":"stefanbeller@gmail.com","avatar":"https://avatars.githubusercontent.com/u/455868?v=4"},"body":"On Wed, Oct 26, 2016 at 5:49 PM, Junio C Hamano <gitster@pobox.com> wrote:\n> Stefan Beller <sbeller@google.com> writes:\n>\n>>  extern struct git_attr *git_attr(const char *);\n>> ...\n>> +extern void git_attr_check_append(struct git_attr_check *,\n>> +                               const struct git_attr *);\n>\n> Another thing.  Do we still need to expose git_attr() to the outside\n> callers?  If we change git_attr_check_append() to take \"const char *\"\n> as its second parameter, can we retire it from the public interface?\n>\n> It being an \"intern\" function, by definition it is not thread-safe.\n> Its use from prepare_attr_stack() inside git_check_attr() that takes\n> the Big Attribute Subsystem Lock is safe, but the callers that do\n>\n>         struct git_attr_check *check = ...;\n>         struct git_attr *text_attr = git_attr(\"text\");\n>\n>         git_attr_check_append(check, text_attr);\n>\n> would risk racing to register the same entry to the \"all the\n> attributes in the known universe\" table.\n>\n> If the attribute API does not have to expose git_attr(const char *)\n> to the external callers at all, that would be ideal.  Otherwise, we\n> would need to rename the current git_attr() that is used internally\n> under the Big Lock to\n>\n>     static struct git_attr *git_attr_locked(const char*)\n>\n> that is defined inside attr.c, and then provide the external version\n> as a thin wrapper that calls it under the Big Lock.\n>\n>\n\nYeah, I can make it work without exposing struct git_attr.\nHowever then the struct git_attr_check will contain more of\ninternals exposed. (As the header file did not know the size\nof git_attr, the patch under discussion even must use a double pointer\nto a git_attr inside the git_attr_check as the git_attr size is unknown)\n\nSo I'll look into removing that struct git_attr completely.\n"},{"id":"304943","messageId":"xmqq37jia0p8.fsf@gitster.mtv.corp.google.com","threadId":"44358","inReplyTo":"CAGZ79kYiVxJwtAYsgks8LVNWvawWxdy=8Xpdrki-C_u15C3Z9w@mail.gmail.com","subject":"Re: [PATCHv2 1/2] attr: convert to new threadsafe API","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2016-10-27T04:13:39Z","receivedAt":"2016-10-27T04:13:50Z","isPatch":false,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Stefan Beller <sbeller@google.com> writes:\n\n> Yeah, I can make it work without exposing struct git_attr.\n\nYou completely misunderstood me.  \"struct git_attr\" MUST be visible\nto the users so that they can ask for the name in git_check.attr[0].\n\nWhat would be nice to hide if you can is the function to intern a\nstring into a pointer to struct git_attr, i.e. git_attr() function.\n\n"},{"id":"304945","messageId":"xmqqy41a8hxj.fsf@gitster.mtv.corp.google.com","threadId":"44358","inReplyTo":"xmqq37jia0p8.fsf@gitster.mtv.corp.google.com","subject":"Re: [PATCHv2 1/2] attr: convert to new threadsafe API","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2016-10-27T05:44:24Z","receivedAt":"2016-10-27T05:44:33Z","isPatch":false,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Junio C Hamano <gitster@pobox.com> writes:\n\n> Stefan Beller <sbeller@google.com> writes:\n>\n>> Yeah, I can make it work without exposing struct git_attr.\n>\n> You completely misunderstood me.  \"struct git_attr\" MUST be visible\n> to the users so that they can ask for the name in git_check.attr[0].\n>\n> What would be nice to hide if you can is the function to intern a\n> string into a pointer to struct git_attr, i.e. git_attr() function.\n\nEven though that was not the primary point of my suggestion, I\nactually think it is OK to make \"struct git_attr\" a structure that\nis opaque to the users of the API if you wanted to.  The attr_check\nstructure will have an array of pointers to \"struct git_attr\", and\nthe structure definition may be visible to the public attr.h header,\nbut the API users won't have to be able to dereference the pointer\n\"struct git_attr *\".  git_check.attr[0] would be a pointer to an\nopaque structure from API users' point of view, that can be passed\nto API function git_attr_name() to read its string.\n\nWhat is nice to hide is the constructor of the structure.  What it,\ni.e. \"struct git_attr *git_attr(const char *)\", needs to do is to\n(1) see if the attribute object with the same name already exists in\nthe table of \"all known attributes in the universe\", and if there\nis, return that instance, (2) otherwise create a new attribute\nobject, register it to the table and return it.  And it needs to do\nit in a way that is thread-safe.\n\nIf we have to give access to it to the API users, then we'd need to\nacquire and release the Big Attr Lock per each call.  \n\nThe calls to git_attr() you need to make in your implementation will\nbe made from two codepaths:\n\n * check_initl() acquires the Big Attr Lock, creates a check struct,\n   makes multiple calls to git_attr() to construct the necessary\n   git_attr instances to fill the array and then releases the lock,\n   so the git_attr() constructor does not have to be protected for\n   concurrent access.\n\n * check_attr() acquires the Big Attr Lock, calls down to\n   prepare_attr_stack() as necessary to parse .gitattributes files\n   found in the directory hierarchy, which makes calls to git_attr()\n   to record the attributes found in the file.  Then it does the\n   matching to fill results[] array and releases the lock.  Again,\n   git_attr() constructors are called under the lock, so there is no\n   need for a separate lock.\n\nIf these are the only callpaths that reach git_attr() to construct\nnew attribute objects, it would mean that you can make this private\nto attr subsystem and hide it from the users of the API.\n\nOtherwise, you would need to rename the git_attr() constructor that\nused internally under the Big Lock to\n\n    static struct git_attr *git_attr_locked(const char *);\n\nthat is defined inside attr.c, and then provide the external version\nas a thin wrapper that calls it under the Big Lock, i.e.\n\n    struct git_attr *git_attr(const char *s)\n    {\n\tstruct git_attr *attr;\n\ttake_big_attr_lock();\n\tattr = git_attr_locked(s);\n\trelease_big_attr_lock();\n\treturn attr;\n    }\n\nThat will have to make the big attr lock busier, and it would be\ngood if we can avoid it.  That is where my \"can we hide git_attr()\nconstructor?\" comes from.\n\n"},{"id":"304977","messageId":"xmqqzilp63yh.fsf@gitster.mtv.corp.google.com","threadId":"44358","inReplyTo":"20161022233225.8883-33-sbeller@google.com","subject":"Re: [PATCH 32/36] pathspec: allow querying for attributes","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2016-10-27T18:29:10Z","receivedAt":"2016-10-27T18:29:18Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Stefan Beller <sbeller@google.com> writes:\n\n> The pathspec mechanism is extended via the new\n> \":(attr:eol=input)pattern/to/match\" syntax to filter paths so that it\n> requires paths to not just match the given pattern but also have the\n> specified attrs attached for them to be chosen.\n\nI was looking at preload-index.c and its history again, because I\nwanted to ensure that Lars's process filter stuff introduces no\nunexpected interactions with getting multi-threaded, similar to the\nproblem we had in the earlier incarnations of this step, which we\nworked around with <xmqqshwvvaxq.fsf@gitster.mtv.corp.google.com>\n\"pathspec: disable preload-index when attribute pathspec magic is in\nuse\".\n\nI think that Lars's series is safe, because the only thing among\nwhat preload-index.c::preload_thread() does that can go outside the\nsimple stat data and pattern matching with the pathname is the\nracy-git check in ie_match_stat(), and the object store access in\nthat call was explicitly disabled by 7c4ea599b0 (\"Fix index\npreloading for racy dirty case\", 2008-11-17) long time ago.\n\nBy passing CE_MATCH_RACY_IS_DIRTY option to the call, this caller\neffectively says \"A cache entry whose stat data matches may be\nactually dirty when the timestamp is racy, in which case we usually\ncompare data to determine if it really is clean, but it is OK to err\non the safe and lazy side by declaring it dirty and not marking it\nup-to-date while we are preloading.  Do not bother to go to the\nobject store\".\n\nThe reason why I am bringing this up in this discussion thread on\nthis patch is because I wonder if we would benefit by a similar\n\"let's not do too involved things and be cheap by erring on the safe\nand lazy side\" strategy in the call to ce_path_match() call made in\nthis function to avoid making calls to the attr subsystem.\n\nIn other words, would it help the system by either simplifying the\nprocessing done or reducing the cycle spent in preload_thread() if\nwe could tell ce_path_match() \"A pathspec we are checking may\nrequire not just the pattern to match but also attributes given to\nthe path to satisfy the criteria, but for the purpose of preloading,\npretend that the attribute satisfies the match criteria\" (or\n\"pretend that it does not match\"), thereby not having to make any\ncall into the attribute subsystem at all from this codepath?\n\nThe strategy this round takes to make it unnecessary to punt\npreloading (i.e. dropping \"pathspec: disable preload-index when\nattribute pathspec magic is in use\" patch the old series had) is to\nmake the attribute subsystem thread-safe.  But that thread-safety in\nthe initial round is based on a single Big Attribute Lock, so it may\nturn out that the end result performs better for this codepath if we\ndid not to make any call into the attribute subsystem.\n\nI am probably being two step ahead of ourselves by saying the above,\nwhich is just something to keep in mind as a possible solution if\nperformance in this preload codepath becomes an issue when the\npathspec has attributes match specified.\n"},{"id":"304991","messageId":"CAGZ79kb+BRPPA8NGKXq_9cb_ob9CK0vkuX0gZV7-qJ-pcYVCzg@mail.gmail.com","threadId":"44358","inReplyTo":"CAGZ79ka_zr_NXKoxC45swFrj168fP6S7_nQ1jjcfPOtTN4Jd1A@mail.gmail.com","subject":"Re: [PATCH 17/36] attr: expose validity check for attribute names","fromName":"Stefan Beller","fromEmail":"sbeller@google.com","sentAt":"2016-10-27T20:57:30Z","receivedAt":"2016-10-27T20:57:35Z","isPatch":true,"sender":{"key":"stefanbeller@gmail.com","avatar":"https://avatars.githubusercontent.com/u/455868?v=4"},"body":"On Mon, Oct 24, 2016 at 2:07 PM, Stefan Beller <sbeller@google.com> wrote:\n> On Sun, Oct 23, 2016 at 8:07 AM, Ramsay Jones\n> <ramsay@ramsayjones.plus.com> wrote:\n>>\n>>\n>> On 23/10/16 00:32, Stefan Beller wrote:\n>>> From: Junio C Hamano <gitster@pobox.com>\n>>>\n>>> Export attr_name_valid() function, and a helper function that\n>>> returns the message to be given when a given <name, len> pair\n>>> is not a good name for an attribute.\n>>>\n>>> We could later update the message to exactly spell out what the\n>>> rules for a good attribute name are, etc.\n>>>\n>>> Signed-off-by: Junio C Hamano <gitster@pobox.com>\n>>> Signed-off-by: Stefan Beller <sbeller@google.com>\n>>> ---\n>>\n>> [snip]\n>>\n>>> +extern int attr_name_valid(const char *name, size_t namelen);\n>>> +extern void invalid_attr_name_message(struct strbuf *, const char *, int);\n>>> +\n>>\n>> The symbol 'attr_name_valid()' is not used outside of attr.c, even\n>> by the end of this series. Do you expect this function to be used\n>> in any future series? (The export is deliberate and it certainly\n>> seems like it should be part of the public interface, but ...)\n>>\n\nrefactoring this series again, I will make use of attr_name_valid\noutside of attr.c, so I'll keep this patch.\n"},{"id":"304997","messageId":"CAGZ79kYTOhWbmk27x3JwwBqPedmw-sKbP9S4FJqcM6yT_uQKiA@mail.gmail.com","threadId":"44358","inReplyTo":"CACsJy8DhsaTY1w_e-0O5d4KLxr4Gmo3g9rDagQq1ooSHywcRoQ@mail.gmail.com","subject":"Re: [PATCH 32/36] pathspec: allow querying for attributes","fromName":"Stefan Beller","fromEmail":"sbeller@google.com","sentAt":"2016-10-27T21:32:38Z","receivedAt":"2016-10-27T21:32:45Z","isPatch":true,"sender":{"key":"stefanbeller@gmail.com","avatar":"https://avatars.githubusercontent.com/u/455868?v=4"},"body":"On Wed, Oct 26, 2016 at 6:33 AM, Duy Nguyen <pclouds@gmail.com> wrote:\n> (sorry if this should have been answered if I went through the series\n> patch by patch, I wanted to do a proper review but finally have to\n> admit to myself I won't, so I just skim through a single giant diff\n> instead)\n>\n> On Sun, Oct 23, 2016 at 6:32 AM, Stefan Beller <sbeller@google.com> wrote:\n>> +attr;;\n>> +After `attr:` comes a space separated list of \"attribute\n>> +requirements\", all of which must be met in order for the\n>> +path to be considered a match;\n>\n> What about (attr=abc def,attr=ghi lkj)? Does it mean (abc && def) ||\n> (ghi && lkj), or abc && def && ghi && lkj? Or is it forbidden to have\n> multiple 'attr' attribute in the same pathspec?\n\nGood point. I'll add a test for that.\n\nRemembering the original discussion, multiple attrs\nare forbidden for now as it is unclear what you want to see\nas a user.\n\nTo model  (abc && def) || (ghi && lkj), you would need to give\nmultiple pathspec items as these are naturally ORed:\n\n    git ls-files :(attr:abc def) :(attr:ghi lkj) .\n\n(compare \"git ls-files Makefile README\" which gives\n2 files to you, that are named respectively.)\n\nTo get \"abc && def && ghi && lkj\" you go with\n\n    git ls-files :(attr:abc def ghi lkj) .\n\nas then all things included into this one attr are\nANDed. I hope the documentation is clear for one\nattr.\n\nThanks,\nStefan\n"},{"id":"305008","messageId":"20161027221550.14930-1-sbeller@google.com","threadId":"44358","inReplyTo":"xmqqy41a8hxj.fsf@gitster.mtv.corp.google.com","subject":"[PATCH] attr: convert to new threadsafe API","fromName":"Stefan Beller","fromEmail":"sbeller@google.com","sentAt":"2016-10-27T22:15:50Z","receivedAt":"2016-10-27T22:19:01Z","isPatch":true,"sender":{"key":"stefanbeller@gmail.com","avatar":"https://avatars.githubusercontent.com/u/455868?v=4"},"body":"This revamps the API of the attr subsystem to be thread safe.\nBefore we had the question and its results in one struct type.\nThe typical usage of the API was\n\n    static struct git_attr_check *check;\n\n    if (!check)\n        check = git_attr_check_initl(\"text\", NULL);\n\n    git_check_attr(path, check);\n    act_on(check->value[0]);\n\nThis has a couple of issues when it comes to thread safety:\n\n* the initialization is racy in this implementation. To make it\n  thread safe, we need to acquire a mutex, such that only one\n  thread is executing the code in git_attr_check_initl.\n  As we do not want to introduce a mutex at each call site,\n  this is best done in the attr code. However to do so, we need\n  to have access to the `check` variable, i.e. the API has to\n  look like\n    git_attr_check_initl(struct git_attr_check**, ...);\n  Then one of the threads calling git_attr_check_initl will\n  acquire the mutex and init the `check`, while all other threads\n  will wait on the mutex just to realize they're late to the\n  party and they'll return with no work done.\n\n* While the check for attributes to be questioned only need to\n  be initalized once as that part will be read only after its\n  initialisation, the answer may be different for each path.\n  Because of that we need to decouple the check and the answer,\n  such that each thread can obtain an answer for the path it\n  is currently processing.\n\nThis commit changes the API and adds locking in\ngit_attr_check_initl that provides the thread safety for constructing\n`struct git_attr_check`.\n\nThe usage of the new API will be:\n\n    /*\n     * The initl call will thread-safely check whether the\n     * struct git_attr_check has been initialized. We only\n     * want to do the initialization work once, hence we do\n     * that work inside a thread safe environment.\n     */\n    static struct git_attr_check *check;\n    git_attr_check_initl(&check, \"text\", NULL);\n\n    /* We're just asking for one attribute \"text\". */\n    git_attr_result myresult[1];\n\n    /* Perform the check and act on it: */\n    git_check_attr(path, check, myresult);\n    act_on(myresult[0].value);\n\n    /*\n     * No need to free the check as it is static, hence doesn't leak\n     * memory. The result is also static, so no need to free there either.\n     */\n\nSigned-off-by: Stefan Beller <sbeller@google.com>\n---\n\n* use attr_start on Windows to dynamically initialize the Single Big Attr Mutex\n* rewrote the documentation by Junios guiding comments\n* interned struct git_attr, i.e. you hand \"const char *\" to the attr API, and\n  internally it will make sense of it. When asking git_all_attrs, however you\n  still need to know about git_attrs as you need to find out the name of the\n  attribute via `git_attr_name(struct git_attr *)`.\n  \nIf there are no other huge design discussions, I'll reroll the whole series.\n\nThanks,\nStefan\n\n Documentation/technical/api-gitattributes.txt | 104 +++++++++++-------\n archive.c                                     |  11 +-\n attr.c                                        | 150 ++++++++++++++++++--------\n attr.h                                        |  76 +++++++------\n builtin/check-attr.c                          |  47 ++++----\n builtin/pack-objects.c                        |  16 +--\n compat/mingw.c                                |   3 +\n convert.c                                     |  40 +++----\n ll-merge.c                                    |  24 +++--\n userdiff.c                                    |  16 +--\n ws.c                                          |   8 +-\n 11 files changed, 305 insertions(+), 190 deletions(-)\n\ndiff --git a/Documentation/technical/api-gitattributes.txt b/Documentation/technical/api-gitattributes.txt\nindex 92fc32a..ba04c30 100644\n--- a/Documentation/technical/api-gitattributes.txt\n+++ b/Documentation/technical/api-gitattributes.txt\n@@ -16,15 +16,19 @@ Data Structure\n \tof no interest to the calling programs.  The name of the\n \tattribute can be retrieved by calling `git_attr_name()`.\n \n-`struct git_attr_check_elem`::\n-\n-\tThis structure represents one attribute and its value.\n-\n `struct git_attr_check`::\n \n-\tThis structure represents a collection of `git_attr_check_elem`.\n+\tThis structure represents a collection of `struct git_attrs`.\n \tIt is passed to `git_check_attr()` function, specifying the\n-\tattributes to check, and receives their values.\n+\tattributes to check, and receives their values into a corresponding\n+\t`struct git_attr_result`.\n+\n+`struct git_attr_result`::\n+\n+\tThis structure represents one results for a check, such that an\n+\tarray of `struct git_attr_results` corresponds to a\n+\t`struct git_attr_check`. The answers given in that array are in\n+\tthe the same order as the check struct.\n \n \n Attribute Values\n@@ -32,7 +36,7 @@ Attribute Values\n \n An attribute for a path can be in one of four states: Set, Unset,\n Unspecified or set to a string, and `.value` member of `struct\n-git_attr_check` records it.  There are three macros to check these:\n+git_attr_result` records it.  There are three macros to check these:\n \n `ATTR_TRUE()`::\n \n@@ -53,19 +57,32 @@ value of the attribute for the path.\n Querying Specific Attributes\n ----------------------------\n \n-* Prepare `struct git_attr_check` using git_attr_check_initl()\n+* Prepare a `struct git_attr_check` using `git_attr_check_initl()`\n   function, enumerating the names of attributes whose values you are\n   interested in, terminated with a NULL pointer.  Alternatively, an\n-  empty `struct git_attr_check` can be prepared by calling\n-  `git_attr_check_alloc()` function and then attributes you want to\n-  ask about can be added to it with `git_attr_check_append()`\n-  function.\n-\n-* Call `git_check_attr()` to check the attributes for the path.\n-\n-* Inspect `git_attr_check` structure to see how each of the\n-  attribute in the array is defined for the path.\n-\n+  empty `struct git_attr_check` as allocated by git_attr_check_alloc()\n+  can be prepared by calling `git_attr_check_alloc()` function and\n+  then attributes you want to ask about can be added to it with\n+  `git_attr_check_append()` function.\n+  Both ways with `git_attr_check_initl()` as well as the\n+  alloc and append route are thread safe, i.e. you can call it\n+  from different threads at the same time; when check determines\n+  the initialization is still needed, the threads will use a\n+  single global mutex to perform the initialization just once, the\n+  others will wait on the the thread to actually perform the\n+  initialization.\n+\n+* Allocate an array of `struct git_attr_result` either on the stack\n+  or via `git_attr_result_alloc` on the heap when the result size\n+  is not known at compile time.\n+\n+* Call `git_check_attr()` to check the attributes for the path,\n+  the given `git_attr_result` will be filled with the result.\n+  You must not change the `struct git_attr_check` after calling\n+  `git_check_attr()`.\n+\n+* Inspect each `git_attr_result` structure to see how\n+  each of the attribute in the array is defined for the path.\n \n Example\n -------\n@@ -76,28 +93,23 @@ To see how attributes \"crlf\" and \"ident\" are set for different paths.\n   we are checking two attributes):\n \n ------------\n-static struct git_attr_check *check;\n-static void setup_check(void)\n-{\n-\tif (check)\n-\t\treturn; /* already done */\n-\tcheck = git_attr_check_initl(\"crlf\", \"ident\", NULL);\n-}\n+\tstatic struct git_attr_check *check;\n+\tgit_attr_check_initl(&check, \"crlf\", \"ident\", NULL);\n ------------\n \n . Call `git_check_attr()` with the prepared `struct git_attr_check`:\n \n ------------\n \tconst char *path;\n+\tstruct git_attr_result result[2];\n \n-\tsetup_check();\n-\tgit_check_attr(path, check);\n+\tgit_check_attr(path, check, result);\n ------------\n \n-. Act on `.value` member of the result, left in `check->check[]`:\n+. Act on `result.value[]`:\n \n ------------\n-\tconst char *value = check->check[0].value;\n+\tconst char *value = result.value[0];\n \n \tif (ATTR_TRUE(value)) {\n \t\tThe attribute is Set, by listing only the name of the\n@@ -123,12 +135,15 @@ the first step in the above would be different.\n static struct git_attr_check *check;\n static void setup_check(const char **argv)\n {\n+\tif (check)\n+\t\treturn; /* already done */\n \tcheck = git_attr_check_alloc();\n \twhile (*argv) {\n \t\tstruct git_attr *attr = git_attr(*argv);\n \t\tgit_attr_check_append(check, attr);\n \t\targv++;\n \t}\n+\tstruct git_attr_result *result = git_attr_result_alloc(check);\n }\n ------------\n \n@@ -138,17 +153,30 @@ Querying All Attributes\n \n To get the values of all attributes associated with a file:\n \n-* Prepare an empty `git_attr_check` structure by calling\n-  `git_attr_check_alloc()`.\n+* Setup a local variables for the question\n+  `struct git_attr_check` as well as a pointer where the result\n+  `struct git_attr_result` will be stored. Both should be initialized\n+  to NULL.\n+\n+------------\n+  struct git_attr_check *check = NULL;\n+  struct git_attr_result *result = NULL;\n+------------\n+\n+* Call `git_all_attrs()`.\n \n-* Call `git_all_attrs()`, which populates the `git_attr_check`\n-  with the attributes attached to the path.\n+------------\n+  git_all_attrs(full_path, &check, &result);\n+------------\n \n-* Iterate over the `git_attr_check.check[]` array to examine\n-  the attribute names and values.  The name of the attribute\n-  described by a  `git_attr_check.check[]` object can be retrieved via\n-  `git_attr_name(check->check[i].attr)`.  (Please note that no items\n+* Iterate over the `git_attr_check.attr[]` array to examine the\n+  attribute names.  The name of the attribute described by a\n+  `git_attr_check.attr[]` object can be retrieved via\n+  `git_attr_name(check->attr[i])`.  (Please note that no items\n   will be returned for unset attributes, so `ATTR_UNSET()` will return\n   false for all returned `git_array_check` objects.)\n+  The respective value for an attribute can be found in the same\n+  index position in of `git_attr_result`.\n \n-* Free the `git_array_check` by calling `git_attr_check_free()`.\n+* Clear the variables by calling `git_attr_check_clear()` and\n+  `git_attr_result_free()`.\ndiff --git a/archive.c b/archive.c\nindex 11e3951..65bc6b7 100644\n--- a/archive.c\n+++ b/archive.c\n@@ -111,6 +111,7 @@ static int write_archive_entry(const unsigned char *sha1, const char *base,\n \tstruct archiver_args *args = c->args;\n \twrite_archive_entry_fn_t write_entry = c->write_entry;\n \tstatic struct git_attr_check *check;\n+\tstruct git_attr_result result[2];\n \tconst char *path_without_prefix;\n \tint err;\n \n@@ -124,12 +125,12 @@ static int write_archive_entry(const unsigned char *sha1, const char *base,\n \t\tstrbuf_addch(&path, '/');\n \tpath_without_prefix = path.buf + args->baselen;\n \n-\tif (!check)\n-\t\tcheck = git_attr_check_initl(\"export-ignore\", \"export-subst\", NULL);\n-\tif (!git_check_attr(path_without_prefix, check)) {\n-\t\tif (ATTR_TRUE(check->check[0].value))\n+\tgit_attr_check_initl(&check, \"export-ignore\", \"export-subst\", NULL);\n+\n+\tif (!git_check_attr(path_without_prefix, check, result)) {\n+\t\tif (ATTR_TRUE(result[0].value))\n \t\t\treturn 0;\n-\t\targs->convert = ATTR_TRUE(check->check[1].value);\n+\t\targs->convert = ATTR_TRUE(result[1].value);\n \t}\n \n \tif (S_ISDIR(mode) || S_ISGITLINK(mode)) {\ndiff --git a/attr.c b/attr.c\nindex 881bdfa..ea3d742 100644\n--- a/attr.c\n+++ b/attr.c\n@@ -14,6 +14,7 @@\n #include \"dir.h\"\n #include \"utf8.h\"\n #include \"quote.h\"\n+#include \"thread-utils.h\"\n \n const char git_attr__true[] = \"(builtin)true\";\n const char git_attr__false[] = \"\\0(builtin)false\";\n@@ -46,6 +47,21 @@ struct git_attr {\n static int attr_nr;\n static struct git_attr *(git_attr_hash[HASHSIZE]);\n \n+#ifndef NO_PTHREADS\n+\n+static pthread_mutex_t attr_mutex = PTHREAD_MUTEX_INITIALIZER;\n+#define attr_lock()\t\tpthread_mutex_lock(&attr_mutex)\n+#define attr_unlock()\t\tpthread_mutex_unlock(&attr_mutex)\n+void attr_start(void) { pthread_mutex_init(&attr_mutex, NULL); }\n+\n+#else\n+\n+#define attr_lock()\t\t(void)0\n+#define attr_unlock()\t\t(void)0\n+void attr_start(void) {;}\n+\n+#endif /* NO_PTHREADS */\n+\n /*\n  * NEEDSWORK: maybe-real, maybe-macro are not property of\n  * an attribute, as it depends on what .gitattributes are\n@@ -55,6 +71,16 @@ static struct git_attr *(git_attr_hash[HASHSIZE]);\n  */\n static int cannot_trust_maybe_real;\n \n+/*\n+ * Send one or more git_attr_check to git_check_attrs(), and\n+ * each 'value' member tells what its value is.\n+ * Unset one is returned as NULL.\n+ */\n+struct git_attr_check_elem {\n+\tconst struct git_attr *attr;\n+\tconst char *value;\n+};\n+\n /* NEEDSWORK: This will become per git_attr_check */\n static struct git_attr_check_elem *check_all_attr;\n \n@@ -781,7 +807,7 @@ static int macroexpand_one(int nr, int rem)\n \n static int attr_check_is_dynamic(const struct git_attr_check *check)\n {\n-\treturn (void *)(check->check) != (void *)(check + 1);\n+\treturn (void *)(check->attr) != (void *)(check + 1);\n }\n \n static void empty_attr_check_elems(struct git_attr_check *check)\n@@ -799,7 +825,9 @@ static void empty_attr_check_elems(struct git_attr_check *check)\n  * any and all attributes that are visible are collected in it.\n  */\n static void collect_some_attrs(const char *path, int pathlen,\n-\t\t\t       struct git_attr_check *check, int collect_all)\n+\t\t\t       struct git_attr_check *check,\n+\t\t\t       struct git_attr_result **result,\n+\t\t\t       int collect_all)\n \n {\n \tstruct attr_stack *stk;\n@@ -825,13 +853,11 @@ static void collect_some_attrs(const char *path, int pathlen,\n \t\tcheck_all_attr[i].value = ATTR__UNKNOWN;\n \n \tif (!collect_all && !cannot_trust_maybe_real) {\n-\t\tstruct git_attr_check_elem *celem = check->check;\n-\n \t\trem = 0;\n \t\tfor (i = 0; i < check->check_nr; i++) {\n-\t\t\tif (!celem[i].attr->maybe_real) {\n+\t\t\tif (!check->attr[i]->maybe_real) {\n \t\t\t\tstruct git_attr_check_elem *c;\n-\t\t\t\tc = check_all_attr + celem[i].attr->attr_nr;\n+\t\t\t\tc = check_all_attr + check->attr[i]->attr_nr;\n \t\t\t\tc->value = ATTR__UNSET;\n \t\t\t\trem++;\n \t\t\t}\n@@ -845,38 +871,53 @@ static void collect_some_attrs(const char *path, int pathlen,\n \t\trem = fill(path, pathlen, basename_offset, stk, rem);\n \n \tif (collect_all) {\n-\t\tempty_attr_check_elems(check);\n+\t\tint check_nr = 0, check_alloc = 0;\n+\t\tconst char **res = NULL;\n+\n \t\tfor (i = 0; i < attr_nr; i++) {\n \t\t\tconst struct git_attr *attr = check_all_attr[i].attr;\n \t\t\tconst char *value = check_all_attr[i].value;\n \t\t\tif (value == ATTR__UNSET || value == ATTR__UNKNOWN)\n \t\t\t\tcontinue;\n-\t\t\tgit_attr_check_append(check, attr)->value = value;\n+\n+\t\t\tALLOC_GROW(check->attr, check->check_nr + 1, check->check_alloc);\n+\t\t\tcheck->attr[check->check_nr++] = attr;\n+\n+\t\t\tALLOC_GROW(res, check_nr + 1, check_alloc);\n+\t\t\tres[check_nr++] = value;\n \t\t}\n+\n+\t\t*result = git_attr_result_alloc(check);\n+\t\tfor (i = 0; i < check->check_nr; i++)\n+\t\t\t(*result)[i].value = res[i];\n+\n+\t\tfree(res);\n \t}\n }\n \n static int git_check_attrs(const char *path, int pathlen,\n-\t\t\t   struct git_attr_check *check)\n+\t\t\t   struct git_attr_check *check,\n+\t\t\t   struct git_attr_result *result)\n {\n \tint i;\n-\tstruct git_attr_check_elem *celem = check->check;\n \n-\tcollect_some_attrs(path, pathlen, check, 0);\n+\tcollect_some_attrs(path, pathlen, check, &result, 0);\n \n \tfor (i = 0; i < check->check_nr; i++) {\n-\t\tconst char *value = check_all_attr[celem[i].attr->attr_nr].value;\n+\t\tconst char *value = check_all_attr[check->attr[i]->attr_nr].value;\n \t\tif (value == ATTR__UNKNOWN)\n \t\t\tvalue = ATTR__UNSET;\n-\t\tcelem[i].value = value;\n+\t\tresult[i].value = value;\n \t}\n \n \treturn 0;\n }\n \n-void git_all_attrs(const char *path, struct git_attr_check *check)\n+void git_all_attrs(const char *path,\n+\t\t   struct git_attr_check *check,\n+\t\t   struct git_attr_result **result)\n {\n-\tcollect_some_attrs(path, strlen(path), check, 1);\n+\tcollect_some_attrs(path, strlen(path), check, result, 1);\n }\n \n void git_attr_set_direction(enum git_attr_direction new, struct index_state *istate)\n@@ -892,36 +933,40 @@ void git_attr_set_direction(enum git_attr_direction new, struct index_state *ist\n \tuse_index = istate;\n }\n \n-static int git_check_attr_counted(const char *path, int pathlen,\n-\t\t\t\t  struct git_attr_check *check)\n+int git_check_attr(const char *path,\n+\t\t    struct git_attr_check *check,\n+\t\t    struct git_attr_result *result)\n {\n \tcheck->finalized = 1;\n-\treturn git_check_attrs(path, pathlen, check);\n+\treturn git_check_attrs(path, strlen(path), check, result);\n }\n \n-int git_check_attr(const char *path, struct git_attr_check *check)\n+void git_attr_check_initl(struct git_attr_check **check_,\n+\t\t\t  const char *one, ...)\n {\n-\treturn git_check_attr_counted(path, strlen(path), check);\n-}\n-\n-struct git_attr_check *git_attr_check_initl(const char *one, ...)\n-{\n-\tstruct git_attr_check *check;\n \tint cnt;\n \tva_list params;\n \tconst char *param;\n+\tstruct git_attr_check *check;\n+\n+\tattr_lock();\n+\tif (*check_) {\n+\t\tattr_unlock();\n+\t\treturn;\n+\t}\n \n \tva_start(params, one);\n \tfor (cnt = 1; (param = va_arg(params, const char *)) != NULL; cnt++)\n \t\t;\n \tva_end(params);\n+\n \tcheck = xcalloc(1,\n-\t\t\tsizeof(*check) + cnt * sizeof(*(check->check)));\n+\t\t\tsizeof(*check) + cnt * sizeof(*(check->attr)));\n \tcheck->check_nr = cnt;\n \tcheck->finalized = 1;\n-\tcheck->check = (struct git_attr_check_elem *)(check + 1);\n+\tcheck->attr = (const struct git_attr **)(check + 1);\n \n-\tcheck->check[0].attr = git_attr(one);\n+\tcheck->attr[0] = git_attr(one);\n \tva_start(params, one);\n \tfor (cnt = 1; cnt < check->check_nr; cnt++) {\n \t\tstruct git_attr *attr;\n@@ -932,29 +977,48 @@ struct git_attr_check *git_attr_check_initl(const char *one, ...)\n \t\tattr = git_attr(param);\n \t\tif (!attr)\n \t\t\tdie(\"BUG: %s: not a valid attribute name\", param);\n-\t\tcheck->check[cnt].attr = attr;\n+\t\tcheck->attr[cnt] = attr;\n \t}\n \tva_end(params);\n-\treturn check;\n+\t*check_ = check;\n+\tattr_unlock();\n }\n \n-struct git_attr_check *git_attr_check_alloc(void)\n+void git_attr_check_alloc(struct git_attr_check **check)\n {\n-\treturn xcalloc(1, sizeof(struct git_attr_check));\n+\tattr_lock();\n+\tif (!*check)\n+\t\t*check = xcalloc(1, sizeof(struct git_attr_check));\n+\n+\tattr_unlock();\n }\n \n-struct git_attr_check_elem *git_attr_check_append(struct git_attr_check *check,\n-\t\t\t\t\t\t  const struct git_attr *attr)\n+struct git_attr_result *git_attr_result_alloc(struct git_attr_check *check)\n {\n-\tstruct git_attr_check_elem *elem;\n+\treturn xcalloc(1, sizeof(struct git_attr_result) * check->check_nr);\n+}\n+\n+void git_attr_check_append(struct git_attr_check *check,\n+\t\t\t   const char *name, int len)\n+{\n+\tint i;\n+\tstruct git_attr *attr;\n \tif (check->finalized)\n \t\tdie(\"BUG: append after git_attr_check structure is finalized\");\n \tif (!attr_check_is_dynamic(check))\n \t\tdie(\"BUG: appending to a statically initialized git_attr_check\");\n-\tALLOC_GROW(check->check, check->check_nr + 1, check->check_alloc);\n-\telem = &check->check[check->check_nr++];\n-\telem->attr = attr;\n-\treturn elem;\n+\tattr_lock();\n+\n+\tattr = git_attr_counted(name, len);\n+\n+\tfor (i = 0; i < check->check_nr; i++)\n+\t\tif (check->attr[i] == attr)\n+\t\t\tbreak;\n+\tif (i == check->check_nr) {\n+\t\tALLOC_GROW(check->attr, check->check_nr + 1, check->check_alloc);\n+\t\tcheck->attr[check->check_nr++] = attr;\n+\t}\n+\tattr_unlock();\n }\n \n void git_attr_check_clear(struct git_attr_check *check)\n@@ -962,12 +1026,12 @@ void git_attr_check_clear(struct git_attr_check *check)\n \tempty_attr_check_elems(check);\n \tif (!attr_check_is_dynamic(check))\n \t\tdie(\"BUG: clearing a statically initialized git_attr_check\");\n-\tfree(check->check);\n+\tfree(check->attr);\n \tcheck->check_alloc = 0;\n }\n \n-void git_attr_check_free(struct git_attr_check *check)\n+void git_attr_result_free(struct git_attr_result *result)\n {\n-\tgit_attr_check_clear(check);\n-\tfree(check);\n+\t/* No need to free values as they are interned. */\n+\tfree(result);\n }\ndiff --git a/attr.h b/attr.h\nindex 292d56f..381eba9 100644\n--- a/attr.h\n+++ b/attr.h\n@@ -1,17 +1,14 @@\n #ifndef ATTR_H\n #define ATTR_H\n \n-/* An attribute is a pointer to this opaque structure */\n-struct git_attr;\n-\n /*\n- * Given a string, return the gitattribute object that\n- * corresponds to it.\n+ * Must be called on platforms that do not support static initialisation\n+ * of mutexes.\n  */\n-extern struct git_attr *git_attr(const char *);\n+extern void attr_start(void);\n \n-/* The same, but with counted string */\n-extern struct git_attr *git_attr_counted(const char *, size_t);\n+/* An attribute is a pointer to this opaque structure */\n+struct git_attr;\n \n /*\n  * Return the name of the attribute represented by the argument.  The\n@@ -32,44 +29,53 @@ extern const char git_attr__false[];\n #define ATTR_FALSE(v) ((v) == git_attr__false)\n #define ATTR_UNSET(v) ((v) == NULL)\n \n-/*\n- * Send one or more git_attr_check to git_check_attrs(), and\n- * each 'value' member tells what its value is.\n- * Unset one is returned as NULL.\n- */\n-struct git_attr_check_elem {\n-\tconst struct git_attr *attr;\n-\tconst char *value;\n-};\n-\n struct git_attr_check {\n \tint finalized;\n \tint check_nr;\n \tint check_alloc;\n-\tstruct git_attr_check_elem *check;\n+\tconst struct git_attr **attr;\n };\n+#define GIT_ATTR_CHECK_INIT {0, 0, 0, NULL}\n \n-extern struct git_attr_check *git_attr_check_initl(const char *, ...);\n-extern int git_check_attr(const char *path, struct git_attr_check *);\n+struct git_attr_result {\n+\tconst char *value;\n+};\n \n-extern struct git_attr_check *git_attr_check_alloc(void);\n-extern struct git_attr_check_elem *git_attr_check_append(struct git_attr_check *, const struct git_attr *);\n+/*\n+ * Initialize the `git_attr_check` via one of the following three functions:\n+ *\n+ * git_attr_check_alloc  allocates an empty check,\n+ * git_attr_check_append add an attribute to the given git_attr_check\n+ *\n+ * git_all_attrs         allocates a check and fills in all attributes that\n+ *                       are set for the given path.\n+ * git_attr_check_initl  takes a pointer to where the check will be initialized,\n+ *                       followed by all attributes that are to be checked.\n+ *                       This makes it potentially thread safe as it could\n+ *                       internally have a mutex for that memory location.\n+ *                       Currently it is not thread safe!\n+ */\n+extern void git_attr_check_alloc(struct git_attr_check **);\n+extern struct git_attr_result *git_attr_result_alloc(struct git_attr_check *check);\n+\n+extern void git_attr_check_append(struct git_attr_check *,\n+\t\t\t\t  const char *, int);\n+extern void git_attr_check_initl(struct git_attr_check **,\n+\t\t\t\t const char *, ...);\n+\n+extern void git_all_attrs(const char *path,\n+\t\t\t  struct git_attr_check *,\n+\t\t\t  struct git_attr_result **);\n+\n+/* Query a path for its attributes */\n+extern int git_check_attr(const char *path,\n+\t\t\t  struct git_attr_check *,\n+\t\t\t  struct git_attr_result *result);\n \n extern void git_attr_check_clear(struct git_attr_check *);\n-extern void git_attr_check_free(struct git_attr_check *);\n \n-/*\n- * Return the name of the attribute represented by the argument.  The\n- * return value is a pointer to a null-delimited string that is part\n- * of the internal data structure; it should not be modified or freed.\n- */\n-extern const char *git_attr_name(const struct git_attr *);\n+extern void git_attr_result_free(struct git_attr_result *);\n \n-/*\n- * Retrieve all attributes that apply to the specified path.\n- * check holds the attributes and their values.\n- */\n-void git_all_attrs(const char *path, struct git_attr_check *check);\n \n enum git_attr_direction {\n \tGIT_ATTR_CHECKIN,\ndiff --git a/builtin/check-attr.c b/builtin/check-attr.c\nindex ec61476..6b9e746 100644\n--- a/builtin/check-attr.c\n+++ b/builtin/check-attr.c\n@@ -24,13 +24,14 @@ static const struct option check_attr_options[] = {\n \tOPT_END()\n };\n \n-static void output_attr(struct git_attr_check *check, const char *file)\n+static void output_attr(struct git_attr_check *check,\n+\t\t\tstruct git_attr_result *result, const char *file)\n {\n \tint j;\n \tint cnt = check->check_nr;\n \n \tfor (j = 0; j < cnt; j++) {\n-\t\tconst char *value = check->check[j].value;\n+\t\tconst char *value = result[j].value;\n \n \t\tif (ATTR_TRUE(value))\n \t\t\tvalue = \"set\";\n@@ -44,11 +45,11 @@ static void output_attr(struct git_attr_check *check, const char *file)\n \t\t\t       \"%s%c\" /* attrname */\n \t\t\t       \"%s%c\" /* attrvalue */,\n \t\t\t       file, 0,\n-\t\t\t       git_attr_name(check->check[j].attr), 0, value, 0);\n+\t\t\t       git_attr_name(check->attr[j]), 0, value, 0);\n \t\t} else {\n \t\t\tquote_c_style(file, NULL, stdout, 0);\n \t\t\tprintf(\": %s: %s\\n\",\n-\t\t\t       git_attr_name(check->check[j].attr), value);\n+\t\t\t       git_attr_name(check->attr[j]), value);\n \t\t}\n \t}\n }\n@@ -59,16 +60,20 @@ static void check_attr(const char *prefix,\n {\n \tchar *full_path =\n \t\tprefix_path(prefix, prefix ? strlen(prefix) : 0, file);\n+\tstruct git_attr_check local_check = GIT_ATTR_CHECK_INIT;\n+\tstruct git_attr_result *result = NULL;\n+\n \tif (check != NULL) {\n-\t\tif (git_check_attr(full_path, check))\n-\t\t\tdie(\"git_check_attr died\");\n-\t\toutput_attr(check, file);\n+\t\tresult = git_attr_result_alloc(check);\n+\t\tgit_check_attr(full_path, check, result);\n \t} else {\n-\t\tcheck = git_attr_check_alloc();\n-\t\tgit_all_attrs(full_path, check);\n-\t\toutput_attr(check, file);\n-\t\tgit_attr_check_free(check);\n+\t\tgit_all_attrs(full_path, &local_check, &result);\n+\t\tcheck = &local_check;\n \t}\n+\toutput_attr(check, result, file);\n+\tgit_attr_check_clear(&local_check);\n+\n+\tgit_attr_result_free(result);\n \tfree(full_path);\n }\n \n@@ -102,7 +107,7 @@ static NORETURN void error_with_usage(const char *msg)\n \n int cmd_check_attr(int argc, const char **argv, const char *prefix)\n {\n-\tstruct git_attr_check *check;\n+\tstruct git_attr_check *check = NULL;\n \tint cnt, i, doubledash, filei;\n \n \tif (!is_bare_repository())\n@@ -162,16 +167,16 @@ int cmd_check_attr(int argc, const char **argv, const char *prefix)\n \t\t\terror_with_usage(\"No file specified\");\n \t}\n \n-\tif (all_attrs) {\n-\t\tcheck = NULL;\n-\t} else {\n-\t\tcheck = git_attr_check_alloc();\n+\tif (!all_attrs) {\n+\t\tgit_attr_check_alloc(&check);\n \t\tfor (i = 0; i < cnt; i++) {\n-\t\t\tstruct git_attr *a = git_attr(argv[i]);\n-\t\t\tif (!a)\n-\t\t\t\treturn error(\"%s: not a valid attribute name\",\n-\t\t\t\t\t     argv[i]);\n-\t\t\tgit_attr_check_append(check, a);\n+\t\t\tif (!attr_name_valid(argv[i], strlen(argv[i]))) {\n+\t\t\t\tstruct strbuf sb = STRBUF_INIT;\n+\t\t\t\tinvalid_attr_name_message(&sb, argv[i],\n+\t\t\t\t\t\t\t  strlen(argv[i]));\n+\t\t\t\treturn error(\"%s\", strbuf_detach(&sb, NULL));\n+\t\t\t}\n+\t\t\tgit_attr_check_append(check, argv[i], strlen(argv[i]));\n \t\t}\n \t}\n \ndiff --git a/builtin/pack-objects.c b/builtin/pack-objects.c\nindex 3918c07..3751836 100644\n--- a/builtin/pack-objects.c\n+++ b/builtin/pack-objects.c\n@@ -899,14 +899,16 @@ static void write_pack_file(void)\n static int no_try_delta(const char *path)\n {\n \tstatic struct git_attr_check *check;\n+\tint ret = 0;\n+\tstruct git_attr_result result[1];\n \n-\tif (!check)\n-\t\tcheck = git_attr_check_initl(\"delta\", NULL);\n-\tif (git_check_attr(path, check))\n-\t\treturn 0;\n-\tif (ATTR_FALSE(check->check[0].value))\n-\t\treturn 1;\n-\treturn 0;\n+\tgit_attr_check_initl(&check, \"delta\", NULL);\n+\n+\tif (!git_check_attr(path, check, result)) {\n+\t\tif (ATTR_FALSE(result[0].value))\n+\t\t\tret = 1;\n+\t}\n+\treturn ret;\n }\n \n /*\ndiff --git a/compat/mingw.c b/compat/mingw.c\nindex 3fbfda5..9173709 100644\n--- a/compat/mingw.c\n+++ b/compat/mingw.c\n@@ -2232,6 +2232,9 @@ void mingw_startup(void)\n \t/* initialize critical section for waitpid pinfo_t list */\n \tInitializeCriticalSection(&pinfo_cs);\n \n+\t/* initialize critical sections in the attr code */\n+\tattr_start();\n+\n \t/* set up default file mode and file modes for stdin/out/err */\n \t_fmode = _O_BINARY;\n \t_setmode(_fileno(stdin), _O_BINARY);\ndiff --git a/convert.c b/convert.c\nindex 4eca0b5..6feffa3 100644\n--- a/convert.c\n+++ b/convert.c\n@@ -722,9 +722,9 @@ static int ident_to_worktree(const char *path, const char *src, size_t len,\n \treturn 1;\n }\n \n-static enum crlf_action git_path_check_crlf(struct git_attr_check_elem *check)\n+static enum crlf_action git_path_check_crlf(struct git_attr_result *result)\n {\n-\tconst char *value = check->value;\n+\tconst char *value = result->value;\n \n \tif (ATTR_TRUE(value))\n \t\treturn CRLF_TEXT;\n@@ -739,9 +739,9 @@ static enum crlf_action git_path_check_crlf(struct git_attr_check_elem *check)\n \treturn CRLF_UNDEFINED;\n }\n \n-static enum eol git_path_check_eol(struct git_attr_check_elem *check)\n+static enum eol git_path_check_eol(struct git_attr_result *result)\n {\n-\tconst char *value = check->value;\n+\tconst char *value = result->value;\n \n \tif (ATTR_UNSET(value))\n \t\t;\n@@ -752,9 +752,9 @@ static enum eol git_path_check_eol(struct git_attr_check_elem *check)\n \treturn EOL_UNSET;\n }\n \n-static struct convert_driver *git_path_check_convert(struct git_attr_check_elem *check)\n+static struct convert_driver *git_path_check_convert(struct git_attr_result *result)\n {\n-\tconst char *value = check->value;\n+\tconst char *value = result->value;\n \tstruct convert_driver *drv;\n \n \tif (ATTR_TRUE(value) || ATTR_FALSE(value) || ATTR_UNSET(value))\n@@ -765,9 +765,9 @@ static struct convert_driver *git_path_check_convert(struct git_attr_check_elem\n \treturn NULL;\n }\n \n-static int git_path_check_ident(struct git_attr_check_elem *check)\n+static int git_path_check_ident(struct git_attr_result *result)\n {\n-\tconst char *value = check->value;\n+\tconst char *value = result->value;\n \n \treturn !!ATTR_TRUE(value);\n }\n@@ -782,25 +782,27 @@ struct conv_attrs {\n static void convert_attrs(struct conv_attrs *ca, const char *path)\n {\n \tstatic struct git_attr_check *check;\n+\tstatic int init_user_convert_tail;\n+\tstruct git_attr_result result[5];\n \n-\tif (!check) {\n-\t\tcheck = git_attr_check_initl(\"crlf\", \"ident\",\n-\t\t\t\t\t     \"filter\", \"eol\", \"text\",\n-\t\t\t\t\t     NULL);\n+\tgit_attr_check_initl(&check, \"crlf\", \"ident\", \"filter\",\n+\t\t\t     \"eol\", \"text\", NULL);\n+\n+\tif (!init_user_convert_tail) {\n \t\tuser_convert_tail = &user_convert;\n \t\tgit_config(read_convert_config, NULL);\n+\t\tinit_user_convert_tail = 1;\n \t}\n \n-\tif (!git_check_attr(path, check)) {\n-\t\tstruct git_attr_check_elem *ccheck = check->check;\n-\t\tca->crlf_action = git_path_check_crlf(ccheck + 4);\n+\tif (!git_check_attr(path, check, result)) {\n+\t\tca->crlf_action = git_path_check_crlf(&result[4]);\n \t\tif (ca->crlf_action == CRLF_UNDEFINED)\n-\t\t\tca->crlf_action = git_path_check_crlf(ccheck + 0);\n+\t\t\tca->crlf_action = git_path_check_crlf(&result[0]);\n \t\tca->attr_action = ca->crlf_action;\n-\t\tca->ident = git_path_check_ident(ccheck + 1);\n-\t\tca->drv = git_path_check_convert(ccheck + 2);\n+\t\tca->ident = git_path_check_ident(&result[1]);\n+\t\tca->drv = git_path_check_convert(&result[2]);\n \t\tif (ca->crlf_action != CRLF_BINARY) {\n-\t\t\tenum eol eol_attr = git_path_check_eol(ccheck + 3);\n+\t\t\tenum eol eol_attr = git_path_check_eol(&result[3]);\n \t\t\tif (ca->crlf_action == CRLF_AUTO && eol_attr == EOL_LF)\n \t\t\t\tca->crlf_action = CRLF_AUTO_INPUT;\n \t\t\telse if (ca->crlf_action == CRLF_AUTO && eol_attr == EOL_CRLF)\ndiff --git a/ll-merge.c b/ll-merge.c\nindex bc6479c..d71354a 100644\n--- a/ll-merge.c\n+++ b/ll-merge.c\n@@ -353,12 +353,14 @@ int ll_merge(mmbuffer_t *result_buf,\n \t     mmfile_t *theirs, const char *their_label,\n \t     const struct ll_merge_options *opts)\n {\n-\tstatic struct git_attr_check *check;\n \tstatic const struct ll_merge_options default_opts;\n \tconst char *ll_driver_name = NULL;\n \tint marker_size = DEFAULT_CONFLICT_MARKER_SIZE;\n \tconst struct ll_merge_driver *driver;\n \n+\tstatic struct git_attr_check *check;\n+\tstruct git_attr_result result[2];\n+\n \tif (!opts)\n \t\topts = &default_opts;\n \n@@ -368,13 +370,12 @@ int ll_merge(mmbuffer_t *result_buf,\n \t\tnormalize_file(theirs, path);\n \t}\n \n-\tif (!check)\n-\t\tcheck = git_attr_check_initl(\"merge\", \"conflict-marker-size\", NULL);\n+\tgit_attr_check_initl(&check, \"merge\", \"conflict-marker-size\", NULL);\n \n-\tif (!git_check_attr(path, check)) {\n-\t\tll_driver_name = check->check[0].value;\n-\t\tif (check->check[1].value) {\n-\t\t\tmarker_size = atoi(check->check[1].value);\n+\tif (!git_check_attr(path, check, result)) {\n+\t\tll_driver_name = result[0].value;\n+\t\tif (result[1].value) {\n+\t\t\tmarker_size = atoi(result[1].value);\n \t\t\tif (marker_size <= 0)\n \t\t\t\tmarker_size = DEFAULT_CONFLICT_MARKER_SIZE;\n \t\t}\n@@ -395,11 +396,12 @@ int ll_merge_marker_size(const char *path)\n {\n \tstatic struct git_attr_check *check;\n \tint marker_size = DEFAULT_CONFLICT_MARKER_SIZE;\n+\tstruct git_attr_result result[1];\n+\n+\tgit_attr_check_initl(&check, \"conflict-marker-size\", NULL);\n \n-\tif (!check)\n-\t\tcheck = git_attr_check_initl(\"conflict-marker-size\", NULL);\n-\tif (!git_check_attr(path, check) && check->check[0].value) {\n-\t\tmarker_size = atoi(check->check[0].value);\n+\tif (!git_check_attr(path, check, result) && !ATTR_UNSET(result[0].value)) {\n+\t\tmarker_size = atoi(result[0].value);\n \t\tif (marker_size <= 0)\n \t\t\tmarker_size = DEFAULT_CONFLICT_MARKER_SIZE;\n \t}\ndiff --git a/userdiff.c b/userdiff.c\nindex 46dfd32..1d6d363 100644\n--- a/userdiff.c\n+++ b/userdiff.c\n@@ -263,21 +263,23 @@ struct userdiff_driver *userdiff_find_by_name(const char *name) {\n struct userdiff_driver *userdiff_find_by_path(const char *path)\n {\n \tstatic struct git_attr_check *check;\n+\tstruct git_attr_result result[1];\n \n-\tif (!check)\n-\t\tcheck = git_attr_check_initl(\"diff\", NULL);\n \tif (!path)\n \t\treturn NULL;\n-\tif (git_check_attr(path, check))\n+\n+\tgit_attr_check_initl(&check, \"diff\", NULL);\n+\n+\tif (git_check_attr(path, check, result))\n \t\treturn NULL;\n \n-\tif (ATTR_TRUE(check->check[0].value))\n+\tif (ATTR_TRUE(result[0].value))\n \t\treturn &driver_true;\n-\tif (ATTR_FALSE(check->check[0].value))\n+\tif (ATTR_FALSE(result[0].value))\n \t\treturn &driver_false;\n-\tif (ATTR_UNSET(check->check[0].value))\n+\tif (ATTR_UNSET(result[0].value))\n \t\treturn NULL;\n-\treturn userdiff_find_by_name(check->check[0].value);\n+\treturn userdiff_find_by_name(result[0].value);\n }\n \n struct userdiff_driver *userdiff_get_textconv(struct userdiff_driver *driver)\ndiff --git a/ws.c b/ws.c\nindex bb3270c..7e355c4 100644\n--- a/ws.c\n+++ b/ws.c\n@@ -74,14 +74,14 @@ unsigned parse_whitespace_rule(const char *string)\n unsigned whitespace_rule(const char *pathname)\n {\n \tstatic struct git_attr_check *attr_whitespace_rule;\n+\tstruct git_attr_result result[1];\n \n-\tif (!attr_whitespace_rule)\n-\t\tattr_whitespace_rule = git_attr_check_initl(\"whitespace\", NULL);\n+\tgit_attr_check_initl(&attr_whitespace_rule, \"whitespace\", NULL);\n \n-\tif (!git_check_attr(pathname, attr_whitespace_rule)) {\n+\tif (!git_check_attr(pathname, attr_whitespace_rule, result)) {\n \t\tconst char *value;\n \n-\t\tvalue = attr_whitespace_rule->check[0].value;\n+\t\tvalue = result[0].value;\n \t\tif (ATTR_TRUE(value)) {\n \t\t\t/* true (whitespace) */\n \t\t\tunsigned all_rule = ws_tab_width(whitespace_rule_cfg);\n-- \n2.10.1.530.g38c3896\n\n"},{"id":"305049","messageId":"alpine.DEB.2.20.1610281053330.3264@virtualbox","threadId":"44358","inReplyTo":"20161027221550.14930-1-sbeller@google.com","subject":"Re: [PATCH] attr: convert to new threadsafe API","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2016-10-28T08:55:17Z","receivedAt":"2016-10-28T08:55:44Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Hi Stefan,\n\nOn Thu, 27 Oct 2016, Stefan Beller wrote:\n\n> * use attr_start on Windows to dynamically initialize the Single Big Attr Mutex\n\nI would have preferred that call in common-main.c, but whatevs...\n\nThanks you for fixing the bug,\nDscho\n"},{"id":"305072","messageId":"xmqqinsc1jcg.fsf@gitster.mtv.corp.google.com","threadId":"44358","inReplyTo":"20161027221550.14930-1-sbeller@google.com","subject":"Re: [PATCH] attr: convert to new threadsafe API","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2016-10-28T17:20:15Z","receivedAt":"2016-10-28T17:20:24Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Stefan Beller <sbeller@google.com> writes:\n\n> +* Prepare a `struct git_attr_check` using `git_attr_check_initl()`\n>    function, enumerating the names of attributes whose values you are\n>    interested in, terminated with a NULL pointer.  Alternatively, an\n> -  empty `struct git_attr_check` can be prepared by calling\n> -  `git_attr_check_alloc()` function and then attributes you want to\n> -  ask about can be added to it with `git_attr_check_append()`\n> -  function.\n> -\n> -* Call `git_check_attr()` to check the attributes for the path.\n> -\n> -* Inspect `git_attr_check` structure to see how each of the\n> -  attribute in the array is defined for the path.\n> -\n> +  empty `struct git_attr_check` as allocated by git_attr_check_alloc()\n\nNeed to drop \"as allocated by git_attr_check_alloc()\" here.\n\n> +  can be prepared by calling `git_attr_check_alloc()` function and\n> +  then attributes you want to ask about can be added to it with\n> +  `git_attr_check_append()` function.\n> +  Both ways with `git_attr_check_initl()` as well as the\n> +  alloc and append route are thread safe, i.e. you can call it\n> +  from different threads at the same time; when check determines\n> +  the initialization is still needed, the threads will use a\n> +  single global mutex to perform the initialization just once, the\n> +  others will wait on the the thread to actually perform the\n> +  initialization.\n\nI have some comments on the example in the doc on the \"alloc-append\"\nside.  _initl() side looks OK.\n\n> +\tstatic struct git_attr_check *check;\n> +\tgit_attr_check_initl(&check, \"crlf\", \"ident\", NULL);\n\nOK.\n\n>  \tconst char *path;\n> +\tstruct git_attr_result result[2];\n>  \n> +\tgit_check_attr(path, check, result);\n\nOK.  The above two may be easier to understand if they were a single\nexample, though.\n\n> +. Act on `result.value[]`:\n>  \n>  ------------\n> -\tconst char *value = check->check[0].value;\n> +\tconst char *value = result.value[0];\n\nOK.\n\n> @@ -123,12 +135,15 @@ the first step in the above would be different.\n>  static struct git_attr_check *check;\n>  static void setup_check(const char **argv)\n>  {\n> +\tif (check)\n> +\t\treturn; /* already done */\n>  \tcheck = git_attr_check_alloc();\n>  \twhile (*argv) {\n>  \t\tstruct git_attr *attr = git_attr(*argv);\n>  \t\tgit_attr_check_append(check, attr);\n>  \t\targv++;\n>  \t}\n> +\tstruct git_attr_result *result = git_attr_result_alloc(check);\n\nThis does not look like thread-safe.\n\nI could understand it if the calling convention were like this,\nthough:\n\n\tif (git_attr_check_alloc(&check)) {\n\t\twhile (*argv) {\n\t        \t... append ...\n\t\t}\n\t\tgit_attr_check_finished_appending(&check);\n\t}\n\tresult = result_alloc();\n\nIn this variant, git_attr_check_alloc() is responsible for ensuring\nthat the \"check\" is allocated only once just like _initl() is, and\nat the same time, it makes simultanous callers wait until the first\ncaller who appends to the singleton check instance declares that it\nfinished appending.  The return value signals if you are the first\ncaller (who is responsible for populating the check and for\ndeclaring the check is ready to use at the end of appending).\nEverybody else waits while the first caller is doing the if (...) {\n} thing, and then receives false, at which time everybody (including\nthe first caller) goes on and allocating its own result and start\nmaking queries.\n\n> +* Setup a local variables for the question\n> +  `struct git_attr_check` as well as a pointer where the result\n> +  `struct git_attr_result` will be stored. Both should be initialized\n> +  to NULL.\n> +\n> +------------\n> +  struct git_attr_check *check = NULL;\n> +  struct git_attr_result *result = NULL;\n> +------------\n> +\n> +* Call `git_all_attrs()`.\n>  \n> +------------\n> +  git_all_attrs(full_path, &check, &result);\n> +------------\n\nOK.\n\nThanks.\n"},{"id":"305074","messageId":"xmqqa8do1iv6.fsf@gitster.mtv.corp.google.com","threadId":"44358","inReplyTo":"xmqqinsc1jcg.fsf@gitster.mtv.corp.google.com","subject":"Re: [PATCH] attr: convert to new threadsafe API","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2016-10-28T17:30:37Z","receivedAt":"2016-10-28T17:30:46Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Junio C Hamano <gitster@pobox.com> writes:\n\n>> +\tif (check)\n>> +\t\treturn; /* already done */\n>>  \tcheck = git_attr_check_alloc();\n>>  \twhile (*argv) {\n>>  \t\tstruct git_attr *attr = git_attr(*argv);\n>>  \t\tgit_attr_check_append(check, attr);\n\nI thought you made git_attr() constructor unavailable, so\ncheck_append() would just get a \"const char *\" instead?\n\n>>  \t\targv++;\n>>  \t}\n>> +\tstruct git_attr_result *result = git_attr_result_alloc(check);\n>\n> This does not look like thread-safe.\n>\n> I could understand it if the calling convention were like this,\n> though:\n>\n> \tif (git_attr_check_alloc(&check)) {\n> \t\twhile (*argv) {\n> \t        \t... append ...\n> \t\t}\n> \t\tgit_attr_check_finished_appending(&check);\n> \t}\n> \tresult = result_alloc();\n>\n> In this variant, git_attr_check_alloc() is responsible for ensuring\n> that the \"check\" is allocated only once just like _initl() is, and\n> at the same time, it makes simultanous callers wait until the first\n> caller who appends to the singleton check instance declares that it\n> finished appending.  The return value signals if you are the first\n> caller (who is responsible for populating the check and for\n> declaring the check is ready to use at the end of appending).\n> Everybody else waits while the first caller is doing the if (...) {\n> } thing, and then receives false, at which time everybody (including\n> the first caller) goes on and allocating its own result and start\n> making queries.\n\nHaving said that, how flexible does the \"alloc then append\" side of\nAPI have to be in the envisioned set of callers?  Is it expected\nthat it wouldn't be too hard to arrange them so that they have an\narray of \"const char *\"s when they need to initialize/populate a\ncheck?  If that is the case, instead of the \"alloc and have others\nwait\" illustrated above, it may be a lot simpler to give _initv()\nvariant to them and be done with it, i.e. the above sample caller\nwould become just:\n\n\tgit_attr_check_initv(&check, argv);\n\tresult = git_attr_result_alloc(&check);\n\nwould that be too limiting?  The use of \"alloc then append\" pattern\nin \"git check-attr\" done in your patch seems to fit the _initv()\nwell, and the \"pathspec with attr match\" that appears later in the\nseries also has all the attributes it needs to query available by\nthe time it wants to allocate and append to create an instance of\n\"check\", I would think, so the _initv() might be sufficient as a\npublic API.\n"},{"id":"305077","messageId":"7ce9676b-c43a-337a-b19d-c93d30493dde@kdbg.org","threadId":"44358","inReplyTo":"20161027221550.14930-1-sbeller@google.com","subject":"Re: [PATCH] attr: convert to new threadsafe API","fromName":"Johannes Sixt","fromEmail":"j6t@kdbg.org","sentAt":"2016-10-28T18:16:19Z","receivedAt":"2016-10-28T18:31:55Z","isPatch":true,"sender":{"key":"j6t@kdbg.org","avatar":"https://avatars.githubusercontent.com/u/14810926?v=4"},"body":"Am 28.10.2016 um 00:15 schrieb Stefan Beller:\n> * use attr_start on Windows to dynamically initialize the Single Big Attr Mutex\n\nI'm sorry, I didn't find time to test the patch on Windows. I won't be \nback at my Windows box before Wednesday.\n\n-- Hannes\n\n"},{"id":"305623","messageId":"CACsJy8C2MLg4ncLBXXJGf+=mPF_rRoKs2vN6=+chZeNeXWZsbg@mail.gmail.com","threadId":"44358","inReplyTo":"xmqqzilp63yh.fsf@gitster.mtv.corp.google.com","subject":"Re: [PATCH 32/36] pathspec: allow querying for attributes","fromName":"Duy Nguyen","fromEmail":"pclouds@gmail.com","sentAt":"2016-11-09T09:45:39Z","receivedAt":"2016-11-09T09:46:22Z","isPatch":true,"sender":{"key":"pclouds@gmail.com","avatar":"https://avatars.githubusercontent.com/u/720?v=4"},"body":"On Fri, Oct 28, 2016 at 1:29 AM, Junio C Hamano <gitster@pobox.com> wrote:\n> The reason why I am bringing this up in this discussion thread on\n> this patch is because I wonder if we would benefit by a similar\n> \"let's not do too involved things and be cheap by erring on the safe\n> and lazy side\" strategy in the call to ce_path_match() call made in\n> this function to avoid making calls to the attr subsystem.\n>\n> In other words, would it help the system by either simplifying the\n> processing done or reducing the cycle spent in preload_thread() if\n> we could tell ce_path_match() \"A pathspec we are checking may\n> require not just the pattern to match but also attributes given to\n> the path to satisfy the criteria, but for the purpose of preloading,\n> pretend that the attribute satisfies the match criteria\" (or\n> \"pretend that it does not match\"), thereby not having to make any\n> call into the attribute subsystem at all from this codepath?\n>\n> The strategy this round takes to make it unnecessary to punt\n> preloading (i.e. dropping \"pathspec: disable preload-index when\n> attribute pathspec magic is in use\" patch the old series had) is to\n> make the attribute subsystem thread-safe.  But that thread-safety in\n> the initial round is based on a single Big Attribute Lock, so it may\n> turn out that the end result performs better for this codepath if we\n> did not to make any call into the attribute subsystem.\n\nIt does sound good and I want to say \"yes please do this\", but is it\nmaking pathspec api a bit more complex (to express \"assume all\nattr-related criteria match\")? I guess we can have an api to simply\nfilter out attr-related magic (basically set attr_match_nr back to\nzero) then pass a safe (but more relaxing) pathspec to the threaded\ncode. That would not add big maintenance burden.\n\n> I am probably being two step ahead of ourselves by saying the above,\n> which is just something to keep in mind as a possible solution if\n> performance in this preload codepath becomes an issue when the\n> pathspec has attributes match specified.\n-- \nDuy\n"},{"id":"305640","messageId":"CAGZ79kZ=9QeZLKKrH27U2iE9x3WxgVe4RvCZpbdzZriMArV6Sg@mail.gmail.com","threadId":"44358","inReplyTo":"CACsJy8C2MLg4ncLBXXJGf+=mPF_rRoKs2vN6=+chZeNeXWZsbg@mail.gmail.com","subject":"Re: [PATCH 32/36] pathspec: allow querying for attributes","fromName":"Stefan Beller","fromEmail":"sbeller@google.com","sentAt":"2016-11-09T18:08:45Z","receivedAt":"2016-11-09T18:08:50Z","isPatch":true,"sender":{"key":"stefanbeller@gmail.com","avatar":"https://avatars.githubusercontent.com/u/455868?v=4"},"body":"On Wed, Nov 9, 2016 at 1:45 AM, Duy Nguyen <pclouds@gmail.com> wrote:\n> On Fri, Oct 28, 2016 at 1:29 AM, Junio C Hamano <gitster@pobox.com> wrote:\n>> The reason why I am bringing this up in this discussion thread on\n>> this patch is because I wonder if we would benefit by a similar\n>> \"let's not do too involved things and be cheap by erring on the safe\n>> and lazy side\" strategy in the call to ce_path_match() call made in\n>> this function to avoid making calls to the attr subsystem.\n>>\n>> In other words, would it help the system by either simplifying the\n>> processing done or reducing the cycle spent in preload_thread() if\n>> we could tell ce_path_match() \"A pathspec we are checking may\n>> require not just the pattern to match but also attributes given to\n>> the path to satisfy the criteria, but for the purpose of preloading,\n>> pretend that the attribute satisfies the match criteria\" (or\n>> \"pretend that it does not match\"), thereby not having to make any\n>> call into the attribute subsystem at all from this codepath?\n>>\n>> The strategy this round takes to make it unnecessary to punt\n>> preloading (i.e. dropping \"pathspec: disable preload-index when\n>> attribute pathspec magic is in use\" patch the old series had) is to\n>> make the attribute subsystem thread-safe.  But that thread-safety in\n>> the initial round is based on a single Big Attribute Lock, so it may\n>> turn out that the end result performs better for this codepath if we\n>> did not to make any call into the attribute subsystem.\n>\n> It does sound good and I want to say \"yes please do this\", but is it\n> making pathspec api a bit more complex (to express \"assume all\n> attr-related criteria match\")? I guess we can have an api to simply\n> filter out attr-related magic (basically set attr_match_nr back to\n> zero) then pass a safe (but more relaxing) pathspec to the threaded\n> code. That would not add big maintenance burden.\n>\n\nSo with the current implementation, we already have the shortcut as:\n\n     if (item->attr_match_nr && !match_attrs(name, namelen, item))\n\ni.e. if attr_match_nr is zero, we do not even look at the mutexes and such,\nso I am not sure what you intend to say in this email?\n"},{"id":"305647","messageId":"xmqqy40s8f5i.fsf@gitster.mtv.corp.google.com","threadId":"44358","inReplyTo":"CAGZ79kZ=9QeZLKKrH27U2iE9x3WxgVe4RvCZpbdzZriMArV6Sg@mail.gmail.com","subject":"Re: [PATCH 32/36] pathspec: allow querying for attributes","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2016-11-09T22:25:13Z","receivedAt":"2016-11-09T22:25:21Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Stefan Beller <sbeller@google.com> writes:\n\n> On Wed, Nov 9, 2016 at 1:45 AM, Duy Nguyen <pclouds@gmail.com> wrote:\n>> On Fri, Oct 28, 2016 at 1:29 AM, Junio C Hamano <gitster@pobox.com> wrote:\n>>> ...\n>>> The strategy this round takes to make it unnecessary to punt\n>>> preloading (i.e. dropping \"pathspec: disable preload-index when\n>>> attribute pathspec magic is in use\" patch the old series had) is to\n>>> make the attribute subsystem thread-safe.  But that thread-safety in\n>>> the initial round is based on a single Big Attribute Lock, so it may\n>>> turn out that the end result performs better for this codepath if we\n>>> did not to make any call into the attribute subsystem.\n>>\n>> It does sound good and I want to say \"yes please do this\", but is it\n>> making pathspec api a bit more complex (to express \"assume all\n>> attr-related criteria match\")? I guess we can have an api to simply\n>> filter out attr-related magic (basically set attr_match_nr back to\n>> zero) then pass a safe (but more relaxing) pathspec to the threaded\n>> code. That would not add big maintenance burden.\n>\n> So with the current implementation, we already have the shortcut as:\n>\n>      if (item->attr_match_nr && !match_attrs(name, namelen, item))\n>\n> i.e. if attr_match_nr is zero, we do not even look at the mutexes and such,\n> so I am not sure what you intend to say in this email?\n\nI am not sure what relevance the \"we call into attribute subsystem\nonly when there is any need to check attributes\" obvious short-cut\nhas to what is being discussed.\n\nThe issue is specific to what preloading is about.  It is merely an\nattempt to run cheap checks that could be easily multi-threaded with\nmultiple threads early in the program that we _know_ we would need\nto eventually refresh the index before doing some interesting work.\nA full refresh_index() will be done eventually, and because it needs\nto trigger thread-unsafe part of the API, it needs to be done in the\nmain thread.  Doing the preload allows us to mark index entries that\ndo not have to be scanned again in the upcoming refresh_index()\ncall.  It is OK for preload-index.c::preload_thread() to skip and\nnot mark some index entries (iow, its sole purpose is to leave a\nnote in each index entry \"this is fresh, you do not need to look at\nit again\", and it can choose to skip an entry, which essentially\nmeans \"this I didn't check, so you, refresh_index(), need to check\nyourself\").\n\npreload_thread() for example skips index entries that needs to\ntrigger \"racy Git avoidance\" logic that is heavyweight (it has to go\nto the filesystem and the object store), and it is a sensible thing\nto skip because they are rather rare.\n\nThe message by Duy you are responding to was his response to me who\nwondered if the attribute based pathspec match also falls into the\nsame category.  Just like racy Git code was deemed too heavyweight\nto be called from preloading codepath and CE_MATCH_RACY_IS_DIRTY bit\nwas added as a way to ask ie_match_stat() API to avoid it (and hence\nwe are skipping, the caller is also telling \"if you suspect a racy,\nwithout checking for real, just answer 'I cannot say it is\nclean/fresh'\"), if we invent a new flag and pass it through\nmatch_pathspec() down to match_pathspec_item() and have that if()\nstatement you quoted also skip match_attrs() for a pathspec element\nwith attribute based narrowing (as we are skipping, the flag may\nalso have say \"instead of checking the attributes, just pretend that\nthe path did not satisfy the attribute narrowing\"), would it benefit\nthe overall performance?\n\nTo answer Duy's \"would it make sense to force the caller to create a\nnew pathspec from an existing one by filtering out pathspec elements\nwith attr-based narrowing?\" question, I do not think it does.\n\n"}]}