{"thread":{"id":"44454","subject":"[PATCHv3 00/35] Revamp the attr subsystem!","startedAt":"2016-11-10T20:34:43Z","lastAt":"2016-11-29T17:37:49Z","messageCount":43,"participants":["Stefan Beller","Duy Nguyen","Brandon Williams"],"isPatch":false,"patchVersion":null,"patchTotal":null},"messages":[{"id":"305689","messageId":"20161110203428.30512-1-sbeller@google.com","threadId":"44454","inReplyTo":null,"subject":"[PATCHv3 00/35] Revamp the attr subsystem!","fromName":"Stefan Beller","fromEmail":"sbeller@google.com","sentAt":"2016-11-10T20:33:53Z","receivedAt":"2016-11-10T20:34:43Z","isPatch":false,"sender":{"key":"stefanbeller@gmail.com","avatar":"https://avatars.githubusercontent.com/u/455868?v=4"},"body":"previous discussion: https://public-inbox.org/git/20161028185502.8789-1-sbeller@google.com/\n\nChanges since v2:\n* dropped one patch (that exposes git_attr_counted, nobody uses\n  it throughout the series )\n* added pathspec guarding and added a test with git-add to see if it works.\n* squashed \"SQUASH\" commits.\n* changed test for multithreaded tests as git-status makes use of pathspec\n  code that doesn't support this new magic yet. \n\ninterdiff to currently queued at the bottom of this email.\n\nv2 was:\nprevious discussion at https://public-inbox.org/git/20161022233225.8883-1-sbeller@google.com\n\nThis implements the discarded series':\njc/attr\njc/attr-more\nsb/pathspec-label\nsb/submodule-default-paths\n\nThis includes\n* The fixes for windows\n* Junios latest suggestion to use git_attr_check_initv instead of\n  alloc/append.\n\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) as well as a test to disallow\n  multiple \"attr\"s in a pathspec.\n\nThanks,\nStefan\n\n\nJunio C Hamano (23):\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: 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 | 140 ++++---\n archive.c                                     |  25 +-\n attr.c                                        | 517 ++++++++++++++++++--------\n attr.h                                        |  71 ++--\n builtin/check-attr.c                          |  74 ++--\n builtin/clone.c                               |  36 +-\n builtin/pack-objects.c                        |  27 +-\n commit.c                                      |   3 +-\n common-main.c                                 |   2 +\n contrib/completion/git-completion.bash        |   1 +\n convert.c                                     |  46 ++-\n dir.c                                         |  41 +-\n git-submodule.sh                              |  21 +-\n hashmap.h                                     |   2 +\n ll-merge.c                                    |  35 +-\n pathspec.c                                    | 228 ++++++++++--\n pathspec.h                                    |  19 +-\n t/t0003-attributes.sh                         |  26 ++\n t/t6134-pathspec-with-labels.sh               | 201 ++++++++++\n t/t7400-submodule-basic.sh                    | 134 +++++++\n userdiff.c                                    |  21 +-\n ws.c                                          |  19 +-\n 27 files changed, 1354 insertions(+), 410 deletions(-)\n create mode 100755 t/t6134-pathspec-with-labels.sh\n\n-- interdiff to current origin/sb/attr:\n\ndiff --git a/attr.c b/attr.c\nindex cf15e76..979bd3c 100644\n--- a/attr.c\n+++ b/attr.c\n@@ -135,7 +135,7 @@ static void report_invalid_attr(const char *name, size_t len,\n \tstrbuf_release(&err);\n }\n \n-struct git_attr *git_attr_counted(const char *name, size_t len)\n+static struct git_attr *git_attr_internal(const char *name, int len)\n {\n \tunsigned hval = hash_name(name, len);\n \tunsigned pos = hval % HASHSIZE;\n@@ -171,7 +171,7 @@ struct git_attr *git_attr_counted(const char *name, size_t len)\n \n struct git_attr *git_attr(const char *name)\n {\n-\treturn git_attr_counted(name, strlen(name));\n+\treturn git_attr_internal(name, strlen(name));\n }\n \n /* What does a matched pattern decide? */\n@@ -259,7 +259,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_counted(cp, len);\n+\t\te->attr = git_attr_internal(cp, len);\n \t}\n \treturn ep + strspn(ep, blank);\n }\n@@ -320,7 +320,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_counted(name, namelen);\n+\t\tres->u.attr = git_attr_internal(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 e7534da..ab381ae 100644\n--- a/attr.h\n+++ b/attr.h\n@@ -2,7 +2,7 @@\n #define ATTR_H\n \n /*\n- * Must be called on platforms that do not support static initialisation\n+ * Must be called on platforms that do not support static initialization\n  * of mutexes.\n  */\n extern void attr_start(void);\ndiff --git a/dir.c b/dir.c\nindex 3ec9117..c56d2ee 100644\n--- a/dir.c\n+++ b/dir.c\n@@ -140,7 +140,8 @@ static size_t common_prefix_len(const struct pathspec *pathspec)\n \t\t       PATHSPEC_LITERAL |\n \t\t       PATHSPEC_GLOB |\n \t\t       PATHSPEC_ICASE |\n-\t\t       PATHSPEC_EXCLUDE);\n+\t\t       PATHSPEC_EXCLUDE |\n+\t\t       PATHSPEC_ATTR);\n \n \tfor (n = 0; n < pathspec->nr; n++) {\n \t\tsize_t i = 0, len = 0, item_len;\n@@ -349,7 +350,8 @@ static int do_match_pathspec(const struct pathspec *ps,\n \t\t       PATHSPEC_LITERAL |\n \t\t       PATHSPEC_GLOB |\n \t\t       PATHSPEC_ICASE |\n-\t\t       PATHSPEC_EXCLUDE);\n+\t\t       PATHSPEC_EXCLUDE |\n+\t\t       PATHSPEC_ATTR);\n \n \tif (!ps->nr) {\n \t\tif (!ps->recursive ||\ndiff --git a/pathspec.c b/pathspec.c\nindex 8ea5dcb..d702803 100644\n--- a/pathspec.c\n+++ b/pathspec.c\n@@ -232,6 +232,7 @@ static void eat_long_magic(struct pathspec_item *item, const char *elt,\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\t*magic |= PATHSPEC_ATTR;\n \t\t\tcontinue;\n \t\t}\n \ndiff --git a/pathspec.h b/pathspec.h\nindex ebef4bf..c2d046d 100644\n--- a/pathspec.h\n+++ b/pathspec.h\n@@ -8,13 +8,15 @@\n #define PATHSPEC_GLOB\t\t(1<<3)\n #define PATHSPEC_ICASE\t\t(1<<4)\n #define PATHSPEC_EXCLUDE\t(1<<5)\n+#define PATHSPEC_ATTR\t\t(1<<6)\n #define PATHSPEC_ALL_MAGIC\t  \\\n \t(PATHSPEC_FROMTOP\t| \\\n \t PATHSPEC_MAXDEPTH\t| \\\n \t PATHSPEC_LITERAL\t| \\\n \t PATHSPEC_GLOB\t\t| \\\n \t PATHSPEC_ICASE\t\t| \\\n-\t PATHSPEC_EXCLUDE)\n+\t PATHSPEC_EXCLUDE\t| \\\n+\t PATHSPEC_ATTR)\n \n #define PATHSPEC_ONESTAR 1\t/* the pathspec pattern satisfies GFNM_ONESTAR */\n \ndiff --git a/t/t6134-pathspec-with-labels.sh b/t/t6134-pathspec-with-labels.sh\nindex 3d03e0a..5aafc0c 100755\n--- a/t/t6134-pathspec-with-labels.sh\n+++ b/t/t6134-pathspec-with-labels.sh\n@@ -160,7 +160,23 @@ test_expect_success 'fail on multiple attr specifiers in one pathspec item' '\n '\n \n test_expect_success 'checking attributes in a multithreaded process' '\n-\tgit status \":(attr:labelB)\"\n+\tcat <<-EOF >expect &&\n+\tfileA:A\n+\tEOF\n+\techo A >fileA &&\n+\techo A >fileB &&\n+\tgit grep A -- \":(attr:labelA)\" >actual &&\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'fail if attr magic is used places not implemented' '\n+\t# The main purpose of this test is to check that we actually fail\n+\t# when you attempt to use attr magic in commands that do not implement\n+\t# attr magic. This test does not advocate git-add to stay that way,\n+\t# though, but git-add is convenient as it has its own internal pathspec\n+\t# parsing.\n+\ttest_must_fail git add \":(attr:labelB)\" 2>actual &&\n+\ttest_i18ngrep \"unsupported magic\" actual\n '\n \n test_expect_success 'abort on giving invalid label on the command line' '\n\n\n\n-- \n2.10.1.469.g00a8914\n\n"},{"id":"305690","messageId":"20161110203428.30512-3-sbeller@google.com","threadId":"44454","inReplyTo":"20161110203428.30512-1-sbeller@google.com","subject":"[PATCH 02/35] attr.c: use strchrnul() to scan for one line","fromName":"Stefan Beller","fromEmail":"sbeller@google.com","sentAt":"2016-11-10T20:33:55Z","receivedAt":"2016-11-10T20:34:47Z","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>\nSigned-off-by: Junio C Hamano <gitster@pobox.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.469.g00a8914\n\n"},{"id":"305691","messageId":"20161110203428.30512-7-sbeller@google.com","threadId":"44454","inReplyTo":"20161110203428.30512-1-sbeller@google.com","subject":"[PATCH 06/35] attr.c: mark where #if DEBUG ends more clearly","fromName":"Stefan Beller","fromEmail":"sbeller@google.com","sentAt":"2016-11-10T20:33:59Z","receivedAt":"2016-11-10T20:34:50Z","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>\nSigned-off-by: Junio C Hamano <gitster@pobox.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.469.g00a8914\n\n"},{"id":"305692","messageId":"20161110203428.30512-6-sbeller@google.com","threadId":"44454","inReplyTo":"20161110203428.30512-1-sbeller@google.com","subject":"[PATCH 05/35] attr.c: complete a sentence in a comment","fromName":"Stefan Beller","fromEmail":"sbeller@google.com","sentAt":"2016-11-10T20:33:58Z","receivedAt":"2016-11-10T20:34: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>\nSigned-off-by: Junio C Hamano <gitster@pobox.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.469.g00a8914\n\n"},{"id":"305693","messageId":"20161110203428.30512-10-sbeller@google.com","threadId":"44454","inReplyTo":"20161110203428.30512-1-sbeller@google.com","subject":"[PATCH 09/35] attr.c: plug small leak in parse_attr_line()","fromName":"Stefan Beller","fromEmail":"sbeller@google.com","sentAt":"2016-11-10T20:34:02Z","receivedAt":"2016-11-10T20:34: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\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>\nSigned-off-by: Junio C Hamano <gitster@pobox.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.469.g00a8914\n\n"},{"id":"305694","messageId":"20161110203428.30512-11-sbeller@google.com","threadId":"44454","inReplyTo":"20161110203428.30512-1-sbeller@google.com","subject":"[PATCH 10/35] attr: rename function and struct related to checking attributes","fromName":"Stefan Beller","fromEmail":"sbeller@google.com","sentAt":"2016-11-10T20:34:03Z","receivedAt":"2016-11-10T20:35:02Z","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>\nSigned-off-by: Junio C Hamano <gitster@pobox.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.469.g00a8914\n\n"},{"id":"305695","messageId":"20161110203428.30512-13-sbeller@google.com","threadId":"44454","inReplyTo":"20161110203428.30512-1-sbeller@google.com","subject":"[PATCH 12/35] attr: convert git_all_attrs() to use \"struct git_attr_check\"","fromName":"Stefan Beller","fromEmail":"sbeller@google.com","sentAt":"2016-11-10T20:34:05Z","receivedAt":"2016-11-10T20:35:03Z","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>\nSigned-off-by: Junio C Hamano <gitster@pobox.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.469.g00a8914\n\n"},{"id":"305696","messageId":"20161110203428.30512-12-sbeller@google.com","threadId":"44454","inReplyTo":"20161110203428.30512-1-sbeller@google.com","subject":"[PATCH 11/35] attr: (re)introduce git_check_attr() and struct git_attr_check","fromName":"Stefan Beller","fromEmail":"sbeller@google.com","sentAt":"2016-11-10T20:34:04Z","receivedAt":"2016-11-10T20:35:07Z","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>\nSigned-off-by: Junio C Hamano <gitster@pobox.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.469.g00a8914\n\n"},{"id":"305697","messageId":"20161110203428.30512-14-sbeller@google.com","threadId":"44454","inReplyTo":"20161110203428.30512-1-sbeller@google.com","subject":"[PATCH 13/35] attr: convert git_check_attrs() callers to use the new API","fromName":"Stefan Beller","fromEmail":"sbeller@google.com","sentAt":"2016-11-10T20:34:06Z","receivedAt":"2016-11-10T20:35:10Z","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>\nSigned-off-by: Junio C Hamano <gitster@pobox.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.469.g00a8914\n\n"},{"id":"305698","messageId":"20161110203428.30512-19-sbeller@google.com","threadId":"44454","inReplyTo":"20161110203428.30512-1-sbeller@google.com","subject":"[PATCH 18/35] attr.c: add push_stack() helper","fromName":"Stefan Beller","fromEmail":"sbeller@google.com","sentAt":"2016-11-10T20:34:11Z","receivedAt":"2016-11-10T20:35:13Z","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>\nSigned-off-by: Junio C Hamano <gitster@pobox.com>\n---\n attr.c | 71 +++++++++++++++++++++++++++++++-----------------------------------\n 1 file changed, 33 insertions(+), 38 deletions(-)\n\ndiff --git a/attr.c b/attr.c\nindex 88fcb7c..a504cb3 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.469.g00a8914\n\n"},{"id":"305699","messageId":"20161110203428.30512-21-sbeller@google.com","threadId":"44454","inReplyTo":"20161110203428.30512-1-sbeller@google.com","subject":"[PATCH 20/35] attr.c: rename a local variable check","fromName":"Stefan Beller","fromEmail":"sbeller@google.com","sentAt":"2016-11-10T20:34:13Z","receivedAt":"2016-11-10T20:35:16Z","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>\nSigned-off-by: Junio C Hamano <gitster@pobox.com>\n---\n attr.c | 4 ++--\n 1 file changed, 2 insertions(+), 2 deletions(-)\n\ndiff --git a/attr.c b/attr.c\nindex 219ab11..e18098c 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.469.g00a8914\n\n"},{"id":"305700","messageId":"20161110203428.30512-23-sbeller@google.com","threadId":"44454","inReplyTo":"20161110203428.30512-1-sbeller@google.com","subject":"[PATCH 22/35] attr.c: introduce empty_attr_check_elems()","fromName":"Stefan Beller","fromEmail":"sbeller@google.com","sentAt":"2016-11-10T20:34:15Z","receivedAt":"2016-11-10T20:35: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\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>\nSigned-off-by: Junio C Hamano <gitster@pobox.com>\n---\n attr.c | 11 +++++++++--\n 1 file changed, 9 insertions(+), 2 deletions(-)\n\ndiff --git a/attr.c b/attr.c\nindex 48460d2..5bb925d 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.469.g00a8914\n\n"},{"id":"305701","messageId":"20161110203428.30512-20-sbeller@google.com","threadId":"44454","inReplyTo":"20161110203428.30512-1-sbeller@google.com","subject":"[PATCH 19/35] attr.c: pass struct git_attr_check down the callchain","fromName":"Stefan Beller","fromEmail":"sbeller@google.com","sentAt":"2016-11-10T20:34:12Z","receivedAt":"2016-11-10T20:35: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\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>\nSigned-off-by: Junio C Hamano <gitster@pobox.com>\n---\n attr.c | 36 ++++++++++++++++++++++++------------\n 1 file changed, 24 insertions(+), 12 deletions(-)\n\ndiff --git a/attr.c b/attr.c\nindex a504cb3..219ab11 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.469.g00a8914\n\n"},{"id":"305702","messageId":"20161110203428.30512-18-sbeller@google.com","threadId":"44454","inReplyTo":"20161110203428.30512-1-sbeller@google.com","subject":"[PATCH 17/35] attr: support quoting pathname patterns in C style","fromName":"Stefan Beller","fromEmail":"sbeller@google.com","sentAt":"2016-11-10T20:34:10Z","receivedAt":"2016-11-10T20:35:27Z","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>\nSigned-off-by: Junio C Hamano <gitster@pobox.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 14bbfd4..88fcb7c 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.469.g00a8914\n\n"},{"id":"305703","messageId":"20161110203428.30512-17-sbeller@google.com","threadId":"44454","inReplyTo":"20161110203428.30512-1-sbeller@google.com","subject":"[PATCH 16/35] attr: expose validity check for attribute names","fromName":"Stefan Beller","fromEmail":"sbeller@google.com","sentAt":"2016-11-10T20:34:09Z","receivedAt":"2016-11-10T20:35:28Z","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>\nSigned-off-by: Junio C Hamano <gitster@pobox.com>\n---\n attr.c | 39 +++++++++++++++++++++++++--------------\n attr.h | 10 ++++++++++\n 2 files changed, 35 insertions(+), 14 deletions(-)\n\ndiff --git a/attr.c b/attr.c\nindex 9bec243..14bbfd4 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 static struct git_attr *git_attr_internal(const char *name, int len)\n@@ -90,7 +105,7 @@ static struct git_attr *git_attr_internal(const char *name, int 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 c84f164..1c6a6d8 100644\n--- a/attr.h\n+++ b/attr.h\n@@ -10,6 +10,16 @@ struct git_attr;\n  */\n struct git_attr *git_attr(const char *);\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 /* Internal use */\n extern const char git_attr__true[];\n extern const char git_attr__false[];\n-- \n2.10.1.469.g00a8914\n\n"},{"id":"305704","messageId":"20161110203428.30512-35-sbeller@google.com","threadId":"44454","inReplyTo":"20161110203428.30512-1-sbeller@google.com","subject":"[PATCH 34/35] clone: add --init-submodule=<pathspec> switch","fromName":"Stefan Beller","fromEmail":"sbeller@google.com","sentAt":"2016-11-10T20:34:27Z","receivedAt":"2016-11-10T20:35:32Z","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/config.txt    |  2 +-\n Documentation/git-clone.txt | 23 +++++++++----\n builtin/clone.c             | 36 ++++++++++++++++++--\n t/t7400-submodule-basic.sh  | 81 +++++++++++++++++++++++++++++++++++++++++++++\n 4 files changed, 132 insertions(+), 10 deletions(-)\n\ndiff --git a/Documentation/config.txt b/Documentation/config.txt\nindex 290de3b..917904e 100644\n--- a/Documentation/config.txt\n+++ b/Documentation/config.txt\n@@ -2887,7 +2887,7 @@ submodule.alternateErrorStrategy\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+\t`git submodule --init-default-path` by using the pathspec\n \tsyntax.\n \n tag.forceSignAnnotated::\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 6c80690..ef924c6 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@@ -730,13 +743,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@@ -884,6 +905,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.469.g00a8914\n\n"},{"id":"305705","messageId":"20161110203428.30512-27-sbeller@google.com","threadId":"44454","inReplyTo":"20161110203428.30512-1-sbeller@google.com","subject":"[PATCH 26/35] attr: convert to new threadsafe API","fromName":"Stefan Beller","fromEmail":"sbeller@google.com","sentAt":"2016-11-10T20:34:19Z","receivedAt":"2016-11-10T20:35:34Z","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>\nSigned-off-by: Junio C Hamano <gitster@pobox.com>\n\n# Conflicts:\n#\tattr.h\n\nSigned-off-by: Stefan Beller <sbeller@google.com>\n---\n Documentation/technical/api-gitattributes.txt | 136 +++++++++++---------\n archive.c                                     |  11 +-\n attr.c                                        | 172 +++++++++++++++++---------\n attr.h                                        |  72 ++++++-----\n builtin/check-attr.c                          |  50 ++++----\n builtin/pack-objects.c                        |  16 +--\n common-main.c                                 |   2 +\n convert.c                                     |  40 +++---\n ll-merge.c                                    |  24 ++--\n userdiff.c                                    |  16 +--\n ws.c                                          |   8 +-\n 11 files changed, 323 insertions(+), 224 deletions(-)\n\ndiff --git a/Documentation/technical/api-gitattributes.txt b/Documentation/technical/api-gitattributes.txt\nindex 92fc32a..7dd6616 100644\n--- a/Documentation/technical/api-gitattributes.txt\n+++ b/Documentation/technical/api-gitattributes.txt\n@@ -10,21 +10,23 @@ Data Structure\n \n `struct git_attr`::\n \n-\tAn attribute is an opaque object that is identified by its name.\n-\tPass the name to `git_attr()` function to obtain the object of\n-\tthis type.  The internal representation of this structure is\n+\tThe internal representation of this structure is\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 +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,28 @@ 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-  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+* Prepare a `struct git_attr_check` using `git_attr_check_initl()` or\n+  `git_attr_check_initv()` function, enumerating the names of\n+  attributes whose values you are interested in, terminated with\n+  a NULL.\n+  Preparing the `struct git_attr_check` is thread safe, i.e. you\n+  can call it from different threads at the same time; when check\n+  determines the initialization is still needed, the threads will\n+  use a single global mutex to perform the initialization just once,\n+  the 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@@ -73,31 +84,22 @@ Example\n To see how attributes \"crlf\" and \"ident\" are set for different paths.\n \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;\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-------------\n-\n-. Call `git_check_attr()` with the prepared `struct git_attr_check`:\n+  we are checking two attributes) and then call `git_check_attr()`\n+  with the prepared `struct git_attr_check`:\n \n ------------\n \tconst char *path;\n+\tstruct git_attr_result result[2];\n+\tstatic struct git_attr_check *check;\n \n-\tsetup_check();\n-\tgit_check_attr(path, check);\n+\tgit_attr_check_initl(&check, \"crlf\", \"ident\", NULL);\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@@ -116,19 +118,28 @@ 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+To see how attributes in argv[] are set for different paths, use\n+`git_attr_check_initv` instead. When an invalid attr name is given\n+both init functions die unconditionally with a non user friendly message.\n+So you want to check the attr values first:\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+\tconst char **argv_copy = argv;\n+\twhile (*argv_copy) {\n+\t\tconst char *name = argv_copy[0];\n+\t\tconst int len = strlen(argv_copy[0]);\n+\t\tif (!attr_name_valid(name, len)) {\n+\t\t\tstruct strbuf sb = STRBUF_INIT;\n+\t\t\tinvalid_attr_name_message(&sb, name, len);\n+\t\t\tdie (\"Oops this is bad: %s\", sb.buf);\n+\t\t}\n+\t\targv_copy++;\n \t}\n+\tgit_attr_check_initv(&check, argv);\n+\tstruct git_attr_result *result = git_attr_result_alloc(check);\n }\n ------------\n \n@@ -138,17 +149,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 19a3651..32ec453 100644\n--- a/attr.c\n+++ b/attr.c\n@@ -14,6 +14,8 @@\n #include \"dir.h\"\n #include \"utf8.h\"\n #include \"quote.h\"\n+#include \"thread-utils.h\"\n+#include \"argv-array.h\"\n \n const char git_attr__true[] = \"(builtin)true\";\n const char git_attr__false[] = \"\\0(builtin)false\";\n@@ -46,6 +48,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;\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 +72,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 +808,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 +826,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 +854,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 +872,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,69 +934,79 @@ 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+static void git_attr_check_initv_unlocked(struct git_attr_check **check_,\n+\t\t\t\t const char **argv)\n {\n-\treturn git_check_attr_counted(path, strlen(path), check);\n+\tint cnt;\n+\tstruct git_attr_check *check;\n+\n+\tfor (cnt = 0; argv[cnt] != NULL; cnt++)\n+\t\t;\n+\n+\tcheck = xcalloc(1, sizeof(*check) + cnt * sizeof(*(check->attr)));\n+\tcheck->check_nr = cnt;\n+\tcheck->finalized = 1;\n+\tcheck->attr = (const struct git_attr **)(check + 1);\n+\n+\tfor (cnt = 0; cnt < check->check_nr; cnt++) {\n+\t\tstruct git_attr *attr = git_attr(argv[cnt]);\n+\t\tif (!attr)\n+\t\t\tdie(\"BUG: %s: not a valid attribute name\", argv[cnt]);\n+\t\tcheck->attr[cnt] = attr;\n+\t}\n+\n+\t*check_ = check;\n }\n \n-struct git_attr_check *git_attr_check_initl(const char *one, ...)\n+void git_attr_check_initl(struct git_attr_check **check,\n+\t\t\t  const char *one, ...)\n {\n-\tstruct git_attr_check *check;\n \tint cnt;\n \tva_list params;\n \tconst char *param;\n+\tstruct argv_array attrs = ARGV_ARRAY_INIT;\n+\n+\tattr_lock();\n+\tif (*check) {\n+\t\tattr_unlock();\n+\t\treturn;\n+\t}\n \n \tva_start(params, one);\n+\targv_array_push(&attrs, one);\n \tfor (cnt = 1; (param = va_arg(params, const char *)) != NULL; cnt++)\n-\t\t;\n+\t\targv_array_push(&attrs, param);\n \tva_end(params);\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\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+\tgit_attr_check_initv_unlocked(check, attrs.argv);\n+\n+\tattr_unlock();\n+\targv_array_clear(&attrs);\n }\n \n-struct git_attr_check *git_attr_check_alloc(void)\n+void git_attr_check_initv(struct git_attr_check **check, const char **argv)\n {\n-\treturn xcalloc(1, sizeof(struct git_attr_check));\n+\tattr_lock();\n+\tif (*check) {\n+\t\tattr_unlock();\n+\t\treturn;\n+\t}\n+\tgit_attr_check_initv_unlocked(check, argv);\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-\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+\treturn xcalloc(1, sizeof(struct git_attr_result) * check->check_nr);\n }\n \n void git_attr_check_clear(struct git_attr_check *check)\n@@ -962,12 +1014,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 7416b14..1a50288 100644\n--- a/attr.h\n+++ b/attr.h\n@@ -1,14 +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 initialization\n+ * of mutexes.\n  */\n-struct git_attr *git_attr(const char *);\n+extern void attr_start(void);\n+\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@@ -29,44 +29,48 @@ 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-\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+struct git_attr_result {\n+\tconst char *value;\n+};\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+ * Initialize the `git_attr_check` via one of the following three functions:\n+ *\n+ * git_all_attrs         allocates a check and fills in all attributes and\n+ *                       results that 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+ * git_attr_check_initv  takes a pointer to where the check will be initialized,\n+ *                       and a NULL terminated array of attributes.\n+ *\n+ * All initialization methods are thread safe.\n+ * To allocate memory for the result of a given check,\n+ * use git_attr_result_alloc.\n  */\n-extern const char *git_attr_name(const struct git_attr *);\n+extern void git_attr_check_initl(struct git_attr_check **,\n+\t\t\t\t const char *, ...);\n+extern void git_attr_check_initv(struct git_attr_check **,\n+\t\t\t\t const char **);\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+extern struct git_attr_result *git_attr_result_alloc(struct git_attr_check *check);\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+/* 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_result_free(struct git_attr_result *);\n \n enum git_attr_direction {\n \tGIT_ATTR_CHECKIN,\ndiff --git a/builtin/check-attr.c b/builtin/check-attr.c\nindex ec61476..418c3da 100644\n--- a/builtin/check-attr.c\n+++ b/builtin/check-attr.c\n@@ -3,6 +3,7 @@\n #include \"attr.h\"\n #include \"quote.h\"\n #include \"parse-options.h\"\n+#include \"argv-array.h\"\n \n static int all_attrs;\n static int cached_attrs;\n@@ -24,13 +25,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 +46,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 +61,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 +108,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,17 +168,19 @@ 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\tstruct argv_array attrs = ARGV_ARRAY_INIT;\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\targv_array_push(&attrs, argv[i]);\n \t\t}\n+\t\tgit_attr_check_initv(&check, attrs.argv);\n+\t\targv_array_clear(&attrs);\n \t}\n \n \tif (stdin_paths)\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/common-main.c b/common-main.c\nindex 44a29e8..d4699cd 100644\n--- a/common-main.c\n+++ b/common-main.c\n@@ -1,5 +1,6 @@\n #include \"cache.h\"\n #include \"exec_cmd.h\"\n+#include \"attr.h\"\n \n /*\n  * Many parts of Git have subprograms communicate via pipe, expect the\n@@ -32,6 +33,7 @@ int main(int argc, const char **argv)\n \tsanitize_stdfds();\n \n \tgit_setup_gettext();\n+\tattr_start();\n \n \targv[0] = git_extract_argv0_path(argv[0]);\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.469.g00a8914\n\n"},{"id":"305706","messageId":"20161110203428.30512-36-sbeller@google.com","threadId":"44454","inReplyTo":"20161110203428.30512-1-sbeller@google.com","subject":"[PATCH 35/35] completion: clone can initialize specific submodules","fromName":"Stefan Beller","fromEmail":"sbeller@google.com","sentAt":"2016-11-10T20:34:28Z","receivedAt":"2016-11-10T20:35:37Z","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 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.469.g00a8914\n\n"},{"id":"305707","messageId":"20161110203428.30512-28-sbeller@google.com","threadId":"44454","inReplyTo":"20161110203428.30512-1-sbeller@google.com","subject":"[PATCH 27/35] attr: keep attr stack for each check","fromName":"Stefan Beller","fromEmail":"sbeller@google.com","sentAt":"2016-11-10T20:34:20Z","receivedAt":"2016-11-10T20:35:40Z","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\nSigned-off-by: Stefan Beller <sbeller@google.com>\nSigned-off-by: Junio C Hamano <gitster@pobox.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 32ec453..979bd3c 100644\n--- a/attr.c\n+++ b/attr.c\n@@ -375,15 +375,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+static struct hashmap all_attr_stacks;\n+static int all_attr_stacks_init;\n \n static void free_attr_elem(struct attr_stack *e)\n {\n@@ -564,11 +566,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@@ -598,40 +612,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@@ -651,13 +667,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@@ -665,17 +681,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@@ -691,9 +707,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@@ -707,7 +723,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@@ -717,7 +733,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@@ -743,9 +765,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@@ -761,14 +784,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@@ -779,12 +802,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@@ -793,13 +816,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@@ -848,7 +871,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@@ -868,8 +891,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@@ -902,6 +925,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@@ -911,6 +936,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 1a50288..ab381ae 100644\n--- a/attr.h\n+++ b/attr.h\n@@ -30,12 +30,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.469.g00a8914\n\n"},{"id":"305708","messageId":"20161110203428.30512-34-sbeller@google.com","threadId":"44454","inReplyTo":"20161110203428.30512-1-sbeller@google.com","subject":"[PATCH 33/35] submodule update: add `--init-default-path` switch","fromName":"Stefan Beller","fromEmail":"sbeller@google.com","sentAt":"2016-11-10T20:34:26Z","receivedAt":"2016-11-10T20:35:41Z","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 a179474..290de3b 100644\n--- a/Documentation/config.txt\n+++ b/Documentation/config.txt\n@@ -2885,6 +2885,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.469.g00a8914\n\n"},{"id":"305709","messageId":"20161110203428.30512-29-sbeller@google.com","threadId":"44454","inReplyTo":"20161110203428.30512-1-sbeller@google.com","subject":"[PATCH 28/35] Documentation: fix a typo","fromName":"Stefan Beller","fromEmail":"sbeller@google.com","sentAt":"2016-11-10T20:34:21Z","receivedAt":"2016-11-10T20:35:42Z","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.469.g00a8914\n\n"},{"id":"305710","messageId":"20161110203428.30512-32-sbeller@google.com","threadId":"44454","inReplyTo":"20161110203428.30512-1-sbeller@google.com","subject":"[PATCH 31/35] pathspec: allow querying for attributes","fromName":"Stefan Beller","fromEmail":"sbeller@google.com","sentAt":"2016-11-10T20:34:24Z","receivedAt":"2016-11-10T20:35:46Z","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                              |  41 +++++++-\n pathspec.c                         | 106 +++++++++++++++++++-\n pathspec.h                         |  19 +++-\n t/t6134-pathspec-with-labels.sh    | 191 +++++++++++++++++++++++++++++++++++++\n 5 files changed, 370 insertions(+), 7 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..c56d2ee 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@@ -139,7 +140,8 @@ static size_t common_prefix_len(const struct pathspec *pathspec)\n \t\t       PATHSPEC_LITERAL |\n \t\t       PATHSPEC_GLOB |\n \t\t       PATHSPEC_ICASE |\n-\t\t       PATHSPEC_EXCLUDE);\n+\t\t       PATHSPEC_EXCLUDE |\n+\t\t       PATHSPEC_ATTR);\n \n \tfor (n = 0; n < pathspec->nr; n++) {\n \t\tsize_t i = 0, len = 0, item_len;\n@@ -207,6 +209,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 +295,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;\n@@ -314,7 +350,8 @@ static int do_match_pathspec(const struct pathspec *ps,\n \t\t       PATHSPEC_LITERAL |\n \t\t       PATHSPEC_GLOB |\n \t\t       PATHSPEC_ICASE |\n-\t\t       PATHSPEC_EXCLUDE);\n+\t\t       PATHSPEC_EXCLUDE |\n+\t\t       PATHSPEC_ATTR);\n \n \tif (!ps->nr) {\n \t\tif (!ps->recursive ||\ndiff --git a/pathspec.c b/pathspec.c\nindex d44f8e7..4aa7ea5 100644\n--- a/pathspec.c\n+++ b/pathspec.c\n@@ -1,6 +1,8 @@\n #include \"cache.h\"\n #include \"dir.h\"\n #include \"pathspec.h\"\n+#include \"attr.h\"\n+#include \"argv-array.h\"\n \n /*\n  * Finds which of the given pathspecs match items in the index.\n@@ -88,12 +90,79 @@ 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+\tstruct argv_array attrs = ARGV_ARRAY_INIT;\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+\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\tif (!attr_name_valid(attr, attr_len)) {\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\targv_array_push(&attrs, xmemdupz(attr, attr_len));\n+\t}\n+\n+\tif (!item->attr_check)\n+\t\tgit_attr_check_initv(&item->attr_check, attrs.argv);\n+\telse\n+\t\tdie(_(\"Only one 'attr:' specification is allowed.\"));\n+\n+\targv_array_clear(&attrs);\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 +177,22 @@ 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\t*magic |= PATHSPEC_ATTR;\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 +501,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 +526,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 +586,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..c2d046d 100644\n--- a/pathspec.h\n+++ b/pathspec.h\n@@ -8,13 +8,15 @@\n #define PATHSPEC_GLOB\t\t(1<<3)\n #define PATHSPEC_ICASE\t\t(1<<4)\n #define PATHSPEC_EXCLUDE\t(1<<5)\n+#define PATHSPEC_ATTR\t\t(1<<6)\n #define PATHSPEC_ALL_MAGIC\t  \\\n \t(PATHSPEC_FROMTOP\t| \\\n \t PATHSPEC_MAXDEPTH\t| \\\n \t PATHSPEC_LITERAL\t| \\\n \t PATHSPEC_GLOB\t\t| \\\n \t PATHSPEC_ICASE\t\t| \\\n-\t PATHSPEC_EXCLUDE)\n+\t PATHSPEC_EXCLUDE\t| \\\n+\t PATHSPEC_ATTR)\n \n #define PATHSPEC_ONESTAR 1\t/* the pathspec pattern satisfies GFNM_ONESTAR */\n \n@@ -32,6 +34,21 @@ 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\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..e276811\n--- /dev/null\n+++ b/t/t6134-pathspec-with-labels.sh\n@@ -0,0 +1,191 @@\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 'fail on multiple attr specifiers in one pathspec item' '\n+\ttest_must_fail git ls-files . \":(attr:labelB,attr:labelC)\" 2>actual &&\n+\ttest_i18ngrep \"Only one\" actual\n+'\n+\n+test_expect_success 'checking attributes in a multithreaded process' '\n+\tcat <<-EOF >expect &&\n+\tfileA:A\n+\tEOF\n+\techo A >fileA &&\n+\techo A >fileB &&\n+\tgit grep A -- \":(attr:labelA)\" >actual &&\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'fail if attr magic is used places not implemented' '\n+\t# The main purpose of this test is to check that we actually fail\n+\t# when you attempt to use attr magic in commands that do not implement\n+\t# attr magic. This test does not advocate git-add to stay that way,\n+\t# though, but git-add is convenient as it has its own internal pathspec\n+\t# parsing.\n+\ttest_must_fail git add \":(attr:labelB)\" 2>actual &&\n+\ttest_i18ngrep \"unsupported magic\" actual\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.469.g00a8914\n\n"},{"id":"305711","messageId":"20161110203428.30512-33-sbeller@google.com","threadId":"44454","inReplyTo":"20161110203428.30512-1-sbeller@google.com","subject":"[PATCH 32/35] pathspec: allow escaped query values","fromName":"Stefan Beller","fromEmail":"sbeller@google.com","sentAt":"2016-11-10T20:34:25Z","receivedAt":"2016-11-10T20:35:47Z","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 4aa7ea5..d702803 100644\n--- a/pathspec.c\n+++ b/pathspec.c\n@@ -90,13 +90,57 @@ 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 \tstruct argv_array attrs = ARGV_ARRAY_INIT;\n \n-\n \tif (!value || !strlen(value))\n \t\tdie(_(\"attr spec must not be empty\"));\n \n@@ -128,10 +172,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@@ -168,7 +211,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 e276811..5aafc0c 100755\n--- a/t/t6134-pathspec-with-labels.sh\n+++ b/t/t6134-pathspec-with-labels.sh\n@@ -188,4 +188,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.469.g00a8914\n\n"},{"id":"305712","messageId":"20161110203428.30512-31-sbeller@google.com","threadId":"44454","inReplyTo":"20161110203428.30512-1-sbeller@google.com","subject":"[PATCH 30/35] pathspec: move prefix check out of the inner loop","fromName":"Stefan Beller","fromEmail":"sbeller@google.com","sentAt":"2016-11-10T20:34:23Z","receivedAt":"2016-11-10T20:35:51Z","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.469.g00a8914\n\n"},{"id":"305713","messageId":"20161110203428.30512-30-sbeller@google.com","threadId":"44454","inReplyTo":"20161110203428.30512-1-sbeller@google.com","subject":"[PATCH 29/35] pathspec: move long magic parsing out of prefix_pathspec","fromName":"Stefan Beller","fromEmail":"sbeller@google.com","sentAt":"2016-11-10T20:34:22Z","receivedAt":"2016-11-10T20:35:52Z","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.469.g00a8914\n\n"},{"id":"305714","messageId":"20161110203428.30512-24-sbeller@google.com","threadId":"44454","inReplyTo":"20161110203428.30512-1-sbeller@google.com","subject":"[PATCH 23/35] attr.c: always pass check[] to collect_some_attrs()","fromName":"Stefan Beller","fromEmail":"sbeller@google.com","sentAt":"2016-11-10T20:34:16Z","receivedAt":"2016-11-10T20:35: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\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>\nSigned-off-by: Junio C Hamano <gitster@pobox.com>\n---\n attr.c | 37 +++++++++++++++++++------------------\n 1 file changed, 19 insertions(+), 18 deletions(-)\n\ndiff --git a/attr.c b/attr.c\nindex 5bb925d..246a5d0 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.469.g00a8914\n\n"},{"id":"305715","messageId":"20161110203428.30512-26-sbeller@google.com","threadId":"44454","inReplyTo":"20161110203428.30512-1-sbeller@google.com","subject":"[PATCH 25/35] attr: make git_check_attr_counted static","fromName":"Stefan Beller","fromEmail":"sbeller@google.com","sentAt":"2016-11-10T20:34:18Z","receivedAt":"2016-11-10T20:35:57Z","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>\nSigned-off-by: Junio C Hamano <gitster@pobox.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 60d7eec..19a3651 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 1c6a6d8..7416b14 100644\n--- a/attr.h\n+++ b/attr.h\n@@ -48,7 +48,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.469.g00a8914\n\n"},{"id":"305716","messageId":"20161110203428.30512-25-sbeller@google.com","threadId":"44454","inReplyTo":"20161110203428.30512-1-sbeller@google.com","subject":"[PATCH 24/35] attr.c: outline the future plans by heavily commenting","fromName":"Stefan Beller","fromEmail":"sbeller@google.com","sentAt":"2016-11-10T20:34:17Z","receivedAt":"2016-11-10T20:35: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\nSigned-off-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 attr.c | 40 +++++++++++++++++++++++++++++++++++++++-\n 1 file changed, 39 insertions(+), 1 deletion(-)\n\ndiff --git a/attr.c b/attr.c\nindex 246a5d0..60d7eec 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 @@ static struct git_attr *git_attr_internal(const char *name, int 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.469.g00a8914\n\n"},{"id":"305717","messageId":"20161110203428.30512-22-sbeller@google.com","threadId":"44454","inReplyTo":"20161110203428.30512-1-sbeller@google.com","subject":"[PATCH 21/35] attr.c: correct ugly hack for git_all_attrs()","fromName":"Stefan Beller","fromEmail":"sbeller@google.com","sentAt":"2016-11-10T20:34:14Z","receivedAt":"2016-11-10T20:36:02Z","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>\nSigned-off-by: Junio C Hamano <gitster@pobox.com>\n---\n attr.c | 24 ++++++++----------------\n 1 file changed, 8 insertions(+), 16 deletions(-)\n\ndiff --git a/attr.c b/attr.c\nindex e18098c..48460d2 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.469.g00a8914\n\n"},{"id":"305718","messageId":"20161110203428.30512-15-sbeller@google.com","threadId":"44454","inReplyTo":"20161110203428.30512-1-sbeller@google.com","subject":"[PATCH 14/35] attr: retire git_check_attrs() API","fromName":"Stefan Beller","fromEmail":"sbeller@google.com","sentAt":"2016-11-10T20:34:07Z","receivedAt":"2016-11-10T20:36: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\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>\nSigned-off-by: Junio C Hamano <gitster@pobox.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.469.g00a8914\n\n"},{"id":"305719","messageId":"20161110203428.30512-2-sbeller@google.com","threadId":"44454","inReplyTo":"20161110203428.30512-1-sbeller@google.com","subject":"[PATCH 01/35] commit.c: use strchrnul() to scan for one line","fromName":"Stefan Beller","fromEmail":"sbeller@google.com","sentAt":"2016-11-10T20:33:54Z","receivedAt":"2016-11-10T20:36:07Z","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>\nSigned-off-by: Junio C Hamano <gitster@pobox.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.469.g00a8914\n\n"},{"id":"305720","messageId":"20161110203428.30512-16-sbeller@google.com","threadId":"44454","inReplyTo":"20161110203428.30512-1-sbeller@google.com","subject":"[PATCH 15/35] attr: add counted string version of git_check_attr()","fromName":"Stefan Beller","fromEmail":"sbeller@google.com","sentAt":"2016-11-10T20:34:08Z","receivedAt":"2016-11-10T20:36:10Z","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>\nSigned-off-by: Junio C Hamano <gitster@pobox.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.469.g00a8914\n\n"},{"id":"305721","messageId":"20161110203428.30512-9-sbeller@google.com","threadId":"44454","inReplyTo":"20161110203428.30512-1-sbeller@google.com","subject":"[PATCH 08/35] attr.c: tighten constness around \"git_attr\" structure","fromName":"Stefan Beller","fromEmail":"sbeller@google.com","sentAt":"2016-11-10T20:34:01Z","receivedAt":"2016-11-10T20:36: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\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>\nSigned-off-by: Junio C Hamano <gitster@pobox.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.469.g00a8914\n\n"},{"id":"305722","messageId":"20161110203428.30512-4-sbeller@google.com","threadId":"44454","inReplyTo":"20161110203428.30512-1-sbeller@google.com","subject":"[PATCH 03/35] attr.c: update a stale comment on \"struct match_attr\"","fromName":"Stefan Beller","fromEmail":"sbeller@google.com","sentAt":"2016-11-10T20:33:56Z","receivedAt":"2016-11-10T20:36: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\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>\nSigned-off-by: Junio C Hamano <gitster@pobox.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.469.g00a8914\n\n"},{"id":"305723","messageId":"20161110203428.30512-8-sbeller@google.com","threadId":"44454","inReplyTo":"20161110203428.30512-1-sbeller@google.com","subject":"[PATCH 07/35] attr.c: simplify macroexpand_one()","fromName":"Stefan Beller","fromEmail":"sbeller@google.com","sentAt":"2016-11-10T20:34:00Z","receivedAt":"2016-11-10T20:36: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\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>\nSigned-off-by: Junio C Hamano <gitster@pobox.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.469.g00a8914\n\n"},{"id":"305724","messageId":"20161110203428.30512-5-sbeller@google.com","threadId":"44454","inReplyTo":"20161110203428.30512-1-sbeller@google.com","subject":"[PATCH 04/35] attr.c: explain the lack of attr-name syntax check in parse_attr()","fromName":"Stefan Beller","fromEmail":"sbeller@google.com","sentAt":"2016-11-10T20:33:57Z","receivedAt":"2016-11-10T20:36:16Z","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>\nSigned-off-by: Junio C Hamano <gitster@pobox.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.469.g00a8914\n\n"},{"id":"306296","messageId":"CACsJy8BXjYOza_1mPCJJw+Mk1zksLLJMBNKvbAk8+1-bdAGJMw@mail.gmail.com","threadId":"44454","inReplyTo":"20161110203428.30512-32-sbeller@google.com","subject":"Re: [PATCH 31/35] pathspec: allow querying for attributes","fromName":"Duy Nguyen","fromEmail":"pclouds@gmail.com","sentAt":"2016-11-22T10:41:30Z","receivedAt":"2016-11-22T10:43:12Z","isPatch":true,"sender":{"key":"pclouds@gmail.com","avatar":"https://avatars.githubusercontent.com/u/720?v=4"},"body":"On Fri, Nov 11, 2016 at 3:34 AM, Stefan Beller <sbeller@google.com> wrote:\n> @@ -139,7 +140,8 @@ static size_t common_prefix_len(const struct pathspec *pathspec)\n>                        PATHSPEC_LITERAL |\n>                        PATHSPEC_GLOB |\n>                        PATHSPEC_ICASE |\n> -                      PATHSPEC_EXCLUDE);\n> +                      PATHSPEC_EXCLUDE |\n> +                      PATHSPEC_ATTR);\n\nHmm.. common_prefix_len() has always been a bit relaxing and can cover\nmore than needed. It's for early pruning. Exact pathspec matching\n_will_ be done later anyway.\n\nIs that obvious? I'm wondering if we need to add a line or two in the\nbig comment code before this statement. I'm thinking it is and we\nprobably don't need more comments...\n-- \nDuy\n"},{"id":"306316","messageId":"CAGZ79kYm4LfXK=1j-ayLawt+BojnkyM4h2RLQ=kfpPgMQbdBag@mail.gmail.com","threadId":"44454","inReplyTo":"CACsJy8BXjYOza_1mPCJJw+Mk1zksLLJMBNKvbAk8+1-bdAGJMw@mail.gmail.com","subject":"Re: [PATCH 31/35] pathspec: allow querying for attributes","fromName":"Stefan Beller","fromEmail":"sbeller@google.com","sentAt":"2016-11-22T17:26:07Z","receivedAt":"2016-11-22T17:26:14Z","isPatch":true,"sender":{"key":"stefanbeller@gmail.com","avatar":"https://avatars.githubusercontent.com/u/455868?v=4"},"body":"On Tue, Nov 22, 2016 at 2:41 AM, Duy Nguyen <pclouds@gmail.com> wrote:\n> On Fri, Nov 11, 2016 at 3:34 AM, Stefan Beller <sbeller@google.com> wrote:\n>> @@ -139,7 +140,8 @@ static size_t common_prefix_len(const struct pathspec *pathspec)\n>>                        PATHSPEC_LITERAL |\n>>                        PATHSPEC_GLOB |\n>>                        PATHSPEC_ICASE |\n>> -                      PATHSPEC_EXCLUDE);\n>> +                      PATHSPEC_EXCLUDE |\n>> +                      PATHSPEC_ATTR);\n>\n> Hmm.. common_prefix_len() has always been a bit relaxing and can cover\n> more than needed. It's for early pruning. Exact pathspec matching\n> _will_ be done later anyway.\n>\n> Is that obvious?\n\nYes it is.\nNot sure what your concern is, though.\n\nGiven the pathspec \":(attr:plumbing)Documentation/\", the common_prefix_len\nis still able to figure out that any match has a prefix of\nstrlen(\"Documentation/\"),\nno matter what attr stuff is involved, because the attr stuff is also\njust reducing the\nmatching set.\n\nNow if we have such a pathspec it is easier to claim common_prefix_len supports\nattr as it is a correct thing to ignore the attrs completely, than to\nrewrite the call to\ncommon_prefix_len to be without attrs involved.\n\nYou *may* be able to improve it with knowledge of the attrs:\n\":(attr:internal-technical-api)Documentation/\" may restrict the results to match\nonly \"Documentation/technical/\", but as you said, we don't have to be\nexact here.\n\n> I'm wondering if we need to add a line or two in the\n> big comment code before this statement. I'm thinking it is and we\n> probably don't need more comments...\n\nDo I misunderstand the code completely here?\n\nThanks,\nStefan\n\n> --\n> Duy\n"},{"id":"306371","messageId":"CACsJy8C5K5UyOYeC0wU8_QVz67-+B6O7ydv7w=70se5X=1Ttsw@mail.gmail.com","threadId":"44454","inReplyTo":"CAGZ79kYm4LfXK=1j-ayLawt+BojnkyM4h2RLQ=kfpPgMQbdBag@mail.gmail.com","subject":"Re: [PATCH 31/35] pathspec: allow querying for attributes","fromName":"Duy Nguyen","fromEmail":"pclouds@gmail.com","sentAt":"2016-11-23T09:38:33Z","receivedAt":"2016-11-23T09:41:32Z","isPatch":true,"sender":{"key":"pclouds@gmail.com","avatar":"https://avatars.githubusercontent.com/u/720?v=4"},"body":"On Wed, Nov 23, 2016 at 12:26 AM, Stefan Beller <sbeller@google.com> wrote:\n> On Tue, Nov 22, 2016 at 2:41 AM, Duy Nguyen <pclouds@gmail.com> wrote:\n>> On Fri, Nov 11, 2016 at 3:34 AM, Stefan Beller <sbeller@google.com> wrote:\n>>> @@ -139,7 +140,8 @@ static size_t common_prefix_len(const struct pathspec *pathspec)\n>>>                        PATHSPEC_LITERAL |\n>>>                        PATHSPEC_GLOB |\n>>>                        PATHSPEC_ICASE |\n>>> -                      PATHSPEC_EXCLUDE);\n>>> +                      PATHSPEC_EXCLUDE |\n>>> +                      PATHSPEC_ATTR);\n>>\n>> Hmm.. common_prefix_len() has always been a bit relaxing and can cover\n>> more than needed. It's for early pruning. Exact pathspec matching\n>> _will_ be done later anyway.\n>>\n>> Is that obvious?\n>\n> Yes it is.\n> Not sure what your concern is, though.\n\nNone really. I was just thinking out loud and trying not to make\nassumptions, because I know this code quite well and I don't know how\npeople see this code anymore :D So all is good then.\n-- \nDuy\n"},{"id":"306504","messageId":"20161128180223.GA150448@google.com","threadId":"44454","inReplyTo":"20161110203428.30512-32-sbeller@google.com","subject":"Re: [PATCH 31/35] pathspec: allow querying for attributes","fromName":"Brandon Williams","fromEmail":"bmwill@google.com","sentAt":"2016-11-28T18:02:23Z","receivedAt":"2016-11-28T18:02:30Z","isPatch":true,"sender":{"key":"bwilliams.eng@gmail.com","avatar":null},"body":"On 11/10, Stefan Beller wrote:\n> @@ -500,6 +586,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\nThis looks like a bug.  Should it be:\n  for (j = 0; j < pathspec->items[i].attr_match_nr; j++)\n\nwhere items[j] -> items[i]\n\n-- \nBrandon Williams\n"},{"id":"306505","messageId":"20161128180358.GB150448@google.com","threadId":"44454","inReplyTo":"20161110203428.30512-32-sbeller@google.com","subject":"Re: [PATCH 31/35] pathspec: allow querying for attributes","fromName":"Brandon Williams","fromEmail":"bmwill@google.com","sentAt":"2016-11-28T18:03:58Z","receivedAt":"2016-11-28T18:04:05Z","isPatch":true,"sender":{"key":"bwilliams.eng@gmail.com","avatar":null},"body":"On 11/10, Stefan Beller wrote:\n> +static void parse_pathspec_attr_match(struct pathspec_item *item, const char *value)\n> +{\n>\n> [...]\n>\n> +\tif (!item->attr_check)\n> +\t\tgit_attr_check_initv(&item->attr_check, attrs.argv);\n> +\telse\n> +\t\tdie(_(\"Only one 'attr:' specification is allowed.\"));\n> +\n> +\targv_array_clear(&attrs);\n> +\tstring_list_clear(&list, 0);\n> +\treturn;\n> +}\n\nUnnecessary return statement, maybe you want to remove it?\n\n-- \nBrandon Williams\n"},{"id":"306522","messageId":"20161128221123.GC150448@google.com","threadId":"44454","inReplyTo":"20161110203428.30512-32-sbeller@google.com","subject":"Re: [PATCH 31/35] pathspec: allow querying for attributes","fromName":"Brandon Williams","fromEmail":"bmwill@google.com","sentAt":"2016-11-28T22:11:23Z","receivedAt":"2016-11-28T22:11:30Z","isPatch":true,"sender":{"key":"bwilliams.eng@gmail.com","avatar":null},"body":"On 11/10, Stefan Beller wrote:\n> @@ -500,6 +586,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\nYou may also want to add logic like this to the 'copy_pathspec' function\nso that when a pathspec struct is copied, the destination also has\nownership of its own attribute items.\n\n-- \nBrandon Williams\n"},{"id":"306548","messageId":"CAGZ79ka9K=VM7afW5E4=e52JOwpKe=o3TTYqWFVrf6TnptSHcA@mail.gmail.com","threadId":"44454","inReplyTo":"20161128221123.GC150448@google.com","subject":"Re: [PATCH 31/35] pathspec: allow querying for attributes","fromName":"Stefan Beller","fromEmail":"sbeller@google.com","sentAt":"2016-11-29T17:37:43Z","receivedAt":"2016-11-29T17:37:49Z","isPatch":true,"sender":{"key":"stefanbeller@gmail.com","avatar":"https://avatars.githubusercontent.com/u/455868?v=4"},"body":"On Mon, Nov 28, 2016 at 2:11 PM, Brandon Williams <bmwill@google.com> wrote:\n> On 11/10, Stefan Beller wrote:\n>> @@ -500,6 +586,18 @@ void copy_pathspec(struct pathspec *dst, const struct pathspec *src)\n>>\n>>  void clear_pathspec(struct pathspec *pathspec)\n>>  {\n>> +     int i, j;\n>> +     for (i = 0; i < pathspec->nr; i++) {\n>> +             if (!pathspec->items[i].attr_match_nr)\n>> +                     continue;\n>> +             for (j = 0; j < pathspec->items[j].attr_match_nr; j++)\n>> +                     free(pathspec->items[i].attr_match[j].value);\n>> +             free(pathspec->items[i].attr_match);\n>> +             if (pathspec->items[i].attr_check)\n>> +                     git_attr_check_clear(pathspec->items[i].attr_check);\n>> +             free(pathspec->items[i].attr_check);\n>> +     }\n>> +\n>>       free(pathspec->items);\n>>       pathspec->items = NULL;\n>\n> You may also want to add logic like this to the 'copy_pathspec' function\n> so that when a pathspec struct is copied, the destination also has\n> ownership of its own attribute items.\n>\n\nThanks for the review comments, I'll plan on resending this series after the\nsubmodule checkout series (which is nowhere near done, but the foundation\nis set with sb/submodule-intern-gitdir as a preparation.)\n\nAfter discussion with Jonathan Nieder, I think it may be possible to make\nthis new pathspec magic work without the need for thread safety. This is\narchived by constructing all relevant attr_check stacks before the preload_index\nfunctionality (which is threaded) and then use the preconstructed attr_checks\nto get attr_results just like in this series.\n\nThat approach would come with the benefit of not needing mutexes\nat all inside the attr code.\n"}]}