{"thread":{"id":"56354","subject":"[PATCH] Make ident dynamic, not just a hardcoded value of \"$Id\".","startedAt":"2021-08-23T16:42:03Z","lastAt":"2021-09-02T19:04:16Z","messageCount":11,"participants":["Maksym Sobolyev via GitGitGadget","Junio C Hamano","Philip Oakley","Matheus Tavares","Đoàn Trần Công Danh"],"isPatch":true,"patchVersion":1,"patchTotal":null},"messages":[{"id":"433461","messageId":"pull.1074.git.git.1629736918861.gitgitgadget@gmail.com","threadId":"56354","inReplyTo":null,"subject":"[PATCH] Make ident dynamic, not just a hardcoded value of \"$Id\".","fromName":"Maksym Sobolyev via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2021-08-23T16:41:58Z","receivedAt":"2021-08-23T16:42:03Z","isPatch":true,"sender":{"key":"name:Maksym Sobolyev","avatar":null},"body":"From: Maksym Sobolyev <sobomax@sippysoft.com>\n\nThis allows ident to be something like $FreeBSD$ so it provides matching\nfunctionality for repos migrated from CVS / SVN.\n\nThis works by allowing ident to have a parameter, i.e.:\n\n* ident=MyCustomId\n\nIn .gitattributes.\n\nSigned-off-by: Maksym Sobolyev <sobomax@sippysoft.com>\n---\n    Make ident dynamic, not just a hardcoded value of \"$Id\".\n    \n    This allows ident to be something like $FreeBSD$ so it provides matching\n    functionality for repos migrated from CVS / SVN.\n    \n    This works by allowing ident to have a parameter, i.e.:\n    \n    * ident=MyCustomId\n    \n    In .gitattributes.\n\nPublished-As: https://github.com/gitgitgadget/git/releases/tag/pr-git-1074%2Fsobomax%2Fpr-custom_ident-v1\nFetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-git-1074/sobomax/pr-custom_ident-v1\nPull-Request: https://github.com/git/git/pull/1074\n\n builtin/checkout--worker.c              |  17 +++-\n convert.c                               | 114 +++++++++++++++---------\n convert.h                               |   7 +-\n parallel-checkout.c                     |  11 ++-\n parallel-checkout.h                     |   8 +-\n t/t2082-parallel-checkout-attributes.sh |   7 +-\n 6 files changed, 108 insertions(+), 56 deletions(-)\n\ndiff --git a/builtin/checkout--worker.c b/builtin/checkout--worker.c\nindex fb9fd13b73c..61ba39402ad 100644\n--- a/builtin/checkout--worker.c\n+++ b/builtin/checkout--worker.c\n@@ -9,7 +9,7 @@ static void packet_to_pc_item(const char *buffer, int len,\n \t\t\t      struct parallel_checkout_item *pc_item)\n {\n \tconst struct pc_item_fixed_portion *fixed_portion;\n-\tconst char *variant;\n+\tconst char *variant, *ident_action;\n \tchar *encoding;\n \n \tif (len < sizeof(struct pc_item_fixed_portion))\n@@ -19,7 +19,8 @@ static void packet_to_pc_item(const char *buffer, int len,\n \tfixed_portion = (struct pc_item_fixed_portion *)buffer;\n \n \tif (len - sizeof(struct pc_item_fixed_portion) !=\n-\t\tfixed_portion->name_len + fixed_portion->working_tree_encoding_len)\n+\t\tfixed_portion->name_len + fixed_portion->working_tree_encoding_len +\n+\t\tfixed_portion->ident_action_len)\n \t\tBUG(\"checkout worker received corrupted item\");\n \n \tvariant = buffer + sizeof(struct pc_item_fixed_portion);\n@@ -43,11 +44,21 @@ static void packet_to_pc_item(const char *buffer, int len,\n \tpc_item->ce->ce_namelen = fixed_portion->name_len;\n \tpc_item->ce->ce_mode = fixed_portion->ce_mode;\n \tmemcpy(pc_item->ce->name, variant, pc_item->ce->ce_namelen);\n+\tvariant += pc_item->ce->ce_namelen;\n \toidcpy(&pc_item->ce->oid, &fixed_portion->oid);\n \n+\tif (fixed_portion->ident_action_len) {\n+\t\tident_action = xmemdupz(variant,\n+\t\t\t\t\tfixed_portion->ident_action_len);\n+\t\tvariant += fixed_portion->ident_action_len;\n+\t} else {\n+\t\tident_action = NULL;\n+\t}\n+\n \tpc_item->id = fixed_portion->id;\n \tpc_item->ca.crlf_action = fixed_portion->crlf_action;\n-\tpc_item->ca.ident = fixed_portion->ident;\n+\tpc_item->ca.ident_action.id = ident_action;\n+\tpc_item->ca.ident_action.id_len = fixed_portion->ident_action_len;\n \tpc_item->ca.working_tree_encoding = encoding;\n }\n \ndiff --git a/convert.c b/convert.c\nindex 0d6fb3410ae..1e8940bf9d7 100644\n--- a/convert.c\n+++ b/convert.c\n@@ -1056,7 +1056,12 @@ static int read_convert_config(const char *var, const char *value, void *cb)\n \treturn 0;\n }\n \n-static int count_ident(const char *cp, unsigned long size)\n+#define ID_STR \"Id\"\n+\n+#define GIT_MAX_IDENT_LEN 255\n+\n+static int count_ident(const char *cp, unsigned long size,\n+\t\t       const struct ident_action *idact)\n {\n \t/*\n \t * \"$Id: 0000000000000000000000000000000000000000 $\" <=> \"$Id$\"\n@@ -1069,13 +1074,13 @@ static int count_ident(const char *cp, unsigned long size)\n \t\tsize--;\n \t\tif (ch != '$')\n \t\t\tcontinue;\n-\t\tif (size < 3)\n+\t\tif (size < idact->id_len + 1)\n \t\t\tbreak;\n-\t\tif (memcmp(\"Id\", cp, 2))\n+\t\tif (memcmp(idact->id, cp, idact->id_len))\n \t\t\tcontinue;\n-\t\tch = cp[2];\n-\t\tcp += 3;\n-\t\tsize -= 3;\n+\t\tch = cp[idact->id_len];\n+\t\tcp += idact->id_len + 1;\n+\t\tsize -= idact->id_len + 1;\n \t\tif (ch == '$')\n \t\t\tcnt++; /* $Id$ */\n \t\tif (ch != ':')\n@@ -1099,11 +1104,11 @@ static int count_ident(const char *cp, unsigned long size)\n }\n \n static int ident_to_git(const char *src, size_t len,\n-\t\t\tstruct strbuf *buf, int ident)\n+\t\t\tstruct strbuf *buf, const struct ident_action *idact)\n {\n \tchar *dst, *dollar;\n \n-\tif (!ident || (src && !count_ident(src, len)))\n+\tif (!idact->id || (src && !count_ident(src, len, idact)))\n \t\treturn 0;\n \n \tif (!buf)\n@@ -1122,17 +1127,18 @@ static int ident_to_git(const char *src, size_t len,\n \t\tlen -= dollar + 1 - src;\n \t\tsrc  = dollar + 1;\n \n-\t\tif (len > 3 && !memcmp(src, \"Id:\", 3)) {\n-\t\t\tdollar = memchr(src + 3, '$', len - 3);\n+\t\tif (len > idact->id_len + 1 && !memcmp(src, idact->id, idact->id_len) && src[idact->id_len + 1] == ':') {\n+\t\t\tdollar = memchr(src + idact->id_len + 1, '$', len - (idact->id_len + 1));\n \t\t\tif (!dollar)\n \t\t\t\tbreak;\n-\t\t\tif (memchr(src + 3, '\\n', dollar - src - 3)) {\n+\t\t\tif (memchr(src + idact->id_len + 1, '\\n', dollar - src - (idact->id_len + 1))) {\n \t\t\t\t/* Line break before the next dollar. */\n \t\t\t\tcontinue;\n \t\t\t}\n \n-\t\t\tmemcpy(dst, \"Id$\", 3);\n-\t\t\tdst += 3;\n+\t\t\tmemcpy(dst, idact->id, idact->id_len);\n+\t\t\tdst[idact->id_len] = '$';\n+\t\t\tdst += idact->id_len + 1;\n \t\t\tlen -= dollar + 1 - src;\n \t\t\tsrc  = dollar + 1;\n \t\t}\n@@ -1143,16 +1149,16 @@ static int ident_to_git(const char *src, size_t len,\n }\n \n static int ident_to_worktree(const char *src, size_t len,\n-\t\t\t     struct strbuf *buf, int ident)\n+\t\t\t     struct strbuf *buf, const struct ident_action *idact)\n {\n \tstruct object_id oid;\n \tchar *to_free = NULL, *dollar, *spc;\n \tint cnt;\n \n-\tif (!ident)\n+\tif (!idact->id)\n \t\treturn 0;\n \n-\tcnt = count_ident(src, len);\n+\tcnt = count_ident(src, len, idact);\n \tif (!cnt)\n \t\treturn 0;\n \n@@ -1161,7 +1167,7 @@ static int ident_to_worktree(const char *src, size_t len,\n \t\tto_free = strbuf_detach(buf, NULL);\n \thash_object_file(the_hash_algo, src, len, \"blob\", &oid);\n \n-\tstrbuf_grow(buf, len + cnt * (the_hash_algo->hexsz + 3));\n+\tstrbuf_grow(buf, len + cnt * (the_hash_algo->hexsz + idact->id_len + 1));\n \tfor (;;) {\n \t\t/* step 1: run to the next '$' */\n \t\tdollar = memchr(src, '$', len);\n@@ -1172,14 +1178,14 @@ static int ident_to_worktree(const char *src, size_t len,\n \t\tsrc  = dollar + 1;\n \n \t\t/* step 2: does it looks like a bit like Id:xxx$ or Id$ ? */\n-\t\tif (len < 3 || memcmp(\"Id\", src, 2))\n+\t\tif (len < idact->id_len + 1 || memcmp(idact->id, src, idact->id_len))\n \t\t\tcontinue;\n \n \t\t/* step 3: skip over Id$ or Id:xxxxx$ */\n-\t\tif (src[2] == '$') {\n-\t\t\tsrc += 3;\n-\t\t\tlen -= 3;\n-\t\t} else if (src[2] == ':') {\n+\t\tif (src[idact->id_len] == '$') {\n+\t\t\tsrc += idact->id_len + 1;\n+\t\t\tlen -= idact->id_len + 1;\n+\t\t} else if (src[idact->id_len] == ':') {\n \t\t\t/*\n \t\t\t * It's possible that an expanded Id has crept its way into the\n \t\t\t * repository, we cope with that by stripping the expansion out.\n@@ -1187,18 +1193,18 @@ static int ident_to_worktree(const char *src, size_t len,\n \t\t\t * on checkout, which won't go away by stash, but let's keep it\n \t\t\t * for git-style ids.\n \t\t\t */\n-\t\t\tdollar = memchr(src + 3, '$', len - 3);\n+\t\t\tdollar = memchr(src + idact->id_len + 1, '$', len - (idact->id_len + 1));\n \t\t\tif (!dollar) {\n \t\t\t\t/* incomplete keyword, no more '$', so just quit the loop */\n \t\t\t\tbreak;\n \t\t\t}\n \n-\t\t\tif (memchr(src + 3, '\\n', dollar - src - 3)) {\n+\t\t\tif (memchr(src + idact->id_len + 1, '\\n', dollar - src - (idact->id_len + 1))) {\n \t\t\t\t/* Line break before the next dollar. */\n \t\t\t\tcontinue;\n \t\t\t}\n \n-\t\t\tspc = memchr(src + 4, ' ', dollar - src - 4);\n+\t\t\tspc = memchr(src + idact->id_len + 2, ' ', dollar - src - (idact->id_len + 2));\n \t\t\tif (spc && spc < dollar-1) {\n \t\t\t\t/* There are spaces in unexpected places.\n \t\t\t\t * This is probably an id from some other\n@@ -1215,7 +1221,8 @@ static int ident_to_worktree(const char *src, size_t len,\n \t\t}\n \n \t\t/* step 4: substitute */\n-\t\tstrbuf_addstr(buf, \"Id: \");\n+\t\tstrbuf_addstr(buf, idact->id);\n+\t\tstrbuf_addstr(buf, \": \");\n \t\tstrbuf_addstr(buf, oid_to_hex(&oid));\n \t\tstrbuf_addstr(buf, \" $\");\n \t}\n@@ -1286,11 +1293,21 @@ static struct convert_driver *git_path_check_convert(struct attr_check_item *che\n \treturn NULL;\n }\n \n-static int git_path_check_ident(struct attr_check_item *check)\n+static struct ident_action git_path_check_ident(struct attr_check_item *check)\n {\n+\tstruct ident_action idact = {.id = NULL, .id_len = 0};\n \tconst char *value = check->value;\n \n-\treturn !!ATTR_TRUE(value);\n+\tif (!ATTR_UNSET(value) && !ATTR_FALSE(value)) {\n+\t\tif (ATTR_TRUE(value))\n+\t\t\tidact.id = ID_STR;\n+\t\telse\n+\t\t\tidact.id = value;\n+\t\tidact.id_len = strlen(idact.id);\n+\t\tif (idact.id_len > GIT_MAX_IDENT_LEN)\n+\t\t\tdie(_(\"ident value length exceeds GIT_MAX_IDENT_LEN\"));\n+\t}\n+\treturn idact;\n }\n \n static struct attr_check *check;\n@@ -1313,7 +1330,7 @@ void convert_attrs(struct index_state *istate,\n \tca->crlf_action = git_path_check_crlf(ccheck + 4);\n \tif (ca->crlf_action == CRLF_UNDEFINED)\n \t\tca->crlf_action = git_path_check_crlf(ccheck + 0);\n-\tca->ident = git_path_check_ident(ccheck + 1);\n+\tca->ident_action = git_path_check_ident(ccheck + 1);\n \tca->drv = git_path_check_convert(ccheck + 2);\n \tif (ca->crlf_action != CRLF_BINARY) {\n \t\tenum eol eol_attr = git_path_check_eol(ccheck + 3);\n@@ -1433,7 +1450,7 @@ int convert_to_git(struct index_state *istate,\n \t\t\tlen = dst->len;\n \t\t}\n \t}\n-\treturn ret | ident_to_git(src, len, dst, ca.ident);\n+\treturn ret | ident_to_git(src, len, dst, &ca.ident_action);\n }\n \n void convert_to_git_filter_fd(struct index_state *istate,\n@@ -1450,7 +1467,7 @@ void convert_to_git_filter_fd(struct index_state *istate,\n \n \tencode_to_git(path, dst->buf, dst->len, dst, ca.working_tree_encoding, conv_flags);\n \tcrlf_to_git(istate, path, dst->buf, dst->len, dst, ca.crlf_action, conv_flags);\n-\tident_to_git(dst->buf, dst->len, dst, ca.ident);\n+\tident_to_git(dst->buf, dst->len, dst, &ca.ident_action);\n }\n \n static int convert_to_working_tree_ca_internal(const struct conv_attrs *ca,\n@@ -1462,7 +1479,7 @@ static int convert_to_working_tree_ca_internal(const struct conv_attrs *ca,\n {\n \tint ret = 0, ret_filter = 0;\n \n-\tret |= ident_to_worktree(src, len, dst, ca->ident);\n+\tret |= ident_to_worktree(src, len, dst, &(ca->ident_action));\n \tif (ret) {\n \t\tsrc = dst->buf;\n \t\tlen = dst->len;\n@@ -1810,7 +1827,8 @@ struct ident_filter {\n \tstruct stream_filter filter;\n \tstruct strbuf left;\n \tint state;\n-\tchar ident[GIT_MAX_HEXSZ + 5]; /* \": x40 $\" */\n+\tconst struct ident_action *idact;\n+\tchar ident[GIT_MAX_HEXSZ + GIT_MAX_IDENT_LEN + 3]; /* \": x40 $\" */\n };\n \n static int is_foreign_ident(const char *str)\n@@ -1847,13 +1865,16 @@ static int ident_filter_fn(struct stream_filter *filter,\n \t\t\t   char *output, size_t *osize_p)\n {\n \tstruct ident_filter *ident = (struct ident_filter *)filter;\n-\tstatic const char head[] = \"$Id\";\n+\tconst struct ident_action *idact = ident->idact;\n \n \tif (!input) {\n \t\t/* drain upon eof */\n \t\tswitch (ident->state) {\n \t\tdefault:\n-\t\t\tstrbuf_add(&ident->left, head, ident->state);\n+\t\t\tif (ident->state > 0)\n+\t\t\t\tstrbuf_addch(&ident->left, '$');\n+\t\t\tif (ident->state > 1)\n+\t\t\t\tstrbuf_add(&ident->left, idact->id, ident->state - 1);\n \t\t\t/* fallthrough */\n \t\tcase IDENT_SKIPPING:\n \t\t\t/* fallthrough */\n@@ -1885,22 +1906,26 @@ static int ident_filter_fn(struct stream_filter *filter,\n \t\t\tif (ch != '\\n' && ch != '$')\n \t\t\t\tcontinue;\n \t\t\tif (ch == '$' && !is_foreign_ident(ident->left.buf)) {\n-\t\t\t\tstrbuf_setlen(&ident->left, sizeof(head) - 1);\n+\t\t\t\tstrbuf_setlen(&ident->left, idact->id_len + 1);\n \t\t\t\tstrbuf_addstr(&ident->left, ident->ident);\n \t\t\t}\n \t\t\tident->state = IDENT_DRAINING;\n \t\t\tcontinue;\n \t\t}\n \n-\t\tif (ident->state < sizeof(head) &&\n-\t\t    head[ident->state] == ch) {\n+\t\tif ((ident->state == 0 && ch == '$') ||\n+\t\t    (ident->state > 0 && ident->state < idact->id_len + 1 &&\n+\t\t     idact->id[ident->state - 1] == ch)) {\n \t\t\tident->state++;\n \t\t\tcontinue;\n \t\t}\n \n-\t\tif (ident->state)\n-\t\t\tstrbuf_add(&ident->left, head, ident->state);\n-\t\tif (ident->state == sizeof(head) - 1) {\n+\t\tif (ident->state) {\n+\t\t\tstrbuf_addch(&ident->left, '$');\n+\t\t\tif (ident->state > 1)\n+\t\t\t\tstrbuf_add(&ident->left, idact->id, ident->state - 1);\n+\t\t}\n+\t\tif (ident->state == idact->id_len + 1) {\n \t\t\tif (ch != ':' && ch != '$') {\n \t\t\t\tstrbuf_addch(&ident->left, ch);\n \t\t\t\tident->state = 0;\n@@ -1935,7 +1960,7 @@ static struct stream_filter_vtbl ident_vtbl = {\n \tident_free_fn,\n };\n \n-static struct stream_filter *ident_filter(const struct object_id *oid)\n+static struct stream_filter *ident_filter(const struct object_id *oid, const struct ident_action *idact)\n {\n \tstruct ident_filter *ident = xmalloc(sizeof(*ident));\n \n@@ -1944,6 +1969,7 @@ static struct stream_filter *ident_filter(const struct object_id *oid)\n \tstrbuf_init(&ident->left, 0);\n \tident->filter.vtbl = &ident_vtbl;\n \tident->state = 0;\n+\tident->idact = idact;\n \treturn (struct stream_filter *)ident;\n }\n \n@@ -1963,8 +1989,8 @@ struct stream_filter *get_stream_filter_ca(const struct conv_attrs *ca,\n \tif (classify_conv_attrs(ca) != CA_CLASS_STREAMABLE)\n \t\treturn NULL;\n \n-\tif (ca->ident)\n-\t\tfilter = ident_filter(oid);\n+\tif (ca->ident_action.id)\n+\t\tfilter = ident_filter(oid, &(ca->ident_action));\n \n \tif (output_eol(ca->crlf_action) == EOL_CRLF)\n \t\tfilter = cascade_filter(filter, lf_to_crlf_filter());\ndiff --git a/convert.h b/convert.h\nindex 5ee1c322058..a02632e8104 100644\n--- a/convert.h\n+++ b/convert.h\n@@ -76,11 +76,16 @@ enum convert_crlf_action {\n \n struct convert_driver;\n \n+struct ident_action {\n+\tconst char *id;\n+\tint id_len;\n+};\n+\n struct conv_attrs {\n \tstruct convert_driver *drv;\n \tenum convert_crlf_action attr_action; /* What attr says */\n \tenum convert_crlf_action crlf_action; /* When no attr is set, use core.autocrlf */\n-\tint ident;\n+\tstruct ident_action ident_action; /* What ident says */\n \tconst char *working_tree_encoding; /* Supported encoding or default encoding if NULL */\n };\n \ndiff --git a/parallel-checkout.c b/parallel-checkout.c\nindex ddc0ff3c064..f9a3f2ff25b 100644\n--- a/parallel-checkout.c\n+++ b/parallel-checkout.c\n@@ -403,13 +403,15 @@ static void send_one_item(int fd, struct parallel_checkout_item *pc_item)\n \tsize_t name_len = pc_item->ce->ce_namelen;\n \tsize_t working_tree_encoding_len = working_tree_encoding ?\n \t\t\t\t\t   strlen(working_tree_encoding) : 0;\n+\tconst char *ident_action_id = pc_item->ca.ident_action.id;\n+\tsize_t ident_action_len = pc_item->ca.ident_action.id_len;\n \n \t/*\n \t * Any changes in the calculation of the message size must also be made\n \t * in is_eligible_for_parallel_checkout().\n \t */\n \tlen_data = sizeof(struct pc_item_fixed_portion) + name_len +\n-\t\t   working_tree_encoding_len;\n+\t\t   working_tree_encoding_len + ident_action_len;\n \n \tdata = xmalloc(len_data);\n \n@@ -417,7 +419,7 @@ static void send_one_item(int fd, struct parallel_checkout_item *pc_item)\n \tfixed_portion->id = pc_item->id;\n \tfixed_portion->ce_mode = pc_item->ce->ce_mode;\n \tfixed_portion->crlf_action = pc_item->ca.crlf_action;\n-\tfixed_portion->ident = pc_item->ca.ident;\n+\tfixed_portion->ident_action_len = ident_action_len;\n \tfixed_portion->name_len = name_len;\n \tfixed_portion->working_tree_encoding_len = working_tree_encoding_len;\n \t/*\n@@ -434,6 +436,11 @@ static void send_one_item(int fd, struct parallel_checkout_item *pc_item)\n \t\tvariant += working_tree_encoding_len;\n \t}\n \tmemcpy(variant, pc_item->ce->name, name_len);\n+\tvariant += name_len;\n+\tif (ident_action_len) {\n+\t\tmemcpy(variant, ident_action_id, ident_action_len);\n+\t\tvariant += ident_action_len;\n+\t}\n \n \tpacket_write(fd, data, len_data);\n \ndiff --git a/parallel-checkout.h b/parallel-checkout.h\nindex 80f539bcb77..c3c282f516b 100644\n--- a/parallel-checkout.h\n+++ b/parallel-checkout.h\n@@ -76,9 +76,9 @@ struct parallel_checkout_item {\n \n /*\n  * The fixed-size portion of `struct parallel_checkout_item` that is sent to the\n- * workers. Following this will be 2 strings: ca.working_tree_encoding and\n- * ce.name; These are NOT null terminated, since we have the size in the fixed\n- * portion.\n+ * workers. Following this will be 3 strings: ca.working_tree_encoding, ca.name\n+ * and ca.ident_action.id; These are NOT null terminated, since we have the size\n+ * in the fixed portion.\n  *\n  * Note that not all fields of conv_attrs and cache_entry are passed, only the\n  * ones that will be required by the workers to smudge and write the entry.\n@@ -88,7 +88,7 @@ struct pc_item_fixed_portion {\n \tstruct object_id oid;\n \tunsigned int ce_mode;\n \tenum convert_crlf_action crlf_action;\n-\tint ident;\n+\tsize_t ident_action_len;\n \tsize_t working_tree_encoding_len;\n \tsize_t name_len;\n };\ndiff --git a/t/t2082-parallel-checkout-attributes.sh b/t/t2082-parallel-checkout-attributes.sh\nindex 25254579618..822957a8dc8 100755\n--- a/t/t2082-parallel-checkout-attributes.sh\n+++ b/t/t2082-parallel-checkout-attributes.sh\n@@ -20,16 +20,19 @@ test_expect_success 'parallel-checkout with ident' '\n \t(\n \t\tcd ident &&\n \t\techo \"A ident\" >.gitattributes &&\n+\t\techo \"C ident=MyCusomVeryLongAndWordyId\" >>.gitattributes &&\n \t\techo \"\\$Id\\$\" >A &&\n \t\techo \"\\$Id\\$\" >B &&\n+\t\techo \"\\$MyCusomVeryLongAndWordyId\\$\" >C &&\n \t\tgit add -A &&\n \t\tgit commit -m id &&\n \n-\t\trm A B &&\n+\t\trm A B C &&\n \t\ttest_checkout_workers 2 git reset --hard &&\n \t\thexsz=$(test_oid hexsz) &&\n \t\tgrep -E \"\\\\\\$Id: [0-9a-f]{$hexsz} \\\\\\$\" A &&\n-\t\tgrep \"\\\\\\$Id\\\\\\$\" B\n+\t\tgrep \"\\\\\\$Id\\\\\\$\" B &&\n+\t\tgrep -E \"\\\\\\$MyCusomVeryLongAndWordyId: [0-9a-f]{$hexsz} \\\\\\$\" C\n \t)\n '\n \n\nbase-commit: 225bc32a989d7a22fa6addafd4ce7dcd04675dbf\n-- \ngitgitgadget\n"},{"id":"433475","messageId":"xmqqa6l8qbgc.fsf@gitster.g","threadId":"56354","inReplyTo":"pull.1074.git.git.1629736918861.gitgitgadget@gmail.com","subject":"Re: [PATCH] Make ident dynamic, not just a hardcoded value of \"$Id\".","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2021-08-23T18:10:11Z","receivedAt":"2021-08-23T18:10:20Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"\"Maksym Sobolyev via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n\n> From: Maksym Sobolyev <sobomax@sippysoft.com>\n>\n> This allows ident to be something like $FreeBSD$ so it provides matching\n> functionality for repos migrated from CVS / SVN.\n>\n> This works by allowing ident to have a parameter, i.e.:\n>\n> * ident=MyCustomId\n>\n> In .gitattributes.\n\nI would have expected that, just like cvs uses its configuration\nmechanism for LocalKeyword, this would belong to the .git/config\nfile.\n\nThe attribute mechanism is inherently for stuff that you would want\nto specify per-path, so the \"do we do keyword expansion for this\nfile (yes/no)?\" is a good match to be an attribute, and that\nquestion is what the original 'ident' attribute asks.\n\nI do not think \"what keyword do we want to expand as if it were\n$Id$?\" is a question we want to be giving different answers per\npath, and it does not smell like a good match to be an attribute.\n\nBy allowing a potentially separate values for\n\n    *.txt ident=FreeBSD\n    *.cc  ident=OpenBSD\n\nthis gives long rope to create a mess to the users.\n\nI also think that the ident expansion is overrated, and have a\nfeeling that we should discourage its use, not encourage it, but\nthat is a separate story.\n\nThanks.\n"},{"id":"433479","messageId":"da4e66ec-0613-a8ba-0b69-cd247fcd457b@iee.email","threadId":"56354","inReplyTo":"pull.1074.git.git.1629736918861.gitgitgadget@gmail.com","subject":"Re: [PATCH] Make ident dynamic, not just a hardcoded value of \"$Id\".","fromName":"Philip Oakley","fromEmail":"philipoakley@iee.email","sentAt":"2021-08-23T18:41:22Z","receivedAt":"2021-08-23T18:41:39Z","isPatch":true,"sender":{"key":"philipoakley@iee.email","avatar":"https://avatars.githubusercontent.com/u/914343?v=4"},"body":"On 23/08/2021 17:41, Maksym Sobolyev via GitGitGadget wrote:\n> From: Maksym Sobolyev <sobomax@sippysoft.com>\n>\n> This allows ident to be something like $FreeBSD$ so it provides matching\n> functionality for repos migrated from CVS / SVN.\n>\n> This works by allowing ident to have a parameter, i.e.:\n>\n> * ident=MyCustomId\n>\n> In .gitattributes.\n>\n> Signed-off-by: Maksym Sobolyev <sobomax@sippysoft.com>\n> ---\n>     Make ident dynamic, not just a hardcoded value of \"$Id\".\n>     \n>     This allows ident to be something like $FreeBSD$ so it provides matching\n>     functionality for repos migrated from CVS / SVN.\n>     \n>     This works by allowing ident to have a parameter, i.e.:\n>     \n>     * ident=MyCustomId\n>     \n>     In .gitattributes.\n>\n> Published-As: https://github.com/gitgitgadget/git/releases/tag/pr-git-1074%2Fsobomax%2Fpr-custom_ident-v1\n> Fetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-git-1074/sobomax/pr-custom_ident-v1\n> Pull-Request: https://github.com/git/git/pull/1074\n>\n>  builtin/checkout--worker.c              |  17 +++-\n>  convert.c                               | 114 +++++++++++++++---------\n>  convert.h                               |   7 +-\n>  parallel-checkout.c                     |  11 ++-\n>  parallel-checkout.h                     |   8 +-\n>  t/t2082-parallel-checkout-attributes.sh |   7 +-\n>  6 files changed, 108 insertions(+), 56 deletions(-)\n>\n> diff --git a/builtin/checkout--worker.c b/builtin/checkout--worker.c\n\nShould this also include some documentation updates?\n-- \nPhilip\n"},{"id":"433804","messageId":"pull.1074.v2.git.git.1629952119446.gitgitgadget@gmail.com","threadId":"56354","inReplyTo":"pull.1074.git.git.1629736918861.gitgitgadget@gmail.com","subject":"[PATCH v2] Make ident dynamic, not just a hardcoded value of \"$Id\".","fromName":"Maksym Sobolyev via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2021-08-26T04:28:39Z","receivedAt":"2021-08-26T04:28:43Z","isPatch":true,"sender":{"key":"name:Maksym Sobolyev","avatar":null},"body":"From: Maksym Sobolyev <sobomax@sippysoft.com>\n\nThis allows ident to be something like $FreeBSD$ so it provides matching\nfunctionality for repos migrated from CVS / SVN.\n\nThis works by allowing ident to have a parameter, i.e.:\n\n* ident=MyCustomId\n\nIn .gitattributes.\n\nSigned-off-by: Maksym Sobolyev <sobomax@sippysoft.com>\n---\n    Make ident dynamic, not just a hardcoded value of \"$Id\".\n    \n    This allows ident to be something like $FreeBSD$ so it provides matching\n    functionality for repos migrated from CVS / SVN.\n    \n    This works by allowing ident to have a parameter, i.e.:\n    \n    * ident=MyCustomId\n    \n    In .gitattributes.\n\nPublished-As: https://github.com/gitgitgadget/git/releases/tag/pr-git-1074%2Fsobomax%2Fpr-custom_ident-v2\nFetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-git-1074/sobomax/pr-custom_ident-v2\nPull-Request: https://github.com/git/git/pull/1074\n\nRange-diff vs v1:\n\n 1:  52e0adde935 ! 1:  16713788a26 Make ident dynamic, not just a hardcoded value of \"$Id\".\n     @@ Commit message\n      \n          Signed-off-by: Maksym Sobolyev <sobomax@sippysoft.com>\n      \n     + ## Documentation/gitattributes.txt ##\n     +@@ Documentation/gitattributes.txt: sign `$` upon checkout.  Any byte sequence that begins with\n     + `$Id:` and ends with `$` in the worktree file is replaced\n     + with `$Id$` upon check-in.\n     + \n     ++The `ident` attribute can also provide an optional value,\n     ++which if supplied is going to be used for expansion instead of\n     ++the string `Id`.\n     ++\n     ++------------------------\n     ++*.[ch]\t\tident=FreeBSD\n     ++------------------------\n     ++\n     + \n     + `filter`\n     + ^^^^^^^^\n     +\n       ## builtin/checkout--worker.c ##\n      @@ builtin/checkout--worker.c: static void packet_to_pc_item(const char *buffer, int len,\n       \t\t\t      struct parallel_checkout_item *pc_item)\n\n\n Documentation/gitattributes.txt         |   8 ++\n builtin/checkout--worker.c              |  17 +++-\n convert.c                               | 114 +++++++++++++++---------\n convert.h                               |   7 +-\n parallel-checkout.c                     |  11 ++-\n parallel-checkout.h                     |   8 +-\n t/t2082-parallel-checkout-attributes.sh |   7 +-\n 7 files changed, 116 insertions(+), 56 deletions(-)\n\ndiff --git a/Documentation/gitattributes.txt b/Documentation/gitattributes.txt\nindex 83fd4e19a41..9e486f3e8d3 100644\n--- a/Documentation/gitattributes.txt\n+++ b/Documentation/gitattributes.txt\n@@ -382,6 +382,14 @@ sign `$` upon checkout.  Any byte sequence that begins with\n `$Id:` and ends with `$` in the worktree file is replaced\n with `$Id$` upon check-in.\n \n+The `ident` attribute can also provide an optional value,\n+which if supplied is going to be used for expansion instead of\n+the string `Id`.\n+\n+------------------------\n+*.[ch]\t\tident=FreeBSD\n+------------------------\n+\n \n `filter`\n ^^^^^^^^\ndiff --git a/builtin/checkout--worker.c b/builtin/checkout--worker.c\nindex fb9fd13b73c..61ba39402ad 100644\n--- a/builtin/checkout--worker.c\n+++ b/builtin/checkout--worker.c\n@@ -9,7 +9,7 @@ static void packet_to_pc_item(const char *buffer, int len,\n \t\t\t      struct parallel_checkout_item *pc_item)\n {\n \tconst struct pc_item_fixed_portion *fixed_portion;\n-\tconst char *variant;\n+\tconst char *variant, *ident_action;\n \tchar *encoding;\n \n \tif (len < sizeof(struct pc_item_fixed_portion))\n@@ -19,7 +19,8 @@ static void packet_to_pc_item(const char *buffer, int len,\n \tfixed_portion = (struct pc_item_fixed_portion *)buffer;\n \n \tif (len - sizeof(struct pc_item_fixed_portion) !=\n-\t\tfixed_portion->name_len + fixed_portion->working_tree_encoding_len)\n+\t\tfixed_portion->name_len + fixed_portion->working_tree_encoding_len +\n+\t\tfixed_portion->ident_action_len)\n \t\tBUG(\"checkout worker received corrupted item\");\n \n \tvariant = buffer + sizeof(struct pc_item_fixed_portion);\n@@ -43,11 +44,21 @@ static void packet_to_pc_item(const char *buffer, int len,\n \tpc_item->ce->ce_namelen = fixed_portion->name_len;\n \tpc_item->ce->ce_mode = fixed_portion->ce_mode;\n \tmemcpy(pc_item->ce->name, variant, pc_item->ce->ce_namelen);\n+\tvariant += pc_item->ce->ce_namelen;\n \toidcpy(&pc_item->ce->oid, &fixed_portion->oid);\n \n+\tif (fixed_portion->ident_action_len) {\n+\t\tident_action = xmemdupz(variant,\n+\t\t\t\t\tfixed_portion->ident_action_len);\n+\t\tvariant += fixed_portion->ident_action_len;\n+\t} else {\n+\t\tident_action = NULL;\n+\t}\n+\n \tpc_item->id = fixed_portion->id;\n \tpc_item->ca.crlf_action = fixed_portion->crlf_action;\n-\tpc_item->ca.ident = fixed_portion->ident;\n+\tpc_item->ca.ident_action.id = ident_action;\n+\tpc_item->ca.ident_action.id_len = fixed_portion->ident_action_len;\n \tpc_item->ca.working_tree_encoding = encoding;\n }\n \ndiff --git a/convert.c b/convert.c\nindex 0d6fb3410ae..1e8940bf9d7 100644\n--- a/convert.c\n+++ b/convert.c\n@@ -1056,7 +1056,12 @@ static int read_convert_config(const char *var, const char *value, void *cb)\n \treturn 0;\n }\n \n-static int count_ident(const char *cp, unsigned long size)\n+#define ID_STR \"Id\"\n+\n+#define GIT_MAX_IDENT_LEN 255\n+\n+static int count_ident(const char *cp, unsigned long size,\n+\t\t       const struct ident_action *idact)\n {\n \t/*\n \t * \"$Id: 0000000000000000000000000000000000000000 $\" <=> \"$Id$\"\n@@ -1069,13 +1074,13 @@ static int count_ident(const char *cp, unsigned long size)\n \t\tsize--;\n \t\tif (ch != '$')\n \t\t\tcontinue;\n-\t\tif (size < 3)\n+\t\tif (size < idact->id_len + 1)\n \t\t\tbreak;\n-\t\tif (memcmp(\"Id\", cp, 2))\n+\t\tif (memcmp(idact->id, cp, idact->id_len))\n \t\t\tcontinue;\n-\t\tch = cp[2];\n-\t\tcp += 3;\n-\t\tsize -= 3;\n+\t\tch = cp[idact->id_len];\n+\t\tcp += idact->id_len + 1;\n+\t\tsize -= idact->id_len + 1;\n \t\tif (ch == '$')\n \t\t\tcnt++; /* $Id$ */\n \t\tif (ch != ':')\n@@ -1099,11 +1104,11 @@ static int count_ident(const char *cp, unsigned long size)\n }\n \n static int ident_to_git(const char *src, size_t len,\n-\t\t\tstruct strbuf *buf, int ident)\n+\t\t\tstruct strbuf *buf, const struct ident_action *idact)\n {\n \tchar *dst, *dollar;\n \n-\tif (!ident || (src && !count_ident(src, len)))\n+\tif (!idact->id || (src && !count_ident(src, len, idact)))\n \t\treturn 0;\n \n \tif (!buf)\n@@ -1122,17 +1127,18 @@ static int ident_to_git(const char *src, size_t len,\n \t\tlen -= dollar + 1 - src;\n \t\tsrc  = dollar + 1;\n \n-\t\tif (len > 3 && !memcmp(src, \"Id:\", 3)) {\n-\t\t\tdollar = memchr(src + 3, '$', len - 3);\n+\t\tif (len > idact->id_len + 1 && !memcmp(src, idact->id, idact->id_len) && src[idact->id_len + 1] == ':') {\n+\t\t\tdollar = memchr(src + idact->id_len + 1, '$', len - (idact->id_len + 1));\n \t\t\tif (!dollar)\n \t\t\t\tbreak;\n-\t\t\tif (memchr(src + 3, '\\n', dollar - src - 3)) {\n+\t\t\tif (memchr(src + idact->id_len + 1, '\\n', dollar - src - (idact->id_len + 1))) {\n \t\t\t\t/* Line break before the next dollar. */\n \t\t\t\tcontinue;\n \t\t\t}\n \n-\t\t\tmemcpy(dst, \"Id$\", 3);\n-\t\t\tdst += 3;\n+\t\t\tmemcpy(dst, idact->id, idact->id_len);\n+\t\t\tdst[idact->id_len] = '$';\n+\t\t\tdst += idact->id_len + 1;\n \t\t\tlen -= dollar + 1 - src;\n \t\t\tsrc  = dollar + 1;\n \t\t}\n@@ -1143,16 +1149,16 @@ static int ident_to_git(const char *src, size_t len,\n }\n \n static int ident_to_worktree(const char *src, size_t len,\n-\t\t\t     struct strbuf *buf, int ident)\n+\t\t\t     struct strbuf *buf, const struct ident_action *idact)\n {\n \tstruct object_id oid;\n \tchar *to_free = NULL, *dollar, *spc;\n \tint cnt;\n \n-\tif (!ident)\n+\tif (!idact->id)\n \t\treturn 0;\n \n-\tcnt = count_ident(src, len);\n+\tcnt = count_ident(src, len, idact);\n \tif (!cnt)\n \t\treturn 0;\n \n@@ -1161,7 +1167,7 @@ static int ident_to_worktree(const char *src, size_t len,\n \t\tto_free = strbuf_detach(buf, NULL);\n \thash_object_file(the_hash_algo, src, len, \"blob\", &oid);\n \n-\tstrbuf_grow(buf, len + cnt * (the_hash_algo->hexsz + 3));\n+\tstrbuf_grow(buf, len + cnt * (the_hash_algo->hexsz + idact->id_len + 1));\n \tfor (;;) {\n \t\t/* step 1: run to the next '$' */\n \t\tdollar = memchr(src, '$', len);\n@@ -1172,14 +1178,14 @@ static int ident_to_worktree(const char *src, size_t len,\n \t\tsrc  = dollar + 1;\n \n \t\t/* step 2: does it looks like a bit like Id:xxx$ or Id$ ? */\n-\t\tif (len < 3 || memcmp(\"Id\", src, 2))\n+\t\tif (len < idact->id_len + 1 || memcmp(idact->id, src, idact->id_len))\n \t\t\tcontinue;\n \n \t\t/* step 3: skip over Id$ or Id:xxxxx$ */\n-\t\tif (src[2] == '$') {\n-\t\t\tsrc += 3;\n-\t\t\tlen -= 3;\n-\t\t} else if (src[2] == ':') {\n+\t\tif (src[idact->id_len] == '$') {\n+\t\t\tsrc += idact->id_len + 1;\n+\t\t\tlen -= idact->id_len + 1;\n+\t\t} else if (src[idact->id_len] == ':') {\n \t\t\t/*\n \t\t\t * It's possible that an expanded Id has crept its way into the\n \t\t\t * repository, we cope with that by stripping the expansion out.\n@@ -1187,18 +1193,18 @@ static int ident_to_worktree(const char *src, size_t len,\n \t\t\t * on checkout, which won't go away by stash, but let's keep it\n \t\t\t * for git-style ids.\n \t\t\t */\n-\t\t\tdollar = memchr(src + 3, '$', len - 3);\n+\t\t\tdollar = memchr(src + idact->id_len + 1, '$', len - (idact->id_len + 1));\n \t\t\tif (!dollar) {\n \t\t\t\t/* incomplete keyword, no more '$', so just quit the loop */\n \t\t\t\tbreak;\n \t\t\t}\n \n-\t\t\tif (memchr(src + 3, '\\n', dollar - src - 3)) {\n+\t\t\tif (memchr(src + idact->id_len + 1, '\\n', dollar - src - (idact->id_len + 1))) {\n \t\t\t\t/* Line break before the next dollar. */\n \t\t\t\tcontinue;\n \t\t\t}\n \n-\t\t\tspc = memchr(src + 4, ' ', dollar - src - 4);\n+\t\t\tspc = memchr(src + idact->id_len + 2, ' ', dollar - src - (idact->id_len + 2));\n \t\t\tif (spc && spc < dollar-1) {\n \t\t\t\t/* There are spaces in unexpected places.\n \t\t\t\t * This is probably an id from some other\n@@ -1215,7 +1221,8 @@ static int ident_to_worktree(const char *src, size_t len,\n \t\t}\n \n \t\t/* step 4: substitute */\n-\t\tstrbuf_addstr(buf, \"Id: \");\n+\t\tstrbuf_addstr(buf, idact->id);\n+\t\tstrbuf_addstr(buf, \": \");\n \t\tstrbuf_addstr(buf, oid_to_hex(&oid));\n \t\tstrbuf_addstr(buf, \" $\");\n \t}\n@@ -1286,11 +1293,21 @@ static struct convert_driver *git_path_check_convert(struct attr_check_item *che\n \treturn NULL;\n }\n \n-static int git_path_check_ident(struct attr_check_item *check)\n+static struct ident_action git_path_check_ident(struct attr_check_item *check)\n {\n+\tstruct ident_action idact = {.id = NULL, .id_len = 0};\n \tconst char *value = check->value;\n \n-\treturn !!ATTR_TRUE(value);\n+\tif (!ATTR_UNSET(value) && !ATTR_FALSE(value)) {\n+\t\tif (ATTR_TRUE(value))\n+\t\t\tidact.id = ID_STR;\n+\t\telse\n+\t\t\tidact.id = value;\n+\t\tidact.id_len = strlen(idact.id);\n+\t\tif (idact.id_len > GIT_MAX_IDENT_LEN)\n+\t\t\tdie(_(\"ident value length exceeds GIT_MAX_IDENT_LEN\"));\n+\t}\n+\treturn idact;\n }\n \n static struct attr_check *check;\n@@ -1313,7 +1330,7 @@ void convert_attrs(struct index_state *istate,\n \tca->crlf_action = git_path_check_crlf(ccheck + 4);\n \tif (ca->crlf_action == CRLF_UNDEFINED)\n \t\tca->crlf_action = git_path_check_crlf(ccheck + 0);\n-\tca->ident = git_path_check_ident(ccheck + 1);\n+\tca->ident_action = git_path_check_ident(ccheck + 1);\n \tca->drv = git_path_check_convert(ccheck + 2);\n \tif (ca->crlf_action != CRLF_BINARY) {\n \t\tenum eol eol_attr = git_path_check_eol(ccheck + 3);\n@@ -1433,7 +1450,7 @@ int convert_to_git(struct index_state *istate,\n \t\t\tlen = dst->len;\n \t\t}\n \t}\n-\treturn ret | ident_to_git(src, len, dst, ca.ident);\n+\treturn ret | ident_to_git(src, len, dst, &ca.ident_action);\n }\n \n void convert_to_git_filter_fd(struct index_state *istate,\n@@ -1450,7 +1467,7 @@ void convert_to_git_filter_fd(struct index_state *istate,\n \n \tencode_to_git(path, dst->buf, dst->len, dst, ca.working_tree_encoding, conv_flags);\n \tcrlf_to_git(istate, path, dst->buf, dst->len, dst, ca.crlf_action, conv_flags);\n-\tident_to_git(dst->buf, dst->len, dst, ca.ident);\n+\tident_to_git(dst->buf, dst->len, dst, &ca.ident_action);\n }\n \n static int convert_to_working_tree_ca_internal(const struct conv_attrs *ca,\n@@ -1462,7 +1479,7 @@ static int convert_to_working_tree_ca_internal(const struct conv_attrs *ca,\n {\n \tint ret = 0, ret_filter = 0;\n \n-\tret |= ident_to_worktree(src, len, dst, ca->ident);\n+\tret |= ident_to_worktree(src, len, dst, &(ca->ident_action));\n \tif (ret) {\n \t\tsrc = dst->buf;\n \t\tlen = dst->len;\n@@ -1810,7 +1827,8 @@ struct ident_filter {\n \tstruct stream_filter filter;\n \tstruct strbuf left;\n \tint state;\n-\tchar ident[GIT_MAX_HEXSZ + 5]; /* \": x40 $\" */\n+\tconst struct ident_action *idact;\n+\tchar ident[GIT_MAX_HEXSZ + GIT_MAX_IDENT_LEN + 3]; /* \": x40 $\" */\n };\n \n static int is_foreign_ident(const char *str)\n@@ -1847,13 +1865,16 @@ static int ident_filter_fn(struct stream_filter *filter,\n \t\t\t   char *output, size_t *osize_p)\n {\n \tstruct ident_filter *ident = (struct ident_filter *)filter;\n-\tstatic const char head[] = \"$Id\";\n+\tconst struct ident_action *idact = ident->idact;\n \n \tif (!input) {\n \t\t/* drain upon eof */\n \t\tswitch (ident->state) {\n \t\tdefault:\n-\t\t\tstrbuf_add(&ident->left, head, ident->state);\n+\t\t\tif (ident->state > 0)\n+\t\t\t\tstrbuf_addch(&ident->left, '$');\n+\t\t\tif (ident->state > 1)\n+\t\t\t\tstrbuf_add(&ident->left, idact->id, ident->state - 1);\n \t\t\t/* fallthrough */\n \t\tcase IDENT_SKIPPING:\n \t\t\t/* fallthrough */\n@@ -1885,22 +1906,26 @@ static int ident_filter_fn(struct stream_filter *filter,\n \t\t\tif (ch != '\\n' && ch != '$')\n \t\t\t\tcontinue;\n \t\t\tif (ch == '$' && !is_foreign_ident(ident->left.buf)) {\n-\t\t\t\tstrbuf_setlen(&ident->left, sizeof(head) - 1);\n+\t\t\t\tstrbuf_setlen(&ident->left, idact->id_len + 1);\n \t\t\t\tstrbuf_addstr(&ident->left, ident->ident);\n \t\t\t}\n \t\t\tident->state = IDENT_DRAINING;\n \t\t\tcontinue;\n \t\t}\n \n-\t\tif (ident->state < sizeof(head) &&\n-\t\t    head[ident->state] == ch) {\n+\t\tif ((ident->state == 0 && ch == '$') ||\n+\t\t    (ident->state > 0 && ident->state < idact->id_len + 1 &&\n+\t\t     idact->id[ident->state - 1] == ch)) {\n \t\t\tident->state++;\n \t\t\tcontinue;\n \t\t}\n \n-\t\tif (ident->state)\n-\t\t\tstrbuf_add(&ident->left, head, ident->state);\n-\t\tif (ident->state == sizeof(head) - 1) {\n+\t\tif (ident->state) {\n+\t\t\tstrbuf_addch(&ident->left, '$');\n+\t\t\tif (ident->state > 1)\n+\t\t\t\tstrbuf_add(&ident->left, idact->id, ident->state - 1);\n+\t\t}\n+\t\tif (ident->state == idact->id_len + 1) {\n \t\t\tif (ch != ':' && ch != '$') {\n \t\t\t\tstrbuf_addch(&ident->left, ch);\n \t\t\t\tident->state = 0;\n@@ -1935,7 +1960,7 @@ static struct stream_filter_vtbl ident_vtbl = {\n \tident_free_fn,\n };\n \n-static struct stream_filter *ident_filter(const struct object_id *oid)\n+static struct stream_filter *ident_filter(const struct object_id *oid, const struct ident_action *idact)\n {\n \tstruct ident_filter *ident = xmalloc(sizeof(*ident));\n \n@@ -1944,6 +1969,7 @@ static struct stream_filter *ident_filter(const struct object_id *oid)\n \tstrbuf_init(&ident->left, 0);\n \tident->filter.vtbl = &ident_vtbl;\n \tident->state = 0;\n+\tident->idact = idact;\n \treturn (struct stream_filter *)ident;\n }\n \n@@ -1963,8 +1989,8 @@ struct stream_filter *get_stream_filter_ca(const struct conv_attrs *ca,\n \tif (classify_conv_attrs(ca) != CA_CLASS_STREAMABLE)\n \t\treturn NULL;\n \n-\tif (ca->ident)\n-\t\tfilter = ident_filter(oid);\n+\tif (ca->ident_action.id)\n+\t\tfilter = ident_filter(oid, &(ca->ident_action));\n \n \tif (output_eol(ca->crlf_action) == EOL_CRLF)\n \t\tfilter = cascade_filter(filter, lf_to_crlf_filter());\ndiff --git a/convert.h b/convert.h\nindex 5ee1c322058..a02632e8104 100644\n--- a/convert.h\n+++ b/convert.h\n@@ -76,11 +76,16 @@ enum convert_crlf_action {\n \n struct convert_driver;\n \n+struct ident_action {\n+\tconst char *id;\n+\tint id_len;\n+};\n+\n struct conv_attrs {\n \tstruct convert_driver *drv;\n \tenum convert_crlf_action attr_action; /* What attr says */\n \tenum convert_crlf_action crlf_action; /* When no attr is set, use core.autocrlf */\n-\tint ident;\n+\tstruct ident_action ident_action; /* What ident says */\n \tconst char *working_tree_encoding; /* Supported encoding or default encoding if NULL */\n };\n \ndiff --git a/parallel-checkout.c b/parallel-checkout.c\nindex ddc0ff3c064..f9a3f2ff25b 100644\n--- a/parallel-checkout.c\n+++ b/parallel-checkout.c\n@@ -403,13 +403,15 @@ static void send_one_item(int fd, struct parallel_checkout_item *pc_item)\n \tsize_t name_len = pc_item->ce->ce_namelen;\n \tsize_t working_tree_encoding_len = working_tree_encoding ?\n \t\t\t\t\t   strlen(working_tree_encoding) : 0;\n+\tconst char *ident_action_id = pc_item->ca.ident_action.id;\n+\tsize_t ident_action_len = pc_item->ca.ident_action.id_len;\n \n \t/*\n \t * Any changes in the calculation of the message size must also be made\n \t * in is_eligible_for_parallel_checkout().\n \t */\n \tlen_data = sizeof(struct pc_item_fixed_portion) + name_len +\n-\t\t   working_tree_encoding_len;\n+\t\t   working_tree_encoding_len + ident_action_len;\n \n \tdata = xmalloc(len_data);\n \n@@ -417,7 +419,7 @@ static void send_one_item(int fd, struct parallel_checkout_item *pc_item)\n \tfixed_portion->id = pc_item->id;\n \tfixed_portion->ce_mode = pc_item->ce->ce_mode;\n \tfixed_portion->crlf_action = pc_item->ca.crlf_action;\n-\tfixed_portion->ident = pc_item->ca.ident;\n+\tfixed_portion->ident_action_len = ident_action_len;\n \tfixed_portion->name_len = name_len;\n \tfixed_portion->working_tree_encoding_len = working_tree_encoding_len;\n \t/*\n@@ -434,6 +436,11 @@ static void send_one_item(int fd, struct parallel_checkout_item *pc_item)\n \t\tvariant += working_tree_encoding_len;\n \t}\n \tmemcpy(variant, pc_item->ce->name, name_len);\n+\tvariant += name_len;\n+\tif (ident_action_len) {\n+\t\tmemcpy(variant, ident_action_id, ident_action_len);\n+\t\tvariant += ident_action_len;\n+\t}\n \n \tpacket_write(fd, data, len_data);\n \ndiff --git a/parallel-checkout.h b/parallel-checkout.h\nindex 80f539bcb77..c3c282f516b 100644\n--- a/parallel-checkout.h\n+++ b/parallel-checkout.h\n@@ -76,9 +76,9 @@ struct parallel_checkout_item {\n \n /*\n  * The fixed-size portion of `struct parallel_checkout_item` that is sent to the\n- * workers. Following this will be 2 strings: ca.working_tree_encoding and\n- * ce.name; These are NOT null terminated, since we have the size in the fixed\n- * portion.\n+ * workers. Following this will be 3 strings: ca.working_tree_encoding, ca.name\n+ * and ca.ident_action.id; These are NOT null terminated, since we have the size\n+ * in the fixed portion.\n  *\n  * Note that not all fields of conv_attrs and cache_entry are passed, only the\n  * ones that will be required by the workers to smudge and write the entry.\n@@ -88,7 +88,7 @@ struct pc_item_fixed_portion {\n \tstruct object_id oid;\n \tunsigned int ce_mode;\n \tenum convert_crlf_action crlf_action;\n-\tint ident;\n+\tsize_t ident_action_len;\n \tsize_t working_tree_encoding_len;\n \tsize_t name_len;\n };\ndiff --git a/t/t2082-parallel-checkout-attributes.sh b/t/t2082-parallel-checkout-attributes.sh\nindex 25254579618..822957a8dc8 100755\n--- a/t/t2082-parallel-checkout-attributes.sh\n+++ b/t/t2082-parallel-checkout-attributes.sh\n@@ -20,16 +20,19 @@ test_expect_success 'parallel-checkout with ident' '\n \t(\n \t\tcd ident &&\n \t\techo \"A ident\" >.gitattributes &&\n+\t\techo \"C ident=MyCusomVeryLongAndWordyId\" >>.gitattributes &&\n \t\techo \"\\$Id\\$\" >A &&\n \t\techo \"\\$Id\\$\" >B &&\n+\t\techo \"\\$MyCusomVeryLongAndWordyId\\$\" >C &&\n \t\tgit add -A &&\n \t\tgit commit -m id &&\n \n-\t\trm A B &&\n+\t\trm A B C &&\n \t\ttest_checkout_workers 2 git reset --hard &&\n \t\thexsz=$(test_oid hexsz) &&\n \t\tgrep -E \"\\\\\\$Id: [0-9a-f]{$hexsz} \\\\\\$\" A &&\n-\t\tgrep \"\\\\\\$Id\\\\\\$\" B\n+\t\tgrep \"\\\\\\$Id\\\\\\$\" B &&\n+\t\tgrep -E \"\\\\\\$MyCusomVeryLongAndWordyId: [0-9a-f]{$hexsz} \\\\\\$\" C\n \t)\n '\n \n\nbase-commit: 225bc32a989d7a22fa6addafd4ce7dcd04675dbf\n-- \ngitgitgadget\n"},{"id":"433869","messageId":"20210826203713.45139-1-matheus.bernardino@usp.br","threadId":"56354","inReplyTo":"pull.1074.v2.git.git.1629952119446.gitgitgadget@gmail.com","subject":"Re: [PATCH v2] Make ident dynamic, not just a hardcoded value of \"$Id\".","fromName":"Matheus Tavares","fromEmail":"matheus.bernardino@usp.br","sentAt":"2021-08-26T20:37:13Z","receivedAt":"2021-08-26T20:37:32Z","isPatch":true,"sender":{"key":"matheus.tavb@gmail.com","avatar":"https://avatars.githubusercontent.com/u/12701583?v=4"},"body":"Hi, Maksym\n\nI haven't read the entire patch (and I don't normally use the ident feature),\nbut I left a few comments below: \n\nOn Thu, Aug 26, 2021 at 1:28 AM Maksym Sobolyev via GitGitGadget <gitgitgadget@gmail.com> wrote:\n>\n> diff --git a/Documentation/gitattributes.txt b/Documentation/gitattributes.txt\n> index 83fd4e19a4..9e486f3e8d 100644\n> --- a/Documentation/gitattributes.txt\n> +++ b/Documentation/gitattributes.txt\n> @@ -382,6 +382,14 @@ sign `$` upon checkout.  Any byte sequence that begins with\n>  `$Id:` and ends with `$` in the worktree file is replaced\n>  with `$Id$` upon check-in.\n>\n> +The `ident` attribute can also provide an optional value,\n> +which if supplied is going to be used for expansion instead of\n> +the string `Id`.\n> +\n> +------------------------\n> +*.[ch]         ident=FreeBSD\n> +------------------------\n\nWhat happens if there is a ':' or '$' in the custom id name?\n\n$ echo 'f\tident=$weird:$' >.gitattributes\n$ echo '$$weird:$$' >f\n$ git add .\n$ git commit -m files\n$ rm f\n$ git checkout f\n$ cat f\n\n$$weird:$: 907f73b343505bf289a4a25e41ea91d90250fedb $\n\nSeem right. Now let's see the cleaning direction:\n\n$ echo foo >> f\n$ git add f\n$ git cat-file -p :f\n\n$$weird:$: 907f73b343505bf289a4a25e41ea91d90250fedb $\nfoo\n\nHmm, this one is not right. I think I know the cause:\n\n> diff --git a/convert.c b/convert.c\n> index 0d6fb3410ae..1e8940bf9d7 100644\n> --- a/convert.c\n> +++ b/convert.c\n> @@ -1122,17 +1127,18 @@ static int ident_to_git(const char *src, size_t len,\n>                 len -= dollar + 1 - src;\n>                 src  = dollar + 1;\n>\n> -               if (len > 3 && !memcmp(src, \"Id:\", 3)) {\n> -                       dollar = memchr(src + 3, '$', len - 3);\n> +               if (len > idact->id_len + 1 && !memcmp(src, idact->id, idact->id_len) && src[idact->id_len + 1] == ':') {\n\nShouldn't this be `... && src[idact->id_len] == ':')`? I.e. without the \"+ 1\".\n\n> diff --git a/convert.h b/convert.h\n> index 5ee1c322058..a02632e8104 100644\n> --- a/convert.h\n> +++ b/convert.h\n> @@ -76,11 +76,16 @@ enum convert_crlf_action {\n>\n>  struct convert_driver;\n>\n> +struct ident_action {\n> +       const char *id;\n> +       int id_len;\n\nI known the string size is limited to GIT_MAX_IDENT_LEN, but perhaps it\nwould be better to future-proof this and use `size_t` (or at least\n`unsigned int`).\n\n> diff --git a/parallel-checkout.c b/parallel-checkout.c\n> index ddc0ff3c064..f9a3f2ff25b 100644\n> --- a/parallel-checkout.c\n> +++ b/parallel-checkout.c\n> @@ -403,13 +403,15 @@ static void send_one_item(int fd, struct parallel_checkout_item *pc_item)\n>         size_t name_len = pc_item->ce->ce_namelen;\n>         size_t working_tree_encoding_len = working_tree_encoding ?\n>                                            strlen(working_tree_encoding) : 0;\n> +       const char *ident_action_id = pc_item->ca.ident_action.id;\n> +       size_t ident_action_len = pc_item->ca.ident_action.id_len;\n>\n>         /*\n>          * Any changes in the calculation of the message size must also be made\n>          * in is_eligible_for_parallel_checkout().\n>          */\n>         len_data = sizeof(struct pc_item_fixed_portion) + name_len +\n> -                  working_tree_encoding_len;\n> +                  working_tree_encoding_len + ident_action_len;\n\nThis update in the packet size calculation should also be reflected in\nis_eligible_for_parallel_checkout():\n\n\tpacked_item_size = sizeof(struct pc_item_fixed_portion) + ce->ce_namelen +\n\t\t(ca->working_tree_encoding ? strlen(ca->working_tree_encoding) : 0);\n\n> diff --git a/t/t2082-parallel-checkout-attributes.sh b/t/t2082-parallel-checkout-attributes.sh\n> index 25254579618..822957a8dc8 100755\n> --- a/t/t2082-parallel-checkout-attributes.sh\n> +++ b/t/t2082-parallel-checkout-attributes.sh\n> @@ -20,16 +20,19 @@ test_expect_success 'parallel-checkout with ident' '\n>         (\n>                 cd ident &&\n>                 echo \"A ident\" >.gitattributes &&\n> +               echo \"C ident=MyCusomVeryLongAndWordyId\" >>.gitattributes &&\n>                 echo \"\\$Id\\$\" >A &&\n>                 echo \"\\$Id\\$\" >B &&\n> +               echo \"\\$MyCusomVeryLongAndWordyId\\$\" >C &&\n>                 git add -A &&\n>                 git commit -m id &&\n>\n> -               rm A B &&\n> +               rm A B C &&\n>                 test_checkout_workers 2 git reset --hard &&\n>                 hexsz=$(test_oid hexsz) &&\n>                 grep -E \"\\\\\\$Id: [0-9a-f]{$hexsz} \\\\\\$\" A &&\n> -               grep \"\\\\\\$Id\\\\\\$\" B\n> +               grep \"\\\\\\$Id\\\\\\$\" B &&\n> +               grep -E \"\\\\\\$MyCusomVeryLongAndWordyId: [0-9a-f]{$hexsz} \\\\\\$\" C\n>         )\n>  '\n\nThe change to this test looks good. But there are other things related\nto the ident filter that are not covered here (as the test is mostly\ninterested in how ident interacts with parallel checkout). For example,\nthe test doesn't check whether the \"cleaning\" direction is performed\ncorrectly, it just checks the \"smudging\" part. (That's why it didn't catch the\nerror I showed earlier.)\n\nYou might want to take a look at the other ident tests in\nt0021-conversion.sh and perhaps addapt/copy some of them to ensure that\nthe expected behavior persists when using a custom id name. For example,\nquickly changing the \"$Id:\" references to \"$customId:\" and replacing\n\"ident\" with \"ident=customId\", I seem to get at least one test failure:\n\n--- expected-output     2021-08-26 19:40:07.181596662 +0000\n+++ expanded-keywords   2021-08-26 19:40:07.188263463 +0000\n@@ -6,5 +6,5 @@\n $customId: bebd07c752ffffd6779e1056db5de66c3bb733ed $\n $customId: bebd07c752ffffd6779e1056db5de66c3bb733ed $\n $customId: NoTerminatingSymbol\n-$customId: Foreign Commit With Spaces $\n+$customId: bebd07c752ffffd6779e1056db5de66c3bb733ed $\n $customId: NoTerminatingSymbolAtEOF\n\\ No newline at end of file\nerror: last command exited with $?=1\nnot ok 3 - expanded_in_repo\n\n(But I haven't dig further, and this was just a quick test, so it could be my\nfault.)\n"},{"id":"433900","messageId":"xmqq1r6fd23h.fsf@gitster.g","threadId":"56354","inReplyTo":"pull.1074.v2.git.git.1629952119446.gitgitgadget@gmail.com","subject":"Re: [PATCH v2] Make ident dynamic, not just a hardcoded value of \"$Id\".","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2021-08-27T02:59:46Z","receivedAt":"2021-08-27T02:59:54Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"\"Maksym Sobolyev via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n\n> From: Maksym Sobolyev <sobomax@sippysoft.com>\n>\n> This allows ident to be something like $FreeBSD$ so it provides matching\n> functionality for repos migrated from CVS / SVN.\n>\n> This works by allowing ident to have a parameter, i.e.:\n>\n> * ident=MyCustomId\n>\n> In .gitattributes.\n>\n> Signed-off-by: Maksym Sobolyev <sobomax@sippysoft.com>\n\nJust like cvs uses its configuration mechanism for LocalKeyword,\nthis belongs to the .git/config file.\n\nThe attribute mechanism is inherently for stuff that you would want\nto specify per-path, so the \"do we do keyword expansion for this\nfile (yes/no)?\" is a good match to be an attribute, and that\nquestion is what the original 'ident' attribute asks.\n\nI do not think \"what keyword do we want to expand as if it were\n$Id$?\" is a question we want to be giving different answers per\npath, and it does not smell like a good match to be an attribute.\n\nBy allowing a potentially separate values for\n\n    *.txt ident=FreeBSD\n    *.cc  ident=OpenBSD\n\nthis gives long rope to create a mess to the users.  In addition, a\nproject can easily attack and break its users by having .gitattributes\nwith overly long ident defined, as it would cause the attribute code\nto trigger the die() call added by this patch.\n\nIn short, I do not think the approach taken by this patch to use\nper-path attribute is a good idea at all.\n\nI suspect that this patch mimics crlf_action when naming the new\n\"ident_action\" thing, but that is a poor choice.  crlf_action does\nspecify what different actions to be taken, but the setting does not\nchange the way 'ident' filter behaves in any fundamental way.  It\njust changes the constant string used to locate and replace from\n$Id$ to something else.  ident_keyword (as this is \"keyword\nexpansion\" behaviour) may be a better name.\n\nThere is no inherent reason why the custom ident string must be\nshorter than 255, even though it might not be a bad idea to have a\nreasonable floor for the length.\n\n"},{"id":"434380","messageId":"pull.1074.v3.git.git.1630462385587.gitgitgadget@gmail.com","threadId":"56354","inReplyTo":"pull.1074.v2.git.git.1629952119446.gitgitgadget@gmail.com","subject":"[PATCH v3] Make ident dynamic, not just a hardcoded value of \"$Id\".","fromName":"Maksym Sobolyev via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2021-09-01T02:13:05Z","receivedAt":"2021-09-01T02:13:10Z","isPatch":true,"sender":{"key":"name:Maksym Sobolyev","avatar":null},"body":"From: Maksym Sobolyev <sobomax@sippysoft.com>\n\nThis allows ident to be something like $FreeBSD$ so it provides matching\nfunctionality for repos migrated from CVS / SVN.\n\nThis works by allowing ident to have a parameter, i.e.:\n\n* ident=MyCustomId\n\nIn .gitattributes.\n\nExtend the ident expansion tests to also verify custom ident.\n\nSigned-off-by: Maksym Sobolyev <sobomax@sippysoft.com>\n---\n    Make ident dynamic, not just a hardcoded value of \"$Id\".\n    \n    This allows ident to be something like $FreeBSD$ so it provides matching\n    functionality for repos migrated from CVS / SVN.\n    \n    This works by allowing ident to have a parameter, i.e.:\n    \n    * ident=MyCustomId\n    \n    In .gitattributes.\n    \n    cc: Philip Oakley philipoakley@iee.email\n\nPublished-As: https://github.com/gitgitgadget/git/releases/tag/pr-git-1074%2Fsobomax%2Fpr-custom_ident-v3\nFetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-git-1074/sobomax/pr-custom_ident-v3\nPull-Request: https://github.com/git/git/pull/1074\n\nRange-diff vs v2:\n\n 1:  16713788a26 ! 1:  74a705e5bbe Make ident dynamic, not just a hardcoded value of \"$Id\".\n     @@ Commit message\n      \n          In .gitattributes.\n      \n     +    Extend the ident expansion tests to also verify custom ident.\n     +\n          Signed-off-by: Maksym Sobolyev <sobomax@sippysoft.com>\n      \n       ## Documentation/gitattributes.txt ##\n     @@ convert.c: static int read_convert_config(const char *var, const char *value, vo\n       }\n       \n      -static int count_ident(const char *cp, unsigned long size)\n     -+#define ID_STR \"Id\"\n     -+\n     -+#define GIT_MAX_IDENT_LEN 255\n     ++#define ID_STR_DEFAULT \"Id\"\n      +\n      +static int count_ident(const char *cp, unsigned long size,\n      +\t\t       const struct ident_action *idact)\n     @@ convert.c: static int ident_to_git(const char *src, size_t len,\n       \n      -\t\tif (len > 3 && !memcmp(src, \"Id:\", 3)) {\n      -\t\t\tdollar = memchr(src + 3, '$', len - 3);\n     -+\t\tif (len > idact->id_len + 1 && !memcmp(src, idact->id, idact->id_len) && src[idact->id_len + 1] == ':') {\n     ++\t\tif (len > idact->id_len + 1 && !memcmp(src, idact->id, idact->id_len) && src[idact->id_len] == ':') {\n      +\t\t\tdollar = memchr(src + idact->id_len + 1, '$', len - (idact->id_len + 1));\n       \t\t\tif (!dollar)\n       \t\t\t\tbreak;\n     @@ convert.c: static struct convert_driver *git_path_check_convert(struct attr_chec\n      -\treturn !!ATTR_TRUE(value);\n      +\tif (!ATTR_UNSET(value) && !ATTR_FALSE(value)) {\n      +\t\tif (ATTR_TRUE(value))\n     -+\t\t\tidact.id = ID_STR;\n     ++\t\t\tidact.id = ID_STR_DEFAULT;\n      +\t\telse\n      +\t\t\tidact.id = value;\n      +\t\tidact.id_len = strlen(idact.id);\n     -+\t\tif (idact.id_len > GIT_MAX_IDENT_LEN)\n     -+\t\t\tdie(_(\"ident value length exceeds GIT_MAX_IDENT_LEN\"));\n      +\t}\n      +\treturn idact;\n       }\n     @@ convert.c: struct ident_filter {\n       \tstruct stream_filter filter;\n       \tstruct strbuf left;\n       \tint state;\n     --\tchar ident[GIT_MAX_HEXSZ + 5]; /* \": x40 $\" */\n      +\tconst struct ident_action *idact;\n     -+\tchar ident[GIT_MAX_HEXSZ + GIT_MAX_IDENT_LEN + 3]; /* \": x40 $\" */\n     + \tchar ident[GIT_MAX_HEXSZ + 5]; /* \": x40 $\" */\n       };\n       \n     - static int is_foreign_ident(const char *str)\n     +-static int is_foreign_ident(const char *str)\n     ++static int is_foreign_ident(const struct ident_action *idact, const char *str)\n     + {\n     + \tint i;\n     + \n     +-\tif (!skip_prefix(str, \"$Id: \", &str))\n     ++\tif (str[0] != '$' || strlen(str) < idact->id_len + 3 ||\n     ++\t    memcmp(str + 1, idact->id, idact->id_len) != 0 ||\n     ++\t    !skip_prefix(str + 1 + idact->id_len, \": \", &str))\n     + \t\treturn 0;\n     + \tfor (i = 0; str[i]; i++) {\n     + \t\tif (isspace(str[i]) && str[i+1] != '$')\n      @@ convert.c: static int ident_filter_fn(struct stream_filter *filter,\n       \t\t\t   char *output, size_t *osize_p)\n       {\n     @@ convert.c: static int ident_filter_fn(struct stream_filter *filter,\n       \t\tcase IDENT_SKIPPING:\n       \t\t\t/* fallthrough */\n      @@ convert.c: static int ident_filter_fn(struct stream_filter *filter,\n     + \t\t\tstrbuf_addch(&ident->left, ch);\n       \t\t\tif (ch != '\\n' && ch != '$')\n       \t\t\t\tcontinue;\n     - \t\t\tif (ch == '$' && !is_foreign_ident(ident->left.buf)) {\n     +-\t\t\tif (ch == '$' && !is_foreign_ident(ident->left.buf)) {\n      -\t\t\t\tstrbuf_setlen(&ident->left, sizeof(head) - 1);\n     ++\t\t\tif (ch == '$' && !is_foreign_ident(idact, ident->left.buf)) {\n      +\t\t\t\tstrbuf_setlen(&ident->left, idact->id_len + 1);\n       \t\t\t\tstrbuf_addstr(&ident->left, ident->ident);\n       \t\t\t}\n     @@ convert.h: enum convert_crlf_action {\n       \n      +struct ident_action {\n      +\tconst char *id;\n     -+\tint id_len;\n     ++\tsize_t id_len;\n      +};\n      +\n       struct conv_attrs {\n     @@ convert.h: enum convert_crlf_action {\n       \n      \n       ## parallel-checkout.c ##\n     +@@ parallel-checkout.c: static int is_eligible_for_parallel_checkout(const struct cache_entry *ce,\n     + \t\treturn 0;\n     + \n     + \tpacked_item_size = sizeof(struct pc_item_fixed_portion) + ce->ce_namelen +\n     +-\t\t(ca->working_tree_encoding ? strlen(ca->working_tree_encoding) : 0);\n     ++\t\t(ca->working_tree_encoding ? strlen(ca->working_tree_encoding) : 0) +\n     ++\t\tca->ident_action.id_len;\n     + \n     + \t/*\n     + \t * The amount of data we send to the workers per checkout item is\n      @@ parallel-checkout.c: static void send_one_item(int fd, struct parallel_checkout_item *pc_item)\n       \tsize_t name_len = pc_item->ce->ce_namelen;\n       \tsize_t working_tree_encoding_len = working_tree_encoding ?\n     @@ parallel-checkout.h: struct pc_item_fixed_portion {\n       \tsize_t name_len;\n       };\n      \n     + ## t/t0021-conversion.sh ##\n     +@@ t/t0021-conversion.sh: test_expect_success setup '\n     + \t{\n     + \t    echo \"*.t filter=rot13\"\n     + \t    echo \"*.i ident\"\n     ++\t    echo \"*.ci ident=customId\"\n     + \t} >.gitattributes &&\n     + \n     + \t{\n     + \t    echo a b c d e f g h i j k l m\n     + \t    echo n o p q r s t u v w x y z\n     + \t    echo '\\''$Id$'\\''\n     ++\t    echo '\\''$customId$'\\''\n     + \t} >test &&\n     + \tcat test >test.t &&\n     + \tcat test >test.o &&\n     + \tcat test >test.i &&\n     +-\tgit add test test.t test.i &&\n     +-\trm -f test test.t test.i &&\n     +-\tgit checkout -- test test.t test.i &&\n     ++\tcat test >test.ci &&\n     ++\tgit add test test.t test.i test.ci &&\n     ++\trm -f test test.t test.i test.ci &&\n     ++\tgit checkout -- test test.t test.i test.ci &&\n     + \n     + \techo \"content-test2\" >test2.o &&\n     + \techo \"content-test3 - filename with special characters\" >\"test3 '\\''sq'\\'',\\$x=.o\"\n     + '\n     + \n     +-script='s/^\\$Id: \\([0-9a-f]*\\) \\$/\\1/p'\n     ++script_i='s/^\\$Id: \\([0-9a-f]*\\) \\$/\\1/p'\n     ++script_ci='s/^\\$customId: \\([0-9a-f]*\\) \\$/\\1/p'\n     + \n     + test_expect_success check '\n     + \n     +@@ t/t0021-conversion.sh: test_expect_success check '\n     + \t# ident should be stripped in the repository\n     + \tgit diff --raw --exit-code :test :test.i &&\n     + \tid=$(git rev-parse --verify :test) &&\n     +-\tembedded=$(sed -ne \"$script\" test.i) &&\n     ++\tembedded=$(sed -ne \"$script_i\" test.i) &&\n     ++\tnembedded=$(sed -ne \"$script_ci\" test.i) &&\n     + \ttest \"z$id\" = \"z$embedded\" &&\n     ++\ttest \"z\" = \"z$nembedded\" &&\n     ++\tembedded=$(sed -ne \"$script_ci\" test.ci) &&\n     ++\tnembedded=$(sed -ne \"$script_i\" test.ci) &&\n     ++\ttest \"z$id\" = \"z$embedded\" &&\n     ++\ttest \"z\" = \"z$nembedded\" &&\n     + \n     + \tgit cat-file blob :test.t >test.r &&\n     + \n     +@@ t/t0021-conversion.sh: test_expect_success check '\n     + \ttest_cmp test.r test.t\n     + '\n     + \n     ++gen_expanded_keywords() {\n     ++\tlocal id=\"${1}\"\n     ++\techo \"File with expanded keywords\"\n     ++\techo \"\\$$id\\$\"\n     ++\techo \"\\$$id:\\$\"\n     ++\techo \"\\$$id: 0000000000000000000000000000000000000000 \\$\"\n     ++\techo \"\\$$id: NoSpaceAtEnd\\$\"\n     ++\techo \"\\$$id:NoSpaceAtFront \\$\"\n     ++\techo \"\\$$id:NoSpaceAtEitherEnd\\$\"\n     ++\techo \"\\$$id: NoTerminatingSymbol\"\n     ++\techo \"\\$$id: Foreign Commit With Spaces \\$\"\n     ++\tprintf \"\\$$id: NoTerminatingSymbolAtEOF\"\n     ++}\n     ++\n     ++gen_expected_output_0() {\n     ++\tlocal id=\"${1}\"\n     ++\tlocal hid=\"${2}\"\n     ++\techo \"File with expanded keywords\"\n     ++\techo \"\\$$id: $hid \\$\"\n     ++\techo \"\\$$id: $hid \\$\"\n     ++\techo \"\\$$id: $hid \\$\"\n     ++\techo \"\\$$id: $hid \\$\"\n     ++\techo \"\\$$id: $hid \\$\"\n     ++\techo \"\\$$id: $hid \\$\"\n     ++\techo \"\\$$id: NoTerminatingSymbol\"\n     ++\techo \"\\$$id: Foreign Commit With Spaces \\$\"\n     ++}\n     ++\n     ++gen_expected_output() {\n     ++\tlocal id=\"${1}\"\n     ++\tgen_expected_output_0 \"${@}\"\n     ++\tprintf \"\\$$id: NoTerminatingSymbolAtEOF\"\n     ++}\n     ++\n     ++gen_expected_output_crlf() {\n     ++\tlocal id=\"${1}\"\n     ++\tgen_expected_output_0 \"${@}\" | append_cr\n     ++\tprintf \"\\$$id: NoTerminatingSymbolAtEOF\"\n     ++}\n     ++\n     + # If an expanded ident ever gets into the repository, we want to make sure that\n     + # it is collapsed before being expanded again on checkout\n     + test_expect_success expanded_in_repo '\n     +-\t{\n     +-\t\techo \"File with expanded keywords\"\n     +-\t\techo \"\\$Id\\$\"\n     +-\t\techo \"\\$Id:\\$\"\n     +-\t\techo \"\\$Id: 0000000000000000000000000000000000000000 \\$\"\n     +-\t\techo \"\\$Id: NoSpaceAtEnd\\$\"\n     +-\t\techo \"\\$Id:NoSpaceAtFront \\$\"\n     +-\t\techo \"\\$Id:NoSpaceAtEitherEnd\\$\"\n     +-\t\techo \"\\$Id: NoTerminatingSymbol\"\n     +-\t\techo \"\\$Id: Foreign Commit With Spaces \\$\"\n     +-\t} >expanded-keywords.0 &&\n     +-\n     +-\t{\n     +-\t\tcat expanded-keywords.0 &&\n     +-\t\tprintf \"\\$Id: NoTerminatingSymbolAtEOF\"\n     +-\t} >expanded-keywords &&\n     ++\tgen_expanded_keywords Id >expanded-keywords &&\n     ++\tgen_expanded_keywords customId >expanded-keywords_ci &&\n     + \tcat expanded-keywords >expanded-keywords-crlf &&\n     ++\tcat expanded-keywords_ci >expanded-keywords-crlf_ci &&\n     + \tgit add expanded-keywords expanded-keywords-crlf &&\n     ++\tgit add expanded-keywords_ci expanded-keywords-crlf_ci &&\n     + \tgit commit -m \"File with keywords expanded\" &&\n     + \tid=$(git rev-parse --verify :expanded-keywords) &&\n     ++\tid_ci=$(git rev-parse --verify :expanded-keywords_ci) &&\n     + \n     +-\t{\n     +-\t\techo \"File with expanded keywords\"\n     +-\t\techo \"\\$Id: $id \\$\"\n     +-\t\techo \"\\$Id: $id \\$\"\n     +-\t\techo \"\\$Id: $id \\$\"\n     +-\t\techo \"\\$Id: $id \\$\"\n     +-\t\techo \"\\$Id: $id \\$\"\n     +-\t\techo \"\\$Id: $id \\$\"\n     +-\t\techo \"\\$Id: NoTerminatingSymbol\"\n     +-\t\techo \"\\$Id: Foreign Commit With Spaces \\$\"\n     +-\t} >expected-output.0 &&\n     +-\t{\n     +-\t\tcat expected-output.0 &&\n     +-\t\tprintf \"\\$Id: NoTerminatingSymbolAtEOF\"\n     +-\t} >expected-output &&\n     +-\t{\n     +-\t\tappend_cr <expected-output.0 &&\n     +-\t\tprintf \"\\$Id: NoTerminatingSymbolAtEOF\"\n     +-\t} >expected-output-crlf &&\n     ++\tgen_expected_output Id $id >expected-output &&\n     ++\tgen_expected_output customId $id_ci >expected-output_ci &&\n     ++\tgen_expected_output_crlf Id $id >expected-output-crlf &&\n     ++\tgen_expected_output_crlf customId $id_ci >expected-output-crlf_ci &&\n     + \t{\n     + \t\techo \"expanded-keywords ident\"\n     ++\t\techo \"expanded-keywords_ci ident=customId\"\n     + \t\techo \"expanded-keywords-crlf ident text eol=crlf\"\n     ++\t\techo \"expanded-keywords-crlf_ci ident=customId text eol=crlf\"\n     + \t} >>.gitattributes &&\n     + \n     + \trm -f expanded-keywords expanded-keywords-crlf &&\n     ++\trm -f expanded-keywords_ci expanded-keywords-crlf_ci &&\n     + \n     + \tgit checkout -- expanded-keywords &&\n     + \ttest_cmp expected-output expanded-keywords &&\n     + \n     + \tgit checkout -- expanded-keywords-crlf &&\n     +-\ttest_cmp expected-output-crlf expanded-keywords-crlf\n     ++\ttest_cmp expected-output-crlf expanded-keywords-crlf &&\n     ++\n     ++\tgit checkout -- expanded-keywords_ci &&\n     ++\ttest_cmp expected-output_ci expanded-keywords_ci &&\n     ++\n     ++\tgit checkout -- expanded-keywords-crlf_ci &&\n     ++\ttest_cmp expected-output-crlf_ci expanded-keywords-crlf_ci\n     + '\n     + \n     + # The use of %f in a filter definition is expanded to the path to\n     +\n       ## t/t2082-parallel-checkout-attributes.sh ##\n      @@ t/t2082-parallel-checkout-attributes.sh: test_expect_success 'parallel-checkout with ident' '\n       \t(\n\n\n Documentation/gitattributes.txt         |   8 ++\n builtin/checkout--worker.c              |  17 +++-\n convert.c                               | 116 ++++++++++++++----------\n convert.h                               |   7 +-\n parallel-checkout.c                     |  14 ++-\n parallel-checkout.h                     |   8 +-\n t/t0021-conversion.sh                   | 115 ++++++++++++++---------\n t/t2082-parallel-checkout-attributes.sh |   7 +-\n 8 files changed, 192 insertions(+), 100 deletions(-)\n\ndiff --git a/Documentation/gitattributes.txt b/Documentation/gitattributes.txt\nindex 83fd4e19a41..9e486f3e8d3 100644\n--- a/Documentation/gitattributes.txt\n+++ b/Documentation/gitattributes.txt\n@@ -382,6 +382,14 @@ sign `$` upon checkout.  Any byte sequence that begins with\n `$Id:` and ends with `$` in the worktree file is replaced\n with `$Id$` upon check-in.\n \n+The `ident` attribute can also provide an optional value,\n+which if supplied is going to be used for expansion instead of\n+the string `Id`.\n+\n+------------------------\n+*.[ch]\t\tident=FreeBSD\n+------------------------\n+\n \n `filter`\n ^^^^^^^^\ndiff --git a/builtin/checkout--worker.c b/builtin/checkout--worker.c\nindex fb9fd13b73c..61ba39402ad 100644\n--- a/builtin/checkout--worker.c\n+++ b/builtin/checkout--worker.c\n@@ -9,7 +9,7 @@ static void packet_to_pc_item(const char *buffer, int len,\n \t\t\t      struct parallel_checkout_item *pc_item)\n {\n \tconst struct pc_item_fixed_portion *fixed_portion;\n-\tconst char *variant;\n+\tconst char *variant, *ident_action;\n \tchar *encoding;\n \n \tif (len < sizeof(struct pc_item_fixed_portion))\n@@ -19,7 +19,8 @@ static void packet_to_pc_item(const char *buffer, int len,\n \tfixed_portion = (struct pc_item_fixed_portion *)buffer;\n \n \tif (len - sizeof(struct pc_item_fixed_portion) !=\n-\t\tfixed_portion->name_len + fixed_portion->working_tree_encoding_len)\n+\t\tfixed_portion->name_len + fixed_portion->working_tree_encoding_len +\n+\t\tfixed_portion->ident_action_len)\n \t\tBUG(\"checkout worker received corrupted item\");\n \n \tvariant = buffer + sizeof(struct pc_item_fixed_portion);\n@@ -43,11 +44,21 @@ static void packet_to_pc_item(const char *buffer, int len,\n \tpc_item->ce->ce_namelen = fixed_portion->name_len;\n \tpc_item->ce->ce_mode = fixed_portion->ce_mode;\n \tmemcpy(pc_item->ce->name, variant, pc_item->ce->ce_namelen);\n+\tvariant += pc_item->ce->ce_namelen;\n \toidcpy(&pc_item->ce->oid, &fixed_portion->oid);\n \n+\tif (fixed_portion->ident_action_len) {\n+\t\tident_action = xmemdupz(variant,\n+\t\t\t\t\tfixed_portion->ident_action_len);\n+\t\tvariant += fixed_portion->ident_action_len;\n+\t} else {\n+\t\tident_action = NULL;\n+\t}\n+\n \tpc_item->id = fixed_portion->id;\n \tpc_item->ca.crlf_action = fixed_portion->crlf_action;\n-\tpc_item->ca.ident = fixed_portion->ident;\n+\tpc_item->ca.ident_action.id = ident_action;\n+\tpc_item->ca.ident_action.id_len = fixed_portion->ident_action_len;\n \tpc_item->ca.working_tree_encoding = encoding;\n }\n \ndiff --git a/convert.c b/convert.c\nindex 0d6fb3410ae..111bfeeaf36 100644\n--- a/convert.c\n+++ b/convert.c\n@@ -1056,7 +1056,10 @@ static int read_convert_config(const char *var, const char *value, void *cb)\n \treturn 0;\n }\n \n-static int count_ident(const char *cp, unsigned long size)\n+#define ID_STR_DEFAULT \"Id\"\n+\n+static int count_ident(const char *cp, unsigned long size,\n+\t\t       const struct ident_action *idact)\n {\n \t/*\n \t * \"$Id: 0000000000000000000000000000000000000000 $\" <=> \"$Id$\"\n@@ -1069,13 +1072,13 @@ static int count_ident(const char *cp, unsigned long size)\n \t\tsize--;\n \t\tif (ch != '$')\n \t\t\tcontinue;\n-\t\tif (size < 3)\n+\t\tif (size < idact->id_len + 1)\n \t\t\tbreak;\n-\t\tif (memcmp(\"Id\", cp, 2))\n+\t\tif (memcmp(idact->id, cp, idact->id_len))\n \t\t\tcontinue;\n-\t\tch = cp[2];\n-\t\tcp += 3;\n-\t\tsize -= 3;\n+\t\tch = cp[idact->id_len];\n+\t\tcp += idact->id_len + 1;\n+\t\tsize -= idact->id_len + 1;\n \t\tif (ch == '$')\n \t\t\tcnt++; /* $Id$ */\n \t\tif (ch != ':')\n@@ -1099,11 +1102,11 @@ static int count_ident(const char *cp, unsigned long size)\n }\n \n static int ident_to_git(const char *src, size_t len,\n-\t\t\tstruct strbuf *buf, int ident)\n+\t\t\tstruct strbuf *buf, const struct ident_action *idact)\n {\n \tchar *dst, *dollar;\n \n-\tif (!ident || (src && !count_ident(src, len)))\n+\tif (!idact->id || (src && !count_ident(src, len, idact)))\n \t\treturn 0;\n \n \tif (!buf)\n@@ -1122,17 +1125,18 @@ static int ident_to_git(const char *src, size_t len,\n \t\tlen -= dollar + 1 - src;\n \t\tsrc  = dollar + 1;\n \n-\t\tif (len > 3 && !memcmp(src, \"Id:\", 3)) {\n-\t\t\tdollar = memchr(src + 3, '$', len - 3);\n+\t\tif (len > idact->id_len + 1 && !memcmp(src, idact->id, idact->id_len) && src[idact->id_len] == ':') {\n+\t\t\tdollar = memchr(src + idact->id_len + 1, '$', len - (idact->id_len + 1));\n \t\t\tif (!dollar)\n \t\t\t\tbreak;\n-\t\t\tif (memchr(src + 3, '\\n', dollar - src - 3)) {\n+\t\t\tif (memchr(src + idact->id_len + 1, '\\n', dollar - src - (idact->id_len + 1))) {\n \t\t\t\t/* Line break before the next dollar. */\n \t\t\t\tcontinue;\n \t\t\t}\n \n-\t\t\tmemcpy(dst, \"Id$\", 3);\n-\t\t\tdst += 3;\n+\t\t\tmemcpy(dst, idact->id, idact->id_len);\n+\t\t\tdst[idact->id_len] = '$';\n+\t\t\tdst += idact->id_len + 1;\n \t\t\tlen -= dollar + 1 - src;\n \t\t\tsrc  = dollar + 1;\n \t\t}\n@@ -1143,16 +1147,16 @@ static int ident_to_git(const char *src, size_t len,\n }\n \n static int ident_to_worktree(const char *src, size_t len,\n-\t\t\t     struct strbuf *buf, int ident)\n+\t\t\t     struct strbuf *buf, const struct ident_action *idact)\n {\n \tstruct object_id oid;\n \tchar *to_free = NULL, *dollar, *spc;\n \tint cnt;\n \n-\tif (!ident)\n+\tif (!idact->id)\n \t\treturn 0;\n \n-\tcnt = count_ident(src, len);\n+\tcnt = count_ident(src, len, idact);\n \tif (!cnt)\n \t\treturn 0;\n \n@@ -1161,7 +1165,7 @@ static int ident_to_worktree(const char *src, size_t len,\n \t\tto_free = strbuf_detach(buf, NULL);\n \thash_object_file(the_hash_algo, src, len, \"blob\", &oid);\n \n-\tstrbuf_grow(buf, len + cnt * (the_hash_algo->hexsz + 3));\n+\tstrbuf_grow(buf, len + cnt * (the_hash_algo->hexsz + idact->id_len + 1));\n \tfor (;;) {\n \t\t/* step 1: run to the next '$' */\n \t\tdollar = memchr(src, '$', len);\n@@ -1172,14 +1176,14 @@ static int ident_to_worktree(const char *src, size_t len,\n \t\tsrc  = dollar + 1;\n \n \t\t/* step 2: does it looks like a bit like Id:xxx$ or Id$ ? */\n-\t\tif (len < 3 || memcmp(\"Id\", src, 2))\n+\t\tif (len < idact->id_len + 1 || memcmp(idact->id, src, idact->id_len))\n \t\t\tcontinue;\n \n \t\t/* step 3: skip over Id$ or Id:xxxxx$ */\n-\t\tif (src[2] == '$') {\n-\t\t\tsrc += 3;\n-\t\t\tlen -= 3;\n-\t\t} else if (src[2] == ':') {\n+\t\tif (src[idact->id_len] == '$') {\n+\t\t\tsrc += idact->id_len + 1;\n+\t\t\tlen -= idact->id_len + 1;\n+\t\t} else if (src[idact->id_len] == ':') {\n \t\t\t/*\n \t\t\t * It's possible that an expanded Id has crept its way into the\n \t\t\t * repository, we cope with that by stripping the expansion out.\n@@ -1187,18 +1191,18 @@ static int ident_to_worktree(const char *src, size_t len,\n \t\t\t * on checkout, which won't go away by stash, but let's keep it\n \t\t\t * for git-style ids.\n \t\t\t */\n-\t\t\tdollar = memchr(src + 3, '$', len - 3);\n+\t\t\tdollar = memchr(src + idact->id_len + 1, '$', len - (idact->id_len + 1));\n \t\t\tif (!dollar) {\n \t\t\t\t/* incomplete keyword, no more '$', so just quit the loop */\n \t\t\t\tbreak;\n \t\t\t}\n \n-\t\t\tif (memchr(src + 3, '\\n', dollar - src - 3)) {\n+\t\t\tif (memchr(src + idact->id_len + 1, '\\n', dollar - src - (idact->id_len + 1))) {\n \t\t\t\t/* Line break before the next dollar. */\n \t\t\t\tcontinue;\n \t\t\t}\n \n-\t\t\tspc = memchr(src + 4, ' ', dollar - src - 4);\n+\t\t\tspc = memchr(src + idact->id_len + 2, ' ', dollar - src - (idact->id_len + 2));\n \t\t\tif (spc && spc < dollar-1) {\n \t\t\t\t/* There are spaces in unexpected places.\n \t\t\t\t * This is probably an id from some other\n@@ -1215,7 +1219,8 @@ static int ident_to_worktree(const char *src, size_t len,\n \t\t}\n \n \t\t/* step 4: substitute */\n-\t\tstrbuf_addstr(buf, \"Id: \");\n+\t\tstrbuf_addstr(buf, idact->id);\n+\t\tstrbuf_addstr(buf, \": \");\n \t\tstrbuf_addstr(buf, oid_to_hex(&oid));\n \t\tstrbuf_addstr(buf, \" $\");\n \t}\n@@ -1286,11 +1291,19 @@ static struct convert_driver *git_path_check_convert(struct attr_check_item *che\n \treturn NULL;\n }\n \n-static int git_path_check_ident(struct attr_check_item *check)\n+static struct ident_action git_path_check_ident(struct attr_check_item *check)\n {\n+\tstruct ident_action idact = {.id = NULL, .id_len = 0};\n \tconst char *value = check->value;\n \n-\treturn !!ATTR_TRUE(value);\n+\tif (!ATTR_UNSET(value) && !ATTR_FALSE(value)) {\n+\t\tif (ATTR_TRUE(value))\n+\t\t\tidact.id = ID_STR_DEFAULT;\n+\t\telse\n+\t\t\tidact.id = value;\n+\t\tidact.id_len = strlen(idact.id);\n+\t}\n+\treturn idact;\n }\n \n static struct attr_check *check;\n@@ -1313,7 +1326,7 @@ void convert_attrs(struct index_state *istate,\n \tca->crlf_action = git_path_check_crlf(ccheck + 4);\n \tif (ca->crlf_action == CRLF_UNDEFINED)\n \t\tca->crlf_action = git_path_check_crlf(ccheck + 0);\n-\tca->ident = git_path_check_ident(ccheck + 1);\n+\tca->ident_action = git_path_check_ident(ccheck + 1);\n \tca->drv = git_path_check_convert(ccheck + 2);\n \tif (ca->crlf_action != CRLF_BINARY) {\n \t\tenum eol eol_attr = git_path_check_eol(ccheck + 3);\n@@ -1433,7 +1446,7 @@ int convert_to_git(struct index_state *istate,\n \t\t\tlen = dst->len;\n \t\t}\n \t}\n-\treturn ret | ident_to_git(src, len, dst, ca.ident);\n+\treturn ret | ident_to_git(src, len, dst, &ca.ident_action);\n }\n \n void convert_to_git_filter_fd(struct index_state *istate,\n@@ -1450,7 +1463,7 @@ void convert_to_git_filter_fd(struct index_state *istate,\n \n \tencode_to_git(path, dst->buf, dst->len, dst, ca.working_tree_encoding, conv_flags);\n \tcrlf_to_git(istate, path, dst->buf, dst->len, dst, ca.crlf_action, conv_flags);\n-\tident_to_git(dst->buf, dst->len, dst, ca.ident);\n+\tident_to_git(dst->buf, dst->len, dst, &ca.ident_action);\n }\n \n static int convert_to_working_tree_ca_internal(const struct conv_attrs *ca,\n@@ -1462,7 +1475,7 @@ static int convert_to_working_tree_ca_internal(const struct conv_attrs *ca,\n {\n \tint ret = 0, ret_filter = 0;\n \n-\tret |= ident_to_worktree(src, len, dst, ca->ident);\n+\tret |= ident_to_worktree(src, len, dst, &(ca->ident_action));\n \tif (ret) {\n \t\tsrc = dst->buf;\n \t\tlen = dst->len;\n@@ -1810,14 +1823,17 @@ struct ident_filter {\n \tstruct stream_filter filter;\n \tstruct strbuf left;\n \tint state;\n+\tconst struct ident_action *idact;\n \tchar ident[GIT_MAX_HEXSZ + 5]; /* \": x40 $\" */\n };\n \n-static int is_foreign_ident(const char *str)\n+static int is_foreign_ident(const struct ident_action *idact, const char *str)\n {\n \tint i;\n \n-\tif (!skip_prefix(str, \"$Id: \", &str))\n+\tif (str[0] != '$' || strlen(str) < idact->id_len + 3 ||\n+\t    memcmp(str + 1, idact->id, idact->id_len) != 0 ||\n+\t    !skip_prefix(str + 1 + idact->id_len, \": \", &str))\n \t\treturn 0;\n \tfor (i = 0; str[i]; i++) {\n \t\tif (isspace(str[i]) && str[i+1] != '$')\n@@ -1847,13 +1863,16 @@ static int ident_filter_fn(struct stream_filter *filter,\n \t\t\t   char *output, size_t *osize_p)\n {\n \tstruct ident_filter *ident = (struct ident_filter *)filter;\n-\tstatic const char head[] = \"$Id\";\n+\tconst struct ident_action *idact = ident->idact;\n \n \tif (!input) {\n \t\t/* drain upon eof */\n \t\tswitch (ident->state) {\n \t\tdefault:\n-\t\t\tstrbuf_add(&ident->left, head, ident->state);\n+\t\t\tif (ident->state > 0)\n+\t\t\t\tstrbuf_addch(&ident->left, '$');\n+\t\t\tif (ident->state > 1)\n+\t\t\t\tstrbuf_add(&ident->left, idact->id, ident->state - 1);\n \t\t\t/* fallthrough */\n \t\tcase IDENT_SKIPPING:\n \t\t\t/* fallthrough */\n@@ -1884,23 +1903,27 @@ static int ident_filter_fn(struct stream_filter *filter,\n \t\t\tstrbuf_addch(&ident->left, ch);\n \t\t\tif (ch != '\\n' && ch != '$')\n \t\t\t\tcontinue;\n-\t\t\tif (ch == '$' && !is_foreign_ident(ident->left.buf)) {\n-\t\t\t\tstrbuf_setlen(&ident->left, sizeof(head) - 1);\n+\t\t\tif (ch == '$' && !is_foreign_ident(idact, ident->left.buf)) {\n+\t\t\t\tstrbuf_setlen(&ident->left, idact->id_len + 1);\n \t\t\t\tstrbuf_addstr(&ident->left, ident->ident);\n \t\t\t}\n \t\t\tident->state = IDENT_DRAINING;\n \t\t\tcontinue;\n \t\t}\n \n-\t\tif (ident->state < sizeof(head) &&\n-\t\t    head[ident->state] == ch) {\n+\t\tif ((ident->state == 0 && ch == '$') ||\n+\t\t    (ident->state > 0 && ident->state < idact->id_len + 1 &&\n+\t\t     idact->id[ident->state - 1] == ch)) {\n \t\t\tident->state++;\n \t\t\tcontinue;\n \t\t}\n \n-\t\tif (ident->state)\n-\t\t\tstrbuf_add(&ident->left, head, ident->state);\n-\t\tif (ident->state == sizeof(head) - 1) {\n+\t\tif (ident->state) {\n+\t\t\tstrbuf_addch(&ident->left, '$');\n+\t\t\tif (ident->state > 1)\n+\t\t\t\tstrbuf_add(&ident->left, idact->id, ident->state - 1);\n+\t\t}\n+\t\tif (ident->state == idact->id_len + 1) {\n \t\t\tif (ch != ':' && ch != '$') {\n \t\t\t\tstrbuf_addch(&ident->left, ch);\n \t\t\t\tident->state = 0;\n@@ -1935,7 +1958,7 @@ static struct stream_filter_vtbl ident_vtbl = {\n \tident_free_fn,\n };\n \n-static struct stream_filter *ident_filter(const struct object_id *oid)\n+static struct stream_filter *ident_filter(const struct object_id *oid, const struct ident_action *idact)\n {\n \tstruct ident_filter *ident = xmalloc(sizeof(*ident));\n \n@@ -1944,6 +1967,7 @@ static struct stream_filter *ident_filter(const struct object_id *oid)\n \tstrbuf_init(&ident->left, 0);\n \tident->filter.vtbl = &ident_vtbl;\n \tident->state = 0;\n+\tident->idact = idact;\n \treturn (struct stream_filter *)ident;\n }\n \n@@ -1963,8 +1987,8 @@ struct stream_filter *get_stream_filter_ca(const struct conv_attrs *ca,\n \tif (classify_conv_attrs(ca) != CA_CLASS_STREAMABLE)\n \t\treturn NULL;\n \n-\tif (ca->ident)\n-\t\tfilter = ident_filter(oid);\n+\tif (ca->ident_action.id)\n+\t\tfilter = ident_filter(oid, &(ca->ident_action));\n \n \tif (output_eol(ca->crlf_action) == EOL_CRLF)\n \t\tfilter = cascade_filter(filter, lf_to_crlf_filter());\ndiff --git a/convert.h b/convert.h\nindex 5ee1c322058..2422e289784 100644\n--- a/convert.h\n+++ b/convert.h\n@@ -76,11 +76,16 @@ enum convert_crlf_action {\n \n struct convert_driver;\n \n+struct ident_action {\n+\tconst char *id;\n+\tsize_t id_len;\n+};\n+\n struct conv_attrs {\n \tstruct convert_driver *drv;\n \tenum convert_crlf_action attr_action; /* What attr says */\n \tenum convert_crlf_action crlf_action; /* When no attr is set, use core.autocrlf */\n-\tint ident;\n+\tstruct ident_action ident_action; /* What ident says */\n \tconst char *working_tree_encoding; /* Supported encoding or default encoding if NULL */\n };\n \ndiff --git a/parallel-checkout.c b/parallel-checkout.c\nindex ddc0ff3c064..b5908c299af 100644\n--- a/parallel-checkout.c\n+++ b/parallel-checkout.c\n@@ -91,7 +91,8 @@ static int is_eligible_for_parallel_checkout(const struct cache_entry *ce,\n \t\treturn 0;\n \n \tpacked_item_size = sizeof(struct pc_item_fixed_portion) + ce->ce_namelen +\n-\t\t(ca->working_tree_encoding ? strlen(ca->working_tree_encoding) : 0);\n+\t\t(ca->working_tree_encoding ? strlen(ca->working_tree_encoding) : 0) +\n+\t\tca->ident_action.id_len;\n \n \t/*\n \t * The amount of data we send to the workers per checkout item is\n@@ -403,13 +404,15 @@ static void send_one_item(int fd, struct parallel_checkout_item *pc_item)\n \tsize_t name_len = pc_item->ce->ce_namelen;\n \tsize_t working_tree_encoding_len = working_tree_encoding ?\n \t\t\t\t\t   strlen(working_tree_encoding) : 0;\n+\tconst char *ident_action_id = pc_item->ca.ident_action.id;\n+\tsize_t ident_action_len = pc_item->ca.ident_action.id_len;\n \n \t/*\n \t * Any changes in the calculation of the message size must also be made\n \t * in is_eligible_for_parallel_checkout().\n \t */\n \tlen_data = sizeof(struct pc_item_fixed_portion) + name_len +\n-\t\t   working_tree_encoding_len;\n+\t\t   working_tree_encoding_len + ident_action_len;\n \n \tdata = xmalloc(len_data);\n \n@@ -417,7 +420,7 @@ static void send_one_item(int fd, struct parallel_checkout_item *pc_item)\n \tfixed_portion->id = pc_item->id;\n \tfixed_portion->ce_mode = pc_item->ce->ce_mode;\n \tfixed_portion->crlf_action = pc_item->ca.crlf_action;\n-\tfixed_portion->ident = pc_item->ca.ident;\n+\tfixed_portion->ident_action_len = ident_action_len;\n \tfixed_portion->name_len = name_len;\n \tfixed_portion->working_tree_encoding_len = working_tree_encoding_len;\n \t/*\n@@ -434,6 +437,11 @@ static void send_one_item(int fd, struct parallel_checkout_item *pc_item)\n \t\tvariant += working_tree_encoding_len;\n \t}\n \tmemcpy(variant, pc_item->ce->name, name_len);\n+\tvariant += name_len;\n+\tif (ident_action_len) {\n+\t\tmemcpy(variant, ident_action_id, ident_action_len);\n+\t\tvariant += ident_action_len;\n+\t}\n \n \tpacket_write(fd, data, len_data);\n \ndiff --git a/parallel-checkout.h b/parallel-checkout.h\nindex 80f539bcb77..c3c282f516b 100644\n--- a/parallel-checkout.h\n+++ b/parallel-checkout.h\n@@ -76,9 +76,9 @@ struct parallel_checkout_item {\n \n /*\n  * The fixed-size portion of `struct parallel_checkout_item` that is sent to the\n- * workers. Following this will be 2 strings: ca.working_tree_encoding and\n- * ce.name; These are NOT null terminated, since we have the size in the fixed\n- * portion.\n+ * workers. Following this will be 3 strings: ca.working_tree_encoding, ca.name\n+ * and ca.ident_action.id; These are NOT null terminated, since we have the size\n+ * in the fixed portion.\n  *\n  * Note that not all fields of conv_attrs and cache_entry are passed, only the\n  * ones that will be required by the workers to smudge and write the entry.\n@@ -88,7 +88,7 @@ struct pc_item_fixed_portion {\n \tstruct object_id oid;\n \tunsigned int ce_mode;\n \tenum convert_crlf_action crlf_action;\n-\tint ident;\n+\tsize_t ident_action_len;\n \tsize_t working_tree_encoding_len;\n \tsize_t name_len;\n };\ndiff --git a/t/t0021-conversion.sh b/t/t0021-conversion.sh\nindex b5749f327dd..07df50a7e5d 100755\n--- a/t/t0021-conversion.sh\n+++ b/t/t0021-conversion.sh\n@@ -77,25 +77,29 @@ test_expect_success setup '\n \t{\n \t    echo \"*.t filter=rot13\"\n \t    echo \"*.i ident\"\n+\t    echo \"*.ci ident=customId\"\n \t} >.gitattributes &&\n \n \t{\n \t    echo a b c d e f g h i j k l m\n \t    echo n o p q r s t u v w x y z\n \t    echo '\\''$Id$'\\''\n+\t    echo '\\''$customId$'\\''\n \t} >test &&\n \tcat test >test.t &&\n \tcat test >test.o &&\n \tcat test >test.i &&\n-\tgit add test test.t test.i &&\n-\trm -f test test.t test.i &&\n-\tgit checkout -- test test.t test.i &&\n+\tcat test >test.ci &&\n+\tgit add test test.t test.i test.ci &&\n+\trm -f test test.t test.i test.ci &&\n+\tgit checkout -- test test.t test.i test.ci &&\n \n \techo \"content-test2\" >test2.o &&\n \techo \"content-test3 - filename with special characters\" >\"test3 '\\''sq'\\'',\\$x=.o\"\n '\n \n-script='s/^\\$Id: \\([0-9a-f]*\\) \\$/\\1/p'\n+script_i='s/^\\$Id: \\([0-9a-f]*\\) \\$/\\1/p'\n+script_ci='s/^\\$customId: \\([0-9a-f]*\\) \\$/\\1/p'\n \n test_expect_success check '\n \n@@ -105,8 +109,14 @@ test_expect_success check '\n \t# ident should be stripped in the repository\n \tgit diff --raw --exit-code :test :test.i &&\n \tid=$(git rev-parse --verify :test) &&\n-\tembedded=$(sed -ne \"$script\" test.i) &&\n+\tembedded=$(sed -ne \"$script_i\" test.i) &&\n+\tnembedded=$(sed -ne \"$script_ci\" test.i) &&\n \ttest \"z$id\" = \"z$embedded\" &&\n+\ttest \"z\" = \"z$nembedded\" &&\n+\tembedded=$(sed -ne \"$script_ci\" test.ci) &&\n+\tnembedded=$(sed -ne \"$script_i\" test.ci) &&\n+\ttest \"z$id\" = \"z$embedded\" &&\n+\ttest \"z\" = \"z$nembedded\" &&\n \n \tgit cat-file blob :test.t >test.r &&\n \n@@ -114,61 +124,84 @@ test_expect_success check '\n \ttest_cmp test.r test.t\n '\n \n+gen_expanded_keywords() {\n+\tlocal id=\"${1}\"\n+\techo \"File with expanded keywords\"\n+\techo \"\\$$id\\$\"\n+\techo \"\\$$id:\\$\"\n+\techo \"\\$$id: 0000000000000000000000000000000000000000 \\$\"\n+\techo \"\\$$id: NoSpaceAtEnd\\$\"\n+\techo \"\\$$id:NoSpaceAtFront \\$\"\n+\techo \"\\$$id:NoSpaceAtEitherEnd\\$\"\n+\techo \"\\$$id: NoTerminatingSymbol\"\n+\techo \"\\$$id: Foreign Commit With Spaces \\$\"\n+\tprintf \"\\$$id: NoTerminatingSymbolAtEOF\"\n+}\n+\n+gen_expected_output_0() {\n+\tlocal id=\"${1}\"\n+\tlocal hid=\"${2}\"\n+\techo \"File with expanded keywords\"\n+\techo \"\\$$id: $hid \\$\"\n+\techo \"\\$$id: $hid \\$\"\n+\techo \"\\$$id: $hid \\$\"\n+\techo \"\\$$id: $hid \\$\"\n+\techo \"\\$$id: $hid \\$\"\n+\techo \"\\$$id: $hid \\$\"\n+\techo \"\\$$id: NoTerminatingSymbol\"\n+\techo \"\\$$id: Foreign Commit With Spaces \\$\"\n+}\n+\n+gen_expected_output() {\n+\tlocal id=\"${1}\"\n+\tgen_expected_output_0 \"${@}\"\n+\tprintf \"\\$$id: NoTerminatingSymbolAtEOF\"\n+}\n+\n+gen_expected_output_crlf() {\n+\tlocal id=\"${1}\"\n+\tgen_expected_output_0 \"${@}\" | append_cr\n+\tprintf \"\\$$id: NoTerminatingSymbolAtEOF\"\n+}\n+\n # If an expanded ident ever gets into the repository, we want to make sure that\n # it is collapsed before being expanded again on checkout\n test_expect_success expanded_in_repo '\n-\t{\n-\t\techo \"File with expanded keywords\"\n-\t\techo \"\\$Id\\$\"\n-\t\techo \"\\$Id:\\$\"\n-\t\techo \"\\$Id: 0000000000000000000000000000000000000000 \\$\"\n-\t\techo \"\\$Id: NoSpaceAtEnd\\$\"\n-\t\techo \"\\$Id:NoSpaceAtFront \\$\"\n-\t\techo \"\\$Id:NoSpaceAtEitherEnd\\$\"\n-\t\techo \"\\$Id: NoTerminatingSymbol\"\n-\t\techo \"\\$Id: Foreign Commit With Spaces \\$\"\n-\t} >expanded-keywords.0 &&\n-\n-\t{\n-\t\tcat expanded-keywords.0 &&\n-\t\tprintf \"\\$Id: NoTerminatingSymbolAtEOF\"\n-\t} >expanded-keywords &&\n+\tgen_expanded_keywords Id >expanded-keywords &&\n+\tgen_expanded_keywords customId >expanded-keywords_ci &&\n \tcat expanded-keywords >expanded-keywords-crlf &&\n+\tcat expanded-keywords_ci >expanded-keywords-crlf_ci &&\n \tgit add expanded-keywords expanded-keywords-crlf &&\n+\tgit add expanded-keywords_ci expanded-keywords-crlf_ci &&\n \tgit commit -m \"File with keywords expanded\" &&\n \tid=$(git rev-parse --verify :expanded-keywords) &&\n+\tid_ci=$(git rev-parse --verify :expanded-keywords_ci) &&\n \n-\t{\n-\t\techo \"File with expanded keywords\"\n-\t\techo \"\\$Id: $id \\$\"\n-\t\techo \"\\$Id: $id \\$\"\n-\t\techo \"\\$Id: $id \\$\"\n-\t\techo \"\\$Id: $id \\$\"\n-\t\techo \"\\$Id: $id \\$\"\n-\t\techo \"\\$Id: $id \\$\"\n-\t\techo \"\\$Id: NoTerminatingSymbol\"\n-\t\techo \"\\$Id: Foreign Commit With Spaces \\$\"\n-\t} >expected-output.0 &&\n-\t{\n-\t\tcat expected-output.0 &&\n-\t\tprintf \"\\$Id: NoTerminatingSymbolAtEOF\"\n-\t} >expected-output &&\n-\t{\n-\t\tappend_cr <expected-output.0 &&\n-\t\tprintf \"\\$Id: NoTerminatingSymbolAtEOF\"\n-\t} >expected-output-crlf &&\n+\tgen_expected_output Id $id >expected-output &&\n+\tgen_expected_output customId $id_ci >expected-output_ci &&\n+\tgen_expected_output_crlf Id $id >expected-output-crlf &&\n+\tgen_expected_output_crlf customId $id_ci >expected-output-crlf_ci &&\n \t{\n \t\techo \"expanded-keywords ident\"\n+\t\techo \"expanded-keywords_ci ident=customId\"\n \t\techo \"expanded-keywords-crlf ident text eol=crlf\"\n+\t\techo \"expanded-keywords-crlf_ci ident=customId text eol=crlf\"\n \t} >>.gitattributes &&\n \n \trm -f expanded-keywords expanded-keywords-crlf &&\n+\trm -f expanded-keywords_ci expanded-keywords-crlf_ci &&\n \n \tgit checkout -- expanded-keywords &&\n \ttest_cmp expected-output expanded-keywords &&\n \n \tgit checkout -- expanded-keywords-crlf &&\n-\ttest_cmp expected-output-crlf expanded-keywords-crlf\n+\ttest_cmp expected-output-crlf expanded-keywords-crlf &&\n+\n+\tgit checkout -- expanded-keywords_ci &&\n+\ttest_cmp expected-output_ci expanded-keywords_ci &&\n+\n+\tgit checkout -- expanded-keywords-crlf_ci &&\n+\ttest_cmp expected-output-crlf_ci expanded-keywords-crlf_ci\n '\n \n # The use of %f in a filter definition is expanded to the path to\ndiff --git a/t/t2082-parallel-checkout-attributes.sh b/t/t2082-parallel-checkout-attributes.sh\nindex 25254579618..822957a8dc8 100755\n--- a/t/t2082-parallel-checkout-attributes.sh\n+++ b/t/t2082-parallel-checkout-attributes.sh\n@@ -20,16 +20,19 @@ test_expect_success 'parallel-checkout with ident' '\n \t(\n \t\tcd ident &&\n \t\techo \"A ident\" >.gitattributes &&\n+\t\techo \"C ident=MyCusomVeryLongAndWordyId\" >>.gitattributes &&\n \t\techo \"\\$Id\\$\" >A &&\n \t\techo \"\\$Id\\$\" >B &&\n+\t\techo \"\\$MyCusomVeryLongAndWordyId\\$\" >C &&\n \t\tgit add -A &&\n \t\tgit commit -m id &&\n \n-\t\trm A B &&\n+\t\trm A B C &&\n \t\ttest_checkout_workers 2 git reset --hard &&\n \t\thexsz=$(test_oid hexsz) &&\n \t\tgrep -E \"\\\\\\$Id: [0-9a-f]{$hexsz} \\\\\\$\" A &&\n-\t\tgrep \"\\\\\\$Id\\\\\\$\" B\n+\t\tgrep \"\\\\\\$Id\\\\\\$\" B &&\n+\t\tgrep -E \"\\\\\\$MyCusomVeryLongAndWordyId: [0-9a-f]{$hexsz} \\\\\\$\" C\n \t)\n '\n \n\nbase-commit: 225bc32a989d7a22fa6addafd4ce7dcd04675dbf\n-- \ngitgitgadget\n"},{"id":"434392","messageId":"xmqqfsuosvrh.fsf@gitster.g","threadId":"56354","inReplyTo":"CABFYoQC_FzbU_E4hU0kCz-WFJNOLspwL2Gjc01sMXDZosxJWjw@mail.gmail.com","subject":"Re: [PATCH v2] Make ident dynamic, not just a hardcoded value of \"$Id\".","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2021-09-01T05:35:46Z","receivedAt":"2021-09-01T05:35:53Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Maksym Sobolyev <sobomax@gmail.com> writes:\n\n> Junio, I've got rid of the  GIT_MAX_IDENT_LEN in rev3 posted here. You were\n> right it was not really necessary.\n\nSounds good.\n\n> Let me see if there are any more issues with the functional part of the\n> change, if not I'll see about changing the structure name from ident_action\n> to something more relevant.\n\nI do not think the \"structure\" should be relevant, not just its name\nbut its presence.  As I said already, the data (i.e. the name of the\ncustom token that replaces $Id$) should not come from the attribute\nsubsystem---it belongs to the config subsystem, I would think.\n\nThanks.\n\n"},{"id":"434481","messageId":"xmqqr1e77q01.fsf@gitster.g","threadId":"56354","inReplyTo":"20210826203713.45139-1-matheus.bernardino@usp.br","subject":"Re: [PATCH v2] Make ident dynamic, not just a hardcoded value of \"$Id\".","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2021-09-02T00:58:06Z","receivedAt":"2021-09-02T00:58:13Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Matheus Tavares <matheus.bernardino@usp.br> writes:\n\n> Hi, Maksym\n>\n> I haven't read the entire patch (and I don't normally use the ident feature),\n> but I left a few comments below: \n>\n> On Thu, Aug 26, 2021 at 1:28 AM Maksym Sobolyev via GitGitGadget <gitgitgadget@gmail.com> wrote:\n>>\n>> diff --git a/Documentation/gitattributes.txt b/Documentation/gitattributes.txt\n>> index 83fd4e19a4..9e486f3e8d 100644\n>> --- a/Documentation/gitattributes.txt\n>> +++ b/Documentation/gitattributes.txt\n>> @@ -382,6 +382,14 @@ sign `$` upon checkout.  Any byte sequence that begins with\n>>  `$Id:` and ends with `$` in the worktree file is replaced\n>>  with `$Id$` upon check-in.\n>>\n>> +The `ident` attribute can also provide an optional value,\n>> +which if supplied is going to be used for expansion instead of\n>> +the string `Id`.\n>> +\n>> +------------------------\n>> +*.[ch]         ident=FreeBSD\n>> +------------------------\n>\n> What happens if there is a ':' or '$' in the custom id name?\n\nIt is a valid question.  I actually think these two bytes should be\nexcluded from the custom ID string --- FWIW, I also do not think\nthere is any practical problem if we limited the set of characters\nto [A-Za-z0-9] and nothing else.\n\n> You might want to take a look at the other ident tests in\n> t0021-conversion.sh and perhaps addapt/copy some of them to ensure that\n> the expected behavior persists when using a custom id name. For example,\n> quickly changing the \"$Id:\" references to \"$customId:\" and replacing\n> \"ident\" with \"ident=customId\", I seem to get at least one test failure:\n>\n> --- expected-output     2021-08-26 19:40:07.181596662 +0000\n> +++ expanded-keywords   2021-08-26 19:40:07.188263463 +0000\n> @@ -6,5 +6,5 @@\n>  $customId: bebd07c752ffffd6779e1056db5de66c3bb733ed $\n>  $customId: bebd07c752ffffd6779e1056db5de66c3bb733ed $\n>  $customId: NoTerminatingSymbol\n> -$customId: Foreign Commit With Spaces $\n> +$customId: bebd07c752ffffd6779e1056db5de66c3bb733ed $\n>  $customId: NoTerminatingSymbolAtEOF\n> \\ No newline at end of file\n> error: last command exited with $?=1\n> not ok 3 - expanded_in_repo\n>\n> (But I haven't dig further, and this was just a quick test, so it could be my\n> fault.)\n\nIf we are not restricting the characters that can be used in the\ncustom ID placeholder, we probably should have tests that use\nallowed but unusual characters.\n\nThanks.\n"},{"id":"434484","messageId":"YTBHk+cS7WluKGL2@danh.dev","threadId":"56354","inReplyTo":"pull.1074.v3.git.git.1630462385587.gitgitgadget@gmail.com","subject":"Re: [PATCH v3] Make ident dynamic, not just a hardcoded value of \"$Id\".","fromName":"Đoàn Trần Công Danh","fromEmail":"congdanhqx@gmail.com","sentAt":"2021-09-02T03:40:18Z","receivedAt":"2021-09-02T03:40:23Z","isPatch":true,"sender":{"key":"congdanhqx@gmail.com","avatar":"https://avatars.githubusercontent.com/u/42673067?v=4"},"body":"On 2021-09-01 02:13:05+0000, Maksym Sobolyev via GitGitGadget <gitgitgadget@gmail.com> wrote:\n> diff --git a/convert.h b/convert.h\n> index 5ee1c322058..2422e289784 100644\n> --- a/convert.h\n> +++ b/convert.h\n> @@ -76,11 +76,16 @@ enum convert_crlf_action {\n>  \n>  struct convert_driver;\n>  \n> +struct ident_action {\n> +\tconst char *id;\n> +\tsize_t id_len;\n> +};\n\nJust out of curiousity, can we use strbuf instead of inventing new\nstructure for this one? This struct is purely a string representation?\n\n\n-- \nDanh\n"},{"id":"434591","messageId":"xmqqmtoukdec.fsf@gitster.g","threadId":"56354","inReplyTo":"xmqqr1e77q01.fsf@gitster.g","subject":"Re: [PATCH v2] Make ident dynamic, not just a hardcoded value of \"$Id\".","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2021-09-02T19:04:11Z","receivedAt":"2021-09-02T19:04:16Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Junio C Hamano <gitster@pobox.com> writes:\n\n> If we are not restricting the characters that can be used in the\n> custom ID placeholder, we probably should have tests that use\n> allowed but unusual characters.\n\nThere is a test in t/t0021-conversion.sh that ensures that we\nrecognise expanded and unexpanded keyword strings using various\nlines that has placeholder-lookalike on it, which starts like this:\n\n    # If an expanded ident ever gets into the repository, we want to make sure that\n    # it is collapsed before being expanded again on checkout\n    test_expect_success expanded_in_repo '\n            {\n                    echo \"File with expanded keywords\"\n                    echo \"\\$Id\\$\"\n                    echo \"\\$Id:\\$\"\n                    echo \"\\$Id: 0000000000000000000000000000000000000000 \\$\"\n                    echo \"\\$Id: NoSpaceAtEnd\\$\"\n                    echo \"\\$Id:NoSpaceAtFront \\$\"\n                    echo \"\\$Id:NoSpaceAtEitherEnd\\$\"\n                    echo \"\\$Id: NoTerminatingSymbol\"\n                    echo \"\\$Id: Foreign Commit With Spaces \\$\"\n            } >expanded-keywords.0 &&\n\t    ...\n\nIt would be good to have a copy of this test with the placeholder\ncustomized to something unusual like \" Id:\", \"Id: $ $\", \":\", \"$ $\"\netc. to make sure we can parse out the keyword correctly without\nimposing the letters that can be used in the placeholder.\n\n    Side note.  I of course do not literally mean to \"copy\" and make\n    the number of tests explode.  I think it can be done with a loop\n    to test various custom strings, which may begin like ...\n\n\tfor kwd in \"Id\" \" Id:\" \"Id $\" \"Id: $ $\" \":\" \"$ $\"\n\tdo\n\t\tcat >expanded-keywords.0 <<-EOF &&\n\t\t\\$$kwd\\$\n\t\t\\$$kwd:\\$\n\t\t\\$$kwd: 0000000000000000000000000000000000000000 \\$\n\t\t\\$$kwd: NoSpaceAtEnd\\$\n\t\t\\$$kwd:NoSpaceAtFront \\$\n\t\t\\$$kwd:NoSpaceAtEitherEnd\\$\n\t\t\\$$kwd: NoTerminatingSymbol\n\t\t\\$$kwd: Foreign Commit With Spaces \\$\n\t\tEOF\n\t\t...\n\nIf we can do without restriction, that would be even better, but I\ndo not think it is the end of the world if we found some corner case\nthat a certain customized placeholder can make the syntax ambiguous\nand we cannot recognise expanded keywords in the working tree files,\nas it is reasonable to limit the letters we allow in placeholders.\n\nOne thing we should not do is to tell users that they can use\nanything as their customized Id string, and then later find such a\ncorner case and tell them now we forbid certain letters.  To avoid\nsuch a mistake, it is better to start reasonably tight, allowing\nwhat we know will never cause problems (like alphanumerics) and\nforbidding what we know will not be missed in real-world usecases\n(like whitespaces and punctuations).  After we discover that certain\nthings that we initially forbid have real uses in the future, we can\nloosen the restriction.  \"Start loose and then tighten\" will lead to\na regression when viewed from the end-user's workflow.  \"Start tight\nand then loosen\" will not have that problem.\n"}]}