{"thread":{"id":"55371","subject":"[PATCH] [GSOC]trailer: change $ARG to environment variable","startedAt":"2021-03-23T14:54:20Z","lastAt":"2021-05-03T15:41:17Z","messageCount":101,"participants":["ZheNing Hu via GitGitGadget","Junio C Hamano","ZheNing Hu","Christian Couder"],"isPatch":true,"patchVersion":1,"patchTotal":null},"messages":[{"id":"420037","messageId":"pull.913.git.1616511182942.gitgitgadget@gmail.com","threadId":"55371","inReplyTo":null,"subject":"[PATCH] [GSOC]trailer: change $ARG to environment variable","fromName":"ZheNing Hu via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2021-03-23T14:53:02Z","receivedAt":"2021-03-23T14:54:20Z","isPatch":true,"sender":{"key":"adlternative@gmail.com","avatar":"https://avatars.githubusercontent.com/u/58138461?v=4"},"body":"From: ZheNing Hu <adlternative@gmail.com>\n\nIn the original implementation of `trailer.<token>.command`,\nuse `strbuf_replace` to replace `$ARG` in the <value> of the trailer,\nbut it have a problem:`strbuf_replace` replace the `$ARG` in command\nonly once, If the user's trailer command have used more then one `$ARG`,\nerror will occur.\n\nSo pass `$ARG` as an environment variable to the trailer command,\nall `$ARG` in the command will be replaced, which will fix this problem.\n\nSigned-off-by: ZheNing Hu <adlternative@gmail.com>\n---\n    [GSOC] trailer: change $ARG to environment variable\n    \n    In https://lore.kernel.org/git/xmqqv99i4ck2.fsf@gitster.g/ Junio and\n    Christian talked about the problem of using strbuf_replace() to replace\n    $ARG.\n    \n    The current new solution is to pass $ARG as an environment variable into\n    the command.\n\nPublished-As: https://github.com/gitgitgadget/git/releases/tag/pr-913%2Fadlternative%2Ftrailer-pass-ARG-env-v1\nFetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-913/adlternative/trailer-pass-ARG-env-v1\nPull-Request: https://github.com/gitgitgadget/git/pull/913\n\n Documentation/git-interpret-trailers.txt |  3 ++-\n t/t7513-interpret-trailers.sh            | 17 +++++++++++++++++\n trailer.c                                |  9 ++++++---\n 3 files changed, 25 insertions(+), 4 deletions(-)\n\ndiff --git a/Documentation/git-interpret-trailers.txt b/Documentation/git-interpret-trailers.txt\nindex 96ec6499f001..7cf7c032a0e9 100644\n--- a/Documentation/git-interpret-trailers.txt\n+++ b/Documentation/git-interpret-trailers.txt\n@@ -242,7 +242,8 @@ line, where <value> is taken to be the standard output of the\n specified command with any leading and trailing whitespace trimmed\n off.\n +\n-If the command contains the `$ARG` string, this string will be\n+If the command contains the `$ARG` string (`$ARG` is an exported\n+environment variable), this string will be\n replaced with the <value> part of an existing trailer with the same\n <token>, if any, before the command is launched.\n +\ndiff --git a/t/t7513-interpret-trailers.sh b/t/t7513-interpret-trailers.sh\nindex 6602790b5f4c..d303cf0e4b36 100755\n--- a/t/t7513-interpret-trailers.sh\n+++ b/t/t7513-interpret-trailers.sh\n@@ -1291,6 +1291,23 @@ test_expect_success 'with command using $ARG' '\n \ttest_cmp expected actual\n '\n \n+test_expect_success 'with command using more than one $ARG' '\n+\tgit config trailer.fix.ifExists \"replace\" &&\n+\tgit config trailer.fix.command \"test -n $ARG && git log -1 --oneline --format=\\\"%h (%s)\\\" --abbrev-commit --abbrev=14 \\$ARG || true\" &&\n+\tFIXED=$(git log -1 --oneline --format=\"%h (%s)\" --abbrev-commit --abbrev=14 HEAD) &&\n+\tcat complex_message_body >expected &&\n+\tsed -e \"s/ Z\\$/ /\" >>expected <<-EOF &&\n+\t\tFixes: $FIXED\n+\t\tAcked-by= Z\n+\t\tReviewed-by:\n+\t\tSigned-off-by: Z\n+\t\tSigned-off-by: A U Thor <author@example.com>\n+\tEOF\n+\tgit interpret-trailers --trailer \"review:\" --trailer \"fix=HEAD\" \\\n+\t\t<complex_message >actual &&\n+\ttest_cmp expected actual\n+'\n+\n test_expect_success 'with failing command using $ARG' '\n \tgit config trailer.fix.ifExists \"replace\" &&\n \tgit config trailer.fix.command \"false \\$ARG\" &&\ndiff --git a/trailer.c b/trailer.c\nindex be4e9726421c..42e3b818327a 100644\n--- a/trailer.c\n+++ b/trailer.c\n@@ -44,7 +44,7 @@ static char *separators = \":\";\n \n static int configured;\n \n-#define TRAILER_ARG_STRING \"$ARG\"\n+#define TRAILER_ARG_STRING \"ARG\"\n \n static const char *git_generated_prefixes[] = {\n \t\"Signed-off-by: \",\n@@ -222,13 +222,16 @@ static char *apply_command(const char *command, const char *arg)\n \tstruct strbuf buf = STRBUF_INIT;\n \tstruct child_process cp = CHILD_PROCESS_INIT;\n \tchar *result;\n+\tconst char *const *var;\n \n \tstrbuf_addstr(&cmd, command);\n+\tfor (var = local_repo_env; *var; var++)\n+\t\tstrvec_push(&cp.env_array, *var);\n \tif (arg)\n-\t\tstrbuf_replace(&cmd, TRAILER_ARG_STRING, arg);\n+\t\tstrvec_pushf(&cp.env_array, \"%s=%s\", TRAILER_ARG_STRING, arg);\n \n \tstrvec_push(&cp.args, cmd.buf);\n-\tcp.env = local_repo_env;\n+\n \tcp.no_stdin = 1;\n \tcp.use_shell = 1;\n \n\nbase-commit: 142430338477d9d1bb25be66267225fb58498d92\n-- \ngitgitgadget\n"},{"id":"420118","messageId":"pull.913.v2.git.1616600555906.gitgitgadget@gmail.com","threadId":"55371","inReplyTo":"pull.913.git.1616511182942.gitgitgadget@gmail.com","subject":"[PATCH v2] [GSOC]trailer: pass arg as positional parameter","fromName":"ZheNing Hu via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2021-03-24T15:42:35Z","receivedAt":"2021-03-24T15:43:24Z","isPatch":true,"sender":{"key":"adlternative@gmail.com","avatar":"https://avatars.githubusercontent.com/u/58138461?v=4"},"body":"From: ZheNing Hu <adlternative@gmail.com>\n\nIn the original implementation of `trailer.<token>.command`,\nuse `strbuf_replace` to replace $ARG in the <value> of the\ntrailer, but it have a problem: `strbuf_replace` replace the\n$ARG in command only once, If the user's trailer command have\nused more than one $ARG, error will occur.\n\nIf directly modify the implementation of the original\n`trailer.<token>.command`, The user’s previous `'$ARG'` in\ntrailer command will not be replaced. So now add new\nconfig \"trailer.<token>.cmd\", pass trailer's value as\npositional parameter 1 to the user's command, users can\nuse $1 as trailer's value, to implement original variable\nreplacement.\n\nOriginal `trailer.<token>.command` can still be used until git\ncompletely abandoned it.\n\nSigned-off-by: ZheNing Hu <adlternative@gmail.com>\n---\n    [GSOC]trailer: pass arg as positional parameter\n    \n    In https://lore.kernel.org/git/xmqqv99i4ck2.fsf@gitster.g/ Junio and\n    Christian talked about the problem of using strbuf_replace() to replace\n    $ARG.\n    \n    Now pass trailer value as $1 to the trailer command with another\n    trailer.<token>.cmd config.\n\nPublished-As: https://github.com/gitgitgadget/git/releases/tag/pr-913%2Fadlternative%2Ftrailer-pass-ARG-env-v2\nFetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-913/adlternative/trailer-pass-ARG-env-v2\nPull-Request: https://github.com/gitgitgadget/git/pull/913\n\nRange-diff vs v1:\n\n 1:  abc5b04d152f ! 1:  185356d6fc90 [GSOC]trailer: change $ARG to environment variable\n     @@ Metadata\n      Author: ZheNing Hu <adlternative@gmail.com>\n      \n       ## Commit message ##\n     -    [GSOC]trailer: change $ARG to environment variable\n     +    [GSOC]trailer: pass arg as positional parameter\n      \n          In the original implementation of `trailer.<token>.command`,\n     -    use `strbuf_replace` to replace `$ARG` in the <value> of the trailer,\n     -    but it have a problem:`strbuf_replace` replace the `$ARG` in command\n     -    only once, If the user's trailer command have used more then one `$ARG`,\n     -    error will occur.\n     +    use `strbuf_replace` to replace $ARG in the <value> of the\n     +    trailer, but it have a problem: `strbuf_replace` replace the\n     +    $ARG in command only once, If the user's trailer command have\n     +    used more than one $ARG, error will occur.\n      \n     -    So pass `$ARG` as an environment variable to the trailer command,\n     -    all `$ARG` in the command will be replaced, which will fix this problem.\n     +    If directly modify the implementation of the original\n     +    `trailer.<token>.command`, The user’s previous `'$ARG'` in\n     +    trailer command will not be replaced. So now add new\n     +    config \"trailer.<token>.cmd\", pass trailer's value as\n     +    positional parameter 1 to the user's command, users can\n     +    use $1 as trailer's value, to implement original variable\n     +    replacement.\n     +\n     +    Original `trailer.<token>.command` can still be used until git\n     +    completely abandoned it.\n      \n          Signed-off-by: ZheNing Hu <adlternative@gmail.com>\n      \n       ## Documentation/git-interpret-trailers.txt ##\n     -@@ Documentation/git-interpret-trailers.txt: line, where <value> is taken to be the standard output of the\n     - specified command with any leading and trailing whitespace trimmed\n     - off.\n     - +\n     --If the command contains the `$ARG` string, this string will be\n     -+If the command contains the `$ARG` string (`$ARG` is an exported\n     -+environment variable), this string will be\n     - replaced with the <value> part of an existing trailer with the same\n     - <token>, if any, before the command is launched.\n     - +\n     +@@ Documentation/git-interpret-trailers.txt: also be executed for each of these arguments. And the <value> part of\n     + these arguments, if any, will be used to replace the `$ARG` string in\n     + the command.\n     + \n     ++trailer.<token>.cmd::\n     ++\tSimilar to 'trailer.<token>.command'. But the difference is that\n     ++\t`$1` is used in the command to replace the value of the trailer\n     ++\tinstead of the original `$ARG`, which means that we can quote the\n     ++\ttrailer value multiple times in the command.\n     ++\tE.g. `trailer.sign.cmd=\"test -n \\\"$1\\\" && echo \\\"$1\\\" || true \"`\n     ++\n     + EXAMPLES\n     + --------\n     + \n      \n       ## t/t7513-interpret-trailers.sh ##\n     -@@ t/t7513-interpret-trailers.sh: test_expect_success 'with command using $ARG' '\n     - \ttest_cmp expected actual\n     +@@ t/t7513-interpret-trailers.sh: test_expect_success 'setup a commit' '\n     + \tgit commit -m \"Add file a.txt\"\n       '\n       \n     -+test_expect_success 'with command using more than one $ARG' '\n     ++test_expect_success 'with cmd using $1' '\n     ++\ttest_when_finished \"git config --unset trailer.fix.cmd\" &&\n      +\tgit config trailer.fix.ifExists \"replace\" &&\n     -+\tgit config trailer.fix.command \"test -n $ARG && git log -1 --oneline --format=\\\"%h (%s)\\\" --abbrev-commit --abbrev=14 \\$ARG || true\" &&\n     ++\tgit config trailer.fix.cmd \"test -n \\\"\\$1\\\" && git log -1 --oneline --format=\\\"%h (%s)\\\" \\\n     ++\t\t--abbrev-commit --abbrev=14 \\\"\\$1\\\" || true\" &&\n      +\tFIXED=$(git log -1 --oneline --format=\"%h (%s)\" --abbrev-commit --abbrev=14 HEAD) &&\n     -+\tcat complex_message_body >expected &&\n     -+\tsed -e \"s/ Z\\$/ /\" >>expected <<-EOF &&\n     ++\tcat complex_message_body >expected2 &&\n     ++\tsed -e \"s/ Z\\$/ /\" >>expected2 <<-EOF &&\n      +\t\tFixes: $FIXED\n      +\t\tAcked-by= Z\n      +\t\tReviewed-by:\n     @@ t/t7513-interpret-trailers.sh: test_expect_success 'with command using $ARG' '\n      +\t\tSigned-off-by: A U Thor <author@example.com>\n      +\tEOF\n      +\tgit interpret-trailers --trailer \"review:\" --trailer \"fix=HEAD\" \\\n     -+\t\t<complex_message >actual &&\n     -+\ttest_cmp expected actual\n     ++\t\t<complex_message >actual2 &&\n     ++\ttest_cmp expected2 actual2\n      +'\n      +\n     - test_expect_success 'with failing command using $ARG' '\n     + test_expect_success 'with command using $ARG' '\n       \tgit config trailer.fix.ifExists \"replace\" &&\n     - \tgit config trailer.fix.command \"false \\$ARG\" &&\n     +-\tgit config trailer.fix.command \"git log -1 --oneline --format=\\\"%h (%s)\\\" --abbrev-commit --abbrev=14 \\$ARG\" &&\n     ++\tgit config trailer.fix.command \"git log -1 --oneline --format=\\\"%h (%s)\\\" \\\n     ++\t\t--abbrev-commit --abbrev=14 \\$ARG\" &&\n     + \tFIXED=$(git log -1 --oneline --format=\"%h (%s)\" --abbrev-commit --abbrev=14 HEAD) &&\n     + \tcat complex_message_body >expected &&\n     + \tsed -e \"s/ Z\\$/ /\" >>expected <<-EOF &&\n      \n       ## trailer.c ##\n     -@@ trailer.c: static char *separators = \":\";\n     - \n     - static int configured;\n     - \n     --#define TRAILER_ARG_STRING \"$ARG\"\n     -+#define TRAILER_ARG_STRING \"ARG\"\n     +@@ trailer.c: struct conf_info {\n     + \tchar *name;\n     + \tchar *key;\n     + \tchar *command;\n     ++\tint is_new_cmd;\n     + \tenum trailer_where where;\n     + \tenum trailer_if_exists if_exists;\n     + \tenum trailer_if_missing if_missing;\n     +@@ trailer.c: static int check_if_different(struct trailer_item *in_tok,\n     + \treturn 1;\n     + }\n       \n     - static const char *git_generated_prefixes[] = {\n     - \t\"Signed-off-by: \",\n     -@@ trailer.c: static char *apply_command(const char *command, const char *arg)\n     +-static char *apply_command(const char *command, const char *arg)\n     ++static char *apply_command(const char *command, int is_new_cmd , const char *arg)\n     + {\n     + \tstruct strbuf cmd = STRBUF_INIT;\n       \tstruct strbuf buf = STRBUF_INIT;\n     - \tstruct child_process cp = CHILD_PROCESS_INIT;\n     +@@ trailer.c: static char *apply_command(const char *command, const char *arg)\n       \tchar *result;\n     -+\tconst char *const *var;\n       \n       \tstrbuf_addstr(&cmd, command);\n     -+\tfor (var = local_repo_env; *var; var++)\n     -+\t\tstrvec_push(&cp.env_array, *var);\n     - \tif (arg)\n     +-\tif (arg)\n      -\t\tstrbuf_replace(&cmd, TRAILER_ARG_STRING, arg);\n     -+\t\tstrvec_pushf(&cp.env_array, \"%s=%s\", TRAILER_ARG_STRING, arg);\n     - \n     +-\n       \tstrvec_push(&cp.args, cmd.buf);\n     --\tcp.env = local_repo_env;\n     -+\n     ++\tif (arg) {\n     ++\t\tif (is_new_cmd)\n     ++\t\t\tstrvec_push(&cp.args, arg);\n     ++\t\telse\n     ++\t\t\tstrbuf_replace(&cmd, TRAILER_ARG_STRING, arg);\n     ++\t}\n     + \tcp.env = local_repo_env;\n       \tcp.no_stdin = 1;\n       \tcp.use_shell = 1;\n     +@@ trailer.c: static void apply_item_command(struct trailer_item *in_tok, struct arg_item *arg\n     + \t\t\telse\n     + \t\t\t\targ = xstrdup(\"\");\n     + \t\t}\n     +-\t\targ_tok->value = apply_command(arg_tok->conf.command, arg);\n     ++\t\targ_tok->value = apply_command(arg_tok->conf.command, arg_tok->conf.is_new_cmd, arg);\n     + \t\tfree((char *)arg);\n     + \t}\n     + }\n     +@@ trailer.c: static struct arg_item *get_conf_item(const char *name)\n     + \treturn item;\n     + }\n     + \n     +-enum trailer_info_type { TRAILER_KEY, TRAILER_COMMAND, TRAILER_WHERE,\n     +-\t\t\t TRAILER_IF_EXISTS, TRAILER_IF_MISSING };\n     ++enum trailer_info_type { TRAILER_KEY, TRAILER_COMMAND, TRAILER_CMD,\n     ++\t\t\tTRAILER_WHERE, TRAILER_IF_EXISTS, TRAILER_IF_MISSING };\n       \n     + static struct {\n     + \tconst char *name;\n     +@@ trailer.c: static struct {\n     + } trailer_config_items[] = {\n     + \t{ \"key\", TRAILER_KEY },\n     + \t{ \"command\", TRAILER_COMMAND },\n     ++\t{ \"cmd\", TRAILER_CMD },\n     + \t{ \"where\", TRAILER_WHERE },\n     + \t{ \"ifexists\", TRAILER_IF_EXISTS },\n     + \t{ \"ifmissing\", TRAILER_IF_MISSING }\n     +@@ trailer.c: static int git_trailer_config(const char *conf_key, const char *value, void *cb)\n     + \tcase TRAILER_COMMAND:\n     + \t\tif (conf->command)\n     + \t\t\twarning(_(\"more than one %s\"), conf_key);\n     ++\t\tconf->is_new_cmd = 0;\n     ++\t\tconf->command = xstrdup(value);\n     ++\t\tbreak;\n     ++\tcase TRAILER_CMD:\n     ++\t\tif (conf->command)\n     ++\t\t\twarning(_(\"more than one %s\"), conf_key);\n     ++\t\tconf->is_new_cmd = 1;\n     + \t\tconf->command = xstrdup(value);\n     + \t\tbreak;\n     + \tcase TRAILER_WHERE:\n\n\n Documentation/git-interpret-trailers.txt |  7 +++++++\n t/t7513-interpret-trailers.sh            | 22 +++++++++++++++++++-\n trailer.c                                | 26 +++++++++++++++++-------\n 3 files changed, 47 insertions(+), 8 deletions(-)\n\ndiff --git a/Documentation/git-interpret-trailers.txt b/Documentation/git-interpret-trailers.txt\nindex 96ec6499f001..38656b1b3841 100644\n--- a/Documentation/git-interpret-trailers.txt\n+++ b/Documentation/git-interpret-trailers.txt\n@@ -252,6 +252,13 @@ also be executed for each of these arguments. And the <value> part of\n these arguments, if any, will be used to replace the `$ARG` string in\n the command.\n \n+trailer.<token>.cmd::\n+\tSimilar to 'trailer.<token>.command'. But the difference is that\n+\t`$1` is used in the command to replace the value of the trailer\n+\tinstead of the original `$ARG`, which means that we can quote the\n+\ttrailer value multiple times in the command.\n+\tE.g. `trailer.sign.cmd=\"test -n \\\"$1\\\" && echo \\\"$1\\\" || true \"`\n+\n EXAMPLES\n --------\n \ndiff --git a/t/t7513-interpret-trailers.sh b/t/t7513-interpret-trailers.sh\nindex 6602790b5f4c..7cb81201442a 100755\n--- a/t/t7513-interpret-trailers.sh\n+++ b/t/t7513-interpret-trailers.sh\n@@ -1274,9 +1274,29 @@ test_expect_success 'setup a commit' '\n \tgit commit -m \"Add file a.txt\"\n '\n \n+test_expect_success 'with cmd using $1' '\n+\ttest_when_finished \"git config --unset trailer.fix.cmd\" &&\n+\tgit config trailer.fix.ifExists \"replace\" &&\n+\tgit config trailer.fix.cmd \"test -n \\\"\\$1\\\" && git log -1 --oneline --format=\\\"%h (%s)\\\" \\\n+\t\t--abbrev-commit --abbrev=14 \\\"\\$1\\\" || true\" &&\n+\tFIXED=$(git log -1 --oneline --format=\"%h (%s)\" --abbrev-commit --abbrev=14 HEAD) &&\n+\tcat complex_message_body >expected2 &&\n+\tsed -e \"s/ Z\\$/ /\" >>expected2 <<-EOF &&\n+\t\tFixes: $FIXED\n+\t\tAcked-by= Z\n+\t\tReviewed-by:\n+\t\tSigned-off-by: Z\n+\t\tSigned-off-by: A U Thor <author@example.com>\n+\tEOF\n+\tgit interpret-trailers --trailer \"review:\" --trailer \"fix=HEAD\" \\\n+\t\t<complex_message >actual2 &&\n+\ttest_cmp expected2 actual2\n+'\n+\n test_expect_success 'with command using $ARG' '\n \tgit config trailer.fix.ifExists \"replace\" &&\n-\tgit config trailer.fix.command \"git log -1 --oneline --format=\\\"%h (%s)\\\" --abbrev-commit --abbrev=14 \\$ARG\" &&\n+\tgit config trailer.fix.command \"git log -1 --oneline --format=\\\"%h (%s)\\\" \\\n+\t\t--abbrev-commit --abbrev=14 \\$ARG\" &&\n \tFIXED=$(git log -1 --oneline --format=\"%h (%s)\" --abbrev-commit --abbrev=14 HEAD) &&\n \tcat complex_message_body >expected &&\n \tsed -e \"s/ Z\\$/ /\" >>expected <<-EOF &&\ndiff --git a/trailer.c b/trailer.c\nindex be4e9726421c..80f47657ff1a 100644\n--- a/trailer.c\n+++ b/trailer.c\n@@ -14,6 +14,7 @@ struct conf_info {\n \tchar *name;\n \tchar *key;\n \tchar *command;\n+\tint is_new_cmd;\n \tenum trailer_where where;\n \tenum trailer_if_exists if_exists;\n \tenum trailer_if_missing if_missing;\n@@ -216,7 +217,7 @@ static int check_if_different(struct trailer_item *in_tok,\n \treturn 1;\n }\n \n-static char *apply_command(const char *command, const char *arg)\n+static char *apply_command(const char *command, int is_new_cmd , const char *arg)\n {\n \tstruct strbuf cmd = STRBUF_INIT;\n \tstruct strbuf buf = STRBUF_INIT;\n@@ -224,10 +225,13 @@ static char *apply_command(const char *command, const char *arg)\n \tchar *result;\n \n \tstrbuf_addstr(&cmd, command);\n-\tif (arg)\n-\t\tstrbuf_replace(&cmd, TRAILER_ARG_STRING, arg);\n-\n \tstrvec_push(&cp.args, cmd.buf);\n+\tif (arg) {\n+\t\tif (is_new_cmd)\n+\t\t\tstrvec_push(&cp.args, arg);\n+\t\telse\n+\t\t\tstrbuf_replace(&cmd, TRAILER_ARG_STRING, arg);\n+\t}\n \tcp.env = local_repo_env;\n \tcp.no_stdin = 1;\n \tcp.use_shell = 1;\n@@ -257,7 +261,7 @@ static void apply_item_command(struct trailer_item *in_tok, struct arg_item *arg\n \t\t\telse\n \t\t\t\targ = xstrdup(\"\");\n \t\t}\n-\t\targ_tok->value = apply_command(arg_tok->conf.command, arg);\n+\t\targ_tok->value = apply_command(arg_tok->conf.command, arg_tok->conf.is_new_cmd, arg);\n \t\tfree((char *)arg);\n \t}\n }\n@@ -454,8 +458,8 @@ static struct arg_item *get_conf_item(const char *name)\n \treturn item;\n }\n \n-enum trailer_info_type { TRAILER_KEY, TRAILER_COMMAND, TRAILER_WHERE,\n-\t\t\t TRAILER_IF_EXISTS, TRAILER_IF_MISSING };\n+enum trailer_info_type { TRAILER_KEY, TRAILER_COMMAND, TRAILER_CMD,\n+\t\t\tTRAILER_WHERE, TRAILER_IF_EXISTS, TRAILER_IF_MISSING };\n \n static struct {\n \tconst char *name;\n@@ -463,6 +467,7 @@ static struct {\n } trailer_config_items[] = {\n \t{ \"key\", TRAILER_KEY },\n \t{ \"command\", TRAILER_COMMAND },\n+\t{ \"cmd\", TRAILER_CMD },\n \t{ \"where\", TRAILER_WHERE },\n \t{ \"ifexists\", TRAILER_IF_EXISTS },\n \t{ \"ifmissing\", TRAILER_IF_MISSING }\n@@ -540,6 +545,13 @@ static int git_trailer_config(const char *conf_key, const char *value, void *cb)\n \tcase TRAILER_COMMAND:\n \t\tif (conf->command)\n \t\t\twarning(_(\"more than one %s\"), conf_key);\n+\t\tconf->is_new_cmd = 0;\n+\t\tconf->command = xstrdup(value);\n+\t\tbreak;\n+\tcase TRAILER_CMD:\n+\t\tif (conf->command)\n+\t\t\twarning(_(\"more than one %s\"), conf_key);\n+\t\tconf->is_new_cmd = 1;\n \t\tconf->command = xstrdup(value);\n \t\tbreak;\n \tcase TRAILER_WHERE:\n\nbase-commit: 142430338477d9d1bb25be66267225fb58498d92\n-- \ngitgitgadget\n"},{"id":"420143","messageId":"xmqqft0kxq4m.fsf@gitster.g","threadId":"55371","inReplyTo":"pull.913.v2.git.1616600555906.gitgitgadget@gmail.com","subject":"Re: [PATCH v2] [GSOC]trailer: pass arg as positional parameter","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2021-03-24T20:18:01Z","receivedAt":"2021-03-24T20:18:55Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"\"ZheNing Hu via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n\n> From: ZheNing Hu <adlternative@gmail.com>\n>\n> In the original implementation of `trailer.<token>.command`,\n> use `strbuf_replace` to replace $ARG in the <value> of the\n> trailer, but it have a problem: `strbuf_replace` replace the\n> $ARG in command only once, If the user's trailer command have\n> used more than one $ARG, error will occur.\n>\n> If directly modify the implementation of the original\n> `trailer.<token>.command`, The user’s previous `'$ARG'` in\n> trailer command will not be replaced. So now add new\n> config \"trailer.<token>.cmd\", pass trailer's value as\n> positional parameter 1 to the user's command, users can\n> use $1 as trailer's value, to implement original variable\n> replacement.\n>\n> Original `trailer.<token>.command` can still be used until git\n> completely abandoned it.\n\nSorry, but that's quite an ungrammatical mess X-<.\n\n>  1:  abc5b04d152f ! 1:  185356d6fc90 [GSOC]trailer: change $ARG to environment variable\n>      @@ Metadata\n>       Author: ZheNing Hu <adlternative@gmail.com>\n\nAs this is completely a different design and does not share anything\nwith the earlier round, the range-diff is merely distracting and\nuseless.\n\n>  Documentation/git-interpret-trailers.txt |  7 +++++++\n>  t/t7513-interpret-trailers.sh            | 22 +++++++++++++++++++-\n>  trailer.c                                | 26 +++++++++++++++++-------\n>  3 files changed, 47 insertions(+), 8 deletions(-)\n>\n> diff --git a/Documentation/git-interpret-trailers.txt b/Documentation/git-interpret-trailers.txt\n> index 96ec6499f001..38656b1b3841 100644\n> --- a/Documentation/git-interpret-trailers.txt\n> +++ b/Documentation/git-interpret-trailers.txt\n> @@ -252,6 +252,13 @@ also be executed for each of these arguments. And the <value> part of\n>  these arguments, if any, will be used to replace the `$ARG` string in\n>  the command.\n>  \n> +trailer.<token>.cmd::\n> +\tSimilar to 'trailer.<token>.command'. But the difference is that\n> +\t`$1` is used in the command to replace the value of the trailer\n> +\tinstead of the original `$ARG`, which means that we can quote the\n\n\"quote\"?\n\n> +\ttrailer value multiple times in the command.\n> +\tE.g. `trailer.sign.cmd=\"test -n \\\"$1\\\" && echo \\\"$1\\\" || true \"`\n\nThis needs to explain what happens if the user gives both .cmd and\n.command to the same token.  Is it an error?  Is the newly invented\n.cmd takes precedence?  Something else?\n\nWhatever the answer is, the reasoning behind reaching the design\nmust be explained and defended in the proposed log message.\n\n\n> diff --git a/trailer.c b/trailer.c\n> index be4e9726421c..80f47657ff1a 100644\n> --- a/trailer.c\n> +++ b/trailer.c\n> @@ -14,6 +14,7 @@ struct conf_info {\n>  \tchar *name;\n>  \tchar *key;\n>  \tchar *command;\n> +\tint is_new_cmd;\n\nPoor naming.  The .cmd thing may be \"new\" right now in your mind,\nbut how would you transition out of it when design flaws are\ndiscovered in it and replace it with yet another mechanism?\n\nAdd a new \"char *cmd\" field, and at the using site, define the\nprecedence between the two when both cmd and command members of the\nstructure are populated, perhaps?\n"},{"id":"420171","messageId":"CAOLTT8TeL3MofqoGUR2fXq+b+s8ZuxdjkdVnfiuG+vk2TUcwcg@mail.gmail.com","threadId":"55371","inReplyTo":"xmqqft0kxq4m.fsf@gitster.g","subject":"Re: [PATCH v2] [GSOC]trailer: pass arg as positional parameter","fromName":"ZheNing Hu","fromEmail":"adlternative@gmail.com","sentAt":"2021-03-25T01:43:58Z","receivedAt":"2021-03-25T01:45:03Z","isPatch":true,"sender":{"key":"adlternative@gmail.com","avatar":"https://avatars.githubusercontent.com/u/58138461?v=4"},"body":"Junio C Hamano <gitster@pobox.com> 于2021年3月25日周四 上午4:18写道：\n>\n> \"ZheNing Hu via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n>\n> > From: ZheNing Hu <adlternative@gmail.com>\n> >\n> > In the original implementation of `trailer.<token>.command`,\n> > use `strbuf_replace` to replace $ARG in the <value> of the\n> > trailer, but it have a problem: `strbuf_replace` replace the\n> > $ARG in command only once, If the user's trailer command have\n> > used more than one $ARG, error will occur.\n> >\n> > If directly modify the implementation of the original\n> > `trailer.<token>.command`, The user’s previous `'$ARG'` in\n> > trailer command will not be replaced. So now add new\n> > config \"trailer.<token>.cmd\", pass trailer's value as\n> > positional parameter 1 to the user's command, users can\n> > use $1 as trailer's value, to implement original variable\n> > replacement.\n> >\n> > Original `trailer.<token>.command` can still be used until git\n> > completely abandoned it.\n>\n> Sorry, but that's quite an ungrammatical mess X-<.\n>\n\nSomewhat embarrassing. I have tried to fix it...\n\n> >  1:  abc5b04d152f ! 1:  185356d6fc90 [GSOC]trailer: change $ARG to environment variable\n> >      @@ Metadata\n> >       Author: ZheNing Hu <adlternative@gmail.com>\n>\n> As this is completely a different design and does not share anything\n> with the earlier round, the range-diff is merely distracting and\n> useless.\n>\n\nI thought the designs of the two were very similar.\n\n> >  Documentation/git-interpret-trailers.txt |  7 +++++++\n> >  t/t7513-interpret-trailers.sh            | 22 +++++++++++++++++++-\n> >  trailer.c                                | 26 +++++++++++++++++-------\n> >  3 files changed, 47 insertions(+), 8 deletions(-)\n> >\n> > diff --git a/Documentation/git-interpret-trailers.txt b/Documentation/git-interpret-trailers.txt\n> > index 96ec6499f001..38656b1b3841 100644\n> > --- a/Documentation/git-interpret-trailers.txt\n> > +++ b/Documentation/git-interpret-trailers.txt\n> > @@ -252,6 +252,13 @@ also be executed for each of these arguments. And the <value> part of\n> >  these arguments, if any, will be used to replace the `$ARG` string in\n> >  the command.\n> >\n> > +trailer.<token>.cmd::\n> > +     Similar to 'trailer.<token>.command'. But the difference is that\n> > +     `$1` is used in the command to replace the value of the trailer\n> > +     instead of the original `$ARG`, which means that we can quote the\n>\n> \"quote\"?\n>\n\nparse.\n\n> > +     trailer value multiple times in the command.\n> > +     E.g. `trailer.sign.cmd=\"test -n \\\"$1\\\" && echo \\\"$1\\\" || true \"`\n>\n> This needs to explain what happens if the user gives both .cmd and\n> .command to the same token.  Is it an error?  Is the newly invented\n> .cmd takes precedence?  Something else?\n>\n\nFor the time being, if I make \"cmd\" and \"command\" equivalent, it will\nonly trigger a warning.\n\n> Whatever the answer is, the reasoning behind reaching the design\n> must be explained and defended in the proposed log message.\n>\n\nOK.\n\n>\n> > diff --git a/trailer.c b/trailer.c\n> > index be4e9726421c..80f47657ff1a 100644\n> > --- a/trailer.c\n> > +++ b/trailer.c\n> > @@ -14,6 +14,7 @@ struct conf_info {\n> >       char *name;\n> >       char *key;\n> >       char *command;\n> > +     int is_new_cmd;\n>\n> Poor naming.  The .cmd thing may be \"new\" right now in your mind,\n> but how would you transition out of it when design flaws are\n> discovered in it and replace it with yet another mechanism?\n>\n\nI thought if the \"command\" will need to be replaced in later releases,\n \"is_new_cmd\" will be removed at the same time, now I think\n \"is_new_cmd\" may cause misunderstandings.\n\n> Add a new \"char *cmd\" field, and at the using site, define the\n> precedence between the two when both cmd and command members of the\n> structure are populated, perhaps?\n\nIt sounds feasible, I will try.\n\nThanks.\n"},{"id":"420188","messageId":"pull.913.v3.git.1616673200809.gitgitgadget@gmail.com","threadId":"55371","inReplyTo":"pull.913.v2.git.1616600555906.gitgitgadget@gmail.com","subject":"[PATCH v3] [GSOC]trailer: pass arg as positional parameter","fromName":"ZheNing Hu via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2021-03-25T11:53:20Z","receivedAt":"2021-03-25T11:54:04Z","isPatch":true,"sender":{"key":"adlternative@gmail.com","avatar":"https://avatars.githubusercontent.com/u/58138461?v=4"},"body":"From: ZheNing Hu <adlternative@gmail.com>\n\nOriginal implementation of `trailer.<token>.command` use\n`strbuf_replace` to replace $ARG in command with the <value>\nof the trailer, but it have a problem: `strbuf_replace`\nreplace the $ARG only once, If the user's trailer command\nhave used more than one $ARG, the remaining replacement will\nfail.\n\nIf directly modify the implementation of the original\n`trailer.<token>.command`, The user’s previous `'$ARG'` in\ntrailer command will not be replaced. So now add new config\n\"trailer.<token>.cmd\", pass trailer's value as positional\nparameter 1 to the user's command, the user can use $1 as\ntrailer's value, to implement original variable replacement.\n\nIf the user has these two configuration: \"trailer.<token>.cmd\"\nand \"trailer.<token>.command\", \"cmd\" will execute and \"command\"\nwill not executed.\n\nOriginal `trailer.<token>.command` can still be used until git\ncompletely abandoned it.\n\nSigned-off-by: ZheNing Hu <adlternative@gmail.com>\n---\n    [GSOC]trailer: pass arg as positional parameter\n    \n    In https://lore.kernel.org/git/xmqqv99i4ck2.fsf@gitster.g/ Junio and\n    Christian talked about the problem of using strbuf_replace() to replace\n    $ARG.\n    \n    Now pass trailer value as $1 to the trailer command with another\n    trailer.<token>.cmd config.\n\nPublished-As: https://github.com/gitgitgadget/git/releases/tag/pr-913%2Fadlternative%2Ftrailer-pass-ARG-env-v3\nFetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-913/adlternative/trailer-pass-ARG-env-v3\nPull-Request: https://github.com/gitgitgadget/git/pull/913\n\nRange-diff vs v2:\n\n 1:  185356d6fc90 ! 1:  b268ecd7b395 [GSOC]trailer: pass arg as positional parameter\n     @@ Metadata\n       ## Commit message ##\n          [GSOC]trailer: pass arg as positional parameter\n      \n     -    In the original implementation of `trailer.<token>.command`,\n     -    use `strbuf_replace` to replace $ARG in the <value> of the\n     -    trailer, but it have a problem: `strbuf_replace` replace the\n     -    $ARG in command only once, If the user's trailer command have\n     -    used more than one $ARG, error will occur.\n     +    Original implementation of `trailer.<token>.command` use\n     +    `strbuf_replace` to replace $ARG in command with the <value>\n     +    of the trailer, but it have a problem: `strbuf_replace`\n     +    replace the $ARG only once, If the user's trailer command\n     +    have used more than one $ARG, the remaining replacement will\n     +    fail.\n      \n          If directly modify the implementation of the original\n          `trailer.<token>.command`, The user’s previous `'$ARG'` in\n     -    trailer command will not be replaced. So now add new\n     -    config \"trailer.<token>.cmd\", pass trailer's value as\n     -    positional parameter 1 to the user's command, users can\n     -    use $1 as trailer's value, to implement original variable\n     -    replacement.\n     +    trailer command will not be replaced. So now add new config\n     +    \"trailer.<token>.cmd\", pass trailer's value as positional\n     +    parameter 1 to the user's command, the user can use $1 as\n     +    trailer's value, to implement original variable replacement.\n     +\n     +    If the user has these two configuration: \"trailer.<token>.cmd\"\n     +    and \"trailer.<token>.command\", \"cmd\" will execute and \"command\"\n     +    will not executed.\n      \n          Original `trailer.<token>.command` can still be used until git\n          completely abandoned it.\n     @@ Documentation/git-interpret-trailers.txt: also be executed for each of these arg\n      +trailer.<token>.cmd::\n      +\tSimilar to 'trailer.<token>.command'. But the difference is that\n      +\t`$1` is used in the command to replace the value of the trailer\n     -+\tinstead of the original `$ARG`, which means that we can quote the\n     ++\tinstead of the original `$ARG`, which means that we can pass the\n      +\ttrailer value multiple times in the command.\n     -+\tE.g. `trailer.sign.cmd=\"test -n \\\"$1\\\" && echo \\\"$1\\\" || true \"`\n     ++\tE.g. `git config trailer.sign.cmd \"test -n \\\"$1\\\" && echo \\\"$1\\\" || true \"`.\n     ++\tIf the user has these two configuration: \"trailer.<token>.cmd\"\n     ++\tand \"trailer.<token>.command\", \"cmd\" will be executed and \"command\"\n     ++\twill not be executed.\n      +\n       EXAMPLES\n       --------\n     @@ t/t7513-interpret-trailers.sh: test_expect_success 'setup a commit' '\n       \tgit commit -m \"Add file a.txt\"\n       '\n       \n     -+test_expect_success 'with cmd using $1' '\n     ++test_expect_success 'with cmd and $1' '\n      +\ttest_when_finished \"git config --unset trailer.fix.cmd\" &&\n      +\tgit config trailer.fix.ifExists \"replace\" &&\n      +\tgit config trailer.fix.cmd \"test -n \\\"\\$1\\\" && git log -1 --oneline --format=\\\"%h (%s)\\\" \\\n     @@ t/t7513-interpret-trailers.sh: test_expect_success 'setup a commit' '\n      +\t\t<complex_message >actual2 &&\n      +\ttest_cmp expected2 actual2\n      +'\n     ++\n     ++test_expect_success 'cmd takes precedence over command' '\n     ++\ttest_when_finished \"git config --unset trailer.fix.cmd\" &&\n     ++\tgit config trailer.fix.ifExists \"replace\" &&\n     ++\tgit config trailer.fix.cmd \"test -n \\\"\\$1\\\" && git log -1 --oneline --format=\\\"%h (%aN)\\\" \\\n     ++\t\t--abbrev-commit --abbrev=14 \\\"\\$1\\\" || true\" &&\n     ++\tgit config trailer.fix.command \"git log -1 --oneline --format=\\\"%h (%s)\\\" \\\n     ++\t\t--abbrev-commit --abbrev=14 \\$ARG\" &&\n     ++\tFIXED=$(git log -1 --oneline --format=\"%h (%aN)\" --abbrev-commit --abbrev=14 HEAD) &&\n     ++\tcat complex_message_body >expected2 &&\n     ++\tsed -e \"s/ Z\\$/ /\" >>expected2 <<-EOF &&\n     ++\t\tFixes: $FIXED\n     ++\t\tAcked-by= Z\n     ++\t\tReviewed-by:\n     ++\t\tSigned-off-by: Z\n     ++\t\tSigned-off-by: A U Thor <author@example.com>\n     ++\tEOF\n     ++\tgit interpret-trailers --trailer \"review:\" --trailer \"fix=HEAD\" \\\n     ++\t\t<complex_message >actual2 &&\n     ++\ttest_cmp expected2 actual2\n     ++'\n      +\n       test_expect_success 'with command using $ARG' '\n       \tgit config trailer.fix.ifExists \"replace\" &&\n     @@ trailer.c: struct conf_info {\n       \tchar *name;\n       \tchar *key;\n       \tchar *command;\n     -+\tint is_new_cmd;\n     ++\tchar *cmd;\n       \tenum trailer_where where;\n       \tenum trailer_if_exists if_exists;\n       \tenum trailer_if_missing if_missing;\n     +@@ trailer.c: static void free_arg_item(struct arg_item *item)\n     + \tfree(item->conf.name);\n     + \tfree(item->conf.key);\n     + \tfree(item->conf.command);\n     ++\tfree(item->conf.cmd);\n     + \tfree(item->token);\n     + \tfree(item->value);\n     + \tfree(item);\n      @@ trailer.c: static int check_if_different(struct trailer_item *in_tok,\n       \treturn 1;\n       }\n       \n      -static char *apply_command(const char *command, const char *arg)\n     -+static char *apply_command(const char *command, int is_new_cmd , const char *arg)\n     ++static char *apply_command(const char *command, const char *cmd_, const char *arg)\n       {\n       \tstruct strbuf cmd = STRBUF_INIT;\n       \tstruct strbuf buf = STRBUF_INIT;\n     -@@ trailer.c: static char *apply_command(const char *command, const char *arg)\n     + \tstruct child_process cp = CHILD_PROCESS_INIT;\n       \tchar *result;\n       \n     - \tstrbuf_addstr(&cmd, command);\n     +-\tstrbuf_addstr(&cmd, command);\n      -\tif (arg)\n      -\t\tstrbuf_replace(&cmd, TRAILER_ARG_STRING, arg);\n      -\n     - \tstrvec_push(&cp.args, cmd.buf);\n     -+\tif (arg) {\n     -+\t\tif (is_new_cmd)\n     +-\tstrvec_push(&cp.args, cmd.buf);\n     ++\tif (cmd_) {\n     ++\t\tstrbuf_addstr(&cmd, cmd_);\n     ++\t\tstrvec_push(&cp.args, cmd.buf);\n     ++\t\tif (arg)\n      +\t\t\tstrvec_push(&cp.args, arg);\n     -+\t\telse\n     ++\t} else if (command) {\n     ++\t\tstrbuf_addstr(&cmd, command);\n     ++\t\tstrvec_push(&cp.args, cmd.buf);\n     ++\t\tif (arg)\n      +\t\t\tstrbuf_replace(&cmd, TRAILER_ARG_STRING, arg);\n      +\t}\n       \tcp.env = local_repo_env;\n       \tcp.no_stdin = 1;\n       \tcp.use_shell = 1;\n     +@@ trailer.c: static char *apply_command(const char *command, const char *arg)\n     + \n     + static void apply_item_command(struct trailer_item *in_tok, struct arg_item *arg_tok)\n     + {\n     +-\tif (arg_tok->conf.command) {\n     ++\tif (arg_tok->conf.command || arg_tok->conf.cmd) {\n     + \t\tconst char *arg;\n     + \t\tif (arg_tok->value && arg_tok->value[0]) {\n     + \t\t\targ = arg_tok->value;\n      @@ trailer.c: static void apply_item_command(struct trailer_item *in_tok, struct arg_item *arg\n       \t\t\telse\n       \t\t\t\targ = xstrdup(\"\");\n       \t\t}\n      -\t\targ_tok->value = apply_command(arg_tok->conf.command, arg);\n     -+\t\targ_tok->value = apply_command(arg_tok->conf.command, arg_tok->conf.is_new_cmd, arg);\n     ++\t\targ_tok->value = apply_command(arg_tok->conf.command, arg_tok->conf.cmd, arg);\n       \t\tfree((char *)arg);\n       \t}\n       }\n     +@@ trailer.c: static void duplicate_conf(struct conf_info *dst, const struct conf_info *src)\n     + \tdst->name = xstrdup_or_null(src->name);\n     + \tdst->key = xstrdup_or_null(src->key);\n     + \tdst->command = xstrdup_or_null(src->command);\n     ++\tdst->cmd = xstrdup_or_null(src->cmd);\n     + }\n     + \n     + static struct arg_item *get_conf_item(const char *name)\n      @@ trailer.c: static struct arg_item *get_conf_item(const char *name)\n       \treturn item;\n       }\n     @@ trailer.c: static struct {\n       \t{ \"ifexists\", TRAILER_IF_EXISTS },\n       \t{ \"ifmissing\", TRAILER_IF_MISSING }\n      @@ trailer.c: static int git_trailer_config(const char *conf_key, const char *value, void *cb)\n     - \tcase TRAILER_COMMAND:\n     - \t\tif (conf->command)\n       \t\t\twarning(_(\"more than one %s\"), conf_key);\n     -+\t\tconf->is_new_cmd = 0;\n     -+\t\tconf->command = xstrdup(value);\n     -+\t\tbreak;\n     -+\tcase TRAILER_CMD:\n     -+\t\tif (conf->command)\n     -+\t\t\twarning(_(\"more than one %s\"), conf_key);\n     -+\t\tconf->is_new_cmd = 1;\n       \t\tconf->command = xstrdup(value);\n       \t\tbreak;\n     ++\tcase TRAILER_CMD:\n     ++\t\tif (conf->cmd)\n     ++\t\t\twarning(_(\"more than one %s\"), conf_key);\n     ++\t\tconf->cmd = xstrdup(value);\n     ++\t\tbreak;\n       \tcase TRAILER_WHERE:\n     + \t\tif (trailer_set_where(&conf->where, value))\n     + \t\t\twarning(_(\"unknown value '%s' for key '%s'\"), value, conf_key);\n     +@@ trailer.c: static void process_command_line_args(struct list_head *arg_head,\n     + \t/* Add an arg item for each configured trailer with a command */\n     + \tlist_for_each(pos, &conf_head) {\n     + \t\titem = list_entry(pos, struct arg_item, list);\n     +-\t\tif (item->conf.command)\n     ++\t\tif (item->conf.cmd || item->conf.command)\n     + \t\t\tadd_arg_item(arg_head,\n     + \t\t\t\t     xstrdup(token_from_item(item, NULL)),\n     + \t\t\t\t     xstrdup(\"\"),\n\n\n Documentation/git-interpret-trailers.txt | 10 ++++++\n t/t7513-interpret-trailers.sh            | 43 +++++++++++++++++++++++-\n trailer.c                                | 37 ++++++++++++++------\n 3 files changed, 78 insertions(+), 12 deletions(-)\n\ndiff --git a/Documentation/git-interpret-trailers.txt b/Documentation/git-interpret-trailers.txt\nindex 96ec6499f001..f796041514bf 100644\n--- a/Documentation/git-interpret-trailers.txt\n+++ b/Documentation/git-interpret-trailers.txt\n@@ -252,6 +252,16 @@ also be executed for each of these arguments. And the <value> part of\n these arguments, if any, will be used to replace the `$ARG` string in\n the command.\n \n+trailer.<token>.cmd::\n+\tSimilar to 'trailer.<token>.command'. But the difference is that\n+\t`$1` is used in the command to replace the value of the trailer\n+\tinstead of the original `$ARG`, which means that we can pass the\n+\ttrailer value multiple times in the command.\n+\tE.g. `git config trailer.sign.cmd \"test -n \\\"$1\\\" && echo \\\"$1\\\" || true \"`.\n+\tIf the user has these two configuration: \"trailer.<token>.cmd\"\n+\tand \"trailer.<token>.command\", \"cmd\" will be executed and \"command\"\n+\twill not be executed.\n+\n EXAMPLES\n --------\n \ndiff --git a/t/t7513-interpret-trailers.sh b/t/t7513-interpret-trailers.sh\nindex 6602790b5f4c..059beec0c0de 100755\n--- a/t/t7513-interpret-trailers.sh\n+++ b/t/t7513-interpret-trailers.sh\n@@ -1274,9 +1274,50 @@ test_expect_success 'setup a commit' '\n \tgit commit -m \"Add file a.txt\"\n '\n \n+test_expect_success 'with cmd and $1' '\n+\ttest_when_finished \"git config --unset trailer.fix.cmd\" &&\n+\tgit config trailer.fix.ifExists \"replace\" &&\n+\tgit config trailer.fix.cmd \"test -n \\\"\\$1\\\" && git log -1 --oneline --format=\\\"%h (%s)\\\" \\\n+\t\t--abbrev-commit --abbrev=14 \\\"\\$1\\\" || true\" &&\n+\tFIXED=$(git log -1 --oneline --format=\"%h (%s)\" --abbrev-commit --abbrev=14 HEAD) &&\n+\tcat complex_message_body >expected2 &&\n+\tsed -e \"s/ Z\\$/ /\" >>expected2 <<-EOF &&\n+\t\tFixes: $FIXED\n+\t\tAcked-by= Z\n+\t\tReviewed-by:\n+\t\tSigned-off-by: Z\n+\t\tSigned-off-by: A U Thor <author@example.com>\n+\tEOF\n+\tgit interpret-trailers --trailer \"review:\" --trailer \"fix=HEAD\" \\\n+\t\t<complex_message >actual2 &&\n+\ttest_cmp expected2 actual2\n+'\n+\n+test_expect_success 'cmd takes precedence over command' '\n+\ttest_when_finished \"git config --unset trailer.fix.cmd\" &&\n+\tgit config trailer.fix.ifExists \"replace\" &&\n+\tgit config trailer.fix.cmd \"test -n \\\"\\$1\\\" && git log -1 --oneline --format=\\\"%h (%aN)\\\" \\\n+\t\t--abbrev-commit --abbrev=14 \\\"\\$1\\\" || true\" &&\n+\tgit config trailer.fix.command \"git log -1 --oneline --format=\\\"%h (%s)\\\" \\\n+\t\t--abbrev-commit --abbrev=14 \\$ARG\" &&\n+\tFIXED=$(git log -1 --oneline --format=\"%h (%aN)\" --abbrev-commit --abbrev=14 HEAD) &&\n+\tcat complex_message_body >expected2 &&\n+\tsed -e \"s/ Z\\$/ /\" >>expected2 <<-EOF &&\n+\t\tFixes: $FIXED\n+\t\tAcked-by= Z\n+\t\tReviewed-by:\n+\t\tSigned-off-by: Z\n+\t\tSigned-off-by: A U Thor <author@example.com>\n+\tEOF\n+\tgit interpret-trailers --trailer \"review:\" --trailer \"fix=HEAD\" \\\n+\t\t<complex_message >actual2 &&\n+\ttest_cmp expected2 actual2\n+'\n+\n test_expect_success 'with command using $ARG' '\n \tgit config trailer.fix.ifExists \"replace\" &&\n-\tgit config trailer.fix.command \"git log -1 --oneline --format=\\\"%h (%s)\\\" --abbrev-commit --abbrev=14 \\$ARG\" &&\n+\tgit config trailer.fix.command \"git log -1 --oneline --format=\\\"%h (%s)\\\" \\\n+\t\t--abbrev-commit --abbrev=14 \\$ARG\" &&\n \tFIXED=$(git log -1 --oneline --format=\"%h (%s)\" --abbrev-commit --abbrev=14 HEAD) &&\n \tcat complex_message_body >expected &&\n \tsed -e \"s/ Z\\$/ /\" >>expected <<-EOF &&\ndiff --git a/trailer.c b/trailer.c\nindex be4e9726421c..634d3f1ff04a 100644\n--- a/trailer.c\n+++ b/trailer.c\n@@ -14,6 +14,7 @@ struct conf_info {\n \tchar *name;\n \tchar *key;\n \tchar *command;\n+\tchar *cmd;\n \tenum trailer_where where;\n \tenum trailer_if_exists if_exists;\n \tenum trailer_if_missing if_missing;\n@@ -127,6 +128,7 @@ static void free_arg_item(struct arg_item *item)\n \tfree(item->conf.name);\n \tfree(item->conf.key);\n \tfree(item->conf.command);\n+\tfree(item->conf.cmd);\n \tfree(item->token);\n \tfree(item->value);\n \tfree(item);\n@@ -216,18 +218,24 @@ static int check_if_different(struct trailer_item *in_tok,\n \treturn 1;\n }\n \n-static char *apply_command(const char *command, const char *arg)\n+static char *apply_command(const char *command, const char *cmd_, const char *arg)\n {\n \tstruct strbuf cmd = STRBUF_INIT;\n \tstruct strbuf buf = STRBUF_INIT;\n \tstruct child_process cp = CHILD_PROCESS_INIT;\n \tchar *result;\n \n-\tstrbuf_addstr(&cmd, command);\n-\tif (arg)\n-\t\tstrbuf_replace(&cmd, TRAILER_ARG_STRING, arg);\n-\n-\tstrvec_push(&cp.args, cmd.buf);\n+\tif (cmd_) {\n+\t\tstrbuf_addstr(&cmd, cmd_);\n+\t\tstrvec_push(&cp.args, cmd.buf);\n+\t\tif (arg)\n+\t\t\tstrvec_push(&cp.args, arg);\n+\t} else if (command) {\n+\t\tstrbuf_addstr(&cmd, command);\n+\t\tstrvec_push(&cp.args, cmd.buf);\n+\t\tif (arg)\n+\t\t\tstrbuf_replace(&cmd, TRAILER_ARG_STRING, arg);\n+\t}\n \tcp.env = local_repo_env;\n \tcp.no_stdin = 1;\n \tcp.use_shell = 1;\n@@ -247,7 +255,7 @@ static char *apply_command(const char *command, const char *arg)\n \n static void apply_item_command(struct trailer_item *in_tok, struct arg_item *arg_tok)\n {\n-\tif (arg_tok->conf.command) {\n+\tif (arg_tok->conf.command || arg_tok->conf.cmd) {\n \t\tconst char *arg;\n \t\tif (arg_tok->value && arg_tok->value[0]) {\n \t\t\targ = arg_tok->value;\n@@ -257,7 +265,7 @@ static void apply_item_command(struct trailer_item *in_tok, struct arg_item *arg\n \t\t\telse\n \t\t\t\targ = xstrdup(\"\");\n \t\t}\n-\t\targ_tok->value = apply_command(arg_tok->conf.command, arg);\n+\t\targ_tok->value = apply_command(arg_tok->conf.command, arg_tok->conf.cmd, arg);\n \t\tfree((char *)arg);\n \t}\n }\n@@ -430,6 +438,7 @@ static void duplicate_conf(struct conf_info *dst, const struct conf_info *src)\n \tdst->name = xstrdup_or_null(src->name);\n \tdst->key = xstrdup_or_null(src->key);\n \tdst->command = xstrdup_or_null(src->command);\n+\tdst->cmd = xstrdup_or_null(src->cmd);\n }\n \n static struct arg_item *get_conf_item(const char *name)\n@@ -454,8 +463,8 @@ static struct arg_item *get_conf_item(const char *name)\n \treturn item;\n }\n \n-enum trailer_info_type { TRAILER_KEY, TRAILER_COMMAND, TRAILER_WHERE,\n-\t\t\t TRAILER_IF_EXISTS, TRAILER_IF_MISSING };\n+enum trailer_info_type { TRAILER_KEY, TRAILER_COMMAND, TRAILER_CMD,\n+\t\t\tTRAILER_WHERE, TRAILER_IF_EXISTS, TRAILER_IF_MISSING };\n \n static struct {\n \tconst char *name;\n@@ -463,6 +472,7 @@ static struct {\n } trailer_config_items[] = {\n \t{ \"key\", TRAILER_KEY },\n \t{ \"command\", TRAILER_COMMAND },\n+\t{ \"cmd\", TRAILER_CMD },\n \t{ \"where\", TRAILER_WHERE },\n \t{ \"ifexists\", TRAILER_IF_EXISTS },\n \t{ \"ifmissing\", TRAILER_IF_MISSING }\n@@ -542,6 +552,11 @@ static int git_trailer_config(const char *conf_key, const char *value, void *cb)\n \t\t\twarning(_(\"more than one %s\"), conf_key);\n \t\tconf->command = xstrdup(value);\n \t\tbreak;\n+\tcase TRAILER_CMD:\n+\t\tif (conf->cmd)\n+\t\t\twarning(_(\"more than one %s\"), conf_key);\n+\t\tconf->cmd = xstrdup(value);\n+\t\tbreak;\n \tcase TRAILER_WHERE:\n \t\tif (trailer_set_where(&conf->where, value))\n \t\t\twarning(_(\"unknown value '%s' for key '%s'\"), value, conf_key);\n@@ -708,7 +723,7 @@ static void process_command_line_args(struct list_head *arg_head,\n \t/* Add an arg item for each configured trailer with a command */\n \tlist_for_each(pos, &conf_head) {\n \t\titem = list_entry(pos, struct arg_item, list);\n-\t\tif (item->conf.command)\n+\t\tif (item->conf.cmd || item->conf.command)\n \t\t\tadd_arg_item(arg_head,\n \t\t\t\t     xstrdup(token_from_item(item, NULL)),\n \t\t\t\t     xstrdup(\"\"),\n\nbase-commit: 142430338477d9d1bb25be66267225fb58498d92\n-- \ngitgitgadget\n"},{"id":"420208","messageId":"xmqqwntuuav7.fsf@gitster.g","threadId":"55371","inReplyTo":"pull.913.v3.git.1616673200809.gitgitgadget@gmail.com","subject":"Re: [PATCH v3] [GSOC]trailer: pass arg as positional parameter","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2021-03-25T22:28:12Z","receivedAt":"2021-03-25T22:29:14Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"\"ZheNing Hu via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n\n> From: ZheNing Hu <adlternative@gmail.com>\n>\n> Original implementation of `trailer.<token>.command` use\n\nuses\n\n> `strbuf_replace` to replace $ARG in command with the <value>\n> of the trailer, but it have a problem: `strbuf_replace`\n\nhas\n\n> replace the $ARG only once, If the user's trailer command\n\nreplaces the $ARG only once.\n\n> have used more than one $ARG, the remaining replacement will\n> fail.\n\n\"will fail\" is quite vague.  It is just left unreplaced, and if the\nuser expects all of them to be replaced, then the expectation and\nreality would not match, but all of that you have already said by\n\"replaces the $ARG only once.\", so I think this sentence should be\nremoved.\n\n> If directly modify the implementation of the original\n> `trailer.<token>.command`, The user’s previous `'$ARG'` in\n> trailer command will not be replaced.\n\nThat statement does not make much sense.  Depending on the way how\nthe implementation is \"directly\" modified, you can fix the \"replaces\nonly once\" problem without introducing such a problem.  Just look\nfor '$ARG' in the string and replace all of them, not just the first\none.  It's not too difficult.\n\nThis confusion primarily comes from the fact that you forgot to\nexplain the other problem you are fixing, I think.  Even though the\ntrailer.<token>.command documentation implies that the user is\nexpected to give a shell script or some sort as the command and the\nuse of $ARG makes it look like a shell variable, the original\nimplementation does not treat it as a shell variable at all.  And\nthe textual replacement is done without making sure the value being\nreplaced has characters with special meaning in the shell language.\n\nSo existing .command may incorrectly use $ARG inside a single-quote\npair and expect it to be replaced to a string inside a single-quote\npair.  A malformed, or worse, malicious, value would escape out of\nthe single-quote pair (remember, the '; rm -fr .' example?) and\nexecute arbitrary code in an unexpected way.  The (ungrammatical)\n\"if directly modify the implementation\" refers to a potential way to\nfix these two problems at the same time by doing the $ARG thing\nwithout using textual replacement, namely, exporting the value as an\nenvironment variable ARG.  If that approach was taken, then, $ARG\nenclosed in a single-quote pair will no longer be replaced, which\nmakes it a backward incompatible change.\n\nBut without describing what solution you are talking about, your\nthree-line description does not make much sense.\n\n> So now add new config\n> \"trailer.<token>.cmd\", pass trailer's value as positional\n> parameter 1 to the user's command, the user can use $1 as\n> trailer's value, to implement original variable replacement.\n>\n> If the user has these two configuration: \"trailer.<token>.cmd\"\n> and \"trailer.<token>.command\", \"cmd\" will execute and \"command\"\n> will not executed.\n>\n> Original `trailer.<token>.command` can still be used until git\n> completely abandoned it.\n>\n> Signed-off-by: ZheNing Hu <adlternative@gmail.com>\n\nLet's rewrite it completely.\n\n\tThe `trailer.<token>.command` configuration variable\n\tspecifies a command (run via the shall, so it does not have\n\tto be a single name of or path to the command, but can be a\n\tshell script), and the first occurrence of substring $ARG is\n\treplaced with the value given to the `interpret-trailer`\n\tcommand for the token.  This has two downsides:\n\n\t* The use of $ARG in the mechanism misleads the users that\n          the value is passed in the shell variable, and tempt them\n          to use $ARG more than once, but that would not work, as\n          the second and subsequent $ARG are not replaced.\n\n\t* Because $ARG is textually replaced without regard to the\n          shell language syntax, even '$ARG' (inside a single-quote\n          pair), which a user would expect to stay intact, would be\n          replaced, and worse, if the value had an unmatching single\n          quote (imagine a name like \"O'Connor\", substituted into\n          NAME='$ARG' to make it NAME='O'Connor), it would result in\n          a broken command that is not syntactically correct (or\n          worse).\n\n\tIntroduce a new `trailer.<token>.cmd` configuration that\n\ttakes higher precedence to deprecate and eventually remove\n\t`trailer.<token>.command`, which passes the value as a\n\tparameter to the command.  Instead of \"$ARG\", the users will\n\trefer to the value as positional argument, $1, in their\n\tscripts.\n\nI tried to cover everything we need to tell the reviewers about this\nchange with the above.  Did I miss anything?\n\n> +trailer.<token>.cmd::\n> +\tSimilar to 'trailer.<token>.command'. But the difference is that\n> +\t`$1` is used in the command to replace the value of the trailer\n> +\tinstead of the original `$ARG`, which means that we can pass the\n> +\ttrailer value multiple times in the command.\n\nWe eventually want to deprecate the .command, so we'd prefer not to\nrely on its description too much (e.g. try to find a way to say what\nyou want to say without \"instead of the original `$ARG`\").\n\n\tThe command specified by this configuration variable is run\n\twith a single parameter, which is the <value> part of an\n\texisting trailer with the same <token>.  The output from the\n\tcommand is then used as the value for the <token> in the\n\tresulting trailer.\n\nwould be the replacement for the part that talks about $ARG in the\ndescription of trailer.<token>.command.\n\nThe original description for `trailer.<token>.command` is so jumbled\n(not your failure at all) that I had a hard time to understand what\nit is trying to say (e.g. what does \"as if a special <token>=<value>\nargument were added at the beginning of the command line\" mean?  Is\nit making a one-shot export of environment variable to run the\ncommand???), so the above may need further adjustment.  Christian?\nCare to help out?\n\n> +\tE.g. `git config trailer.sign.cmd \"test -n \\\"$1\\\" && echo \\\"$1\\\" || true \"`.\n\nAn example is good.  There is a whole EXAMPLES section in this\nmanual page, and the one that uses $ARG may be a good candidate to\nlook at and change to use .cmd (instead of .command).\n\n> +\tIf the user has these two configuration: \"trailer.<token>.cmd\"\n> +\tand \"trailer.<token>.command\", \"cmd\" will be executed and \"command\"\n> +\twill not be executed.\n\n\tWhen both .cmd and .command are given for the same <token>,\n\t.cmd is used and .command is ignored.\n\n> diff --git a/trailer.c b/trailer.c\n> index be4e9726421c..634d3f1ff04a 100644\n> --- a/trailer.c\n> +++ b/trailer.c\n> @@ -14,6 +14,7 @@ struct conf_info {\n>  \tchar *name;\n>  \tchar *key;\n>  \tchar *command;\n> +\tchar *cmd;\n>  \tenum trailer_where where;\n>  \tenum trailer_if_exists if_exists;\n>  \tenum trailer_if_missing if_missing;\n> @@ -127,6 +128,7 @@ static void free_arg_item(struct arg_item *item)\n>  \tfree(item->conf.name);\n>  \tfree(item->conf.key);\n>  \tfree(item->conf.command);\n> +\tfree(item->conf.cmd);\n>  \tfree(item->token);\n>  \tfree(item->value);\n>  \tfree(item);\n> @@ -216,18 +218,24 @@ static int check_if_different(struct trailer_item *in_tok,\n>  \treturn 1;\n>  }\n>  \n> -static char *apply_command(const char *command, const char *arg)\n> +static char *apply_command(const char *command, const char *cmd_, const char *arg)\n>  {\n>  \tstruct strbuf cmd = STRBUF_INIT;\n>  \tstruct strbuf buf = STRBUF_INIT;\n>  \tstruct child_process cp = CHILD_PROCESS_INIT;\n>  \tchar *result;\n>  \n> -\tstrbuf_addstr(&cmd, command);\n> -\tif (arg)\n> -\t\tstrbuf_replace(&cmd, TRAILER_ARG_STRING, arg);\n> -\n> -\tstrvec_push(&cp.args, cmd.buf);\n> +\tif (cmd_) {\n> +\t\tstrbuf_addstr(&cmd, cmd_);\n> +\t\tstrvec_push(&cp.args, cmd.buf);\n> +\t\tif (arg)\n> +\t\t\tstrvec_push(&cp.args, arg);\n> +\t} else if (command) {\n> +\t\tstrbuf_addstr(&cmd, command);\n> +\t\tstrvec_push(&cp.args, cmd.buf);\n> +\t\tif (arg)\n> +\t\t\tstrbuf_replace(&cmd, TRAILER_ARG_STRING, arg);\n> +\t}\n\nOK.  it is clear cmd_ takes precedence this way.\n\nLater (not as part of this patch, but a few releases down the road),\nwe may want to add a warning() about using a deprecated feature when\n\"else if (command)\" block is taken.\n\n> @@ -247,7 +255,7 @@ static char *apply_command(const char *command, const char *arg)\n>  \n>  static void apply_item_command(struct trailer_item *in_tok, struct arg_item *arg_tok)\n>  {\n> -\tif (arg_tok->conf.command) {\n> +\tif (arg_tok->conf.command || arg_tok->conf.cmd) {\n>  \t\tconst char *arg;\n>  \t\tif (arg_tok->value && arg_tok->value[0]) {\n>  \t\t\targ = arg_tok->value;\n> @@ -257,7 +265,7 @@ static void apply_item_command(struct trailer_item *in_tok, struct arg_item *arg\n>  \t\t\telse\n>  \t\t\t\targ = xstrdup(\"\");\n>  \t\t}\n> -\t\targ_tok->value = apply_command(arg_tok->conf.command, arg);\n> +\t\targ_tok->value = apply_command(arg_tok->conf.command, arg_tok->conf.cmd, arg);\n\nIt might be cleaner to just pass arg_tok->conf to apply_command()\nand hide \"cmd takes precedence over command\" as an implementation\ndetail of that helper function.\n\nThe implementation looks as good as the original \"command\" with that\nchange at this point.  Documentation may need a bit more polishing.\n\nThanks.\n"},{"id":"420261","messageId":"CAOLTT8TMFjvH1TzZ9qRtW1v7aDYypdajJxRKpmZ0TaG_xx5H0w@mail.gmail.com","threadId":"55371","inReplyTo":"xmqqwntuuav7.fsf@gitster.g","subject":"Re: [PATCH v3] [GSOC]trailer: pass arg as positional parameter","fromName":"ZheNing Hu","fromEmail":"adlternative@gmail.com","sentAt":"2021-03-26T13:29:46Z","receivedAt":"2021-03-26T13:30:37Z","isPatch":true,"sender":{"key":"adlternative@gmail.com","avatar":"https://avatars.githubusercontent.com/u/58138461?v=4"},"body":"Junio C Hamano <gitster@pobox.com> 于2021年3月26日周五 上午6:28写道：\n>\n> \"ZheNing Hu via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n>\n> > From: ZheNing Hu <adlternative@gmail.com>\n> >\n> > Original implementation of `trailer.<token>.command` use\n>\n> uses\n>\n> > `strbuf_replace` to replace $ARG in command with the <value>\n> > of the trailer, but it have a problem: `strbuf_replace`\n>\n> has\n>\n> > replace the $ARG only once, If the user's trailer command\n>\n> replaces the $ARG only once.\n>\n\nOkay... singular and plural problems.\n\n> > have used more than one $ARG, the remaining replacement will\n> > fail.\n>\n> \"will fail\" is quite vague.  It is just left unreplaced, and if the\n> user expects all of them to be replaced, then the expectation and\n> reality would not match, but all of that you have already said by\n> \"replaces the $ARG only once.\", so I think this sentence should be\n> removed.\n>\n\nIndeed so.\n\n> > If directly modify the implementation of the original\n> > `trailer.<token>.command`, The user’s previous `'$ARG'` in\n> > trailer command will not be replaced.\n>\n> That statement does not make much sense.  Depending on the way how\n> the implementation is \"directly\" modified, you can fix the \"replaces\n> only once\" problem without introducing such a problem.  Just look\n> for '$ARG' in the string and replace all of them, not just the first\n> one.  It's not too difficult.\n>\n> This confusion primarily comes from the fact that you forgot to\n> explain the other problem you are fixing, I think.  Even though the\n> trailer.<token>.command documentation implies that the user is\n> expected to give a shell script or some sort as the command and the\n> use of $ARG makes it look like a shell variable, the original\n> implementation does not treat it as a shell variable at all.  And\n> the textual replacement is done without making sure the value being\n> replaced has characters with special meaning in the shell language.\n>\n\nYes! The accidental injection problem caused should have been the focus\nof my explanation.\n\n> So existing .command may incorrectly use $ARG inside a single-quote\n> pair and expect it to be replaced to a string inside a single-quote\n> pair.  A malformed, or worse, malicious, value would escape out of\n> the single-quote pair (remember, the '; rm -fr .' example?) and\n> execute arbitrary code in an unexpected way.  The (ungrammatical)\n> \"if directly modify the implementation\" refers to a potential way to\n> fix these two problems at the same time by doing the $ARG thing\n> without using textual replacement, namely, exporting the value as an\n> environment variable ARG.  If that approach was taken, then, $ARG\n> enclosed in a single-quote pair will no longer be replaced, which\n> makes it a backward incompatible change.\n>\n\nOh, I remeber it: terrible shell injection. Specifically, it looks like this:\n\n$ git config trailer.sign.command \"git log --author='\\$ARG'\"\n$ git interpret-trailers --trailer \"sign = adl' && rm -rf ./repo/'\"\n\nnow I know that should be \"backward incompatible change\" as you said.\n\n> But without describing what solution you are talking about, your\n> three-line description does not make much sense.\n>\n> > So now add new config\n> > \"trailer.<token>.cmd\", pass trailer's value as positional\n> > parameter 1 to the user's command, the user can use $1 as\n> > trailer's value, to implement original variable replacement.\n> >\n> > If the user has these two configuration: \"trailer.<token>.cmd\"\n> > and \"trailer.<token>.command\", \"cmd\" will execute and \"command\"\n> > will not executed.\n> >\n> > Original `trailer.<token>.command` can still be used until git\n> > completely abandoned it.\n> >\n> > Signed-off-by: ZheNing Hu <adlternative@gmail.com>\n>\n> Let's rewrite it completely.\n>\n>         The `trailer.<token>.command` configuration variable\n>         specifies a command (run via the shall, so it does not have\n>         to be a single name of or path to the command, but can be a\n>         shell script), and the first occurrence of substring $ARG is\n>         replaced with the value given to the `interpret-trailer`\n>         command for the token.  This has two downsides:\n>\n>         * The use of $ARG in the mechanism misleads the users that\n>           the value is passed in the shell variable, and tempt them\n>           to use $ARG more than once, but that would not work, as\n>           the second and subsequent $ARG are not replaced.\n>\n>         * Because $ARG is textually replaced without regard to the\n>           shell language syntax, even '$ARG' (inside a single-quote\n>           pair), which a user would expect to stay intact, would be\n>           replaced, and worse, if the value had an unmatching single\n>           quote (imagine a name like \"O'Connor\", substituted into\n>           NAME='$ARG' to make it NAME='O'Connor), it would result in\n>           a broken command that is not syntactically correct (or\n>           worse).\n>\n>         Introduce a new `trailer.<token>.cmd` configuration that\n>         takes higher precedence to deprecate and eventually remove\n>         `trailer.<token>.command`, which passes the value as a\n>         parameter to the command.  Instead of \"$ARG\", the users will\n>         refer to the value as positional argument, $1, in their\n>         scripts.\n>\n> I tried to cover everything we need to tell the reviewers about this\n> change with the above.  Did I miss anything?\n\nNothing to blame, feature of the old command, 2 problems, 1 solution,\nthis is what the log should look like.\n\n>\n> > +trailer.<token>.cmd::\n> > +     Similar to 'trailer.<token>.command'. But the difference is that\n> > +     `$1` is used in the command to replace the value of the trailer\n> > +     instead of the original `$ARG`, which means that we can pass the\n> > +     trailer value multiple times in the command.\n>\n> We eventually want to deprecate the .command, so we'd prefer not to\n> rely on its description too much (e.g. try to find a way to say what\n> you want to say without \"instead of the original `$ARG`\").\n>\n\nOh! here I can’t rely on the documentation of the old `.command`, otherwise it’s\nnot easy to delete the old documentation.\n\n>         The command specified by this configuration variable is run\n>         with a single parameter, which is the <value> part of an\n>         existing trailer with the same <token>.  The output from the\n>         command is then used as the value for the <token> in the\n>         resulting trailer.\n>\n> would be the replacement for the part that talks about $ARG in the\n> description of trailer.<token>.command.\n>\n> The original description for `trailer.<token>.command` is so jumbled\n> (not your failure at all) that I had a hard time to understand what\n> it is trying to say (e.g. what does \"as if a special <token>=<value>\n> argument were added at the beginning of the command line\" mean?  Is\n> it making a one-shot export of environment variable to run the\n> command???), so the above may need further adjustment.  Christian?\n> Care to help out?\n>\n> > +     E.g. `git config trailer.sign.cmd \"test -n \\\"$1\\\" && echo \\\"$1\\\" || true \"`.\n>\n> An example is good.  There is a whole EXAMPLES section in this\n> manual page, and the one that uses $ARG may be a good candidate to\n> look at and change to use .cmd (instead of .command).\n>\n\nOkay, I will modify the paragraphs containing `.command` in these examples.\n\n> > +     If the user has these two configuration: \"trailer.<token>.cmd\"\n> > +     and \"trailer.<token>.command\", \"cmd\" will be executed and \"command\"\n> > +     will not be executed.\n>\n>         When both .cmd and .command are given for the same <token>,\n>         .cmd is used and .command is ignored.\n>\n> > diff --git a/trailer.c b/trailer.c\n> > index be4e9726421c..634d3f1ff04a 100644\n> > --- a/trailer.c\n> > +++ b/trailer.c\n> > @@ -14,6 +14,7 @@ struct conf_info {\n> >       char *name;\n> >       char *key;\n> >       char *command;\n> > +     char *cmd;\n> >       enum trailer_where where;\n> >       enum trailer_if_exists if_exists;\n> >       enum trailer_if_missing if_missing;\n> > @@ -127,6 +128,7 @@ static void free_arg_item(struct arg_item *item)\n> >       free(item->conf.name);\n> >       free(item->conf.key);\n> >       free(item->conf.command);\n> > +     free(item->conf.cmd);\n> >       free(item->token);\n> >       free(item->value);\n> >       free(item);\n> > @@ -216,18 +218,24 @@ static int check_if_different(struct trailer_item *in_tok,\n> >       return 1;\n> >  }\n> >\n> > -static char *apply_command(const char *command, const char *arg)\n> > +static char *apply_command(const char *command, const char *cmd_, const char *arg)\n> >  {\n> >       struct strbuf cmd = STRBUF_INIT;\n> >       struct strbuf buf = STRBUF_INIT;\n> >       struct child_process cp = CHILD_PROCESS_INIT;\n> >       char *result;\n> >\n> > -     strbuf_addstr(&cmd, command);\n> > -     if (arg)\n> > -             strbuf_replace(&cmd, TRAILER_ARG_STRING, arg);\n> > -\n> > -     strvec_push(&cp.args, cmd.buf);\n> > +     if (cmd_) {\n> > +             strbuf_addstr(&cmd, cmd_);\n> > +             strvec_push(&cp.args, cmd.buf);\n> > +             if (arg)\n> > +                     strvec_push(&cp.args, arg);\n> > +     } else if (command) {\n> > +             strbuf_addstr(&cmd, command);\n> > +             strvec_push(&cp.args, cmd.buf);\n> > +             if (arg)\n> > +                     strbuf_replace(&cmd, TRAILER_ARG_STRING, arg);\n> > +     }\n>\n> OK.  it is clear cmd_ takes precedence this way.\n>\n> Later (not as part of this patch, but a few releases down the road),\n> we may want to add a warning() about using a deprecated feature when\n> \"else if (command)\" block is taken.\n>\n\nFine, I will keep this version of \"cmd priority execution\" for now.\n\n> > @@ -247,7 +255,7 @@ static char *apply_command(const char *command, const char *arg)\n> >\n> >  static void apply_item_command(struct trailer_item *in_tok, struct arg_item *arg_tok)\n> >  {\n> > -     if (arg_tok->conf.command) {\n> > +     if (arg_tok->conf.command || arg_tok->conf.cmd) {\n> >               const char *arg;\n> >               if (arg_tok->value && arg_tok->value[0]) {\n> >                       arg = arg_tok->value;\n> > @@ -257,7 +265,7 @@ static void apply_item_command(struct trailer_item *in_tok, struct arg_item *arg\n> >                       else\n> >                               arg = xstrdup(\"\");\n> >               }\n> > -             arg_tok->value = apply_command(arg_tok->conf.command, arg);\n> > +             arg_tok->value = apply_command(arg_tok->conf.command, arg_tok->conf.cmd, arg);\n>\n> It might be cleaner to just pass arg_tok->conf to apply_command()\n> and hide \"cmd takes precedence over command\" as an implementation\n> detail of that helper function.\n>\n> The implementation looks as good as the original \"command\" with that\n> change at this point.  Documentation may need a bit more polishing.\n>\n\nyou're right.\n\n> Thanks.\n\nThanks, Junio.\nYou and the people in the git community are very enthusiastic,\nYou have patiently explained these small mistakes that I made,\nand taught me a lot of problems that I didn't notice.\n\nGrateful.\n\n--\nZheNing Hu\n"},{"id":"420272","messageId":"pull.913.v4.git.1616775185562.gitgitgadget@gmail.com","threadId":"55371","inReplyTo":"pull.913.v3.git.1616673200809.gitgitgadget@gmail.com","subject":"[PATCH v4] [GSOC]trailer: pass arg as positional parameter","fromName":"ZheNing Hu via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2021-03-26T16:13:05Z","receivedAt":"2021-03-26T16:13:46Z","isPatch":true,"sender":{"key":"adlternative@gmail.com","avatar":"https://avatars.githubusercontent.com/u/58138461?v=4"},"body":"From: ZheNing Hu <adlternative@gmail.com>\n\nThe `trailer.<token>.command` configuration variable\nspecifies a command (run via the shell, so it does not have\nto be a single name of or path to the command, but can be a\nshell script), and the first occurrence of substring $ARG is\nreplaced with the value given to the `interpret-trailer`\ncommand for the token.  This has two downsides:\n\n* The use of $ARG in the mechanism misleads the users that\nthe value is passed in the shell variable, and tempt them\nto use $ARG more than once, but that would not work, as\nthe second and subsequent $ARG are not replaced.\n\n* Because $ARG is textually replaced without regard to the\nshell language syntax, even '$ARG' (inside a single-quote\npair), which a user would expect to stay intact, would be\nreplaced, and worse, if the value had an unmatching single\nquote (imagine a name like \"O'Connor\", substituted into\nNAME='$ARG' to make it NAME='O'Connor), it would result in\na broken command that is not syntactically correct (or\nworse).\n\nIntroduce a new `trailer.<token>.cmd` configuration that\ntakes higher precedence to deprecate and eventually remove\n`trailer.<token>.command`, which passes the value as a\nparameter to the command.  Instead of \"$ARG\", the users will\nrefer to the value as positional argument, $1, in their\nscripts.\n\nHelped-by: Junio C Hamano <gitster@pobox.com>\nSigned-off-by: ZheNing Hu <adlternative@gmail.com>\n---\n    [GSOC]trailer: pass arg as positional parameter\n    \n    In https://lore.kernel.org/git/xmqqv99i4ck2.fsf@gitster.g/ Junio and\n    Christian talked about the problem of using strbuf_replace() to replace\n    $ARG.\n    \n    Now pass trailer value as $1 to the trailer command with another\n    trailer.<token>.cmd config.\n\nPublished-As: https://github.com/gitgitgadget/git/releases/tag/pr-913%2Fadlternative%2Ftrailer-pass-ARG-env-v4\nFetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-913/adlternative/trailer-pass-ARG-env-v4\nPull-Request: https://github.com/gitgitgadget/git/pull/913\n\nRange-diff vs v3:\n\n 1:  b268ecd7b395 ! 1:  e2bbdcb943c2 [GSOC]trailer: pass arg as positional parameter\n     @@ Metadata\n       ## Commit message ##\n          [GSOC]trailer: pass arg as positional parameter\n      \n     -    Original implementation of `trailer.<token>.command` use\n     -    `strbuf_replace` to replace $ARG in command with the <value>\n     -    of the trailer, but it have a problem: `strbuf_replace`\n     -    replace the $ARG only once, If the user's trailer command\n     -    have used more than one $ARG, the remaining replacement will\n     -    fail.\n     +    The `trailer.<token>.command` configuration variable\n     +    specifies a command (run via the shell, so it does not have\n     +    to be a single name of or path to the command, but can be a\n     +    shell script), and the first occurrence of substring $ARG is\n     +    replaced with the value given to the `interpret-trailer`\n     +    command for the token.  This has two downsides:\n      \n     -    If directly modify the implementation of the original\n     -    `trailer.<token>.command`, The user’s previous `'$ARG'` in\n     -    trailer command will not be replaced. So now add new config\n     -    \"trailer.<token>.cmd\", pass trailer's value as positional\n     -    parameter 1 to the user's command, the user can use $1 as\n     -    trailer's value, to implement original variable replacement.\n     +    * The use of $ARG in the mechanism misleads the users that\n     +    the value is passed in the shell variable, and tempt them\n     +    to use $ARG more than once, but that would not work, as\n     +    the second and subsequent $ARG are not replaced.\n      \n     -    If the user has these two configuration: \"trailer.<token>.cmd\"\n     -    and \"trailer.<token>.command\", \"cmd\" will execute and \"command\"\n     -    will not executed.\n     +    * Because $ARG is textually replaced without regard to the\n     +    shell language syntax, even '$ARG' (inside a single-quote\n     +    pair), which a user would expect to stay intact, would be\n     +    replaced, and worse, if the value had an unmatching single\n     +    quote (imagine a name like \"O'Connor\", substituted into\n     +    NAME='$ARG' to make it NAME='O'Connor), it would result in\n     +    a broken command that is not syntactically correct (or\n     +    worse).\n      \n     -    Original `trailer.<token>.command` can still be used until git\n     -    completely abandoned it.\n     +    Introduce a new `trailer.<token>.cmd` configuration that\n     +    takes higher precedence to deprecate and eventually remove\n     +    `trailer.<token>.command`, which passes the value as a\n     +    parameter to the command.  Instead of \"$ARG\", the users will\n     +    refer to the value as positional argument, $1, in their\n     +    scripts.\n      \n     +    Helped-by: Junio C Hamano <gitster@pobox.com>\n          Signed-off-by: ZheNing Hu <adlternative@gmail.com>\n      \n       ## Documentation/git-interpret-trailers.txt ##\n     @@ Documentation/git-interpret-trailers.txt: also be executed for each of these arg\n       the command.\n       \n      +trailer.<token>.cmd::\n     -+\tSimilar to 'trailer.<token>.command'. But the difference is that\n     -+\t`$1` is used in the command to replace the value of the trailer\n     -+\tinstead of the original `$ARG`, which means that we can pass the\n     -+\ttrailer value multiple times in the command.\n     -+\tE.g. `git config trailer.sign.cmd \"test -n \\\"$1\\\" && echo \\\"$1\\\" || true \"`.\n     -+\tIf the user has these two configuration: \"trailer.<token>.cmd\"\n     -+\tand \"trailer.<token>.command\", \"cmd\" will be executed and \"command\"\n     -+\twill not be executed.\n     ++\tThe command specified by this configuration variable is run\n     ++\twith a single parameter, which is the <value> part of an\n     ++\texisting trailer with the same <token>.  The output from the\n     ++\tcommand is then used as the value for the <token> in the\n     ++\tresulting trailer.\n     ++\tThe command is expected to replace `trailer.<token>.cmd`.\n     ++\tWhen both .cmd and .command are given for the same <token>,\n     ++        .cmd is used and .command is ignored.\n      +\n       EXAMPLES\n       --------\n       \n     +@@ Documentation/git-interpret-trailers.txt: $ git format-patch -1\n     + $ git interpret-trailers --trailer 'Cc: Alice <alice@example.com>' --trailer 'Reviewed-by: Bob <bob@example.com>' 0001-foo.patch >0001-bar.patch\n     + ------------\n     + \n     +-* Configure a 'sign' trailer with a command to automatically add a\n     ++* Configure a 'sign' trailer with a cmd to automatically add a\n     +   'Signed-off-by: ' with the author information only if there is no\n     +   'Signed-off-by: ' already, and show how it works:\n     + +\n     +@@ Documentation/git-interpret-trailers.txt: $ git interpret-trailers --trailer 'Cc: Alice <alice@example.com>' --trailer 'Re\n     + $ git config trailer.sign.key \"Signed-off-by: \"\n     + $ git config trailer.sign.ifmissing add\n     + $ git config trailer.sign.ifexists doNothing\n     +-$ git config trailer.sign.command 'echo \"$(git config user.name) <$(git config user.email)>\"'\n     ++$ git config trailer.sign.cmd 'echo \"$(git config user.name) <$(git config user.email)>\"'\n     + $ git interpret-trailers <<EOF\n     + > EOF\n     + \n     +@@ Documentation/git-interpret-trailers.txt: subject\n     + Fix #42\n     + ------------\n     + \n     +-* Configure a 'see' trailer with a command to show the subject of a\n     ++* Configure a 'see' trailer with a cmd to show the subject of a\n     +   commit that is related, and show how it works:\n     + +\n     + ------------\n     + $ git config trailer.see.key \"See-also: \"\n     + $ git config trailer.see.ifExists \"replace\"\n     + $ git config trailer.see.ifMissing \"doNothing\"\n     +-$ git config trailer.see.command \"git log -1 --oneline --format=\\\"%h (%s)\\\" --abbrev-commit --abbrev=14 \\$ARG\"\n     ++$ git config trailer.see.cmd \"test -n \\\"\\$1\\\" && git log -1 --oneline --format=\\\"%h (%s)\\\" --abbrev-commit --abbrev=14 \\\"\\$1\\\"|| true \"\n     + $ git interpret-trailers <<EOF\n     + > subject\n     + > \n      \n       ## t/t7513-interpret-trailers.sh ##\n      @@ t/t7513-interpret-trailers.sh: test_expect_success 'setup a commit' '\n     @@ trailer.c: static int check_if_different(struct trailer_item *in_tok,\n       }\n       \n      -static char *apply_command(const char *command, const char *arg)\n     -+static char *apply_command(const char *command, const char *cmd_, const char *arg)\n     ++static char *apply_command(struct conf_info *conf, const char *arg)\n       {\n       \tstruct strbuf cmd = STRBUF_INIT;\n       \tstruct strbuf buf = STRBUF_INIT;\n     @@ trailer.c: static int check_if_different(struct trailer_item *in_tok,\n      -\t\tstrbuf_replace(&cmd, TRAILER_ARG_STRING, arg);\n      -\n      -\tstrvec_push(&cp.args, cmd.buf);\n     -+\tif (cmd_) {\n     -+\t\tstrbuf_addstr(&cmd, cmd_);\n     ++\tif (conf->cmd) {\n     ++\t\tstrbuf_addstr(&cmd, conf->cmd);\n      +\t\tstrvec_push(&cp.args, cmd.buf);\n      +\t\tif (arg)\n      +\t\t\tstrvec_push(&cp.args, arg);\n     -+\t} else if (command) {\n     -+\t\tstrbuf_addstr(&cmd, command);\n     ++\t} else if (conf->command) {\n     ++\t\tstrbuf_addstr(&cmd, conf->command);\n      +\t\tstrvec_push(&cp.args, cmd.buf);\n      +\t\tif (arg)\n      +\t\t\tstrbuf_replace(&cmd, TRAILER_ARG_STRING, arg);\n     @@ trailer.c: static void apply_item_command(struct trailer_item *in_tok, struct ar\n       \t\t\t\targ = xstrdup(\"\");\n       \t\t}\n      -\t\targ_tok->value = apply_command(arg_tok->conf.command, arg);\n     -+\t\targ_tok->value = apply_command(arg_tok->conf.command, arg_tok->conf.cmd, arg);\n     ++\t\targ_tok->value = apply_command(&arg_tok->conf, arg);\n       \t\tfree((char *)arg);\n       \t}\n       }\n\n\n Documentation/git-interpret-trailers.txt | 18 +++++++---\n t/t7513-interpret-trailers.sh            | 43 +++++++++++++++++++++++-\n trailer.c                                | 37 ++++++++++++++------\n 3 files changed, 82 insertions(+), 16 deletions(-)\n\ndiff --git a/Documentation/git-interpret-trailers.txt b/Documentation/git-interpret-trailers.txt\nindex 96ec6499f001..b73f9c8d71eb 100644\n--- a/Documentation/git-interpret-trailers.txt\n+++ b/Documentation/git-interpret-trailers.txt\n@@ -252,6 +252,16 @@ also be executed for each of these arguments. And the <value> part of\n these arguments, if any, will be used to replace the `$ARG` string in\n the command.\n \n+trailer.<token>.cmd::\n+\tThe command specified by this configuration variable is run\n+\twith a single parameter, which is the <value> part of an\n+\texisting trailer with the same <token>.  The output from the\n+\tcommand is then used as the value for the <token> in the\n+\tresulting trailer.\n+\tThe command is expected to replace `trailer.<token>.cmd`.\n+\tWhen both .cmd and .command are given for the same <token>,\n+        .cmd is used and .command is ignored.\n+\n EXAMPLES\n --------\n \n@@ -301,7 +311,7 @@ $ git format-patch -1\n $ git interpret-trailers --trailer 'Cc: Alice <alice@example.com>' --trailer 'Reviewed-by: Bob <bob@example.com>' 0001-foo.patch >0001-bar.patch\n ------------\n \n-* Configure a 'sign' trailer with a command to automatically add a\n+* Configure a 'sign' trailer with a cmd to automatically add a\n   'Signed-off-by: ' with the author information only if there is no\n   'Signed-off-by: ' already, and show how it works:\n +\n@@ -309,7 +319,7 @@ $ git interpret-trailers --trailer 'Cc: Alice <alice@example.com>' --trailer 'Re\n $ git config trailer.sign.key \"Signed-off-by: \"\n $ git config trailer.sign.ifmissing add\n $ git config trailer.sign.ifexists doNothing\n-$ git config trailer.sign.command 'echo \"$(git config user.name) <$(git config user.email)>\"'\n+$ git config trailer.sign.cmd 'echo \"$(git config user.name) <$(git config user.email)>\"'\n $ git interpret-trailers <<EOF\n > EOF\n \n@@ -333,14 +343,14 @@ subject\n Fix #42\n ------------\n \n-* Configure a 'see' trailer with a command to show the subject of a\n+* Configure a 'see' trailer with a cmd to show the subject of a\n   commit that is related, and show how it works:\n +\n ------------\n $ git config trailer.see.key \"See-also: \"\n $ git config trailer.see.ifExists \"replace\"\n $ git config trailer.see.ifMissing \"doNothing\"\n-$ git config trailer.see.command \"git log -1 --oneline --format=\\\"%h (%s)\\\" --abbrev-commit --abbrev=14 \\$ARG\"\n+$ git config trailer.see.cmd \"test -n \\\"\\$1\\\" && git log -1 --oneline --format=\\\"%h (%s)\\\" --abbrev-commit --abbrev=14 \\\"\\$1\\\"|| true \"\n $ git interpret-trailers <<EOF\n > subject\n > \ndiff --git a/t/t7513-interpret-trailers.sh b/t/t7513-interpret-trailers.sh\nindex 6602790b5f4c..059beec0c0de 100755\n--- a/t/t7513-interpret-trailers.sh\n+++ b/t/t7513-interpret-trailers.sh\n@@ -1274,9 +1274,50 @@ test_expect_success 'setup a commit' '\n \tgit commit -m \"Add file a.txt\"\n '\n \n+test_expect_success 'with cmd and $1' '\n+\ttest_when_finished \"git config --unset trailer.fix.cmd\" &&\n+\tgit config trailer.fix.ifExists \"replace\" &&\n+\tgit config trailer.fix.cmd \"test -n \\\"\\$1\\\" && git log -1 --oneline --format=\\\"%h (%s)\\\" \\\n+\t\t--abbrev-commit --abbrev=14 \\\"\\$1\\\" || true\" &&\n+\tFIXED=$(git log -1 --oneline --format=\"%h (%s)\" --abbrev-commit --abbrev=14 HEAD) &&\n+\tcat complex_message_body >expected2 &&\n+\tsed -e \"s/ Z\\$/ /\" >>expected2 <<-EOF &&\n+\t\tFixes: $FIXED\n+\t\tAcked-by= Z\n+\t\tReviewed-by:\n+\t\tSigned-off-by: Z\n+\t\tSigned-off-by: A U Thor <author@example.com>\n+\tEOF\n+\tgit interpret-trailers --trailer \"review:\" --trailer \"fix=HEAD\" \\\n+\t\t<complex_message >actual2 &&\n+\ttest_cmp expected2 actual2\n+'\n+\n+test_expect_success 'cmd takes precedence over command' '\n+\ttest_when_finished \"git config --unset trailer.fix.cmd\" &&\n+\tgit config trailer.fix.ifExists \"replace\" &&\n+\tgit config trailer.fix.cmd \"test -n \\\"\\$1\\\" && git log -1 --oneline --format=\\\"%h (%aN)\\\" \\\n+\t\t--abbrev-commit --abbrev=14 \\\"\\$1\\\" || true\" &&\n+\tgit config trailer.fix.command \"git log -1 --oneline --format=\\\"%h (%s)\\\" \\\n+\t\t--abbrev-commit --abbrev=14 \\$ARG\" &&\n+\tFIXED=$(git log -1 --oneline --format=\"%h (%aN)\" --abbrev-commit --abbrev=14 HEAD) &&\n+\tcat complex_message_body >expected2 &&\n+\tsed -e \"s/ Z\\$/ /\" >>expected2 <<-EOF &&\n+\t\tFixes: $FIXED\n+\t\tAcked-by= Z\n+\t\tReviewed-by:\n+\t\tSigned-off-by: Z\n+\t\tSigned-off-by: A U Thor <author@example.com>\n+\tEOF\n+\tgit interpret-trailers --trailer \"review:\" --trailer \"fix=HEAD\" \\\n+\t\t<complex_message >actual2 &&\n+\ttest_cmp expected2 actual2\n+'\n+\n test_expect_success 'with command using $ARG' '\n \tgit config trailer.fix.ifExists \"replace\" &&\n-\tgit config trailer.fix.command \"git log -1 --oneline --format=\\\"%h (%s)\\\" --abbrev-commit --abbrev=14 \\$ARG\" &&\n+\tgit config trailer.fix.command \"git log -1 --oneline --format=\\\"%h (%s)\\\" \\\n+\t\t--abbrev-commit --abbrev=14 \\$ARG\" &&\n \tFIXED=$(git log -1 --oneline --format=\"%h (%s)\" --abbrev-commit --abbrev=14 HEAD) &&\n \tcat complex_message_body >expected &&\n \tsed -e \"s/ Z\\$/ /\" >>expected <<-EOF &&\ndiff --git a/trailer.c b/trailer.c\nindex be4e9726421c..278e40974a4c 100644\n--- a/trailer.c\n+++ b/trailer.c\n@@ -14,6 +14,7 @@ struct conf_info {\n \tchar *name;\n \tchar *key;\n \tchar *command;\n+\tchar *cmd;\n \tenum trailer_where where;\n \tenum trailer_if_exists if_exists;\n \tenum trailer_if_missing if_missing;\n@@ -127,6 +128,7 @@ static void free_arg_item(struct arg_item *item)\n \tfree(item->conf.name);\n \tfree(item->conf.key);\n \tfree(item->conf.command);\n+\tfree(item->conf.cmd);\n \tfree(item->token);\n \tfree(item->value);\n \tfree(item);\n@@ -216,18 +218,24 @@ static int check_if_different(struct trailer_item *in_tok,\n \treturn 1;\n }\n \n-static char *apply_command(const char *command, const char *arg)\n+static char *apply_command(struct conf_info *conf, const char *arg)\n {\n \tstruct strbuf cmd = STRBUF_INIT;\n \tstruct strbuf buf = STRBUF_INIT;\n \tstruct child_process cp = CHILD_PROCESS_INIT;\n \tchar *result;\n \n-\tstrbuf_addstr(&cmd, command);\n-\tif (arg)\n-\t\tstrbuf_replace(&cmd, TRAILER_ARG_STRING, arg);\n-\n-\tstrvec_push(&cp.args, cmd.buf);\n+\tif (conf->cmd) {\n+\t\tstrbuf_addstr(&cmd, conf->cmd);\n+\t\tstrvec_push(&cp.args, cmd.buf);\n+\t\tif (arg)\n+\t\t\tstrvec_push(&cp.args, arg);\n+\t} else if (conf->command) {\n+\t\tstrbuf_addstr(&cmd, conf->command);\n+\t\tstrvec_push(&cp.args, cmd.buf);\n+\t\tif (arg)\n+\t\t\tstrbuf_replace(&cmd, TRAILER_ARG_STRING, arg);\n+\t}\n \tcp.env = local_repo_env;\n \tcp.no_stdin = 1;\n \tcp.use_shell = 1;\n@@ -247,7 +255,7 @@ static char *apply_command(const char *command, const char *arg)\n \n static void apply_item_command(struct trailer_item *in_tok, struct arg_item *arg_tok)\n {\n-\tif (arg_tok->conf.command) {\n+\tif (arg_tok->conf.command || arg_tok->conf.cmd) {\n \t\tconst char *arg;\n \t\tif (arg_tok->value && arg_tok->value[0]) {\n \t\t\targ = arg_tok->value;\n@@ -257,7 +265,7 @@ static void apply_item_command(struct trailer_item *in_tok, struct arg_item *arg\n \t\t\telse\n \t\t\t\targ = xstrdup(\"\");\n \t\t}\n-\t\targ_tok->value = apply_command(arg_tok->conf.command, arg);\n+\t\targ_tok->value = apply_command(&arg_tok->conf, arg);\n \t\tfree((char *)arg);\n \t}\n }\n@@ -430,6 +438,7 @@ static void duplicate_conf(struct conf_info *dst, const struct conf_info *src)\n \tdst->name = xstrdup_or_null(src->name);\n \tdst->key = xstrdup_or_null(src->key);\n \tdst->command = xstrdup_or_null(src->command);\n+\tdst->cmd = xstrdup_or_null(src->cmd);\n }\n \n static struct arg_item *get_conf_item(const char *name)\n@@ -454,8 +463,8 @@ static struct arg_item *get_conf_item(const char *name)\n \treturn item;\n }\n \n-enum trailer_info_type { TRAILER_KEY, TRAILER_COMMAND, TRAILER_WHERE,\n-\t\t\t TRAILER_IF_EXISTS, TRAILER_IF_MISSING };\n+enum trailer_info_type { TRAILER_KEY, TRAILER_COMMAND, TRAILER_CMD,\n+\t\t\tTRAILER_WHERE, TRAILER_IF_EXISTS, TRAILER_IF_MISSING };\n \n static struct {\n \tconst char *name;\n@@ -463,6 +472,7 @@ static struct {\n } trailer_config_items[] = {\n \t{ \"key\", TRAILER_KEY },\n \t{ \"command\", TRAILER_COMMAND },\n+\t{ \"cmd\", TRAILER_CMD },\n \t{ \"where\", TRAILER_WHERE },\n \t{ \"ifexists\", TRAILER_IF_EXISTS },\n \t{ \"ifmissing\", TRAILER_IF_MISSING }\n@@ -542,6 +552,11 @@ static int git_trailer_config(const char *conf_key, const char *value, void *cb)\n \t\t\twarning(_(\"more than one %s\"), conf_key);\n \t\tconf->command = xstrdup(value);\n \t\tbreak;\n+\tcase TRAILER_CMD:\n+\t\tif (conf->cmd)\n+\t\t\twarning(_(\"more than one %s\"), conf_key);\n+\t\tconf->cmd = xstrdup(value);\n+\t\tbreak;\n \tcase TRAILER_WHERE:\n \t\tif (trailer_set_where(&conf->where, value))\n \t\t\twarning(_(\"unknown value '%s' for key '%s'\"), value, conf_key);\n@@ -708,7 +723,7 @@ static void process_command_line_args(struct list_head *arg_head,\n \t/* Add an arg item for each configured trailer with a command */\n \tlist_for_each(pos, &conf_head) {\n \t\titem = list_entry(pos, struct arg_item, list);\n-\t\tif (item->conf.command)\n+\t\tif (item->conf.cmd || item->conf.command)\n \t\t\tadd_arg_item(arg_head,\n \t\t\t\t     xstrdup(token_from_item(item, NULL)),\n \t\t\t\t     xstrdup(\"\"),\n\nbase-commit: 142430338477d9d1bb25be66267225fb58498d92\n-- \ngitgitgadget\n"},{"id":"420305","messageId":"xmqqk0psqxqo.fsf@gitster.g","threadId":"55371","inReplyTo":"pull.913.v4.git.1616775185562.gitgitgadget@gmail.com","subject":"Re: [PATCH v4] [GSOC]trailer: pass arg as positional parameter","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2021-03-27T18:04:31Z","receivedAt":"2021-03-27T18:05:41Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"\"ZheNing Hu via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n\n> @@ -252,6 +252,16 @@ also be executed for each of these arguments. And the <value> part of\n>  these arguments, if any, will be used to replace the `$ARG` string in\n>  the command.\n>  \n> +trailer.<token>.cmd::\n> +\tThe command specified by this configuration variable is run\n> +\twith a single parameter, which is the <value> part of an\n> +\texisting trailer with the same <token>.  The output from the\n> +\tcommand is then used as the value for the <token> in the\n> +\tresulting trailer.\n> +\tThe command is expected to replace `trailer.<token>.cmd`.\n> +\tWhen both .cmd and .command are given for the same <token>,\n> +        .cmd is used and .command is ignored.\n\nChristian, because \".cmd\" is trying to eventually replace it, I find\nit a bit disturbing that the description we give here looks a lot\nsmaller compared to the one for \".command\".  I am afraid that we may\nhave failed to reproduce something important from the description of\nthe \".command\" for the above; care to rend a hand or two here to\ncomplete the description?\n\nAs I cannot grok what the description for \".command\" is trying to\nsay, especially around this part:\n\n    When this option is specified, the behavior is as if a special\n    '<token>=<value>' argument were added at the beginning of the command\n    line, where <value> is ...\n\nand\n\n    If some '<token>=<value>' arguments are also passed on the command\n    line, when a 'trailer.<token>.command' is configured, the command will\n    also be executed for each of these arguments.\n\nI cannot quite judge if what we came up with in the above\ndescription is sufficient.\n\n> -* Configure a 'sign' trailer with a command to automatically add a\n> +* Configure a 'sign' trailer with a cmd to automatically add a\n>    'Signed-off-by: ' with the author information only if there is no\n>    'Signed-off-by: ' already, and show how it works:\n>  +\n> @@ -309,7 +319,7 @@ $ git interpret-trailers --trailer 'Cc: Alice <alice@example.com>' --trailer 'Re\n>  $ git config trailer.sign.key \"Signed-off-by: \"\n>  $ git config trailer.sign.ifmissing add\n>  $ git config trailer.sign.ifexists doNothing\n> -$ git config trailer.sign.command 'echo \"$(git config user.name) <$(git config user.email)>\"'\n> +$ git config trailer.sign.cmd 'echo \"$(git config user.name) <$(git config user.email)>\"'\n>  $ git interpret-trailers <<EOF\n>  > EOF\n\nThis change would definitely be needed when the support for\n\".command\" is removed after deprecation period.  As it does not take\nany argument, .cmd and .command should behave identically, so making\nthis change now, without waiting, may make sense.\n\n> @@ -333,14 +343,14 @@ subject\n>  Fix #42\n>  ------------\n>  \n> -* Configure a 'see' trailer with a command to show the subject of a\n> +* Configure a 'see' trailer with a cmd to show the subject of a\n>    commit that is related, and show how it works:\n>  +\n>  ------------\n>  $ git config trailer.see.key \"See-also: \"\n>  $ git config trailer.see.ifExists \"replace\"\n>  $ git config trailer.see.ifMissing \"doNothing\"\n> -$ git config trailer.see.command \"git log -1 --oneline --format=\\\"%h (%s)\\\" --abbrev-commit --abbrev=14 \\$ARG\"\n> +$ git config trailer.see.cmd \"test -n \\\"\\$1\\\" && git log -1 --oneline --format=\\\"%h (%s)\\\" --abbrev-commit --abbrev=14 \\\"\\$1\\\"|| true \"\n>  $ git interpret-trailers <<EOF\n>  > subject\n\nThis, too, but until \".command\" is removed, wouldn't it be better\nfor readers to keep both variants, as the distinction between $ARG\nand $1 needs to be illustrated?\n\nBesides, the examples given here are not equivalent.  The original\nassumes that ARG is there, or it is OK to default to HEAD; the new\none gives no output when $ARG/$1 is not supplied.  It would confuse\nreaders to give two too-similar-but-subtly-different examles, as\nthey will be forced to wonder if the difference is something needed\nto transition from .command to .cmd (and I am guessing that it is\nnot).\n\nRewriting both to use \"--pretty=reference\" may be worth doing.  As\ncan be seen in these examples:\n\ngit show -s --pretty=reference \\$1\ngit log -1 --oneline --format=\\\"%h (%s)\\\" --abbrev-commit --abbrev=14 \\$1\n\nthat it makes the result much easier to read.\n\nThanks.  Do not send a reroll prematurely; I want to see area\nexpert's input at this point.\n\n\n"},{"id":"420312","messageId":"CAP8UFD26YaoDGs_8eUhuRCytDMyOhFM-Egs-Srk83iMpZxbKxA@mail.gmail.com","threadId":"55371","inReplyTo":"xmqqk0psqxqo.fsf@gitster.g","subject":"Re: [PATCH v4] [GSOC]trailer: pass arg as positional parameter","fromName":"Christian Couder","fromEmail":"christian.couder@gmail.com","sentAt":"2021-03-27T19:53:11Z","receivedAt":"2021-03-27T19:54:20Z","isPatch":true,"sender":{"key":"christian.couder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/208954?v=4"},"body":"On Sat, Mar 27, 2021 at 7:04 PM Junio C Hamano <gitster@pobox.com> wrote:\n>\n> \"ZheNing Hu via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n>\n> > @@ -252,6 +252,16 @@ also be executed for each of these arguments. And the <value> part of\n> >  these arguments, if any, will be used to replace the `$ARG` string in\n> >  the command.\n> >\n> > +trailer.<token>.cmd::\n> > +     The command specified by this configuration variable is run\n> > +     with a single parameter, which is the <value> part of an\n> > +     existing trailer with the same <token>.  The output from the\n> > +     command is then used as the value for the <token> in the\n> > +     resulting trailer.\n> > +     The command is expected to replace `trailer.<token>.cmd`.\n\ns/trailer.<token>.cmd/trailer.<token>.command/\n\n> > +     When both .cmd and .command are given for the same <token>,\n> > +        .cmd is used and .command is ignored.\n>\n> Christian, because \".cmd\" is trying to eventually replace it, I find\n> it a bit disturbing that the description we give here looks a lot\n> smaller compared to the one for \".command\".  I am afraid that we may\n> have failed to reproduce something important from the description of\n> the \".command\" for the above; care to rend a hand or two here to\n> complete the description?\n\nYeah, sure. I just saw that you already asked about this in this\nthread. Sorry for not answering earlier.\n\n> As I cannot grok what the description for \".command\" is trying to\n> say, especially around this part:\n>\n>     When this option is specified, the behavior is as if a special\n>     '<token>=<value>' argument were added at the beginning of the command\n>     line, where <value> is ...\n\nThis is because when a number of trailers are passed on the command\nline, and some other trailers are in the input file, the order in\nwhich the different trailers are processed and their priorities can be\nimportant. So by saying the above, people can get an idea about at\nwhich point and with which priority a trailer coming from such a\nconfig option will be processed.\n\n> and\n>\n>     If some '<token>=<value>' arguments are also passed on the command\n>     line, when a 'trailer.<token>.command' is configured, the command will\n>     also be executed for each of these arguments.\n\nYeah, this means that when a 'trailer.foo.command' is configured, it\nis always executed at least once. The first time it is executed, it is\npassed nothing ($ARG is replaced with the empty string). Then for each\n'foo=<value>' argument passed on the command line, it is executed once\nmore with $ARG replaced by <value>.\n\nThe reason it is always executed first with $ARG replaced with the\nempty string is that this way it makes it possible to set up commands\nthat will always be executed when `git interpret-trailers` is run.\nThis makes it possible to automatically add some trailers if they are\nmissing for example.\n\nAnother way to do it would be to have another config option called\n`trailer.<token>.alwaysRunCmd` to tell if the cmd specified by\n`trailer.<token>.cmd` should be run even if no '<token>=<value>'\nargument is passed on the command line. As we are introducing\n`trailer.<token>.cmd`, it's a good time to wonder if this would be a\nbetter design. But this issue is quite complex, because of the fact\nthat 'trailer.<token>.ifMissing' and 'trailer.<token>.ifExists' also\ntake a part in deciding if the command will be run.\n\nThis mechanism is the reason why a trick, when setting up a\n'trailer.foo.command' trailer, is to also set 'trailer.foo.ifexists'\nto \"replace\", so that the first time the command is run (with $ARG\nreplaced with the empty string) it will add a foo trailer with a\ndefault value, and if it is run another time, because a 'foo=bar'\nargument is passed on the command line, then the trailer with the\ndefault value will be replaced by the value computed from running the\ncommand again with $ARG replaced with \"bar\".\n\nAnother trick is to have the command output nothing when $ARG is the\nempty string along with using --trim-empty. This way the command will\ncreate an empty trailer, when it is run the first time, and if it's\nnot another time, then this empty trailer will be removed because of\n--trim-empty.\n\n> I cannot quite judge if what we came up with in the above\n> description is sufficient.\n\nI don't think it's sufficient. I think that, while we are at it, a bit\nmore thinking/discussion is required to make sure we want to keep the\nsame design as 'trailer.<token>.command'.\n\n> > -* Configure a 'sign' trailer with a command to automatically add a\n> > +* Configure a 'sign' trailer with a cmd to automatically add a\n> >    'Signed-off-by: ' with the author information only if there is no\n> >    'Signed-off-by: ' already, and show how it works:\n> >  +\n> > @@ -309,7 +319,7 @@ $ git interpret-trailers --trailer 'Cc: Alice <alice@example.com>' --trailer 'Re\n> >  $ git config trailer.sign.key \"Signed-off-by: \"\n> >  $ git config trailer.sign.ifmissing add\n> >  $ git config trailer.sign.ifexists doNothing\n> > -$ git config trailer.sign.command 'echo \"$(git config user.name) <$(git config user.email)>\"'\n> > +$ git config trailer.sign.cmd 'echo \"$(git config user.name) <$(git config user.email)>\"'\n> >  $ git interpret-trailers <<EOF\n> >  > EOF\n>\n> This change would definitely be needed when the support for\n> \".command\" is removed after deprecation period.  As it does not take\n> any argument, .cmd and .command should behave identically, so making\n> this change now, without waiting, may make sense.\n\nBy the way the above example is an example of why we might want any\nconfigured command to be executed at least once, even when no\ncorresponding '<token>=<value>' argument is passed on the command\nline.\n\n> > @@ -333,14 +343,14 @@ subject\n> >  Fix #42\n> >  ------------\n> >\n> > -* Configure a 'see' trailer with a command to show the subject of a\n> > +* Configure a 'see' trailer with a cmd to show the subject of a\n> >    commit that is related, and show how it works:\n> >  +\n> >  ------------\n> >  $ git config trailer.see.key \"See-also: \"\n> >  $ git config trailer.see.ifExists \"replace\"\n> >  $ git config trailer.see.ifMissing \"doNothing\"\n> > -$ git config trailer.see.command \"git log -1 --oneline --format=\\\"%h (%s)\\\" --abbrev-commit --abbrev=14 \\$ARG\"\n> > +$ git config trailer.see.cmd \"test -n \\\"\\$1\\\" && git log -1 --oneline --format=\\\"%h (%s)\\\" --abbrev-commit --abbrev=14 \\\"\\$1\\\"|| true \"\n> >  $ git interpret-trailers <<EOF\n> >  > subject\n>\n> This, too, but until \".command\" is removed, wouldn't it be better\n> for readers to keep both variants, as the distinction between $ARG\n> and $1 needs to be illustrated?\n>\n> Besides, the examples given here are not equivalent.  The original\n> assumes that ARG is there, or it is OK to default to HEAD; the new\n> one gives no output when $ARG/$1 is not supplied.\n\nYeah, I agree they are not equivalent.\n\n> It would confuse\n> readers to give two too-similar-but-subtly-different examles, as\n> they will be forced to wonder if the difference is something needed\n> to transition from .command to .cmd (and I am guessing that it is\n> not).\n\nI agree.\n\n> Rewriting both to use \"--pretty=reference\" may be worth doing.  As\n> can be seen in these examples:\n>\n> git show -s --pretty=reference \\$1\n> git log -1 --oneline --format=\\\"%h (%s)\\\" --abbrev-commit --abbrev=14 \\$1\n>\n> that it makes the result much easier to read.\n\nYeah, thanks for the good suggestion.\n"},{"id":"420381","messageId":"CAOLTT8Ryrp90xJ0=Y2avndYpf_2JvabK=XAuc+hactk8idyv1w@mail.gmail.com","threadId":"55371","inReplyTo":"CAP8UFD26YaoDGs_8eUhuRCytDMyOhFM-Egs-Srk83iMpZxbKxA@mail.gmail.com","subject":"Re: [PATCH v4] [GSOC]trailer: pass arg as positional parameter","fromName":"ZheNing Hu","fromEmail":"adlternative@gmail.com","sentAt":"2021-03-28T10:46:42Z","receivedAt":"2021-03-28T10:47:46Z","isPatch":true,"sender":{"key":"adlternative@gmail.com","avatar":"https://avatars.githubusercontent.com/u/58138461?v=4"},"body":"Christian Couder <christian.couder@gmail.com> 于2021年3月28日周日 上午3:53写道：\n>\n> On Sat, Mar 27, 2021 at 7:04 PM Junio C Hamano <gitster@pobox.com> wrote:\n> >\n> > \"ZheNing Hu via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n> >\n> > > @@ -252,6 +252,16 @@ also be executed for each of these arguments. And the <value> part of\n> > >  these arguments, if any, will be used to replace the `$ARG` string in\n> > >  the command.\n> > >\n> > > +trailer.<token>.cmd::\n> > > +     The command specified by this configuration variable is run\n> > > +     with a single parameter, which is the <value> part of an\n> > > +     existing trailer with the same <token>.  The output from the\n> > > +     command is then used as the value for the <token> in the\n> > > +     resulting trailer.\n> > > +     The command is expected to replace `trailer.<token>.cmd`.\n>\n> s/trailer.<token>.cmd/trailer.<token>.command/\n>\n> > > +     When both .cmd and .command are given for the same <token>,\n> > > +        .cmd is used and .command is ignored.\n> >\n> > Christian, because \".cmd\" is trying to eventually replace it, I find\n> > it a bit disturbing that the description we give here looks a lot\n> > smaller compared to the one for \".command\".  I am afraid that we may\n> > have failed to reproduce something important from the description of\n> > the \".command\" for the above; care to rend a hand or two here to\n> > complete the description?\n>\n> Yeah, sure. I just saw that you already asked about this in this\n> thread. Sorry for not answering earlier.\n>\n> > As I cannot grok what the description for \".command\" is trying to\n> > say, especially around this part:\n> >\n> >     When this option is specified, the behavior is as if a special\n> >     '<token>=<value>' argument were added at the beginning of the command\n> >     line, where <value> is ...\n>\n> This is because when a number of trailers are passed on the command\n> line, and some other trailers are in the input file, the order in\n> which the different trailers are processed and their priorities can be\n> important. So by saying the above, people can get an idea about at\n> which point and with which priority a trailer coming from such a\n> config option will be processed.\n>\n\nThis shows that .command itself has the characteristic of alwaysRun:\neven if <token> <value> is not specified, the shell in .command will be\nexecuted at least once, $ARG is empty by default. This is why I asked\n`log --author=$ARG -1` will show the last commit identity when `--trailer`\n is not used.\n\n> > and\n> >\n> >     If some '<token>=<value>' arguments are also passed on the command\n> >     line, when a 'trailer.<token>.command' is configured, the command will\n> >     also be executed for each of these arguments.\n>\n> Yeah, this means that when a 'trailer.foo.command' is configured, it\n> is always executed at least once. The first time it is executed, it is\n> passed nothing ($ARG is replaced with the empty string). Then for each\n> 'foo=<value>' argument passed on the command line, it is executed once\n> more with $ARG replaced by <value>.\n>\n> The reason it is always executed first with $ARG replaced with the\n> empty string is that this way it makes it possible to set up commands\n> that will always be executed when `git interpret-trailers` is run.\n> This makes it possible to automatically add some trailers if they are\n> missing for example.\n>\n\nYes, $ARG or $1 are always exist because of:\n\n               arg = xstrdup(\"\");\n\nso I think maybe we don't even need this judge in `apply_command`?\n+               if (arg)\n+                       strvec_push(&cp.args, arg);\n\n> Another way to do it would be to have another config option called\n> `trailer.<token>.alwaysRunCmd` to tell if the cmd specified by\n> `trailer.<token>.cmd` should be run even if no '<token>=<value>'\n> argument is passed on the command line. As we are introducing\n> `trailer.<token>.cmd`, it's a good time to wonder if this would be a\n> better design. But this issue is quite complex, because of the fact\n> that 'trailer.<token>.ifMissing' and 'trailer.<token>.ifExists' also\n> take a part in deciding if the command will be run.\n>\n\nIn fact, I would prefer this design, because if I don’t add any trailers,\nthe trailer.<token>.command I set will be executed, which may be very\ndistressing sometimes, and `alwayRunCmd` is the user I hope that \"trailers\"\ncan be added automatically, and other trailers.<token>.command will not be\nexecuted automatically. This allows the user to reasonably configure the\ncommands that need to be executed. This must be a very comfortable thing.\n\nBut as you said, to disable the automatic addition in the original .command\nand use the new .alwaysRunCmd, I’m afraid there are a lot of things to consider.\nPerhaps future series of patches can be considered to do it.\n\n> This mechanism is the reason why a trick, when setting up a\n> 'trailer.foo.command' trailer, is to also set 'trailer.foo.ifexists'\n> to \"replace\", so that the first time the command is run (with $ARG\n> replaced with the empty string) it will add a foo trailer with a\n> default value, and if it is run another time, because a 'foo=bar'\n> argument is passed on the command line, then the trailer with the\n> default value will be replaced by the value computed from running the\n> command again with $ARG replaced with \"bar\".\n>\n> Another trick is to have the command output nothing when $ARG is the\n> empty string along with using --trim-empty. This way the command will\n> create an empty trailer, when it is run the first time, and if it's\n> not another time, then this empty trailer will be removed because of\n> --trim-empty.\n>\n\nIt looks very practical indeed.\n\n> > I cannot quite judge if what we came up with in the above\n> > description is sufficient.\n>\n> I don't think it's sufficient. I think that, while we are at it, a bit\n> more thinking/discussion is required to make sure we want to keep the\n> same design as 'trailer.<token>.command'.\n\nSure. I agree that more discussion is needed.\nI think if the documents that once belonged to .command are copied to .cmd,\nwill the readers be too burdensome to read them? Will it be better to migrate\nits documentation until we completely delete .command?\n\n>\n> > > -* Configure a 'sign' trailer with a command to automatically add a\n> > > +* Configure a 'sign' trailer with a cmd to automatically add a\n> > >    'Signed-off-by: ' with the author information only if there is no\n> > >    'Signed-off-by: ' already, and show how it works:\n> > >  +\n> > > @@ -309,7 +319,7 @@ $ git interpret-trailers --trailer 'Cc: Alice <alice@example.com>' --trailer 'Re\n> > >  $ git config trailer.sign.key \"Signed-off-by: \"\n> > >  $ git config trailer.sign.ifmissing add\n> > >  $ git config trailer.sign.ifexists doNothing\n> > > -$ git config trailer.sign.command 'echo \"$(git config user.name) <$(git config user.email)>\"'\n> > > +$ git config trailer.sign.cmd 'echo \"$(git config user.name) <$(git config user.email)>\"'\n> > >  $ git interpret-trailers <<EOF\n> > >  > EOF\n> >\n> > This change would definitely be needed when the support for\n> > \".command\" is removed after deprecation period.  As it does not take\n> > any argument, .cmd and .command should behave identically, so making\n> > this change now, without waiting, may make sense.\n>\n> By the way the above example is an example of why we might want any\n> configured command to be executed at least once, even when no\n> corresponding '<token>=<value>' argument is passed on the command\n> line.\n\nAlready noticed that.\n\n>\n> > > @@ -333,14 +343,14 @@ subject\n> > >  Fix #42\n> > >  ------------\n> > >\n> > > -* Configure a 'see' trailer with a command to show the subject of a\n> > > +* Configure a 'see' trailer with a cmd to show the subject of a\n> > >    commit that is related, and show how it works:\n> > >  +\n> > >  ------------\n> > >  $ git config trailer.see.key \"See-also: \"\n> > >  $ git config trailer.see.ifExists \"replace\"\n> > >  $ git config trailer.see.ifMissing \"doNothing\"\n> > > -$ git config trailer.see.command \"git log -1 --oneline --format=\\\"%h (%s)\\\" --abbrev-commit --abbrev=14 \\$ARG\"\n> > > +$ git config trailer.see.cmd \"test -n \\\"\\$1\\\" && git log -1 --oneline --format=\\\"%h (%s)\\\" --abbrev-commit --abbrev=14 \\\"\\$1\\\"|| true \"\n> > >  $ git interpret-trailers <<EOF\n> > >  > subject\n> >\n> > This, too, but until \".command\" is removed, wouldn't it be better\n> > for readers to keep both variants, as the distinction between $ARG\n> > and $1 needs to be illustrated?\n\nSo the correct solution should be to keep the original .command Examples,\nand then give the .cmd examples again.\n\n> >\n> > Besides, the examples given here are not equivalent.  The original\n> > assumes that ARG is there, or it is OK to default to HEAD; the new\n> > one gives no output when $ARG/$1 is not supplied.\n>\n> Yeah, I agree they are not equivalent.\n>\n> > It would confuse\n> > readers to give two too-similar-but-subtly-different examles, as\n> > they will be forced to wonder if the difference is something needed\n> > to transition from .command to .cmd (and I am guessing that it is\n> > not).\n>\n> I agree.\n\nOK...I will modify it.\n\n>\n> > Rewriting both to use \"--pretty=reference\" may be worth doing.  As\n> > can be seen in these examples:\n> >\n> > git show -s --pretty=reference \\$1\n> > git log -1 --oneline --format=\\\"%h (%s)\\\" --abbrev-commit --abbrev=14 \\$1\n> >\n> > that it makes the result much easier to read.\n>\n> Yeah, thanks for the good suggestion.\n\nYes, `--pretty=reference` is similar to `--format=\"%h(%s)\"` and provides better\nreadability.\n\nThanks,Junio and Christian!\n\n--\nZheNing Hu\n"},{"id":"420435","messageId":"CAP8UFD0OMJfkuX_JoDros7h0B20D8sm0ZbtkVpL3dCYRV_M=OA@mail.gmail.com","threadId":"55371","inReplyTo":"CAOLTT8Ryrp90xJ0=Y2avndYpf_2JvabK=XAuc+hactk8idyv1w@mail.gmail.com","subject":"Re: [PATCH v4] [GSOC]trailer: pass arg as positional parameter","fromName":"Christian Couder","fromEmail":"christian.couder@gmail.com","sentAt":"2021-03-29T09:04:50Z","receivedAt":"2021-03-29T09:06:52Z","isPatch":true,"sender":{"key":"christian.couder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/208954?v=4"},"body":"On Sun, Mar 28, 2021 at 12:46 PM ZheNing Hu <adlternative@gmail.com> wrote:\n>\n> Christian Couder <christian.couder@gmail.com> 于2021年3月28日周日 上午3:53写道：\n> >\n> > On Sat, Mar 27, 2021 at 7:04 PM Junio C Hamano <gitster@pobox.com> wrote:\n\n> > > As I cannot grok what the description for \".command\" is trying to\n> > > say, especially around this part:\n> > >\n> > >     When this option is specified, the behavior is as if a special\n> > >     '<token>=<value>' argument were added at the beginning of the command\n> > >     line, where <value> is ...\n> >\n> > This is because when a number of trailers are passed on the command\n> > line, and some other trailers are in the input file, the order in\n> > which the different trailers are processed and their priorities can be\n> > important. So by saying the above, people can get an idea about at\n> > which point and with which priority a trailer coming from such a\n> > config option will be processed.\n>\n> This shows that .command itself has the characteristic of alwaysRun:\n> even if <token> <value> is not specified, the shell in .command will be\n> executed at least once, $ARG is empty by default. This is why I asked\n> `log --author=$ARG -1` will show the last commit identity when `--trailer`\n>  is not used.\n\nYeah, that's the reason.\n\n> > > and\n> > >\n> > >     If some '<token>=<value>' arguments are also passed on the command\n> > >     line, when a 'trailer.<token>.command' is configured, the command will\n> > >     also be executed for each of these arguments.\n> >\n> > Yeah, this means that when a 'trailer.foo.command' is configured, it\n> > is always executed at least once. The first time it is executed, it is\n> > passed nothing ($ARG is replaced with the empty string). Then for each\n> > 'foo=<value>' argument passed on the command line, it is executed once\n> > more with $ARG replaced by <value>.\n> >\n> > The reason it is always executed first with $ARG replaced with the\n> > empty string is that this way it makes it possible to set up commands\n> > that will always be executed when `git interpret-trailers` is run.\n> > This makes it possible to automatically add some trailers if they are\n> > missing for example.\n>\n> Yes, $ARG or $1 are always exist because of:\n>\n>                arg = xstrdup(\"\");\n>\n> so I think maybe we don't even need this judge in `apply_command`?\n> +               if (arg)\n> +                       strvec_push(&cp.args, arg);\n\nYeah, I haven't looked at the code, but that might be a good\nsimplification. If you work on this, please submit it in a separate\ncommit.\n\n> > Another way to do it would be to have another config option called\n> > `trailer.<token>.alwaysRunCmd` to tell if the cmd specified by\n> > `trailer.<token>.cmd` should be run even if no '<token>=<value>'\n> > argument is passed on the command line. As we are introducing\n> > `trailer.<token>.cmd`, it's a good time to wonder if this would be a\n> > better design. But this issue is quite complex, because of the fact\n> > that 'trailer.<token>.ifMissing' and 'trailer.<token>.ifExists' also\n> > take a part in deciding if the command will be run.\n\nActually after thinking about it, I think it might be better, instead\nof `trailer.<token>.alwaysRunCmd`, to add something like\n`trailer.<token>.runMode` that could take multiple values like:\n\n- \"beforeCLI\": would make it run once, like \".command\" does now before\nany CLI trailer are processed\n\n- \"forEachCLIToken\": would make it run once for each trailer that has\nthe token, like \".command\" also does now, the difference would be that\nthe value for the token would be passed in the $1 argument\n\n- \"afterCLI\": would make it run once after all the CLI trailers have\nbeen processed and it could pass the different values for the token if\nany in different arguments: $1, $2, $3, ...\n\nThis would make it possible to extend later if the need arises for\nmore different times or ways to run configured commands.\n\n> In fact, I would prefer this design, because if I don’t add any trailers,\n> the trailer.<token>.command I set will be executed, which may be very\n> distressing sometimes, and `alwayRunCmd` is the user I hope that \"trailers\"\n> can be added automatically, and other trailers.<token>.command will not be\n> executed automatically. This allows the user to reasonably configure the\n> commands that need to be executed. This must be a very comfortable thing.\n\nI agree that it should be easier and more straightforward, than it is\nnow, to configure this.\n\n> But as you said, to disable the automatic addition in the original .command\n> and use the new .alwaysRunCmd, I’m afraid there are a lot of things to consider.\n> Perhaps future series of patches can be considered to do it.\n\nYeah, support for `trailer.<token>.runMode` might be added in\ndifferent commits at least and possibly later in a different patch\nseries. There are the following issues to resolve, though, if we want\nto focus only on a new \".cmd\" config option:\n\n- how and when should it run by default,\n- how to explain that in the doc, and maybe\n- how to improve the current description of what happens for \".command\"\n\n> > This mechanism is the reason why a trick, when setting up a\n> > 'trailer.foo.command' trailer, is to also set 'trailer.foo.ifexists'\n> > to \"replace\", so that the first time the command is run (with $ARG\n> > replaced with the empty string) it will add a foo trailer with a\n> > default value, and if it is run another time, because a 'foo=bar'\n> > argument is passed on the command line, then the trailer with the\n> > default value will be replaced by the value computed from running the\n> > command again with $ARG replaced with \"bar\".\n> >\n> > Another trick is to have the command output nothing when $ARG is the\n> > empty string along with using --trim-empty. This way the command will\n> > create an empty trailer, when it is run the first time, and if it's\n> > not another time, then this empty trailer will be removed because of\n> > --trim-empty.\n> >\n>\n> It looks very practical indeed.\n>\n> > > I cannot quite judge if what we came up with in the above\n> > > description is sufficient.\n> >\n> > I don't think it's sufficient. I think that, while we are at it, a bit\n> > more thinking/discussion is required to make sure we want to keep the\n> > same design as 'trailer.<token>.command'.\n>\n> Sure. I agree that more discussion is needed.\n> I think if the documents that once belonged to .command are copied to .cmd,\n> will the readers be too burdensome to read them? Will it be better to migrate\n> its documentation until we completely delete .command?\n\nMy opinion (if we focus only on adding \".cmd\") is that:\n\n- for simplicity for now it should run at the same time as \".command\",\nthe only difference being how the argument is passed (using $1 instead\nof textually replacing $ARG)\n- the doc for \".command\" should be first improved if possible, and\nthen moved over to \".cmd\" saying for \".command\" that \".command\" is\ndeprecated in favor of \".cmd\" but otherwise works as \".cmd\" except\nthat instead using $1 the value is passed by textually replacing $ARG\nwhich could be a safety and correctness issue.\n\nAnother way to work on all this, would be to first work on adding\nsupport for `trailer.<token>.runMode` and on improving existing\ndocumentation, and then to add \".cmd\", which could then by default use\na different \".runMode\" than \".command\".\n\n> > > This, too, but until \".command\" is removed, wouldn't it be better\n> > > for readers to keep both variants, as the distinction between $ARG\n> > > and $1 needs to be illustrated?\n>\n> So the correct solution should be to keep the original .command Examples,\n> and then give the .cmd examples again.\n\nMaybe we could take advantage of \".cmd\" to show other nice\npossibilities to use all of this. Especially if support for `git\ncommit --trailer ...` is already merged, we might be able to use it in\nthose examples, or perhaps add some examples to the git commit doc.\n\nBest,\nChristian.\n"},{"id":"420474","messageId":"CAOLTT8RAe0HhTL6p6MXeqbSazaJF0=PtnDKvh06-FXXBB+w94A@mail.gmail.com","threadId":"55371","inReplyTo":"CAP8UFD0OMJfkuX_JoDros7h0B20D8sm0ZbtkVpL3dCYRV_M=OA@mail.gmail.com","subject":"Re: [PATCH v4] [GSOC]trailer: pass arg as positional parameter","fromName":"ZheNing Hu","fromEmail":"adlternative@gmail.com","sentAt":"2021-03-29T13:43:54Z","receivedAt":"2021-03-29T13:44:59Z","isPatch":true,"sender":{"key":"adlternative@gmail.com","avatar":"https://avatars.githubusercontent.com/u/58138461?v=4"},"body":"Christian Couder <christian.couder@gmail.com> 于2021年3月29日周一 下午5:05写道：\n>\n> >\n> > Yes, $ARG or $1 are always exist because of:\n> >\n> >                arg = xstrdup(\"\");\n> >\n> > so I think maybe we don't even need this judge in `apply_command`?\n> > +               if (arg)\n> > +                       strvec_push(&cp.args, arg);\n>\n> Yeah, I haven't looked at the code, but that might be a good\n> simplification. If you work on this, please submit it in a separate\n> commit.\n>\n\nWell, if necessary, I'll put it in another commit, maybe I should double check\nto see if there's anything special going on.\n\n> > > Another way to do it would be to have another config option called\n> > > `trailer.<token>.alwaysRunCmd` to tell if the cmd specified by\n> > > `trailer.<token>.cmd` should be run even if no '<token>=<value>'\n> > > argument is passed on the command line. As we are introducing\n> > > `trailer.<token>.cmd`, it's a good time to wonder if this would be a\n> > > better design. But this issue is quite complex, because of the fact\n> > > that 'trailer.<token>.ifMissing' and 'trailer.<token>.ifExists' also\n> > > take a part in deciding if the command will be run.\n>\n> Actually after thinking about it, I think it might be better, instead\n> of `trailer.<token>.alwaysRunCmd`, to add something like\n> `trailer.<token>.runMode` that could take multiple values like:\n>\n\nIf really can achieve it is certainly better than 'alwaysRunCmd'.\nThe following three small configuration options look delicious.\nBut I think it needs to be discussed in more detail:\n\n> - \"beforeCLI\": would make it run once, like \".command\" does now before\n> any CLI trailer are processed\n>\n\nDoes \"beforeCLI\" handle all trailers? Or is it just doing something to add empty\nvalue trailers?\n\n> - \"forEachCLIToken\": would make it run once for each trailer that has\n> the token, like \".command\" also does now, the difference would be that\n> the value for the token would be passed in the $1 argument\n>\n\nThis is exactly same as before.\n\n> - \"afterCLI\": would make it run once after all the CLI trailers have\n> been processed and it could pass the different values for the token if\n> any in different arguments: $1, $2, $3, ...\n>\n\nI might get a little confused here: What's the input for $1,$2,$3?\nIs users more interested in dealing with trailers value or a line of the\ntrailer?\n\n> This would make it possible to extend later if the need arises for\n> more different times or ways to run configured commands.\n>\n> > In fact, I would prefer this design, because if I don’t add any trailers,\n> > the trailer.<token>.command I set will be executed, which may be very\n> > distressing sometimes, and `alwayRunCmd` is the user I hope that \"trailers\"\n> > can be added automatically, and other trailers.<token>.command will not be\n> > executed automatically. This allows the user to reasonably configure the\n> > commands that need to be executed. This must be a very comfortable thing.\n>\n> I agree that it should be easier and more straightforward, than it is\n> now, to configure this.\n>\n> > But as you said, to disable the automatic addition in the original .command\n> > and use the new .alwaysRunCmd, I’m afraid there are a lot of things to consider.\n> > Perhaps future series of patches can be considered to do it.\n>\n> Yeah, support for `trailer.<token>.runMode` might be added in\n> different commits at least and possibly later in a different patch\n> series. There are the following issues to resolve, though, if we want\n> to focus only on a new \".cmd\" config option:\n>\n> - how and when should it run by default,\n\nDo you mean that \".cmd\" can get rid of the \".command\" auto-add problem\nin this patch series?\nThis might be a good idea if I can add the three modes you mentioned above\nin the later patch series.\n\n> - how to explain that in the doc, and maybe\n> - how to improve the current description of what happens for \".command\"\n>\n> > > This mechanism is the reason why a trick, when setting up a\n> > > 'trailer.foo.command' trailer, is to also set 'trailer.foo.ifexists'\n> > > to \"replace\", so that the first time the command is run (with $ARG\n> > > replaced with the empty string) it will add a foo trailer with a\n> > > default value, and if it is run another time, because a 'foo=bar'\n> > > argument is passed on the command line, then the trailer with the\n> > > default value will be replaced by the value computed from running the\n> > > command again with $ARG replaced with \"bar\".\n> > >\n> > > Another trick is to have the command output nothing when $ARG is the\n> > > empty string along with using --trim-empty. This way the command will\n> > > create an empty trailer, when it is run the first time, and if it's\n> > > not another time, then this empty trailer will be removed because of\n> > > --trim-empty.\n> > >\n> >\n> > It looks very practical indeed.\n> >\n> > > > I cannot quite judge if what we came up with in the above\n> > > > description is sufficient.\n> > >\n> > > I don't think it's sufficient. I think that, while we are at it, a bit\n> > > more thinking/discussion is required to make sure we want to keep the\n> > > same design as 'trailer.<token>.command'.\n> >\n> > Sure. I agree that more discussion is needed.\n> > I think if the documents that once belonged to .command are copied to .cmd,\n> > will the readers be too burdensome to read them? Will it be better to migrate\n> > its documentation until we completely delete .command?\n>\n> My opinion (if we focus only on adding \".cmd\") is that:\n>\n> - for simplicity for now it should run at the same time as \".command\",\n> the only difference being how the argument is passed (using $1 instead\n> of textually replacing $ARG)\n> - the doc for \".command\" should be first improved if possible, and\n> then moved over to \".cmd\" saying for \".command\" that \".command\" is\n> deprecated in favor of \".cmd\" but otherwise works as \".cmd\" except\n> that instead using $1 the value is passed by textually replacing $ARG\n> which could be a safety and correctness issue.\n>\n\nI agree with you. There may be need some discretion.\n\n> Another way to work on all this, would be to first work on adding\n> support for `trailer.<token>.runMode` and on improving existing\n> documentation, and then to add \".cmd\", which could then by default use\n> a different \".runMode\" than \".command\".\n>\n\nI think the task can be put off until April.\nDeal with the easier \".cmd\" first.\n\n> > > > This, too, but until \".command\" is removed, wouldn't it be better\n> > > > for readers to keep both variants, as the distinction between $ARG\n> > > > and $1 needs to be illustrated?\n> >\n> > So the correct solution should be to keep the original .command Examples,\n> > and then give the .cmd examples again.\n>\n> Maybe we could take advantage of \".cmd\" to show other nice\n> possibilities to use all of this. Especially if support for `git\n> commit --trailer ...` is already merged, we might be able to use it in\n> those examples, or perhaps add some examples to the git commit doc.\n>\n\nOh, the 'commit --trailer' may still be queuing, It may take a while.\n\n> Best,\n> Christian.\n\nThanks.\n\n--\nZheNing Hu\n"},{"id":"420564","messageId":"CAP8UFD1XSTAq28LrBe-q+M_Vs78gZhr58mHM6EgYt9g3pPuPDg@mail.gmail.com","threadId":"55371","inReplyTo":"CAOLTT8RAe0HhTL6p6MXeqbSazaJF0=PtnDKvh06-FXXBB+w94A@mail.gmail.com","subject":"Re: [PATCH v4] [GSOC]trailer: pass arg as positional parameter","fromName":"Christian Couder","fromEmail":"christian.couder@gmail.com","sentAt":"2021-03-30T08:45:06Z","receivedAt":"2021-03-30T08:46:07Z","isPatch":true,"sender":{"key":"christian.couder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/208954?v=4"},"body":"On Mon, Mar 29, 2021 at 3:44 PM ZheNing Hu <adlternative@gmail.com> wrote:\n>\n> Christian Couder <christian.couder@gmail.com> 于2021年3月29日周一 下午5:05写道：\n> >\n> > >\n> > > Yes, $ARG or $1 are always exist because of:\n> > >\n> > >                arg = xstrdup(\"\");\n> > >\n> > > so I think maybe we don't even need this judge in `apply_command`?\n> > > +               if (arg)\n> > > +                       strvec_push(&cp.args, arg);\n> >\n> > Yeah, I haven't looked at the code, but that might be a good\n> > simplification. If you work on this, please submit it in a separate\n> > commit.\n>\n> Well, if necessary, I'll put it in another commit, maybe I should double check\n> to see if there's anything special going on.\n>\n> > > > Another way to do it would be to have another config option called\n> > > > `trailer.<token>.alwaysRunCmd` to tell if the cmd specified by\n> > > > `trailer.<token>.cmd` should be run even if no '<token>=<value>'\n> > > > argument is passed on the command line. As we are introducing\n> > > > `trailer.<token>.cmd`, it's a good time to wonder if this would be a\n> > > > better design. But this issue is quite complex, because of the fact\n> > > > that 'trailer.<token>.ifMissing' and 'trailer.<token>.ifExists' also\n> > > > take a part in deciding if the command will be run.\n> >\n> > Actually after thinking about it, I think it might be better, instead\n> > of `trailer.<token>.alwaysRunCmd`, to add something like\n> > `trailer.<token>.runMode` that could take multiple values like:\n>\n> If really can achieve it is certainly better than 'alwaysRunCmd'.\n> The following three small configuration options look delicious.\n> But I think it needs to be discussed in more detail:\n>\n> > - \"beforeCLI\": would make it run once, like \".command\" does now before\n> > any CLI trailer are processed\n>\n> Does \"beforeCLI\" handle all trailers? Or is it just doing something to add empty\n> value trailers?\n\nI am not sure what you mean by \"handle all trailers\". What I mean is\nthat it would just work like \".command\" does right now before the\n\"--trailers ...\" options are processed.\n\nLet's suppose the \"trailer.foo.command\" config option is set to \"bar\".\nThen the \"bar\" command will be run just before the \"--trailers ...\"\noptions are processed and the output of that, let's say \"baz\" will be\nused to add a new \"foo: baz\" trailer to the ouput of `git\ninterpret-trailers`.\n\nFor example:\n\n-------\n$ git -c trailer.foo.command='echo baz' interpret-trailers<<EOF\nEOF\n\nfoo: baz\n-------\n\nIn other words an empty value trailer is just a special case when the\ncommand that is run does not output anything. But such commands are\nexpected to output something not trivial at least in some cases.\n\nSee also the example in the doc that uses:\n\n$ git config trailer.sign.command 'echo \"$(git config user.name)\n<$(git config user.email)>\"'\n\n> > - \"forEachCLIToken\": would make it run once for each trailer that has\n> > the token, like \".command\" also does now, the difference would be that\n> > the value for the token would be passed in the $1 argument\n>\n> This is exactly same as before.\n\nYeah it is the same as before when the \"--trailers ...\" options are\nprocessed, but not before that.\n\nTo get exactly the same as before one would need to configure both\n\"beforeCLI\" _and_ \"forEachCLIToken\", for example like this (note that\nwe use \"--add\" when adding \"forEachCLIToken\"):\n\n$ git config trailer.foo.runMode beforeCLI\n$ git config --add trailer.foo.runMode forEachCLIToken\n$ git config -l | grep foo\ntrailer.foo.runmode=beforeCLI\ntrailer.foo.runmode=forEachCLIToken\n\n> > - \"afterCLI\": would make it run once after all the CLI trailers have\n> > been processed and it could pass the different values for the token if\n> > any in different arguments: $1, $2, $3, ...\n>\n> I might get a little confused here: What's the input for $1,$2,$3?\n\nThe input would be the different values that are used for the token in\nthe \"--trailer ...\" CLI arguments.\n\nFor (an hypothetical) example:\n\n------\n$ git config trailer.foo.runMode afterCLI\n$ git config trailer.foo.cmd 'echo $@'\n$ git interpret-trailers --trailer foo=a --trailer foo=b --trailer foo=c<<EOF\nEOF\n\nfoo: a b c\n$ git interpret-trailers<<EOF\nEOF\n\nfoo:\n------\n\nI am not sure \"afterCLI\" would be useful, but we might not want to\nimplement it right now. It's just an example to show that we could add\nother modes to run the configured \".cmd\" (and maybe \".command\" too).\n\n> Is users more interested in dealing with trailers value or a line of the\n> trailer?\n\nI am not sure what you mean here. If \"a line of the trailer\" means a\ntrailer that is already in the input file that is passed to `git\ninterpret-trailers`, and if \"trailers value\" means a \"--trailer ...\"\nargument, then I would say that users could be interested in dealing\nwith both.\n\nIt's true that right now the command configured by a \".command\" is not\nrun when `git interpret-trailers` processes in input file that\ncontains a trailer with the corresponding token. So new values for\n\".runMode\" could be implemented to make that happen.\n\n> > > But as you said, to disable the automatic addition in the original .command\n> > > and use the new .alwaysRunCmd, I’m afraid there are a lot of things to consider.\n> > > Perhaps future series of patches can be considered to do it.\n> >\n> > Yeah, support for `trailer.<token>.runMode` might be added in\n> > different commits at least and possibly later in a different patch\n> > series. There are the following issues to resolve, though, if we want\n> > to focus only on a new \".cmd\" config option:\n> >\n> > - how and when should it run by default,\n>\n> Do you mean that \".cmd\" can get rid of the \".command\" auto-add problem\n> in this patch series?\n\nI am not sure what you mean with \"auto-add\". Do you mean that fact\nthat the \".command\" runs once before the CLI \"--trailer ...\" options\nare processed?\n\n> This might be a good idea if I can add the three modes you mentioned above\n> in the later patch series.\n\nI like that your are interested in improving trailer handling in Git,\nbut I must say that if you intend to apply for the GSoC, you might\nwant to work on your application document first, as it will need to be\ndiscussed on the mailing list too and it will take some time. You are\nalso free to work on this too, but that shouldn't be your priority.\n\nBy the way if this (or another Git related) subject is more\ninteresting to you than the project ideas we propose on\nhttps://git.github.io/SoC-2021-Ideas/, then you are welcome to write a\nproposal about working on this (improving trailer handling) rather\nthan on a project idea from that page. You might want to make sure\nthat some people would be willing to (co-)mentor you working on it\nthough.\n\n[...]\n\n> > Another way to work on all this, would be to first work on adding\n> > support for `trailer.<token>.runMode` and on improving existing\n> > documentation, and then to add \".cmd\", which could then by default use\n> > a different \".runMode\" than \".command\".\n>\n> I think the task can be put off until April.\n> Deal with the easier \".cmd\" first.\n\nOk for me, but see above about GSoC application.\n\n\n> > > > > This, too, but until \".command\" is removed, wouldn't it be better\n> > > > > for readers to keep both variants, as the distinction between $ARG\n> > > > > and $1 needs to be illustrated?\n> > >\n> > > So the correct solution should be to keep the original .command Examples,\n> > > and then give the .cmd examples again.\n> >\n> > Maybe we could take advantage of \".cmd\" to show other nice\n> > possibilities to use all of this. Especially if support for `git\n> > commit --trailer ...` is already merged, we might be able to use it in\n> > those examples, or perhaps add some examples to the git commit doc.\n>\n> Oh, the 'commit --trailer' may still be queuing, It may take a while.\n\nYou might want to check if it needs another reroll or if there are\nother reasons (like no reviews) why it's not listed in the last\n\"What's cooking ...\" email from Junio. If you think it is ready and\nhas been forgotten, you can ping reviewers (including me), to ask them\nto review it one more time, or Junio if the last version you sent has\nalready been reviewed.\n"},{"id":"420570","messageId":"CAOLTT8SfOKS41uJDHAMAmhWZXc3qZsngfFtsbzXxdNP1cEObzg@mail.gmail.com","threadId":"55371","inReplyTo":"CAP8UFD1XSTAq28LrBe-q+M_Vs78gZhr58mHM6EgYt9g3pPuPDg@mail.gmail.com","subject":"Re: [PATCH v4] [GSOC]trailer: pass arg as positional parameter","fromName":"ZheNing Hu","fromEmail":"adlternative@gmail.com","sentAt":"2021-03-30T11:22:13Z","receivedAt":"2021-03-30T11:23:10Z","isPatch":true,"sender":{"key":"adlternative@gmail.com","avatar":"https://avatars.githubusercontent.com/u/58138461?v=4"},"body":"Christian Couder <christian.couder@gmail.com> 于2021年3月30日周二 下午4:45写道：\n>\n> On Mon, Mar 29, 2021 at 3:44 PM ZheNing Hu <adlternative@gmail.com> wrote:\n> >\n> > Christian Couder <christian.couder@gmail.com> 于2021年3月29日周一 下午5:05写道：\n> > >\n> > > >\n> > > > Yes, $ARG or $1 are always exist because of:\n> > > >\n> > > >                arg = xstrdup(\"\");\n> > > >\n> > > > so I think maybe we don't even need this judge in `apply_command`?\n> > > > +               if (arg)\n> > > > +                       strvec_push(&cp.args, arg);\n> > >\n> > > Yeah, I haven't looked at the code, but that might be a good\n> > > simplification. If you work on this, please submit it in a separate\n> > > commit.\n> >\n> > Well, if necessary, I'll put it in another commit, maybe I should double check\n> > to see if there's anything special going on.\n> >\n> > > > > Another way to do it would be to have another config option called\n> > > > > `trailer.<token>.alwaysRunCmd` to tell if the cmd specified by\n> > > > > `trailer.<token>.cmd` should be run even if no '<token>=<value>'\n> > > > > argument is passed on the command line. As we are introducing\n> > > > > `trailer.<token>.cmd`, it's a good time to wonder if this would be a\n> > > > > better design. But this issue is quite complex, because of the fact\n> > > > > that 'trailer.<token>.ifMissing' and 'trailer.<token>.ifExists' also\n> > > > > take a part in deciding if the command will be run.\n> > >\n> > > Actually after thinking about it, I think it might be better, instead\n> > > of `trailer.<token>.alwaysRunCmd`, to add something like\n> > > `trailer.<token>.runMode` that could take multiple values like:\n> >\n> > If really can achieve it is certainly better than 'alwaysRunCmd'.\n> > The following three small configuration options look delicious.\n> > But I think it needs to be discussed in more detail:\n> >\n> > > - \"beforeCLI\": would make it run once, like \".command\" does now before\n> > > any CLI trailer are processed\n> >\n> > Does \"beforeCLI\" handle all trailers? Or is it just doing something to add empty\n> > value trailers?\n>\n> I am not sure what you mean by \"handle all trailers\". What I mean is\n> that it would just work like \".command\" does right now before the\n> \"--trailers ...\" options are processed.\n>\n> Let's suppose the \"trailer.foo.command\" config option is set to \"bar\".\n> Then the \"bar\" command will be run just before the \"--trailers ...\"\n> options are processed and the output of that, let's say \"baz\" will be\n> used to add a new \"foo: baz\" trailer to the ouput of `git\n> interpret-trailers`.\n>\n> For example:\n>\n> -------\n> $ git -c trailer.foo.command='echo baz' interpret-trailers<<EOF\n> EOF\n>\n> foo: baz\n> -------\n>\n> In other words an empty value trailer is just a special case when the\n> command that is run does not output anything. But such commands are\n> expected to output something not trivial at least in some cases.\n>\n> See also the example in the doc that uses:\n>\n> $ git config trailer.sign.command 'echo \"$(git config user.name)\n> <$(git config user.email)>\"'\n>\n\nI see what you mean, which is to provide a default value for any\ntrailers that haven't been run command yet.\n\n> > > - \"forEachCLIToken\": would make it run once for each trailer that has\n> > > the token, like \".command\" also does now, the difference would be that\n> > > the value for the token would be passed in the $1 argument\n> >\n> > This is exactly same as before.\n>\n> Yeah it is the same as before when the \"--trailers ...\" options are\n> processed, but not before that.\n>\n> To get exactly the same as before one would need to configure both\n> \"beforeCLI\" _and_ \"forEachCLIToken\", for example like this (note that\n> we use \"--add\" when adding \"forEachCLIToken\"):\n>\n> $ git config trailer.foo.runMode beforeCLI\n> $ git config --add trailer.foo.runMode forEachCLIToken\n> $ git config -l | grep foo\n> trailer.foo.runmode=beforeCLI\n> trailer.foo.runmode=forEachCLIToken\n>\n> > > - \"afterCLI\": would make it run once after all the CLI trailers have\n> > > been processed and it could pass the different values for the token if\n> > > any in different arguments: $1, $2, $3, ...\n> >\n> > I might get a little confused here: What's the input for $1,$2,$3?\n>\n> The input would be the different values that are used for the token in\n> the \"--trailer ...\" CLI arguments.\n>\n> For (an hypothetical) example:\n>\n> ------\n> $ git config trailer.foo.runMode afterCLI\n> $ git config trailer.foo.cmd 'echo $@'\n> $ git interpret-trailers --trailer foo=a --trailer foo=b --trailer foo=c<<EOF\n> EOF\n>\n> foo: a b c\n> $ git interpret-trailers<<EOF\n> EOF\n>\n> foo:\n> ------\n>\n> I am not sure \"afterCLI\" would be useful, but we might not want to\n> implement it right now. It's just an example to show that we could add\n> other modes to run the configured \".cmd\" (and maybe \".command\" too).\n>\n\nYes, not so useful.\n\n> > Is users more interested in dealing with trailers value or a line of the\n> > trailer?\n>\n> I am not sure what you mean here. If \"a line of the trailer\" means a\n> trailer that is already in the input file that is passed to `git\n> interpret-trailers`, and if \"trailers value\" means a \"--trailer ...\"\n> argument, then I would say that users could be interested in dealing\n> with both.\n>\n\nSorry, I mean after we running those command, a line trailer is\n\"foo: bar\" and trailers value will be \"bar\".\n\n> It's true that right now the command configured by a \".command\" is not\n> run when `git interpret-trailers` processes in input file that\n> contains a trailer with the corresponding token. So new values for\n> \".runMode\" could be implemented to make that happen.\n>\n\nSure.\n\n> > > > But as you said, to disable the automatic addition in the original .command\n> > > > and use the new .alwaysRunCmd, I’m afraid there are a lot of things to consider.\n> > > > Perhaps future series of patches can be considered to do it.\n> > >\n> > > Yeah, support for `trailer.<token>.runMode` might be added in\n> > > different commits at least and possibly later in a different patch\n> > > series. There are the following issues to resolve, though, if we want\n> > > to focus only on a new \".cmd\" config option:\n> > >\n> > > - how and when should it run by default,\n> >\n> > Do you mean that \".cmd\" can get rid of the \".command\" auto-add problem\n> > in this patch series?\n>\n> I am not sure what you mean with \"auto-add\". Do you mean that fact\n> that the \".command\" runs once before the CLI \"--trailer ...\" options\n> are processed?\n>\n\nI'm talking about the empty values $ARG passing to the user's command,\nthose command  at least run once, You say \"how and when should it run by\ndefault\", I was wondering if I could not run .cmd without passing trailer.\n\n> > This might be a good idea if I can add the three modes you mentioned above\n> > in the later patch series.\n>\n> I like that your are interested in improving trailer handling in Git,\n> but I must say that if you intend to apply for the GSoC, you might\n> want to work on your application document first, as it will need to be\n> discussed on the mailing list too and it will take some time. You are\n> also free to work on this too, but that shouldn't be your priority.\n>\n\nIn fact, I had written the proposal carefully.\nI have been studying what went wrong with OIga's improvement of cat-file\nrecently.\n\nI may have thought of some ideas, and has been written in Proposal,\nI will submit it in about two days :)\n\n> By the way if this (or another Git related) subject is more\n> interesting to you than the project ideas we propose on\n> https://git.github.io/SoC-2021-Ideas/, then you are welcome to write a\n> proposal about working on this (improving trailer handling) rather\n> than on a project idea from that page. You might want to make sure\n> that some people would be willing to (co-)mentor you working on it\n> though.\n>\n\nAha, for the time being, you are the most suitable mentor,\nBut I might just take improvement of `interpret-tarilers` as my interest to\ndo something. I will choice the project of \"git cat-file\" .\n\n> [...]\n>\n> > > Another way to work on all this, would be to first work on adding\n> > > support for `trailer.<token>.runMode` and on improving existing\n> > > documentation, and then to add \".cmd\", which could then by default use\n> > > a different \".runMode\" than \".command\".\n> >\n> > I think the task can be put off until April.\n> > Deal with the easier \".cmd\" first.\n>\n> Ok for me, but see above about GSoC application.\n>\n>\n> > > > > > This, too, but until \".command\" is removed, wouldn't it be better\n> > > > > > for readers to keep both variants, as the distinction between $ARG\n> > > > > > and $1 needs to be illustrated?\n> > > >\n> > > > So the correct solution should be to keep the original .command Examples,\n> > > > and then give the .cmd examples again.\n> > >\n> > > Maybe we could take advantage of \".cmd\" to show other nice\n> > > possibilities to use all of this. Especially if support for `git\n> > > commit --trailer ...` is already merged, we might be able to use it in\n> > > those examples, or perhaps add some examples to the git commit doc.\n> >\n> > Oh, the 'commit --trailer' may still be queuing, It may take a while.\n>\n> You might want to check if it needs another reroll or if there are\n> other reasons (like no reviews) why it's not listed in the last\n> \"What's cooking ...\" email from Junio. If you think it is ready and\n> has been forgotten, you can ping reviewers (including me), to ask them\n> to review it one more time, or Junio if the last version you sent has\n> already been reviewed.\n\nIt should still be in \"seen\" inheritance, Junio is advancing it.\nMaybe you think it has something to improve, please feel free to tell me.\n\nIn addition, I now found a small bug in \".cmd\",\n\ngit config -l |grep bug\ntrailer.bug.key=bug-descibe:\ntrailer.bug.ifexists=replace\ntrailer.bug.cmd=echo 123\n\nsee what will happen:\n\ngit interpret-trailers --trailer=\"bug:text\" <<-EOF\n`heredocd> EOF\n\nbug-descibe:123 text\n\n\"text\" seem print to stdout.\n\nI'm looking at what's going on here.\n\n--\nZheNing Hu\n"},{"id":"420620","messageId":"CAOLTT8SPRArgwwd_isw48gWQysgqJ9JJpn9JNGH+=9aY+0=SPA@mail.gmail.com","threadId":"55371","inReplyTo":"CAOLTT8SfOKS41uJDHAMAmhWZXc3qZsngfFtsbzXxdNP1cEObzg@mail.gmail.com","subject":"Re: [PATCH v4] [GSOC]trailer: pass arg as positional parameter","fromName":"ZheNing Hu","fromEmail":"adlternative@gmail.com","sentAt":"2021-03-30T15:07:19Z","receivedAt":"2021-03-30T15:08:35Z","isPatch":true,"sender":{"key":"adlternative@gmail.com","avatar":"https://avatars.githubusercontent.com/u/58138461?v=4"},"body":"Hi, Junio,\n\nZheNing Hu <adlternative@gmail.com> 于2021年3月30日周二 下午7:22写道：\n>\n> In addition, I now found a small bug in \".cmd\",\n>\n> git config -l |grep bug\n> trailer.bug.key=bug-descibe:\n> trailer.bug.ifexists=replace\n> trailer.bug.cmd=echo 123\n>\n> see what will happen:\n>\n> git interpret-trailers --trailer=\"bug:text\" <<-EOF\n> `heredocd> EOF\n>\n> bug-descibe:123 text\n>\n> \"text\" seem print to stdout.\n>\n> I'm looking at what's going on here.\n>\n\nHere I may need to think with you whether it is reasonable to pass \"$1\".\n\nI found that we passed the parameters in the above situation like this:\n\n(gdb) print cp.args.v[0]\n$7 = 0x5555558f4e20 \"echo \\\"123\\\"\"\n(gdb) print cp.args.v[1]\n$8 = 0x5555558ee150 \"text\"\n\nAt this time, our idea is base on that v[0] will be the content of the shell,\nand v[1] will be the $1 of the shell.\n\nBut in fact, git handles shell subprocesses in a special way:\n\nThe `prepare_shell_cmd()` in \"run-command.c\" seem to use \"$@\" to pass\nshell args.\n\nBefore exec:\n\n(gdb) print argv.v[1]\n$22 = 0x5555558edfd0 \"/bin/sh\"\n(gdb) print argv.v[2]\n$23 = 0x5555558f4c80 \"-c\"\n(gdb) print argv.v[3]\n$24 = 0x5555558ed4b0 \"echo \\\"123\\\" \\\"$@\\\"\"\n(gdb) print argv.v[4]\n$25 = 0x5555558f5980 \"echo \\\"123\\\"\"\n(gdb) print argv.v[5]\n$26 = 0x5555558edab0 \"abc\"\n(gdb) print argv.v[6]\n$27 = 0x0\n\nSome unexpected things happened here.\nMaybe \"abc\" was wrongly used as the parameter of \"echo\"?\nLooking forward to your reply.\n\n--\nZheNing Hu\n"},{"id":"420630","messageId":"xmqqwntoef81.fsf@gitster.g","threadId":"55371","inReplyTo":"CAOLTT8SPRArgwwd_isw48gWQysgqJ9JJpn9JNGH+=9aY+0=SPA@mail.gmail.com","subject":"Re: [PATCH v4] [GSOC]trailer: pass arg as positional parameter","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2021-03-30T17:14:22Z","receivedAt":"2021-03-30T17:15:23Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"ZheNing Hu <adlternative@gmail.com> writes:\n\n> The `prepare_shell_cmd()` in \"run-command.c\" seem to use \"$@\" to pass\n> shell args.\n\nYes. \"$@\" is a way to write \"$1\" \"$2\" \"$3\"...\nSince you are passing only one, \n\n\techo \"$@\"\n\nand\n\n\techo \"$1\"\n\nwould be the equivalent.\n\nI am not sure what program you fed to the gdb (and remote debugging\nover e-mail is not my forte ;-), but let's see.\n\n> Before exec:\n>\n> (gdb) print argv.v[1]\n> $22 = 0x5555558edfd0 \"/bin/sh\"\n> (gdb) print argv.v[2]\n> $23 = 0x5555558f4c80 \"-c\"\n> (gdb) print argv.v[3]\n> $24 = 0x5555558ed4b0 \"echo \\\"123\\\" \\\"$@\\\"\"\n> (gdb) print argv.v[4]\n> $25 = 0x5555558f5980 \"echo \\\"123\\\"\"\n> (gdb) print argv.v[5]\n> $26 = 0x5555558edab0 \"abc\"\n> (gdb) print argv.v[6]\n> $27 = 0x0\n>\n> Some unexpected things happened here.\n> Maybe \"abc\" was wrongly used as the parameter of \"echo\"?\n> Looking forward to your reply.\n\nObserve\n\n\t$ sh -c '\n\t\techo \"\\$0 == $0\"\n\t\tcount=0\n\t\tfor arg in \"$@\"\n\t\tdo\n\t\t\tcount=$(( $count + 1 ))\n\t\t\techo \"\\$$count == $arg\"\n\t\tdone\n\t' 0 1 2\n\t$0 == 0\n\t$1 == 1\n\t$2 == 2\n\ni.e. the first arg after\n\n\targv[1] = \"/bin/sh\"\n        argv[2] = \"-c\"\n\targv[3] = \"script\"\n\nis used to give the script the name of the program ($0).  Are we\ngetting hit by this common confusion?\n\nIt is customery to write such an invocation with '-' as the \"name of\nthe program\" thing, so that ordinary positional parameters are\navailable starting at $1, not $0, like so:\n\n\tsh -c 'script' - arg1 arg2 ...\n"},{"id":"420687","messageId":"CAOLTT8SLX7wVfND9ru8NPx_YhvP2Ed17UcuRdY6Uioj9XSFmwQ@mail.gmail.com","threadId":"55371","inReplyTo":"xmqqwntoef81.fsf@gitster.g","subject":"Re: [PATCH v4] [GSOC]trailer: pass arg as positional parameter","fromName":"ZheNing Hu","fromEmail":"adlternative@gmail.com","sentAt":"2021-03-31T05:14:57Z","receivedAt":"2021-03-31T05:15:48Z","isPatch":true,"sender":{"key":"adlternative@gmail.com","avatar":"https://avatars.githubusercontent.com/u/58138461?v=4"},"body":"Junio C Hamano <gitster@pobox.com> 于2021年3月31日周三 上午1:14写道：\n>\n> ZheNing Hu <adlternative@gmail.com> writes:\n>\n> > The `prepare_shell_cmd()` in \"run-command.c\" seem to use \"$@\" to pass\n> > shell args.\n>\n> Yes. \"$@\" is a way to write \"$1\" \"$2\" \"$3\"...\n> Since you are passing only one,\n>\n>         echo \"$@\"\n>\n> and\n>\n>         echo \"$1\"\n>\n> would be the equivalent.\n>\n> I am not sure what program you fed to the gdb (and remote debugging\n> over e-mail is not my forte ;-), but let's see.\n>\n\n\n\n> > Before exec:\n> >\n> > (gdb) print argv.v[1]\n> > $22 = 0x5555558edfd0 \"/bin/sh\"\n> > (gdb) print argv.v[2]\n> > $23 = 0x5555558f4c80 \"-c\"\n> > (gdb) print argv.v[3]\n> > $24 = 0x5555558ed4b0 \"echo \\\"123\\\" \\\"$@\\\"\"\n> > (gdb) print argv.v[4]\n> > $25 = 0x5555558f5980 \"echo \\\"123\\\"\"\n> > (gdb) print argv.v[5]\n> > $26 = 0x5555558edab0 \"abc\"\n> > (gdb) print argv.v[6]\n> > $27 = 0x0\n> >\n> > Some unexpected things happened here.\n> > Maybe \"abc\" was wrongly used as the parameter of \"echo\"?\n> > Looking forward to your reply.\n>\n> Observe\n>\n>         $ sh -c '\n>                 echo \"\\$0 == $0\"\n>                 count=0\n>                 for arg in \"$@\"\n>                 do\n>                         count=$(( $count + 1 ))\n>                         echo \"\\$$count == $arg\"\n>                 done\n>         ' 0 1 2\n>         $0 == 0\n>         $1 == 1\n>         $2 == 2\n>\n> i.e. the first arg after\n>\n>         argv[1] = \"/bin/sh\"\n>         argv[2] = \"-c\"\n>         argv[3] = \"script\"\n>\n> is used to give the script the name of the program ($0).  Are we\n> getting hit by this common confusion?\n>\n> It is customery to write such an invocation with '-' as the \"name of\n> the program\" thing, so that ordinary positional parameters are\n> available starting at $1, not $0, like so:\n>\n>         sh -c 'script' - arg1 arg2 ...\n\nThe configuration is like this:\ntrailer.bug.key=BUG:\ntrailer.bug.ifexists=add\ntrailer.bug.cmd=echo \"123\"\n\nAnd use:\n\n$ git interpret-trailers --trailer=\"bug:456\" --trailer=\"bug:789\"<<-EOF\nEOF\n\nBUG: 123\nBUG: 123 456\nBUG: 123 789\n\nI just want three \"BUG: 123\", but \"456\" and \"789\" appeared...\n\nIn fact, I think about this problem like this way:\nWhen we execute a child process that runs the shell,\nthe function`prepare_shell_cmd()` will actively add \"$@\" to the end of our\nshell command when we have more than zero args ,\n\ne.g.\n\n\"echo \\\"123\\\"\" \"abc\"\n\nwill turn to\n\n \"echo \\\"123\\\" \\\"$@\\\"\" \"echo \\\"123\\\"\" \"abc\"\n\nNormally, $@ should not cause any problems because it passes arguments\nto the script what we provide.\n\nBut now, what we actually want is take any $1 that appears in the script as an\nargument, the automatically added $@ causes $1 to be implicitly included.\nAnd the original $ARG does not have this problem, Or if we pass environment\nvariables, this kind of problem will not occur.\n\nOr If we want to avoid this problem, should we add one new options in\n`struct child_process` , such as: \"shell_no_implicit_args\" , let git not add\n extra \"$@\" before we run the shell script?\n\nThanks.\n\n--\nZheNing Hu\n"},{"id":"420693","messageId":"4c59cab53a0d9bb7c9cccfaf5544ae5c904bb2ba.1617185147.git.gitgitgadget@gmail.com","threadId":"55371","inReplyTo":"pull.913.v5.git.1617185147.gitgitgadget@gmail.com","subject":"[PATCH v5 1/2] [GSOC] run-command: add shell_no_implicit_args option","fromName":"ZheNing Hu via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2021-03-31T10:05:46Z","receivedAt":"2021-03-31T10:06:25Z","isPatch":true,"sender":{"key":"adlternative@gmail.com","avatar":"https://avatars.githubusercontent.com/u/58138461?v=4"},"body":"From: ZheNing Hu <adlternative@gmail.com>\n\nWhen we use subprocess to run a shell-script, if we have any\nargs, git will add extra $@ to the end of the shell-script,\nThis can pass positional parameters correctly, But if we just\nwant to use some of these passed parameters, git will still\nadd an extra \"$@\", which contains all positional parameters we\npassed. This does not meet our expectations.\n\nE.g. our shell-script is:\n\"echo \\\"\\$1\\\"\"\nand pass $1 \"abc\", git will change our script to:\n\"echo \\\"\\$1\\\" \\\"$@\\\"\"\n\nThe positional parameters we entered will be printed\nrepeatedly. So let add a new `shell_no_implicit_args`\nto `struct child_process`, which can suppress the\njoining of $@ if `shell_no_implicit_args` is set to 1,\nthis will allow us to use only few of positional args\nin multi-parameter shell script, instead of using all\nof them.\n\nSigned-off-by: ZheNing Hu <adlternative@gmail.com>\n---\n run-command.c | 8 ++++----\n run-command.h | 1 +\n trailer.c     | 1 +\n 3 files changed, 6 insertions(+), 4 deletions(-)\n\ndiff --git a/run-command.c b/run-command.c\nindex be6bc128cd9d..a2cf6177f522 100644\n--- a/run-command.c\n+++ b/run-command.c\n@@ -264,7 +264,7 @@ int sane_execvp(const char *file, char * const argv[])\n \treturn -1;\n }\n \n-static const char **prepare_shell_cmd(struct strvec *out, const char **argv)\n+static const char **prepare_shell_cmd(struct strvec *out, const char **argv, int shell_no_implicit_args)\n {\n \tif (!argv[0])\n \t\tBUG(\"shell command is empty\");\n@@ -281,7 +281,7 @@ static const char **prepare_shell_cmd(struct strvec *out, const char **argv)\n \t\t * If we have no extra arguments, we do not even need to\n \t\t * bother with the \"$@\" magic.\n \t\t */\n-\t\tif (!argv[1])\n+\t\tif (!argv[1] || shell_no_implicit_args)\n \t\t\tstrvec_push(out, argv[0]);\n \t\telse\n \t\t\tstrvec_pushf(out, \"%s \\\"$@\\\"\", argv[0]);\n@@ -416,7 +416,7 @@ static int prepare_cmd(struct strvec *out, const struct child_process *cmd)\n \tif (cmd->git_cmd) {\n \t\tprepare_git_cmd(out, cmd->argv);\n \t} else if (cmd->use_shell) {\n-\t\tprepare_shell_cmd(out, cmd->argv);\n+\t\tprepare_shell_cmd(out, cmd->argv, cmd->shell_no_implicit_args);\n \t} else {\n \t\tstrvec_pushv(out, cmd->argv);\n \t}\n@@ -929,7 +929,7 @@ int start_command(struct child_process *cmd)\n \tif (cmd->git_cmd)\n \t\tcmd->argv = prepare_git_cmd(&nargv, cmd->argv);\n \telse if (cmd->use_shell)\n-\t\tcmd->argv = prepare_shell_cmd(&nargv, cmd->argv);\n+\t\tcmd->argv = prepare_shell_cmd(&nargv, cmd->argv, cmd->shell_no_implicit_args);\n \n \tcmd->pid = mingw_spawnvpe(cmd->argv[0], cmd->argv, (char**) cmd->env,\n \t\t\tcmd->dir, fhin, fhout, fherr);\ndiff --git a/run-command.h b/run-command.h\nindex d08414a92e73..9597c987c5bb 100644\n--- a/run-command.h\n+++ b/run-command.h\n@@ -133,6 +133,7 @@ struct child_process {\n \t * argv[1], etc, do not need to be shell-quoted.\n \t */\n \tunsigned use_shell:1;\n+\tunsigned shell_no_implicit_args:1;\n \n \tunsigned stdout_to_stderr:1;\n \tunsigned clean_on_exit:1;\ndiff --git a/trailer.c b/trailer.c\nindex be4e9726421c..35dd0f4c8512 100644\n--- a/trailer.c\n+++ b/trailer.c\n@@ -231,6 +231,7 @@ static char *apply_command(const char *command, const char *arg)\n \tcp.env = local_repo_env;\n \tcp.no_stdin = 1;\n \tcp.use_shell = 1;\n+\tcp.shell_no_implicit_args = 1;\n \n \tif (capture_command(&cp, &buf, 1024)) {\n \t\terror(_(\"running trailer command '%s' failed\"), cmd.buf);\n-- \ngitgitgadget\n\n"},{"id":"420694","messageId":"pull.913.v5.git.1617185147.gitgitgadget@gmail.com","threadId":"55371","inReplyTo":"pull.913.v4.git.1616775185562.gitgitgadget@gmail.com","subject":"[PATCH v5 0/2] [GSOC]trailer: pass arg as positional parameter","fromName":"ZheNing Hu via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2021-03-31T10:05:45Z","receivedAt":"2021-03-31T10:06:25Z","isPatch":true,"sender":{"key":"adlternative@gmail.com","avatar":"https://avatars.githubusercontent.com/u/58138461?v=4"},"body":"In https://lore.kernel.org/git/xmqqv99i4ck2.fsf@gitster.g/ Junio and\nChristian talked about the problem of using strbuf_replace() to replace\n$ARG.\n\nNow pass trailer value as $1 to the trailer command with another\ntrailer.<token>.cmd config.\n\nZheNing Hu (2):\n  [GSOC] run-command: add shell_no_implicit_args option\n  [GSOC]trailer: pass arg as positional parameter\n\n Documentation/git-interpret-trailers.txt | 75 ++++++++++++++++++++----\n run-command.c                            |  8 +--\n run-command.h                            |  1 +\n t/t7513-interpret-trailers.sh            | 61 ++++++++++++++++++-\n trailer.c                                | 38 ++++++++----\n 5 files changed, 157 insertions(+), 26 deletions(-)\n\n\nbase-commit: 142430338477d9d1bb25be66267225fb58498d92\nPublished-As: https://github.com/gitgitgadget/git/releases/tag/pr-913%2Fadlternative%2Ftrailer-pass-ARG-env-v5\nFetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-913/adlternative/trailer-pass-ARG-env-v5\nPull-Request: https://github.com/gitgitgadget/git/pull/913\n\nRange-diff vs v4:\n\n -:  ------------ > 1:  4c59cab53a0d [GSOC] run-command: add shell_no_implicit_args option\n 1:  e2bbdcb943c2 ! 2:  5894d8c4b364 [GSOC]trailer: pass arg as positional parameter\n     @@ Commit message\n          scripts.\n      \n          Helped-by: Junio C Hamano <gitster@pobox.com>\n     +    Helped-by: Christian Couder <christian.couder@gmail.com>\n          Signed-off-by: ZheNing Hu <adlternative@gmail.com>\n      \n       ## Documentation/git-interpret-trailers.txt ##\n     -@@ Documentation/git-interpret-trailers.txt: also be executed for each of these arguments. And the <value> part of\n     - these arguments, if any, will be used to replace the `$ARG` string in\n     - the command.\n     - \n     +@@ Documentation/git-interpret-trailers.txt: trailer.<token>.command::\n     + \tbe called to automatically add or modify a trailer with the\n     + \tspecified <token>.\n     + +\n     +-When this option is specified, the behavior is as if a special\n     +-'<token>=<value>' argument were added at the beginning of the command\n     +-line, where <value> is taken to be the standard output of the\n     +-specified command with any leading and trailing whitespace trimmed\n     +-off.\n     ++When this option is specified, the first occurrence of substring $ARG is\n     ++replaced with the value given to the `interpret-trailer` command for the\n     ++same token.\n     + +\n     +-If the command contains the `$ARG` string, this string will be\n     +-replaced with the <value> part of an existing trailer with the same\n     +-<token>, if any, before the command is launched.\n     ++\".command\" has been deprecated due to the $ARG in the user's command can\n     ++only be replaced once and the original way of replacing $ARG was not safe.\n     ++Now the preferred option is using \"trailer.<token>.cmd\", which use position\n     ++argument to pass the value.\n     +++\n     ++When both .cmd and .command are given for the same <token>,\n     ++.cmd is used and .command is ignored.\n     ++\n      +trailer.<token>.cmd::\n      +\tThe command specified by this configuration variable is run\n      +\twith a single parameter, which is the <value> part of an\n      +\texisting trailer with the same <token>.  The output from the\n      +\tcommand is then used as the value for the <token> in the\n      +\tresulting trailer.\n     -+\tThe command is expected to replace `trailer.<token>.cmd`.\n     -+\tWhen both .cmd and .command are given for the same <token>,\n     -+        .cmd is used and .command is ignored.\n     -+\n     - EXAMPLES\n     - --------\n     - \n     -@@ Documentation/git-interpret-trailers.txt: $ git format-patch -1\n     - $ git interpret-trailers --trailer 'Cc: Alice <alice@example.com>' --trailer 'Reviewed-by: Bob <bob@example.com>' 0001-foo.patch >0001-bar.patch\n     - ------------\n     - \n     --* Configure a 'sign' trailer with a command to automatically add a\n     -+* Configure a 'sign' trailer with a cmd to automatically add a\n     -   'Signed-off-by: ' with the author information only if there is no\n     -   'Signed-off-by: ' already, and show how it works:\n     +++\n     ++When this option is specified, If there is no trailer with same <token>,\n     ++the behavior is as if a special '<token>=<value>' argument were added at\n     ++the beginning of the command, <value> will be passed to the user's\n     ++command as an empty value.\n       +\n     -@@ Documentation/git-interpret-trailers.txt: $ git interpret-trailers --trailer 'Cc: Alice <alice@example.com>' --trailer 'Re\n     - $ git config trailer.sign.key \"Signed-off-by: \"\n     - $ git config trailer.sign.ifmissing add\n     - $ git config trailer.sign.ifexists doNothing\n     --$ git config trailer.sign.command 'echo \"$(git config user.name) <$(git config user.email)>\"'\n     -+$ git config trailer.sign.cmd 'echo \"$(git config user.name) <$(git config user.email)>\"'\n     - $ git interpret-trailers <<EOF\n     - > EOF\n     + If some '<token>=<value>' arguments are also passed on the command\n     + line, when a 'trailer.<token>.command' is configured, the command will\n     + also be executed for each of these arguments. And the <value> part of\n     +-these arguments, if any, will be used to replace the `$ARG` string in\n     +-the command.\n     ++these arguments, if any, will be passed to the command as first parameter.\n       \n     + EXAMPLES\n     + --------\n      @@ Documentation/git-interpret-trailers.txt: subject\n       Fix #42\n       ------------\n       \n     --* Configure a 'see' trailer with a command to show the subject of a\n      +* Configure a 'see' trailer with a cmd to show the subject of a\n     ++  commit that is related, and show how it works:\n     +++\n     ++------------\n     ++$ git config trailer.see.key \"See-also: \"\n     ++$ git config trailer.see.ifExists \"replace\"\n     ++$ git config trailer.see.ifMissing \"doNothing\"\n     ++$ git config trailer.see.cmd \"git show -s --pretty=reference \\\"\\$1\\\"\"\n     ++$ git interpret-trailers <<EOF\n     ++> subject\n     ++> \n     ++> message\n     ++> \n     ++> see: HEAD~2\n     ++> EOF\n     ++subject\n     ++\n     ++message\n     ++\n     ++See-also: fe3187489d69c4 (subject of related commit, 2021-3-20)\n     ++------------\n     ++\n     ++* Configure a 'bug' trailer with a cmd to show when and where\n     ++  was the bug introduced, and show how it works:\n     +++\n     ++------------\n     ++$ git config trailer.bug.key \"Bug-from: \"\n     ++$ git config trailer.bug.ifExists \"replace\"\n     ++$ git config trailer.bug.cmd \"git log --grep \\\"\\$1\\\" -1 --pretty=\\\"%h %aD\\\"\"\n     ++$ git interpret-trailers --trailer=\"bug:the information manager from hell\" <<EOF\n     ++> subject\n     ++> \n     ++> message\n     ++> \n     ++> EOF\n     ++subject\n     ++\n     ++message\n     ++\n     ++Bug-from: 57d84f8d93 Mon, 6 Aug 2012 18:27:09 +0700\n     ++------------\n     ++\n     + * Configure a 'see' trailer with a command to show the subject of a\n         commit that is related, and show how it works:\n       +\n     - ------------\n     - $ git config trailer.see.key \"See-also: \"\n     - $ git config trailer.see.ifExists \"replace\"\n     - $ git config trailer.see.ifMissing \"doNothing\"\n     --$ git config trailer.see.command \"git log -1 --oneline --format=\\\"%h (%s)\\\" --abbrev-commit --abbrev=14 \\$ARG\"\n     -+$ git config trailer.see.cmd \"test -n \\\"\\$1\\\" && git log -1 --oneline --format=\\\"%h (%s)\\\" --abbrev-commit --abbrev=14 \\\"\\$1\\\"|| true \"\n     - $ git interpret-trailers <<EOF\n     - > subject\n     - > \n      \n       ## t/t7513-interpret-trailers.sh ##\n     +@@ t/t7513-interpret-trailers.sh: test_expect_success 'setup' '\n     + \tEOF\n     + '\n     + \n     ++test_expect_success 'with cmd' '\n     ++\ttest_when_finished \"git config --unset trailer.bug.key && \\\n     ++\tgit config --unset trailer.bug.ifExists && \\\n     ++\tgit config --unset trailer.bug.cmd\" &&\n     ++\tgit config trailer.bug.key \"Bug-maker: \" &&\n     ++\tgit config trailer.bug.ifExists \"add\" &&\n     ++\tgit config trailer.bug.cmd \"echo \\\"\\$@\\\"\" &&\n     ++\tcat >>expected2 <<-EOF &&\n     ++\n     ++\tBug-maker: \n     ++\tBug-maker: jocker\n     ++\tBug-maker: batman\n     ++\tEOF\n     ++\tgit interpret-trailers --trailer \"bug: jocker\" --trailer \"bug:batman\" \\\n     ++\t\t>actual2 &&\n     ++\ttest_cmp expected2 actual2\n     ++'\n     ++\n     + test_expect_success 'without config' '\n     + \tsed -e \"s/ Z\\$/ /\" >expected <<-\\EOF &&\n     + \n      @@ t/t7513-interpret-trailers.sh: test_expect_success 'setup a commit' '\n       \tgit commit -m \"Add file a.txt\"\n       '\n     @@ trailer.c: static int check_if_different(struct trailer_item *in_tok,\n      -\n      -\tstrvec_push(&cp.args, cmd.buf);\n      +\tif (conf->cmd) {\n     ++\t\tcp.shell_no_implicit_args = 1;\n      +\t\tstrbuf_addstr(&cmd, conf->cmd);\n      +\t\tstrvec_push(&cp.args, cmd.buf);\n      +\t\tif (arg)\n     @@ trailer.c: static int check_if_different(struct trailer_item *in_tok,\n       \tcp.env = local_repo_env;\n       \tcp.no_stdin = 1;\n       \tcp.use_shell = 1;\n     +-\tcp.shell_no_implicit_args = 1;\n     + \n     + \tif (capture_command(&cp, &buf, 1024)) {\n     + \t\terror(_(\"running trailer command '%s' failed\"), cmd.buf);\n      @@ trailer.c: static char *apply_command(const char *command, const char *arg)\n       \n       static void apply_item_command(struct trailer_item *in_tok, struct arg_item *arg_tok)\n\n-- \ngitgitgadget\n"},{"id":"420695","messageId":"5894d8c4b36466326b0427bfda0d6981e52a0907.1617185147.git.gitgitgadget@gmail.com","threadId":"55371","inReplyTo":"pull.913.v5.git.1617185147.gitgitgadget@gmail.com","subject":"[PATCH v5 2/2] [GSOC]trailer: pass arg as positional parameter","fromName":"ZheNing Hu via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2021-03-31T10:05:47Z","receivedAt":"2021-03-31T10:07:00Z","isPatch":true,"sender":{"key":"adlternative@gmail.com","avatar":"https://avatars.githubusercontent.com/u/58138461?v=4"},"body":"From: ZheNing Hu <adlternative@gmail.com>\n\nThe `trailer.<token>.command` configuration variable\nspecifies a command (run via the shell, so it does not have\nto be a single name of or path to the command, but can be a\nshell script), and the first occurrence of substring $ARG is\nreplaced with the value given to the `interpret-trailer`\ncommand for the token.  This has two downsides:\n\n* The use of $ARG in the mechanism misleads the users that\nthe value is passed in the shell variable, and tempt them\nto use $ARG more than once, but that would not work, as\nthe second and subsequent $ARG are not replaced.\n\n* Because $ARG is textually replaced without regard to the\nshell language syntax, even '$ARG' (inside a single-quote\npair), which a user would expect to stay intact, would be\nreplaced, and worse, if the value had an unmatching single\nquote (imagine a name like \"O'Connor\", substituted into\nNAME='$ARG' to make it NAME='O'Connor), it would result in\na broken command that is not syntactically correct (or\nworse).\n\nIntroduce a new `trailer.<token>.cmd` configuration that\ntakes higher precedence to deprecate and eventually remove\n`trailer.<token>.command`, which passes the value as a\nparameter to the command.  Instead of \"$ARG\", the users will\nrefer to the value as positional argument, $1, in their\nscripts.\n\nHelped-by: Junio C Hamano <gitster@pobox.com>\nHelped-by: Christian Couder <christian.couder@gmail.com>\nSigned-off-by: ZheNing Hu <adlternative@gmail.com>\n---\n Documentation/git-interpret-trailers.txt | 75 ++++++++++++++++++++----\n t/t7513-interpret-trailers.sh            | 61 ++++++++++++++++++-\n trailer.c                                | 39 ++++++++----\n 3 files changed, 152 insertions(+), 23 deletions(-)\n\ndiff --git a/Documentation/git-interpret-trailers.txt b/Documentation/git-interpret-trailers.txt\nindex 96ec6499f001..bbd1c9bfd65e 100644\n--- a/Documentation/git-interpret-trailers.txt\n+++ b/Documentation/git-interpret-trailers.txt\n@@ -236,21 +236,34 @@ trailer.<token>.command::\n \tbe called to automatically add or modify a trailer with the\n \tspecified <token>.\n +\n-When this option is specified, the behavior is as if a special\n-'<token>=<value>' argument were added at the beginning of the command\n-line, where <value> is taken to be the standard output of the\n-specified command with any leading and trailing whitespace trimmed\n-off.\n+When this option is specified, the first occurrence of substring $ARG is\n+replaced with the value given to the `interpret-trailer` command for the\n+same token.\n +\n-If the command contains the `$ARG` string, this string will be\n-replaced with the <value> part of an existing trailer with the same\n-<token>, if any, before the command is launched.\n+\".command\" has been deprecated due to the $ARG in the user's command can\n+only be replaced once and the original way of replacing $ARG was not safe.\n+Now the preferred option is using \"trailer.<token>.cmd\", which use position\n+argument to pass the value.\n++\n+When both .cmd and .command are given for the same <token>,\n+.cmd is used and .command is ignored.\n+\n+trailer.<token>.cmd::\n+\tThe command specified by this configuration variable is run\n+\twith a single parameter, which is the <value> part of an\n+\texisting trailer with the same <token>.  The output from the\n+\tcommand is then used as the value for the <token> in the\n+\tresulting trailer.\n++\n+When this option is specified, If there is no trailer with same <token>,\n+the behavior is as if a special '<token>=<value>' argument were added at\n+the beginning of the command, <value> will be passed to the user's\n+command as an empty value.\n +\n If some '<token>=<value>' arguments are also passed on the command\n line, when a 'trailer.<token>.command' is configured, the command will\n also be executed for each of these arguments. And the <value> part of\n-these arguments, if any, will be used to replace the `$ARG` string in\n-the command.\n+these arguments, if any, will be passed to the command as first parameter.\n \n EXAMPLES\n --------\n@@ -333,6 +346,48 @@ subject\n Fix #42\n ------------\n \n+* Configure a 'see' trailer with a cmd to show the subject of a\n+  commit that is related, and show how it works:\n++\n+------------\n+$ git config trailer.see.key \"See-also: \"\n+$ git config trailer.see.ifExists \"replace\"\n+$ git config trailer.see.ifMissing \"doNothing\"\n+$ git config trailer.see.cmd \"git show -s --pretty=reference \\\"\\$1\\\"\"\n+$ git interpret-trailers <<EOF\n+> subject\n+> \n+> message\n+> \n+> see: HEAD~2\n+> EOF\n+subject\n+\n+message\n+\n+See-also: fe3187489d69c4 (subject of related commit, 2021-3-20)\n+------------\n+\n+* Configure a 'bug' trailer with a cmd to show when and where\n+  was the bug introduced, and show how it works:\n++\n+------------\n+$ git config trailer.bug.key \"Bug-from: \"\n+$ git config trailer.bug.ifExists \"replace\"\n+$ git config trailer.bug.cmd \"git log --grep \\\"\\$1\\\" -1 --pretty=\\\"%h %aD\\\"\"\n+$ git interpret-trailers --trailer=\"bug:the information manager from hell\" <<EOF\n+> subject\n+> \n+> message\n+> \n+> EOF\n+subject\n+\n+message\n+\n+Bug-from: 57d84f8d93 Mon, 6 Aug 2012 18:27:09 +0700\n+------------\n+\n * Configure a 'see' trailer with a command to show the subject of a\n   commit that is related, and show how it works:\n +\ndiff --git a/t/t7513-interpret-trailers.sh b/t/t7513-interpret-trailers.sh\nindex 6602790b5f4c..aec240f1dc05 100755\n--- a/t/t7513-interpret-trailers.sh\n+++ b/t/t7513-interpret-trailers.sh\n@@ -51,6 +51,24 @@ test_expect_success 'setup' '\n \tEOF\n '\n \n+test_expect_success 'with cmd' '\n+\ttest_when_finished \"git config --unset trailer.bug.key && \\\n+\tgit config --unset trailer.bug.ifExists && \\\n+\tgit config --unset trailer.bug.cmd\" &&\n+\tgit config trailer.bug.key \"Bug-maker: \" &&\n+\tgit config trailer.bug.ifExists \"add\" &&\n+\tgit config trailer.bug.cmd \"echo \\\"\\$@\\\"\" &&\n+\tcat >>expected2 <<-EOF &&\n+\n+\tBug-maker: \n+\tBug-maker: jocker\n+\tBug-maker: batman\n+\tEOF\n+\tgit interpret-trailers --trailer \"bug: jocker\" --trailer \"bug:batman\" \\\n+\t\t>actual2 &&\n+\ttest_cmp expected2 actual2\n+'\n+\n test_expect_success 'without config' '\n \tsed -e \"s/ Z\\$/ /\" >expected <<-\\EOF &&\n \n@@ -1274,9 +1292,50 @@ test_expect_success 'setup a commit' '\n \tgit commit -m \"Add file a.txt\"\n '\n \n+test_expect_success 'with cmd and $1' '\n+\ttest_when_finished \"git config --unset trailer.fix.cmd\" &&\n+\tgit config trailer.fix.ifExists \"replace\" &&\n+\tgit config trailer.fix.cmd \"test -n \\\"\\$1\\\" && git log -1 --oneline --format=\\\"%h (%s)\\\" \\\n+\t\t--abbrev-commit --abbrev=14 \\\"\\$1\\\" || true\" &&\n+\tFIXED=$(git log -1 --oneline --format=\"%h (%s)\" --abbrev-commit --abbrev=14 HEAD) &&\n+\tcat complex_message_body >expected2 &&\n+\tsed -e \"s/ Z\\$/ /\" >>expected2 <<-EOF &&\n+\t\tFixes: $FIXED\n+\t\tAcked-by= Z\n+\t\tReviewed-by:\n+\t\tSigned-off-by: Z\n+\t\tSigned-off-by: A U Thor <author@example.com>\n+\tEOF\n+\tgit interpret-trailers --trailer \"review:\" --trailer \"fix=HEAD\" \\\n+\t\t<complex_message >actual2 &&\n+\ttest_cmp expected2 actual2\n+'\n+\n+test_expect_success 'cmd takes precedence over command' '\n+\ttest_when_finished \"git config --unset trailer.fix.cmd\" &&\n+\tgit config trailer.fix.ifExists \"replace\" &&\n+\tgit config trailer.fix.cmd \"test -n \\\"\\$1\\\" && git log -1 --oneline --format=\\\"%h (%aN)\\\" \\\n+\t\t--abbrev-commit --abbrev=14 \\\"\\$1\\\" || true\" &&\n+\tgit config trailer.fix.command \"git log -1 --oneline --format=\\\"%h (%s)\\\" \\\n+\t\t--abbrev-commit --abbrev=14 \\$ARG\" &&\n+\tFIXED=$(git log -1 --oneline --format=\"%h (%aN)\" --abbrev-commit --abbrev=14 HEAD) &&\n+\tcat complex_message_body >expected2 &&\n+\tsed -e \"s/ Z\\$/ /\" >>expected2 <<-EOF &&\n+\t\tFixes: $FIXED\n+\t\tAcked-by= Z\n+\t\tReviewed-by:\n+\t\tSigned-off-by: Z\n+\t\tSigned-off-by: A U Thor <author@example.com>\n+\tEOF\n+\tgit interpret-trailers --trailer \"review:\" --trailer \"fix=HEAD\" \\\n+\t\t<complex_message >actual2 &&\n+\ttest_cmp expected2 actual2\n+'\n+\n test_expect_success 'with command using $ARG' '\n \tgit config trailer.fix.ifExists \"replace\" &&\n-\tgit config trailer.fix.command \"git log -1 --oneline --format=\\\"%h (%s)\\\" --abbrev-commit --abbrev=14 \\$ARG\" &&\n+\tgit config trailer.fix.command \"git log -1 --oneline --format=\\\"%h (%s)\\\" \\\n+\t\t--abbrev-commit --abbrev=14 \\$ARG\" &&\n \tFIXED=$(git log -1 --oneline --format=\"%h (%s)\" --abbrev-commit --abbrev=14 HEAD) &&\n \tcat complex_message_body >expected &&\n \tsed -e \"s/ Z\\$/ /\" >>expected <<-EOF &&\ndiff --git a/trailer.c b/trailer.c\nindex 35dd0f4c8512..a000293d6e7e 100644\n--- a/trailer.c\n+++ b/trailer.c\n@@ -14,6 +14,7 @@ struct conf_info {\n \tchar *name;\n \tchar *key;\n \tchar *command;\n+\tchar *cmd;\n \tenum trailer_where where;\n \tenum trailer_if_exists if_exists;\n \tenum trailer_if_missing if_missing;\n@@ -127,6 +128,7 @@ static void free_arg_item(struct arg_item *item)\n \tfree(item->conf.name);\n \tfree(item->conf.key);\n \tfree(item->conf.command);\n+\tfree(item->conf.cmd);\n \tfree(item->token);\n \tfree(item->value);\n \tfree(item);\n@@ -216,22 +218,28 @@ static int check_if_different(struct trailer_item *in_tok,\n \treturn 1;\n }\n \n-static char *apply_command(const char *command, const char *arg)\n+static char *apply_command(struct conf_info *conf, const char *arg)\n {\n \tstruct strbuf cmd = STRBUF_INIT;\n \tstruct strbuf buf = STRBUF_INIT;\n \tstruct child_process cp = CHILD_PROCESS_INIT;\n \tchar *result;\n \n-\tstrbuf_addstr(&cmd, command);\n-\tif (arg)\n-\t\tstrbuf_replace(&cmd, TRAILER_ARG_STRING, arg);\n-\n-\tstrvec_push(&cp.args, cmd.buf);\n+\tif (conf->cmd) {\n+\t\tcp.shell_no_implicit_args = 1;\n+\t\tstrbuf_addstr(&cmd, conf->cmd);\n+\t\tstrvec_push(&cp.args, cmd.buf);\n+\t\tif (arg)\n+\t\t\tstrvec_push(&cp.args, arg);\n+\t} else if (conf->command) {\n+\t\tstrbuf_addstr(&cmd, conf->command);\n+\t\tstrvec_push(&cp.args, cmd.buf);\n+\t\tif (arg)\n+\t\t\tstrbuf_replace(&cmd, TRAILER_ARG_STRING, arg);\n+\t}\n \tcp.env = local_repo_env;\n \tcp.no_stdin = 1;\n \tcp.use_shell = 1;\n-\tcp.shell_no_implicit_args = 1;\n \n \tif (capture_command(&cp, &buf, 1024)) {\n \t\terror(_(\"running trailer command '%s' failed\"), cmd.buf);\n@@ -248,7 +256,7 @@ static char *apply_command(const char *command, const char *arg)\n \n static void apply_item_command(struct trailer_item *in_tok, struct arg_item *arg_tok)\n {\n-\tif (arg_tok->conf.command) {\n+\tif (arg_tok->conf.command || arg_tok->conf.cmd) {\n \t\tconst char *arg;\n \t\tif (arg_tok->value && arg_tok->value[0]) {\n \t\t\targ = arg_tok->value;\n@@ -258,7 +266,7 @@ static void apply_item_command(struct trailer_item *in_tok, struct arg_item *arg\n \t\t\telse\n \t\t\t\targ = xstrdup(\"\");\n \t\t}\n-\t\targ_tok->value = apply_command(arg_tok->conf.command, arg);\n+\t\targ_tok->value = apply_command(&arg_tok->conf, arg);\n \t\tfree((char *)arg);\n \t}\n }\n@@ -431,6 +439,7 @@ static void duplicate_conf(struct conf_info *dst, const struct conf_info *src)\n \tdst->name = xstrdup_or_null(src->name);\n \tdst->key = xstrdup_or_null(src->key);\n \tdst->command = xstrdup_or_null(src->command);\n+\tdst->cmd = xstrdup_or_null(src->cmd);\n }\n \n static struct arg_item *get_conf_item(const char *name)\n@@ -455,8 +464,8 @@ static struct arg_item *get_conf_item(const char *name)\n \treturn item;\n }\n \n-enum trailer_info_type { TRAILER_KEY, TRAILER_COMMAND, TRAILER_WHERE,\n-\t\t\t TRAILER_IF_EXISTS, TRAILER_IF_MISSING };\n+enum trailer_info_type { TRAILER_KEY, TRAILER_COMMAND, TRAILER_CMD,\n+\t\t\tTRAILER_WHERE, TRAILER_IF_EXISTS, TRAILER_IF_MISSING };\n \n static struct {\n \tconst char *name;\n@@ -464,6 +473,7 @@ static struct {\n } trailer_config_items[] = {\n \t{ \"key\", TRAILER_KEY },\n \t{ \"command\", TRAILER_COMMAND },\n+\t{ \"cmd\", TRAILER_CMD },\n \t{ \"where\", TRAILER_WHERE },\n \t{ \"ifexists\", TRAILER_IF_EXISTS },\n \t{ \"ifmissing\", TRAILER_IF_MISSING }\n@@ -543,6 +553,11 @@ static int git_trailer_config(const char *conf_key, const char *value, void *cb)\n \t\t\twarning(_(\"more than one %s\"), conf_key);\n \t\tconf->command = xstrdup(value);\n \t\tbreak;\n+\tcase TRAILER_CMD:\n+\t\tif (conf->cmd)\n+\t\t\twarning(_(\"more than one %s\"), conf_key);\n+\t\tconf->cmd = xstrdup(value);\n+\t\tbreak;\n \tcase TRAILER_WHERE:\n \t\tif (trailer_set_where(&conf->where, value))\n \t\t\twarning(_(\"unknown value '%s' for key '%s'\"), value, conf_key);\n@@ -709,7 +724,7 @@ static void process_command_line_args(struct list_head *arg_head,\n \t/* Add an arg item for each configured trailer with a command */\n \tlist_for_each(pos, &conf_head) {\n \t\titem = list_entry(pos, struct arg_item, list);\n-\t\tif (item->conf.command)\n+\t\tif (item->conf.cmd || item->conf.command)\n \t\t\tadd_arg_item(arg_head,\n \t\t\t\t     xstrdup(token_from_item(item, NULL)),\n \t\t\t\t     xstrdup(\"\"),\n-- \ngitgitgadget\n"},{"id":"420716","messageId":"xmqqeefv89tc.fsf@gitster.g","threadId":"55371","inReplyTo":"CAOLTT8SLX7wVfND9ru8NPx_YhvP2Ed17UcuRdY6Uioj9XSFmwQ@mail.gmail.com","subject":"Re: [PATCH v4] [GSOC]trailer: pass arg as positional parameter","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2021-03-31T18:19:59Z","receivedAt":"2021-03-31T18:20:40Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"ZheNing Hu <adlternative@gmail.com> writes:\n\n> The configuration is like this:\n> trailer.bug.key=BUG:\n> trailer.bug.ifexists=add\n> trailer.bug.cmd=echo \"123\"\n>\n> And use:\n>\n> $ git interpret-trailers --trailer=\"bug:456\" --trailer=\"bug:789\"<<-EOF\n> EOF\n>\n> BUG: 123\n> BUG: 123 456\n> BUG: 123 789\n\nI think that is quite expected.  You said the command to run is\n'echo 123', and that is not \"pick a directory $D on $PATH where\nthere is an executable '$D/echo 123' exists, and run that\".  It\nruns the given command with the shell, and in general that is\nwhat we want for end-user supplied commands specified in the\nconfiguration file [*1*].\n\nSo we form a shell command whose beginning is 'echo 123' and tuck\nthe argument after that command line, so it is understandable that\n\"echo 123 456\" gets executed for \"--trailer=bug:456\".\n\nI wasn't following the discussion between you and Christian closely\nbut I recall seeing him saying that the command is executed one\nextra time without any arg before it is run for actual --trailer\nrequests with the value?  I am guessing that is where the first\noutput \"BUG: 123\" (without anything else) is coming from.\n\n\n*1* Imagine .editor set to 'emacs -nw' or 'vim -f'; we do not want\n    Git to find a directory on $PATH that has an executable whose\n    name is 'emacs -nw' and run that file (i.e. give 'emacs -nw' as\n    the first argument to execlp()).  Instead, you'd want to behave\n    as if the user typed \"emacs -nw\", followed by any arguments we\n    want to give to it (in .editor's case, the name of the file to\n    be edited) properly quoted for the shell.\n\n    And the way we do so is to form a moral equivalent of\n\n\texeclp(\"sh\", \"-c\", \"emacs -nw $@\", ...);\n\n    and put the arguments at the end where I wrote ... (we actually\n    do so with execvp(), but illustrating with execlp() is easier to\n    read and write---hence \"a moral equivalent of\").\n"},{"id":"420717","messageId":"xmqq7dln89dd.fsf@gitster.g","threadId":"55371","inReplyTo":"xmqqeefv89tc.fsf@gitster.g","subject":"Re: [PATCH v4] [GSOC]trailer: pass arg as positional parameter","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2021-03-31T18:29:34Z","receivedAt":"2021-03-31T18:30:27Z","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> ZheNing Hu <adlternative@gmail.com> writes:\n>\n>> The configuration is like this:\n>> trailer.bug.key=BUG:\n>> trailer.bug.ifexists=add\n>> trailer.bug.cmd=echo \"123\"\n>>\n>> And use:\n>>\n>> $ git interpret-trailers --trailer=\"bug:456\" --trailer=\"bug:789\"<<-EOF\n>> EOF\n>>\n>> BUG: 123\n>> BUG: 123 456\n>> BUG: 123 789\n>\n> I think that is quite expected.  You said the command to run is\n> 'echo 123', and that is not \"pick a directory $D on $PATH where\n> there is an executable '$D/echo 123' exists, and run that\".  It\n> runs the given command with the shell, and in general that is\n> what we want for end-user supplied commands specified in the\n> configuration file [*1*].\n> ...\n> *1* Imagine .editor set to 'emacs -nw' or 'vim -f'; we do not want\n>     Git to find a directory on $PATH that has an executable whose\n>     name is 'emacs -nw' and run that file (i.e. give 'emacs -nw' as\n>     the first argument to execlp()).  Instead, you'd want to behave\n>     as if the user typed \"emacs -nw\", followed by any arguments we\n>     want to give to it (in .editor's case, the name of the file to\n>     be edited) properly quoted for the shell.\n>\n>     And the way we do so is to form a moral equivalent of\n>\n> \texeclp(\"sh\", \"-c\", \"emacs -nw \\\"$@\\\"\", ...);\n>\n>     and put the arguments at the end where I wrote ... (we actually\n>     do so with execvp(), but illustrating with execlp() is easier to\n>     read and write---hence \"a moral equivalent of\").\n\nSo, learning from that .editor example, what you can do when you do\nnot want to take any parameter is to explicitly ignore them.  \n\nLet's take the very basic form first.  Imagine you wrote a little\nscript and wanted to see three \"123\", ignoring end-user input after\n\"--trailer=bug:\".\n\n    .cmd = my-script 123\n\nwould run 'my-script \"$@\"'.  What should you write in my-script to\ncause that happen?  Here is an example solution:\n\n    #!/bin/sh\n    echo 123\n\nNotice that \"$1\" is completely ignored, even if the machinery that\ndrives .cmd makes three calls?\n\n\tsh -c 'my-script 123 \"$@\"'\n\tsh -c 'my-script 123 \"$@\"' 456\n\tsh -c 'my-script 123 \"$@\"' 789\n\nThe way to do the same without an extra script on disk is for you to\nuse sh-c yourself.\n\n    .cmd = sh -c 'echo 123'\n\nAnd if you do want to use $1, you can do the same.  E.g. if you want\nto double them in the output, you'd probably do something like this:\n\n    .cmd = sh -c 'echo \"<$1 - $1>\"'\n\nYou'd need to quote the value appropriately for the config file,\nthough.\n"},{"id":"420794","messageId":"CAOLTT8Tn9E5qo-mv_o9-r+zQ0-FT6vOH3BZDLuXpACjPscMqeQ@mail.gmail.com","threadId":"55371","inReplyTo":"xmqqeefv89tc.fsf@gitster.g","subject":"Re: [PATCH v4] [GSOC]trailer: pass arg as positional parameter","fromName":"ZheNing Hu","fromEmail":"adlternative@gmail.com","sentAt":"2021-04-01T03:39:30Z","receivedAt":"2021-04-01T03:40:42Z","isPatch":true,"sender":{"key":"adlternative@gmail.com","avatar":"https://avatars.githubusercontent.com/u/58138461?v=4"},"body":"Junio C Hamano <gitster@pobox.com> 于2021年4月1日周四 上午2:20写道：\n>\n> ZheNing Hu <adlternative@gmail.com> writes:\n>\n> > The configuration is like this:\n> > trailer.bug.key=BUG:\n> > trailer.bug.ifexists=add\n> > trailer.bug.cmd=echo \"123\"\n> >\n> > And use:\n> >\n> > $ git interpret-trailers --trailer=\"bug:456\" --trailer=\"bug:789\"<<-EOF\n> > EOF\n> >\n> > BUG: 123\n> > BUG: 123 456\n> > BUG: 123 789\n>\n> I think that is quite expected.  You said the command to run is\n> 'echo 123', and that is not \"pick a directory $D on $PATH where\n> there is an executable '$D/echo 123' exists, and run that\".  It\n> runs the given command with the shell, and in general that is\n> what we want for end-user supplied commands specified in the\n> configuration file [*1*].\n>\n\nI agree that if you want to use execv directly to execute a terminal\ncommand, if arg[0] is something like \"emacs -nw\", error will be\nreported:\"No such file or directory\". But by wrapping a layer of\n\"sh\" \"-c\", The program name 'emacs' in 'emacs -nw' can be\nfound and executed normally.\n\n> So we form a shell command whose beginning is 'echo 123' and tuck\n> the argument after that command line, so it is understandable that\n> \"echo 123 456\" gets executed for \"--trailer=bug:456\".\n>\n> I wasn't following the discussion between you and Christian closely\n> but I recall seeing him saying that the command is executed one\n> extra time without any arg before it is run for actual --trailer\n> requests with the value?  I am guessing that is where the first\n> output \"BUG: 123\" (without anything else) is coming from.\n>\n\nExactly.\nEach .command/.cmd willl executes this 'beforeCLI' operation  once,\nI use ifexists='add' here just for seeing the effect, In general, we will\nuse ifexists='replace'.\n\n>\n> *1* Imagine .editor set to 'emacs -nw' or 'vim -f'; we do not want\n>     Git to find a directory on $PATH that has an executable whose\n>     name is 'emacs -nw' and run that file (i.e. give 'emacs -nw' as\n>     the first argument to execlp()).  Instead, you'd want to behave\n>     as if the user typed \"emacs -nw\", followed by any arguments we\n>     want to give to it (in .editor's case, the name of the file to\n>     be edited) properly quoted for the shell.\n>\n>     And the way we do so is to form a moral equivalent of\n>\n>         execlp(\"sh\", \"-c\", \"emacs -nw $@\", ...);\n>\n>     and put the arguments at the end where I wrote ... (we actually\n>     do so with execvp(), but illustrating with execlp() is easier to\n>     read and write---hence \"a moral equivalent of\").\n\nI can see the benefits of this.\n"},{"id":"420795","messageId":"CAOLTT8SV_ixYWTV-83vY6B6HUTwW1hTWJqamstf=F6tzkJm3aQ@mail.gmail.com","threadId":"55371","inReplyTo":"xmqq7dln89dd.fsf@gitster.g","subject":"Re: [PATCH v4] [GSOC]trailer: pass arg as positional parameter","fromName":"ZheNing Hu","fromEmail":"adlternative@gmail.com","sentAt":"2021-04-01T03:56:30Z","receivedAt":"2021-04-01T03:57:38Z","isPatch":true,"sender":{"key":"adlternative@gmail.com","avatar":"https://avatars.githubusercontent.com/u/58138461?v=4"},"body":"Junio C Hamano <gitster@pobox.com> 于2021年4月1日周四 上午2:29写道：\n>\n> Junio C Hamano <gitster@pobox.com> writes:\n>\n> > ZheNing Hu <adlternative@gmail.com> writes:\n> >\n> >> The configuration is like this:\n> >> trailer.bug.key=BUG:\n> >> trailer.bug.ifexists=add\n> >> trailer.bug.cmd=echo \"123\"\n> >>\n> >> And use:\n> >>\n> >> $ git interpret-trailers --trailer=\"bug:456\" --trailer=\"bug:789\"<<-EOF\n> >> EOF\n> >>\n> >> BUG: 123\n> >> BUG: 123 456\n> >> BUG: 123 789\n> >\n> > I think that is quite expected.  You said the command to run is\n> > 'echo 123', and that is not \"pick a directory $D on $PATH where\n> > there is an executable '$D/echo 123' exists, and run that\".  It\n> > runs the given command with the shell, and in general that is\n> > what we want for end-user supplied commands specified in the\n> > configuration file [*1*].\n> > ...\n> > *1* Imagine .editor set to 'emacs -nw' or 'vim -f'; we do not want\n> >     Git to find a directory on $PATH that has an executable whose\n> >     name is 'emacs -nw' and run that file (i.e. give 'emacs -nw' as\n> >     the first argument to execlp()).  Instead, you'd want to behave\n> >     as if the user typed \"emacs -nw\", followed by any arguments we\n> >     want to give to it (in .editor's case, the name of the file to\n> >     be edited) properly quoted for the shell.\n> >\n> >     And the way we do so is to form a moral equivalent of\n> >\n> >       execlp(\"sh\", \"-c\", \"emacs -nw \\\"$@\\\"\", ...);\n> >\n> >     and put the arguments at the end where I wrote ... (we actually\n> >     do so with execvp(), but illustrating with execlp() is easier to\n> >     read and write---hence \"a moral equivalent of\").\n>\n> So, learning from that .editor example, what you can do when you do\n> not want to take any parameter is to explicitly ignore them.\n>\n> Let's take the very basic form first.  Imagine you wrote a little\n> script and wanted to see three \"123\", ignoring end-user input after\n> \"--trailer=bug:\".\n>\n>     .cmd = my-script 123\n>\n> would run 'my-script \"$@\"'.  What should you write in my-script to\n> cause that happen?  Here is an example solution:\n>\n>     #!/bin/sh\n>     echo 123\n>\n> Notice that \"$1\" is completely ignored, even if the machinery that\n> drives .cmd makes three calls?\n>\n>         sh -c 'my-script 123 \"$@\"'\n>         sh -c 'my-script 123 \"$@\"' 456\n>         sh -c 'my-script 123 \"$@\"' 789\n>\n> The way to do the same without an extra script on disk is for you to\n> use sh-c yourself.\n>\n>     .cmd = sh -c 'echo 123'\n>\n\nThis is indeed a viable solution, But the extra \"sh -c\" seems to put an\nunnecessary burden on the user.\nSometimes I wonder, why not recommend using environment variables\nlike $ARG?\n\n> And if you do want to use $1, you can do the same.  E.g. if you want\n> to double them in the output, you'd probably do something like this:\n>\n>     .cmd = sh -c 'echo \"<$1 - $1>\"'\n>\n> You'd need to quote the value appropriately for the config file,\n> though.\n\nIn fact, In the following example, trailer <value> contains whitespaces ,\n\n$ git interpret-trailers --trailer=\"bug:the information manager from hell\"\n\nwhich can make it to work properly (But it's a little bit tedious):\n\n$ git config trailer.bug.cmd \"sh -c \\\"echo \\\\\\\"\\$1\\\"\\\\\\\"\"\n$ git interpret-trailers --trailer=\"bug:the information manager from hell\"\n\nBug-from:\nBug-from: the information manager from hell\n\nIs there an easier way?\nOr can make the user ignore the details of \"sh -c\"?\n\nThanks.\n\n--\nZheNing Hu\n"},{"id":"420799","messageId":"CAP8UFD2tb6Fca58YPiFejqGKm3Mu+NaMA8YU3HQj+c2L-AH-Yw@mail.gmail.com","threadId":"55371","inReplyTo":"4c59cab53a0d9bb7c9cccfaf5544ae5c904bb2ba.1617185147.git.gitgitgadget@gmail.com","subject":"Re: [PATCH v5 1/2] [GSOC] run-command: add shell_no_implicit_args option","fromName":"Christian Couder","fromEmail":"christian.couder@gmail.com","sentAt":"2021-04-01T07:22:10Z","receivedAt":"2021-04-01T07:23:03Z","isPatch":true,"sender":{"key":"christian.couder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/208954?v=4"},"body":"On Wed, Mar 31, 2021 at 12:05 PM ZheNing Hu via GitGitGadget\n<gitgitgadget@gmail.com> wrote:\n>\n> From: ZheNing Hu <adlternative@gmail.com>\n>\n> When we use subprocess to run a shell-script, if we have any\n\nMaybe: s/subprocess/a subprocess/\n\n> args, git will add extra $@ to the end of the shell-script,\n> This can pass positional parameters correctly, But if we just\n> want to use some of these passed parameters, git will still\n> add an extra \"$@\", which contains all positional parameters we\n> passed. This does not meet our expectations.\n\nI am not sure explaining things using $@ is the best way to make this\nas clear as possible. I don't have a clear alternative right now\nthough.\n\n> E.g. our shell-script is:\n> \"echo \\\"\\$1\\\"\"\n> and pass $1 \"abc\",\n\nMaybe: s/pass $1 \"abc\"/we pass \"abc\" as $1/\n\n> git will change our script to:\n> \"echo \\\"\\$1\\\" \\\"$@\\\"\"\n\nWhere will \"abc\" appear then?\n\n> The positional parameters we entered will be printed\n> repeatedly.\n\nIf you take us passing \"abc\" in $1 as an example, then I think it's a\ngood idea to show us the result of that.\n\n> So let add a new `shell_no_implicit_args`\n\nMaybe: s/`shell_no_implicit_args`/`shell_no_implicit_args` flag/\n\n> to `struct child_process`, which can suppress the\n> joining of $@ if `shell_no_implicit_args` is set to 1,\n> this will allow us to use only few of positional args\n> in multi-parameter shell script, instead of using all\n> of them.\n\nI think our goal is more to have each argument we pass be passed just once.\n"},{"id":"420800","messageId":"CAP8UFD0a_HQ9bEQLASn_D1jj+ut+okENJs=aENDxTDL1f2VZKQ@mail.gmail.com","threadId":"55371","inReplyTo":"pull.913.v5.git.1617185147.gitgitgadget@gmail.com","subject":"Re: [PATCH v5 0/2] [GSOC]trailer: pass arg as positional parameter","fromName":"Christian Couder","fromEmail":"christian.couder@gmail.com","sentAt":"2021-04-01T07:28:43Z","receivedAt":"2021-04-01T07:30:01Z","isPatch":true,"sender":{"key":"christian.couder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/208954?v=4"},"body":"On Wed, Mar 31, 2021 at 12:05 PM ZheNing Hu via GitGitGadget\n<gitgitgadget@gmail.com> wrote:\n>\n> In https://lore.kernel.org/git/xmqqv99i4ck2.fsf@gitster.g/ Junio and\n> Christian talked about the problem of using strbuf_replace() to replace\n> $ARG.\n\nIt's better to sum up a bit the discussions. It's nice to provide a\nlink to the discussions though.\n\n> Now pass trailer value as $1 to the trailer command with another\n> trailer.<token>.cmd config.\n\nIf this patch series introduces a new trailer.<token>.cmd config\noption, then I would expect one of the patch in the series to have a\nsubject like \"trailer: add new trailer.<token>.cmd config option\".\n\n> ZheNing Hu (2):\n>   [GSOC] run-command: add shell_no_implicit_args option\n>   [GSOC]trailer: pass arg as positional parameter\n\nI guess the \"trailer: pass arg as positional parameter\" is the one\nintroducing the new trailer.<token>.cmd config option.\n\nAlso it seems strange that there is no space between \"[GSOC]\" and \"trailer\".\n"},{"id":"420803","messageId":"CAOLTT8QUwjpb5AqKR0FgxWH_ESPzy8-fkDJk18uggO_SkT4Fzg@mail.gmail.com","threadId":"55371","inReplyTo":"CAP8UFD2tb6Fca58YPiFejqGKm3Mu+NaMA8YU3HQj+c2L-AH-Yw@mail.gmail.com","subject":"Re: [PATCH v5 1/2] [GSOC] run-command: add shell_no_implicit_args option","fromName":"ZheNing Hu","fromEmail":"adlternative@gmail.com","sentAt":"2021-04-01T09:58:07Z","receivedAt":"2021-04-01T09:59:19Z","isPatch":true,"sender":{"key":"adlternative@gmail.com","avatar":"https://avatars.githubusercontent.com/u/58138461?v=4"},"body":"Christian Couder <christian.couder@gmail.com> 于2021年4月1日周四 下午3:22写道：\n>\n> On Wed, Mar 31, 2021 at 12:05 PM ZheNing Hu via GitGitGadget\n> <gitgitgadget@gmail.com> wrote:\n> >\n> > From: ZheNing Hu <adlternative@gmail.com>\n> >\n> > When we use subprocess to run a shell-script, if we have any\n>\n> Maybe: s/subprocess/a subprocess/\n>\n> > args, git will add extra $@ to the end of the shell-script,\n> > This can pass positional parameters correctly, But if we just\n> > want to use some of these passed parameters, git will still\n> > add an extra \"$@\", which contains all positional parameters we\n> > passed. This does not meet our expectations.\n>\n> I am not sure explaining things using $@ is the best way to make this\n> as clear as possible. I don't have a clear alternative right now\n> though.\n>\n> > E.g. our shell-script is:\n> > \"echo \\\"\\$1\\\"\"\n> > and pass $1 \"abc\",\n>\n> Maybe: s/pass $1 \"abc\"/we pass \"abc\" as $1/\n>\n> > git will change our script to:\n> > \"echo \\\"\\$1\\\" \\\"$@\\\"\"\n>\n> Where will \"abc\" appear then?\n>\n> > The positional parameters we entered will be printed\n> > repeatedly.\n>\n> If you take us passing \"abc\" in $1 as an example, then I think it's a\n> good idea to show us the result of that.\n>\n> > So let add a new `shell_no_implicit_args`\n>\n> Maybe: s/`shell_no_implicit_args`/`shell_no_implicit_args` flag/\n>\n\nThanks for these grammar corrections.\n\n> > to `struct child_process`, which can suppress the\n> > joining of $@ if `shell_no_implicit_args` is set to 1,\n> > this will allow us to use only few of positional args\n> > in multi-parameter shell script, instead of using all\n> > of them.\n>\n> I think our goal is more to have each argument we pass be passed just once.\n\nMore accurately, we only want those explicit positional\nparameters to be replaced.\nBut Junio probably thinks it's OK to put on a layer of\n\"sh -c\"  to \"absorb\" the \"$@\". I think it works, but it may\ncause some trouble for users.\n\n--\nZheNing Hu\n"},{"id":"420804","messageId":"CAOLTT8QxB==BPfwBVex5uof=3EBGdEoTPci7PP1K0mfkQY-5aQ@mail.gmail.com","threadId":"55371","inReplyTo":"CAP8UFD0a_HQ9bEQLASn_D1jj+ut+okENJs=aENDxTDL1f2VZKQ@mail.gmail.com","subject":"Re: [PATCH v5 0/2] [GSOC]trailer: pass arg as positional parameter","fromName":"ZheNing Hu","fromEmail":"adlternative@gmail.com","sentAt":"2021-04-01T10:02:09Z","receivedAt":"2021-04-01T10:03:31Z","isPatch":true,"sender":{"key":"adlternative@gmail.com","avatar":"https://avatars.githubusercontent.com/u/58138461?v=4"},"body":"Christian Couder <christian.couder@gmail.com> 于2021年4月1日周四 下午3:28写道：\n>\n> On Wed, Mar 31, 2021 at 12:05 PM ZheNing Hu via GitGitGadget\n> <gitgitgadget@gmail.com> wrote:\n> >\n> > In https://lore.kernel.org/git/xmqqv99i4ck2.fsf@gitster.g/ Junio and\n> > Christian talked about the problem of using strbuf_replace() to replace\n> > $ARG.\n>\n> It's better to sum up a bit the discussions. It's nice to provide a\n> link to the discussions though.\n>\n> > Now pass trailer value as $1 to the trailer command with another\n> > trailer.<token>.cmd config.\n>\n> If this patch series introduces a new trailer.<token>.cmd config\n> option, then I would expect one of the patch in the series to have a\n> subject like \"trailer: add new trailer.<token>.cmd config option\".\n>\n> > ZheNing Hu (2):\n> >   [GSOC] run-command: add shell_no_implicit_args option\n> >   [GSOC]trailer: pass arg as positional parameter\n>\n> I guess the \"trailer: pass arg as positional parameter\" is the one\n> introducing the new trailer.<token>.cmd config option.\n>\n> Also it seems strange that there is no space between \"[GSOC]\" and \"trailer\".\n\nThanks, I'll fix them.\n\n--\nZheNing Hu\n"},{"id":"420847","messageId":"xmqqlfa13hw3.fsf@gitster.g","threadId":"55371","inReplyTo":"CAOLTT8SV_ixYWTV-83vY6B6HUTwW1hTWJqamstf=F6tzkJm3aQ@mail.gmail.com","subject":"Re: [PATCH v4] [GSOC]trailer: pass arg as positional parameter","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2021-04-01T19:49:00Z","receivedAt":"2021-04-01T19:49:04Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"ZheNing Hu <adlternative@gmail.com> writes:\n\n>> The way to do the same without an extra script on disk is for you to\n>> use sh-c yourself.\n>>\n>>     .cmd = sh -c 'echo 123'\n>\n> This is indeed a viable solution, But the extra \"sh -c\" seems to put an\n> unnecessary burden on the user.\n\nNobody forces you to write long script in the configuration file.\nIn fact, the \"find author from history\" is so useful that I'd think\npeople have an alias or a script in ~/bin/ already for their own\ninteractive use.  E.g.\n\n    $ cat ~/bin/git-who\n    #!/bin/sh\n    git log -1 --format=\"%an <%ae>\" --author=\"$1\"\n    $ cat ~/bin/git-one\n    #!/bin/sh\n    git show -s --pretty=reference \"$1\"\n\nand with them:\n\n\ttrailer.key.cmd = git who\n\nthat is internally wrapped into \n\n\tsh -c 'git who \"$@\"'\n\nand fed \"gitster@\" as the first parameter when \"--trailer=key:gitster@\"\nis given would work just fine.\n\n> Sometimes I wonder, why not recommend using environment variables\n> like $ARG?\n\nI am also fine with that; when we discovered the design flaw of\n.command, I think I suggested either would make a viable choice.\nThe only downside is that it would squat on a good name $ARG and\nforbids end-users from using the symbol for other purpose, but as\nlong as the application is limited in scope, that would be fine.\n"},{"id":"420859","messageId":"CAOLTT8TDiAxJh3xn1WdF5mTtbA29nTSP0ohyi6aqGds8p6vd1A@mail.gmail.com","threadId":"55371","inReplyTo":"xmqqlfa13hw3.fsf@gitster.g","subject":"Re: [PATCH v4] [GSOC]trailer: pass arg as positional parameter","fromName":"ZheNing Hu","fromEmail":"adlternative@gmail.com","sentAt":"2021-04-02T02:08:26Z","receivedAt":"2021-04-02T02:08:41Z","isPatch":true,"sender":{"key":"adlternative@gmail.com","avatar":"https://avatars.githubusercontent.com/u/58138461?v=4"},"body":"Junio C Hamano <gitster@pobox.com> 于2021年4月2日周五 上午3:49写道：\n>\n> ZheNing Hu <adlternative@gmail.com> writes:\n>\n> >> The way to do the same without an extra script on disk is for you to\n> >> use sh-c yourself.\n> >>\n> >>     .cmd = sh -c 'echo 123'\n> >\n> > This is indeed a viable solution, But the extra \"sh -c\" seems to put an\n> > unnecessary burden on the user.\n>\n> Nobody forces you to write long script in the configuration file.\n> In fact, the \"find author from history\" is so useful that I'd think\n> people have an alias or a script in ~/bin/ already for their own\n> interactive use.  E.g.\n>\n>     $ cat ~/bin/git-who\n>     #!/bin/sh\n>     git log -1 --format=\"%an <%ae>\" --author=\"$1\"\n>     $ cat ~/bin/git-one\n>     #!/bin/sh\n>     git show -s --pretty=reference \"$1\"\n>\n> and with them:\n>\n>         trailer.key.cmd = git who\n>\n> that is internally wrapped into\n>\n>         sh -c 'git who \"$@\"'\n>\n> and fed \"gitster@\" as the first parameter when \"--trailer=key:gitster@\"\n> is given would work just fine.\n>\n\nOkay, now I get it. This allows us to run some common scripts or just use\n\"sh -c\". I might need some additional tests to illustrate those changes made\nby using '.cmd'.\n\n> > Sometimes I wonder, why not recommend using environment variables\n> > like $ARG?\n>\n> I am also fine with that; when we discovered the design flaw of\n> .command, I think I suggested either would make a viable choice.\n> The only downside is that it would squat on a good name $ARG and\n> forbids end-users from using the symbol for other purpose, but as\n> long as the application is limited in scope, that would be fine.\n\nI agree with you. This may indeed be a minor drawback.\n\nThanks, Junio.\n\n--\nZheNing Hu\n"},{"id":"420881","messageId":"pull.913.v6.git.1617369973328.gitgitgadget@gmail.com","threadId":"55371","inReplyTo":"pull.913.v5.git.1617185147.gitgitgadget@gmail.com","subject":"[PATCH v6] [GSOC] trailer: add new trailer.<token>.cmd config option","fromName":"ZheNing Hu via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2021-04-02T13:26:12Z","receivedAt":"2021-04-02T13:26:18Z","isPatch":true,"sender":{"key":"adlternative@gmail.com","avatar":"https://avatars.githubusercontent.com/u/58138461?v=4"},"body":"From: ZheNing Hu <adlternative@gmail.com>\n\nThe `trailer.<token>.command` configuration variable\nspecifies a command (run via the shell, so it does not have\nto be a single name of or path to the command, but can be a\nshell script), and the first occurrence of substring $ARG is\nreplaced with the value given to the `interpret-trailer`\ncommand for the token.  This has two downsides:\n\n* The use of $ARG in the mechanism misleads the users that\nthe value is passed in the shell variable, and tempt them\nto use $ARG more than once, but that would not work, as\nthe second and subsequent $ARG are not replaced.\n\n* Because $ARG is textually replaced without regard to the\nshell language syntax, even '$ARG' (inside a single-quote\npair), which a user would expect to stay intact, would be\nreplaced, and worse, if the value had an unmatching single\nquote (imagine a name like \"O'Connor\", substituted into\nNAME='$ARG' to make it NAME='O'Connor), it would result in\na broken command that is not syntactically correct (or\nworse).\n\nIntroduce a new `trailer.<token>.cmd` configuration that\ntakes higher precedence to deprecate and eventually remove\n`trailer.<token>.command`, which passes the value as a\nparameter to the command.  Instead of \"$ARG\", the users will\nrefer to the value as positional argument, $1, in their\nscripts.\n\nHelped-by: Junio C Hamano <gitster@pobox.com>\nHelped-by: Christian Couder <christian.couder@gmail.com>\nSigned-off-by: ZheNing Hu <adlternative@gmail.com>\n---\n    [GSOC] trailer: add new trailer..cmd config option\n    \n    In https://lore.kernel.org/git/xmqqv99i4ck2.fsf@gitster.g/ Junio and\n    Christian talked about the problem of using strbuf_replace() to replace\n    $ARG:\n    \n     1. if user's script have more than one $ARG, only the first one will be\n        replaced, which is incorrected.\n     2. $ARG is textually replaced without shell syntax, which may result a\n        broken command when $ARG include some unmatching single quote, very\n        unsafe.\n    \n    Now pass trailer value as $1 to the trailer command with another\n    trailer.<token>.cmd config, to solve these above two problems,\n\nPublished-As: https://github.com/gitgitgadget/git/releases/tag/pr-913%2Fadlternative%2Ftrailer-pass-ARG-env-v6\nFetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-913/adlternative/trailer-pass-ARG-env-v6\nPull-Request: https://github.com/gitgitgadget/git/pull/913\n\nRange-diff vs v5:\n\n 1:  4c59cab53a0d < -:  ------------ [GSOC] run-command: add shell_no_implicit_args option\n 2:  5894d8c4b364 ! 1:  3aed77d077b9 [GSOC]trailer: pass arg as positional parameter\n     @@ Metadata\n      Author: ZheNing Hu <adlternative@gmail.com>\n      \n       ## Commit message ##\n     -    [GSOC]trailer: pass arg as positional parameter\n     +    [GSOC] trailer: add new trailer.<token>.cmd config option\n      \n          The `trailer.<token>.command` configuration variable\n          specifies a command (run via the shell, so it does not have\n     @@ Documentation/git-interpret-trailers.txt: subject\n       Fix #42\n       ------------\n       \n     -+* Configure a 'see' trailer with a cmd to show the subject of a\n     -+  commit that is related, and show how it works:\n     ++* Configure a 'see' trailer with a cmd use a global script `git-one`\n     ++  to show the subject of a commit that is related, and show how it works:\n      ++\n      +------------\n     ++$ cat ~/bin/git-one\n     ++#!/bin/sh\n     ++git show -s --pretty=reference \"$1\"\n      +$ git config trailer.see.key \"See-also: \"\n      +$ git config trailer.see.ifExists \"replace\"\n      +$ git config trailer.see.ifMissing \"doNothing\"\n     -+$ git config trailer.see.cmd \"git show -s --pretty=reference \\\"\\$1\\\"\"\n     ++$ git config trailer.see.cmd \"~/bin/git-one\"\n      +$ git interpret-trailers <<EOF\n      +> subject\n      +> \n     @@ Documentation/git-interpret-trailers.txt: subject\n      +\n      +message\n      +\n     -+See-also: fe3187489d69c4 (subject of related commit, 2021-3-20)\n     ++See-also: fe3187e (subject of related commit, 2021-4-2)\n      +------------\n      +\n     -+* Configure a 'bug' trailer with a cmd to show when and where\n     -+  was the bug introduced, and show how it works:\n     ++* Configure a 'who' trailer with a cmd use a global script `git-who`\n     ++  to find the recent matching \"author <mail>\" pair in git log and\n     ++  show how it works:\n      ++\n      +------------\n     -+$ git config trailer.bug.key \"Bug-from: \"\n     -+$ git config trailer.bug.ifExists \"replace\"\n     -+$ git config trailer.bug.cmd \"git log --grep \\\"\\$1\\\" -1 --pretty=\\\"%h %aD\\\"\"\n     -+$ git interpret-trailers --trailer=\"bug:the information manager from hell\" <<EOF\n     ++$ cat ~/bin/git-who\n     ++ #!/bin/sh\n     ++    git log -1 --format=\"%an <%ae>\" --author=\"$1\"\n     ++$ git config trailer.help.key \"Helped-by: \"\n     ++$ git config trailer.help.ifExists \"replace\"\n     ++$ git config trailer.help.cmd \"~/bin/git-who\"\n     ++$ git interpret-trailers --trailer=\"help:gitster@\" <<EOF\n      +> subject\n      +> \n      +> message\n     @@ Documentation/git-interpret-trailers.txt: subject\n      +\n      +message\n      +\n     -+Bug-from: 57d84f8d93 Mon, 6 Aug 2012 18:27:09 +0700\n     ++Helped-by: Junio C Hamano <gitster@pobox.com>\n      +------------\n      +\n       * Configure a 'see' trailer with a command to show the subject of a\n     @@ t/t7513-interpret-trailers.sh: test_expect_success 'setup' '\n       \n      +test_expect_success 'with cmd' '\n      +\ttest_when_finished \"git config --unset trailer.bug.key && \\\n     -+\tgit config --unset trailer.bug.ifExists && \\\n     -+\tgit config --unset trailer.bug.cmd\" &&\n     ++\t\t\t    git config --unset trailer.bug.ifExists && \\\n     ++\t\t\t    git config --unset trailer.bug.cmd\" &&\n      +\tgit config trailer.bug.key \"Bug-maker: \" &&\n      +\tgit config trailer.bug.ifExists \"add\" &&\n     -+\tgit config trailer.bug.cmd \"echo \\\"\\$@\\\"\" &&\n     -+\tcat >>expected2 <<-EOF &&\n     ++\tgit config trailer.bug.cmd \"echo \\\"maybe is\\\"\" &&\n     ++\tcat >expected2 <<-EOF &&\n      +\n     -+\tBug-maker: \n     -+\tBug-maker: jocker\n     -+\tBug-maker: batman\n     ++\tBug-maker: maybe is\n     ++\tBug-maker: maybe is him\n     ++\tBug-maker: maybe is me\n      +\tEOF\n     -+\tgit interpret-trailers --trailer \"bug: jocker\" --trailer \"bug:batman\" \\\n     ++\tgit interpret-trailers --trailer \"bug: him\" --trailer \"bug:me\" \\\n     ++\t\t>actual2 &&\n     ++\ttest_cmp expected2 actual2\n     ++'\n     ++\n     ++test_expect_success 'with cmd and $1' '\n     ++\ttest_when_finished \"git config --unset trailer.bug.key && \\\n     ++\t\t\t    git config --unset trailer.bug.ifExists && \\\n     ++\t\t\t    git config --unset trailer.bug.cmd\" &&\n     ++\tgit config trailer.bug.key \"Bug-maker: \" &&\n     ++\tgit config trailer.bug.ifExists \"replace\" &&\n     ++\tgit config trailer.bug.cmd \"echo \\\"\\$1\\\" is\" &&\n     ++\tcat >expected2 <<-EOF &&\n     ++\n     ++\tBug-maker: me is me\n     ++\tEOF\n     ++\tgit interpret-trailers --trailer \"bug: him\" --trailer \"bug:me\" \\\n     ++\t\t>actual2 &&\n     ++\ttest_cmp expected2 actual2\n     ++'\n     ++\n     ++test_expect_success 'with cmd and $1 with sh -c' '\n     ++\ttest_when_finished \"git config --unset trailer.bug.key && \\\n     ++\t\t\t    git config --unset trailer.bug.ifExists && \\\n     ++\t\t\t    git config --unset trailer.bug.cmd\" &&\n     ++\tgit config trailer.bug.key \"Bug-maker: \" &&\n     ++\tgit config trailer.bug.ifExists \"replace\" &&\n     ++\tgit config trailer.bug.cmd \"sh -c \\\"echo who is \\\"\\$1\\\"\\\"\" &&\n     ++\tcat >expected2 <<-EOF &&\n     ++\n     ++\tBug-maker: who is me\n     ++\tEOF\n     ++\tgit interpret-trailers --trailer \"bug: him\" --trailer \"bug:me\" \\\n     ++\t\t>actual2 &&\n     ++\ttest_cmp expected2 actual2\n     ++'\n     ++\n     ++test_expect_success 'with cmd and $1 with shell script' '\n     ++\ttest_when_finished \"git config --unset trailer.bug.key && \\\n     ++\t\t\t    git config --unset trailer.bug.ifExists && \\\n     ++\t\t\t    git config --unset trailer.bug.cmd\" &&\n     ++\tgit config trailer.bug.key \"Bug-maker: \" &&\n     ++\tgit config trailer.bug.ifExists \"replace\" &&\n     ++\tgit config trailer.bug.cmd \"./echoscript\" &&\n     ++\tcat >expected2 <<-EOF &&\n     ++\n     ++\tBug-maker: who is me\n     ++\tEOF\n     ++\tcat >echoscript <<-EOF &&\n     ++\t#!/bin/sh\n     ++\techo who is \"\\$1\"\n     ++\tEOF\n     ++\tchmod +x echoscript &&\n     ++\tgit interpret-trailers --trailer \"bug: him\" --trailer \"bug:me\" \\\n      +\t\t>actual2 &&\n      +\ttest_cmp expected2 actual2\n      +'\n     @@ t/t7513-interpret-trailers.sh: test_expect_success 'setup a commit' '\n       \tgit commit -m \"Add file a.txt\"\n       '\n       \n     -+test_expect_success 'with cmd and $1' '\n     -+\ttest_when_finished \"git config --unset trailer.fix.cmd\" &&\n     -+\tgit config trailer.fix.ifExists \"replace\" &&\n     -+\tgit config trailer.fix.cmd \"test -n \\\"\\$1\\\" && git log -1 --oneline --format=\\\"%h (%s)\\\" \\\n     -+\t\t--abbrev-commit --abbrev=14 \\\"\\$1\\\" || true\" &&\n     -+\tFIXED=$(git log -1 --oneline --format=\"%h (%s)\" --abbrev-commit --abbrev=14 HEAD) &&\n     -+\tcat complex_message_body >expected2 &&\n     -+\tsed -e \"s/ Z\\$/ /\" >>expected2 <<-EOF &&\n     -+\t\tFixes: $FIXED\n     -+\t\tAcked-by= Z\n     -+\t\tReviewed-by:\n     -+\t\tSigned-off-by: Z\n     -+\t\tSigned-off-by: A U Thor <author@example.com>\n     -+\tEOF\n     -+\tgit interpret-trailers --trailer \"review:\" --trailer \"fix=HEAD\" \\\n     -+\t\t<complex_message >actual2 &&\n     -+\ttest_cmp expected2 actual2\n     -+'\n     -+\n      +test_expect_success 'cmd takes precedence over command' '\n      +\ttest_when_finished \"git config --unset trailer.fix.cmd\" &&\n      +\tgit config trailer.fix.ifExists \"replace\" &&\n     @@ trailer.c: static int check_if_different(struct trailer_item *in_tok,\n      -\n      -\tstrvec_push(&cp.args, cmd.buf);\n      +\tif (conf->cmd) {\n     -+\t\tcp.shell_no_implicit_args = 1;\n     ++\t\t// cp.shell_no_implicit_args = 1;\n      +\t\tstrbuf_addstr(&cmd, conf->cmd);\n      +\t\tstrvec_push(&cp.args, cmd.buf);\n      +\t\tif (arg)\n     @@ trailer.c: static int check_if_different(struct trailer_item *in_tok,\n       \tcp.env = local_repo_env;\n       \tcp.no_stdin = 1;\n       \tcp.use_shell = 1;\n     --\tcp.shell_no_implicit_args = 1;\n     - \n     - \tif (capture_command(&cp, &buf, 1024)) {\n     - \t\terror(_(\"running trailer command '%s' failed\"), cmd.buf);\n      @@ trailer.c: static char *apply_command(const char *command, const char *arg)\n       \n       static void apply_item_command(struct trailer_item *in_tok, struct arg_item *arg_tok)\n\n\n Documentation/git-interpret-trailers.txt | 82 +++++++++++++++++---\n t/t7513-interpret-trailers.sh            | 95 +++++++++++++++++++++++-\n trailer.c                                | 38 +++++++---\n 3 files changed, 193 insertions(+), 22 deletions(-)\n\ndiff --git a/Documentation/git-interpret-trailers.txt b/Documentation/git-interpret-trailers.txt\nindex 96ec6499f001..67649ec6134c 100644\n--- a/Documentation/git-interpret-trailers.txt\n+++ b/Documentation/git-interpret-trailers.txt\n@@ -236,21 +236,34 @@ trailer.<token>.command::\n \tbe called to automatically add or modify a trailer with the\n \tspecified <token>.\n +\n-When this option is specified, the behavior is as if a special\n-'<token>=<value>' argument were added at the beginning of the command\n-line, where <value> is taken to be the standard output of the\n-specified command with any leading and trailing whitespace trimmed\n-off.\n+When this option is specified, the first occurrence of substring $ARG is\n+replaced with the value given to the `interpret-trailer` command for the\n+same token.\n +\n-If the command contains the `$ARG` string, this string will be\n-replaced with the <value> part of an existing trailer with the same\n-<token>, if any, before the command is launched.\n+\".command\" has been deprecated due to the $ARG in the user's command can\n+only be replaced once and the original way of replacing $ARG was not safe.\n+Now the preferred option is using \"trailer.<token>.cmd\", which use position\n+argument to pass the value.\n++\n+When both .cmd and .command are given for the same <token>,\n+.cmd is used and .command is ignored.\n+\n+trailer.<token>.cmd::\n+\tThe command specified by this configuration variable is run\n+\twith a single parameter, which is the <value> part of an\n+\texisting trailer with the same <token>.  The output from the\n+\tcommand is then used as the value for the <token> in the\n+\tresulting trailer.\n++\n+When this option is specified, If there is no trailer with same <token>,\n+the behavior is as if a special '<token>=<value>' argument were added at\n+the beginning of the command, <value> will be passed to the user's\n+command as an empty value.\n +\n If some '<token>=<value>' arguments are also passed on the command\n line, when a 'trailer.<token>.command' is configured, the command will\n also be executed for each of these arguments. And the <value> part of\n-these arguments, if any, will be used to replace the `$ARG` string in\n-the command.\n+these arguments, if any, will be passed to the command as first parameter.\n \n EXAMPLES\n --------\n@@ -333,6 +346,55 @@ subject\n Fix #42\n ------------\n \n+* Configure a 'see' trailer with a cmd use a global script `git-one`\n+  to show the subject of a commit that is related, and show how it works:\n++\n+------------\n+$ cat ~/bin/git-one\n+#!/bin/sh\n+git show -s --pretty=reference \"$1\"\n+$ git config trailer.see.key \"See-also: \"\n+$ git config trailer.see.ifExists \"replace\"\n+$ git config trailer.see.ifMissing \"doNothing\"\n+$ git config trailer.see.cmd \"~/bin/git-one\"\n+$ git interpret-trailers <<EOF\n+> subject\n+> \n+> message\n+> \n+> see: HEAD~2\n+> EOF\n+subject\n+\n+message\n+\n+See-also: fe3187e (subject of related commit, 2021-4-2)\n+------------\n+\n+* Configure a 'who' trailer with a cmd use a global script `git-who`\n+  to find the recent matching \"author <mail>\" pair in git log and\n+  show how it works:\n++\n+------------\n+$ cat ~/bin/git-who\n+ #!/bin/sh\n+    git log -1 --format=\"%an <%ae>\" --author=\"$1\"\n+$ git config trailer.help.key \"Helped-by: \"\n+$ git config trailer.help.ifExists \"replace\"\n+$ git config trailer.help.cmd \"~/bin/git-who\"\n+$ git interpret-trailers --trailer=\"help:gitster@\" <<EOF\n+> subject\n+> \n+> message\n+> \n+> EOF\n+subject\n+\n+message\n+\n+Helped-by: Junio C Hamano <gitster@pobox.com>\n+------------\n+\n * Configure a 'see' trailer with a command to show the subject of a\n   commit that is related, and show how it works:\n +\ndiff --git a/t/t7513-interpret-trailers.sh b/t/t7513-interpret-trailers.sh\nindex 6602790b5f4c..923923e57573 100755\n--- a/t/t7513-interpret-trailers.sh\n+++ b/t/t7513-interpret-trailers.sh\n@@ -51,6 +51,77 @@ test_expect_success 'setup' '\n \tEOF\n '\n \n+test_expect_success 'with cmd' '\n+\ttest_when_finished \"git config --unset trailer.bug.key && \\\n+\t\t\t    git config --unset trailer.bug.ifExists && \\\n+\t\t\t    git config --unset trailer.bug.cmd\" &&\n+\tgit config trailer.bug.key \"Bug-maker: \" &&\n+\tgit config trailer.bug.ifExists \"add\" &&\n+\tgit config trailer.bug.cmd \"echo \\\"maybe is\\\"\" &&\n+\tcat >expected2 <<-EOF &&\n+\n+\tBug-maker: maybe is\n+\tBug-maker: maybe is him\n+\tBug-maker: maybe is me\n+\tEOF\n+\tgit interpret-trailers --trailer \"bug: him\" --trailer \"bug:me\" \\\n+\t\t>actual2 &&\n+\ttest_cmp expected2 actual2\n+'\n+\n+test_expect_success 'with cmd and $1' '\n+\ttest_when_finished \"git config --unset trailer.bug.key && \\\n+\t\t\t    git config --unset trailer.bug.ifExists && \\\n+\t\t\t    git config --unset trailer.bug.cmd\" &&\n+\tgit config trailer.bug.key \"Bug-maker: \" &&\n+\tgit config trailer.bug.ifExists \"replace\" &&\n+\tgit config trailer.bug.cmd \"echo \\\"\\$1\\\" is\" &&\n+\tcat >expected2 <<-EOF &&\n+\n+\tBug-maker: me is me\n+\tEOF\n+\tgit interpret-trailers --trailer \"bug: him\" --trailer \"bug:me\" \\\n+\t\t>actual2 &&\n+\ttest_cmp expected2 actual2\n+'\n+\n+test_expect_success 'with cmd and $1 with sh -c' '\n+\ttest_when_finished \"git config --unset trailer.bug.key && \\\n+\t\t\t    git config --unset trailer.bug.ifExists && \\\n+\t\t\t    git config --unset trailer.bug.cmd\" &&\n+\tgit config trailer.bug.key \"Bug-maker: \" &&\n+\tgit config trailer.bug.ifExists \"replace\" &&\n+\tgit config trailer.bug.cmd \"sh -c \\\"echo who is \\\"\\$1\\\"\\\"\" &&\n+\tcat >expected2 <<-EOF &&\n+\n+\tBug-maker: who is me\n+\tEOF\n+\tgit interpret-trailers --trailer \"bug: him\" --trailer \"bug:me\" \\\n+\t\t>actual2 &&\n+\ttest_cmp expected2 actual2\n+'\n+\n+test_expect_success 'with cmd and $1 with shell script' '\n+\ttest_when_finished \"git config --unset trailer.bug.key && \\\n+\t\t\t    git config --unset trailer.bug.ifExists && \\\n+\t\t\t    git config --unset trailer.bug.cmd\" &&\n+\tgit config trailer.bug.key \"Bug-maker: \" &&\n+\tgit config trailer.bug.ifExists \"replace\" &&\n+\tgit config trailer.bug.cmd \"./echoscript\" &&\n+\tcat >expected2 <<-EOF &&\n+\n+\tBug-maker: who is me\n+\tEOF\n+\tcat >echoscript <<-EOF &&\n+\t#!/bin/sh\n+\techo who is \"\\$1\"\n+\tEOF\n+\tchmod +x echoscript &&\n+\tgit interpret-trailers --trailer \"bug: him\" --trailer \"bug:me\" \\\n+\t\t>actual2 &&\n+\ttest_cmp expected2 actual2\n+'\n+\n test_expect_success 'without config' '\n \tsed -e \"s/ Z\\$/ /\" >expected <<-\\EOF &&\n \n@@ -1274,9 +1345,31 @@ test_expect_success 'setup a commit' '\n \tgit commit -m \"Add file a.txt\"\n '\n \n+test_expect_success 'cmd takes precedence over command' '\n+\ttest_when_finished \"git config --unset trailer.fix.cmd\" &&\n+\tgit config trailer.fix.ifExists \"replace\" &&\n+\tgit config trailer.fix.cmd \"test -n \\\"\\$1\\\" && git log -1 --oneline --format=\\\"%h (%aN)\\\" \\\n+\t\t--abbrev-commit --abbrev=14 \\\"\\$1\\\" || true\" &&\n+\tgit config trailer.fix.command \"git log -1 --oneline --format=\\\"%h (%s)\\\" \\\n+\t\t--abbrev-commit --abbrev=14 \\$ARG\" &&\n+\tFIXED=$(git log -1 --oneline --format=\"%h (%aN)\" --abbrev-commit --abbrev=14 HEAD) &&\n+\tcat complex_message_body >expected2 &&\n+\tsed -e \"s/ Z\\$/ /\" >>expected2 <<-EOF &&\n+\t\tFixes: $FIXED\n+\t\tAcked-by= Z\n+\t\tReviewed-by:\n+\t\tSigned-off-by: Z\n+\t\tSigned-off-by: A U Thor <author@example.com>\n+\tEOF\n+\tgit interpret-trailers --trailer \"review:\" --trailer \"fix=HEAD\" \\\n+\t\t<complex_message >actual2 &&\n+\ttest_cmp expected2 actual2\n+'\n+\n test_expect_success 'with command using $ARG' '\n \tgit config trailer.fix.ifExists \"replace\" &&\n-\tgit config trailer.fix.command \"git log -1 --oneline --format=\\\"%h (%s)\\\" --abbrev-commit --abbrev=14 \\$ARG\" &&\n+\tgit config trailer.fix.command \"git log -1 --oneline --format=\\\"%h (%s)\\\" \\\n+\t\t--abbrev-commit --abbrev=14 \\$ARG\" &&\n \tFIXED=$(git log -1 --oneline --format=\"%h (%s)\" --abbrev-commit --abbrev=14 HEAD) &&\n \tcat complex_message_body >expected &&\n \tsed -e \"s/ Z\\$/ /\" >>expected <<-EOF &&\ndiff --git a/trailer.c b/trailer.c\nindex be4e9726421c..6aeff6a1bd33 100644\n--- a/trailer.c\n+++ b/trailer.c\n@@ -14,6 +14,7 @@ struct conf_info {\n \tchar *name;\n \tchar *key;\n \tchar *command;\n+\tchar *cmd;\n \tenum trailer_where where;\n \tenum trailer_if_exists if_exists;\n \tenum trailer_if_missing if_missing;\n@@ -127,6 +128,7 @@ static void free_arg_item(struct arg_item *item)\n \tfree(item->conf.name);\n \tfree(item->conf.key);\n \tfree(item->conf.command);\n+\tfree(item->conf.cmd);\n \tfree(item->token);\n \tfree(item->value);\n \tfree(item);\n@@ -216,18 +218,25 @@ static int check_if_different(struct trailer_item *in_tok,\n \treturn 1;\n }\n \n-static char *apply_command(const char *command, const char *arg)\n+static char *apply_command(struct conf_info *conf, const char *arg)\n {\n \tstruct strbuf cmd = STRBUF_INIT;\n \tstruct strbuf buf = STRBUF_INIT;\n \tstruct child_process cp = CHILD_PROCESS_INIT;\n \tchar *result;\n \n-\tstrbuf_addstr(&cmd, command);\n-\tif (arg)\n-\t\tstrbuf_replace(&cmd, TRAILER_ARG_STRING, arg);\n-\n-\tstrvec_push(&cp.args, cmd.buf);\n+\tif (conf->cmd) {\n+\t\t// cp.shell_no_implicit_args = 1;\n+\t\tstrbuf_addstr(&cmd, conf->cmd);\n+\t\tstrvec_push(&cp.args, cmd.buf);\n+\t\tif (arg)\n+\t\t\tstrvec_push(&cp.args, arg);\n+\t} else if (conf->command) {\n+\t\tstrbuf_addstr(&cmd, conf->command);\n+\t\tstrvec_push(&cp.args, cmd.buf);\n+\t\tif (arg)\n+\t\t\tstrbuf_replace(&cmd, TRAILER_ARG_STRING, arg);\n+\t}\n \tcp.env = local_repo_env;\n \tcp.no_stdin = 1;\n \tcp.use_shell = 1;\n@@ -247,7 +256,7 @@ static char *apply_command(const char *command, const char *arg)\n \n static void apply_item_command(struct trailer_item *in_tok, struct arg_item *arg_tok)\n {\n-\tif (arg_tok->conf.command) {\n+\tif (arg_tok->conf.command || arg_tok->conf.cmd) {\n \t\tconst char *arg;\n \t\tif (arg_tok->value && arg_tok->value[0]) {\n \t\t\targ = arg_tok->value;\n@@ -257,7 +266,7 @@ static void apply_item_command(struct trailer_item *in_tok, struct arg_item *arg\n \t\t\telse\n \t\t\t\targ = xstrdup(\"\");\n \t\t}\n-\t\targ_tok->value = apply_command(arg_tok->conf.command, arg);\n+\t\targ_tok->value = apply_command(&arg_tok->conf, arg);\n \t\tfree((char *)arg);\n \t}\n }\n@@ -430,6 +439,7 @@ static void duplicate_conf(struct conf_info *dst, const struct conf_info *src)\n \tdst->name = xstrdup_or_null(src->name);\n \tdst->key = xstrdup_or_null(src->key);\n \tdst->command = xstrdup_or_null(src->command);\n+\tdst->cmd = xstrdup_or_null(src->cmd);\n }\n \n static struct arg_item *get_conf_item(const char *name)\n@@ -454,8 +464,8 @@ static struct arg_item *get_conf_item(const char *name)\n \treturn item;\n }\n \n-enum trailer_info_type { TRAILER_KEY, TRAILER_COMMAND, TRAILER_WHERE,\n-\t\t\t TRAILER_IF_EXISTS, TRAILER_IF_MISSING };\n+enum trailer_info_type { TRAILER_KEY, TRAILER_COMMAND, TRAILER_CMD,\n+\t\t\tTRAILER_WHERE, TRAILER_IF_EXISTS, TRAILER_IF_MISSING };\n \n static struct {\n \tconst char *name;\n@@ -463,6 +473,7 @@ static struct {\n } trailer_config_items[] = {\n \t{ \"key\", TRAILER_KEY },\n \t{ \"command\", TRAILER_COMMAND },\n+\t{ \"cmd\", TRAILER_CMD },\n \t{ \"where\", TRAILER_WHERE },\n \t{ \"ifexists\", TRAILER_IF_EXISTS },\n \t{ \"ifmissing\", TRAILER_IF_MISSING }\n@@ -542,6 +553,11 @@ static int git_trailer_config(const char *conf_key, const char *value, void *cb)\n \t\t\twarning(_(\"more than one %s\"), conf_key);\n \t\tconf->command = xstrdup(value);\n \t\tbreak;\n+\tcase TRAILER_CMD:\n+\t\tif (conf->cmd)\n+\t\t\twarning(_(\"more than one %s\"), conf_key);\n+\t\tconf->cmd = xstrdup(value);\n+\t\tbreak;\n \tcase TRAILER_WHERE:\n \t\tif (trailer_set_where(&conf->where, value))\n \t\t\twarning(_(\"unknown value '%s' for key '%s'\"), value, conf_key);\n@@ -708,7 +724,7 @@ static void process_command_line_args(struct list_head *arg_head,\n \t/* Add an arg item for each configured trailer with a command */\n \tlist_for_each(pos, &conf_head) {\n \t\titem = list_entry(pos, struct arg_item, list);\n-\t\tif (item->conf.command)\n+\t\tif (item->conf.cmd || item->conf.command)\n \t\t\tadd_arg_item(arg_head,\n \t\t\t\t     xstrdup(token_from_item(item, NULL)),\n \t\t\t\t     xstrdup(\"\"),\n\nbase-commit: 142430338477d9d1bb25be66267225fb58498d92\n-- \ngitgitgadget\n"},{"id":"420891","messageId":"xmqqim544dl4.fsf@gitster.g","threadId":"55371","inReplyTo":"pull.913.v6.git.1617369973328.gitgitgadget@gmail.com","subject":"Re: [PATCH v6] [GSOC] trailer: add new trailer.<token>.cmd config option","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2021-04-02T20:48:55Z","receivedAt":"2021-04-02T20:49:05Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"\"ZheNing Hu via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n\n> quote (imagine a name like \"O'Connor\", substituted into\n> NAME='$ARG' to make it NAME='O'Connor), it would result in\n\nYou inherited a typo from my review comments here.  This line should\nhave said\n\n    NAME='$ARG' to make it NAME='O'Connor'), it would result in\n\nI will tweak locally while queuing (read: no need to send an update\nonly to fix this line---but please do not forget to change it if you\nare going to send an update to fix or improve other things).\n\n\n>  Documentation/git-interpret-trailers.txt | 82 +++++++++++++++++---\n>  t/t7513-interpret-trailers.sh            | 95 +++++++++++++++++++++++-\n>  trailer.c                                | 38 +++++++---\n>  3 files changed, 193 insertions(+), 22 deletions(-)\n>\n> diff --git a/Documentation/git-interpret-trailers.txt b/Documentation/git-interpret-trailers.txt\n> index 96ec6499f001..67649ec6134c 100644\n> --- a/Documentation/git-interpret-trailers.txt\n> +++ b/Documentation/git-interpret-trailers.txt\n> @@ -236,21 +236,34 @@ trailer.<token>.command::\n>  \tbe called to automatically add or modify a trailer with the\n>  \tspecified <token>.\n>  +\n> -When this option is specified, the behavior is as if a special\n> -'<token>=<value>' argument were added at the beginning of the command\n> -line, where <value> is taken to be the standard output of the\n> -specified command with any leading and trailing whitespace trimmed\n> -off.\n> +When this option is specified, the first occurrence of substring $ARG is\n> +replaced with the value given to the `interpret-trailer` command for the\n> +same token.\n>  +\n> -If the command contains the `$ARG` string, this string will be\n> -replaced with the <value> part of an existing trailer with the same\n> -<token>, if any, before the command is launched.\n> +\".command\" has been deprecated due to the $ARG in the user's command can\n> +only be replaced once and the original way of replacing $ARG was not safe.\n> +Now the preferred option is using \"trailer.<token>.cmd\", which use position\n> +argument to pass the value.\n> ++\n> +When both .cmd and .command are given for the same <token>,\n> +.cmd is used and .command is ignored.\n\nWarning about unsafe replacement is a good idea.  OK.\n\n> +trailer.<token>.cmd::\n> +\tThe command specified by this configuration variable is run\n> +\twith a single parameter, which is the <value> part of an\n> +\texisting trailer with the same <token>.  The output from the\n> +\tcommand is then used as the value for the <token> in the\n> +\tresulting trailer.\n> ++\n> +When this option is specified, If there is no trailer with same <token>,\n\ns/If/if/ (downcase).\n\n> +the behavior is as if a special '<token>=<value>' argument were added at\n> +the beginning of the command, <value> will be passed to the user's\n> +command as an empty value.\n\nDo the two occurrences of the word \"command\" in the sentence refer\nto different things?  I do not think this is an existing problem\ninherited from the original, but as we are trying to improve the\ndescription, I wonder if we can clarify them a bit.\n\n\t... as if a '<token>=<value>' argument were added at the\n\tbeginning of the \"git interpret-trailers\" command, the\n\tcommand specified by this configuration variable will be\n\tcalled with an empty string as the argument.\n\nis my attempt, but I am not still sure what that \"as if\" part is\ntrying to say.  Does it mean with\n\n\t[trailer \"Foo\"] cmd = foo-cmd\n\nand the 'input-file' does not have \"Foo: <some existing value>\"\ntrailer in it, the command \"git interpret-trailers input-file\"\nwould behave as if this command was run\n\n\t$ Foo= git interpret-trailers input-file\n\n(as there is no <value>, I am not sure what <value> is used when\n<token>=<value> is prefixed to the command)?\n\nPuzzled and confused utterly am I...  Help, Christian?\n\n>  +\n>  If some '<token>=<value>' arguments are also passed on the command\n>  line, when a 'trailer.<token>.command' is configured, the command will\n>  also be executed for each of these arguments. And the <value> part of\n\nThis talks about 'trailer.<token>.command'.  Should this be changed\nto '.cmd'? \n\nOr does everything after \"When this option is specified, if there is\nno trailer with ...\" apply to both the old .command and new .cmd?\nIf so, that was not clear at all---we'd need to clarify this part.\n\n> -these arguments, if any, will be used to replace the `$ARG` string in\n> -the command.\n> +these arguments, if any, will be passed to the command as first parameter.\n>  \n>  EXAMPLES\n>  --------\n> @@ -333,6 +346,55 @@ subject\n>  Fix #42\n>  ------------\n>  \n> +* Configure a 'see' trailer with a cmd use a global script `git-one`\n> +  to show the subject of a commit that is related, and show how it works:\n> ++\n> +------------\n> +$ cat ~/bin/git-one\n> +#!/bin/sh\n> +git show -s --pretty=reference \"$1\"\n> +$ git config trailer.see.key \"See-also: \"\n> +$ git config trailer.see.ifExists \"replace\"\n> +$ git config trailer.see.ifMissing \"doNothing\"\n> +$ git config trailer.see.cmd \"~/bin/git-one\"\n> +$ git interpret-trailers <<EOF\n> +> subject\n> +> \n> +> message\n> +> \n> +> see: HEAD~2\n> +> EOF\n> +subject\n> +\n> +message\n> +\n> +See-also: fe3187e (subject of related commit, 2021-4-2)\n> +------------\n> +\n> +* Configure a 'who' trailer with a cmd use a global script `git-who`\n> +  to find the recent matching \"author <mail>\" pair in git log and\n> +  show how it works:\n> ++\n> +------------\n> +$ cat ~/bin/git-who\n> + #!/bin/sh\n> +    git log -1 --format=\"%an <%ae>\" --author=\"$1\"\n\nUnusual indentation here.  But more importantly, I am not sure if \nhaving both 'see' and 'help' examples is worth it---they are similar\nenough that the second one does not teach anything new to those who\nstudied the first one already, aren't they?\n\n> +$ git config trailer.help.key \"Helped-by: \"\n> +$ git config trailer.help.ifExists \"replace\"\n> +$ git config trailer.help.cmd \"~/bin/git-who\"\n> +$ git interpret-trailers --trailer=\"help:gitster@\" <<EOF\n> +> subject\n> +> \n> +> message\n> +> \n> +> EOF\n> +subject\n> +\n> +message\n> +\n> +Helped-by: Junio C Hamano <gitster@pobox.com>\n> +------------\n> +\n>  * Configure a 'see' trailer with a command to show the subject of a\n>    commit that is related, and show how it works:\n>  +\n\n\n\n> diff --git a/t/t7513-interpret-trailers.sh b/t/t7513-interpret-trailers.sh\n> index 6602790b5f4c..923923e57573 100755\n> --- a/t/t7513-interpret-trailers.sh\n> +++ b/t/t7513-interpret-trailers.sh\n> @@ -51,6 +51,77 @@ test_expect_success 'setup' '\n>  \tEOF\n>  '\n>  \n> +test_expect_success 'with cmd' '\n> +\ttest_when_finished \"git config --unset trailer.bug.key && \\\n> +\t\t\t    git config --unset trailer.bug.ifExists && \\\n> +\t\t\t    git config --unset trailer.bug.cmd\" &&\n\nIt is unwise to use && between these three \"git config\" invocations,\nI suspect.  \"git config --unset\" exits with non-zero status when you\nattempt to remove with an non-existent key, but you would remove the\n.ifExists and .cmd even if .key is not defined.  Perhaps\n\n\ttest_when_finished \"git config --unset-all trailer.bug.key\n\t\t\t    git config --unset-all trailer.bug.ifExists\n\t\t\t    git config --unset-all trailer.bug.cmd\" &&\n\nwould be more sensible.  Or if we just want to remove everything\nunder the trailer.bug.* section, this might be even better:\n\n\ttest_when_finished \"git config --remove-section trailer.bug\" &&\n\nas we can add new trailer.bug.* to the system and use them in this\ntest, but removing the entire section would still be a good way to\nclean after ourselves.\n\n> diff --git a/trailer.c b/trailer.c\n> index be4e9726421c..6aeff6a1bd33 100644\n> --- a/trailer.c\n> +++ b/trailer.c\n> ...\n> +static char *apply_command(struct conf_info *conf, const char *arg)\n>  {\n>  \tstruct strbuf cmd = STRBUF_INIT;\n>  \tstruct strbuf buf = STRBUF_INIT;\n>  \tstruct child_process cp = CHILD_PROCESS_INIT;\n>  \tchar *result;\n>  \n> -\tstrbuf_addstr(&cmd, command);\n> -\tif (arg)\n> -\t\tstrbuf_replace(&cmd, TRAILER_ARG_STRING, arg);\n> -\n> -\tstrvec_push(&cp.args, cmd.buf);\n> +\tif (conf->cmd) {\n> +\t\t// cp.shell_no_implicit_args = 1;\n\nDo not add new code that is commented out.  Besides we do not use // comment.\n\n> +\t\tstrbuf_addstr(&cmd, conf->cmd);\n> +\t\tstrvec_push(&cp.args, cmd.buf);\n> +\t\tif (arg)\n> +\t\t\tstrvec_push(&cp.args, arg);\n\nThanks.\n"},{"id":"420911","messageId":"xmqq35w82qvu.fsf@gitster.g","threadId":"55371","inReplyTo":"pull.913.v6.git.1617369973328.gitgitgadget@gmail.com","subject":"Re: [PATCH v6] [GSOC] trailer: add new trailer.<token>.cmd config option","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2021-04-02T23:44:37Z","receivedAt":"2021-04-02T23:44:45Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"\"ZheNing Hu via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n\n> From: ZheNing Hu <adlternative@gmail.com>\n>\n> The `trailer.<token>.command` configuration variable\n> specifies a command (run via the shell, so it does not have\n> to be a single name of or path to the command, but can be a\n> shell script), and the first occurrence of substring $ARG is\n> replaced with the value given to the `interpret-trailer`\n> command for the token.  This has two downsides:\n> ...\n>\n>  Documentation/git-interpret-trailers.txt | 82 +++++++++++++++++---\n>  t/t7513-interpret-trailers.sh            | 95 +++++++++++++++++++++++-\n>  trailer.c                                | 38 +++++++---\n>  3 files changed, 193 insertions(+), 22 deletions(-)\n\nMerging this to anything that has zh/commit-trailer (which extends\nthe tests in t7502) seems to break t7502.  Running the test with the\n\"-i -v\" option ends like so.\n\nexpecting success of 7502.24 'commit --trailer with -c and command': \n\ttrailer_commit_base &&\n\tcat >expected <<-\\EOF &&\n\thello\n\n\tSigned-off-by: C O Mitter <committer@example.com>\n\tSigned-off-by: C1 E1\n\tHelped-by: C2 E2\n\tMentored-by: C4 E4\n\tReported-by: A U Thor <author@example.com>\n\tEOF\n\tgit -c trailer.report.key=\"Reported-by: \" \\\n\t\t-c trailer.report.ifexists=\"replace\" \\\n\t\t-c trailer.report.command=\"NAME=\\\"\\$ARG\\\"; test -n \\\"\\$NAME\\\" && \\\n\t\tgit log --author=\\\"\\$NAME\\\" -1 --format=\\\"format:%aN <%aE>\\\" || true\" \\\n\t\tcommit --trailer \"report = author\" --amend &&\n\tgit cat-file commit HEAD >commit.msg &&\n\tsed -e \"1,/^\\$/d\" commit.msg >actual &&\n\ttest_cmp expected actual\n\n[main 6b1e5e9] hello\n Author: A U Thor <author@example.com>\n 1 file changed, 1 insertion(+)\n[main 97c7a39] hello\n Author: A U Thor <author@example.com>\n Date: Thu Apr 7 15:22:13 2005 -0700\n 1 file changed, 1 insertion(+)\n--- expected\t2021-04-02 23:43:10.649082950 +0000\n+++ actual\t2021-04-02 23:43:10.673085111 +0000\n@@ -4,4 +4,4 @@\n Signed-off-by: C1 E1\n Helped-by: C2 E2\n Mentored-by: C4 E4\n-Reported-by: A U Thor <author@example.com>\n+Reported-by:\nnot ok 24 - commit --trailer with -c and command\n#\t\n#\t\ttrailer_commit_base &&\n#\t\tcat >expected <<-\\EOF &&\n#\t\thello\n#\t\n#\t\tSigned-off-by: C O Mitter <committer@example.com>\n#\t\tSigned-off-by: C1 E1\n#\t\tHelped-by: C2 E2\n#\t\tMentored-by: C4 E4\n#\t\tReported-by: A U Thor <author@example.com>\n#\t\tEOF\n#\t\tgit -c trailer.report.key=\"Reported-by: \" \\\n#\t\t\t-c trailer.report.ifexists=\"replace\" \\\n#\t\t\t-c trailer.report.command=\"NAME=\\\"\\$ARG\\\"; test -n \\\"\\$NAME\\\" && \\\n#\t\t\tgit log --author=\\\"\\$NAME\\\" -1 --format=\\\"format:%aN <%aE>\\\" || true\" \\\n#\t\t\tcommit --trailer \"report = author\" --amend &&\n#\t\tgit cat-file commit HEAD >commit.msg &&\n#\t\tsed -e \"1,/^\\$/d\" commit.msg >actual &&\n#\t\ttest_cmp expected actual\n#\t\n"},{"id":"420916","messageId":"CAOLTT8TaUkfdOhD4+P=VVxuy3dPCaf4KNC8+1hBQnxSjpvO4HQ@mail.gmail.com","threadId":"55371","inReplyTo":"xmqq35w82qvu.fsf@gitster.g","subject":"Re: [PATCH v6] [GSOC] trailer: add new trailer.<token>.cmd config option","fromName":"ZheNing Hu","fromEmail":"adlternative@gmail.com","sentAt":"2021-04-03T03:22:26Z","receivedAt":"2021-04-03T03:22:44Z","isPatch":true,"sender":{"key":"adlternative@gmail.com","avatar":"https://avatars.githubusercontent.com/u/58138461?v=4"},"body":"Junio C Hamano <gitster@pobox.com> 于2021年4月3日周六 上午7:44写道：\n>\n> \"ZheNing Hu via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n>\n> > From: ZheNing Hu <adlternative@gmail.com>\n> >\n> > The `trailer.<token>.command` configuration variable\n> > specifies a command (run via the shell, so it does not have\n> > to be a single name of or path to the command, but can be a\n> > shell script), and the first occurrence of substring $ARG is\n> > replaced with the value given to the `interpret-trailer`\n> > command for the token.  This has two downsides:\n> > ...\n> >\n> >  Documentation/git-interpret-trailers.txt | 82 +++++++++++++++++---\n> >  t/t7513-interpret-trailers.sh            | 95 +++++++++++++++++++++++-\n> >  trailer.c                                | 38 +++++++---\n> >  3 files changed, 193 insertions(+), 22 deletions(-)\n>\n> Merging this to anything that has zh/commit-trailer (which extends\n> the tests in t7502) seems to break t7502.  Running the test with the\n> \"-i -v\" option ends like so.\n>\n> expecting success of 7502.24 'commit --trailer with -c and command':\n>         trailer_commit_base &&\n>         cat >expected <<-\\EOF &&\n>         hello\n>\n>         Signed-off-by: C O Mitter <committer@example.com>\n>         Signed-off-by: C1 E1\n>         Helped-by: C2 E2\n>         Mentored-by: C4 E4\n>         Reported-by: A U Thor <author@example.com>\n>         EOF\n>         git -c trailer.report.key=\"Reported-by: \" \\\n>                 -c trailer.report.ifexists=\"replace\" \\\n>                 -c trailer.report.command=\"NAME=\\\"\\$ARG\\\"; test -n \\\"\\$NAME\\\" && \\\n>                 git log --author=\\\"\\$NAME\\\" -1 --format=\\\"format:%aN <%aE>\\\" || true\" \\\n>                 commit --trailer \"report = author\" --amend &&\n>         git cat-file commit HEAD >commit.msg &&\n>         sed -e \"1,/^\\$/d\" commit.msg >actual &&\n>         test_cmp expected actual\n>\n> [main 6b1e5e9] hello\n>  Author: A U Thor <author@example.com>\n>  1 file changed, 1 insertion(+)\n> [main 97c7a39] hello\n>  Author: A U Thor <author@example.com>\n>  Date: Thu Apr 7 15:22:13 2005 -0700\n>  1 file changed, 1 insertion(+)\n> --- expected    2021-04-02 23:43:10.649082950 +0000\n> +++ actual      2021-04-02 23:43:10.673085111 +0000\n> @@ -4,4 +4,4 @@\n>  Signed-off-by: C1 E1\n>  Helped-by: C2 E2\n>  Mentored-by: C4 E4\n> -Reported-by: A U Thor <author@example.com>\n> +Reported-by:\n> not ok 24 - commit --trailer with -c and command\n> #\n> #               trailer_commit_base &&\n> #               cat >expected <<-\\EOF &&\n> #               hello\n> #\n> #               Signed-off-by: C O Mitter <committer@example.com>\n> #               Signed-off-by: C1 E1\n> #               Helped-by: C2 E2\n> #               Mentored-by: C4 E4\n> #               Reported-by: A U Thor <author@example.com>\n> #               EOF\n> #               git -c trailer.report.key=\"Reported-by: \" \\\n> #                       -c trailer.report.ifexists=\"replace\" \\\n> #                       -c trailer.report.command=\"NAME=\\\"\\$ARG\\\"; test -n \\\"\\$NAME\\\" && \\\n> #                       git log --author=\\\"\\$NAME\\\" -1 --format=\\\"format:%aN <%aE>\\\" || true\" \\\n> #                       commit --trailer \"report = author\" --amend &&\n> #               git cat-file commit HEAD >commit.msg &&\n> #               sed -e \"1,/^\\$/d\" commit.msg >actual &&\n> #               test_cmp expected actual\n> #\n\nLittle bug, Change it like this will work:\n\n        } else if (conf->command) {\n                strbuf_addstr(&cmd, conf->command);\n-               strvec_push(&cp.args, cmd.buf);\n                if (arg)\n                        strbuf_replace(&cmd, TRAILER_ARG_STRING, arg);\n+               strvec_push(&cp.args, cmd.buf);\n        }\n\nthanks.\n--\nZheNing Hu\n"},{"id":"420919","messageId":"xmqqtuoo0z1l.fsf@gitster.g","threadId":"55371","inReplyTo":"CAOLTT8TaUkfdOhD4+P=VVxuy3dPCaf4KNC8+1hBQnxSjpvO4HQ@mail.gmail.com","subject":"Re: [PATCH v6] [GSOC] trailer: add new trailer.<token>.cmd config option","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2021-04-03T04:31:18Z","receivedAt":"2021-04-03T04:31:30Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"ZheNing Hu <adlternative@gmail.com> writes:\n\n> Little bug, Change it like this will work:\n>\n>         } else if (conf->command) {\n>                 strbuf_addstr(&cmd, conf->command);\n> -               strvec_push(&cp.args, cmd.buf);\n>                 if (arg)\n>                         strbuf_replace(&cmd, TRAILER_ARG_STRING, arg);\n> +               strvec_push(&cp.args, cmd.buf);\n>         }\n\nIt means the coverage of the test included in this patch was not\nsufficient, right?\n\nI queued this separately from the \"commit --trailer\" topic in\ntonight's pushout, but it may make sense to queue this step\nimmediately on top of the \"commit --trailer\" patch.\n\nIn any case, I suspect we would not hear from Christian right away\ndue to timezone differences, so perhaps let this v6 simmer on the\nlist, and then hopefully update the documentation part with his\ninput.\n\nThanks.\n"},{"id":"420920","messageId":"CAOLTT8Tq+-tRzgG1AFD2cE2Vp9z3E94eW1eyN2BxKKg17FzTng@mail.gmail.com","threadId":"55371","inReplyTo":"xmqqim544dl4.fsf@gitster.g","subject":"Re: [PATCH v6] [GSOC] trailer: add new trailer.<token>.cmd config option","fromName":"ZheNing Hu","fromEmail":"adlternative@gmail.com","sentAt":"2021-04-03T05:08:05Z","receivedAt":"2021-04-03T05:08:21Z","isPatch":true,"sender":{"key":"adlternative@gmail.com","avatar":"https://avatars.githubusercontent.com/u/58138461?v=4"},"body":"Junio C Hamano <gitster@pobox.com> 于2021年4月3日周六 上午4:49写道：\n\n> > +the behavior is as if a special '<token>=<value>' argument were added at\n> > +the beginning of the command, <value> will be passed to the user's\n> > +command as an empty value.\n>\n> Do the two occurrences of the word \"command\" in the sentence refer\n> to different things?  I do not think this is an existing problem\n> inherited from the original, but as we are trying to improve the\n> description, I wonder if we can clarify them a bit.\n>\n>         ... as if a '<token>=<value>' argument were added at the\n>         beginning of the \"git interpret-trailers\" command, the\n>         command specified by this configuration variable will be\n>         called with an empty string as the argument.\n>\n> is my attempt, but I am not still sure what that \"as if\" part is\n> trying to say.  Does it mean with\n>\n>         [trailer \"Foo\"] cmd = foo-cmd\n>\n> and the 'input-file' does not have \"Foo: <some existing value>\"\n> trailer in it, the command \"git interpret-trailers input-file\"\n> would behave as if this command was run\n>\n>         $ Foo= git interpret-trailers input-file\n>\n> (as there is no <value>, I am not sure what <value> is used when\n> <token>=<value> is prefixed to the command)?\n>\n> Puzzled and confused utterly am I...  Help, Christian?\n>\n\nIf we don’t want a input-file without any trailers to execute this command,\n I’m thinking that we maybe need \"trailer.<token>.ifmissing=doNothing\"\nor something else, otherwise execute this 'alwaysRunCmd' for create a\nempty trailer like \"Signed-off-by\" and users can use it to add content.\n\n> >  +\n> >  If some '<token>=<value>' arguments are also passed on the command\n> >  line, when a 'trailer.<token>.command' is configured, the command will\n> >  also be executed for each of these arguments. And the <value> part of\n>\n> This talks about 'trailer.<token>.command'.  Should this be changed\n> to '.cmd'?\n>\n> Or does everything after \"When this option is specified, if there is\n> no trailer with ...\" apply to both the old .command and new .cmd?\n> If so, that was not clear at all---we'd need to clarify this part.\n>\n\nBecause \".command\" will be eliminated, may be only leave those\ndescription Information to \".cmd\" is better.\n\n> > -these arguments, if any, will be used to replace the `$ARG` string in\n> > -the command.\n> > +these arguments, if any, will be passed to the command as first parameter.\n> >\n> >  EXAMPLES\n> >  --------\n> > @@ -333,6 +346,55 @@ subject\n> >  Fix #42\n> >  ------------\n> >\n> > +* Configure a 'see' trailer with a cmd use a global script `git-one`\n> > +  to show the subject of a commit that is related, and show how it works:\n> > ++\n> > +------------\n> > +$ cat ~/bin/git-one\n> > +#!/bin/sh\n> > +git show -s --pretty=reference \"$1\"\n> > +$ git config trailer.see.key \"See-also: \"\n> > +$ git config trailer.see.ifExists \"replace\"\n> > +$ git config trailer.see.ifMissing \"doNothing\"\n> > +$ git config trailer.see.cmd \"~/bin/git-one\"\n> > +$ git interpret-trailers <<EOF\n> > +> subject\n> > +>\n> > +> message\n> > +>\n> > +> see: HEAD~2\n> > +> EOF\n> > +subject\n> > +\n> > +message\n> > +\n> > +See-also: fe3187e (subject of related commit, 2021-4-2)\n> > +------------\n> > +\n> > +* Configure a 'who' trailer with a cmd use a global script `git-who`\n> > +  to find the recent matching \"author <mail>\" pair in git log and\n> > +  show how it works:\n> > ++\n> > +------------\n> > +$ cat ~/bin/git-who\n> > + #!/bin/sh\n> > +    git log -1 --format=\"%an <%ae>\" --author=\"$1\"\n>\n> Unusual indentation here.  But more importantly, I am not sure if\n> having both 'see' and 'help' examples is worth it---they are similar\n> enough that the second one does not teach anything new to those who\n> studied the first one already, aren't they?\n>\n\nOk, I will think about other examples.\n\n> > +$ git config trailer.help.key \"Helped-by: \"\n> > +$ git config trailer.help.ifExists \"replace\"\n> > +$ git config trailer.help.cmd \"~/bin/git-who\"\n> > +$ git interpret-trailers --trailer=\"help:gitster@\" <<EOF\n> > +> subject\n> > +>\n> > +> message\n> > +>\n> > +> EOF\n> > +subject\n> > +\n> > +message\n> > +\n> > +Helped-by: Junio C Hamano <gitster@pobox.com>\n> > +------------\n> > +\n> >  * Configure a 'see' trailer with a command to show the subject of a\n> >    commit that is related, and show how it works:\n> >  +\n>\n>\n>\n> > diff --git a/t/t7513-interpret-trailers.sh b/t/t7513-interpret-trailers.sh\n> > index 6602790b5f4c..923923e57573 100755\n> > --- a/t/t7513-interpret-trailers.sh\n> > +++ b/t/t7513-interpret-trailers.sh\n> > @@ -51,6 +51,77 @@ test_expect_success 'setup' '\n> >       EOF\n> >  '\n> >\n> > +test_expect_success 'with cmd' '\n> > +     test_when_finished \"git config --unset trailer.bug.key && \\\n> > +                         git config --unset trailer.bug.ifExists && \\\n> > +                         git config --unset trailer.bug.cmd\" &&\n>\n> It is unwise to use && between these three \"git config\" invocations,\n> I suspect.  \"git config --unset\" exits with non-zero status when you\n> attempt to remove with an non-existent key, but you would remove the\n> .ifExists and .cmd even if .key is not defined.  Perhaps\n>\n>         test_when_finished \"git config --unset-all trailer.bug.key\n>                             git config --unset-all trailer.bug.ifExists\n>                             git config --unset-all trailer.bug.cmd\" &&\n>\n> would be more sensible.  Or if we just want to remove everything\n> under the trailer.bug.* section, this might be even better:\n>\n>         test_when_finished \"git config --remove-section trailer.bug\" &&\n>\n> as we can add new trailer.bug.* to the system and use them in this\n> test, but removing the entire section would still be a good way to\n> clean after ourselves.\n>\n\nGood idea, This removing is also more efficient.\n\n> > diff --git a/trailer.c b/trailer.c\n> > index be4e9726421c..6aeff6a1bd33 100644\n> > --- a/trailer.c\n> > +++ b/trailer.c\n> > ...\n> > +static char *apply_command(struct conf_info *conf, const char *arg)\n> >  {\n> >       struct strbuf cmd = STRBUF_INIT;\n> >       struct strbuf buf = STRBUF_INIT;\n> >       struct child_process cp = CHILD_PROCESS_INIT;\n> >       char *result;\n> >\n> > -     strbuf_addstr(&cmd, command);\n> > -     if (arg)\n> > -             strbuf_replace(&cmd, TRAILER_ARG_STRING, arg);\n> > -\n> > -     strvec_push(&cp.args, cmd.buf);\n> > +     if (conf->cmd) {\n> > +             // cp.shell_no_implicit_args = 1;\n>\n> Do not add new code that is commented out.  Besides we do not use // comment.\n>\n> > +             strbuf_addstr(&cmd, conf->cmd);\n> > +             strvec_push(&cp.args, cmd.buf);\n> > +             if (arg)\n> > +                     strvec_push(&cp.args, arg);\n>\n> Thanks.\n\nThanks.\n"},{"id":"420921","messageId":"CAOLTT8Q8J-5xuEfdx5VKyB1WrGjwngdiyPeUEuD8QMnFfSE5mA@mail.gmail.com","threadId":"55371","inReplyTo":"xmqqtuoo0z1l.fsf@gitster.g","subject":"Re: [PATCH v6] [GSOC] trailer: add new trailer.<token>.cmd config option","fromName":"ZheNing Hu","fromEmail":"adlternative@gmail.com","sentAt":"2021-04-03T05:15:17Z","receivedAt":"2021-04-03T05:15:31Z","isPatch":true,"sender":{"key":"adlternative@gmail.com","avatar":"https://avatars.githubusercontent.com/u/58138461?v=4"},"body":"Junio C Hamano <gitster@pobox.com> 于2021年4月3日周六 下午12:31写道：\n>\n> ZheNing Hu <adlternative@gmail.com> writes:\n>\n> > Little bug, Change it like this will work:\n> >\n> >         } else if (conf->command) {\n> >                 strbuf_addstr(&cmd, conf->command);\n> > -               strvec_push(&cp.args, cmd.buf);\n> >                 if (arg)\n> >                         strbuf_replace(&cmd, TRAILER_ARG_STRING, arg);\n> > +               strvec_push(&cp.args, cmd.buf);\n> >         }\n>\n> It means the coverage of the test included in this patch was not\n> sufficient, right?\n>\n\nIndeed, I might have to think what tests need to be added to the \".cmd\".\n\n> I queued this separately from the \"commit --trailer\" topic in\n> tonight's pushout, but it may make sense to queue this step\n> immediately on top of the \"commit --trailer\" patch.\n>\n\nMaybe I can rebase this \"trailer .cmd\" to \"master\" which already have\ninclude \"commit --trailer\" later, this will cover \"commit --trailer\" tests.\n\n> In any case, I suspect we would not hear from Christian right away\n> due to timezone differences, so perhaps let this v6 simmer on the\n> list, and then hopefully update the documentation part with his\n> input.\n>\n\nYes, it's worth waiting patiently,\n\n> Thanks.\n\nThanks.\n"},{"id":"420924","messageId":"CAP8UFD0AZWey045qK=9h8nVCVwK09s=t-=Z7OVBpvr4LAvSLUw@mail.gmail.com","threadId":"55371","inReplyTo":"xmqqim544dl4.fsf@gitster.g","subject":"Re: [PATCH v6] [GSOC] trailer: add new trailer.<token>.cmd config option","fromName":"Christian Couder","fromEmail":"christian.couder@gmail.com","sentAt":"2021-04-03T05:51:17Z","receivedAt":"2021-04-03T05:51:31Z","isPatch":true,"sender":{"key":"christian.couder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/208954?v=4"},"body":"On Fri, Apr 2, 2021 at 10:49 PM Junio C Hamano <gitster@pobox.com> wrote:\n>\n> \"ZheNing Hu via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n\n> > +trailer.<token>.cmd::\n> > +     The command specified by this configuration variable is run\n> > +     with a single parameter, which is the <value> part of an\n\nI prefer \"argument\" over \"parameter\". In the next paragraph you use\n\"argument\" (in \"as if a special '<token>=<value>' argument\"), so it\nwould be more consistent and easier to read if \"argument\" was used\nhere too.\n\n> > +     existing trailer with the same <token>.\n\nIt would be better to say something like \"which is the <value> part of\na `--trailer <token>=<value>` on the command line\". (Yeah, the\nexisting doc might be bad about this.)\n\nAlso it might be better to explicitly say that the command is run once\nfor each `--trailer <token>=<value>` on the command line with the same\n<token>.\n\n> > The output from the\n> > +     command is then used as the value for the <token> in the\n> > +     resulting trailer.\n> > ++\n> > +When this option is specified, If there is no trailer with same <token>,\n\nAs above, it's better to be more explicit and say something like \"if\nthere is no `--trailer <token>=<value>` with the same <token> on the\ncommand line\". That's because there could already be such trailers in\nthe input file, but those don't trigger the command.\n\nAnyway see below as you might need to change the whole paragraph anyway.\n\n> s/If/if/ (downcase).\n>\n> > +the behavior is as if a special '<token>=<value>' argument were added at\n> > +the beginning of the command, <value> will be passed to the user's\n> > +command as an empty value.\n\nThe problem with this is that people could understand that the command\nwill run with an empty argument only if there is no trailer with the\nsame <token>. The current situation though is that the command will\nrun once with an empty argument whether there are `--trailer\n<token>=<value>` options with the same <token> passed on the command\nline or not.\n\n> Do the two occurrences of the word \"command\" in the sentence refer\n> to different things?  I do not think this is an existing problem\n> inherited from the original, but as we are trying to improve the\n> description, I wonder if we can clarify them a bit.\n>\n>         ... as if a '<token>=<value>' argument were added at the\n>         beginning of the \"git interpret-trailers\" command, the\n>         command specified by this configuration variable will be\n>         called with an empty string as the argument.\n>\n> is my attempt,\n\nIt looks better to me.\n\n> but I am not still sure what that \"as if\" part is\n> trying to say.  Does it mean with\n>\n>         [trailer \"Foo\"] cmd = foo-cmd\n>\n> and the 'input-file' does not have \"Foo: <some existing value>\"\n> trailer in it, the command \"git interpret-trailers input-file\"\n> would behave as if this command was run\n>\n>         $ Foo= git interpret-trailers input-file\n\nI would say it would behave as if:\n\n$ git interpret-trailers --trailer Foo= input-file\n\n> (as there is no <value>, I am not sure what <value> is used when\n> <token>=<value> is prefixed to the command)?\n\nNothing is done when such things are prefixed to the command.\n\n> Puzzled and confused utterly am I...  Help, Christian?\n\nI hope the above helps.\n\n> >  If some '<token>=<value>' arguments are also passed on the command\n> >  line, when a 'trailer.<token>.command' is configured, the command will\n> >  also be executed for each of these arguments. And the <value> part of\n>\n> This talks about 'trailer.<token>.command'.  Should this be changed\n> to '.cmd'?\n\nI think so.\n\n> Or does everything after \"When this option is specified, if there is\n> no trailer with ...\" apply to both the old .command and new .cmd?\n> If so, that was not clear at all---we'd need to clarify this part.\n\nYeah, in the doc about \".command\" I think we should say that it\nbehaves in a similar way as \".cmd\" except for the $ARG vs $1 issue.\n\n> > -these arguments, if any, will be used to replace the `$ARG` string in\n> > -the command.\n> > +these arguments, if any, will be passed to the command as first parameter.\n\ns/as first parameter/as its first argument/\n"},{"id":"420954","messageId":"xmqqa6qezk7c.fsf@gitster.g","threadId":"55371","inReplyTo":"CAOLTT8Tq+-tRzgG1AFD2cE2Vp9z3E94eW1eyN2BxKKg17FzTng@mail.gmail.com","subject":"Re: [PATCH v6] [GSOC] trailer: add new trailer.<token>.cmd config option","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2021-04-04T05:34:47Z","receivedAt":"2021-04-04T05:35:52Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"ZheNing Hu <adlternative@gmail.com> writes:\n\n>> >  If some '<token>=<value>' arguments are also passed on the command\n>> >  line, when a 'trailer.<token>.command' is configured, the command will\n>> >  also be executed for each of these arguments. And the <value> part of\n>>\n>> This talks about 'trailer.<token>.command'.  Should this be changed\n>> to '.cmd'?\n>>\n>> Or does everything after \"When this option is specified, if there is\n>> no trailer with ...\" apply to both the old .command and new .cmd?\n>> If so, that was not clear at all---we'd need to clarify this part.\n>>\n>\n> Because \".command\" will be eliminated, may be only leave those\n> description Information to \".cmd\" is better.\n\nNot really.\n\nUntil .command goes away, people who want to migrate a config file\nwritten in the .command days to .cmd would need to know what rules\ngovern .command variant.  Otherwise they would not know what the\noriginal they inherited, using .command, wanted to do, and have no\nway to emulate it with the new .cmd approach.  If something is\nshared between the two, at least you need to mention that it applies\nto both.\n\nUntil .command actually gets removed, that is.\n\n>> Unusual indentation here.  But more importantly, I am not sure if\n>> having both 'see' and 'help' examples is worth it---they are similar\n>> enough that the second one does not teach anything new to those who\n>> studied the first one already, aren't they?\n>>\n>\n> Ok, I will think about other examples.\n\nOr just use one, and let somebody else in the future encounter real\nworld example that is different enough to come up with a follow up\npatch to describe that example.\n\n>> > diff --git a/trailer.c b/trailer.c\n>> > index be4e9726421c..6aeff6a1bd33 100644\n>> > --- a/trailer.c\n>> > +++ b/trailer.c\n>> > ...\n>> > -     strvec_push(&cp.args, cmd.buf);\n>> > +     if (conf->cmd) {\n>> > +             // cp.shell_no_implicit_args = 1;\n>>\n>> Do not add new code that is commented out.  Besides we do not use // comment.\n>>\n>> > +             strbuf_addstr(&cmd, conf->cmd);\n>> > +             strvec_push(&cp.args, cmd.buf);\n>> > +             if (arg)\n>> > +                     strvec_push(&cp.args, arg);\n>>\n>> Thanks.\n>\n> Thanks.\n\n"},{"id":"420956","messageId":"CAOLTT8QvPPJ4jYXjch+RhdHDnvDT6Woh2oQoX2LcoiVX_t7jZQ@mail.gmail.com","threadId":"55371","inReplyTo":"xmqqim544dl4.fsf@gitster.g","subject":"Re: [PATCH v6] [GSOC] trailer: add new trailer.<token>.cmd config option","fromName":"ZheNing Hu","fromEmail":"adlternative@gmail.com","sentAt":"2021-04-04T05:43:58Z","receivedAt":"2021-04-04T05:44:17Z","isPatch":true,"sender":{"key":"adlternative@gmail.com","avatar":"https://avatars.githubusercontent.com/u/58138461?v=4"},"body":"Hi, Junio and Christian,\n\n> > +------------\n> > +$ cat ~/bin/git-one\n> > +#!/bin/sh\n> > +git show -s --pretty=reference \"$1\"\n> > +$ git config trailer.see.key \"See-also: \"\n> > +$ git config trailer.see.ifExists \"replace\"\n> > +$ git config trailer.see.ifMissing \"doNothing\"\n> > +$ git config trailer.see.cmd \"~/bin/git-one\"\n> > +$ git interpret-trailers <<EOF\n> > +> subject\n> > +>\n> > +> message\n> > +>\n> > +> see: HEAD~2\n> > +> EOF\n> > +subject\n> > +\n> > +message\n> > +\n> > +See-also: fe3187e (subject of related commit, 2021-4-2)\n> > +------------\n> > +\n> > +* Configure a 'who' trailer with a cmd use a global script `git-who`\n> > +  to find the recent matching \"author <mail>\" pair in git log and\n> > +  show how it works:\n> > ++\n> > +------------\n> > +$ cat ~/bin/git-who\n> > + #!/bin/sh\n> > +    git log -1 --format=\"%an <%ae>\" --author=\"$1\"\n>\n> Unusual indentation here.  But more importantly, I am not sure if\n> having both 'see' and 'help' examples is worth it---they are similar\n> enough that the second one does not teach anything new to those who\n> studied the first one already, aren't they?\n>\n\nThis may be an off-topic question:\nI wanted to use `shortlog -s` instead of the document example,\nBut I found a very strange place:\nHere we have two shell scripts:\n\n$ cat ~/bin/gcount\n#!/bin/sh\nif test \"$1\" != \"\"\nthen\ngit log -1 --author=\"$1\"\nelse\necho \"hello there\"\nfi\n\ncat ~/bin/gcount2\n#!/bin/sh\nif test \"$1\" != \"\"\nthen\ngit shortlog -1 --author=\"$1\"\nelse\necho \"hello there\"\nfi\n\nIf I use them in the terminal, there is no problem with them,\n\n$ ~/bin/gcount gitster\ncommit 142430338477d9d1bb25be66267225fb58498d92\n(interpret-trailers-command-arg, abc5b)\nAuthor: Junio C Hamano <gitster@pobox.com>\nDate:   Mon Mar 22 14:00:00 2021 -0700\n\n    The second batch\n\n    Signed-off-by: Junio C Hamano <gitster@pobox.com>\n\n$ ~/bin/gcount2 gitster\nJunio C Hamano (1):\n      The second batch\n\nif I use .cmd to run these scripts, the situation is totally different:\n\n$ git config -l | grep trailer\ntrailer.cnt.ifexists=add\ntrailer.cnt.key=Cnt:\ntrailer.cnt.cmd=~/bin/gcount\n\n$ git interpret-trailers --trailer=\"cnt:gitster\" <<EOF\nEOF\n\nCnt: hello there\nCnt: commit 142430338477d9d1bb25be66267225fb58498d92\nAuthor: Junio C Hamano <gitster@pobox.com>\nDate:   Mon Mar 22 14:00:00 2021 -0700\n\n    The second batch\n\n    Signed-off-by: Junio C Hamano <gitster@pobox.com>\n\nAnd if I turn to use gcount2:\n$ git config trailer.cnt.cmd \"~/bin/gcount2\"\n$ git interpret-trailers --trailer=\"cnt:gitster\" <<EOF\nEOF\n\nCnt: hello there\nCnt:\n\nIt looks like `shortlog` does not write to standard output.\nNote that in `short_log.c`, log will be output to `log->file`.\nDoes it make the above behavior different?\nIs there a good solution?\n\n\n> > +$ git config trailer.help.key \"Helped-by: \"\n> > +$ git config trailer.help.ifExists \"replace\"\n> > +$ git config trailer.help.cmd \"~/bin/git-who\"\n> > +$ git interpret-trailers --trailer=\"help:gitster@\" <<EOF\n> > +> subject\n> > +>\n> > +> message\n> > +>\n> > +> EOF\n> > +subject\n> > +\n> > +message\n> > +\n> > +Helped-by: Junio C Hamano <gitster@pobox.com>\n> > +------------\n> > +\n> >  * Configure a 'see' trailer with a command to show the subject of a\n> >    commit that is related, and show how it works:\n> >  +\n"},{"id":"420963","messageId":"CAP8UFD1=v_dfjKT2zY2=DFhZO1RWh9-++m5gAOn5WmwuV2pJXg@mail.gmail.com","threadId":"55371","inReplyTo":"CAOLTT8QvPPJ4jYXjch+RhdHDnvDT6Woh2oQoX2LcoiVX_t7jZQ@mail.gmail.com","subject":"Re: [PATCH v6] [GSOC] trailer: add new trailer.<token>.cmd config option","fromName":"Christian Couder","fromEmail":"christian.couder@gmail.com","sentAt":"2021-04-04T08:52:42Z","receivedAt":"2021-04-04T08:52:57Z","isPatch":true,"sender":{"key":"christian.couder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/208954?v=4"},"body":"On Sun, Apr 4, 2021 at 7:44 AM ZheNing Hu <adlternative@gmail.com> wrote:\n\n> This may be an off-topic question:\n> I wanted to use `shortlog -s` instead of the document example,\n> But I found a very strange place:\n> Here we have two shell scripts:\n>\n> $ cat ~/bin/gcount\n> #!/bin/sh\n> if test \"$1\" != \"\"\n\nIt's better to use `test -n \"$1\"` instead of `test \"$1\" != \"\"`.\n\n> then\n> git log -1 --author=\"$1\"\n> else\n> echo \"hello there\"\n> fi\n>\n> cat ~/bin/gcount2\n> #!/bin/sh\n> if test \"$1\" != \"\"\n> then\n> git shortlog -1 --author=\"$1\"\n> else\n> echo \"hello there\"\n> fi\n>\n> If I use them in the terminal, there is no problem with them,\n>\n> $ ~/bin/gcount gitster\n> commit 142430338477d9d1bb25be66267225fb58498d92\n> (interpret-trailers-command-arg, abc5b)\n> Author: Junio C Hamano <gitster@pobox.com>\n> Date:   Mon Mar 22 14:00:00 2021 -0700\n>\n>     The second batch\n>\n>     Signed-off-by: Junio C Hamano <gitster@pobox.com>\n>\n> $ ~/bin/gcount2 gitster\n> Junio C Hamano (1):\n>       The second batch\n>\n> if I use .cmd to run these scripts, the situation is totally different:\n>\n> $ git config -l | grep trailer\n> trailer.cnt.ifexists=add\n> trailer.cnt.key=Cnt:\n> trailer.cnt.cmd=~/bin/gcount\n\nThe script/command used in \".cmd\" or \".command\" should really return a\nshort string with no newline or line feed char in it. Here your script\ncan return multiple lines which could mess things up and be difficult\nto understand.\n\n> $ git interpret-trailers --trailer=\"cnt:gitster\" <<EOF\n> EOF\n>\n> Cnt: hello there\n> Cnt: commit 142430338477d9d1bb25be66267225fb58498d92\n> Author: Junio C Hamano <gitster@pobox.com>\n> Date:   Mon Mar 22 14:00:00 2021 -0700\n>\n>     The second batch\n>\n>     Signed-off-by: Junio C Hamano <gitster@pobox.com>\n>\n> And if I turn to use gcount2:\n> $ git config trailer.cnt.cmd \"~/bin/gcount2\"\n> $ git interpret-trailers --trailer=\"cnt:gitster\" <<EOF\n> EOF\n>\n> Cnt: hello there\n> Cnt:\n>\n> It looks like `shortlog` does not write to standard output.\n\nIn shortlog's doc there is:\n\n\"If no revisions are passed on the command line and either standard\ninput is not a terminal or there is no current branch, git shortlog\nwill output a summary of the log read from standard input, without\nreference to the current repository.\"\n\nI guess that's what's happening here.\n\n> Note that in `short_log.c`, log will be output to `log->file`.\n> Does it make the above behavior different?\n> Is there a good solution?\n\nI would try to pass a revision on the command line.\n"},{"id":"420969","messageId":"CAOLTT8TpDBmXgTNuV_bS6sYfv6QCh9ZznMq7vUmVDP7PzGmRRQ@mail.gmail.com","threadId":"55371","inReplyTo":"CAP8UFD1=v_dfjKT2zY2=DFhZO1RWh9-++m5gAOn5WmwuV2pJXg@mail.gmail.com","subject":"Re: [PATCH v6] [GSOC] trailer: add new trailer.<token>.cmd config option","fromName":"ZheNing Hu","fromEmail":"adlternative@gmail.com","sentAt":"2021-04-04T09:53:10Z","receivedAt":"2021-04-04T09:53:31Z","isPatch":true,"sender":{"key":"adlternative@gmail.com","avatar":"https://avatars.githubusercontent.com/u/58138461?v=4"},"body":"Christian Couder <christian.couder@gmail.com> 于2021年4月4日周日 下午4:52写道：\n>\n> On Sun, Apr 4, 2021 at 7:44 AM ZheNing Hu <adlternative@gmail.com> wrote:\n>\n> > This may be an off-topic question:\n> > I wanted to use `shortlog -s` instead of the document example,\n> > But I found a very strange place:\n> > Here we have two shell scripts:\n> >\n> > $ cat ~/bin/gcount\n> > #!/bin/sh\n> > if test \"$1\" != \"\"\n>\n> It's better to use `test -n \"$1\"` instead of `test \"$1\" != \"\"`.\n>\n> > then\n> > git log -1 --author=\"$1\"\n> > else\n> > echo \"hello there\"\n> > fi\n> >\n> > cat ~/bin/gcount2\n> > #!/bin/sh\n> > if test \"$1\" != \"\"\n> > then\n> > git shortlog -1 --author=\"$1\"\n> > else\n> > echo \"hello there\"\n> > fi\n> >\n> > If I use them in the terminal, there is no problem with them,\n> >\n> > $ ~/bin/gcount gitster\n> > commit 142430338477d9d1bb25be66267225fb58498d92\n> > (interpret-trailers-command-arg, abc5b)\n> > Author: Junio C Hamano <gitster@pobox.com>\n> > Date:   Mon Mar 22 14:00:00 2021 -0700\n> >\n> >     The second batch\n> >\n> >     Signed-off-by: Junio C Hamano <gitster@pobox.com>\n> >\n> > $ ~/bin/gcount2 gitster\n> > Junio C Hamano (1):\n> >       The second batch\n> >\n> > if I use .cmd to run these scripts, the situation is totally different:\n> >\n> > $ git config -l | grep trailer\n> > trailer.cnt.ifexists=add\n> > trailer.cnt.key=Cnt:\n> > trailer.cnt.cmd=~/bin/gcount\n>\n> The script/command used in \".cmd\" or \".command\" should really return a\n> short string with no newline or line feed char in it. Here your script\n> can return multiple lines which could mess things up and be difficult\n> to understand.\n>\n\nYes, it's just a small test, I want use `git shortlog -s --author=\"$1\"` to show\ncommit count of one author.\n\n> > $ git interpret-trailers --trailer=\"cnt:gitster\" <<EOF\n> > EOF\n> >\n> > Cnt: hello there\n> > Cnt: commit 142430338477d9d1bb25be66267225fb58498d92\n> > Author: Junio C Hamano <gitster@pobox.com>\n> > Date:   Mon Mar 22 14:00:00 2021 -0700\n> >\n> >     The second batch\n> >\n> >     Signed-off-by: Junio C Hamano <gitster@pobox.com>\n> >\n> > And if I turn to use gcount2:\n> > $ git config trailer.cnt.cmd \"~/bin/gcount2\"\n> > $ git interpret-trailers --trailer=\"cnt:gitster\" <<EOF\n> > EOF\n> >\n> > Cnt: hello there\n> > Cnt:\n> >\n> > It looks like `shortlog` does not write to standard output.\n>\n> In shortlog's doc there is:\n>\n> \"If no revisions are passed on the command line and either standard\n> input is not a terminal or there is no current branch, git shortlog\n> will output a summary of the log read from standard input, without\n> reference to the current repository.\"\n>\n> I guess that's what's happening here.\n>\n\nThis solved my confusion ;-)\n\n> > Note that in `short_log.c`, log will be output to `log->file`.\n> > Does it make the above behavior different?\n> > Is there a good solution?\n>\n> I would try to pass a revision on the command line.\n\nThanks, Christian.\n--\nZheNing Hu\n"},{"id":"420970","messageId":"pull.913.v7.git.1617541912381.gitgitgadget@gmail.com","threadId":"55371","inReplyTo":"pull.913.v6.git.1617369973328.gitgitgadget@gmail.com","subject":"[PATCH v7] [GSOC] trailer: add new trailer.<token>.cmd config option","fromName":"ZheNing Hu via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2021-04-04T13:11:51Z","receivedAt":"2021-04-04T13:11:58Z","isPatch":true,"sender":{"key":"adlternative@gmail.com","avatar":"https://avatars.githubusercontent.com/u/58138461?v=4"},"body":"From: ZheNing Hu <adlternative@gmail.com>\n\nThe `trailer.<token>.command` configuration variable\nspecifies a command (run via the shell, so it does not have\nto be a single name of or path to the command, but can be a\nshell script), and the first occurrence of substring $ARG is\nreplaced with the value given to the `interpret-trailer`\ncommand for the token.  This has two downsides:\n\n* The use of $ARG in the mechanism misleads the users that\nthe value is passed in the shell variable, and tempt them\nto use $ARG more than once, but that would not work, as\nthe second and subsequent $ARG are not replaced.\n\n* Because $ARG is textually replaced without regard to the\nshell language syntax, even '$ARG' (inside a single-quote\npair), which a user would expect to stay intact, would be\nreplaced, and worse, if the value had an unmatching single\nquote (imagine a name like \"O'Connor\", substituted into\nNAME='$ARG' to make it NAME='O'Connor'), it would result in\na broken command that is not syntactically correct (or\nworse).\n\nIntroduce a new `trailer.<token>.cmd` configuration that\ntakes higher precedence to deprecate and eventually remove\n`trailer.<token>.command`, which passes the value as a\nparameter to the command.  Instead of \"$ARG\", the users will\nrefer to the value as positional argument, $1, in their\nscripts.\n\nHelped-by: Junio C Hamano <gitster@pobox.com>\nHelped-by: Christian Couder <christian.couder@gmail.com>\nSigned-off-by: ZheNing Hu <adlternative@gmail.com>\n---\n    [GSOC] trailer: add new trailer..cmd config option\n    \n    In https://lore.kernel.org/git/xmqqv99i4ck2.fsf@gitster.g/ Junio and\n    Christian talked about the problem of using strbuf_replace() to replace\n    $ARG:\n    \n     1. if user's script have more than one $ARG, only the first one will be\n        replaced, which is incorrected.\n     2. $ARG is textually replaced without shell syntax, which may result a\n        broken command when $ARG include some unmatching single quote, very\n        unsafe.\n    \n    Now pass trailer value as $1 to the trailer command with another\n    trailer.<token>.cmd config, to solve these above two problems,\n    \n    We are now writing documents that are more readable and correct than\n    before.\n\nPublished-As: https://github.com/gitgitgadget/git/releases/tag/pr-913%2Fadlternative%2Ftrailer-pass-ARG-env-v7\nFetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-913/adlternative/trailer-pass-ARG-env-v7\nPull-Request: https://github.com/gitgitgadget/git/pull/913\n\nRange-diff vs v6:\n\n 1:  3aed77d077b9 ! 1:  1e9a6572ac6f [GSOC] trailer: add new trailer.<token>.cmd config option\n     @@ Commit message\n          pair), which a user would expect to stay intact, would be\n          replaced, and worse, if the value had an unmatching single\n          quote (imagine a name like \"O'Connor\", substituted into\n     -    NAME='$ARG' to make it NAME='O'Connor), it would result in\n     +    NAME='$ARG' to make it NAME='O'Connor'), it would result in\n          a broken command that is not syntactically correct (or\n          worse).\n      \n     @@ Documentation/git-interpret-trailers.txt: trailer.<token>.command::\n      -off.\n      +When this option is specified, the first occurrence of substring $ARG is\n      +replaced with the value given to the `interpret-trailer` command for the\n     -+same token.\n     ++same token. This option behaves in a similar way as \".cmd\", however, it\n     ++passes the value through $ARG.\n       +\n      -If the command contains the `$ARG` string, this string will be\n      -replaced with the <value> part of an existing trailer with the same\n     @@ Documentation/git-interpret-trailers.txt: trailer.<token>.command::\n      +\n      +trailer.<token>.cmd::\n      +\tThe command specified by this configuration variable is run\n     -+\twith a single parameter, which is the <value> part of an\n     -+\texisting trailer with the same <token>.  The output from the\n     -+\tcommand is then used as the value for the <token> in the\n     -+\tresulting trailer.\n     ++\twith a single argument, which is the <value> part of a\n     ++\t`--trailer <token>=<value>` on the command line. The output\n     ++\tfrom the command is then used as the value for the <token>\n     ++\tin the resulting trailer.\n      ++\n     -+When this option is specified, If there is no trailer with same <token>,\n     -+the behavior is as if a special '<token>=<value>' argument were added at\n     -+the beginning of the command, <value> will be passed to the user's\n     -+command as an empty value.\n     ++When this option is specified, the behavior is as if a '<token>=<value>'\n     ++argument were added at the beginning of the \"git interpret-trailers\"\n     ++command, the command specified by this configuration variable will be\n     ++called with an empty string as the argument.\n       +\n       If some '<token>=<value>' arguments are also passed on the command\n     - line, when a 'trailer.<token>.command' is configured, the command will\n     - also be executed for each of these arguments. And the <value> part of\n     +-line, when a 'trailer.<token>.command' is configured, the command will\n     +-also be executed for each of these arguments. And the <value> part of\n      -these arguments, if any, will be used to replace the `$ARG` string in\n      -the command.\n     -+these arguments, if any, will be passed to the command as first parameter.\n     ++line, when a 'trailer.<token>.cmd' is configured, the command is run\n     ++once for each `--trailer <token>=<value>` on the command line with the\n     ++same <token>. And the <value> part of these arguments, if any, will be\n     ++passed to the command as its first argument.\n       \n       EXAMPLES\n       --------\n     @@ Documentation/git-interpret-trailers.txt: subject\n       Fix #42\n       ------------\n       \n     -+* Configure a 'see' trailer with a cmd use a global script `git-one`\n     -+  to show the subject of a commit that is related, and show how it works:\n     ++* Configure a 'cnt' trailer with a cmd use a global script `gcount`\n     ++to record commit counts of a specified author and show how it works:\n      ++\n      +------------\n     -+$ cat ~/bin/git-one\n     ++$ cat ~/bin/gcount\n      +#!/bin/sh\n     -+git show -s --pretty=reference \"$1\"\n     -+$ git config trailer.see.key \"See-also: \"\n     -+$ git config trailer.see.ifExists \"replace\"\n     -+$ git config trailer.see.ifMissing \"doNothing\"\n     -+$ git config trailer.see.cmd \"~/bin/git-one\"\n     -+$ git interpret-trailers <<EOF\n     ++test -n \"$1\" && git shortlog -s --author=\"$1\" HEAD || true\n     ++$ git config trailer.cnt.key \"Commit-count: \"\n     ++$ git config trailer.cnt.ifExists \"replace\"\n     ++$ git config trailer.cnt.cmd \"~/bin/gcount\"\n     ++$ git interpret-trailers --trailer=\"cnt:Junio\" <<EOF\n      +> subject\n      +> \n      +> message\n      +> \n     -+> see: HEAD~2\n      +> EOF\n      +subject\n      +\n      +message\n      +\n     -+See-also: fe3187e (subject of related commit, 2021-4-2)\n     ++Commit-count: 22484     Junio C Hamano\n      +------------\n      +\n     -+* Configure a 'who' trailer with a cmd use a global script `git-who`\n     -+  to find the recent matching \"author <mail>\" pair in git log and\n     -+  show how it works:\n     ++* Configure a 'ref' trailer with a cmd use a global script `glog-grep`\n     ++  to grep last relevant commit from git log in the git repository\n     ++  and show how it works:\n      ++\n      +------------\n     -+$ cat ~/bin/git-who\n     -+ #!/bin/sh\n     -+    git log -1 --format=\"%an <%ae>\" --author=\"$1\"\n     -+$ git config trailer.help.key \"Helped-by: \"\n     -+$ git config trailer.help.ifExists \"replace\"\n     -+$ git config trailer.help.cmd \"~/bin/git-who\"\n     -+$ git interpret-trailers --trailer=\"help:gitster@\" <<EOF\n     ++$ cat ~/bin/glog-grep\n     ++#!/bin/sh\n     ++test -n \"$1\" && git log --grep \"$1\" --pretty=reference -1 || true\n     ++$ git config trailer.ref.key \"Reference-to: \"\n     ++$ git config trailer.ref.ifExists \"replace\"\n     ++$ git config trailer.ref.cmd \"~/bin/glog-grep\"\n     ++$ git interpret-trailers --trailer=\"ref:Add copyright notices.\" <<EOF\n      +> subject\n      +> \n      +> message\n     @@ Documentation/git-interpret-trailers.txt: subject\n      +\n      +message\n      +\n     -+Helped-by: Junio C Hamano <gitster@pobox.com>\n     ++Reference-to: 8bc9a0c769 (Add copyright notices., 2005-04-07)\n      +------------\n      +\n       * Configure a 'see' trailer with a command to show the subject of a\n     @@ t/t7513-interpret-trailers.sh: test_expect_success 'setup' '\n       '\n       \n      +test_expect_success 'with cmd' '\n     -+\ttest_when_finished \"git config --unset trailer.bug.key && \\\n     -+\t\t\t    git config --unset trailer.bug.ifExists && \\\n     -+\t\t\t    git config --unset trailer.bug.cmd\" &&\n     ++\ttest_when_finished \"git config --remove-section trailer.bug\" &&\n      +\tgit config trailer.bug.key \"Bug-maker: \" &&\n      +\tgit config trailer.bug.ifExists \"add\" &&\n      +\tgit config trailer.bug.cmd \"echo \\\"maybe is\\\"\" &&\n     @@ t/t7513-interpret-trailers.sh: test_expect_success 'setup' '\n      +'\n      +\n      +test_expect_success 'with cmd and $1' '\n     -+\ttest_when_finished \"git config --unset trailer.bug.key && \\\n     -+\t\t\t    git config --unset trailer.bug.ifExists && \\\n     -+\t\t\t    git config --unset trailer.bug.cmd\" &&\n     ++\ttest_when_finished \"git config --remove-section trailer.bug\" &&\n      +\tgit config trailer.bug.key \"Bug-maker: \" &&\n      +\tgit config trailer.bug.ifExists \"replace\" &&\n      +\tgit config trailer.bug.cmd \"echo \\\"\\$1\\\" is\" &&\n     @@ t/t7513-interpret-trailers.sh: test_expect_success 'setup' '\n      +'\n      +\n      +test_expect_success 'with cmd and $1 with sh -c' '\n     -+\ttest_when_finished \"git config --unset trailer.bug.key && \\\n     -+\t\t\t    git config --unset trailer.bug.ifExists && \\\n     -+\t\t\t    git config --unset trailer.bug.cmd\" &&\n     ++\ttest_when_finished \"git config --remove-section trailer.bug\" &&\n      +\tgit config trailer.bug.key \"Bug-maker: \" &&\n      +\tgit config trailer.bug.ifExists \"replace\" &&\n      +\tgit config trailer.bug.cmd \"sh -c \\\"echo who is \\\"\\$1\\\"\\\"\" &&\n     @@ t/t7513-interpret-trailers.sh: test_expect_success 'setup' '\n      +'\n      +\n      +test_expect_success 'with cmd and $1 with shell script' '\n     -+\ttest_when_finished \"git config --unset trailer.bug.key && \\\n     -+\t\t\t    git config --unset trailer.bug.ifExists && \\\n     -+\t\t\t    git config --unset trailer.bug.cmd\" &&\n     ++\ttest_when_finished \"git config --remove-section trailer.bug\" &&\n      +\tgit config trailer.bug.key \"Bug-maker: \" &&\n      +\tgit config trailer.bug.ifExists \"replace\" &&\n      +\tgit config trailer.bug.cmd \"./echoscript\" &&\n     @@ trailer.c: static int check_if_different(struct trailer_item *in_tok,\n      -\n      -\tstrvec_push(&cp.args, cmd.buf);\n      +\tif (conf->cmd) {\n     -+\t\t// cp.shell_no_implicit_args = 1;\n      +\t\tstrbuf_addstr(&cmd, conf->cmd);\n      +\t\tstrvec_push(&cp.args, cmd.buf);\n      +\t\tif (arg)\n      +\t\t\tstrvec_push(&cp.args, arg);\n      +\t} else if (conf->command) {\n      +\t\tstrbuf_addstr(&cmd, conf->command);\n     -+\t\tstrvec_push(&cp.args, cmd.buf);\n      +\t\tif (arg)\n      +\t\t\tstrbuf_replace(&cmd, TRAILER_ARG_STRING, arg);\n     ++\t\tstrvec_push(&cp.args, cmd.buf);\n      +\t}\n       \tcp.env = local_repo_env;\n       \tcp.no_stdin = 1;\n\n\n Documentation/git-interpret-trailers.txt | 86 +++++++++++++++++++----\n t/t7513-interpret-trailers.sh            | 87 +++++++++++++++++++++++-\n trailer.c                                | 37 +++++++---\n 3 files changed, 186 insertions(+), 24 deletions(-)\n\ndiff --git a/Documentation/git-interpret-trailers.txt b/Documentation/git-interpret-trailers.txt\nindex 96ec6499f001..83600e93390d 100644\n--- a/Documentation/git-interpret-trailers.txt\n+++ b/Documentation/git-interpret-trailers.txt\n@@ -236,21 +236,36 @@ trailer.<token>.command::\n \tbe called to automatically add or modify a trailer with the\n \tspecified <token>.\n +\n-When this option is specified, the behavior is as if a special\n-'<token>=<value>' argument were added at the beginning of the command\n-line, where <value> is taken to be the standard output of the\n-specified command with any leading and trailing whitespace trimmed\n-off.\n+When this option is specified, the first occurrence of substring $ARG is\n+replaced with the value given to the `interpret-trailer` command for the\n+same token. This option behaves in a similar way as \".cmd\", however, it\n+passes the value through $ARG.\n +\n-If the command contains the `$ARG` string, this string will be\n-replaced with the <value> part of an existing trailer with the same\n-<token>, if any, before the command is launched.\n+\".command\" has been deprecated due to the $ARG in the user's command can\n+only be replaced once and the original way of replacing $ARG was not safe.\n+Now the preferred option is using \"trailer.<token>.cmd\", which use position\n+argument to pass the value.\n++\n+When both .cmd and .command are given for the same <token>,\n+.cmd is used and .command is ignored.\n+\n+trailer.<token>.cmd::\n+\tThe command specified by this configuration variable is run\n+\twith a single argument, which is the <value> part of a\n+\t`--trailer <token>=<value>` on the command line. The output\n+\tfrom the command is then used as the value for the <token>\n+\tin the resulting trailer.\n++\n+When this option is specified, the behavior is as if a '<token>=<value>'\n+argument were added at the beginning of the \"git interpret-trailers\"\n+command, the command specified by this configuration variable will be\n+called with an empty string as the argument.\n +\n If some '<token>=<value>' arguments are also passed on the command\n-line, when a 'trailer.<token>.command' is configured, the command will\n-also be executed for each of these arguments. And the <value> part of\n-these arguments, if any, will be used to replace the `$ARG` string in\n-the command.\n+line, when a 'trailer.<token>.cmd' is configured, the command is run\n+once for each `--trailer <token>=<value>` on the command line with the\n+same <token>. And the <value> part of these arguments, if any, will be\n+passed to the command as its first argument.\n \n EXAMPLES\n --------\n@@ -333,6 +348,53 @@ subject\n Fix #42\n ------------\n \n+* Configure a 'cnt' trailer with a cmd use a global script `gcount`\n+to record commit counts of a specified author and show how it works:\n++\n+------------\n+$ cat ~/bin/gcount\n+#!/bin/sh\n+test -n \"$1\" && git shortlog -s --author=\"$1\" HEAD || true\n+$ git config trailer.cnt.key \"Commit-count: \"\n+$ git config trailer.cnt.ifExists \"replace\"\n+$ git config trailer.cnt.cmd \"~/bin/gcount\"\n+$ git interpret-trailers --trailer=\"cnt:Junio\" <<EOF\n+> subject\n+> \n+> message\n+> \n+> EOF\n+subject\n+\n+message\n+\n+Commit-count: 22484     Junio C Hamano\n+------------\n+\n+* Configure a 'ref' trailer with a cmd use a global script `glog-grep`\n+  to grep last relevant commit from git log in the git repository\n+  and show how it works:\n++\n+------------\n+$ cat ~/bin/glog-grep\n+#!/bin/sh\n+test -n \"$1\" && git log --grep \"$1\" --pretty=reference -1 || true\n+$ git config trailer.ref.key \"Reference-to: \"\n+$ git config trailer.ref.ifExists \"replace\"\n+$ git config trailer.ref.cmd \"~/bin/glog-grep\"\n+$ git interpret-trailers --trailer=\"ref:Add copyright notices.\" <<EOF\n+> subject\n+> \n+> message\n+> \n+> EOF\n+subject\n+\n+message\n+\n+Reference-to: 8bc9a0c769 (Add copyright notices., 2005-04-07)\n+------------\n+\n * Configure a 'see' trailer with a command to show the subject of a\n   commit that is related, and show how it works:\n +\ndiff --git a/t/t7513-interpret-trailers.sh b/t/t7513-interpret-trailers.sh\nindex 6602790b5f4c..6d26a2606604 100755\n--- a/t/t7513-interpret-trailers.sh\n+++ b/t/t7513-interpret-trailers.sh\n@@ -51,6 +51,69 @@ test_expect_success 'setup' '\n \tEOF\n '\n \n+test_expect_success 'with cmd' '\n+\ttest_when_finished \"git config --remove-section trailer.bug\" &&\n+\tgit config trailer.bug.key \"Bug-maker: \" &&\n+\tgit config trailer.bug.ifExists \"add\" &&\n+\tgit config trailer.bug.cmd \"echo \\\"maybe is\\\"\" &&\n+\tcat >expected2 <<-EOF &&\n+\n+\tBug-maker: maybe is\n+\tBug-maker: maybe is him\n+\tBug-maker: maybe is me\n+\tEOF\n+\tgit interpret-trailers --trailer \"bug: him\" --trailer \"bug:me\" \\\n+\t\t>actual2 &&\n+\ttest_cmp expected2 actual2\n+'\n+\n+test_expect_success 'with cmd and $1' '\n+\ttest_when_finished \"git config --remove-section trailer.bug\" &&\n+\tgit config trailer.bug.key \"Bug-maker: \" &&\n+\tgit config trailer.bug.ifExists \"replace\" &&\n+\tgit config trailer.bug.cmd \"echo \\\"\\$1\\\" is\" &&\n+\tcat >expected2 <<-EOF &&\n+\n+\tBug-maker: me is me\n+\tEOF\n+\tgit interpret-trailers --trailer \"bug: him\" --trailer \"bug:me\" \\\n+\t\t>actual2 &&\n+\ttest_cmp expected2 actual2\n+'\n+\n+test_expect_success 'with cmd and $1 with sh -c' '\n+\ttest_when_finished \"git config --remove-section trailer.bug\" &&\n+\tgit config trailer.bug.key \"Bug-maker: \" &&\n+\tgit config trailer.bug.ifExists \"replace\" &&\n+\tgit config trailer.bug.cmd \"sh -c \\\"echo who is \\\"\\$1\\\"\\\"\" &&\n+\tcat >expected2 <<-EOF &&\n+\n+\tBug-maker: who is me\n+\tEOF\n+\tgit interpret-trailers --trailer \"bug: him\" --trailer \"bug:me\" \\\n+\t\t>actual2 &&\n+\ttest_cmp expected2 actual2\n+'\n+\n+test_expect_success 'with cmd and $1 with shell script' '\n+\ttest_when_finished \"git config --remove-section trailer.bug\" &&\n+\tgit config trailer.bug.key \"Bug-maker: \" &&\n+\tgit config trailer.bug.ifExists \"replace\" &&\n+\tgit config trailer.bug.cmd \"./echoscript\" &&\n+\tcat >expected2 <<-EOF &&\n+\n+\tBug-maker: who is me\n+\tEOF\n+\tcat >echoscript <<-EOF &&\n+\t#!/bin/sh\n+\techo who is \"\\$1\"\n+\tEOF\n+\tchmod +x echoscript &&\n+\tgit interpret-trailers --trailer \"bug: him\" --trailer \"bug:me\" \\\n+\t\t>actual2 &&\n+\ttest_cmp expected2 actual2\n+'\n+\n test_expect_success 'without config' '\n \tsed -e \"s/ Z\\$/ /\" >expected <<-\\EOF &&\n \n@@ -1274,9 +1337,31 @@ test_expect_success 'setup a commit' '\n \tgit commit -m \"Add file a.txt\"\n '\n \n+test_expect_success 'cmd takes precedence over command' '\n+\ttest_when_finished \"git config --unset trailer.fix.cmd\" &&\n+\tgit config trailer.fix.ifExists \"replace\" &&\n+\tgit config trailer.fix.cmd \"test -n \\\"\\$1\\\" && git log -1 --oneline --format=\\\"%h (%aN)\\\" \\\n+\t\t--abbrev-commit --abbrev=14 \\\"\\$1\\\" || true\" &&\n+\tgit config trailer.fix.command \"git log -1 --oneline --format=\\\"%h (%s)\\\" \\\n+\t\t--abbrev-commit --abbrev=14 \\$ARG\" &&\n+\tFIXED=$(git log -1 --oneline --format=\"%h (%aN)\" --abbrev-commit --abbrev=14 HEAD) &&\n+\tcat complex_message_body >expected2 &&\n+\tsed -e \"s/ Z\\$/ /\" >>expected2 <<-EOF &&\n+\t\tFixes: $FIXED\n+\t\tAcked-by= Z\n+\t\tReviewed-by:\n+\t\tSigned-off-by: Z\n+\t\tSigned-off-by: A U Thor <author@example.com>\n+\tEOF\n+\tgit interpret-trailers --trailer \"review:\" --trailer \"fix=HEAD\" \\\n+\t\t<complex_message >actual2 &&\n+\ttest_cmp expected2 actual2\n+'\n+\n test_expect_success 'with command using $ARG' '\n \tgit config trailer.fix.ifExists \"replace\" &&\n-\tgit config trailer.fix.command \"git log -1 --oneline --format=\\\"%h (%s)\\\" --abbrev-commit --abbrev=14 \\$ARG\" &&\n+\tgit config trailer.fix.command \"git log -1 --oneline --format=\\\"%h (%s)\\\" \\\n+\t\t--abbrev-commit --abbrev=14 \\$ARG\" &&\n \tFIXED=$(git log -1 --oneline --format=\"%h (%s)\" --abbrev-commit --abbrev=14 HEAD) &&\n \tcat complex_message_body >expected &&\n \tsed -e \"s/ Z\\$/ /\" >>expected <<-EOF &&\ndiff --git a/trailer.c b/trailer.c\nindex be4e9726421c..bd384befe15b 100644\n--- a/trailer.c\n+++ b/trailer.c\n@@ -14,6 +14,7 @@ struct conf_info {\n \tchar *name;\n \tchar *key;\n \tchar *command;\n+\tchar *cmd;\n \tenum trailer_where where;\n \tenum trailer_if_exists if_exists;\n \tenum trailer_if_missing if_missing;\n@@ -127,6 +128,7 @@ static void free_arg_item(struct arg_item *item)\n \tfree(item->conf.name);\n \tfree(item->conf.key);\n \tfree(item->conf.command);\n+\tfree(item->conf.cmd);\n \tfree(item->token);\n \tfree(item->value);\n \tfree(item);\n@@ -216,18 +218,24 @@ static int check_if_different(struct trailer_item *in_tok,\n \treturn 1;\n }\n \n-static char *apply_command(const char *command, const char *arg)\n+static char *apply_command(struct conf_info *conf, const char *arg)\n {\n \tstruct strbuf cmd = STRBUF_INIT;\n \tstruct strbuf buf = STRBUF_INIT;\n \tstruct child_process cp = CHILD_PROCESS_INIT;\n \tchar *result;\n \n-\tstrbuf_addstr(&cmd, command);\n-\tif (arg)\n-\t\tstrbuf_replace(&cmd, TRAILER_ARG_STRING, arg);\n-\n-\tstrvec_push(&cp.args, cmd.buf);\n+\tif (conf->cmd) {\n+\t\tstrbuf_addstr(&cmd, conf->cmd);\n+\t\tstrvec_push(&cp.args, cmd.buf);\n+\t\tif (arg)\n+\t\t\tstrvec_push(&cp.args, arg);\n+\t} else if (conf->command) {\n+\t\tstrbuf_addstr(&cmd, conf->command);\n+\t\tif (arg)\n+\t\t\tstrbuf_replace(&cmd, TRAILER_ARG_STRING, arg);\n+\t\tstrvec_push(&cp.args, cmd.buf);\n+\t}\n \tcp.env = local_repo_env;\n \tcp.no_stdin = 1;\n \tcp.use_shell = 1;\n@@ -247,7 +255,7 @@ static char *apply_command(const char *command, const char *arg)\n \n static void apply_item_command(struct trailer_item *in_tok, struct arg_item *arg_tok)\n {\n-\tif (arg_tok->conf.command) {\n+\tif (arg_tok->conf.command || arg_tok->conf.cmd) {\n \t\tconst char *arg;\n \t\tif (arg_tok->value && arg_tok->value[0]) {\n \t\t\targ = arg_tok->value;\n@@ -257,7 +265,7 @@ static void apply_item_command(struct trailer_item *in_tok, struct arg_item *arg\n \t\t\telse\n \t\t\t\targ = xstrdup(\"\");\n \t\t}\n-\t\targ_tok->value = apply_command(arg_tok->conf.command, arg);\n+\t\targ_tok->value = apply_command(&arg_tok->conf, arg);\n \t\tfree((char *)arg);\n \t}\n }\n@@ -430,6 +438,7 @@ static void duplicate_conf(struct conf_info *dst, const struct conf_info *src)\n \tdst->name = xstrdup_or_null(src->name);\n \tdst->key = xstrdup_or_null(src->key);\n \tdst->command = xstrdup_or_null(src->command);\n+\tdst->cmd = xstrdup_or_null(src->cmd);\n }\n \n static struct arg_item *get_conf_item(const char *name)\n@@ -454,8 +463,8 @@ static struct arg_item *get_conf_item(const char *name)\n \treturn item;\n }\n \n-enum trailer_info_type { TRAILER_KEY, TRAILER_COMMAND, TRAILER_WHERE,\n-\t\t\t TRAILER_IF_EXISTS, TRAILER_IF_MISSING };\n+enum trailer_info_type { TRAILER_KEY, TRAILER_COMMAND, TRAILER_CMD,\n+\t\t\tTRAILER_WHERE, TRAILER_IF_EXISTS, TRAILER_IF_MISSING };\n \n static struct {\n \tconst char *name;\n@@ -463,6 +472,7 @@ static struct {\n } trailer_config_items[] = {\n \t{ \"key\", TRAILER_KEY },\n \t{ \"command\", TRAILER_COMMAND },\n+\t{ \"cmd\", TRAILER_CMD },\n \t{ \"where\", TRAILER_WHERE },\n \t{ \"ifexists\", TRAILER_IF_EXISTS },\n \t{ \"ifmissing\", TRAILER_IF_MISSING }\n@@ -542,6 +552,11 @@ static int git_trailer_config(const char *conf_key, const char *value, void *cb)\n \t\t\twarning(_(\"more than one %s\"), conf_key);\n \t\tconf->command = xstrdup(value);\n \t\tbreak;\n+\tcase TRAILER_CMD:\n+\t\tif (conf->cmd)\n+\t\t\twarning(_(\"more than one %s\"), conf_key);\n+\t\tconf->cmd = xstrdup(value);\n+\t\tbreak;\n \tcase TRAILER_WHERE:\n \t\tif (trailer_set_where(&conf->where, value))\n \t\t\twarning(_(\"unknown value '%s' for key '%s'\"), value, conf_key);\n@@ -708,7 +723,7 @@ static void process_command_line_args(struct list_head *arg_head,\n \t/* Add an arg item for each configured trailer with a command */\n \tlist_for_each(pos, &conf_head) {\n \t\titem = list_entry(pos, struct arg_item, list);\n-\t\tif (item->conf.command)\n+\t\tif (item->conf.cmd || item->conf.command)\n \t\t\tadd_arg_item(arg_head,\n \t\t\t\t     xstrdup(token_from_item(item, NULL)),\n \t\t\t\t     xstrdup(\"\"),\n\nbase-commit: 142430338477d9d1bb25be66267225fb58498d92\n-- \ngitgitgadget\n"},{"id":"420975","messageId":"xmqq8s5x7ht1.fsf@gitster.g","threadId":"55371","inReplyTo":"CAP8UFD0AZWey045qK=9h8nVCVwK09s=t-=Z7OVBpvr4LAvSLUw@mail.gmail.com","subject":"Re: [PATCH v6] [GSOC] trailer: add new trailer.<token>.cmd config option","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2021-04-04T23:26:18Z","receivedAt":"2021-04-04T23:26:27Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Christian Couder <christian.couder@gmail.com> writes:\n\n> On Fri, Apr 2, 2021 at 10:49 PM Junio C Hamano <gitster@pobox.com> wrote:\n>> ...\n>>         ... as if a '<token>=<value>' argument were added at the\n>>         beginning of the \"git interpret-trailers\" command, the\n>>         command specified by this configuration variable will be\n>>         called with an empty string as the argument.\n>>\n>> is my attempt,\n>\n> It looks better to me.\n>\n>> but I am not still sure what that \"as if\" part is\n>> trying to say.  Does it mean with\n>>\n>>         [trailer \"Foo\"] cmd = foo-cmd\n>>\n>> and the 'input-file' does not have \"Foo: <some existing value>\"\n>> trailer in it, the command \"git interpret-trailers input-file\"\n>> would behave as if this command was run\n>>\n>>         $ Foo= git interpret-trailers input-file\n>\n> I would say it would behave as if:\n>\n> $ git interpret-trailers --trailer Foo= input-file\n\nHmmm.  That means that the descrition in the original is quite\nmisleading, no?  If it said\n\n\t... as if \"--trailer\" \"<token>=<value>\" arguments were given\n\tto \"git interpret-trailers\" command near the beginning of\n\tits command line\n\nthen that may be closer description of the command line you are\nforming, but as its written (with or without my attempt to clarify\nabove), it was impossible to infer that you are behaving as if\nanother --trailer option (with <token>=<value> as its value) was\ngiven.\n\n\n"},{"id":"421039","messageId":"CAP8UFD17=BxHRkb7nmC8XsiGet+J=Adzqv4P6d9jzgnKXT9LJw@mail.gmail.com","threadId":"55371","inReplyTo":"xmqq8s5x7ht1.fsf@gitster.g","subject":"Re: [PATCH v6] [GSOC] trailer: add new trailer.<token>.cmd config option","fromName":"Christian Couder","fromEmail":"christian.couder@gmail.com","sentAt":"2021-04-06T03:47:50Z","receivedAt":"2021-04-06T03:48:06Z","isPatch":true,"sender":{"key":"christian.couder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/208954?v=4"},"body":"On Mon, Apr 5, 2021 at 1:26 AM Junio C Hamano <gitster@pobox.com> wrote:\n>\n> Christian Couder <christian.couder@gmail.com> writes:\n>\n> > On Fri, Apr 2, 2021 at 10:49 PM Junio C Hamano <gitster@pobox.com> wrote:\n> >> ...\n> >>         ... as if a '<token>=<value>' argument were added at the\n> >>         beginning of the \"git interpret-trailers\" command, the\n> >>         command specified by this configuration variable will be\n> >>         called with an empty string as the argument.\n> >>\n> >> is my attempt,\n> >\n> > It looks better to me.\n> >\n> >> but I am not still sure what that \"as if\" part is\n> >> trying to say.  Does it mean with\n> >>\n> >>         [trailer \"Foo\"] cmd = foo-cmd\n> >>\n> >> and the 'input-file' does not have \"Foo: <some existing value>\"\n> >> trailer in it, the command \"git interpret-trailers input-file\"\n> >> would behave as if this command was run\n> >>\n> >>         $ Foo= git interpret-trailers input-file\n> >\n> > I would say it would behave as if:\n> >\n> > $ git interpret-trailers --trailer Foo= input-file\n>\n> Hmmm.  That means that the descrition in the original is quite\n> misleading, no?\n\nYeah, I agree it is misleading and difficult to understand.\n\n> If it said\n>\n>         ... as if \"--trailer\" \"<token>=<value>\" arguments were given\n>         to \"git interpret-trailers\" command near the beginning of\n>         its command line\n>\n> then that may be closer description of the command line you are\n> forming, but as its written (with or without my attempt to clarify\n> above), it was impossible to infer that you are behaving as if\n> another --trailer option (with <token>=<value> as its value) was\n> given.\n\nI agree.\n"},{"id":"421040","messageId":"CAP8UFD08Lagij6oqpOJkpbzMLRgQ08=mDfiyf1qXyD5VW6Ae7A@mail.gmail.com","threadId":"55371","inReplyTo":"CAP8UFD17=BxHRkb7nmC8XsiGet+J=Adzqv4P6d9jzgnKXT9LJw@mail.gmail.com","subject":"Re: [PATCH v6] [GSOC] trailer: add new trailer.<token>.cmd config option","fromName":"Christian Couder","fromEmail":"christian.couder@gmail.com","sentAt":"2021-04-06T03:52:28Z","receivedAt":"2021-04-06T03:52:42Z","isPatch":true,"sender":{"key":"christian.couder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/208954?v=4"},"body":"On Tue, Apr 6, 2021 at 5:47 AM Christian Couder\n<christian.couder@gmail.com> wrote:\n>\n> On Mon, Apr 5, 2021 at 1:26 AM Junio C Hamano <gitster@pobox.com> wrote:\n> >\n> > Christian Couder <christian.couder@gmail.com> writes:\n\n> > > I would say it would behave as if:\n> > >\n> > > $ git interpret-trailers --trailer Foo= input-file\n> >\n> > Hmmm.  That means that the descrition in the original is quite\n> > misleading, no?\n>\n> Yeah, I agree it is misleading and difficult to understand.\n>\n> > If it said\n> >\n> >         ... as if \"--trailer\" \"<token>=<value>\" arguments were given\n> >         to \"git interpret-trailers\" command near the beginning of\n> >         its command line\n> >\n> > then that may be closer description of the command line you are\n> > forming, but as its written (with or without my attempt to clarify\n> > above), it was impossible to infer that you are behaving as if\n> > another --trailer option (with <token>=<value> as its value) was\n> > given.\n>\n> I agree.\n\nBy the way it might be better to first have a patch that clarifies the\nexisting documentation of \".command\", before the patch that adds\n\".cmd\". This way the first patch that only clarifies the documentation\ncould be backported to maintenance branches of old releases.\n"},{"id":"421041","messageId":"CAOLTT8TrJn+V-4WijCfi=q5JpvfBj_oMfWRx9TX-0Cs5uShmOg@mail.gmail.com","threadId":"55371","inReplyTo":"CAP8UFD08Lagij6oqpOJkpbzMLRgQ08=mDfiyf1qXyD5VW6Ae7A@mail.gmail.com","subject":"Re: [PATCH v6] [GSOC] trailer: add new trailer.<token>.cmd config option","fromName":"ZheNing Hu","fromEmail":"adlternative@gmail.com","sentAt":"2021-04-06T05:16:14Z","receivedAt":"2021-04-06T05:16:31Z","isPatch":true,"sender":{"key":"adlternative@gmail.com","avatar":"https://avatars.githubusercontent.com/u/58138461?v=4"},"body":"Hi, Junio and Christian,\n\nChristian Couder <christian.couder@gmail.com> 于2021年4月6日周二 上午11:52写道：\n>\n> On Tue, Apr 6, 2021 at 5:47 AM Christian Couder\n> <christian.couder@gmail.com> wrote:\n> >\n> > On Mon, Apr 5, 2021 at 1:26 AM Junio C Hamano <gitster@pobox.com> wrote:\n> > >\n> > > Christian Couder <christian.couder@gmail.com> writes:\n>\n> > > > I would say it would behave as if:\n> > > >\n> > > > $ git interpret-trailers --trailer Foo= input-file\n> > >\n> > > Hmmm.  That means that the descrition in the original is quite\n> > > misleading, no?\n> >\n> > Yeah, I agree it is misleading and difficult to understand.\n> >\n> > > If it said\n> > >\n> > >         ... as if \"--trailer\" \"<token>=<value>\" arguments were given\n> > >         to \"git interpret-trailers\" command near the beginning of\n> > >         its command line\n> > >\n> > > then that may be closer description of the command line you are\n> > > forming, but as its written (with or without my attempt to clarify\n> > > above), it was impossible to infer that you are behaving as if\n> > > another --trailer option (with <token>=<value> as its value) was\n> > > given.\n> >\n> > I agree.\n>\n> By the way it might be better to first have a patch that clarifies the\n> existing documentation of \".command\", before the patch that adds\n> \".cmd\". This way the first patch that only clarifies the documentation\n> could be backported to maintenance branches of old releases.\n\nThis first patch should explain errors of \".command\", and then explain that\nwe will no longer use it, right?\n\nI find in the status update description:\n\"Seems to break new tests for .command variant in zh/commit-trailer\"\nI have tryed to merge my local branch 1e9a657(trailer-cmd) and\n2daae3d(upstream/zh/commit-trailer)\n\nI can't find any tests errors after merging, is there something unexpected\nhappened?\n\n--\nZheNing Hu\n"},{"id":"421042","messageId":"xmqqtuok2cyi.fsf@gitster.g","threadId":"55371","inReplyTo":"CAOLTT8TrJn+V-4WijCfi=q5JpvfBj_oMfWRx9TX-0Cs5uShmOg@mail.gmail.com","subject":"Re: [PATCH v6] [GSOC] trailer: add new trailer.<token>.cmd config option","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2021-04-06T05:34:29Z","receivedAt":"2021-04-06T05:34:34Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"ZheNing Hu <adlternative@gmail.com> writes:\n\n> I find in the status update description:\n> \"Seems to break new tests for .command variant in zh/commit-trailer\"\n> I have tryed to merge my local branch 1e9a657(trailer-cmd) and\n> 2daae3d(upstream/zh/commit-trailer)\n\nThanks for noticing---the description is stale.  Remember that after\nI pointed out the breakage, you responded that the conversion for\nthe .command codepath was wrong?  What is queued in my tree has a\nversion with fix of that bug, so the comment does not apply.\n\n\n\n"},{"id":"421043","messageId":"xmqqpmz82cu0.fsf@gitster.g","threadId":"55371","inReplyTo":"CAOLTT8TrJn+V-4WijCfi=q5JpvfBj_oMfWRx9TX-0Cs5uShmOg@mail.gmail.com","subject":"Re: [PATCH v6] [GSOC] trailer: add new trailer.<token>.cmd config option","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2021-04-06T05:37:11Z","receivedAt":"2021-04-06T05:37:18Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"ZheNing Hu <adlternative@gmail.com> writes:\n\n>> By the way it might be better to first have a patch that clarifies the\n>> existing documentation of \".command\", before the patch that adds\n>> \".cmd\". This way the first patch that only clarifies the documentation\n>> could be backported to maintenance branches of old releases.\n>\n> This first patch should explain errors of \".command\", and then explain that\n> we will no longer use it, right?\n\nIf I am reading what Christian wrote correctly, I think it is not\n\"errors of .command\" but to clarify the misleading description\n(especially around the \"as if <token>=<value> were added at the\nbeginning\").  As if nothing is planned for its deprecation.\n\n\n"},{"id":"421070","messageId":"CAP8UFD3Ga=HR4YsS8UvgEQF9982be_wa8dUzniGsri+6qoEMHg@mail.gmail.com","threadId":"55371","inReplyTo":"pull.913.v7.git.1617541912381.gitgitgadget@gmail.com","subject":"Re: [PATCH v7] [GSOC] trailer: add new trailer.<token>.cmd config option","fromName":"Christian Couder","fromEmail":"christian.couder@gmail.com","sentAt":"2021-04-06T16:23:37Z","receivedAt":"2021-04-06T16:23:53Z","isPatch":true,"sender":{"key":"christian.couder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/208954?v=4"},"body":"On Sun, Apr 4, 2021 at 3:11 PM ZheNing Hu via GitGitGadget\n<gitgitgadget@gmail.com> wrote:\n>\n> From: ZheNing Hu <adlternative@gmail.com>\n>\n> The `trailer.<token>.command` configuration variable\n> specifies a command (run via the shell, so it does not have\n> to be a single name of or path to the command, but can be a\n> shell script), and the first occurrence of substring $ARG is\n> replaced with the value given to the `interpret-trailer`\n> command for the token.  This has two downsides:\n>\n> * The use of $ARG in the mechanism misleads the users that\n> the value is passed in the shell variable, and tempt them\n> to use $ARG more than once, but that would not work, as\n> the second and subsequent $ARG are not replaced.\n>\n> * Because $ARG is textually replaced without regard to the\n> shell language syntax, even '$ARG' (inside a single-quote\n> pair), which a user would expect to stay intact, would be\n> replaced, and worse, if the value had an unmatching single\n\ns/unmatching/unmatched/\n\n> quote (imagine a name like \"O'Connor\", substituted into\n> NAME='$ARG' to make it NAME='O'Connor'), it would result in\n> a broken command that is not syntactically correct (or\n> worse).\n\nGood explanation of the issues with \".command\"!\n\n> Introduce a new `trailer.<token>.cmd` configuration that\n> takes higher precedence to deprecate and eventually remove\n> `trailer.<token>.command`, which passes the value as a\n> parameter to the command.  Instead of \"$ARG\", the users will\n\ns/a parameter/an argument/\ns/the users will/users can/\n\n> refer to the value as positional argument, $1, in their\n> scripts.\n>\n> Helped-by: Junio C Hamano <gitster@pobox.com>\n> Helped-by: Christian Couder <christian.couder@gmail.com>\n> Signed-off-by: ZheNing Hu <adlternative@gmail.com>\n> ---\n>     [GSOC] trailer: add new trailer..cmd config option\n\nMaybe <token> has been removed from \"trailer.<token>.cmd\" above\nbecause it has been interpreted as an HTML or XML tag?\n\n>     In https://lore.kernel.org/git/xmqqv99i4ck2.fsf@gitster.g/ Junio and\n>     Christian talked about the problem of using strbuf_replace() to replace\n>     $ARG:\n>\n>      1. if user's script have more than one $ARG, only the first one will be\n\ns/user's script have/the user's script has/\n\n>         replaced, which is incorrected.\n>      2. $ARG is textually replaced without shell syntax, which may result a\n>         broken command when $ARG include some unmatching single quote, very\n>         unsafe.\n\nYeah, good summary of the issues with \".command\"\n\n>     Now pass trailer value as $1 to the trailer command with another\n>     trailer.<token>.cmd config, to solve these above two problems,\n>\n>     We are now writing documents that are more readable and correct than\n>     before.\n\nYeah, correcting the doc is a good thing to do. By the way, as I said\nto Junio, it might be better to make the doc for \".command\" more\nreadable and correct in a first patch separate from the patch\nintroducing \".cmd\".\n\nIf you really want to do both in the same patch you should tell that\nin the commit message too, not just here after the \"---\" line.\n\n>  Documentation/git-interpret-trailers.txt | 86 +++++++++++++++++++----\n>  t/t7513-interpret-trailers.sh            | 87 +++++++++++++++++++++++-\n>  trailer.c                                | 37 +++++++---\n>  3 files changed, 186 insertions(+), 24 deletions(-)\n>\n> diff --git a/Documentation/git-interpret-trailers.txt b/Documentation/git-interpret-trailers.txt\n> index 96ec6499f001..83600e93390d 100644\n> --- a/Documentation/git-interpret-trailers.txt\n> +++ b/Documentation/git-interpret-trailers.txt\n> @@ -236,21 +236,36 @@ trailer.<token>.command::\n>         be called to automatically add or modify a trailer with the\n>         specified <token>.\n>  +\n> -When this option is specified, the behavior is as if a special\n> -'<token>=<value>' argument were added at the beginning of the command\n> -line, where <value> is taken to be the standard output of the\n> -specified command with any leading and trailing whitespace trimmed\n> -off.\n> +When this option is specified, the first occurrence of substring $ARG is\n> +replaced with the value given to the `interpret-trailer` command for the\n> +same token. This option behaves in a similar way as \".cmd\", however, it\n> +passes the value through $ARG.\n\nMaybe this last sentence could be replaced with \"Otherwise this option\nbehaves in the same way as 'trailer.<token>.cmd'.\"\n\n> -If the command contains the `$ARG` string, this string will be\n> -replaced with the <value> part of an existing trailer with the same\n> -<token>, if any, before the command is launched.\n> +\".command\" has been deprecated due to the $ARG in the user's command can\n\ns/\".command\"/The 'trailer.<token>.command' option/\ns/to the $ARG/to the fact that `$ARG`/\n\n> +only be replaced once and the original way of replacing $ARG was not safe.\n\ns/only be/can only be/\ns/and the/and that the/\n\n> +Now the preferred option is using \"trailer.<token>.cmd\", which use position\n\ns/using//\ns/use/uses/\ns/position/a positional/\n\nAlso please make sure that trailer.<token>.cmd and\ntrailer.<token>.command are always quoted in the same way. I think\nsingle quotes are used in the current doc, so please keep using single\nquotes.\n\n> +argument to pass the value.\n> ++\n> +When both .cmd and .command are given for the same <token>,\n> +.cmd is used and .command is ignored.\n\nPlease spell and quote \".cmd\" and \".command\" consistently, so for\nexample like: 'trailer.<token>.cmd'\n\n> +trailer.<token>.cmd::\n> +       The command specified by this configuration variable is run\n> +       with a single argument, which is the <value> part of a\n> +       `--trailer <token>=<value>` on the command line. The output\n> +       from the command is then used as the value for the <token>\n> +       in the resulting trailer.\n> ++\n> +When this option is specified, the behavior is as if a '<token>=<value>'\n\ns/'<token>=<value>'/`--trailer <token>=<value>`/  (let's try to be as\nexplicit as possible)\n\n> +argument were added at the beginning of the \"git interpret-trailers\"\n\ns/were/was/\n\n> +command, the command specified by this configuration variable will be\n> +called with an empty string as the argument.\n> +\n>  If some '<token>=<value>' arguments are also passed on the command\n> -line, when a 'trailer.<token>.command' is configured, the command will\n> -also be executed for each of these arguments. And the <value> part of\n> -these arguments, if any, will be used to replace the `$ARG` string in\n> -the command.\n> +line, when a 'trailer.<token>.cmd' is configured, the command is run\n> +once for each `--trailer <token>=<value>` on the command line with the\n> +same <token>. And the <value> part of these arguments, if any, will be\n> +passed to the command as its first argument.\n\nYeah, it's much better than it was, but I think we can do better. I\nwill try to come up with something soon.\n\nAlso as I said above and in reply to Junio, I think it might be better\nto split this in 2 patches.\n\n>  EXAMPLES\n>  --------\n> @@ -333,6 +348,53 @@ subject\n>  Fix #42\n>  ------------\n>\n> +* Configure a 'cnt' trailer with a cmd use a global script `gcount`\n> +to record commit counts of a specified author and show how it works:\n> ++\n> +------------\n> +$ cat ~/bin/gcount\n> +#!/bin/sh\n> +test -n \"$1\" && git shortlog -s --author=\"$1\" HEAD || true\n> +$ git config trailer.cnt.key \"Commit-count: \"\n> +$ git config trailer.cnt.ifExists \"replace\"\n> +$ git config trailer.cnt.cmd \"~/bin/gcount\"\n> +$ git interpret-trailers --trailer=\"cnt:Junio\" <<EOF\n> +> subject\n> +>\n> +> message\n> +>\n> +> EOF\n> +subject\n> +\n> +message\n> +\n> +Commit-count: 22484     Junio C Hamano\n> +------------\n> +\n> +* Configure a 'ref' trailer with a cmd use a global script `glog-grep`\n> +  to grep last relevant commit from git log in the git repository\n> +  and show how it works:\n> ++\n> +------------\n> +$ cat ~/bin/glog-grep\n> +#!/bin/sh\n> +test -n \"$1\" && git log --grep \"$1\" --pretty=reference -1 || true\n> +$ git config trailer.ref.key \"Reference-to: \"\n> +$ git config trailer.ref.ifExists \"replace\"\n> +$ git config trailer.ref.cmd \"~/bin/glog-grep\"\n> +$ git interpret-trailers --trailer=\"ref:Add copyright notices.\" <<EOF\n> +> subject\n> +>\n> +> message\n> +>\n> +> EOF\n> +subject\n> +\n> +message\n> +\n> +Reference-to: 8bc9a0c769 (Add copyright notices., 2005-04-07)\n\nThe added examples look good!\n\n> +test_expect_success 'with cmd' '\n> +       test_when_finished \"git config --remove-section trailer.bug\" &&\n> +       git config trailer.bug.key \"Bug-maker: \" &&\n> +       git config trailer.bug.ifExists \"add\" &&\n> +       git config trailer.bug.cmd \"echo \\\"maybe is\\\"\" &&\n> +       cat >expected2 <<-EOF &&\n> +\n> +       Bug-maker: maybe is\n> +       Bug-maker: maybe is him\n> +       Bug-maker: maybe is me\n> +       EOF\n> +       git interpret-trailers --trailer \"bug: him\" --trailer \"bug:me\" \\\n> +               >actual2 &&\n> +       test_cmp expected2 actual2\n> +'\n\nI guess this shows that the command is called multiple times, the\nfirst time with an empty first arg.\n\n> +test_expect_success 'with cmd and $1' '\n> +       test_when_finished \"git config --remove-section trailer.bug\" &&\n> +       git config trailer.bug.key \"Bug-maker: \" &&\n> +       git config trailer.bug.ifExists \"replace\" &&\n> +       git config trailer.bug.cmd \"echo \\\"\\$1\\\" is\" &&\n> +       cat >expected2 <<-EOF &&\n> +\n> +       Bug-maker: me is me\n> +       EOF\n> +       git interpret-trailers --trailer \"bug: him\" --trailer \"bug:me\" \\\n> +               >actual2 &&\n> +       test_cmp expected2 actual2\n> +'\n\nI guess this shows that the argument is also available as \"$1\".\n\n> +test_expect_success 'with cmd and $1 with sh -c' '\n> +       test_when_finished \"git config --remove-section trailer.bug\" &&\n> +       git config trailer.bug.key \"Bug-maker: \" &&\n> +       git config trailer.bug.ifExists \"replace\" &&\n> +       git config trailer.bug.cmd \"sh -c \\\"echo who is \\\"\\$1\\\"\\\"\" &&\n> +       cat >expected2 <<-EOF &&\n> +\n> +       Bug-maker: who is me\n> +       EOF\n> +       git interpret-trailers --trailer \"bug: him\" --trailer \"bug:me\" \\\n> +               >actual2 &&\n> +       test_cmp expected2 actual2\n> +'\n\nOk, this shows how `sh -c ...` can be used in \".cmd\".\n\n> +test_expect_success 'with cmd and $1 with shell script' '\n> +       test_when_finished \"git config --remove-section trailer.bug\" &&\n> +       git config trailer.bug.key \"Bug-maker: \" &&\n> +       git config trailer.bug.ifExists \"replace\" &&\n> +       git config trailer.bug.cmd \"./echoscript\" &&\n> +       cat >expected2 <<-EOF &&\n> +\n> +       Bug-maker: who is me\n> +       EOF\n> +       cat >echoscript <<-EOF &&\n> +       #!/bin/sh\n> +       echo who is \"\\$1\"\n> +       EOF\n> +       chmod +x echoscript &&\n> +       git interpret-trailers --trailer \"bug: him\" --trailer \"bug:me\" \\\n> +               >actual2 &&\n> +       test_cmp expected2 actual2\n> +'\n\nOk.\n\n>  test_expect_success 'without config' '\n>         sed -e \"s/ Z\\$/ /\" >expected <<-\\EOF &&\n>\n> @@ -1274,9 +1337,31 @@ test_expect_success 'setup a commit' '\n>         git commit -m \"Add file a.txt\"\n>  '\n>\n> +test_expect_success 'cmd takes precedence over command' '\n> +       test_when_finished \"git config --unset trailer.fix.cmd\" &&\n> +       git config trailer.fix.ifExists \"replace\" &&\n> +       git config trailer.fix.cmd \"test -n \\\"\\$1\\\" && git log -1 --oneline --format=\\\"%h (%aN)\\\" \\\n> +               --abbrev-commit --abbrev=14 \\\"\\$1\\\" || true\" &&\n> +       git config trailer.fix.command \"git log -1 --oneline --format=\\\"%h (%s)\\\" \\\n> +               --abbrev-commit --abbrev=14 \\$ARG\" &&\n> +       FIXED=$(git log -1 --oneline --format=\"%h (%aN)\" --abbrev-commit --abbrev=14 HEAD) &&\n> +       cat complex_message_body >expected2 &&\n> +       sed -e \"s/ Z\\$/ /\" >>expected2 <<-EOF &&\n> +               Fixes: $FIXED\n> +               Acked-by= Z\n> +               Reviewed-by:\n> +               Signed-off-by: Z\n> +               Signed-off-by: A U Thor <author@example.com>\n> +       EOF\n> +       git interpret-trailers --trailer \"review:\" --trailer \"fix=HEAD\" \\\n> +               <complex_message >actual2 &&\n> +       test_cmp expected2 actual2\n> +'\n\nOk.\n\n>  test_expect_success 'with command using $ARG' '\n>         git config trailer.fix.ifExists \"replace\" &&\n> -       git config trailer.fix.command \"git log -1 --oneline --format=\\\"%h (%s)\\\" --abbrev-commit --abbrev=14 \\$ARG\" &&\n> +       git config trailer.fix.command \"git log -1 --oneline --format=\\\"%h (%s)\\\" \\\n> +               --abbrev-commit --abbrev=14 \\$ARG\" &&\n\nThis is just an indent change. I am not sure it's worth doing in this\npatch. If you think that the file needs better indentation though,\nthen you might do it in a separate preparatory patch at the beginning\nof the current patch series.\n\nIf there is only this place in the file where such an indentation\nimprovement is needed, this might be ok, but please mention in the\ncommit message that while at it you are also doing this small change.\n\nThe other parts of the patch look good to me.\n"},{"id":"421108","messageId":"CAOLTT8Rr_eXXwj3pXLF9zkTcgqu+w5ObH+f1_krrxoW1rQtwZw@mail.gmail.com","threadId":"55371","inReplyTo":"CAP8UFD3Ga=HR4YsS8UvgEQF9982be_wa8dUzniGsri+6qoEMHg@mail.gmail.com","subject":"Re: [PATCH v7] [GSOC] trailer: add new trailer.<token>.cmd config option","fromName":"ZheNing Hu","fromEmail":"adlternative@gmail.com","sentAt":"2021-04-07T04:51:30Z","receivedAt":"2021-04-07T04:51:46Z","isPatch":true,"sender":{"key":"adlternative@gmail.com","avatar":"https://avatars.githubusercontent.com/u/58138461?v=4"},"body":"Christian Couder <christian.couder@gmail.com> 于2021年4月7日周三 上午12:23写道：\n>\n> On Sun, Apr 4, 2021 at 3:11 PM ZheNing Hu via GitGitGadget\n> <gitgitgadget@gmail.com> wrote:\n> >\n> > From: ZheNing Hu <adlternative@gmail.com>\n> >\n> > The `trailer.<token>.command` configuration variable\n> > specifies a command (run via the shell, so it does not have\n> > to be a single name of or path to the command, but can be a\n> > shell script), and the first occurrence of substring $ARG is\n> > replaced with the value given to the `interpret-trailer`\n> > command for the token.  This has two downsides:\n> >\n> > * The use of $ARG in the mechanism misleads the users that\n> > the value is passed in the shell variable, and tempt them\n> > to use $ARG more than once, but that would not work, as\n> > the second and subsequent $ARG are not replaced.\n> >\n> > * Because $ARG is textually replaced without regard to the\n> > shell language syntax, even '$ARG' (inside a single-quote\n> > pair), which a user would expect to stay intact, would be\n> > replaced, and worse, if the value had an unmatching single\n>\n> s/unmatching/unmatched/\n>\n> > quote (imagine a name like \"O'Connor\", substituted into\n> > NAME='$ARG' to make it NAME='O'Connor'), it would result in\n> > a broken command that is not syntactically correct (or\n> > worse).\n>\n> Good explanation of the issues with \".command\"!\n>\n\nCredit for Junio.\n\n> > Introduce a new `trailer.<token>.cmd` configuration that\n> > takes higher precedence to deprecate and eventually remove\n> > `trailer.<token>.command`, which passes the value as a\n> > parameter to the command.  Instead of \"$ARG\", the users will\n>\n> s/a parameter/an argument/\n> s/the users will/users can/\n>\n> > refer to the value as positional argument, $1, in their\n> > scripts.\n> >\n> > Helped-by: Junio C Hamano <gitster@pobox.com>\n> > Helped-by: Christian Couder <christian.couder@gmail.com>\n> > Signed-off-by: ZheNing Hu <adlternative@gmail.com>\n> > ---\n> >     [GSOC] trailer: add new trailer..cmd config option\n>\n> Maybe <token> has been removed from \"trailer.<token>.cmd\" above\n> because it has been interpreted as an HTML or XML tag?\n>\n\nAha, It may well be so.\nMaybe change to \"[GSOC] trailer add .cmd config option\"?\n\n> >     In https://lore.kernel.org/git/xmqqv99i4ck2.fsf@gitster.g/ Junio and\n> >     Christian talked about the problem of using strbuf_replace() to replace\n> >     $ARG:\n> >\n> >      1. if user's script have more than one $ARG, only the first one will be\n>\n> s/user's script have/the user's script has/\n>\n> >         replaced, which is incorrected.\n> >      2. $ARG is textually replaced without shell syntax, which may result a\n> >         broken command when $ARG include some unmatching single quote, very\n> >         unsafe.\n>\n> Yeah, good summary of the issues with \".command\"\n>\n> >     Now pass trailer value as $1 to the trailer command with another\n> >     trailer.<token>.cmd config, to solve these above two problems,\n> >\n> >     We are now writing documents that are more readable and correct than\n> >     before.\n>\n> Yeah, correcting the doc is a good thing to do. By the way, as I said\n> to Junio, it might be better to make the doc for \".command\" more\n> readable and correct in a first patch separate from the patch\n> introducing \".cmd\".\n>\n> If you really want to do both in the same patch you should tell that\n> in the commit message too, not just here after the \"---\" line.\n>\n\nI actually tried to write it yesterday, but...\n\ndiff --git a/Documentation/git-interpret-trailers.txt\nb/Documentation/git-interpret-trailers.txt\nindex 96ec6499f0..39f742b3dc 100644\n--- a/Documentation/git-interpret-trailers.txt\n+++ b/Documentation/git-interpret-trailers.txt\n@@ -237,20 +237,20 @@ trailer.<token>.command::\n        specified <token>.\n +\n When this option is specified, the behavior is as if a special\n-'<token>=<value>' argument were added at the beginning of the command\n-line, where <value> is taken to be the standard output of the\n-specified command with any leading and trailing whitespace trimmed\n-off.\n+'<token>=<value>' argument were added at the beginning of the\n+\"git interpret-trailers\" command, where <value> is taken to be the\n+standard output of the specified command with any leading and\n+trailing whitespace trimmed off.\n +\n-If the command contains the `$ARG` string, this string will be\n-replaced with the <value> part of an existing trailer with the same\n-<token>, if any, before the command is launched.\n+The first occurrence of substring $ARG will be replaced with the\n+<value> part of an existing trailer with the same <token>, if any,\n+before the command is launched.\n +\n If some '<token>=<value>' arguments are also passed on the command\n-line, when a 'trailer.<token>.command' is configured, the command will\n-also be executed for each of these arguments. And the <value> part of\n-these arguments, if any, will be used to replace the `$ARG` string in\n-the command.\n+line, when a 'trailer.<token>.cmd' is configured, the command is run\n+once for each `--trailer <token>=<value>` on the command line with the\n+same <token>. And the <value> part of these arguments, if any, will be\n+used to replace the fist $ARG string in the command.\n\nSince I am based on the document in the second patch, you have also\nuncovered some points worthy of modification below,\nso temporarily what I wrote is not accurate enough.\n\n> >  Documentation/git-interpret-trailers.txt | 86 +++++++++++++++++++----\n> >  t/t7513-interpret-trailers.sh            | 87 +++++++++++++++++++++++-\n> >  trailer.c                                | 37 +++++++---\n> >  3 files changed, 186 insertions(+), 24 deletions(-)\n> >\n> > diff --git a/Documentation/git-interpret-trailers.txt b/Documentation/git-interpret-trailers.txt\n> > index 96ec6499f001..83600e93390d 100644\n> > --- a/Documentation/git-interpret-trailers.txt\n> > +++ b/Documentation/git-interpret-trailers.txt\n> > @@ -236,21 +236,36 @@ trailer.<token>.command::\n> >         be called to automatically add or modify a trailer with the\n> >         specified <token>.\n> >  +\n> > -When this option is specified, the behavior is as if a special\n> > -'<token>=<value>' argument were added at the beginning of the command\n> > -line, where <value> is taken to be the standard output of the\n> > -specified command with any leading and trailing whitespace trimmed\n> > -off.\n> > +When this option is specified, the first occurrence of substring $ARG is\n> > +replaced with the value given to the `interpret-trailer` command for the\n> > +same token. This option behaves in a similar way as \".cmd\", however, it\n> > +passes the value through $ARG.\n>\n> Maybe this last sentence could be replaced with \"Otherwise this option\n> behaves in the same way as 'trailer.<token>.cmd'.\"\n>\n> > -If the command contains the `$ARG` string, this string will be\n> > -replaced with the <value> part of an existing trailer with the same\n> > -<token>, if any, before the command is launched.\n> > +\".command\" has been deprecated due to the $ARG in the user's command can\n>\n> s/\".command\"/The 'trailer.<token>.command' option/\n> s/to the $ARG/to the fact that `$ARG`/\n>\n> > +only be replaced once and the original way of replacing $ARG was not safe.\n>\n> s/only be/can only be/\n> s/and the/and that the/\n>\n> > +Now the preferred option is using \"trailer.<token>.cmd\", which use position\n>\n> s/using//\n> s/use/uses/\n> s/position/a positional/\n>\n> Also please make sure that trailer.<token>.cmd and\n> trailer.<token>.command are always quoted in the same way. I think\n> single quotes are used in the current doc, so please keep using single\n> quotes.\n>\n> > +argument to pass the value.\n> > ++\n> > +When both .cmd and .command are given for the same <token>,\n> > +.cmd is used and .command is ignored.\n>\n> Please spell and quote \".cmd\" and \".command\" consistently, so for\n> example like: 'trailer.<token>.cmd'\n>\n\nOK.\n\n> > +trailer.<token>.cmd::\n> > +       The command specified by this configuration variable is run\n> > +       with a single argument, which is the <value> part of a\n> > +       `--trailer <token>=<value>` on the command line. The output\n> > +       from the command is then used as the value for the <token>\n> > +       in the resulting trailer.\n> > ++\n> > +When this option is specified, the behavior is as if a '<token>=<value>'\n>\n> s/'<token>=<value>'/`--trailer <token>=<value>`/  (let's try to be as\n> explicit as possible)\n>\n> > +argument were added at the beginning of the \"git interpret-trailers\"\n>\n> s/were/was/\n>\n> > +command, the command specified by this configuration variable will be\n> > +called with an empty string as the argument.\n> > +\n> >  If some '<token>=<value>' arguments are also passed on the command\n> > -line, when a 'trailer.<token>.command' is configured, the command will\n> > -also be executed for each of these arguments. And the <value> part of\n> > -these arguments, if any, will be used to replace the `$ARG` string in\n> > -the command.\n> > +line, when a 'trailer.<token>.cmd' is configured, the command is run\n> > +once for each `--trailer <token>=<value>` on the command line with the\n> > +same <token>. And the <value> part of these arguments, if any, will be\n> > +passed to the command as its first argument.\n>\n> Yeah, it's much better than it was, but I think we can do better. I\n> will try to come up with something soon.\n>\n> Also as I said above and in reply to Junio, I think it might be better\n> to split this in 2 patches.\n>\n> >  EXAMPLES\n> >  --------\n> > @@ -333,6 +348,53 @@ subject\n> >  Fix #42\n> >  ------------\n> >\n> > +* Configure a 'cnt' trailer with a cmd use a global script `gcount`\n> > +to record commit counts of a specified author and show how it works:\n> > ++\n> > +------------\n> > +$ cat ~/bin/gcount\n> > +#!/bin/sh\n> > +test -n \"$1\" && git shortlog -s --author=\"$1\" HEAD || true\n> > +$ git config trailer.cnt.key \"Commit-count: \"\n> > +$ git config trailer.cnt.ifExists \"replace\"\n> > +$ git config trailer.cnt.cmd \"~/bin/gcount\"\n> > +$ git interpret-trailers --trailer=\"cnt:Junio\" <<EOF\n> > +> subject\n> > +>\n> > +> message\n> > +>\n> > +> EOF\n> > +subject\n> > +\n> > +message\n> > +\n> > +Commit-count: 22484     Junio C Hamano\n> > +------------\n> > +\n> > +* Configure a 'ref' trailer with a cmd use a global script `glog-grep`\n> > +  to grep last relevant commit from git log in the git repository\n> > +  and show how it works:\n> > ++\n> > +------------\n> > +$ cat ~/bin/glog-grep\n> > +#!/bin/sh\n> > +test -n \"$1\" && git log --grep \"$1\" --pretty=reference -1 || true\n> > +$ git config trailer.ref.key \"Reference-to: \"\n> > +$ git config trailer.ref.ifExists \"replace\"\n> > +$ git config trailer.ref.cmd \"~/bin/glog-grep\"\n> > +$ git interpret-trailers --trailer=\"ref:Add copyright notices.\" <<EOF\n> > +> subject\n> > +>\n> > +> message\n> > +>\n> > +> EOF\n> > +subject\n> > +\n> > +message\n> > +\n> > +Reference-to: 8bc9a0c769 (Add copyright notices., 2005-04-07)\n>\n> The added examples look good!\n>\n\nThanks.\n\n> > +test_expect_success 'with cmd' '\n> > +       test_when_finished \"git config --remove-section trailer.bug\" &&\n> > +       git config trailer.bug.key \"Bug-maker: \" &&\n> > +       git config trailer.bug.ifExists \"add\" &&\n> > +       git config trailer.bug.cmd \"echo \\\"maybe is\\\"\" &&\n> > +       cat >expected2 <<-EOF &&\n> > +\n> > +       Bug-maker: maybe is\n> > +       Bug-maker: maybe is him\n> > +       Bug-maker: maybe is me\n> > +       EOF\n> > +       git interpret-trailers --trailer \"bug: him\" --trailer \"bug:me\" \\\n> > +               >actual2 &&\n> > +       test_cmp expected2 actual2\n> > +'\n>\n> I guess this shows that the command is called multiple times, the\n> first time with an empty first arg.\n>\n> > +test_expect_success 'with cmd and $1' '\n> > +       test_when_finished \"git config --remove-section trailer.bug\" &&\n> > +       git config trailer.bug.key \"Bug-maker: \" &&\n> > +       git config trailer.bug.ifExists \"replace\" &&\n> > +       git config trailer.bug.cmd \"echo \\\"\\$1\\\" is\" &&\n> > +       cat >expected2 <<-EOF &&\n> > +\n> > +       Bug-maker: me is me\n> > +       EOF\n> > +       git interpret-trailers --trailer \"bug: him\" --trailer \"bug:me\" \\\n> > +               >actual2 &&\n> > +       test_cmp expected2 actual2\n> > +'\n>\n> I guess this shows that the argument is also available as \"$1\".\n>\n> > +test_expect_success 'with cmd and $1 with sh -c' '\n> > +       test_when_finished \"git config --remove-section trailer.bug\" &&\n> > +       git config trailer.bug.key \"Bug-maker: \" &&\n> > +       git config trailer.bug.ifExists \"replace\" &&\n> > +       git config trailer.bug.cmd \"sh -c \\\"echo who is \\\"\\$1\\\"\\\"\" &&\n> > +       cat >expected2 <<-EOF &&\n> > +\n> > +       Bug-maker: who is me\n> > +       EOF\n> > +       git interpret-trailers --trailer \"bug: him\" --trailer \"bug:me\" \\\n> > +               >actual2 &&\n> > +       test_cmp expected2 actual2\n> > +'\n>\n> Ok, this shows how `sh -c ...` can be used in \".cmd\".\n>\n> > +test_expect_success 'with cmd and $1 with shell script' '\n> > +       test_when_finished \"git config --remove-section trailer.bug\" &&\n> > +       git config trailer.bug.key \"Bug-maker: \" &&\n> > +       git config trailer.bug.ifExists \"replace\" &&\n> > +       git config trailer.bug.cmd \"./echoscript\" &&\n> > +       cat >expected2 <<-EOF &&\n> > +\n> > +       Bug-maker: who is me\n> > +       EOF\n> > +       cat >echoscript <<-EOF &&\n> > +       #!/bin/sh\n> > +       echo who is \"\\$1\"\n> > +       EOF\n> > +       chmod +x echoscript &&\n> > +       git interpret-trailers --trailer \"bug: him\" --trailer \"bug:me\" \\\n> > +               >actual2 &&\n> > +       test_cmp expected2 actual2\n> > +'\n>\n> Ok.\n>\n> >  test_expect_success 'without config' '\n> >         sed -e \"s/ Z\\$/ /\" >expected <<-\\EOF &&\n> >\n> > @@ -1274,9 +1337,31 @@ test_expect_success 'setup a commit' '\n> >         git commit -m \"Add file a.txt\"\n> >  '\n> >\n> > +test_expect_success 'cmd takes precedence over command' '\n> > +       test_when_finished \"git config --unset trailer.fix.cmd\" &&\n> > +       git config trailer.fix.ifExists \"replace\" &&\n> > +       git config trailer.fix.cmd \"test -n \\\"\\$1\\\" && git log -1 --oneline --format=\\\"%h (%aN)\\\" \\\n> > +               --abbrev-commit --abbrev=14 \\\"\\$1\\\" || true\" &&\n> > +       git config trailer.fix.command \"git log -1 --oneline --format=\\\"%h (%s)\\\" \\\n> > +               --abbrev-commit --abbrev=14 \\$ARG\" &&\n> > +       FIXED=$(git log -1 --oneline --format=\"%h (%aN)\" --abbrev-commit --abbrev=14 HEAD) &&\n> > +       cat complex_message_body >expected2 &&\n> > +       sed -e \"s/ Z\\$/ /\" >>expected2 <<-EOF &&\n> > +               Fixes: $FIXED\n> > +               Acked-by= Z\n> > +               Reviewed-by:\n> > +               Signed-off-by: Z\n> > +               Signed-off-by: A U Thor <author@example.com>\n> > +       EOF\n> > +       git interpret-trailers --trailer \"review:\" --trailer \"fix=HEAD\" \\\n> > +               <complex_message >actual2 &&\n> > +       test_cmp expected2 actual2\n> > +'\n>\n> Ok.\n>\n> >  test_expect_success 'with command using $ARG' '\n> >         git config trailer.fix.ifExists \"replace\" &&\n> > -       git config trailer.fix.command \"git log -1 --oneline --format=\\\"%h (%s)\\\" --abbrev-commit --abbrev=14 \\$ARG\" &&\n> > +       git config trailer.fix.command \"git log -1 --oneline --format=\\\"%h (%s)\\\" \\\n> > +               --abbrev-commit --abbrev=14 \\$ARG\" &&\n>\n> This is just an indent change. I am not sure it's worth doing in this\n> patch. If you think that the file needs better indentation though,\n> then you might do it in a separate preparatory patch at the beginning\n> of the current patch series.\n>\n> If there is only this place in the file where such an indentation\n> improvement is needed, this might be ok, but please mention in the\n> commit message that while at it you are also doing this small change.\n>\n\nWell, I may often think that these small changes are irrelevant.\nSince you said that, I will cancel the modification of this place.\n\n> The other parts of the patch look good to me.\n\nThanks, Christian.\n--\nZheNing Hu\n"},{"id":"421388","messageId":"pull.913.v8.git.1617975462.gitgitgadget@gmail.com","threadId":"55371","inReplyTo":"pull.913.v7.git.1617541912381.gitgitgadget@gmail.com","subject":"[PATCH v8 0/2] [GSOC] trailer: add new .cmd config option","fromName":"ZheNing Hu via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2021-04-09T13:37:40Z","receivedAt":"2021-04-09T13:37:46Z","isPatch":true,"sender":{"key":"adlternative@gmail.com","avatar":"https://avatars.githubusercontent.com/u/58138461?v=4"},"body":"In https://lore.kernel.org/git/xmqqv99i4ck2.fsf@gitster.g/ Junio and\nChristian talked about the problem of using strbuf_replace() to replace\n$ARG:\n\n 1. if the user's script has more than one $ARG, only the first one will be\n    replaced, which is incorrected.\n 2. $ARG is textually replaced without shell syntax, which may result a\n    broken command when $ARG include some unmatching single quote, very\n    unsafe.\n\nNow pass trailer value as $1 to the trailer command with another\ntrailer.<token>.cmd config, to solve these above two problems,\n\nWe are now writing documents that are more readable and correct than before.\n\nZheNing Hu (2):\n  [GSOC] docs: correct descript of trailer.<token>.command\n  [GSOC] trailer: add new .cmd config option\n\n Documentation/git-interpret-trailers.txt | 90 ++++++++++++++++++++----\n t/t7513-interpret-trailers.sh            | 84 ++++++++++++++++++++++\n trailer.c                                | 37 +++++++---\n 3 files changed, 187 insertions(+), 24 deletions(-)\n\n\nbase-commit: 142430338477d9d1bb25be66267225fb58498d92\nPublished-As: https://github.com/gitgitgadget/git/releases/tag/pr-913%2Fadlternative%2Ftrailer-pass-ARG-env-v8\nFetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-913/adlternative/trailer-pass-ARG-env-v8\nPull-Request: https://github.com/gitgitgadget/git/pull/913\n\nRange-diff vs v7:\n\n -:  ------------ > 1:  505903811df8 [GSOC] docs: correct descript of trailer.<token>.command\n 1:  1e9a6572ac6f ! 2:  3dc8983a4702 [GSOC] trailer: add new trailer.<token>.cmd config option\n     @@ Metadata\n      Author: ZheNing Hu <adlternative@gmail.com>\n      \n       ## Commit message ##\n     -    [GSOC] trailer: add new trailer.<token>.cmd config option\n     +    [GSOC] trailer: add new .cmd config option\n      \n          The `trailer.<token>.command` configuration variable\n          specifies a command (run via the shell, so it does not have\n     @@ Commit message\n          * Because $ARG is textually replaced without regard to the\n          shell language syntax, even '$ARG' (inside a single-quote\n          pair), which a user would expect to stay intact, would be\n     -    replaced, and worse, if the value had an unmatching single\n     +    replaced, and worse, if the value had an unmatched single\n          quote (imagine a name like \"O'Connor\", substituted into\n          NAME='$ARG' to make it NAME='O'Connor'), it would result in\n          a broken command that is not syntactically correct (or\n     @@ Commit message\n      \n          Introduce a new `trailer.<token>.cmd` configuration that\n          takes higher precedence to deprecate and eventually remove\n     -    `trailer.<token>.command`, which passes the value as a\n     -    parameter to the command.  Instead of \"$ARG\", the users will\n     +    `trailer.<token>.command`, which passes the value as an\n     +    argument to the command.  Instead of \"$ARG\", users can\n          refer to the value as positional argument, $1, in their\n          scripts.\n      \n     @@ Documentation/git-interpret-trailers.txt: trailer.<token>.command::\n       \tspecified <token>.\n       +\n      -When this option is specified, the behavior is as if a special\n     --'<token>=<value>' argument were added at the beginning of the command\n     --line, where <value> is taken to be the standard output of the\n     --specified command with any leading and trailing whitespace trimmed\n     --off.\n     +-'--trailer <token>=<value>' argument was added at the beginning of\n     +-the \"git interpret-trailers\" command, where <value> is taken to be the\n     +-standard output of the specified command with any leading and trailing\n     +-whitespace trimmed off.\n      +When this option is specified, the first occurrence of substring $ARG is\n      +replaced with the value given to the `interpret-trailer` command for the\n     -+same token. This option behaves in a similar way as \".cmd\", however, it\n     -+passes the value through $ARG.\n     - +\n     --If the command contains the `$ARG` string, this string will be\n     --replaced with the <value> part of an existing trailer with the same\n     --<token>, if any, before the command is launched.\n     -+\".command\" has been deprecated due to the $ARG in the user's command can\n     -+only be replaced once and the original way of replacing $ARG was not safe.\n     -+Now the preferred option is using \"trailer.<token>.cmd\", which use position\n     -+argument to pass the value.\n     ++same token. It passes the value through `$ARG`, otherwise this option behaves\n     ++in the same way as 'trailer.<token>.cmd'.\n      ++\n     -+When both .cmd and .command are given for the same <token>,\n     -+.cmd is used and .command is ignored.\n     ++The 'trailer.<token>.command' option has been deprecated due to the fact\n     ++that $ARG in the user's command can only be replaced once and that the\n     ++original way of replacing $ARG was not safe. Now the preferred option is\n     ++'trailer.<token>.cmd', which uses a positional argument to pass the value.\n     + +\n     +-The first occurrence of substring `$ARG` will be replaced with the\n     +-<value> part of an existing trailer with the same <token>, if any,\n     +-before the command is launched.\n     ++When both 'trailer.<token>.cmd' and 'trailer.<token>.command' are given\n     ++for the same <token>, 'trailer.<token>.cmd' is used and\n     ++'trailer.<token>.command' is ignored.\n      +\n      +trailer.<token>.cmd::\n      +\tThe command specified by this configuration variable is run\n     @@ Documentation/git-interpret-trailers.txt: trailer.<token>.command::\n      +\tfrom the command is then used as the value for the <token>\n      +\tin the resulting trailer.\n      ++\n     -+When this option is specified, the behavior is as if a '<token>=<value>'\n     -+argument were added at the beginning of the \"git interpret-trailers\"\n     -+command, the command specified by this configuration variable will be\n     -+called with an empty string as the argument.\n     ++When this option is specified, the behavior is as if a\n     ++'--trailer <token>=<value>' argument was added at the beginning of\n     ++the \"git interpret-trailers\" command, the command specified by this\n     ++configuration variable will be called with an empty string as the\n     ++argument.\n       +\n     - If some '<token>=<value>' arguments are also passed on the command\n     --line, when a 'trailer.<token>.command' is configured, the command will\n     --also be executed for each of these arguments. And the <value> part of\n     --these arguments, if any, will be used to replace the `$ARG` string in\n     --the command.\n     -+line, when a 'trailer.<token>.cmd' is configured, the command is run\n     -+once for each `--trailer <token>=<value>` on the command line with the\n     -+same <token>. And the <value> part of these arguments, if any, will be\n     -+passed to the command as its first argument.\n     +-If some '<token>=<value>' arguments are also passed on the command\n     +-line, when a 'trailer.<token>.command' is configured, the command is run\n     +-once for each these arguments with the same <token>. And the <value>\n     +-part of these arguments, if any, will be used to replace the first `$ARG`\n     +-string in the command.\n     ++If some '--trailer <token>=<value>' arguments are also passed on the\n     ++command line, when a 'trailer.<token>.cmd' is configured, the command\n     ++is run once for each `--trailer <token>=<value>` on the command line\n     ++with the same <token>. And the <value> part of these arguments, if any,\n     ++will be passed to the command as its first argument.\n       \n       EXAMPLES\n       --------\n     @@ t/t7513-interpret-trailers.sh: test_expect_success 'setup a commit' '\n      +\ttest_when_finished \"git config --unset trailer.fix.cmd\" &&\n      +\tgit config trailer.fix.ifExists \"replace\" &&\n      +\tgit config trailer.fix.cmd \"test -n \\\"\\$1\\\" && git log -1 --oneline --format=\\\"%h (%aN)\\\" \\\n     -+\t\t--abbrev-commit --abbrev=14 \\\"\\$1\\\" || true\" &&\n     ++\t--abbrev-commit --abbrev=14 \\\"\\$1\\\" || true\" &&\n      +\tgit config trailer.fix.command \"git log -1 --oneline --format=\\\"%h (%s)\\\" \\\n      +\t\t--abbrev-commit --abbrev=14 \\$ARG\" &&\n      +\tFIXED=$(git log -1 --oneline --format=\"%h (%aN)\" --abbrev-commit --abbrev=14 HEAD) &&\n     @@ t/t7513-interpret-trailers.sh: test_expect_success 'setup a commit' '\n      +\n       test_expect_success 'with command using $ARG' '\n       \tgit config trailer.fix.ifExists \"replace\" &&\n     --\tgit config trailer.fix.command \"git log -1 --oneline --format=\\\"%h (%s)\\\" --abbrev-commit --abbrev=14 \\$ARG\" &&\n     -+\tgit config trailer.fix.command \"git log -1 --oneline --format=\\\"%h (%s)\\\" \\\n     -+\t\t--abbrev-commit --abbrev=14 \\$ARG\" &&\n     - \tFIXED=$(git log -1 --oneline --format=\"%h (%s)\" --abbrev-commit --abbrev=14 HEAD) &&\n     - \tcat complex_message_body >expected &&\n     - \tsed -e \"s/ Z\\$/ /\" >>expected <<-EOF &&\n     + \tgit config trailer.fix.command \"git log -1 --oneline --format=\\\"%h (%s)\\\" --abbrev-commit --abbrev=14 \\$ARG\" &&\n      \n       ## trailer.c ##\n      @@ trailer.c: struct conf_info {\n\n-- \ngitgitgadget\n"},{"id":"421389","messageId":"505903811df83cf26f4dd70c5b811dde169896a2.1617975462.git.gitgitgadget@gmail.com","threadId":"55371","inReplyTo":"pull.913.v8.git.1617975462.gitgitgadget@gmail.com","subject":"[PATCH v8 1/2] [GSOC] docs: correct descript of trailer.<token>.command","fromName":"ZheNing Hu via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2021-04-09T13:37:41Z","receivedAt":"2021-04-09T13:37:48Z","isPatch":true,"sender":{"key":"adlternative@gmail.com","avatar":"https://avatars.githubusercontent.com/u/58138461?v=4"},"body":"From: ZheNing Hu <adlternative@gmail.com>\n\nIn the original documentation of `trailer.<token>.command`,\nsome descriptions are easily misunderstood. So let's modify\nit to increase its readability.\n\nIn addition, clarify that `$ARG` in command can only be\nreplaced once since `$ARG` is text replacement.\n\nSigned-off-by: ZheNing Hu <adlternative@gmail.com>\n---\n Documentation/git-interpret-trailers.txt | 22 +++++++++++-----------\n 1 file changed, 11 insertions(+), 11 deletions(-)\n\ndiff --git a/Documentation/git-interpret-trailers.txt b/Documentation/git-interpret-trailers.txt\nindex 96ec6499f001..3e5aa3a65ae9 100644\n--- a/Documentation/git-interpret-trailers.txt\n+++ b/Documentation/git-interpret-trailers.txt\n@@ -237,20 +237,20 @@ trailer.<token>.command::\n \tspecified <token>.\n +\n When this option is specified, the behavior is as if a special\n-'<token>=<value>' argument were added at the beginning of the command\n-line, where <value> is taken to be the standard output of the\n-specified command with any leading and trailing whitespace trimmed\n-off.\n+'--trailer <token>=<value>' argument was added at the beginning of\n+the \"git interpret-trailers\" command, where <value> is taken to be the\n+standard output of the specified command with any leading and trailing\n+whitespace trimmed off.\n +\n-If the command contains the `$ARG` string, this string will be\n-replaced with the <value> part of an existing trailer with the same\n-<token>, if any, before the command is launched.\n+The first occurrence of substring `$ARG` will be replaced with the\n+<value> part of an existing trailer with the same <token>, if any,\n+before the command is launched.\n +\n If some '<token>=<value>' arguments are also passed on the command\n-line, when a 'trailer.<token>.command' is configured, the command will\n-also be executed for each of these arguments. And the <value> part of\n-these arguments, if any, will be used to replace the `$ARG` string in\n-the command.\n+line, when a 'trailer.<token>.command' is configured, the command is run\n+once for each these arguments with the same <token>. And the <value>\n+part of these arguments, if any, will be used to replace the first `$ARG`\n+string in the command.\n \n EXAMPLES\n --------\n-- \ngitgitgadget\n\n"},{"id":"421390","messageId":"3dc8983a47020fb417bb8c6c3d835e609b13c155.1617975462.git.gitgitgadget@gmail.com","threadId":"55371","inReplyTo":"pull.913.v8.git.1617975462.gitgitgadget@gmail.com","subject":"[PATCH v8 2/2] [GSOC] trailer: add new .cmd config option","fromName":"ZheNing Hu via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2021-04-09T13:37:42Z","receivedAt":"2021-04-09T13:37:49Z","isPatch":true,"sender":{"key":"adlternative@gmail.com","avatar":"https://avatars.githubusercontent.com/u/58138461?v=4"},"body":"From: ZheNing Hu <adlternative@gmail.com>\n\nThe `trailer.<token>.command` configuration variable\nspecifies a command (run via the shell, so it does not have\nto be a single name of or path to the command, but can be a\nshell script), and the first occurrence of substring $ARG is\nreplaced with the value given to the `interpret-trailer`\ncommand for the token.  This has two downsides:\n\n* The use of $ARG in the mechanism misleads the users that\nthe value is passed in the shell variable, and tempt them\nto use $ARG more than once, but that would not work, as\nthe second and subsequent $ARG are not replaced.\n\n* Because $ARG is textually replaced without regard to the\nshell language syntax, even '$ARG' (inside a single-quote\npair), which a user would expect to stay intact, would be\nreplaced, and worse, if the value had an unmatched single\nquote (imagine a name like \"O'Connor\", substituted into\nNAME='$ARG' to make it NAME='O'Connor'), it would result in\na broken command that is not syntactically correct (or\nworse).\n\nIntroduce a new `trailer.<token>.cmd` configuration that\ntakes higher precedence to deprecate and eventually remove\n`trailer.<token>.command`, which passes the value as an\nargument to the command.  Instead of \"$ARG\", users can\nrefer to the value as positional argument, $1, in their\nscripts.\n\nHelped-by: Junio C Hamano <gitster@pobox.com>\nHelped-by: Christian Couder <christian.couder@gmail.com>\nSigned-off-by: ZheNing Hu <adlternative@gmail.com>\n---\n Documentation/git-interpret-trailers.txt | 90 ++++++++++++++++++++----\n t/t7513-interpret-trailers.sh            | 84 ++++++++++++++++++++++\n trailer.c                                | 37 +++++++---\n 3 files changed, 187 insertions(+), 24 deletions(-)\n\ndiff --git a/Documentation/git-interpret-trailers.txt b/Documentation/git-interpret-trailers.txt\nindex 3e5aa3a65ae9..1a874a93f49b 100644\n--- a/Documentation/git-interpret-trailers.txt\n+++ b/Documentation/git-interpret-trailers.txt\n@@ -236,21 +236,38 @@ trailer.<token>.command::\n \tbe called to automatically add or modify a trailer with the\n \tspecified <token>.\n +\n-When this option is specified, the behavior is as if a special\n-'--trailer <token>=<value>' argument was added at the beginning of\n-the \"git interpret-trailers\" command, where <value> is taken to be the\n-standard output of the specified command with any leading and trailing\n-whitespace trimmed off.\n+When this option is specified, the first occurrence of substring $ARG is\n+replaced with the value given to the `interpret-trailer` command for the\n+same token. It passes the value through `$ARG`, otherwise this option behaves\n+in the same way as 'trailer.<token>.cmd'.\n++\n+The 'trailer.<token>.command' option has been deprecated due to the fact\n+that $ARG in the user's command can only be replaced once and that the\n+original way of replacing $ARG was not safe. Now the preferred option is\n+'trailer.<token>.cmd', which uses a positional argument to pass the value.\n +\n-The first occurrence of substring `$ARG` will be replaced with the\n-<value> part of an existing trailer with the same <token>, if any,\n-before the command is launched.\n+When both 'trailer.<token>.cmd' and 'trailer.<token>.command' are given\n+for the same <token>, 'trailer.<token>.cmd' is used and\n+'trailer.<token>.command' is ignored.\n+\n+trailer.<token>.cmd::\n+\tThe command specified by this configuration variable is run\n+\twith a single argument, which is the <value> part of a\n+\t`--trailer <token>=<value>` on the command line. The output\n+\tfrom the command is then used as the value for the <token>\n+\tin the resulting trailer.\n++\n+When this option is specified, the behavior is as if a\n+'--trailer <token>=<value>' argument was added at the beginning of\n+the \"git interpret-trailers\" command, the command specified by this\n+configuration variable will be called with an empty string as the\n+argument.\n +\n-If some '<token>=<value>' arguments are also passed on the command\n-line, when a 'trailer.<token>.command' is configured, the command is run\n-once for each these arguments with the same <token>. And the <value>\n-part of these arguments, if any, will be used to replace the first `$ARG`\n-string in the command.\n+If some '--trailer <token>=<value>' arguments are also passed on the\n+command line, when a 'trailer.<token>.cmd' is configured, the command\n+is run once for each `--trailer <token>=<value>` on the command line\n+with the same <token>. And the <value> part of these arguments, if any,\n+will be passed to the command as its first argument.\n \n EXAMPLES\n --------\n@@ -333,6 +350,53 @@ subject\n Fix #42\n ------------\n \n+* Configure a 'cnt' trailer with a cmd use a global script `gcount`\n+to record commit counts of a specified author and show how it works:\n++\n+------------\n+$ cat ~/bin/gcount\n+#!/bin/sh\n+test -n \"$1\" && git shortlog -s --author=\"$1\" HEAD || true\n+$ git config trailer.cnt.key \"Commit-count: \"\n+$ git config trailer.cnt.ifExists \"replace\"\n+$ git config trailer.cnt.cmd \"~/bin/gcount\"\n+$ git interpret-trailers --trailer=\"cnt:Junio\" <<EOF\n+> subject\n+> \n+> message\n+> \n+> EOF\n+subject\n+\n+message\n+\n+Commit-count: 22484     Junio C Hamano\n+------------\n+\n+* Configure a 'ref' trailer with a cmd use a global script `glog-grep`\n+  to grep last relevant commit from git log in the git repository\n+  and show how it works:\n++\n+------------\n+$ cat ~/bin/glog-grep\n+#!/bin/sh\n+test -n \"$1\" && git log --grep \"$1\" --pretty=reference -1 || true\n+$ git config trailer.ref.key \"Reference-to: \"\n+$ git config trailer.ref.ifExists \"replace\"\n+$ git config trailer.ref.cmd \"~/bin/glog-grep\"\n+$ git interpret-trailers --trailer=\"ref:Add copyright notices.\" <<EOF\n+> subject\n+> \n+> message\n+> \n+> EOF\n+subject\n+\n+message\n+\n+Reference-to: 8bc9a0c769 (Add copyright notices., 2005-04-07)\n+------------\n+\n * Configure a 'see' trailer with a command to show the subject of a\n   commit that is related, and show how it works:\n +\ndiff --git a/t/t7513-interpret-trailers.sh b/t/t7513-interpret-trailers.sh\nindex 6602790b5f4c..68353af3e62e 100755\n--- a/t/t7513-interpret-trailers.sh\n+++ b/t/t7513-interpret-trailers.sh\n@@ -51,6 +51,69 @@ test_expect_success 'setup' '\n \tEOF\n '\n \n+test_expect_success 'with cmd' '\n+\ttest_when_finished \"git config --remove-section trailer.bug\" &&\n+\tgit config trailer.bug.key \"Bug-maker: \" &&\n+\tgit config trailer.bug.ifExists \"add\" &&\n+\tgit config trailer.bug.cmd \"echo \\\"maybe is\\\"\" &&\n+\tcat >expected2 <<-EOF &&\n+\n+\tBug-maker: maybe is\n+\tBug-maker: maybe is him\n+\tBug-maker: maybe is me\n+\tEOF\n+\tgit interpret-trailers --trailer \"bug: him\" --trailer \"bug:me\" \\\n+\t\t>actual2 &&\n+\ttest_cmp expected2 actual2\n+'\n+\n+test_expect_success 'with cmd and $1' '\n+\ttest_when_finished \"git config --remove-section trailer.bug\" &&\n+\tgit config trailer.bug.key \"Bug-maker: \" &&\n+\tgit config trailer.bug.ifExists \"replace\" &&\n+\tgit config trailer.bug.cmd \"echo \\\"\\$1\\\" is\" &&\n+\tcat >expected2 <<-EOF &&\n+\n+\tBug-maker: me is me\n+\tEOF\n+\tgit interpret-trailers --trailer \"bug: him\" --trailer \"bug:me\" \\\n+\t\t>actual2 &&\n+\ttest_cmp expected2 actual2\n+'\n+\n+test_expect_success 'with cmd and $1 with sh -c' '\n+\ttest_when_finished \"git config --remove-section trailer.bug\" &&\n+\tgit config trailer.bug.key \"Bug-maker: \" &&\n+\tgit config trailer.bug.ifExists \"replace\" &&\n+\tgit config trailer.bug.cmd \"sh -c \\\"echo who is \\\"\\$1\\\"\\\"\" &&\n+\tcat >expected2 <<-EOF &&\n+\n+\tBug-maker: who is me\n+\tEOF\n+\tgit interpret-trailers --trailer \"bug: him\" --trailer \"bug:me\" \\\n+\t\t>actual2 &&\n+\ttest_cmp expected2 actual2\n+'\n+\n+test_expect_success 'with cmd and $1 with shell script' '\n+\ttest_when_finished \"git config --remove-section trailer.bug\" &&\n+\tgit config trailer.bug.key \"Bug-maker: \" &&\n+\tgit config trailer.bug.ifExists \"replace\" &&\n+\tgit config trailer.bug.cmd \"./echoscript\" &&\n+\tcat >expected2 <<-EOF &&\n+\n+\tBug-maker: who is me\n+\tEOF\n+\tcat >echoscript <<-EOF &&\n+\t#!/bin/sh\n+\techo who is \"\\$1\"\n+\tEOF\n+\tchmod +x echoscript &&\n+\tgit interpret-trailers --trailer \"bug: him\" --trailer \"bug:me\" \\\n+\t\t>actual2 &&\n+\ttest_cmp expected2 actual2\n+'\n+\n test_expect_success 'without config' '\n \tsed -e \"s/ Z\\$/ /\" >expected <<-\\EOF &&\n \n@@ -1274,6 +1337,27 @@ test_expect_success 'setup a commit' '\n \tgit commit -m \"Add file a.txt\"\n '\n \n+test_expect_success 'cmd takes precedence over command' '\n+\ttest_when_finished \"git config --unset trailer.fix.cmd\" &&\n+\tgit config trailer.fix.ifExists \"replace\" &&\n+\tgit config trailer.fix.cmd \"test -n \\\"\\$1\\\" && git log -1 --oneline --format=\\\"%h (%aN)\\\" \\\n+\t--abbrev-commit --abbrev=14 \\\"\\$1\\\" || true\" &&\n+\tgit config trailer.fix.command \"git log -1 --oneline --format=\\\"%h (%s)\\\" \\\n+\t\t--abbrev-commit --abbrev=14 \\$ARG\" &&\n+\tFIXED=$(git log -1 --oneline --format=\"%h (%aN)\" --abbrev-commit --abbrev=14 HEAD) &&\n+\tcat complex_message_body >expected2 &&\n+\tsed -e \"s/ Z\\$/ /\" >>expected2 <<-EOF &&\n+\t\tFixes: $FIXED\n+\t\tAcked-by= Z\n+\t\tReviewed-by:\n+\t\tSigned-off-by: Z\n+\t\tSigned-off-by: A U Thor <author@example.com>\n+\tEOF\n+\tgit interpret-trailers --trailer \"review:\" --trailer \"fix=HEAD\" \\\n+\t\t<complex_message >actual2 &&\n+\ttest_cmp expected2 actual2\n+'\n+\n test_expect_success 'with command using $ARG' '\n \tgit config trailer.fix.ifExists \"replace\" &&\n \tgit config trailer.fix.command \"git log -1 --oneline --format=\\\"%h (%s)\\\" --abbrev-commit --abbrev=14 \\$ARG\" &&\ndiff --git a/trailer.c b/trailer.c\nindex be4e9726421c..bd384befe15b 100644\n--- a/trailer.c\n+++ b/trailer.c\n@@ -14,6 +14,7 @@ struct conf_info {\n \tchar *name;\n \tchar *key;\n \tchar *command;\n+\tchar *cmd;\n \tenum trailer_where where;\n \tenum trailer_if_exists if_exists;\n \tenum trailer_if_missing if_missing;\n@@ -127,6 +128,7 @@ static void free_arg_item(struct arg_item *item)\n \tfree(item->conf.name);\n \tfree(item->conf.key);\n \tfree(item->conf.command);\n+\tfree(item->conf.cmd);\n \tfree(item->token);\n \tfree(item->value);\n \tfree(item);\n@@ -216,18 +218,24 @@ static int check_if_different(struct trailer_item *in_tok,\n \treturn 1;\n }\n \n-static char *apply_command(const char *command, const char *arg)\n+static char *apply_command(struct conf_info *conf, const char *arg)\n {\n \tstruct strbuf cmd = STRBUF_INIT;\n \tstruct strbuf buf = STRBUF_INIT;\n \tstruct child_process cp = CHILD_PROCESS_INIT;\n \tchar *result;\n \n-\tstrbuf_addstr(&cmd, command);\n-\tif (arg)\n-\t\tstrbuf_replace(&cmd, TRAILER_ARG_STRING, arg);\n-\n-\tstrvec_push(&cp.args, cmd.buf);\n+\tif (conf->cmd) {\n+\t\tstrbuf_addstr(&cmd, conf->cmd);\n+\t\tstrvec_push(&cp.args, cmd.buf);\n+\t\tif (arg)\n+\t\t\tstrvec_push(&cp.args, arg);\n+\t} else if (conf->command) {\n+\t\tstrbuf_addstr(&cmd, conf->command);\n+\t\tif (arg)\n+\t\t\tstrbuf_replace(&cmd, TRAILER_ARG_STRING, arg);\n+\t\tstrvec_push(&cp.args, cmd.buf);\n+\t}\n \tcp.env = local_repo_env;\n \tcp.no_stdin = 1;\n \tcp.use_shell = 1;\n@@ -247,7 +255,7 @@ static char *apply_command(const char *command, const char *arg)\n \n static void apply_item_command(struct trailer_item *in_tok, struct arg_item *arg_tok)\n {\n-\tif (arg_tok->conf.command) {\n+\tif (arg_tok->conf.command || arg_tok->conf.cmd) {\n \t\tconst char *arg;\n \t\tif (arg_tok->value && arg_tok->value[0]) {\n \t\t\targ = arg_tok->value;\n@@ -257,7 +265,7 @@ static void apply_item_command(struct trailer_item *in_tok, struct arg_item *arg\n \t\t\telse\n \t\t\t\targ = xstrdup(\"\");\n \t\t}\n-\t\targ_tok->value = apply_command(arg_tok->conf.command, arg);\n+\t\targ_tok->value = apply_command(&arg_tok->conf, arg);\n \t\tfree((char *)arg);\n \t}\n }\n@@ -430,6 +438,7 @@ static void duplicate_conf(struct conf_info *dst, const struct conf_info *src)\n \tdst->name = xstrdup_or_null(src->name);\n \tdst->key = xstrdup_or_null(src->key);\n \tdst->command = xstrdup_or_null(src->command);\n+\tdst->cmd = xstrdup_or_null(src->cmd);\n }\n \n static struct arg_item *get_conf_item(const char *name)\n@@ -454,8 +463,8 @@ static struct arg_item *get_conf_item(const char *name)\n \treturn item;\n }\n \n-enum trailer_info_type { TRAILER_KEY, TRAILER_COMMAND, TRAILER_WHERE,\n-\t\t\t TRAILER_IF_EXISTS, TRAILER_IF_MISSING };\n+enum trailer_info_type { TRAILER_KEY, TRAILER_COMMAND, TRAILER_CMD,\n+\t\t\tTRAILER_WHERE, TRAILER_IF_EXISTS, TRAILER_IF_MISSING };\n \n static struct {\n \tconst char *name;\n@@ -463,6 +472,7 @@ static struct {\n } trailer_config_items[] = {\n \t{ \"key\", TRAILER_KEY },\n \t{ \"command\", TRAILER_COMMAND },\n+\t{ \"cmd\", TRAILER_CMD },\n \t{ \"where\", TRAILER_WHERE },\n \t{ \"ifexists\", TRAILER_IF_EXISTS },\n \t{ \"ifmissing\", TRAILER_IF_MISSING }\n@@ -542,6 +552,11 @@ static int git_trailer_config(const char *conf_key, const char *value, void *cb)\n \t\t\twarning(_(\"more than one %s\"), conf_key);\n \t\tconf->command = xstrdup(value);\n \t\tbreak;\n+\tcase TRAILER_CMD:\n+\t\tif (conf->cmd)\n+\t\t\twarning(_(\"more than one %s\"), conf_key);\n+\t\tconf->cmd = xstrdup(value);\n+\t\tbreak;\n \tcase TRAILER_WHERE:\n \t\tif (trailer_set_where(&conf->where, value))\n \t\t\twarning(_(\"unknown value '%s' for key '%s'\"), value, conf_key);\n@@ -708,7 +723,7 @@ static void process_command_line_args(struct list_head *arg_head,\n \t/* Add an arg item for each configured trailer with a command */\n \tlist_for_each(pos, &conf_head) {\n \t\titem = list_entry(pos, struct arg_item, list);\n-\t\tif (item->conf.command)\n+\t\tif (item->conf.cmd || item->conf.command)\n \t\t\tadd_arg_item(arg_head,\n \t\t\t\t     xstrdup(token_from_item(item, NULL)),\n \t\t\t\t     xstrdup(\"\"),\n-- \ngitgitgadget\n"},{"id":"421467","messageId":"CAP8UFD0q=7JT1Wsbr6wj9_LEh41ZKbvKsgnE+_3hSM3aMnjwzw@mail.gmail.com","threadId":"55371","inReplyTo":"505903811df83cf26f4dd70c5b811dde169896a2.1617975462.git.gitgitgadget@gmail.com","subject":"Re: [PATCH v8 1/2] [GSOC] docs: correct descript of trailer.<token>.command","fromName":"Christian Couder","fromEmail":"christian.couder@gmail.com","sentAt":"2021-04-09T19:02:14Z","receivedAt":"2021-04-09T19:02:49Z","isPatch":true,"sender":{"key":"christian.couder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/208954?v=4"},"body":"On Fri, Apr 9, 2021 at 3:37 PM ZheNing Hu via GitGitGadget\n<gitgitgadget@gmail.com> wrote:\n>\n> From: ZheNing Hu <adlternative@gmail.com>\n>\n> In the original documentation of `trailer.<token>.command`,\n> some descriptions are easily misunderstood. So let's modify\n> it to increase its readability.\n>\n> In addition, clarify that `$ARG` in command can only be\n> replaced once since `$ARG` is text replacement.\n\nI think you can remove the \"since `$ARG` is text replacement\" part.\nOtherwise this looks fine.\n\n> Signed-off-by: ZheNing Hu <adlternative@gmail.com>\n> ---\n>  Documentation/git-interpret-trailers.txt | 22 +++++++++++-----------\n>  1 file changed, 11 insertions(+), 11 deletions(-)\n>\n> diff --git a/Documentation/git-interpret-trailers.txt b/Documentation/git-interpret-trailers.txt\n> index 96ec6499f001..3e5aa3a65ae9 100644\n> --- a/Documentation/git-interpret-trailers.txt\n> +++ b/Documentation/git-interpret-trailers.txt\n> @@ -237,20 +237,20 @@ trailer.<token>.command::\n>         specified <token>.\n\nThe beginning of the doc for trailer.<token>.command could already be\nimproved. It is:\n\n       This option can be used to specify a shell command that will\n       be called to automatically add or modify a trailer with the\n       specified <token>.\n\nInstead we could say for example:\n\n       This option can be used to specify a shell command that will\n       be called:\n         - once to automatically add a trailer with the specified\n<token>, and then\n         - each time a '--trailer <token>=<value>' argument to modify\nthe <value> of the trailer that this option would produce\n\n>  When this option is specified, the behavior is as if a special\n\nI would rather say:\n\nWhen the specified command is first called to add a trailer with the\nspecified <token>, the behavior is as if a special\n\n> -'<token>=<value>' argument were added at the beginning of the command\n> -line, where <value> is taken to be the standard output of the\n> -specified command with any leading and trailing whitespace trimmed\n> -off.\n> +'--trailer <token>=<value>' argument was added at the beginning of\n> +the \"git interpret-trailers\" command, where <value> is taken to be the\n> +standard output of the specified command with any leading and trailing\n\nHere we can remove \"specified\" as we now use it at the beginning of\nthe sentence.\n\n> +whitespace trimmed off.\n>  +\n> -If the command contains the `$ARG` string, this string will be\n> -replaced with the <value> part of an existing trailer with the same\n> -<token>, if any, before the command is launched.\n> +The first occurrence of substring `$ARG` will be replaced with the\n> +<value> part of an existing trailer with the same <token>, if any,\n> +before the command is launched.\n\nI think we should not talk about `$ARG` at this point, let's remove\nthis and see what we can do below in the next paragraph or after it.\n\n>  If some '<token>=<value>' arguments are also passed on the command\n\ns/'<token>=<value>'/'--trailer <token>=<value>'/\n\n> -line, when a 'trailer.<token>.command' is configured, the command will\n> -also be executed for each of these arguments. And the <value> part of\n> -these arguments, if any, will be used to replace the `$ARG` string in\n> -the command.\n> +line, when a 'trailer.<token>.command' is configured, the command is run\n\ns/when a 'trailer.<token>.command' is configured, //\ns/the command is run/the command is called again/\n\n> +once for each these arguments with the same <token>.\n\ns/each these/each of these/\n\n> And the <value>\n> +part of these arguments, if any, will be used to replace the first `$ARG`\n> +string in the command.\n\ns/first `$ARG` string/first occurrence of substring `$ARG`/\n\nLet's also add something to explain the purpose of this, for example\nwe could add:\n\n\"This way the command can produce a <value> computed from the <value>\npassed in the '--trailer <token>=<value>' argument.\"\n\nAnd then let's also explain what happens when the command is called\nthe first time, with for example:\n\n\"For consistency, the first occurrence of substring `$ARG` is also\nreplaced, this time with the empty string, in the command when the\ncommand is first called to add a trailer with the specified <token>.\"\n\nTo sum up this would give the following (not properly formatted) description:\n\n-------------------\ntrailer.<token>.command::\n       This option can be used to specify a shell command that will be called:\n         - once to automatically add a trailer with the specified\n<token>, and then\n         - each time a '--trailer <token>=<value>' argument to modify\nthe <value> of the trailer that this option would produce\n\n       When the specified command is first called to add a trailer\nwith the specified <token>, the behavior is as if a special\n       '--trailer <token>=<value>' argument was added at the beginning\nof the \"git interpret-trailers\" command, where <value>\n       is taken to be the standard output of the command with any\nleading and trailing whitespace trimmed off.\n\n       If some '--trailer <token>=<value>' arguments are also passed\non the command\n       line, the command is called again once for each of these\narguments with the same <token>.\n       And the <value> part of these arguments, if any, will be used\nto replace the first occurrence\n       of substring `$ARG` in the command. This way the command can\nproduce a <value>\n       computed from the <value> passed in the '--trailer\n<token>=<value>' argument.\n\n       For consistency, the first occurrence of substring `$ARG` is\nalso replaced, this time with the\n       empty string, in the command when the command is first called\nto add a trailer with the\n       specified <token>.\n-------------------\n\nThanks!\n"},{"id":"421471","messageId":"CAP8UFD3kyFe9KdXEu4R1prasnTVAoEW6YOQeA1YDm0k78M3zRQ@mail.gmail.com","threadId":"55371","inReplyTo":"pull.913.v8.git.1617975462.gitgitgadget@gmail.com","subject":"Re: [PATCH v8 0/2] [GSOC] trailer: add new .cmd config option","fromName":"Christian Couder","fromEmail":"christian.couder@gmail.com","sentAt":"2021-04-09T19:59:26Z","receivedAt":"2021-04-09T19:59:44Z","isPatch":true,"sender":{"key":"christian.couder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/208954?v=4"},"body":"On Fri, Apr 9, 2021 at 3:37 PM ZheNing Hu via GitGitGadget\n<gitgitgadget@gmail.com> wrote:\n>\n> In https://lore.kernel.org/git/xmqqv99i4ck2.fsf@gitster.g/ Junio and\n> Christian talked about the problem of using strbuf_replace() to replace\n> $ARG:\n>\n>  1. if the user's script has more than one $ARG, only the first one will be\n>     replaced, which is incorrected.\n\nMaybe: s/is incorrected/can be puzzling/\n\n>  2. $ARG is textually replaced without shell syntax, which may result a\n\nNot sure what \"without shell syntax\" means here.\n\nAlso: s/may result a/may result in a/\n\n>     broken command when $ARG include some unmatching single quote, very\n\ns/when/if/\ns/include/includes/\ns/some unmatching/an unmatched/\n\n>     unsafe.\n>\n> Now pass trailer value as $1 to the trailer command with another\n> trailer.<token>.cmd config, to solve these above two problems,\n\nI think the important thing here is to explain that we want to\nintroduce a new 'trailer.<token>.cmd' config option, so that we can\nstart deprecating 'trailer.<token>.command' when people have started\nusing the new 'trailer.<token>.cmd' config option. Maybe something\nlike:\n\n\"To address these issues, let's introduce a new 'trailer.<token>.cmd'\nconfig option that behaves in the same way as\n'trailer.<token>.command' except that it passes the trailer value as\n$1 to the configured command instead of textually replacing the first\noccurence of '$ARG' in it. This will let us slowly deprecate\n'trailer.<token>.command' in favor of 'trailer.<token>.cmd' in the\nfuture.\"\n\n> We are now writing documents that are more readable and correct than before.\n\nI would suggest removing this sentence as I don't think it adds much\nto the above.\n\n> ZheNing Hu (2):\n>   [GSOC] docs: correct descript of trailer.<token>.command\n\nBy the way the following title might be a bit better and shorter:\n\n\"[GSOC] docs: improve 'trailer.<token>.command' doc\"\n\n>   [GSOC] trailer: add new .cmd config option\n\nMaybe we can afford: s/.cmd/'trailer.<token>.cmd'/\n\n>      -    [GSOC] trailer: add new trailer.<token>.cmd config option\n>      +    [GSOC] trailer: add new .cmd config option\n\nWas the previous title too long? Or is there an issue with\nGitGitGadget because it contains <token>?\n"},{"id":"421475","messageId":"CAP8UFD2A+9eaMGdK9cCfz9Kmz9w-bgiV6x7=X2Y-hmSNAuAV=Q@mail.gmail.com","threadId":"55371","inReplyTo":"3dc8983a47020fb417bb8c6c3d835e609b13c155.1617975462.git.gitgitgadget@gmail.com","subject":"Re: [PATCH v8 2/2] [GSOC] trailer: add new .cmd config option","fromName":"Christian Couder","fromEmail":"christian.couder@gmail.com","sentAt":"2021-04-09T20:18:57Z","receivedAt":"2021-04-09T20:19:13Z","isPatch":true,"sender":{"key":"christian.couder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/208954?v=4"},"body":"On Fri, Apr 9, 2021 at 3:37 PM ZheNing Hu via GitGitGadget\n<gitgitgadget@gmail.com> wrote:\n>\n> From: ZheNing Hu <adlternative@gmail.com>\n>\n> The `trailer.<token>.command` configuration variable\n> specifies a command (run via the shell, so it does not have\n> to be a single name of or path to the command, but can be a\n\ns/of or/or/\n\n> shell script), and the first occurrence of substring $ARG is\n> replaced with the value given to the `interpret-trailer`\n> command for the token.  This has two downsides:\n\nMaybe: s/for the token/for the token in a '--trailer <token>=<value>' argument/\n\n> * The use of $ARG in the mechanism misleads the users that\n> the value is passed in the shell variable, and tempt them\n> to use $ARG more than once, but that would not work, as\n> the second and subsequent $ARG are not replaced.\n>\n> * Because $ARG is textually replaced without regard to the\n> shell language syntax, even '$ARG' (inside a single-quote\n> pair), which a user would expect to stay intact, would be\n> replaced, and worse, if the value had an unmatched single\n> quote (imagine a name like \"O'Connor\", substituted into\n> NAME='$ARG' to make it NAME='O'Connor'), it would result in\n> a broken command that is not syntactically correct (or\n> worse).\n>\n> Introduce a new `trailer.<token>.cmd` configuration that\n> takes higher precedence to deprecate and eventually remove\n> `trailer.<token>.command`, which passes the value as an\n> argument to the command.  Instead of \"$ARG\", users can\n> refer to the value as positional argument, $1, in their\n> scripts.\n\nGood!\n\n> Helped-by: Junio C Hamano <gitster@pobox.com>\n> Helped-by: Christian Couder <christian.couder@gmail.com>\n> Signed-off-by: ZheNing Hu <adlternative@gmail.com>\n> ---\n>  Documentation/git-interpret-trailers.txt | 90 ++++++++++++++++++++----\n>  t/t7513-interpret-trailers.sh            | 84 ++++++++++++++++++++++\n>  trailer.c                                | 37 +++++++---\n>  3 files changed, 187 insertions(+), 24 deletions(-)\n>\n> diff --git a/Documentation/git-interpret-trailers.txt b/Documentation/git-interpret-trailers.txt\n> index 3e5aa3a65ae9..1a874a93f49b 100644\n> --- a/Documentation/git-interpret-trailers.txt\n> +++ b/Documentation/git-interpret-trailers.txt\n> @@ -236,21 +236,38 @@ trailer.<token>.command::\n>         be called to automatically add or modify a trailer with the\n>         specified <token>.\n>  +\n> -When this option is specified, the behavior is as if a special\n> -'--trailer <token>=<value>' argument was added at the beginning of\n> -the \"git interpret-trailers\" command, where <value> is taken to be the\n> -standard output of the specified command with any leading and trailing\n> -whitespace trimmed off.\n> +When this option is specified, the first occurrence of substring $ARG is\n> +replaced with the value given to the `interpret-trailer` command for the\n> +same token. It passes the value through `$ARG`, otherwise this option behaves\n> +in the same way as 'trailer.<token>.cmd'.\n\nActually I think that we should say first that this behaves in the\nsame way as the 'trailer.<token>.cmd'.\n\nAnd this should also replace the first paragraph in the description of\n'trailer.<token>.command', not just the second one.\n\nMaybe:\n\n\"This option behaves in the same way as 'trailer.<token>.cmd', except\nthat it doesn't pass anything as argument to the specified command.\nInstead the first occurrence of substring $ARG is replaced by the\nvalue that would be passed as argument.\"\n\n> +The 'trailer.<token>.command' option has been deprecated due to the fact\n\ns/deprecated/deprecated in favor of 'trailer.<token>.cmd'/\n\n> +that $ARG in the user's command can only be replaced once and that the\n\ns/can only be/is only/\n\n> +original way of replacing $ARG was not safe.\n\ns/was/is/\n\n> Now the preferred option is 'trailer.<token>.cmd', which uses a positional argument to pass the value.\n\nI think we can remove this sentence especially if we say \"deprecated\nin favor of 'trailer.<token>.cmd'\" above.\n\n> -The first occurrence of substring `$ARG` will be replaced with the\n> -<value> part of an existing trailer with the same <token>, if any,\n> -before the command is launched.\n> +When both 'trailer.<token>.cmd' and 'trailer.<token>.command' are given\n> +for the same <token>, 'trailer.<token>.cmd' is used and\n> +'trailer.<token>.command' is ignored.\n\nOk.\n\n> +trailer.<token>.cmd::\n\nI think we should base the description of this option on what I\nsuggest in patch 1/2. So let's agree on patch 1/2 before discussing\nthis.\n"},{"id":"421531","messageId":"CAOLTT8RD_pWRQzP7zYYJrV-S1pdU30AbAVB0guHD-HPcWVkuhg@mail.gmail.com","threadId":"55371","inReplyTo":"CAP8UFD0q=7JT1Wsbr6wj9_LEh41ZKbvKsgnE+_3hSM3aMnjwzw@mail.gmail.com","subject":"Re: [PATCH v8 1/2] [GSOC] docs: correct descript of trailer.<token>.command","fromName":"ZheNing Hu","fromEmail":"adlternative@gmail.com","sentAt":"2021-04-10T13:40:10Z","receivedAt":"2021-04-10T13:40:29Z","isPatch":true,"sender":{"key":"adlternative@gmail.com","avatar":"https://avatars.githubusercontent.com/u/58138461?v=4"},"body":"Christian Couder <christian.couder@gmail.com> 于2021年4月10日周六 上午3:02写道：\n>\n> On Fri, Apr 9, 2021 at 3:37 PM ZheNing Hu via GitGitGadget\n> <gitgitgadget@gmail.com> wrote:\n> >\n> > From: ZheNing Hu <adlternative@gmail.com>\n> >\n> > In the original documentation of `trailer.<token>.command`,\n> > some descriptions are easily misunderstood. So let's modify\n> > it to increase its readability.\n> >\n> > In addition, clarify that `$ARG` in command can only be\n> > replaced once since `$ARG` is text replacement.\n>\n> I think you can remove the \"since `$ARG` is text replacement\" part.\n> Otherwise this looks fine.\n>\n\nOk.\n\n> > Signed-off-by: ZheNing Hu <adlternative@gmail.com>\n> > ---\n> >  Documentation/git-interpret-trailers.txt | 22 +++++++++++-----------\n> >  1 file changed, 11 insertions(+), 11 deletions(-)\n> >\n> > diff --git a/Documentation/git-interpret-trailers.txt b/Documentation/git-interpret-trailers.txt\n> > index 96ec6499f001..3e5aa3a65ae9 100644\n> > --- a/Documentation/git-interpret-trailers.txt\n> > +++ b/Documentation/git-interpret-trailers.txt\n> > @@ -237,20 +237,20 @@ trailer.<token>.command::\n> >         specified <token>.\n>\n> The beginning of the doc for trailer.<token>.command could already be\n> improved. It is:\n>\n>        This option can be used to specify a shell command that will\n>        be called to automatically add or modify a trailer with the\n>        specified <token>.\n>\n> Instead we could say for example:\n>\n>        This option can be used to specify a shell command that will\n>        be called:\n>          - once to automatically add a trailer with the specified\n> <token>, and then\n>          - each time a '--trailer <token>=<value>' argument to modify\n> the <value> of the trailer that this option would produce\n>\n> >  When this option is specified, the behavior is as if a special\n>\n> I would rather say:\n>\n> When the specified command is first called to add a trailer with the\n> specified <token>, the behavior is as if a special\n>\n> > -'<token>=<value>' argument were added at the beginning of the command\n> > -line, where <value> is taken to be the standard output of the\n> > -specified command with any leading and trailing whitespace trimmed\n> > -off.\n> > +'--trailer <token>=<value>' argument was added at the beginning of\n> > +the \"git interpret-trailers\" command, where <value> is taken to be the\n> > +standard output of the specified command with any leading and trailing\n>\n> Here we can remove \"specified\" as we now use it at the beginning of\n> the sentence.\n>\n> > +whitespace trimmed off.\n> >  +\n> > -If the command contains the `$ARG` string, this string will be\n> > -replaced with the <value> part of an existing trailer with the same\n> > -<token>, if any, before the command is launched.\n> > +The first occurrence of substring `$ARG` will be replaced with the\n> > +<value> part of an existing trailer with the same <token>, if any,\n> > +before the command is launched.\n>\n> I think we should not talk about `$ARG` at this point, let's remove\n> this and see what we can do below in the next paragraph or after it.\n>\n\nHere I may want to keep the content similar to the original paragraph.\nBut if you say so, it will be fine.\n\n> >  If some '<token>=<value>' arguments are also passed on the command\n>\n> s/'<token>=<value>'/'--trailer <token>=<value>'/\n>\n> > -line, when a 'trailer.<token>.command' is configured, the command will\n> > -also be executed for each of these arguments. And the <value> part of\n> > -these arguments, if any, will be used to replace the `$ARG` string in\n> > -the command.\n> > +line, when a 'trailer.<token>.command' is configured, the command is run\n>\n> s/when a 'trailer.<token>.command' is configured, //\n> s/the command is run/the command is called again/\n>\n> > +once for each these arguments with the same <token>.\n>\n> s/each these/each of these/\n>\n> > And the <value>\n> > +part of these arguments, if any, will be used to replace the first `$ARG`\n> > +string in the command.\n>\n> s/first `$ARG` string/first occurrence of substring `$ARG`/\n>\n> Let's also add something to explain the purpose of this, for example\n> we could add:\n>\n> \"This way the command can produce a <value> computed from the <value>\n> passed in the '--trailer <token>=<value>' argument.\"\n>\n> And then let's also explain what happens when the command is called\n> the first time, with for example:\n>\n> \"For consistency, the first occurrence of substring `$ARG` is also\n> replaced, this time with the empty string, in the command when the\n> command is first called to add a trailer with the specified <token>.\"\n>\n> To sum up this would give the following (not properly formatted) description:\n>\n> -------------------\n> trailer.<token>.command::\n>        This option can be used to specify a shell command that will be called:\n>          - once to automatically add a trailer with the specified\n> <token>, and then\n>          - each time a '--trailer <token>=<value>' argument to modify\n> the <value> of the trailer that this option would produce\n>\n\nMaybe I am a little confused: this two \"-\" meaning? Shouldn't it be the\nsame paragraph?\n\n-       This option can be used to specify a shell command that will\n-       be called to automatically add or modify a trailer with the\n-       specified <token>.\n+       This option can be used to specify a shell command that will be called:\n+       - once to automatically add a trailer with the specified\n<token>, and then\n+       - each time a '--trailer <token>=<value>' argument to modify\nthe <value> of\n+       the trailer that this option would produce.\n\nWouldn't it be weird?\n\n>        When the specified command is first called to add a trailer\n> with the specified <token>, the behavior is as if a special\n>        '--trailer <token>=<value>' argument was added at the beginning\n> of the \"git interpret-trailers\" command, where <value>\n>        is taken to be the standard output of the command with any\n> leading and trailing whitespace trimmed off.\n>\n>        If some '--trailer <token>=<value>' arguments are also passed\n> on the command\n>        line, the command is called again once for each of these\n> arguments with the same <token>.\n>        And the <value> part of these arguments, if any, will be used\n> to replace the first occurrence\n>        of substring `$ARG` in the command. This way the command can\n> produce a <value>\n>        computed from the <value> passed in the '--trailer\n> <token>=<value>' argument.\n>\n>        For consistency, the first occurrence of substring `$ARG` is\n> also replaced, this time with the\n>        empty string, in the command when the command is first called\n> to add a trailer with the\n>        specified <token>.\n> -------------------\n>\n> Thanks!\n\nThanks a lot!\nIt looks more professional than what I wrote.\n\n--\nZheNing Hu\n"},{"id":"421532","messageId":"CAOLTT8SHu6HmuZYGSz2aboMi8M4Qzf5dZL_k=CUH5Yo-qAu2cw@mail.gmail.com","threadId":"55371","inReplyTo":"CAP8UFD2A+9eaMGdK9cCfz9Kmz9w-bgiV6x7=X2Y-hmSNAuAV=Q@mail.gmail.com","subject":"Re: [PATCH v8 2/2] [GSOC] trailer: add new .cmd config option","fromName":"ZheNing Hu","fromEmail":"adlternative@gmail.com","sentAt":"2021-04-10T14:09:39Z","receivedAt":"2021-04-10T14:09:59Z","isPatch":true,"sender":{"key":"adlternative@gmail.com","avatar":"https://avatars.githubusercontent.com/u/58138461?v=4"},"body":"Christian Couder <christian.couder@gmail.com> 于2021年4月10日周六 上午4:19写道：\n>\n> On Fri, Apr 9, 2021 at 3:37 PM ZheNing Hu via GitGitGadget\n> <gitgitgadget@gmail.com> wrote:\n> >\n> > From: ZheNing Hu <adlternative@gmail.com>\n> >\n> > The `trailer.<token>.command` configuration variable\n> > specifies a command (run via the shell, so it does not have\n> > to be a single name of or path to the command, but can be a\n>\n> s/of or/or/\n>\n> > shell script), and the first occurrence of substring $ARG is\n> > replaced with the value given to the `interpret-trailer`\n> > command for the token.  This has two downsides:\n>\n> Maybe: s/for the token/for the token in a '--trailer <token>=<value>' argument/\n>\n> > * The use of $ARG in the mechanism misleads the users that\n> > the value is passed in the shell variable, and tempt them\n> > to use $ARG more than once, but that would not work, as\n> > the second and subsequent $ARG are not replaced.\n> >\n> > * Because $ARG is textually replaced without regard to the\n> > shell language syntax, even '$ARG' (inside a single-quote\n> > pair), which a user would expect to stay intact, would be\n> > replaced, and worse, if the value had an unmatched single\n> > quote (imagine a name like \"O'Connor\", substituted into\n> > NAME='$ARG' to make it NAME='O'Connor'), it would result in\n> > a broken command that is not syntactically correct (or\n> > worse).\n> >\n> > Introduce a new `trailer.<token>.cmd` configuration that\n> > takes higher precedence to deprecate and eventually remove\n> > `trailer.<token>.command`, which passes the value as an\n> > argument to the command.  Instead of \"$ARG\", users can\n> > refer to the value as positional argument, $1, in their\n> > scripts.\n>\n> Good!\n>\n> > Helped-by: Junio C Hamano <gitster@pobox.com>\n> > Helped-by: Christian Couder <christian.couder@gmail.com>\n> > Signed-off-by: ZheNing Hu <adlternative@gmail.com>\n> > ---\n> >  Documentation/git-interpret-trailers.txt | 90 ++++++++++++++++++++----\n> >  t/t7513-interpret-trailers.sh            | 84 ++++++++++++++++++++++\n> >  trailer.c                                | 37 +++++++---\n> >  3 files changed, 187 insertions(+), 24 deletions(-)\n> >\n> > diff --git a/Documentation/git-interpret-trailers.txt b/Documentation/git-interpret-trailers.txt\n> > index 3e5aa3a65ae9..1a874a93f49b 100644\n> > --- a/Documentation/git-interpret-trailers.txt\n> > +++ b/Documentation/git-interpret-trailers.txt\n> > @@ -236,21 +236,38 @@ trailer.<token>.command::\n> >         be called to automatically add or modify a trailer with the\n> >         specified <token>.\n> >  +\n> > -When this option is specified, the behavior is as if a special\n> > -'--trailer <token>=<value>' argument was added at the beginning of\n> > -the \"git interpret-trailers\" command, where <value> is taken to be the\n> > -standard output of the specified command with any leading and trailing\n> > -whitespace trimmed off.\n> > +When this option is specified, the first occurrence of substring $ARG is\n> > +replaced with the value given to the `interpret-trailer` command for the\n> > +same token. It passes the value through `$ARG`, otherwise this option behaves\n> > +in the same way as 'trailer.<token>.cmd'.\n>\n> Actually I think that we should say first that this behaves in the\n> same way as the 'trailer.<token>.cmd'.\n>\n> And this should also replace the first paragraph in the description of\n> 'trailer.<token>.command', not just the second one.\n>\n\nMake sence. After all, the primary purpose of this patch is to show that\n\".command\" has been deprecated.\n\n> Maybe:\n>\n> \"This option behaves in the same way as 'trailer.<token>.cmd', except\n> that it doesn't pass anything as argument to the specified command.\n> Instead the first occurrence of substring $ARG is replaced by the\n> value that would be passed as argument.\"\n>\n> > +The 'trailer.<token>.command' option has been deprecated due to the fact\n>\n> s/deprecated/deprecated in favor of 'trailer.<token>.cmd'/\n>\n> > +that $ARG in the user's command can only be replaced once and that the\n>\n> s/can only be/is only/\n>\n> > +original way of replacing $ARG was not safe.\n>\n> s/was/is/\n>\n> > Now the preferred option is 'trailer.<token>.cmd', which uses a positional argument to pass the value.\n>\n> I think we can remove this sentence especially if we say \"deprecated\n> in favor of 'trailer.<token>.cmd'\" above.\n>\n\nI agree.\n\n> > -The first occurrence of substring `$ARG` will be replaced with the\n> > -<value> part of an existing trailer with the same <token>, if any,\n> > -before the command is launched.\n> > +When both 'trailer.<token>.cmd' and 'trailer.<token>.command' are given\n> > +for the same <token>, 'trailer.<token>.cmd' is used and\n> > +'trailer.<token>.command' is ignored.\n>\n> Ok.\n>\n> > +trailer.<token>.cmd::\n>\n> I think we should base the description of this option on what I\n> suggest in patch 1/2. So let's agree on patch 1/2 before discussing\n> this.\n\nYes, The description of the new .cmd is best approximated by\nthe .command in 1/2 .\n\nI should @Junio, I don't know if he has any other opinions.\n\n--\nZheNing Hu\n"},{"id":"421705","messageId":"pull.913.v9.git.1618245568.gitgitgadget@gmail.com","threadId":"55371","inReplyTo":"pull.913.v8.git.1617975462.gitgitgadget@gmail.com","subject":"[PATCH v9 0/2] [GSOC] trailer: add new .cmd config option","fromName":"ZheNing Hu via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2021-04-12T16:39:25Z","receivedAt":"2021-04-12T16:52:28Z","isPatch":true,"sender":{"key":"adlternative@gmail.com","avatar":"https://avatars.githubusercontent.com/u/58138461?v=4"},"body":"In https://lore.kernel.org/git/xmqqv99i4ck2.fsf@gitster.g/ Junio and\nChristian talked about the problem of using strbuf_replace() to replace\n$ARG:\n\n 1. if the user's script has more than one $ARG, only the first one will be\n    replaced, which is incorrected.\n 2. $ARG is textually replaced without shell syntax, which may result a\n    broken command when $ARG include some unmatching single quote, very\n    unsafe.\n\nNow pass trailer value as $1 to the trailer command with another\ntrailer.<token>.cmd config, to solve these above two problems,\n\nWe are now writing documents that are more readable and correct than before.\n\nZheNing Hu (2):\n  [GSOC] docs: correct descript of trailer.<token>.command\n  [GSOC] trailer: add new .cmd config option\n\n Documentation/git-interpret-trailers.txt | 96 ++++++++++++++++++++----\n t/t7513-interpret-trailers.sh            | 84 +++++++++++++++++++++\n trailer.c                                | 37 ++++++---\n 3 files changed, 190 insertions(+), 27 deletions(-)\n\n\nbase-commit: 142430338477d9d1bb25be66267225fb58498d92\nPublished-As: https://github.com/gitgitgadget/git/releases/tag/pr-913%2Fadlternative%2Ftrailer-pass-ARG-env-v9\nFetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-913/adlternative/trailer-pass-ARG-env-v9\nPull-Request: https://github.com/gitgitgadget/git/pull/913\n\nRange-diff vs v8:\n\n 1:  505903811df8 ! 1:  8129ef6c476b [GSOC] docs: correct descript of trailer.<token>.command\n     @@ Commit message\n          it to increase its readability.\n      \n          In addition, clarify that `$ARG` in command can only be\n     -    replaced once since `$ARG` is text replacement.\n     +    replaced once.\n      \n          Signed-off-by: ZheNing Hu <adlternative@gmail.com>\n      \n       ## Documentation/git-interpret-trailers.txt ##\n     -@@ Documentation/git-interpret-trailers.txt: trailer.<token>.command::\n     - \tspecified <token>.\n     +@@ Documentation/git-interpret-trailers.txt: trailer.<token>.ifmissing::\n     + \tthat option for trailers with the specified <token>.\n     + \n     + trailer.<token>.command::\n     +-\tThis option can be used to specify a shell command that will\n     +-\tbe called to automatically add or modify a trailer with the\n     +-\tspecified <token>.\n     ++\tThis option can be used to specify a shell command that will be called:\n     ++\tonce to automatically add a trailer with the specified <token>, and then\n     ++\teach time a '--trailer <token>=<value>' argument to modify the <value> of\n     ++\tthe trailer that this option would produce.\n       +\n     - When this option is specified, the behavior is as if a special\n     +-When this option is specified, the behavior is as if a special\n      -'<token>=<value>' argument were added at the beginning of the command\n      -line, where <value> is taken to be the standard output of the\n      -specified command with any leading and trailing whitespace trimmed\n      -off.\n     -+'--trailer <token>=<value>' argument was added at the beginning of\n     -+the \"git interpret-trailers\" command, where <value> is taken to be the\n     -+standard output of the specified command with any leading and trailing\n     -+whitespace trimmed off.\n     ++When the specified command is first called to add a trailer\n     ++with the specified <token>, the behavior is as if a special\n     ++'--trailer <token>=<value>' argument was added at the beginning\n     ++of the \"git interpret-trailers\" command, where <value>\n     ++is taken to be the standard output of the command with any\n     ++leading and trailing whitespace trimmed off.\n       +\n      -If the command contains the `$ARG` string, this string will be\n      -replaced with the <value> part of an existing trailer with the same\n      -<token>, if any, before the command is launched.\n     -+The first occurrence of substring `$ARG` will be replaced with the\n     -+<value> part of an existing trailer with the same <token>, if any,\n     -+before the command is launched.\n     ++If some '--trailer <token>=<value>' arguments are also passed\n     ++on the command line, the command is called again once for each\n     ++of these arguments with the same <token>. And the <value> part\n     ++of these arguments, if any, will be used to replace the first\n     ++occurrence of substring `$ARG` in the command. This way the\n     ++command can produce a <value> computed from the <value> passed\n     ++in the '--trailer <token>=<value>' argument.\n       +\n     - If some '<token>=<value>' arguments are also passed on the command\n     +-If some '<token>=<value>' arguments are also passed on the command\n      -line, when a 'trailer.<token>.command' is configured, the command will\n      -also be executed for each of these arguments. And the <value> part of\n      -these arguments, if any, will be used to replace the `$ARG` string in\n      -the command.\n     -+line, when a 'trailer.<token>.command' is configured, the command is run\n     -+once for each these arguments with the same <token>. And the <value>\n     -+part of these arguments, if any, will be used to replace the first `$ARG`\n     -+string in the command.\n     ++For consistency, the first occurrence of substring `$ARG` is\n     ++also replaced, this time with the empty string, in the command\n     ++when the command is first called to add a trailer with the\n     ++specified <token>.\n       \n       EXAMPLES\n       --------\n 2:  3dc8983a4702 ! 2:  7f645ec95f48 [GSOC] trailer: add new .cmd config option\n     @@ Commit message\n      \n          The `trailer.<token>.command` configuration variable\n          specifies a command (run via the shell, so it does not have\n     -    to be a single name of or path to the command, but can be a\n     +    to be a single name or path to the command, but can be a\n          shell script), and the first occurrence of substring $ARG is\n          replaced with the value given to the `interpret-trailer`\n     -    command for the token.  This has two downsides:\n     +    command for the token in a '--trailer <token>=<value>' argument.\n     +\n     +    This has two downsides:\n      \n          * The use of $ARG in the mechanism misleads the users that\n          the value is passed in the shell variable, and tempt them\n     @@ Commit message\n          Signed-off-by: ZheNing Hu <adlternative@gmail.com>\n      \n       ## Documentation/git-interpret-trailers.txt ##\n     -@@ Documentation/git-interpret-trailers.txt: trailer.<token>.command::\n     - \tbe called to automatically add or modify a trailer with the\n     - \tspecified <token>.\n     - +\n     --When this option is specified, the behavior is as if a special\n     --'--trailer <token>=<value>' argument was added at the beginning of\n     --the \"git interpret-trailers\" command, where <value> is taken to be the\n     --standard output of the specified command with any leading and trailing\n     --whitespace trimmed off.\n     -+When this option is specified, the first occurrence of substring $ARG is\n     -+replaced with the value given to the `interpret-trailer` command for the\n     -+same token. It passes the value through `$ARG`, otherwise this option behaves\n     -+in the same way as 'trailer.<token>.cmd'.\n     +@@ Documentation/git-interpret-trailers.txt: trailer.<token>.ifmissing::\n     + \tthat option for trailers with the specified <token>.\n     + \n     + trailer.<token>.command::\n     ++\tThis option behaves in the same way as 'trailer.<token>.cmd', except\n     ++\tthat it doesn't pass anything as argument to the specified command.\n     ++\tInstead the first occurrence of substring $ARG is replaced by the\n     ++\tvalue that would be passed as argument.\n     +++\n     ++The 'trailer.<token>.command' option has been deprecated in favor of\n     ++'trailer.<token>.cmd' due to the fact that $ARG in the user's command is\n     ++only replaced once and that the original way of replacing $ARG is not safe.\n      ++\n     -+The 'trailer.<token>.command' option has been deprecated due to the fact\n     -+that $ARG in the user's command can only be replaced once and that the\n     -+original way of replacing $ARG was not safe. Now the preferred option is\n     -+'trailer.<token>.cmd', which uses a positional argument to pass the value.\n     - +\n     --The first occurrence of substring `$ARG` will be replaced with the\n     --<value> part of an existing trailer with the same <token>, if any,\n     --before the command is launched.\n      +When both 'trailer.<token>.cmd' and 'trailer.<token>.command' are given\n      +for the same <token>, 'trailer.<token>.cmd' is used and\n      +'trailer.<token>.command' is ignored.\n      +\n      +trailer.<token>.cmd::\n     -+\tThe command specified by this configuration variable is run\n     -+\twith a single argument, which is the <value> part of a\n     -+\t`--trailer <token>=<value>` on the command line. The output\n     -+\tfrom the command is then used as the value for the <token>\n     -+\tin the resulting trailer.\n     -++\n     -+When this option is specified, the behavior is as if a\n     -+'--trailer <token>=<value>' argument was added at the beginning of\n     -+the \"git interpret-trailers\" command, the command specified by this\n     -+configuration variable will be called with an empty string as the\n     -+argument.\n     + \tThis option can be used to specify a shell command that will be called:\n     + \tonce to automatically add a trailer with the specified <token>, and then\n     + \teach time a '--trailer <token>=<value>' argument to modify the <value> of\n     +@@ Documentation/git-interpret-trailers.txt: leading and trailing whitespace trimmed off.\n     + If some '--trailer <token>=<value>' arguments are also passed\n     + on the command line, the command is called again once for each\n     + of these arguments with the same <token>. And the <value> part\n     +-of these arguments, if any, will be used to replace the first\n     +-occurrence of substring `$ARG` in the command. This way the\n     +-command can produce a <value> computed from the <value> passed\n     +-in the '--trailer <token>=<value>' argument.\n     ++of these arguments, if any, will be passed to the command as its\n     ++first argument. This way the command can produce a <value> computed\n     ++from the <value> passed in the '--trailer <token>=<value>' argument.\n       +\n     --If some '<token>=<value>' arguments are also passed on the command\n     --line, when a 'trailer.<token>.command' is configured, the command is run\n     --once for each these arguments with the same <token>. And the <value>\n     --part of these arguments, if any, will be used to replace the first `$ARG`\n     --string in the command.\n     -+If some '--trailer <token>=<value>' arguments are also passed on the\n     -+command line, when a 'trailer.<token>.cmd' is configured, the command\n     -+is run once for each `--trailer <token>=<value>` on the command line\n     -+with the same <token>. And the <value> part of these arguments, if any,\n     -+will be passed to the command as its first argument.\n     +-For consistency, the first occurrence of substring `$ARG` is\n     +-also replaced, this time with the empty string, in the command\n     +-when the command is first called to add a trailer with the\n     +-specified <token>.\n     ++For consistency, the $1 is also passed, this time with the empty string,\n     ++in the command when the command is first called to add a trailer with\n     ++the specified <token>.\n       \n       EXAMPLES\n       --------\n\n-- \ngitgitgadget\n"},{"id":"421706","messageId":"8129ef6c476b4f35be59eae71367de5b83888068.1618245568.git.gitgitgadget@gmail.com","threadId":"55371","inReplyTo":"pull.913.v9.git.1618245568.gitgitgadget@gmail.com","subject":"[PATCH v9 1/2] [GSOC] docs: correct descript of trailer.<token>.command","fromName":"ZheNing Hu via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2021-04-12T16:39:26Z","receivedAt":"2021-04-12T16:52:49Z","isPatch":true,"sender":{"key":"adlternative@gmail.com","avatar":"https://avatars.githubusercontent.com/u/58138461?v=4"},"body":"From: ZheNing Hu <adlternative@gmail.com>\n\nIn the original documentation of `trailer.<token>.command`,\nsome descriptions are easily misunderstood. So let's modify\nit to increase its readability.\n\nIn addition, clarify that `$ARG` in command can only be\nreplaced once.\n\nSigned-off-by: ZheNing Hu <adlternative@gmail.com>\n---\n Documentation/git-interpret-trailers.txt | 37 ++++++++++++++----------\n 1 file changed, 21 insertions(+), 16 deletions(-)\n\ndiff --git a/Documentation/git-interpret-trailers.txt b/Documentation/git-interpret-trailers.txt\nindex 96ec6499f001..6f2a7a130464 100644\n--- a/Documentation/git-interpret-trailers.txt\n+++ b/Documentation/git-interpret-trailers.txt\n@@ -232,25 +232,30 @@ trailer.<token>.ifmissing::\n \tthat option for trailers with the specified <token>.\n \n trailer.<token>.command::\n-\tThis option can be used to specify a shell command that will\n-\tbe called to automatically add or modify a trailer with the\n-\tspecified <token>.\n+\tThis option can be used to specify a shell command that will be called:\n+\tonce to automatically add a trailer with the specified <token>, and then\n+\teach time a '--trailer <token>=<value>' argument to modify the <value> of\n+\tthe trailer that this option would produce.\n +\n-When this option is specified, the behavior is as if a special\n-'<token>=<value>' argument were added at the beginning of the command\n-line, where <value> is taken to be the standard output of the\n-specified command with any leading and trailing whitespace trimmed\n-off.\n+When the specified command is first called to add a trailer\n+with the specified <token>, the behavior is as if a special\n+'--trailer <token>=<value>' argument was added at the beginning\n+of the \"git interpret-trailers\" command, where <value>\n+is taken to be the standard output of the command with any\n+leading and trailing whitespace trimmed off.\n +\n-If the command contains the `$ARG` string, this string will be\n-replaced with the <value> part of an existing trailer with the same\n-<token>, if any, before the command is launched.\n+If some '--trailer <token>=<value>' arguments are also passed\n+on the command line, the command is called again once for each\n+of these arguments with the same <token>. And the <value> part\n+of these arguments, if any, will be used to replace the first\n+occurrence of substring `$ARG` in the command. This way the\n+command can produce a <value> computed from the <value> passed\n+in the '--trailer <token>=<value>' argument.\n +\n-If some '<token>=<value>' arguments are also passed on the command\n-line, when a 'trailer.<token>.command' is configured, the command will\n-also be executed for each of these arguments. And the <value> part of\n-these arguments, if any, will be used to replace the `$ARG` string in\n-the command.\n+For consistency, the first occurrence of substring `$ARG` is\n+also replaced, this time with the empty string, in the command\n+when the command is first called to add a trailer with the\n+specified <token>.\n \n EXAMPLES\n --------\n-- \ngitgitgadget\n\n"},{"id":"421707","messageId":"7f645ec95f48a206311973ee45578ba14ac58b7f.1618245568.git.gitgitgadget@gmail.com","threadId":"55371","inReplyTo":"pull.913.v9.git.1618245568.gitgitgadget@gmail.com","subject":"[PATCH v9 2/2] [GSOC] trailer: add new .cmd config option","fromName":"ZheNing Hu via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2021-04-12T16:39:27Z","receivedAt":"2021-04-12T16:52:49Z","isPatch":true,"sender":{"key":"adlternative@gmail.com","avatar":"https://avatars.githubusercontent.com/u/58138461?v=4"},"body":"From: ZheNing Hu <adlternative@gmail.com>\n\nThe `trailer.<token>.command` configuration variable\nspecifies a command (run via the shell, so it does not have\nto be a single name or path to the command, but can be a\nshell script), and the first occurrence of substring $ARG is\nreplaced with the value given to the `interpret-trailer`\ncommand for the token in a '--trailer <token>=<value>' argument.\n\nThis has two downsides:\n\n* The use of $ARG in the mechanism misleads the users that\nthe value is passed in the shell variable, and tempt them\nto use $ARG more than once, but that would not work, as\nthe second and subsequent $ARG are not replaced.\n\n* Because $ARG is textually replaced without regard to the\nshell language syntax, even '$ARG' (inside a single-quote\npair), which a user would expect to stay intact, would be\nreplaced, and worse, if the value had an unmatched single\nquote (imagine a name like \"O'Connor\", substituted into\nNAME='$ARG' to make it NAME='O'Connor'), it would result in\na broken command that is not syntactically correct (or\nworse).\n\nIntroduce a new `trailer.<token>.cmd` configuration that\ntakes higher precedence to deprecate and eventually remove\n`trailer.<token>.command`, which passes the value as an\nargument to the command.  Instead of \"$ARG\", users can\nrefer to the value as positional argument, $1, in their\nscripts.\n\nHelped-by: Junio C Hamano <gitster@pobox.com>\nHelped-by: Christian Couder <christian.couder@gmail.com>\nSigned-off-by: ZheNing Hu <adlternative@gmail.com>\n---\n Documentation/git-interpret-trailers.txt | 75 ++++++++++++++++++---\n t/t7513-interpret-trailers.sh            | 84 ++++++++++++++++++++++++\n trailer.c                                | 37 +++++++----\n 3 files changed, 177 insertions(+), 19 deletions(-)\n\ndiff --git a/Documentation/git-interpret-trailers.txt b/Documentation/git-interpret-trailers.txt\nindex 6f2a7a130464..dc1f69fa8b67 100644\n--- a/Documentation/git-interpret-trailers.txt\n+++ b/Documentation/git-interpret-trailers.txt\n@@ -232,6 +232,20 @@ trailer.<token>.ifmissing::\n \tthat option for trailers with the specified <token>.\n \n trailer.<token>.command::\n+\tThis option behaves in the same way as 'trailer.<token>.cmd', except\n+\tthat it doesn't pass anything as argument to the specified command.\n+\tInstead the first occurrence of substring $ARG is replaced by the\n+\tvalue that would be passed as argument.\n++\n+The 'trailer.<token>.command' option has been deprecated in favor of\n+'trailer.<token>.cmd' due to the fact that $ARG in the user's command is\n+only replaced once and that the original way of replacing $ARG is not safe.\n++\n+When both 'trailer.<token>.cmd' and 'trailer.<token>.command' are given\n+for the same <token>, 'trailer.<token>.cmd' is used and\n+'trailer.<token>.command' is ignored.\n+\n+trailer.<token>.cmd::\n \tThis option can be used to specify a shell command that will be called:\n \tonce to automatically add a trailer with the specified <token>, and then\n \teach time a '--trailer <token>=<value>' argument to modify the <value> of\n@@ -247,15 +261,13 @@ leading and trailing whitespace trimmed off.\n If some '--trailer <token>=<value>' arguments are also passed\n on the command line, the command is called again once for each\n of these arguments with the same <token>. And the <value> part\n-of these arguments, if any, will be used to replace the first\n-occurrence of substring `$ARG` in the command. This way the\n-command can produce a <value> computed from the <value> passed\n-in the '--trailer <token>=<value>' argument.\n+of these arguments, if any, will be passed to the command as its\n+first argument. This way the command can produce a <value> computed\n+from the <value> passed in the '--trailer <token>=<value>' argument.\n +\n-For consistency, the first occurrence of substring `$ARG` is\n-also replaced, this time with the empty string, in the command\n-when the command is first called to add a trailer with the\n-specified <token>.\n+For consistency, the $1 is also passed, this time with the empty string,\n+in the command when the command is first called to add a trailer with\n+the specified <token>.\n \n EXAMPLES\n --------\n@@ -338,6 +350,53 @@ subject\n Fix #42\n ------------\n \n+* Configure a 'cnt' trailer with a cmd use a global script `gcount`\n+to record commit counts of a specified author and show how it works:\n++\n+------------\n+$ cat ~/bin/gcount\n+#!/bin/sh\n+test -n \"$1\" && git shortlog -s --author=\"$1\" HEAD || true\n+$ git config trailer.cnt.key \"Commit-count: \"\n+$ git config trailer.cnt.ifExists \"replace\"\n+$ git config trailer.cnt.cmd \"~/bin/gcount\"\n+$ git interpret-trailers --trailer=\"cnt:Junio\" <<EOF\n+> subject\n+> \n+> message\n+> \n+> EOF\n+subject\n+\n+message\n+\n+Commit-count: 22484     Junio C Hamano\n+------------\n+\n+* Configure a 'ref' trailer with a cmd use a global script `glog-grep`\n+  to grep last relevant commit from git log in the git repository\n+  and show how it works:\n++\n+------------\n+$ cat ~/bin/glog-grep\n+#!/bin/sh\n+test -n \"$1\" && git log --grep \"$1\" --pretty=reference -1 || true\n+$ git config trailer.ref.key \"Reference-to: \"\n+$ git config trailer.ref.ifExists \"replace\"\n+$ git config trailer.ref.cmd \"~/bin/glog-grep\"\n+$ git interpret-trailers --trailer=\"ref:Add copyright notices.\" <<EOF\n+> subject\n+> \n+> message\n+> \n+> EOF\n+subject\n+\n+message\n+\n+Reference-to: 8bc9a0c769 (Add copyright notices., 2005-04-07)\n+------------\n+\n * Configure a 'see' trailer with a command to show the subject of a\n   commit that is related, and show how it works:\n +\ndiff --git a/t/t7513-interpret-trailers.sh b/t/t7513-interpret-trailers.sh\nindex 6602790b5f4c..68353af3e62e 100755\n--- a/t/t7513-interpret-trailers.sh\n+++ b/t/t7513-interpret-trailers.sh\n@@ -51,6 +51,69 @@ test_expect_success 'setup' '\n \tEOF\n '\n \n+test_expect_success 'with cmd' '\n+\ttest_when_finished \"git config --remove-section trailer.bug\" &&\n+\tgit config trailer.bug.key \"Bug-maker: \" &&\n+\tgit config trailer.bug.ifExists \"add\" &&\n+\tgit config trailer.bug.cmd \"echo \\\"maybe is\\\"\" &&\n+\tcat >expected2 <<-EOF &&\n+\n+\tBug-maker: maybe is\n+\tBug-maker: maybe is him\n+\tBug-maker: maybe is me\n+\tEOF\n+\tgit interpret-trailers --trailer \"bug: him\" --trailer \"bug:me\" \\\n+\t\t>actual2 &&\n+\ttest_cmp expected2 actual2\n+'\n+\n+test_expect_success 'with cmd and $1' '\n+\ttest_when_finished \"git config --remove-section trailer.bug\" &&\n+\tgit config trailer.bug.key \"Bug-maker: \" &&\n+\tgit config trailer.bug.ifExists \"replace\" &&\n+\tgit config trailer.bug.cmd \"echo \\\"\\$1\\\" is\" &&\n+\tcat >expected2 <<-EOF &&\n+\n+\tBug-maker: me is me\n+\tEOF\n+\tgit interpret-trailers --trailer \"bug: him\" --trailer \"bug:me\" \\\n+\t\t>actual2 &&\n+\ttest_cmp expected2 actual2\n+'\n+\n+test_expect_success 'with cmd and $1 with sh -c' '\n+\ttest_when_finished \"git config --remove-section trailer.bug\" &&\n+\tgit config trailer.bug.key \"Bug-maker: \" &&\n+\tgit config trailer.bug.ifExists \"replace\" &&\n+\tgit config trailer.bug.cmd \"sh -c \\\"echo who is \\\"\\$1\\\"\\\"\" &&\n+\tcat >expected2 <<-EOF &&\n+\n+\tBug-maker: who is me\n+\tEOF\n+\tgit interpret-trailers --trailer \"bug: him\" --trailer \"bug:me\" \\\n+\t\t>actual2 &&\n+\ttest_cmp expected2 actual2\n+'\n+\n+test_expect_success 'with cmd and $1 with shell script' '\n+\ttest_when_finished \"git config --remove-section trailer.bug\" &&\n+\tgit config trailer.bug.key \"Bug-maker: \" &&\n+\tgit config trailer.bug.ifExists \"replace\" &&\n+\tgit config trailer.bug.cmd \"./echoscript\" &&\n+\tcat >expected2 <<-EOF &&\n+\n+\tBug-maker: who is me\n+\tEOF\n+\tcat >echoscript <<-EOF &&\n+\t#!/bin/sh\n+\techo who is \"\\$1\"\n+\tEOF\n+\tchmod +x echoscript &&\n+\tgit interpret-trailers --trailer \"bug: him\" --trailer \"bug:me\" \\\n+\t\t>actual2 &&\n+\ttest_cmp expected2 actual2\n+'\n+\n test_expect_success 'without config' '\n \tsed -e \"s/ Z\\$/ /\" >expected <<-\\EOF &&\n \n@@ -1274,6 +1337,27 @@ test_expect_success 'setup a commit' '\n \tgit commit -m \"Add file a.txt\"\n '\n \n+test_expect_success 'cmd takes precedence over command' '\n+\ttest_when_finished \"git config --unset trailer.fix.cmd\" &&\n+\tgit config trailer.fix.ifExists \"replace\" &&\n+\tgit config trailer.fix.cmd \"test -n \\\"\\$1\\\" && git log -1 --oneline --format=\\\"%h (%aN)\\\" \\\n+\t--abbrev-commit --abbrev=14 \\\"\\$1\\\" || true\" &&\n+\tgit config trailer.fix.command \"git log -1 --oneline --format=\\\"%h (%s)\\\" \\\n+\t\t--abbrev-commit --abbrev=14 \\$ARG\" &&\n+\tFIXED=$(git log -1 --oneline --format=\"%h (%aN)\" --abbrev-commit --abbrev=14 HEAD) &&\n+\tcat complex_message_body >expected2 &&\n+\tsed -e \"s/ Z\\$/ /\" >>expected2 <<-EOF &&\n+\t\tFixes: $FIXED\n+\t\tAcked-by= Z\n+\t\tReviewed-by:\n+\t\tSigned-off-by: Z\n+\t\tSigned-off-by: A U Thor <author@example.com>\n+\tEOF\n+\tgit interpret-trailers --trailer \"review:\" --trailer \"fix=HEAD\" \\\n+\t\t<complex_message >actual2 &&\n+\ttest_cmp expected2 actual2\n+'\n+\n test_expect_success 'with command using $ARG' '\n \tgit config trailer.fix.ifExists \"replace\" &&\n \tgit config trailer.fix.command \"git log -1 --oneline --format=\\\"%h (%s)\\\" --abbrev-commit --abbrev=14 \\$ARG\" &&\ndiff --git a/trailer.c b/trailer.c\nindex be4e9726421c..bd384befe15b 100644\n--- a/trailer.c\n+++ b/trailer.c\n@@ -14,6 +14,7 @@ struct conf_info {\n \tchar *name;\n \tchar *key;\n \tchar *command;\n+\tchar *cmd;\n \tenum trailer_where where;\n \tenum trailer_if_exists if_exists;\n \tenum trailer_if_missing if_missing;\n@@ -127,6 +128,7 @@ static void free_arg_item(struct arg_item *item)\n \tfree(item->conf.name);\n \tfree(item->conf.key);\n \tfree(item->conf.command);\n+\tfree(item->conf.cmd);\n \tfree(item->token);\n \tfree(item->value);\n \tfree(item);\n@@ -216,18 +218,24 @@ static int check_if_different(struct trailer_item *in_tok,\n \treturn 1;\n }\n \n-static char *apply_command(const char *command, const char *arg)\n+static char *apply_command(struct conf_info *conf, const char *arg)\n {\n \tstruct strbuf cmd = STRBUF_INIT;\n \tstruct strbuf buf = STRBUF_INIT;\n \tstruct child_process cp = CHILD_PROCESS_INIT;\n \tchar *result;\n \n-\tstrbuf_addstr(&cmd, command);\n-\tif (arg)\n-\t\tstrbuf_replace(&cmd, TRAILER_ARG_STRING, arg);\n-\n-\tstrvec_push(&cp.args, cmd.buf);\n+\tif (conf->cmd) {\n+\t\tstrbuf_addstr(&cmd, conf->cmd);\n+\t\tstrvec_push(&cp.args, cmd.buf);\n+\t\tif (arg)\n+\t\t\tstrvec_push(&cp.args, arg);\n+\t} else if (conf->command) {\n+\t\tstrbuf_addstr(&cmd, conf->command);\n+\t\tif (arg)\n+\t\t\tstrbuf_replace(&cmd, TRAILER_ARG_STRING, arg);\n+\t\tstrvec_push(&cp.args, cmd.buf);\n+\t}\n \tcp.env = local_repo_env;\n \tcp.no_stdin = 1;\n \tcp.use_shell = 1;\n@@ -247,7 +255,7 @@ static char *apply_command(const char *command, const char *arg)\n \n static void apply_item_command(struct trailer_item *in_tok, struct arg_item *arg_tok)\n {\n-\tif (arg_tok->conf.command) {\n+\tif (arg_tok->conf.command || arg_tok->conf.cmd) {\n \t\tconst char *arg;\n \t\tif (arg_tok->value && arg_tok->value[0]) {\n \t\t\targ = arg_tok->value;\n@@ -257,7 +265,7 @@ static void apply_item_command(struct trailer_item *in_tok, struct arg_item *arg\n \t\t\telse\n \t\t\t\targ = xstrdup(\"\");\n \t\t}\n-\t\targ_tok->value = apply_command(arg_tok->conf.command, arg);\n+\t\targ_tok->value = apply_command(&arg_tok->conf, arg);\n \t\tfree((char *)arg);\n \t}\n }\n@@ -430,6 +438,7 @@ static void duplicate_conf(struct conf_info *dst, const struct conf_info *src)\n \tdst->name = xstrdup_or_null(src->name);\n \tdst->key = xstrdup_or_null(src->key);\n \tdst->command = xstrdup_or_null(src->command);\n+\tdst->cmd = xstrdup_or_null(src->cmd);\n }\n \n static struct arg_item *get_conf_item(const char *name)\n@@ -454,8 +463,8 @@ static struct arg_item *get_conf_item(const char *name)\n \treturn item;\n }\n \n-enum trailer_info_type { TRAILER_KEY, TRAILER_COMMAND, TRAILER_WHERE,\n-\t\t\t TRAILER_IF_EXISTS, TRAILER_IF_MISSING };\n+enum trailer_info_type { TRAILER_KEY, TRAILER_COMMAND, TRAILER_CMD,\n+\t\t\tTRAILER_WHERE, TRAILER_IF_EXISTS, TRAILER_IF_MISSING };\n \n static struct {\n \tconst char *name;\n@@ -463,6 +472,7 @@ static struct {\n } trailer_config_items[] = {\n \t{ \"key\", TRAILER_KEY },\n \t{ \"command\", TRAILER_COMMAND },\n+\t{ \"cmd\", TRAILER_CMD },\n \t{ \"where\", TRAILER_WHERE },\n \t{ \"ifexists\", TRAILER_IF_EXISTS },\n \t{ \"ifmissing\", TRAILER_IF_MISSING }\n@@ -542,6 +552,11 @@ static int git_trailer_config(const char *conf_key, const char *value, void *cb)\n \t\t\twarning(_(\"more than one %s\"), conf_key);\n \t\tconf->command = xstrdup(value);\n \t\tbreak;\n+\tcase TRAILER_CMD:\n+\t\tif (conf->cmd)\n+\t\t\twarning(_(\"more than one %s\"), conf_key);\n+\t\tconf->cmd = xstrdup(value);\n+\t\tbreak;\n \tcase TRAILER_WHERE:\n \t\tif (trailer_set_where(&conf->where, value))\n \t\t\twarning(_(\"unknown value '%s' for key '%s'\"), value, conf_key);\n@@ -708,7 +723,7 @@ static void process_command_line_args(struct list_head *arg_head,\n \t/* Add an arg item for each configured trailer with a command */\n \tlist_for_each(pos, &conf_head) {\n \t\titem = list_entry(pos, struct arg_item, list);\n-\t\tif (item->conf.command)\n+\t\tif (item->conf.cmd || item->conf.command)\n \t\t\tadd_arg_item(arg_head,\n \t\t\t\t     xstrdup(token_from_item(item, NULL)),\n \t\t\t\t     xstrdup(\"\"),\n-- \ngitgitgadget\n"},{"id":"421783","messageId":"xmqqwnt7b5fg.fsf@gitster.g","threadId":"55371","inReplyTo":"8129ef6c476b4f35be59eae71367de5b83888068.1618245568.git.gitgitgadget@gmail.com","subject":"Re: [PATCH v9 1/2] [GSOC] docs: correct descript of trailer.<token>.command","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2021-04-12T20:42:43Z","receivedAt":"2021-04-12T20:42:51Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"\"ZheNing Hu via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n\n>  trailer.<token>.command::\n> +\tThis option can be used to specify a shell command that will be called:\n> +\tonce to automatically add a trailer with the specified <token>, and then\n> +\teach time a '--trailer <token>=<value>' argument to modify the <value> of\n> +\tthe trailer that this option would produce.\n>  +\n> -When this option is specified, the behavior is as if a special\n> +When the specified command is first called to add a trailer\n> +with the specified <token>, the behavior is as if a special\n> +'--trailer <token>=<value>' argument was added at the beginning\n> +of the \"git interpret-trailers\" command, where <value>\n> +is taken to be the standard output of the command with any\n> +leading and trailing whitespace trimmed off.\n\nSo, with\n\n\t[trailer \"foo\"] command = \"echo $ARG\"\n\nin your .git/config\n\n    $ git interpret-trailers </dev/null\n\ngives 'foo:'.\n\n> +If some '--trailer <token>=<value>' arguments are also passed\n> +on the command line, the command is called again once for each\n> +of these arguments with the same <token>. And the <value> part\n> +of these arguments, if any, will be used to replace the first\n> +occurrence of substring `$ARG` in the command. This way the\n> +command can produce a <value> computed from the <value> passed\n> +in the '--trailer <token>=<value>' argument.\n>  +\n> +For consistency, the first occurrence of substring `$ARG` is\n> +also replaced, this time with the empty string, in the command\n> +when the command is first called to add a trailer with the\n> +specified <token>.\n\nAnd then\n\n    $ git interpret-trailers --trailer=foo:F </dev/null\n\nwould give you\n\n    foo:\n    foo: F\n\nThe above is quite an easy to read explanation of the behaviour.\n\nI somehow wonder if this \"run with empty even without anything on\nthe command line\" a misfeature, and I'd prefer to iron it out before\nwe add .cmd, because we may not want to inherit it to the new .cmd,\njust like we avoided '$ARG' that does not properly quote and\nreplaces only once from getting inherited by .cmd mechanism.\n\nThe reason why I suspect this may be a misfeature is because I do\nnot see any way to avoid 'foo' trailer once trailer.foo.command is\nset.  Which means I cannot use this mechanism to emulate \"commit -s\",\nwhich would hopefully be something like\n\n\t[trailer \"signoff\"]\n\t\tcommand = \"git var GIT_COMMITTER_IDENT | sed -e 's/>.*/>/\"\n\t\tifexists = addIfDifferentNeighbor\n\nAnd trailer.signoff.ifmissing=donothing would not help in this case,\neither, I am afraid, as that would not just suppress the automatic\nempty one, which is fairly useless, but also suppresses the one that\nis made in response to the option from the command line.\n\nChristian?  What's your thought on this?\n\nI can understand that it sometimes may be useful to unconditionally\nbe able to add a trailer without doing anything from the command\nline, but it feels fairly useless that an empty one is automatically\nadded, that the only way to hide that empty one from the end result\nis to use ifexists=replace, and that there is no apparent way to remove\nthe empty one.\n\nThe --trim-empty option is a bit too crude a band-aid to use, as the\nexisting log message may have an unrelated trailer for which it is\nperfectly valid not to have any value.\n\nThe fix we'd do when introducing .cmd should also get rid of this\ninitial \"run with an empty even when not asked\"?\n\nOr if the execution without any input from the command line were\ntruly useful sometimes, a configuration variable that disables this\nbehaviour, i.e.\n\n\t[trailer \"signoff\"]\n\t\tcommand = \"git who\"\n\t\tifexists = addIfDifferentNeighbor\n                implicitExecution = false\t\t\n\nso that\n\n\tgit commit --trailer=signoff:Couder --trailer=signoff:gitster@\n\nwould give us two sign-offs without the empty one, perhaps?\n\n\nIn any case, the documentation update in this step looks reasonably\nwell written.\n\nThanks.\n"},{"id":"421784","messageId":"xmqqsg3vb51n.fsf@gitster.g","threadId":"55371","inReplyTo":"7f645ec95f48a206311973ee45578ba14ac58b7f.1618245568.git.gitgitgadget@gmail.com","subject":"Re: [PATCH v9 2/2] [GSOC] trailer: add new .cmd config option","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2021-04-12T20:51:00Z","receivedAt":"2021-04-12T20:51:04Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"\"ZheNing Hu via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n\n> +For consistency, the $1 is also passed, this time with the empty string,\n> +in the command when the command is first called to add a trailer with\n> +the specified <token>.\n\nI guess the same question as 1/2 applies to this part.  I am not\nsure what \"consistency\" the behaviour of calling the configured\ncommand with no argument is trying to achieve.  To me, .cmd doing\nthis may be for consistency with .command but I am not sure why\nthe consistency is even desiable.\n\n> +$ cat ~/bin/gcount\n> +#!/bin/sh\n> +test -n \"$1\" && git shortlog -s --author=\"$1\" HEAD || true\n> +$ git config trailer.cnt.key \"Commit-count: \"\n> +$ git config trailer.cnt.ifExists \"replace\"\n> +$ git config trailer.cnt.cmd \"~/bin/gcount\"\n> +$ git interpret-trailers --trailer=\"cnt:Junio\" <<EOF\n> +> subject\n> +> \n> +> message\n> +> \n> +> EOF\n> +subject\n> +\n> +message\n> +\n> +Commit-count: 22484     Junio C Hamano\n> +------------\n\nThis and the other (omitted) example demonstrates how the initial\n\"empty\" invocation is useless by using \"replace\".  Which also means\nthat you cannot add more than one trailer of the same <key> with the\nmechanism (since the older ones are replaced with the latest).\n\nThe code change and the test change are consistent with the design,\nthough.\n\nThanks.\n"},{"id":"421843","messageId":"CAP8UFD1r2kUaKbjFcRn_FGxz5=hvwY+DHdmPKR61cNdHzBe26A@mail.gmail.com","threadId":"55371","inReplyTo":"xmqqsg3vb51n.fsf@gitster.g","subject":"Re: [PATCH v9 2/2] [GSOC] trailer: add new .cmd config option","fromName":"Christian Couder","fromEmail":"christian.couder@gmail.com","sentAt":"2021-04-13T07:33:38Z","receivedAt":"2021-04-13T07:33:57Z","isPatch":true,"sender":{"key":"christian.couder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/208954?v=4"},"body":"On Mon, Apr 12, 2021 at 10:51 PM Junio C Hamano <gitster@pobox.com> wrote:\n>\n> \"ZheNing Hu via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n>\n> > +For consistency, the $1 is also passed, this time with the empty string,\n> > +in the command when the command is first called to add a trailer with\n> > +the specified <token>.\n>\n> I guess the same question as 1/2 applies to this part.  I am not\n> sure what \"consistency\" the behaviour of calling the configured\n> command with no argument is trying to achieve.  To me, .cmd doing\n> this may be for consistency with .command but I am not sure why\n> the consistency is even desiable.\n\nIt might be desirable to make it easier for people to migrate from\n\".command\" to \".cmd\". I agree this is debatable, but I don't see a big\ndownside in it. Maybe, if no argument was passed at all the first time\nthe command is called instead of the empty string, the command could\nthen know that it's called for the first time. I am not sure this\nwould be very helpful in practice though.\n\n> > +$ cat ~/bin/gcount\n> > +#!/bin/sh\n> > +test -n \"$1\" && git shortlog -s --author=\"$1\" HEAD || true\n> > +$ git config trailer.cnt.key \"Commit-count: \"\n> > +$ git config trailer.cnt.ifExists \"replace\"\n> > +$ git config trailer.cnt.cmd \"~/bin/gcount\"\n> > +$ git interpret-trailers --trailer=\"cnt:Junio\" <<EOF\n> > +> subject\n> > +>\n> > +> message\n> > +>\n> > +> EOF\n> > +subject\n> > +\n> > +message\n> > +\n> > +Commit-count: 22484     Junio C Hamano\n> > +------------\n>\n> This and the other (omitted) example demonstrates how the initial\n> \"empty\" invocation is useless by using \"replace\".  Which also means\n> that you cannot add more than one trailer of the same <key> with the\n> mechanism (since the older ones are replaced with the latest).\n\nYou can add more than one trailer with the same key if you don't use\n\"replace\" but use \"--trim-empty\" on the command line, so that an empty\ntrailer added by the initial invocation is removed. And we can later\nadd a `trailer.<token>.runMode` to remove the initial invocation.\n\n> The code change and the test change are consistent with the design,\n> though.\n\nYeah, this patch looks good to me now.\n\nThanks!\n"},{"id":"421879","messageId":"CAOLTT8TB9UF5z-51pLxdkRUxo5-w2+_U_e1wpDAdzBBmT3Og7w@mail.gmail.com","threadId":"55371","inReplyTo":"CAP8UFD1r2kUaKbjFcRn_FGxz5=hvwY+DHdmPKR61cNdHzBe26A@mail.gmail.com","subject":"Re: [PATCH v9 2/2] [GSOC] trailer: add new .cmd config option","fromName":"ZheNing Hu","fromEmail":"adlternative@gmail.com","sentAt":"2021-04-13T12:02:47Z","receivedAt":"2021-04-13T12:03:03Z","isPatch":true,"sender":{"key":"adlternative@gmail.com","avatar":"https://avatars.githubusercontent.com/u/58138461?v=4"},"body":"Hi, Christian and Junio,\n\nChristian Couder <christian.couder@gmail.com> 于2021年4月13日周二 下午3:33写道：\n>\n> On Mon, Apr 12, 2021 at 10:51 PM Junio C Hamano <gitster@pobox.com> wrote:\n> >\n> > \"ZheNing Hu via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n> >\n> > > +For consistency, the $1 is also passed, this time with the empty string,\n> > > +in the command when the command is first called to add a trailer with\n> > > +the specified <token>.\n> >\n> > I guess the same question as 1/2 applies to this part.  I am not\n> > sure what \"consistency\" the behaviour of calling the configured\n> > command with no argument is trying to achieve.  To me, .cmd doing\n> > this may be for consistency with .command but I am not sure why\n> > the consistency is even desirable.\n>\n> It might be desirable to make it easier for people to migrate from\n> \".command\" to \".cmd\". I agree this is debatable, but I don't see a big\n> downside in it. Maybe, if no argument was passed at all the first time\n> the command is called instead of the empty string, the command could\n> then know that it's called for the first time. I am not sure this\n> would be very helpful in practice though.\n>\n\nIf i'm not wrong, Christan meant that this command must run so it's\n\"consistency\", and Junio thinks this \"consistency\" is not needed.\n\nIt is true that there is not much harm in keeping `.cmd` at the behavior\nof `.command` now. But I remember the `trailer.<token>.runmode` that\nChristan said before, perhaps adding it in the subsequent iterations can\nsolve Junio's doubts. Sometimes I also confirm that the behavior of\n`git interpret-trailers` is very strange too.\n\n> > > +$ cat ~/bin/gcount\n> > > +#!/bin/sh\n> > > +test -n \"$1\" && git shortlog -s --author=\"$1\" HEAD || true\n> > > +$ git config trailer.cnt.key \"Commit-count: \"\n> > > +$ git config trailer.cnt.ifExists \"replace\"\n> > > +$ git config trailer.cnt.cmd \"~/bin/gcount\"\n> > > +$ git interpret-trailers --trailer=\"cnt:Junio\" <<EOF\n> > > +> subject\n> > > +>\n> > > +> message\n> > > +>\n> > > +> EOF\n> > > +subject\n> > > +\n> > > +message\n> > > +\n> > > +Commit-count: 22484     Junio C Hamano\n> > > +------------\n> >\n> > This and the other (omitted) example demonstrates how the initial\n> > \"empty\" invocation is useless by using \"replace\".  Which also means\n> > that you cannot add more than one trailer of the same <key> with the\n> > mechanism (since the older ones are replaced with the latest).\n>\n> You can add more than one trailer with the same key if you don't use\n> \"replace\" but use \"--trim-empty\" on the command line, so that an empty\n> trailer added by the initial invocation is removed. And we can later\n> add a `trailer.<token>.runMode` to remove the initial invocation.\n>\n\nYes, something like:\n\ntrailer.see.command=echo \"$ARG\"\n\ngit interpret-trailers --trim-empty --trailer=\"see = lll\"\n--trailer=\"see:jj\"</dev/null\n\nsee: lll\nsee: jj\n\n> > The code change and the test change are consistent with the design,\n> > though.\n>\n> Yeah, this patch looks good to me now.\n>\n> Thanks!\n\nSo is there anything else should I improve?\n\nThanks.\n--\nZheNing Hu\n"},{"id":"421914","messageId":"xmqq7dl6m4qf.fsf@gitster.g","threadId":"55371","inReplyTo":"CAP8UFD1r2kUaKbjFcRn_FGxz5=hvwY+DHdmPKR61cNdHzBe26A@mail.gmail.com","subject":"Re: [PATCH v9 2/2] [GSOC] trailer: add new .cmd config option","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2021-04-13T18:14:32Z","receivedAt":"2021-04-13T18:14:38Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Christian Couder <christian.couder@gmail.com> writes:\n\n> It might be desirable to make it easier for people to migrate from\n> \".command\" to \".cmd\". I agree this is debatable, but I don't see a big\n> downside in it. Maybe, if no argument was passed at all the first time\n> the command is called instead of the empty string, the command could\n> then know that it's called for the first time. I am not sure this\n> would be very helpful in practice though.\n\nAn integration of the trailer mechanism into the \"git commit\"\ncommand, which cannot emulate \"-s\" at the end-user level, is\nsomething I would call a big failure, though.\n\n>> This and the other (omitted) example demonstrates how the initial\n>> \"empty\" invocation is useless by using \"replace\".  Which also means\n>> that you cannot add more than one trailer of the same <key> with the\n>> mechanism (since the older ones are replaced with the latest).\n>\n> You can add more than one trailer with the same key if you don't use\n> \"replace\" but use \"--trim-empty\" on the command line, so that an empty\n> trailer added by the initial invocation is removed. And we can later\n> add a `trailer.<token>.runMode` to remove the initial invocation.\n\nAs I said in the other message (not the one you are responding to),\ntrim-empty is not a viable workaround.  Imagine what happens when\nyou want to always add a trailer (unrelated to signed-off-by), for\nwhich it is perfectly valid not to have any value.  An attempt to\nwork around the problem \"extra call without being asked from the\ncommand line\" behaviour has on our emulated \"signed-off-by\" example\nwith --trim-empty would nuke such an unrelated trailer.\n\nBesides, if the command produced non-empty value, e.g.\n\n\t[trailer \"baz\"] command = \"echo B $ARG Z\"\n\nthe extra call without being asked from the command line will still\ngive \"baz: B  Z\" that is not empty.\n\nSo... I am very curious to learn what the intended use case is for\nthis puzzling feature.  If it turns out to be that it behaves only\nbecause it was coded that way without any real benefit, I would\nstrongly prefer not to carry it over to the new .cmd thing, which is\nan attempt to deprecate .command to get rid of its broken parts (so\nfar, we identified two---only the first occurrence is replaced, and\nthe replacement can break command line syntax) while salvaging its\ngood parts (being able to programmatically decide the value).  And\nso far what I heard hasn't convinced me that this behaviour falls\ninto the latter category.\n\nThanks.\n"},{"id":"421919","messageId":"xmqq35vum1rn.fsf@gitster.g","threadId":"55371","inReplyTo":"CAOLTT8TB9UF5z-51pLxdkRUxo5-w2+_U_e1wpDAdzBBmT3Og7w@mail.gmail.com","subject":"Re: [PATCH v9 2/2] [GSOC] trailer: add new .cmd config option","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2021-04-13T19:18:36Z","receivedAt":"2021-04-13T19:18:44Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"ZheNing Hu <adlternative@gmail.com> writes:\n\n>> It might be desirable to make it easier for people to migrate from\n>> \".command\" to \".cmd\". I agree this is debatable, but I don't see a big\n>> downside in it. Maybe, if no argument was passed at all the first time\n>> the command is called instead of the empty string, the command could\n>> then know that it's called for the first time. I am not sure this\n>> would be very helpful in practice though.\n>>\n>\n> If i'm not wrong, Christan meant that this command must run so it's\n> \"consistency\", and Junio thinks this \"consistency\" is not needed.\n\nMy stance actually is a bit stronger than that.  I suspect that\nrunning the command without argument once even when no --trailer on\nthe command line asks for that <key> is a misfeature, if not a bug\n(only because it is now documented by 1/2 as such---before that, at\nleast I did not read the document that way).  And unless it is shown\nthat it is not a misfeature but is a useful behaviour with an example\nuse case that benefits from it, I would prefer not to replicate it\nin the \".cmd\".\n\n> It is true that there is not much harm in keeping `.cmd` at the behavior\n> of `.command` now.\n\nIt is far from \"there is not much harm\".  It misses the whole point\nof the exercise.\n\nOnly replacing the first occurrence and not the second and\nsubsequent occurrence is not what we are keeping in \".cmd\".\n\nReplacing in an unsafe way with respect to the command line syntax\nis not what we are keeping in \".cmd\".\n\nThat is because these two are misfeatures if not bugs (only because\nthey are documented).\n\nIn fact, the only reason why we are introducing .cmd is so that we\ncan deprecate .command and get rid of its misfeatures while keeping\nonly good bits.\n\nSo I am waiting to hear why it is not a misfeature.  If it is not,\nthen surely I am fine to keep it for now and add a workaround later,\nbut until that happens, I do not think \"commit --trailer\" can be\nused as a way to allow end-users emulate \"-s\" for their favorite\ntrailer like helped-by, acked-by, etc.\n\n\n\n\n"},{"id":"421969","messageId":"CAOLTT8TNK55AprX2tezoX4YjWV31RRyLWc9NJOvidRqqSgBpQQ@mail.gmail.com","threadId":"55371","inReplyTo":"xmqq35vum1rn.fsf@gitster.g","subject":"Re: [PATCH v9 2/2] [GSOC] trailer: add new .cmd config option","fromName":"ZheNing Hu","fromEmail":"adlternative@gmail.com","sentAt":"2021-04-14T13:27:05Z","receivedAt":"2021-04-14T13:27:21Z","isPatch":true,"sender":{"key":"adlternative@gmail.com","avatar":"https://avatars.githubusercontent.com/u/58138461?v=4"},"body":"Junio C Hamano <gitster@pobox.com> 于2021年4月14日周三 上午3:18写道：\n>\n> ZheNing Hu <adlternative@gmail.com> writes:\n>\n> >> It might be desirable to make it easier for people to migrate from\n> >> \".command\" to \".cmd\". I agree this is debatable, but I don't see a big\n> >> downside in it. Maybe, if no argument was passed at all the first time\n> >> the command is called instead of the empty string, the command could\n> >> then know that it's called for the first time. I am not sure this\n> >> would be very helpful in practice though.\n> >>\n> >\n> > If i'm not wrong, Christan meant that this command must run so it's\n> > \"consistency\", and Junio thinks this \"consistency\" is not needed.\n>\n> My stance actually is a bit stronger than that.  I suspect that\n> running the command without argument once even when no --trailer on\n> the command line asks for that <key> is a misfeature, if not a bug\n> (only because it is now documented by 1/2 as such---before that, at\n> least I did not read the document that way).  And unless it is shown\n> that it is not a misfeature but is a useful behaviour with an example\n> use case that benefits from it, I would prefer not to replicate it\n> in the \".cmd\".\n>\n> > It is true that there is not much harm in keeping `.cmd` at the behavior\n> > of `.command` now.\n>\n> It is far from \"there is not much harm\".  It misses the whole point\n> of the exercise.\n>\n> Only replacing the first occurrence and not the second and\n> subsequent occurrence is not what we are keeping in \".cmd\".\n>\n> Replacing in an unsafe way with respect to the command line syntax\n> is not what we are keeping in \".cmd\".\n>\n> That is because these two are misfeatures if not bugs (only because\n> they are documented).\n>\n> In fact, the only reason why we are introducing .cmd is so that we\n> can deprecate .command and get rid of its misfeatures while keeping\n> only good bits.\n>\n> So I am waiting to hear why it is not a misfeature.  If it is not,\n> then surely I am fine to keep it for now and add a workaround later,\n> but until that happens, I do not think \"commit --trailer\" can be\n> used as a way to allow end-users emulate \"-s\" for their favorite\n> trailer like helped-by, acked-by, etc.\n>\n\nIf it is really necessary to solve this \"empty execution\" in .cmd,\nMaybe we need to consider two points:\n* Do we need a new config flag as you said `[implicitExecution = false]`\nor just drop it? Has anyone been relying on the \"empty execution\" of\n.command before? This may be worthy of concern.\n*  Do we need `trailer.<token>.runMode` as Christan said before?\nI rejected his this suggestion before, and now I regret it a bit.\n\n--\nZheNing Hu\n"},{"id":"421982","messageId":"xmqqv98oy5bh.fsf@gitster.g","threadId":"55371","inReplyTo":"CAOLTT8TNK55AprX2tezoX4YjWV31RRyLWc9NJOvidRqqSgBpQQ@mail.gmail.com","subject":"Re: [PATCH v9 2/2] [GSOC] trailer: add new .cmd config option","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2021-04-14T20:33:22Z","receivedAt":"2021-04-14T20:33:30Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"ZheNing Hu <adlternative@gmail.com> writes:\n\n>> So I am waiting to hear why it is not a misfeature.  If it is not,\n>> then surely I am fine to keep it for now and add a workaround later,\n>> but until that happens, I do not think \"commit --trailer\" can be\n>> used as a way to allow end-users emulate \"-s\" for their favorite\n>> trailer like helped-by, acked-by, etc.\n>>\n>\n> If it is really necessary to solve this \"empty execution\" in .cmd,\n\n> Maybe we need to consider two points:\n> * Do we need a new config flag as you said `[implicitExecution = false]`\n> or just drop it? Has anyone been relying on the \"empty execution\" of\n> .command before? This may be worthy of concern.\n\nYes, if it is useful sometimes to run the .command or .cmd with\nempty <value> even when nobody asks for it on the command line with\na \"--trailer=<key>:<value>\" option, then I agree that the users\nshould be able to choose between running and not running [*].\n\n> *  Do we need `trailer.<token>.runMode` as Christan said before?\n> I rejected his this suggestion before, and now I regret it a bit.\n\nSo far, I haven't heard anything that indicates it is a useful\nbehaviour for .command, so my preference is to get rid of the\nbehaviour when we introduce .cmd to deprecate .command; yes, until\nwe get rid of .command, the behaviour between the two would be\ninconsistent but that is unavoidable when making a bugfix that is\nbackward incompatible.\n\nWhen (and only when) anybody finds a credible use case, we can add a\nmechanism to optionally turn it on (e.g. .runMode).\n\nThat is my thinking right at this moment, but that's of course\nsubject to change when a use case that would be helped by having\nthis extra execution.\n\n\n[Footnote]\n\n* Right now, all I know is that not being able to turn it off makes\n  it impossible to use \"git commit --trailer\" as a more general\n  substitute for \"git commit --signoff\" without breaking other\n  trailers (e.g. --trim-empty may get rid of the result of the extra\n  execution, but would remove other trailers that can be\n  legitimately empty).  And making it on by default with\n  configuration would mean that even though we designed .cmd as a\n  better version of the .command feature with its misdesign\n  corrected, we'd inherit one misdesign from it, which defeats one\n  third of the point of introducing the .cmd in the first place ;-)\n"},{"id":"422035","messageId":"CAOLTT8TVAvk_Hdcd27jHz5wWOR6xEMuCeQvDfu48qVDFPN666A@mail.gmail.com","threadId":"55371","inReplyTo":"xmqqv98oy5bh.fsf@gitster.g","subject":"Re: [PATCH v9 2/2] [GSOC] trailer: add new .cmd config option","fromName":"ZheNing Hu","fromEmail":"adlternative@gmail.com","sentAt":"2021-04-15T15:32:54Z","receivedAt":"2021-04-15T15:33:16Z","isPatch":true,"sender":{"key":"adlternative@gmail.com","avatar":"https://avatars.githubusercontent.com/u/58138461?v=4"},"body":"Junio C Hamano <gitster@pobox.com> 于2021年4月15日周四 上午4:33写道：\n>\n> ZheNing Hu <adlternative@gmail.com> writes:\n>\n> >> So I am waiting to hear why it is not a misfeature.  If it is not,\n> >> then surely I am fine to keep it for now and add a workaround later,\n> >> but until that happens, I do not think \"commit --trailer\" can be\n> >> used as a way to allow end-users emulate \"-s\" for their favorite\n> >> trailer like helped-by, acked-by, etc.\n> >>\n> >\n> > If it is really necessary to solve this \"empty execution\" in .cmd,\n>\n> > Maybe we need to consider two points:\n> > * Do we need a new config flag as you said `[implicitExecution = false]`\n> > or just drop it? Has anyone been relying on the \"empty execution\" of\n> > .command before? This may be worthy of concern.\n>\n> Yes, if it is useful sometimes to run the .command or .cmd with\n> empty <value> even when nobody asks for it on the command line with\n> a \"--trailer=<key>:<value>\" option, then I agree that the users\n> should be able to choose between running and not running [*].\n>\n> > *  Do we need `trailer.<token>.runMode` as Christan said before?\n> > I rejected his this suggestion before, and now I regret it a bit.\n>\n> So far, I haven't heard anything that indicates it is a useful\n> behaviour for .command, so my preference is to get rid of the\n> behaviour when we introduce .cmd to deprecate .command; yes, until\n> we get rid of .command, the behaviour between the two would be\n> inconsistent but that is unavoidable when making a bugfix that is\n> backward incompatible.\n>\n> When (and only when) anybody finds a credible use case, we can add a\n> mechanism to optionally turn it on (e.g. .runMode).\n>\n> That is my thinking right at this moment, but that's of course\n> subject to change when a use case that would be helped by having\n> this extra execution.\n>\n>\n> [Footnote]\n>\n> * Right now, all I know is that not being able to turn it off makes\n>   it impossible to use \"git commit --trailer\" as a more general\n>   substitute for \"git commit --signoff\" without breaking other\n>   trailers (e.g. --trim-empty may get rid of the result of the extra\n>   execution, but would remove other trailers that can be\n>   legitimately empty).  And making it on by default with\n>   configuration would mean that even though we designed .cmd as a\n>   better version of the .command feature with its misdesign\n>   corrected, we'd inherit one misdesign from it, which defeats one\n>   third of the point of introducing the .cmd in the first place ;-)\n\nPerhaps such a modification can meet our temporary needs!\n\n@@ -721,9 +738,10 @@ static void process_command_line_args(struct\nlist_head *arg_head,\n        char *cl_separators = xstrfmt(\"=%s\", separators);\n\n        /* Add an arg item for each configured trailer with a command */\n        list_for_each(pos, &conf_head) {\n                item = list_entry(pos, struct arg_item, list);\n-               if (item->conf.cmd || item->conf.command)\n+               if (item->conf.command)\n\nI'm so busy today that I probably haven't had time to put this into a\npatch today,\nIf this solution is reasonable, I will send a new version of the patch tomorrow.\nAs you said, first solve the misfeature, and we can add [trailer.runMode] later.\n\nThanks, Junio!\n--\nZheNing Hu\n"},{"id":"422039","messageId":"xmqqh7k7savu.fsf@gitster.g","threadId":"55371","inReplyTo":"CAOLTT8TVAvk_Hdcd27jHz5wWOR6xEMuCeQvDfu48qVDFPN666A@mail.gmail.com","subject":"Re: [PATCH v9 2/2] [GSOC] trailer: add new .cmd config option","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2021-04-15T17:41:57Z","receivedAt":"2021-04-15T17:42:05Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"ZheNing Hu <adlternative@gmail.com> writes:\n\n> Junio C Hamano <gitster@pobox.com> 于2021年4月15日周四 上午4:33写道：\n>>\n>> So far, I haven't heard anything that indicates it is a useful\n>> behaviour for .command, so my preference is to get rid of the\n>> behaviour when we introduce .cmd to deprecate .command; yes, until\n>> we get rid of .command, the behaviour between the two would be\n>> inconsistent but that is unavoidable when making a bugfix that is\n>> backward incompatible.\n>>\n>> When (and only when) anybody finds a credible use case, we can add a\n>> mechanism to optionally turn it on (e.g. .runMode).\n> ...\n> Perhaps such a modification can meet our temporary needs!\n>\n> @@ -721,9 +738,10 @@ static void process_command_line_args(struct\n> list_head *arg_head,\n>         char *cl_separators = xstrfmt(\"=%s\", separators);\n>\n>         /* Add an arg item for each configured trailer with a command */\n>         list_for_each(pos, &conf_head) {\n>                 item = list_entry(pos, struct arg_item, list);\n> -               if (item->conf.cmd || item->conf.command)\n> +               if (item->conf.command)\n>\n> I'm so busy today that I probably haven't had time to put this into a\n> patch today,\n> If this solution is reasonable, I will send a new version of the patch tomorrow.\n> As you said, first solve the misfeature, and we can add [trailer.runMode] later.\n\nI am perfectly fine (or rather I'd be happy) to wait for a while to\nsee such a patch, as it would be a good idea to give time to those\nwho do have need for this extra/empty execution to chime in.  So,\nthere is no need to rush.\n\nThanks.\n"},{"id":"422083","messageId":"daa889bd0ade1111eb8b1471fe7e953fcb41d12b.1618562875.git.gitgitgadget@gmail.com","threadId":"55371","inReplyTo":"pull.913.v10.git.1618562875.gitgitgadget@gmail.com","subject":"[PATCH v10 2/2] [GSOC] trailer: add new .cmd config option","fromName":"ZheNing Hu via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2021-04-16T08:47:55Z","receivedAt":"2021-04-16T08:48:33Z","isPatch":true,"sender":{"key":"adlternative@gmail.com","avatar":"https://avatars.githubusercontent.com/u/58138461?v=4"},"body":"From: ZheNing Hu <adlternative@gmail.com>\n\nThe `trailer.<token>.command` configuration variable\nspecifies a command (run via the shell, so it does not have\nto be a single name or path to the command, but can be a\nshell script), and the first occurrence of substring $ARG is\nreplaced with the value given to the `interpret-trailer`\ncommand for the token in a '--trailer <token>=<value>' argument.\n\nThis has three downsides:\n\n* The use of $ARG in the mechanism misleads the users that\nthe value is passed in the shell variable, and tempt them\nto use $ARG more than once, but that would not work, as\nthe second and subsequent $ARG are not replaced.\n\n* Because $ARG is textually replaced without regard to the\nshell language syntax, even '$ARG' (inside a single-quote\npair), which a user would expect to stay intact, would be\nreplaced, and worse, if the value had an unmatched single\nquote (imagine a name like \"O'Connor\", substituted into\nNAME='$ARG' to make it NAME='O'Connor'), it would result in\na broken command that is not syntactically correct (or\nworse).\n\n* The first occurrence of substring `$ARG` will be replaced\nwith the empty string, in the command when the command is\nfirst called to add a trailer with the specified <token>.\nThis is a bad design, the nature of automatic execution\ncauses it to add a trailer that we don't expect.\n\nIntroduce a new `trailer.<token>.cmd` configuration that\ntakes higher precedence to deprecate and eventually remove\n`trailer.<token>.command`, which passes the value as an\nargument to the command.  Instead of \"$ARG\", users can\nrefer to the value as positional argument, $1, in their\nscripts. At the same time, in order to allow\n`git interpret-trailers` to better simulate the behavior\nof `git command -s`, 'trailer.<token>.cmd' will not\nautomatically execute.\n\nHelped-by: Junio C Hamano <gitster@pobox.com>\nHelped-by: Christian Couder <christian.couder@gmail.com>\nSigned-off-by: ZheNing Hu <adlternative@gmail.com>\n---\n Documentation/git-interpret-trailers.txt | 74 ++++++++++++++++++---\n t/t7513-interpret-trailers.sh            | 84 ++++++++++++++++++++++++\n trailer.c                                | 35 +++++++---\n 3 files changed, 174 insertions(+), 19 deletions(-)\n\ndiff --git a/Documentation/git-interpret-trailers.txt b/Documentation/git-interpret-trailers.txt\nindex 6f2a7a130464..88084ca59654 100644\n--- a/Documentation/git-interpret-trailers.txt\n+++ b/Documentation/git-interpret-trailers.txt\n@@ -232,6 +232,20 @@ trailer.<token>.ifmissing::\n \tthat option for trailers with the specified <token>.\n \n trailer.<token>.command::\n+\tThis option behaves in the same way as 'trailer.<token>.cmd', except\n+\tthat it doesn't pass anything as argument to the specified command.\n+\tInstead the first occurrence of substring $ARG is replaced by the\n+\tvalue that would be passed as argument.\n++\n+The 'trailer.<token>.command' option has been deprecated in favor of\n+'trailer.<token>.cmd' due to the fact that $ARG in the user's command is\n+only replaced once and that the original way of replacing $ARG is not safe.\n++\n+When both 'trailer.<token>.cmd' and 'trailer.<token>.command' are given\n+for the same <token>, 'trailer.<token>.cmd' is used and\n+'trailer.<token>.command' is ignored.\n+\n+trailer.<token>.cmd::\n \tThis option can be used to specify a shell command that will be called:\n \tonce to automatically add a trailer with the specified <token>, and then\n \teach time a '--trailer <token>=<value>' argument to modify the <value> of\n@@ -247,15 +261,9 @@ leading and trailing whitespace trimmed off.\n If some '--trailer <token>=<value>' arguments are also passed\n on the command line, the command is called again once for each\n of these arguments with the same <token>. And the <value> part\n-of these arguments, if any, will be used to replace the first\n-occurrence of substring `$ARG` in the command. This way the\n-command can produce a <value> computed from the <value> passed\n-in the '--trailer <token>=<value>' argument.\n-+\n-For consistency, the first occurrence of substring `$ARG` is\n-also replaced, this time with the empty string, in the command\n-when the command is first called to add a trailer with the\n-specified <token>.\n+of these arguments, if any, will be passed to the command as its\n+first argument. This way the command can produce a <value> computed\n+from the <value> passed in the '--trailer <token>=<value>' argument.\n \n EXAMPLES\n --------\n@@ -338,6 +346,54 @@ subject\n Fix #42\n ------------\n \n+* Configure a 'cnt' trailer with a cmd use a global script `gcount`\n+to record commit counts of a specified author and show how it works:\n++\n+------------\n+$ cat ~/bin/gcount\n+#!/bin/sh\n+test -n \"$1\" && git shortlog -s --author=\"$1\" HEAD || true\n+$ git config trailer.cnt.key \"Commit-count: \"\n+$ git config trailer.cnt.ifExists \"addIfDifferentNeighbor\"\n+$ git config trailer.cnt.cmd \"~/bin/gcount\"\n+$ git interpret-trailers --trailer=\"cnt:Junio\" --trailer=\"cnt:Linus Torvalds\"<<EOF\n+> subject\n+> \n+> message\n+> \n+> EOF\n+subject\n+\n+message\n+\n+Commit-count: 22484     Junio C Hamano\n+Commit-count: 1117      Linus Torvalds\n+------------\n+\n+* Configure a 'ref' trailer with a cmd use a global script `glog-grep`\n+  to grep last relevant commit from git log in the git repository\n+  and show how it works:\n++\n+------------\n+$ cat ~/bin/glog-grep\n+#!/bin/sh\n+test -n \"$1\" && git log --grep \"$1\" --pretty=reference -1 || true\n+$ git config trailer.ref.key \"Reference-to: \"\n+$ git config trailer.ref.ifExists \"replace\"\n+$ git config trailer.ref.cmd \"~/bin/glog-grep\"\n+$ git interpret-trailers --trailer=\"ref:Add copyright notices.\" <<EOF\n+> subject\n+> \n+> message\n+> \n+> EOF\n+subject\n+\n+message\n+\n+Reference-to: 8bc9a0c769 (Add copyright notices., 2005-04-07)\n+------------\n+\n * Configure a 'see' trailer with a command to show the subject of a\n   commit that is related, and show how it works:\n +\ndiff --git a/t/t7513-interpret-trailers.sh b/t/t7513-interpret-trailers.sh\nindex 6602790b5f4c..04885d0a5e5c 100755\n--- a/t/t7513-interpret-trailers.sh\n+++ b/t/t7513-interpret-trailers.sh\n@@ -51,6 +51,69 @@ test_expect_success 'setup' '\n \tEOF\n '\n \n+test_expect_success 'with cmd' '\n+\ttest_when_finished \"git config --remove-section trailer.bug\" &&\n+\tgit config trailer.bug.key \"Bug-maker: \" &&\n+\tgit config trailer.bug.ifExists \"add\" &&\n+\tgit config trailer.bug.cmd \"echo \\\"maybe is\\\"\" &&\n+\tcat >expected2 <<-EOF &&\n+\n+\tBug-maker: maybe is him\n+\tBug-maker: maybe is me\n+\tEOF\n+\tgit interpret-trailers --trailer \"bug: him\" --trailer \"bug:me\" \\\n+\t\t>actual2 &&\n+\ttest_cmp expected2 actual2\n+'\n+\n+test_expect_success 'with cmd and $1' '\n+\ttest_when_finished \"git config --remove-section trailer.bug\" &&\n+\tgit config trailer.bug.key \"Bug-maker: \" &&\n+\tgit config trailer.bug.ifExists \"add\" &&\n+\tgit config trailer.bug.cmd \"echo \\\"\\$1\\\" is\" &&\n+\tcat >expected2 <<-EOF &&\n+\n+\tBug-maker: him is him\n+\tBug-maker: me is me\n+\tEOF\n+\tgit interpret-trailers --trailer \"bug: him\" --trailer \"bug:me\" \\\n+\t\t>actual2 &&\n+\ttest_cmp expected2 actual2\n+'\n+\n+test_expect_success 'with cmd and $1 with sh -c' '\n+\ttest_when_finished \"git config --remove-section trailer.bug\" &&\n+\tgit config trailer.bug.key \"Bug-maker: \" &&\n+\tgit config trailer.bug.ifExists \"replace\" &&\n+\tgit config trailer.bug.cmd \"sh -c \\\"echo who is \\\"\\$1\\\"\\\"\" &&\n+\tcat >expected2 <<-EOF &&\n+\n+\tBug-maker: who is me\n+\tEOF\n+\tgit interpret-trailers --trailer \"bug: him\" --trailer \"bug:me\" \\\n+\t\t>actual2 &&\n+\ttest_cmp expected2 actual2\n+'\n+\n+test_expect_success 'with cmd and $1 with shell script' '\n+\ttest_when_finished \"git config --remove-section trailer.bug\" &&\n+\tgit config trailer.bug.key \"Bug-maker: \" &&\n+\tgit config trailer.bug.ifExists \"replace\" &&\n+\tgit config trailer.bug.cmd \"./echoscript\" &&\n+\tcat >expected2 <<-EOF &&\n+\n+\tBug-maker: who is me\n+\tEOF\n+\tcat >echoscript <<-EOF &&\n+\t#!/bin/sh\n+\techo who is \"\\$1\"\n+\tEOF\n+\tchmod +x echoscript &&\n+\tgit interpret-trailers --trailer \"bug: him\" --trailer \"bug:me\" \\\n+\t\t>actual2 &&\n+\ttest_cmp expected2 actual2\n+'\n+\n test_expect_success 'without config' '\n \tsed -e \"s/ Z\\$/ /\" >expected <<-\\EOF &&\n \n@@ -1274,6 +1337,27 @@ test_expect_success 'setup a commit' '\n \tgit commit -m \"Add file a.txt\"\n '\n \n+test_expect_success 'cmd takes precedence over command' '\n+\ttest_when_finished \"git config --unset trailer.fix.cmd\" &&\n+\tgit config trailer.fix.ifExists \"replace\" &&\n+\tgit config trailer.fix.cmd \"test -n \\\"\\$1\\\" && git log -1 --oneline --format=\\\"%h (%aN)\\\" \\\n+\t--abbrev-commit --abbrev=14 \\\"\\$1\\\" || true\" &&\n+\tgit config trailer.fix.command \"git log -1 --oneline --format=\\\"%h (%s)\\\" \\\n+\t\t--abbrev-commit --abbrev=14 \\$ARG\" &&\n+\tFIXED=$(git log -1 --oneline --format=\"%h (%aN)\" --abbrev-commit --abbrev=14 HEAD) &&\n+\tcat complex_message_body >expected2 &&\n+\tsed -e \"s/ Z\\$/ /\" >>expected2 <<-EOF &&\n+\t\tFixes: $FIXED\n+\t\tAcked-by= Z\n+\t\tReviewed-by:\n+\t\tSigned-off-by: Z\n+\t\tSigned-off-by: A U Thor <author@example.com>\n+\tEOF\n+\tgit interpret-trailers --trailer \"review:\" --trailer \"fix=HEAD\" \\\n+\t\t<complex_message >actual2 &&\n+\ttest_cmp expected2 actual2\n+'\n+\n test_expect_success 'with command using $ARG' '\n \tgit config trailer.fix.ifExists \"replace\" &&\n \tgit config trailer.fix.command \"git log -1 --oneline --format=\\\"%h (%s)\\\" --abbrev-commit --abbrev=14 \\$ARG\" &&\ndiff --git a/trailer.c b/trailer.c\nindex be4e9726421c..7c7cb61a945c 100644\n--- a/trailer.c\n+++ b/trailer.c\n@@ -14,6 +14,7 @@ struct conf_info {\n \tchar *name;\n \tchar *key;\n \tchar *command;\n+\tchar *cmd;\n \tenum trailer_where where;\n \tenum trailer_if_exists if_exists;\n \tenum trailer_if_missing if_missing;\n@@ -127,6 +128,7 @@ static void free_arg_item(struct arg_item *item)\n \tfree(item->conf.name);\n \tfree(item->conf.key);\n \tfree(item->conf.command);\n+\tfree(item->conf.cmd);\n \tfree(item->token);\n \tfree(item->value);\n \tfree(item);\n@@ -216,18 +218,24 @@ static int check_if_different(struct trailer_item *in_tok,\n \treturn 1;\n }\n \n-static char *apply_command(const char *command, const char *arg)\n+static char *apply_command(struct conf_info *conf, const char *arg)\n {\n \tstruct strbuf cmd = STRBUF_INIT;\n \tstruct strbuf buf = STRBUF_INIT;\n \tstruct child_process cp = CHILD_PROCESS_INIT;\n \tchar *result;\n \n-\tstrbuf_addstr(&cmd, command);\n-\tif (arg)\n-\t\tstrbuf_replace(&cmd, TRAILER_ARG_STRING, arg);\n-\n-\tstrvec_push(&cp.args, cmd.buf);\n+\tif (conf->cmd) {\n+\t\tstrbuf_addstr(&cmd, conf->cmd);\n+\t\tstrvec_push(&cp.args, cmd.buf);\n+\t\tif (arg)\n+\t\t\tstrvec_push(&cp.args, arg);\n+\t} else if (conf->command) {\n+\t\tstrbuf_addstr(&cmd, conf->command);\n+\t\tif (arg)\n+\t\t\tstrbuf_replace(&cmd, TRAILER_ARG_STRING, arg);\n+\t\tstrvec_push(&cp.args, cmd.buf);\n+\t}\n \tcp.env = local_repo_env;\n \tcp.no_stdin = 1;\n \tcp.use_shell = 1;\n@@ -247,7 +255,7 @@ static char *apply_command(const char *command, const char *arg)\n \n static void apply_item_command(struct trailer_item *in_tok, struct arg_item *arg_tok)\n {\n-\tif (arg_tok->conf.command) {\n+\tif (arg_tok->conf.command || arg_tok->conf.cmd) {\n \t\tconst char *arg;\n \t\tif (arg_tok->value && arg_tok->value[0]) {\n \t\t\targ = arg_tok->value;\n@@ -257,7 +265,7 @@ static void apply_item_command(struct trailer_item *in_tok, struct arg_item *arg\n \t\t\telse\n \t\t\t\targ = xstrdup(\"\");\n \t\t}\n-\t\targ_tok->value = apply_command(arg_tok->conf.command, arg);\n+\t\targ_tok->value = apply_command(&arg_tok->conf, arg);\n \t\tfree((char *)arg);\n \t}\n }\n@@ -430,6 +438,7 @@ static void duplicate_conf(struct conf_info *dst, const struct conf_info *src)\n \tdst->name = xstrdup_or_null(src->name);\n \tdst->key = xstrdup_or_null(src->key);\n \tdst->command = xstrdup_or_null(src->command);\n+\tdst->cmd = xstrdup_or_null(src->cmd);\n }\n \n static struct arg_item *get_conf_item(const char *name)\n@@ -454,8 +463,8 @@ static struct arg_item *get_conf_item(const char *name)\n \treturn item;\n }\n \n-enum trailer_info_type { TRAILER_KEY, TRAILER_COMMAND, TRAILER_WHERE,\n-\t\t\t TRAILER_IF_EXISTS, TRAILER_IF_MISSING };\n+enum trailer_info_type { TRAILER_KEY, TRAILER_COMMAND, TRAILER_CMD,\n+\t\t\tTRAILER_WHERE, TRAILER_IF_EXISTS, TRAILER_IF_MISSING };\n \n static struct {\n \tconst char *name;\n@@ -463,6 +472,7 @@ static struct {\n } trailer_config_items[] = {\n \t{ \"key\", TRAILER_KEY },\n \t{ \"command\", TRAILER_COMMAND },\n+\t{ \"cmd\", TRAILER_CMD },\n \t{ \"where\", TRAILER_WHERE },\n \t{ \"ifexists\", TRAILER_IF_EXISTS },\n \t{ \"ifmissing\", TRAILER_IF_MISSING }\n@@ -542,6 +552,11 @@ static int git_trailer_config(const char *conf_key, const char *value, void *cb)\n \t\t\twarning(_(\"more than one %s\"), conf_key);\n \t\tconf->command = xstrdup(value);\n \t\tbreak;\n+\tcase TRAILER_CMD:\n+\t\tif (conf->cmd)\n+\t\t\twarning(_(\"more than one %s\"), conf_key);\n+\t\tconf->cmd = xstrdup(value);\n+\t\tbreak;\n \tcase TRAILER_WHERE:\n \t\tif (trailer_set_where(&conf->where, value))\n \t\t\twarning(_(\"unknown value '%s' for key '%s'\"), value, conf_key);\n-- \ngitgitgadget\n"},{"id":"422084","messageId":"pull.913.v10.git.1618562875.gitgitgadget@gmail.com","threadId":"55371","inReplyTo":"pull.913.v9.git.1618245568.gitgitgadget@gmail.com","subject":"[PATCH v10 0/2] [GSOC] trailer: add new .cmd config option","fromName":"ZheNing Hu via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2021-04-16T08:47:53Z","receivedAt":"2021-04-16T08:48:35Z","isPatch":true,"sender":{"key":"adlternative@gmail.com","avatar":"https://avatars.githubusercontent.com/u/58138461?v=4"},"body":"In https://lore.kernel.org/git/xmqqv99i4ck2.fsf@gitster.g/ Junio and\nChristian talked about the problem of using strbuf_replace() to replace\n$ARG:\n\n 1. if the user's script has more than one $ARG, only the first one will be\n    replaced, which is incorrected.\n 2. $ARG is textually replaced without shell syntax, which may result a\n    broken command when $ARG include some unmatching single quote, very\n    unsafe.\n\nAt the same time, the trailer.<token>.command has another design\ndisadvantages: It will automatically execute with a empty value in <token>\n<value> pair, which will create a trailer that we don’t expect.\n\nNow pass trailer value as $1 to the trailer command with another\ntrailer.<token>.cmd config, to solve these above problems.\n\nWe are now writing documents that are more readable and correct than before.\n\nZheNing Hu (2):\n  [GSOC] docs: correct descript of trailer.<token>.command\n  [GSOC] trailer: add new .cmd config option\n\n Documentation/git-interpret-trailers.txt | 93 ++++++++++++++++++++----\n t/t7513-interpret-trailers.sh            | 84 +++++++++++++++++++++\n trailer.c                                | 35 ++++++---\n 3 files changed, 186 insertions(+), 26 deletions(-)\n\n\nbase-commit: 142430338477d9d1bb25be66267225fb58498d92\nPublished-As: https://github.com/gitgitgadget/git/releases/tag/pr-913%2Fadlternative%2Ftrailer-pass-ARG-env-v10\nFetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-913/adlternative/trailer-pass-ARG-env-v10\nPull-Request: https://github.com/gitgitgadget/git/pull/913\n\nRange-diff vs v9:\n\n 1:  8129ef6c476b = 1:  8129ef6c476b [GSOC] docs: correct descript of trailer.<token>.command\n 2:  7f645ec95f48 ! 2:  daa889bd0ade [GSOC] trailer: add new .cmd config option\n     @@ Commit message\n          replaced with the value given to the `interpret-trailer`\n          command for the token in a '--trailer <token>=<value>' argument.\n      \n     -    This has two downsides:\n     +    This has three downsides:\n      \n          * The use of $ARG in the mechanism misleads the users that\n          the value is passed in the shell variable, and tempt them\n     @@ Commit message\n          a broken command that is not syntactically correct (or\n          worse).\n      \n     +    * The first occurrence of substring `$ARG` will be replaced\n     +    with the empty string, in the command when the command is\n     +    first called to add a trailer with the specified <token>.\n     +    This is a bad design, the nature of automatic execution\n     +    causes it to add a trailer that we don't expect.\n     +\n          Introduce a new `trailer.<token>.cmd` configuration that\n          takes higher precedence to deprecate and eventually remove\n          `trailer.<token>.command`, which passes the value as an\n          argument to the command.  Instead of \"$ARG\", users can\n          refer to the value as positional argument, $1, in their\n     -    scripts.\n     +    scripts. At the same time, in order to allow\n     +    `git interpret-trailers` to better simulate the behavior\n     +    of `git command -s`, 'trailer.<token>.cmd' will not\n     +    automatically execute.\n      \n          Helped-by: Junio C Hamano <gitster@pobox.com>\n          Helped-by: Christian Couder <christian.couder@gmail.com>\n     @@ Documentation/git-interpret-trailers.txt: leading and trailing whitespace trimme\n      -occurrence of substring `$ARG` in the command. This way the\n      -command can produce a <value> computed from the <value> passed\n      -in the '--trailer <token>=<value>' argument.\n     -+of these arguments, if any, will be passed to the command as its\n     -+first argument. This way the command can produce a <value> computed\n     -+from the <value> passed in the '--trailer <token>=<value>' argument.\n     - +\n     +-+\n      -For consistency, the first occurrence of substring `$ARG` is\n      -also replaced, this time with the empty string, in the command\n      -when the command is first called to add a trailer with the\n      -specified <token>.\n     -+For consistency, the $1 is also passed, this time with the empty string,\n     -+in the command when the command is first called to add a trailer with\n     -+the specified <token>.\n     ++of these arguments, if any, will be passed to the command as its\n     ++first argument. This way the command can produce a <value> computed\n     ++from the <value> passed in the '--trailer <token>=<value>' argument.\n       \n       EXAMPLES\n       --------\n     @@ Documentation/git-interpret-trailers.txt: subject\n      +#!/bin/sh\n      +test -n \"$1\" && git shortlog -s --author=\"$1\" HEAD || true\n      +$ git config trailer.cnt.key \"Commit-count: \"\n     -+$ git config trailer.cnt.ifExists \"replace\"\n     ++$ git config trailer.cnt.ifExists \"addIfDifferentNeighbor\"\n      +$ git config trailer.cnt.cmd \"~/bin/gcount\"\n     -+$ git interpret-trailers --trailer=\"cnt:Junio\" <<EOF\n     ++$ git interpret-trailers --trailer=\"cnt:Junio\" --trailer=\"cnt:Linus Torvalds\"<<EOF\n      +> subject\n      +> \n      +> message\n     @@ Documentation/git-interpret-trailers.txt: subject\n      +message\n      +\n      +Commit-count: 22484     Junio C Hamano\n     ++Commit-count: 1117      Linus Torvalds\n      +------------\n      +\n      +* Configure a 'ref' trailer with a cmd use a global script `glog-grep`\n     @@ t/t7513-interpret-trailers.sh: test_expect_success 'setup' '\n      +\tgit config trailer.bug.cmd \"echo \\\"maybe is\\\"\" &&\n      +\tcat >expected2 <<-EOF &&\n      +\n     -+\tBug-maker: maybe is\n      +\tBug-maker: maybe is him\n      +\tBug-maker: maybe is me\n      +\tEOF\n     @@ t/t7513-interpret-trailers.sh: test_expect_success 'setup' '\n      +test_expect_success 'with cmd and $1' '\n      +\ttest_when_finished \"git config --remove-section trailer.bug\" &&\n      +\tgit config trailer.bug.key \"Bug-maker: \" &&\n     -+\tgit config trailer.bug.ifExists \"replace\" &&\n     ++\tgit config trailer.bug.ifExists \"add\" &&\n      +\tgit config trailer.bug.cmd \"echo \\\"\\$1\\\" is\" &&\n      +\tcat >expected2 <<-EOF &&\n      +\n     ++\tBug-maker: him is him\n      +\tBug-maker: me is me\n      +\tEOF\n      +\tgit interpret-trailers --trailer \"bug: him\" --trailer \"bug:me\" \\\n     @@ trailer.c: static int git_trailer_config(const char *conf_key, const char *value\n       \tcase TRAILER_WHERE:\n       \t\tif (trailer_set_where(&conf->where, value))\n       \t\t\twarning(_(\"unknown value '%s' for key '%s'\"), value, conf_key);\n     -@@ trailer.c: static void process_command_line_args(struct list_head *arg_head,\n     - \t/* Add an arg item for each configured trailer with a command */\n     - \tlist_for_each(pos, &conf_head) {\n     - \t\titem = list_entry(pos, struct arg_item, list);\n     --\t\tif (item->conf.command)\n     -+\t\tif (item->conf.cmd || item->conf.command)\n     - \t\t\tadd_arg_item(arg_head,\n     - \t\t\t\t     xstrdup(token_from_item(item, NULL)),\n     - \t\t\t\t     xstrdup(\"\"),\n\n-- \ngitgitgadget\n"},{"id":"422085","messageId":"8129ef6c476b4f35be59eae71367de5b83888068.1618562875.git.gitgitgadget@gmail.com","threadId":"55371","inReplyTo":"pull.913.v10.git.1618562875.gitgitgadget@gmail.com","subject":"[PATCH v10 1/2] [GSOC] docs: correct descript of trailer.<token>.command","fromName":"ZheNing Hu via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2021-04-16T08:47:54Z","receivedAt":"2021-04-16T08:48:36Z","isPatch":true,"sender":{"key":"adlternative@gmail.com","avatar":"https://avatars.githubusercontent.com/u/58138461?v=4"},"body":"From: ZheNing Hu <adlternative@gmail.com>\n\nIn the original documentation of `trailer.<token>.command`,\nsome descriptions are easily misunderstood. So let's modify\nit to increase its readability.\n\nIn addition, clarify that `$ARG` in command can only be\nreplaced once.\n\nSigned-off-by: ZheNing Hu <adlternative@gmail.com>\n---\n Documentation/git-interpret-trailers.txt | 37 ++++++++++++++----------\n 1 file changed, 21 insertions(+), 16 deletions(-)\n\ndiff --git a/Documentation/git-interpret-trailers.txt b/Documentation/git-interpret-trailers.txt\nindex 96ec6499f001..6f2a7a130464 100644\n--- a/Documentation/git-interpret-trailers.txt\n+++ b/Documentation/git-interpret-trailers.txt\n@@ -232,25 +232,30 @@ trailer.<token>.ifmissing::\n \tthat option for trailers with the specified <token>.\n \n trailer.<token>.command::\n-\tThis option can be used to specify a shell command that will\n-\tbe called to automatically add or modify a trailer with the\n-\tspecified <token>.\n+\tThis option can be used to specify a shell command that will be called:\n+\tonce to automatically add a trailer with the specified <token>, and then\n+\teach time a '--trailer <token>=<value>' argument to modify the <value> of\n+\tthe trailer that this option would produce.\n +\n-When this option is specified, the behavior is as if a special\n-'<token>=<value>' argument were added at the beginning of the command\n-line, where <value> is taken to be the standard output of the\n-specified command with any leading and trailing whitespace trimmed\n-off.\n+When the specified command is first called to add a trailer\n+with the specified <token>, the behavior is as if a special\n+'--trailer <token>=<value>' argument was added at the beginning\n+of the \"git interpret-trailers\" command, where <value>\n+is taken to be the standard output of the command with any\n+leading and trailing whitespace trimmed off.\n +\n-If the command contains the `$ARG` string, this string will be\n-replaced with the <value> part of an existing trailer with the same\n-<token>, if any, before the command is launched.\n+If some '--trailer <token>=<value>' arguments are also passed\n+on the command line, the command is called again once for each\n+of these arguments with the same <token>. And the <value> part\n+of these arguments, if any, will be used to replace the first\n+occurrence of substring `$ARG` in the command. This way the\n+command can produce a <value> computed from the <value> passed\n+in the '--trailer <token>=<value>' argument.\n +\n-If some '<token>=<value>' arguments are also passed on the command\n-line, when a 'trailer.<token>.command' is configured, the command will\n-also be executed for each of these arguments. And the <value> part of\n-these arguments, if any, will be used to replace the `$ARG` string in\n-the command.\n+For consistency, the first occurrence of substring `$ARG` is\n+also replaced, this time with the empty string, in the command\n+when the command is first called to add a trailer with the\n+specified <token>.\n \n EXAMPLES\n --------\n-- \ngitgitgadget\n\n"},{"id":"422088","messageId":"CAP8UFD2gUbWP2LWXDt1nmph1qMyhEBfLErw3=5OTBx-KeBQBVQ@mail.gmail.com","threadId":"55371","inReplyTo":"xmqqwnt7b5fg.fsf@gitster.g","subject":"Re: [PATCH v9 1/2] [GSOC] docs: correct descript of trailer.<token>.command","fromName":"Christian Couder","fromEmail":"christian.couder@gmail.com","sentAt":"2021-04-16T12:03:30Z","receivedAt":"2021-04-16T12:03:50Z","isPatch":true,"sender":{"key":"christian.couder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/208954?v=4"},"body":"Sorry for the late answer.\n\nOn Mon, Apr 12, 2021 at 10:42 PM Junio C Hamano <gitster@pobox.com> wrote:\n>\n> \"ZheNing Hu via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n>\n> >  trailer.<token>.command::\n> > +     This option can be used to specify a shell command that will be called:\n> > +     once to automatically add a trailer with the specified <token>, and then\n> > +     each time a '--trailer <token>=<value>' argument to modify the <value> of\n> > +     the trailer that this option would produce.\n> >  +\n> > -When this option is specified, the behavior is as if a special\n> > +When the specified command is first called to add a trailer\n> > +with the specified <token>, the behavior is as if a special\n> > +'--trailer <token>=<value>' argument was added at the beginning\n> > +of the \"git interpret-trailers\" command, where <value>\n> > +is taken to be the standard output of the command with any\n> > +leading and trailing whitespace trimmed off.\n>\n> So, with\n>\n>         [trailer \"foo\"] command = \"echo $ARG\"\n>\n> in your .git/config\n>\n>     $ git interpret-trailers </dev/null\n>\n> gives 'foo:'.\n\nYeah:\n\n------\n$ git -c trailer.foo.command='echo $ARG' interpret-trailers </dev/null\n\nfoo:\n------\n\n> > +If some '--trailer <token>=<value>' arguments are also passed\n> > +on the command line, the command is called again once for each\n> > +of these arguments with the same <token>. And the <value> part\n> > +of these arguments, if any, will be used to replace the first\n> > +occurrence of substring `$ARG` in the command. This way the\n> > +command can produce a <value> computed from the <value> passed\n> > +in the '--trailer <token>=<value>' argument.\n> >  +\n> > +For consistency, the first occurrence of substring `$ARG` is\n> > +also replaced, this time with the empty string, in the command\n> > +when the command is first called to add a trailer with the\n> > +specified <token>.\n>\n> And then\n>\n>     $ git interpret-trailers --trailer=foo:F </dev/null\n>\n> would give you\n>\n>     foo:\n>     foo: F\n\nYeah:\n\n------\ngit -c trailer.foo.command='echo $ARG' interpret-trailers\n--trailer=foo:F </dev/null\n\nfoo:\nfoo: F\n------\n\n> The above is quite an easy to read explanation of the behaviour.\n>\n> I somehow wonder if this \"run with empty even without anything on\n> the command line\" a misfeature, and I'd prefer to iron it out before\n> we add .cmd, because we may not want to inherit it to the new .cmd,\n> just like we avoided '$ARG' that does not properly quote and\n> replaces only once from getting inherited by .cmd mechanism.\n\nI don't think it's a misfeature. I think it can be very useful in some\ncases like with:\n\n$ git config trailer.sign.command 'echo \"$(git config user.name)\n<$(git config user.email)>\"'\n\nMy opinion is that we should have added a `trailer.<token>.runMode`\nconfig option along with `trailer.<token>.command`. This was not\ndiscussed unfortunately when \".command\" was implemented, but it seems\nto be a good idea now.\n\n> The reason why I suspect this may be a misfeature is because I do\n> not see any way to avoid 'foo' trailer once trailer.foo.command is\n> set.\n\nIt can be avoided when the --trim-empty CLI option can be used. A hook\nto remove empty trailers (which might call `git interpret-trailers\n--trim-empty` itself) could also be used when --trim-empty cannot be\nused directly.\n\n> Which means I cannot use this mechanism to emulate \"commit -s\",\n> which would hopefully be something like\n>\n>         [trailer \"signoff\"]\n>                 command = \"git var GIT_COMMITTER_IDENT | sed -e 's/>.*/>/\"\n>                 ifexists = addIfDifferentNeighbor\n>\n> And trailer.signoff.ifmissing=donothing would not help in this case,\n> either, I am afraid, as that would not just suppress the automatic\n> empty one, which is fairly useless, but also suppresses the one that\n> is made in response to the option from the command line.\n\nI agree that the current mechanism cannot easily emulate \"commit -s\".\n\n> Christian?  What's your thought on this?\n>\n> I can understand that it sometimes may be useful to unconditionally\n> be able to add a trailer without doing anything from the command\n> line, but it feels fairly useless that an empty one is automatically\n> added, that the only way to hide that empty one from the end result\n> is to use ifexists=replace, and that there is no apparent way to remove\n> the empty one.\n>\n> The --trim-empty option is a bit too crude a band-aid to use, as the\n> existing log message may have an unrelated trailer for which it is\n> perfectly valid not to have any value.\n\nI agree that --trim-empty is not usable sometimes. Another idea would\nbe to add 'trailer.<token>.trimEmpty' to be able to do things like:\n\n------\ngit -c trailer.foo.trimEmpty=true -c trailer.foo.command='echo $ARG'\ninterpret-trailers --trailer=foo:F </dev/null\n\nfoo: F\n------\n\nIt could also be used along with `git commit --trailer=foo:F`.\n\n> The fix we'd do when introducing .cmd should also get rid of this\n> initial \"run with an empty even when not asked\"?\n\nI don't think it's a good idea to make \".cmd\" behave in a different\nway as \".command\" regarding this. Yes, there could be a short term\ngain because people could use \".cmd\" when they don't want the initial\n\"run with an empty value\", but, as some people might still want this\ninitial run, long term it looks like a burden that might:\n\n  - make it more difficult to understand how \".cmd\" and \".command\" both work\n  - make it more difficult to migrate from \".command\" to \".cmd\"\n  - prevent us from deprecating \".command\" in favor of \".cmd\"\n\n> Or if the execution without any input from the command line were\n> truly useful sometimes, a configuration variable that disables this\n> behaviour, i.e.\n>\n>         [trailer \"signoff\"]\n>                 command = \"git who\"\n>                 ifexists = addIfDifferentNeighbor\n>                 implicitExecution = false\n>\n> so that\n>\n>         git commit --trailer=signoff:Couder --trailer=signoff:gitster@\n>\n> would give us two sign-offs without the empty one, perhaps?\n\nYeah, that's basically the same idea that we previously discussed with\n'tailer.<token>.runMode' (or 'trailer.<token>.alwaysRunCmd') in:\n\nhttps://lore.kernel.org/git/CAP8UFD0OMJfkuX_JoDros7h0B20D8sm0ZbtkVpL3dCYRV_M=OA@mail.gmail.com/\n\n> In any case, the documentation update in this step looks reasonably\n> well written.\n\nThanks!\n"},{"id":"422094","messageId":"CAP8UFD2J8jz2Ku4ur_L+36b9hKB5drrexKNHYF6dM9G9BKhFug@mail.gmail.com","threadId":"55371","inReplyTo":"xmqqv98oy5bh.fsf@gitster.g","subject":"Re: [PATCH v9 2/2] [GSOC] trailer: add new .cmd config option","fromName":"Christian Couder","fromEmail":"christian.couder@gmail.com","sentAt":"2021-04-16T12:54:58Z","receivedAt":"2021-04-16T12:55:14Z","isPatch":true,"sender":{"key":"christian.couder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/208954?v=4"},"body":"On Wed, Apr 14, 2021 at 10:33 PM Junio C Hamano <gitster@pobox.com> wrote:\n>\n> ZheNing Hu <adlternative@gmail.com> writes:\n>\n> >> So I am waiting to hear why it is not a misfeature.  If it is not,\n> >> then surely I am fine to keep it for now and add a workaround later,\n> >> but until that happens, I do not think \"commit --trailer\" can be\n> >> used as a way to allow end-users emulate \"-s\" for their favorite\n> >> trailer like helped-by, acked-by, etc.\n> >>\n> >\n> > If it is really necessary to solve this \"empty execution\" in .cmd,\n>\n> > Maybe we need to consider two points:\n> > * Do we need a new config flag as you said `[implicitExecution = false]`\n> > or just drop it? Has anyone been relying on the \"empty execution\" of\n> > .command before? This may be worthy of concern.\n>\n> Yes, if it is useful sometimes to run the .command or .cmd with\n> empty <value> even when nobody asks for it on the command line with\n> a \"--trailer=<key>:<value>\" option, then I agree that the users\n> should be able to choose between running and not running [*].\n>\n> > *  Do we need `trailer.<token>.runMode` as Christan said before?\n> > I rejected his this suggestion before, and now I regret it a bit.\n>\n> So far, I haven't heard anything that indicates it is a useful\n> behaviour for .command,\n\nWell the `git config trailer.sign.command 'echo \"$(git config\nuser.name) <$(git config user.email)>\"'` has been documented in the\nEXAMPLES section of the `git interpret-trailers` doc since when\n\".command\" was implemented, and I believe that reviewers thought that\nit was a good feature to have then.\n\n> so my preference is to get rid of the\n> behaviour when we introduce .cmd to deprecate .command; yes, until\n> we get rid of .command, the behaviour between the two would be\n> inconsistent but that is unavoidable when making a bugfix that is\n> backward incompatible.\n>\n> When (and only when) anybody finds a credible use case, we can add a\n> mechanism to optionally turn it on (e.g. .runMode).\n>\n> That is my thinking right at this moment, but that's of course\n> subject to change when a use case that would be helped by having\n> this extra execution.\n\nSuch use cases and some of this were discussed when `git\ninterpret-trailers` was initially implemented.\n\nFor example in https://lore.kernel.org/git/CAP8UFD2oXpW9QEkSh+vpNGRAxRFp0zJF39ZZ8sUZLTcKB9mHWQ@mail.gmail.com/\nI suggested the following:\n\n         [trailer \"m-o-b\"]\n                 key = \"Made-on-branch: \"\n                 command = \"git name-rev --name-only HEAD\"\n\nwhen someone wanted a way to always add \"a trailer for the branch that\nthe commit was checked in to at the time\"\n\nIn https://lore.kernel.org/git/534414FB.6040604@alum.mit.edu/ Michael\nHaggerty suggested:\n\n\"Maybe there should be a trailer.<token>.trimEmpty config option.\"\n\nI haven't fully checked the discussions, so there might be other examples.\n"},{"id":"422114","messageId":"xmqq7dl2m4ci.fsf@gitster.g","threadId":"55371","inReplyTo":"8129ef6c476b4f35be59eae71367de5b83888068.1618562875.git.gitgitgadget@gmail.com","subject":"Re: [PATCH v10 1/2] [GSOC] docs: correct descript of trailer.<token>.command","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2021-04-16T19:11:57Z","receivedAt":"2021-04-16T19:12:02Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"\"ZheNing Hu via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n\n> Subject: Re: [PATCH v10 1/2] [GSOC] docs: correct descript of trailer.<token>.command\n\ns/descript/&ion/ (no need to resend only to fix this).\n\n> diff --git a/Documentation/git-interpret-trailers.txt b/Documentation/git-interpret-trailers.txt\n> index 96ec6499f001..6f2a7a130464 100644\n> --- a/Documentation/git-interpret-trailers.txt\n> +++ b/Documentation/git-interpret-trailers.txt\n> @@ -232,25 +232,30 @@ trailer.<token>.ifmissing::\n>  \tthat option for trailers with the specified <token>.\n>  \n>  trailer.<token>.command::\n> +\tThis option can be used to specify a shell command that will be called:\n> +\tonce to automatically add a trailer with the specified <token>, and then\n> +\teach time a '--trailer <token>=<value>' argument to modify the <value> of\n> +\tthe trailer that this option would produce.\n>  +\n> +When the specified command is first called to add a trailer\n> +with the specified <token>, the behavior is as if a special\n> +'--trailer <token>=<value>' argument was added at the beginning\n> +of the \"git interpret-trailers\" command, where <value>\n> +is taken to be the standard output of the command with any\n> +leading and trailing whitespace trimmed off.\n>  +\n> +If some '--trailer <token>=<value>' arguments are also passed\n> +on the command line, the command is called again once for each\n> +of these arguments with the same <token>. And the <value> part\n> +of these arguments, if any, will be used to replace the first\n> +occurrence of substring `$ARG` in the command. This way the\n> +command can produce a <value> computed from the <value> passed\n> +in the '--trailer <token>=<value>' argument.\n>  +\n\nMakes quite a lot of sense.  I wouldn't have got confused by the\nbehaviour of .command if it were documented like the above from day\none.  Very nice.\n\n> +For consistency, the first occurrence of substring `$ARG` is\n> +also replaced, this time with the empty string, in the command\n> +when the command is first called to add a trailer with the\n> +specified <token>.\n\nOK, so \"for consistency\" is about consistency between the \"execute\nonce first without being asked\" case (where there is no plausible\nsource of $ARG), and \"execute because we were asked by a command\nline option (where we are given <value> to replace $ARG).\n\nMakes sense.\n\nWill queue.  Thanks.\n\n\n\n\n"},{"id":"422115","messageId":"xmqq35vqm4ab.fsf@gitster.g","threadId":"55371","inReplyTo":"daa889bd0ade1111eb8b1471fe7e953fcb41d12b.1618562875.git.gitgitgadget@gmail.com","subject":"Re: [PATCH v10 2/2] [GSOC] trailer: add new .cmd config option","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2021-04-16T19:13:16Z","receivedAt":"2021-04-16T19:13:18Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Hmph, I am getting the following while applying.\n\n.git/rebase-apply/patch:67: trailing whitespace.\n> \n.git/rebase-apply/patch:69: trailing whitespace.\n> \n.git/rebase-apply/patch:92: trailing whitespace.\n> \n.git/rebase-apply/patch:94: trailing whitespace.\n> \nwarning: 4 lines applied after fixing whitespace errors.\nApplying: trailer: add new .cmd config option\n"},{"id":"422117","messageId":"xmqqy2dikpc1.fsf@gitster.g","threadId":"55371","inReplyTo":"daa889bd0ade1111eb8b1471fe7e953fcb41d12b.1618562875.git.gitgitgadget@gmail.com","subject":"Re: [PATCH v10 2/2] [GSOC] trailer: add new .cmd config option","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2021-04-16T19:21:34Z","receivedAt":"2021-04-16T19:21:39Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"\"ZheNing Hu via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n\n> Introduce a new `trailer.<token>.cmd` configuration that\n> takes higher precedence to deprecate and eventually remove\n> `trailer.<token>.command`, which passes the value as an\n> argument to the command.  Instead of \"$ARG\", users can\n> refer to the value as positional argument, $1, in their\n> scripts. At the same time, in order to allow\n> `git interpret-trailers` to better simulate the behavior\n> of `git command -s`, 'trailer.<token>.cmd' will not\n> automatically execute.\n\nOK.  I think there still will be disagreement on this last point\nbetween Christian and I, but I'd be happy with this as the first cut\nfor newly introduced .cmd and then when it becomes needed add\nsomething like the attached patch on top to optionally run the given\ncommand when configured.\n\n\n trailer.c | 3 ++-\n 1 file changed, 2 insertions(+), 1 deletion(-)\n\ndiff --git c/trailer.c w/trailer.c\nindex 7c7cb61a94..39132211cc 100644\n--- c/trailer.c\n+++ w/trailer.c\n@@ -723,7 +723,8 @@ static void process_command_line_args(struct list_head *arg_head,\n \t/* Add an arg item for each configured trailer with a command */\n \tlist_for_each(pos, &conf_head) {\n \t\titem = list_entry(pos, struct arg_item, list);\n-\t\tif (item->conf.command)\n+\t\tif ((item->conf.run_implicitly && item->conf.cmd) ||\n+\t\t    item->conf.command)\n \t\t\tadd_arg_item(arg_head,\n \t\t\t\t     xstrdup(token_from_item(item, NULL)),\n \t\t\t\t     xstrdup(\"\"),\n\n"},{"id":"422118","messageId":"xmqqr1jakp57.fsf@gitster.g","threadId":"55371","inReplyTo":"xmqqy2dikpc1.fsf@gitster.g","subject":"Re: [PATCH v10 2/2] [GSOC] trailer: add new .cmd config option","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2021-04-16T19:25:40Z","receivedAt":"2021-04-16T19:25:46Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Junio C Hamano <gitster@pobox.com> writes:\n\n> OK.  I think there still will be disagreement on this last point\n> between Christian and I, but I'd be happy with this as the first cut\n> for newly introduced .cmd and then when it becomes needed add\n> something like the attached patch on top to optionally run the given\n> command when configured.\n>\n>\n>  trailer.c | 3 ++-\n>  1 file changed, 2 insertions(+), 1 deletion(-)\n>\n> diff --git c/trailer.c w/trailer.c\n> index 7c7cb61a94..39132211cc 100644\n> --- c/trailer.c\n> +++ w/trailer.c\n> @@ -723,7 +723,8 @@ static void process_command_line_args(struct list_head *arg_head,\n>  \t/* Add an arg item for each configured trailer with a command */\n>  \tlist_for_each(pos, &conf_head) {\n>  \t\titem = list_entry(pos, struct arg_item, list);\n> -\t\tif (item->conf.command)\n> +\t\tif ((item->conf.run_implicitly && item->conf.cmd) ||\n> +\t\t    item->conf.command)\n>  \t\t\tadd_arg_item(arg_head,\n>  \t\t\t\t     xstrdup(token_from_item(item, NULL)),\n>  \t\t\t\t     xstrdup(\"\"),\n\nActually, if we were to do this, I actually suspect that, unlike the\ntextual replacement of $ARG in the old .command configuration, we\nshould take advantage of the fact that the command can tell the\ncases between an empty string given as $1 and no positional argument\nwas given for $1.  So what add_arg_item() does here for .command\n(which has to give an empty string, because it will textually\nreplace an occurrence of $ARG) and for .cmd may have to be different.\nPerhaps record NULL here, so that when the command line is formed,\nwe can tell that we do not want add an extra \"\" that becomes $1,\nwhen we are dealing with .cmd in this codepath.\n\nThanks.\n"},{"id":"422145","messageId":"xmqqtuo5iski.fsf@gitster.g","threadId":"55371","inReplyTo":"CAP8UFD2gUbWP2LWXDt1nmph1qMyhEBfLErw3=5OTBx-KeBQBVQ@mail.gmail.com","subject":"Re: [PATCH v9 1/2] [GSOC] docs: correct descript of trailer.<token>.command","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2021-04-17T01:54:37Z","receivedAt":"2021-04-17T01:57:09Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Christian Couder <christian.couder@gmail.com> writes:\n\n> $ git config trailer.sign.command 'echo \"$(git config user.name)\n> <$(git config user.email)>\"'\n>\n> My opinion is that we should have added a `trailer.<token>.runMode`\n> config option along with `trailer.<token>.command`. This was not\n> discussed unfortunately when \".command\" was implemented, but it seems\n> to be a good idea now.\n\nYes, without a knob to diable/enable, the \"feature\" is pretty much\nuseless.  \n\n> It can be avoided when the --trim-empty CLI option can be used. A hook\n> to remove empty trailers (which might call `git interpret-trailers\n> --trim-empty` itself) could also be used when --trim-empty cannot be\n> used directly.\n\nAnd as you know, --trim-empty that applies to all trailer keys would\ndestroy other trailers and trailer.<token>.trimEmpty, even if it\nwere available, would not work at all to remove it for the case you\ncited above, to add \"user.name <user.email>\", which is not an empty\nstring.\n\n> I agree that the current mechanism cannot easily emulate \"commit -s\".\n> ...\n> I agree that --trim-empty is not usable sometimes. Another idea would\n> be to add 'trailer.<token>.trimEmpty' to be able to do things like:\n\n\"easily\"?  \"sometimes\"?\n\n\"--trim-empty\" is unusable, trailer.<token>.trimEmpty would not work\neven if it were added because the reason why this is broken is not\nbecause it gives an empty value, but because it runs even when it is\nnot asked and there is no way to turn it off.\n\nThis shows that even the only plausibly-useful use case we've seen\nso far is not very well supported, and necessary options/knobs to\nmake it usable have not been invented and implemented.\n\nIt is time for us to admit that this is a misfeature that is not\nwell thought out.  Recognising that .command is broken is the first\nstep to remedy it with a better alternative in .cmd; otherwise we\nwould inherit the same breakage in the replacement.\n\nThe only reasonable way out I would think of is to hide the\nunconditional execution behind trailer.<token>.runMode, and make the\ndefault for runMode to \"do not run when --trailer=<token>[:<value>]\nis not asked from the command line\" for .cmd; it is OK for .command\nnot to honor the knob, as we will get rid of it once we see .cmd\nworks well.\n\nThanks.\n"},{"id":"422146","messageId":"xmqqpmytiplw.fsf@gitster.g","threadId":"55371","inReplyTo":"xmqqr1jakp57.fsf@gitster.g","subject":"Re: [PATCH v10 2/2] [GSOC] trailer: add new .cmd config option","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2021-04-17T02:58:35Z","receivedAt":"2021-04-17T02:58:47Z","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> Junio C Hamano <gitster@pobox.com> writes:\n>\n>> OK.  I think there still will be disagreement on this last point\n>> between Christian and I, but I'd be happy with this as the first cut\n>> for newly introduced .cmd and then when it becomes needed add\n>> something like the attached patch on top to optionally run the given\n>> command when configured.\n>>\n>>\n>>  trailer.c | 3 ++-\n>>  1 file changed, 2 insertions(+), 1 deletion(-)\n>>\n>> diff --git c/trailer.c w/trailer.c\n>> index 7c7cb61a94..39132211cc 100644\n>> --- c/trailer.c\n>> +++ w/trailer.c\n>> @@ -723,7 +723,8 @@ static void process_command_line_args(struct list_head *arg_head,\n>>  \t/* Add an arg item for each configured trailer with a command */\n>>  \tlist_for_each(pos, &conf_head) {\n>>  \t\titem = list_entry(pos, struct arg_item, list);\n>> -\t\tif (item->conf.command)\n>> +\t\tif ((item->conf.run_implicitly && item->conf.cmd) ||\n>> +\t\t    item->conf.command)\n>>  \t\t\tadd_arg_item(arg_head,\n>>  \t\t\t\t     xstrdup(token_from_item(item, NULL)),\n>>  \t\t\t\t     xstrdup(\"\"),\n>\n> Actually, if we were to do this, I actually suspect that, unlike the\n> textual replacement of $ARG in the old .command configuration, we\n> should take advantage of the fact that the command can tell the\n> cases between an empty string given as $1 and no positional argument\n> was given for $1.  So what add_arg_item() does here for .command\n> (which has to give an empty string, because it will textually\n> replace an occurrence of $ARG) and for .cmd may have to be different.\n> Perhaps record NULL here, so that when the command line is formed,\n> we can tell that we do not want add an extra \"\" that becomes $1,\n> when we are dealing with .cmd in this codepath.\n\nAnd continuing this line of thought, I think it would be a perfectly\nfine extension to allow the script/program that is launched by the\n.command or .cmd mechanism to signal interpret-trailers that it does\nnot want it to add a trailer as the result of this invocation by\nexiting with non-zero.  And that would be a reasonable way forward\nwithout having to add yet another ugly workaround .runMode.\n\nFor example, trailer.signoff.cmd could be this script:\n\n\t#!/bin/sh\n\tif test $# != 1\n\tthen\n\t\texit 1\n\telse\n\t\tgit log -1 --author=\"$1\" --format='\"%aN\" <$aE>'\n\tfi\n\nwhere the \"implicit\" invocation is signalled by not passing any\nargument, to which the script reacts by exiting with 1, and the\ninterpret-trailers would discard the result of the unasked-for\ninvocation.\n\n\n\n\n"},{"id":"422147","messageId":"xmqqlf9hinuw.fsf@gitster.g","threadId":"55371","inReplyTo":"xmqqpmytiplw.fsf@gitster.g","subject":"Re: [PATCH v10 2/2] [GSOC] trailer: add new .cmd config option","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2021-04-17T03:36:23Z","receivedAt":"2021-04-17T03:36:27Z","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> And continuing this line of thought, I think it would be a perfectly\n> fine extension to allow the script/program that is launched by the\n> .command or .cmd mechanism to signal interpret-trailers that it does\n> not want it to add a trailer as the result of this invocation by\n> exiting with non-zero.  And that would be a reasonable way forward\n> without having to add yet another ugly workaround .runMode.\n>\n> For example, trailer.signoff.cmd could be this script:\n>\n> \t#!/bin/sh\n> \tif test $# != 1\n> \tthen\n> \t\texit 1\n> \telse\n> \t\tgit log -1 --author=\"$1\" --format='\"%aN\" <$aE>'\n> \tfi\n>\n> where the \"implicit\" invocation is signalled by not passing any\n> argument, to which the script reacts by exiting with 1, and the\n> interpret-trailers would discard the result of the unasked-for\n> invocation.\n\nThe beauty of this approach, compared to say .runMode, is that the\nprogram specified by .cmd (and .command, except that it cannot tell\nif the invocation is in response to explicit --trailer=<key>:<value>\nrequest, or the extra one that is done even without being asked)\nhave even better control in squelching the trailer output.  Not just\nto silence the extra unasked-for invocation, it can inspect the\nvalue given to each of --trailer=<key>:<value> option and decide not\nto add a trailer in response to it.\n\n"},{"id":"422151","messageId":"CAOLTT8S-CLSk6Obu-8L+QKb9YjBufVTMcTnSrfaUOQmyej59TQ@mail.gmail.com","threadId":"55371","inReplyTo":"xmqqlf9hinuw.fsf@gitster.g","subject":"Re: [PATCH v10 2/2] [GSOC] trailer: add new .cmd config option","fromName":"ZheNing Hu","fromEmail":"adlternative@gmail.com","sentAt":"2021-04-17T07:41:17Z","receivedAt":"2021-04-17T07:41:38Z","isPatch":true,"sender":{"key":"adlternative@gmail.com","avatar":"https://avatars.githubusercontent.com/u/58138461?v=4"},"body":"Junio C Hamano <gitster@pobox.com> 于2021年4月17日周六 上午11:36写道：\n>\n> Junio C Hamano <gitster@pobox.com> writes:\n>\n> > And continuing this line of thought, I think it would be a perfectly\n> > fine extension to allow the script/program that is launched by the\n> > .command or .cmd mechanism to signal interpret-trailers that it does\n> > not want it to add a trailer as the result of this invocation by\n> > exiting with non-zero.  And that would be a reasonable way forward\n> > without having to add yet another ugly workaround .runMode.\n> >\n> > For example, trailer.signoff.cmd could be this script:\n> >\n> >       #!/bin/sh\n> >       if test $# != 1\n> >       then\n> >               exit 1\n> >       else\n> >               git log -1 --author=\"$1\" --format='\"%aN\" <$aE>'\n> >       fi\n> >\n> > where the \"implicit\" invocation is signalled by not passing any\n> > argument, to which the script reacts by exiting with 1, and the\n> > interpret-trailers would discard the result of the unasked-for\n> > invocation.\n>\n> The beauty of this approach, compared to say .runMode, is that the\n> program specified by .cmd (and .command, except that it cannot tell\n> if the invocation is in response to explicit --trailer=<key>:<value>\n> request, or the extra one that is done even without being asked)\n> have even better control in squelching the trailer output.  Not just\n> to silence the extra unasked-for invocation, it can inspect the\n> value given to each of --trailer=<key>:<value> option and decide not\n> to add a trailer in response to it.\n>\n\nIf I understand correctly, Your approach may be like this:\n\nFirst, Those `<token> <value>` pairs we passed on the command line\nwill use one positional parameter in the shell-script.\n\nSecond, if it is the trailer implicitly added, originally it was\n`<token> \"\"`, now we turn it to `<token> NULL`, This will make\nthe shell-script not pass positional parameters.\n\nThen our shell script can distinguish them by the value of $#.\n\nIf we want shell-script to execute implicitly anyway, We can do nothing.\nthe shell-script can be like  as Christian mention:\n\n#!/bin/sh\necho \"$(git config user.name) <$(git config user.email)>\"\n\nBut If we want shell-script to reject implicit execution, We can judge the\nvalue of $# : 0 exit , 1 normal as you mention:\n\n#!/bin/sh\nif test $# != 1\nthen\n    exit 0\nelse\n    git log -1 --author=\"$1\" --format='%aN <%aE>'\nfi\n\nI agree with your approach.\nBut when I am reproducing your solution, I may be in trouble:\nIf we let shell-script `exit(1)`, `capture_command()` will output\n\"running trailer command '%s' failed\" which is its origin strategy\nin order to catch the user's command error.\n\nBut If we use `exit(0)` , The headache is coming again:\n\nSigned-off-by:\n\nthe empty value trailer still output. What we want is that those\n\"<token> NULL\" are rejected here, we don’t want any other output,\ndo we?\n\n\nThanks.\n--\nZheNing Hu\n"},{"id":"422152","messageId":"xmqqk0p1baad.fsf@gitster.g","threadId":"55371","inReplyTo":"CAOLTT8S-CLSk6Obu-8L+QKb9YjBufVTMcTnSrfaUOQmyej59TQ@mail.gmail.com","subject":"Re: [PATCH v10 2/2] [GSOC] trailer: add new .cmd config option","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2021-04-17T08:11:22Z","receivedAt":"2021-04-17T08:11:28Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"ZheNing Hu <adlternative@gmail.com> writes:\n\n> But when I am reproducing your solution, I may be in trouble:\n> If we let shell-script `exit(1)`, `capture_command()` will output\n> \"running trailer command '%s' failed\" which is its origin strategy\n> in order to catch the user's command error.\n\nYes, if we were to take that route, the trailer.c code needs to be\ntaught to conditionally not to pass $1, and capture_command() needs\nto be taught to conditionally be silent on non-zero exit, as it now\nis a perfectly sane for the program to exit with non-zero status in\nsome cases (like this one).\n"},{"id":"422182","messageId":"pull.913.v11.git.1618672417.gitgitgadget@gmail.com","threadId":"55371","inReplyTo":"pull.913.v10.git.1618562875.gitgitgadget@gmail.com","subject":"[PATCH v11 0/2] [GSOC] trailer: add new .cmd config option","fromName":"ZheNing Hu via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2021-04-17T15:13:35Z","receivedAt":"2021-04-17T15:13:42Z","isPatch":true,"sender":{"key":"adlternative@gmail.com","avatar":"https://avatars.githubusercontent.com/u/58138461?v=4"},"body":"In https://lore.kernel.org/git/xmqqv99i4ck2.fsf@gitster.g/ Junio and\nChristian talked about the problem of using strbuf_replace() to replace\n$ARG:\n\n 1. if the user's script has more than one $ARG, only the first one will be\n    replaced, which is incorrected.\n 2. $ARG is textually replaced without shell syntax, which may result a\n    broken command when $ARG include some unmatching single quote, very\n    unsafe.\n\nNow pass trailer value as $1 to the trailer command with another\ntrailer.<token>.cmd config, to solve these above problems.\n\nWe are now writing documents that are more readable and correct than before.\n\nZheNing Hu (2):\n  [GSOC] docs: correct description of .command\n  [GSOC] trailer: add new .cmd config option\n\n Documentation/git-interpret-trailers.txt | 111 ++++++++++++++++++---\n t/t7513-interpret-trailers.sh            | 122 +++++++++++++++++++++++\n trailer.c                                |  79 ++++++++++++---\n 3 files changed, 280 insertions(+), 32 deletions(-)\n\n\nbase-commit: 142430338477d9d1bb25be66267225fb58498d92\nPublished-As: https://github.com/gitgitgadget/git/releases/tag/pr-913%2Fadlternative%2Ftrailer-pass-ARG-env-v11\nFetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-913/adlternative/trailer-pass-ARG-env-v11\nPull-Request: https://github.com/gitgitgadget/git/pull/913\n\nRange-diff vs v10:\n\n 1:  8129ef6c476b ! 1:  34210e5bd3da [GSOC] docs: correct descript of trailer.<token>.command\n     @@ Metadata\n      Author: ZheNing Hu <adlternative@gmail.com>\n      \n       ## Commit message ##\n     -    [GSOC] docs: correct descript of trailer.<token>.command\n     +    [GSOC] docs: correct description of .command\n      \n          In the original documentation of `trailer.<token>.command`,\n          some descriptions are easily misunderstood. So let's modify\n 2:  daa889bd0ade ! 2:  9c0fc91aba24 [GSOC] trailer: add new .cmd config option\n     @@ Commit message\n          replaced with the value given to the `interpret-trailer`\n          command for the token in a '--trailer <token>=<value>' argument.\n      \n     -    This has three downsides:\n     +    This has two downsides:\n      \n          * The use of $ARG in the mechanism misleads the users that\n          the value is passed in the shell variable, and tempt them\n     @@ Commit message\n          a broken command that is not syntactically correct (or\n          worse).\n      \n     -    * The first occurrence of substring `$ARG` will be replaced\n     -    with the empty string, in the command when the command is\n     -    first called to add a trailer with the specified <token>.\n     -    This is a bad design, the nature of automatic execution\n     -    causes it to add a trailer that we don't expect.\n     -\n          Introduce a new `trailer.<token>.cmd` configuration that\n          takes higher precedence to deprecate and eventually remove\n          `trailer.<token>.command`, which passes the value as an\n     @@ Commit message\n          refer to the value as positional argument, $1, in their\n          scripts. At the same time, in order to allow\n          `git interpret-trailers` to better simulate the behavior\n     -    of `git command -s`, 'trailer.<token>.cmd' will not\n     -    automatically execute.\n     +    of `git command -s`, the first implicitly executed command\n     +    will not pass positional parameters, users can use this\n     +    feature to suppress its output.\n      \n          Helped-by: Junio C Hamano <gitster@pobox.com>\n          Helped-by: Christian Couder <christian.couder@gmail.com>\n     @@ Documentation/git-interpret-trailers.txt: leading and trailing whitespace trimme\n      -occurrence of substring `$ARG` in the command. This way the\n      -command can produce a <value> computed from the <value> passed\n      -in the '--trailer <token>=<value>' argument.\n     --+\n     ++of these arguments, if any, will be passed to the command as its\n     ++first argument. This way the command can produce a <value> computed\n     ++from the <value> passed in the '--trailer <token>=<value>' argument.\n     + +\n      -For consistency, the first occurrence of substring `$ARG` is\n      -also replaced, this time with the empty string, in the command\n      -when the command is first called to add a trailer with the\n      -specified <token>.\n     -+of these arguments, if any, will be passed to the command as its\n     -+first argument. This way the command can produce a <value> computed\n     -+from the <value> passed in the '--trailer <token>=<value>' argument.\n     ++It is worth mentioning that the command is first called to add a\n     ++trailer with the specified <token> and without positional argument.\n     ++Users can make use of this output when they need automatically add\n     ++some trailers. On the other hand, users can use a trick to suppress\n     ++this output by judging whether the number of positional parameters\n     ++is equal to one, if it is true, execute the commands, otherwise exit\n     ++with non-zero to suppress the output.\n       \n       EXAMPLES\n       --------\n     @@ Documentation/git-interpret-trailers.txt: subject\n      +------------\n      +$ cat ~/bin/gcount\n      +#!/bin/sh\n     -+test -n \"$1\" && git shortlog -s --author=\"$1\" HEAD || true\n     ++if test \"$#\" != 1\n     ++then\n     ++\texit 1\n     ++else\n     ++\ttest -n \"$1\" && git shortlog -s --author=\"$1\" HEAD || true\n     ++fi\n      +$ git config trailer.cnt.key \"Commit-count: \"\n      +$ git config trailer.cnt.ifExists \"addIfDifferentNeighbor\"\n      +$ git config trailer.cnt.cmd \"~/bin/gcount\"\n      +$ git interpret-trailers --trailer=\"cnt:Junio\" --trailer=\"cnt:Linus Torvalds\"<<EOF\n      +> subject\n     -+> \n     ++>\n      +> message\n     -+> \n     ++>\n      +> EOF\n      +subject\n      +\n     @@ Documentation/git-interpret-trailers.txt: subject\n      +------------\n      +$ cat ~/bin/glog-grep\n      +#!/bin/sh\n     -+test -n \"$1\" && git log --grep \"$1\" --pretty=reference -1 || true\n     ++if test \"$#\" != 1\n     ++then\n     ++\texit 1\n     ++else\n     ++\ttest -n \"$1\" && git log --grep \"$1\" --pretty=reference -1 || true\n     ++fi\n      +$ git config trailer.ref.key \"Reference-to: \"\n      +$ git config trailer.ref.ifExists \"replace\"\n      +$ git config trailer.ref.cmd \"~/bin/glog-grep\"\n      +$ git interpret-trailers --trailer=\"ref:Add copyright notices.\" <<EOF\n      +> subject\n     -+> \n     ++>\n      +> message\n     -+> \n     ++>\n      +> EOF\n      +subject\n      +\n     @@ t/t7513-interpret-trailers.sh: test_expect_success 'setup' '\n      +\tgit config trailer.bug.cmd \"echo \\\"maybe is\\\"\" &&\n      +\tcat >expected2 <<-EOF &&\n      +\n     ++\tBug-maker: maybe is\n      +\tBug-maker: maybe is him\n      +\tBug-maker: maybe is me\n      +\tEOF\n     @@ t/t7513-interpret-trailers.sh: test_expect_success 'setup' '\n      +\tgit config trailer.bug.cmd \"echo \\\"\\$1\\\" is\" &&\n      +\tcat >expected2 <<-EOF &&\n      +\n     ++\tBug-maker: is\n      +\tBug-maker: him is him\n      +\tBug-maker: me is me\n      +\tEOF\n     @@ t/t7513-interpret-trailers.sh: test_expect_success 'setup' '\n      +\tEOF\n      +\tcat >echoscript <<-EOF &&\n      +\t#!/bin/sh\n     -+\techo who is \"\\$1\"\n     ++\tif test \"\\$#\" != 1\n     ++\tthen\n     ++\t\texit 1\n     ++\telse\n     ++\t\techo who is \"\\$1\"\n     ++\tfi\n      +\tEOF\n      +\tchmod +x echoscript &&\n      +\tgit interpret-trailers --trailer \"bug: him\" --trailer \"bug:me\" \\\n      +\t\t>actual2 &&\n      +\ttest_cmp expected2 actual2\n      +'\n     ++\n     ++test_expect_success 'with cmd, $1 and without --trailer' '\n     ++\ttest_when_finished \"git config --remove-section trailer.bug\" &&\n     ++\ttest_when_finished \"git config --remove-section trailer.gub\" &&\n     ++\tgit config trailer.bug.key \"Bug-maker: \" &&\n     ++\tgit config trailer.bug.ifExists \"replace\" &&\n     ++\tgit config trailer.bug.cmd \"./echoscript\" &&\n     ++\tgit config trailer.gub.key \"Gub-maker: \" &&\n     ++\tgit config trailer.gub.ifExists \"replace\" &&\n     ++\tgit config trailer.gub.cmd \"./echoscript2\" &&\n     ++\tcat >expected2 <<-EOF &&\n     ++\n     ++\tGub-maker: si ohw\n     ++\tEOF\n     ++\tcat >echoscript <<-EOF &&\n     ++\t#!/bin/sh\n     ++\tif test \"\\$#\" != 1\n     ++\tthen\n     ++\t\texit 1\n     ++\telse\n     ++\t\techo who is \"\\$1\"\n     ++\tfi\n     ++\tEOF\n     ++\tcat >echoscript2 <<-EOF &&\n     ++\t\techo si ohw \"\\$1\"\n     ++\tEOF\n     ++\tchmod +x echoscript &&\n     ++\tchmod +x echoscript2 &&\n     ++\tgit interpret-trailers >actual2 &&\n     ++\ttest_cmp expected2 actual2\n     ++'\n      +\n       test_expect_success 'without config' '\n       \tsed -e \"s/ Z\\$/ /\" >expected <<-\\EOF &&\n     @@ trailer.c: static void free_arg_item(struct arg_item *item)\n       \tfree(item->conf.command);\n      +\tfree(item->conf.cmd);\n       \tfree(item->token);\n     - \tfree(item->value);\n     +-\tfree(item->value);\n     ++\tif (item->value)\n     ++\t\tFREE_AND_NULL(item->value);\n       \tfree(item);\n     + }\n     + \n      @@ trailer.c: static int check_if_different(struct trailer_item *in_tok,\n       \treturn 1;\n       }\n     @@ trailer.c: static int check_if_different(struct trailer_item *in_tok,\n       \tcp.env = local_repo_env;\n       \tcp.no_stdin = 1;\n       \tcp.use_shell = 1;\n     + \n     + \tif (capture_command(&cp, &buf, 1024)) {\n     +-\t\terror(_(\"running trailer command '%s' failed\"), cmd.buf);\n     + \t\tstrbuf_release(&buf);\n     +-\t\tresult = xstrdup(\"\");\n     ++\t\tif (!conf->cmd || arg) {\n     ++\t\t\terror(_(\"running trailer command '%s' failed\"), cmd.buf);\n     ++\t\t\tresult = xstrdup(\"\");\n     ++\t\t} else\n     ++\t\t\tresult = NULL;\n     + \t} else {\n     + \t\tstrbuf_trim(&buf);\n     + \t\tresult = strbuf_detach(&buf, NULL);\n      @@ trailer.c: static char *apply_command(const char *command, const char *arg)\n       \n       static void apply_item_command(struct trailer_item *in_tok, struct arg_item *arg_tok)\n       {\n      -\tif (arg_tok->conf.command) {\n     +-\t\tconst char *arg;\n     +-\t\tif (arg_tok->value && arg_tok->value[0]) {\n      +\tif (arg_tok->conf.command || arg_tok->conf.cmd) {\n     - \t\tconst char *arg;\n     - \t\tif (arg_tok->value && arg_tok->value[0]) {\n     ++\t\tconst char *arg = NULL;\n     ++\n     ++\t\tif ((arg_tok->value && arg_tok->value[0]) ||\n     ++\t\t   (arg_tok->conf.cmd && !arg_tok->value)) {\n       \t\t\targ = arg_tok->value;\n     + \t\t} else {\n     + \t\t\tif (in_tok && in_tok->value)\n      @@ trailer.c: static void apply_item_command(struct trailer_item *in_tok, struct arg_item *arg\n       \t\t\telse\n       \t\t\t\targ = xstrdup(\"\");\n       \t\t}\n      -\t\targ_tok->value = apply_command(arg_tok->conf.command, arg);\n     +-\t\tfree((char *)arg);\n      +\t\targ_tok->value = apply_command(&arg_tok->conf, arg);\n     - \t\tfree((char *)arg);\n     ++\t\tif (arg)\n     ++\t\t\tfree((char *)arg);\n       \t}\n       }\n     + \n     +@@ trailer.c: static void apply_arg_if_exists(struct trailer_item *in_tok,\n     + \t\tbreak;\n     + \tcase EXISTS_REPLACE:\n     + \t\tapply_item_command(in_tok, arg_tok);\n     ++\t\tif (!arg_tok->value) {\n     ++\t\t\tfree_arg_item(arg_tok);\n     ++\t\t\treturn;\n     ++\t\t}\n     + \t\tadd_arg_to_input_list(on_tok, arg_tok);\n     + \t\tlist_del(&in_tok->list);\n     + \t\tfree_trailer_item(in_tok);\n     + \t\tbreak;\n     + \tcase EXISTS_ADD:\n     + \t\tapply_item_command(in_tok, arg_tok);\n     ++\t\tif (!arg_tok->value) {\n     ++\t\t\tfree_arg_item(arg_tok);\n     ++\t\t\treturn;\n     ++\t\t}\n     + \t\tadd_arg_to_input_list(on_tok, arg_tok);\n     + \t\tbreak;\n     + \tcase EXISTS_ADD_IF_DIFFERENT:\n     + \t\tapply_item_command(in_tok, arg_tok);\n     ++\t\tif (!arg_tok->value) {\n     ++\t\t\tfree_arg_item(arg_tok);\n     ++\t\t\treturn;\n     ++\t\t}\n     + \t\tif (check_if_different(in_tok, arg_tok, 1, head))\n     + \t\t\tadd_arg_to_input_list(on_tok, arg_tok);\n     + \t\telse\n     +@@ trailer.c: static void apply_arg_if_exists(struct trailer_item *in_tok,\n     + \t\tbreak;\n     + \tcase EXISTS_ADD_IF_DIFFERENT_NEIGHBOR:\n     + \t\tapply_item_command(in_tok, arg_tok);\n     ++\t\tif (!arg_tok->value) {\n     ++\t\t\tfree_arg_item(arg_tok);\n     ++\t\t\treturn;\n     ++\t\t}\n     + \t\tif (check_if_different(on_tok, arg_tok, 0, head))\n     + \t\t\tadd_arg_to_input_list(on_tok, arg_tok);\n     + \t\telse\n     +@@ trailer.c: static void apply_arg_if_missing(struct list_head *head,\n     + \tcase MISSING_ADD:\n     + \t\twhere = arg_tok->conf.where;\n     + \t\tapply_item_command(NULL, arg_tok);\n     ++\t\tif (!arg_tok->value) {\n     ++\t\t\tfree_arg_item(arg_tok);\n     ++\t\t\treturn;\n     ++\t\t}\n     + \t\tto_add = trailer_from_arg(arg_tok);\n     + \t\tif (after_or_end(where))\n     + \t\t\tlist_add_tail(&to_add->list, head);\n      @@ trailer.c: static void duplicate_conf(struct conf_info *dst, const struct conf_info *src)\n       \tdst->name = xstrdup_or_null(src->name);\n       \tdst->key = xstrdup_or_null(src->key);\n     @@ trailer.c: static int git_trailer_config(const char *conf_key, const char *value\n       \tcase TRAILER_WHERE:\n       \t\tif (trailer_set_where(&conf->where, value))\n       \t\t\twarning(_(\"unknown value '%s' for key '%s'\"), value, conf_key);\n     +@@ trailer.c: static void process_command_line_args(struct list_head *arg_head,\n     + \t\t\t\t     xstrdup(token_from_item(item, NULL)),\n     + \t\t\t\t     xstrdup(\"\"),\n     + \t\t\t\t     &item->conf, NULL);\n     ++\t\telse if (item->conf.cmd)\n     ++\t\t\tadd_arg_item(arg_head,\n     ++\t\t\t\t     xstrdup(token_from_item(item, NULL)),\n     ++\t\t\t\t     NULL,\n     ++\t\t\t\t     &item->conf, NULL);\n     + \t}\n     + \n     + \t/* Add an arg item for each trailer on the command line */\n\n-- \ngitgitgadget\n"},{"id":"422183","messageId":"34210e5bd3da3fe540800966860a9deca9c3253a.1618672417.git.gitgitgadget@gmail.com","threadId":"55371","inReplyTo":"pull.913.v11.git.1618672417.gitgitgadget@gmail.com","subject":"[PATCH v11 1/2] [GSOC] docs: correct description of .command","fromName":"ZheNing Hu via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2021-04-17T15:13:36Z","receivedAt":"2021-04-17T15:13:43Z","isPatch":true,"sender":{"key":"adlternative@gmail.com","avatar":"https://avatars.githubusercontent.com/u/58138461?v=4"},"body":"From: ZheNing Hu <adlternative@gmail.com>\n\nIn the original documentation of `trailer.<token>.command`,\nsome descriptions are easily misunderstood. So let's modify\nit to increase its readability.\n\nIn addition, clarify that `$ARG` in command can only be\nreplaced once.\n\nSigned-off-by: ZheNing Hu <adlternative@gmail.com>\n---\n Documentation/git-interpret-trailers.txt | 37 ++++++++++++++----------\n 1 file changed, 21 insertions(+), 16 deletions(-)\n\ndiff --git a/Documentation/git-interpret-trailers.txt b/Documentation/git-interpret-trailers.txt\nindex 96ec6499f001..6f2a7a130464 100644\n--- a/Documentation/git-interpret-trailers.txt\n+++ b/Documentation/git-interpret-trailers.txt\n@@ -232,25 +232,30 @@ trailer.<token>.ifmissing::\n \tthat option for trailers with the specified <token>.\n \n trailer.<token>.command::\n-\tThis option can be used to specify a shell command that will\n-\tbe called to automatically add or modify a trailer with the\n-\tspecified <token>.\n+\tThis option can be used to specify a shell command that will be called:\n+\tonce to automatically add a trailer with the specified <token>, and then\n+\teach time a '--trailer <token>=<value>' argument to modify the <value> of\n+\tthe trailer that this option would produce.\n +\n-When this option is specified, the behavior is as if a special\n-'<token>=<value>' argument were added at the beginning of the command\n-line, where <value> is taken to be the standard output of the\n-specified command with any leading and trailing whitespace trimmed\n-off.\n+When the specified command is first called to add a trailer\n+with the specified <token>, the behavior is as if a special\n+'--trailer <token>=<value>' argument was added at the beginning\n+of the \"git interpret-trailers\" command, where <value>\n+is taken to be the standard output of the command with any\n+leading and trailing whitespace trimmed off.\n +\n-If the command contains the `$ARG` string, this string will be\n-replaced with the <value> part of an existing trailer with the same\n-<token>, if any, before the command is launched.\n+If some '--trailer <token>=<value>' arguments are also passed\n+on the command line, the command is called again once for each\n+of these arguments with the same <token>. And the <value> part\n+of these arguments, if any, will be used to replace the first\n+occurrence of substring `$ARG` in the command. This way the\n+command can produce a <value> computed from the <value> passed\n+in the '--trailer <token>=<value>' argument.\n +\n-If some '<token>=<value>' arguments are also passed on the command\n-line, when a 'trailer.<token>.command' is configured, the command will\n-also be executed for each of these arguments. And the <value> part of\n-these arguments, if any, will be used to replace the `$ARG` string in\n-the command.\n+For consistency, the first occurrence of substring `$ARG` is\n+also replaced, this time with the empty string, in the command\n+when the command is first called to add a trailer with the\n+specified <token>.\n \n EXAMPLES\n --------\n-- \ngitgitgadget\n\n"},{"id":"422184","messageId":"9c0fc91aba241177072739a66bb539925dbb13e8.1618672417.git.gitgitgadget@gmail.com","threadId":"55371","inReplyTo":"pull.913.v11.git.1618672417.gitgitgadget@gmail.com","subject":"[PATCH v11 2/2] [GSOC] trailer: add new .cmd config option","fromName":"ZheNing Hu via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2021-04-17T15:13:37Z","receivedAt":"2021-04-17T15:13:44Z","isPatch":true,"sender":{"key":"adlternative@gmail.com","avatar":"https://avatars.githubusercontent.com/u/58138461?v=4"},"body":"From: ZheNing Hu <adlternative@gmail.com>\n\nThe `trailer.<token>.command` configuration variable\nspecifies a command (run via the shell, so it does not have\nto be a single name or path to the command, but can be a\nshell script), and the first occurrence of substring $ARG is\nreplaced with the value given to the `interpret-trailer`\ncommand for the token in a '--trailer <token>=<value>' argument.\n\nThis has two downsides:\n\n* The use of $ARG in the mechanism misleads the users that\nthe value is passed in the shell variable, and tempt them\nto use $ARG more than once, but that would not work, as\nthe second and subsequent $ARG are not replaced.\n\n* Because $ARG is textually replaced without regard to the\nshell language syntax, even '$ARG' (inside a single-quote\npair), which a user would expect to stay intact, would be\nreplaced, and worse, if the value had an unmatched single\nquote (imagine a name like \"O'Connor\", substituted into\nNAME='$ARG' to make it NAME='O'Connor'), it would result in\na broken command that is not syntactically correct (or\nworse).\n\nIntroduce a new `trailer.<token>.cmd` configuration that\ntakes higher precedence to deprecate and eventually remove\n`trailer.<token>.command`, which passes the value as an\nargument to the command.  Instead of \"$ARG\", users can\nrefer to the value as positional argument, $1, in their\nscripts. At the same time, in order to allow\n`git interpret-trailers` to better simulate the behavior\nof `git command -s`, the first implicitly executed command\nwill not pass positional parameters, users can use this\nfeature to suppress its output.\n\nHelped-by: Junio C Hamano <gitster@pobox.com>\nHelped-by: Christian Couder <christian.couder@gmail.com>\nSigned-off-by: ZheNing Hu <adlternative@gmail.com>\n---\n Documentation/git-interpret-trailers.txt |  90 +++++++++++++++--\n t/t7513-interpret-trailers.sh            | 122 +++++++++++++++++++++++\n trailer.c                                |  79 ++++++++++++---\n 3 files changed, 267 insertions(+), 24 deletions(-)\n\ndiff --git a/Documentation/git-interpret-trailers.txt b/Documentation/git-interpret-trailers.txt\nindex 6f2a7a130464..3fcc721bb3a2 100644\n--- a/Documentation/git-interpret-trailers.txt\n+++ b/Documentation/git-interpret-trailers.txt\n@@ -232,6 +232,20 @@ trailer.<token>.ifmissing::\n \tthat option for trailers with the specified <token>.\n \n trailer.<token>.command::\n+\tThis option behaves in the same way as 'trailer.<token>.cmd', except\n+\tthat it doesn't pass anything as argument to the specified command.\n+\tInstead the first occurrence of substring $ARG is replaced by the\n+\tvalue that would be passed as argument.\n++\n+The 'trailer.<token>.command' option has been deprecated in favor of\n+'trailer.<token>.cmd' due to the fact that $ARG in the user's command is\n+only replaced once and that the original way of replacing $ARG is not safe.\n++\n+When both 'trailer.<token>.cmd' and 'trailer.<token>.command' are given\n+for the same <token>, 'trailer.<token>.cmd' is used and\n+'trailer.<token>.command' is ignored.\n+\n+trailer.<token>.cmd::\n \tThis option can be used to specify a shell command that will be called:\n \tonce to automatically add a trailer with the specified <token>, and then\n \teach time a '--trailer <token>=<value>' argument to modify the <value> of\n@@ -247,15 +261,17 @@ leading and trailing whitespace trimmed off.\n If some '--trailer <token>=<value>' arguments are also passed\n on the command line, the command is called again once for each\n of these arguments with the same <token>. And the <value> part\n-of these arguments, if any, will be used to replace the first\n-occurrence of substring `$ARG` in the command. This way the\n-command can produce a <value> computed from the <value> passed\n-in the '--trailer <token>=<value>' argument.\n+of these arguments, if any, will be passed to the command as its\n+first argument. This way the command can produce a <value> computed\n+from the <value> passed in the '--trailer <token>=<value>' argument.\n +\n-For consistency, the first occurrence of substring `$ARG` is\n-also replaced, this time with the empty string, in the command\n-when the command is first called to add a trailer with the\n-specified <token>.\n+It is worth mentioning that the command is first called to add a\n+trailer with the specified <token> and without positional argument.\n+Users can make use of this output when they need automatically add\n+some trailers. On the other hand, users can use a trick to suppress\n+this output by judging whether the number of positional parameters\n+is equal to one, if it is true, execute the commands, otherwise exit\n+with non-zero to suppress the output.\n \n EXAMPLES\n --------\n@@ -338,6 +354,64 @@ subject\n Fix #42\n ------------\n \n+* Configure a 'cnt' trailer with a cmd use a global script `gcount`\n+to record commit counts of a specified author and show how it works:\n++\n+------------\n+$ cat ~/bin/gcount\n+#!/bin/sh\n+if test \"$#\" != 1\n+then\n+\texit 1\n+else\n+\ttest -n \"$1\" && git shortlog -s --author=\"$1\" HEAD || true\n+fi\n+$ git config trailer.cnt.key \"Commit-count: \"\n+$ git config trailer.cnt.ifExists \"addIfDifferentNeighbor\"\n+$ git config trailer.cnt.cmd \"~/bin/gcount\"\n+$ git interpret-trailers --trailer=\"cnt:Junio\" --trailer=\"cnt:Linus Torvalds\"<<EOF\n+> subject\n+>\n+> message\n+>\n+> EOF\n+subject\n+\n+message\n+\n+Commit-count: 22484     Junio C Hamano\n+Commit-count: 1117      Linus Torvalds\n+------------\n+\n+* Configure a 'ref' trailer with a cmd use a global script `glog-grep`\n+  to grep last relevant commit from git log in the git repository\n+  and show how it works:\n++\n+------------\n+$ cat ~/bin/glog-grep\n+#!/bin/sh\n+if test \"$#\" != 1\n+then\n+\texit 1\n+else\n+\ttest -n \"$1\" && git log --grep \"$1\" --pretty=reference -1 || true\n+fi\n+$ git config trailer.ref.key \"Reference-to: \"\n+$ git config trailer.ref.ifExists \"replace\"\n+$ git config trailer.ref.cmd \"~/bin/glog-grep\"\n+$ git interpret-trailers --trailer=\"ref:Add copyright notices.\" <<EOF\n+> subject\n+>\n+> message\n+>\n+> EOF\n+subject\n+\n+message\n+\n+Reference-to: 8bc9a0c769 (Add copyright notices., 2005-04-07)\n+------------\n+\n * Configure a 'see' trailer with a command to show the subject of a\n   commit that is related, and show how it works:\n +\ndiff --git a/t/t7513-interpret-trailers.sh b/t/t7513-interpret-trailers.sh\nindex 6602790b5f4c..1e5f6160dd5a 100755\n--- a/t/t7513-interpret-trailers.sh\n+++ b/t/t7513-interpret-trailers.sh\n@@ -51,6 +51,107 @@ test_expect_success 'setup' '\n \tEOF\n '\n \n+test_expect_success 'with cmd' '\n+\ttest_when_finished \"git config --remove-section trailer.bug\" &&\n+\tgit config trailer.bug.key \"Bug-maker: \" &&\n+\tgit config trailer.bug.ifExists \"add\" &&\n+\tgit config trailer.bug.cmd \"echo \\\"maybe is\\\"\" &&\n+\tcat >expected2 <<-EOF &&\n+\n+\tBug-maker: maybe is\n+\tBug-maker: maybe is him\n+\tBug-maker: maybe is me\n+\tEOF\n+\tgit interpret-trailers --trailer \"bug: him\" --trailer \"bug:me\" \\\n+\t\t>actual2 &&\n+\ttest_cmp expected2 actual2\n+'\n+\n+test_expect_success 'with cmd and $1' '\n+\ttest_when_finished \"git config --remove-section trailer.bug\" &&\n+\tgit config trailer.bug.key \"Bug-maker: \" &&\n+\tgit config trailer.bug.ifExists \"add\" &&\n+\tgit config trailer.bug.cmd \"echo \\\"\\$1\\\" is\" &&\n+\tcat >expected2 <<-EOF &&\n+\n+\tBug-maker: is\n+\tBug-maker: him is him\n+\tBug-maker: me is me\n+\tEOF\n+\tgit interpret-trailers --trailer \"bug: him\" --trailer \"bug:me\" \\\n+\t\t>actual2 &&\n+\ttest_cmp expected2 actual2\n+'\n+\n+test_expect_success 'with cmd and $1 with sh -c' '\n+\ttest_when_finished \"git config --remove-section trailer.bug\" &&\n+\tgit config trailer.bug.key \"Bug-maker: \" &&\n+\tgit config trailer.bug.ifExists \"replace\" &&\n+\tgit config trailer.bug.cmd \"sh -c \\\"echo who is \\\"\\$1\\\"\\\"\" &&\n+\tcat >expected2 <<-EOF &&\n+\n+\tBug-maker: who is me\n+\tEOF\n+\tgit interpret-trailers --trailer \"bug: him\" --trailer \"bug:me\" \\\n+\t\t>actual2 &&\n+\ttest_cmp expected2 actual2\n+'\n+\n+test_expect_success 'with cmd and $1 with shell script' '\n+\ttest_when_finished \"git config --remove-section trailer.bug\" &&\n+\tgit config trailer.bug.key \"Bug-maker: \" &&\n+\tgit config trailer.bug.ifExists \"replace\" &&\n+\tgit config trailer.bug.cmd \"./echoscript\" &&\n+\tcat >expected2 <<-EOF &&\n+\n+\tBug-maker: who is me\n+\tEOF\n+\tcat >echoscript <<-EOF &&\n+\t#!/bin/sh\n+\tif test \"\\$#\" != 1\n+\tthen\n+\t\texit 1\n+\telse\n+\t\techo who is \"\\$1\"\n+\tfi\n+\tEOF\n+\tchmod +x echoscript &&\n+\tgit interpret-trailers --trailer \"bug: him\" --trailer \"bug:me\" \\\n+\t\t>actual2 &&\n+\ttest_cmp expected2 actual2\n+'\n+\n+test_expect_success 'with cmd, $1 and without --trailer' '\n+\ttest_when_finished \"git config --remove-section trailer.bug\" &&\n+\ttest_when_finished \"git config --remove-section trailer.gub\" &&\n+\tgit config trailer.bug.key \"Bug-maker: \" &&\n+\tgit config trailer.bug.ifExists \"replace\" &&\n+\tgit config trailer.bug.cmd \"./echoscript\" &&\n+\tgit config trailer.gub.key \"Gub-maker: \" &&\n+\tgit config trailer.gub.ifExists \"replace\" &&\n+\tgit config trailer.gub.cmd \"./echoscript2\" &&\n+\tcat >expected2 <<-EOF &&\n+\n+\tGub-maker: si ohw\n+\tEOF\n+\tcat >echoscript <<-EOF &&\n+\t#!/bin/sh\n+\tif test \"\\$#\" != 1\n+\tthen\n+\t\texit 1\n+\telse\n+\t\techo who is \"\\$1\"\n+\tfi\n+\tEOF\n+\tcat >echoscript2 <<-EOF &&\n+\t\techo si ohw \"\\$1\"\n+\tEOF\n+\tchmod +x echoscript &&\n+\tchmod +x echoscript2 &&\n+\tgit interpret-trailers >actual2 &&\n+\ttest_cmp expected2 actual2\n+'\n+\n test_expect_success 'without config' '\n \tsed -e \"s/ Z\\$/ /\" >expected <<-\\EOF &&\n \n@@ -1274,6 +1375,27 @@ test_expect_success 'setup a commit' '\n \tgit commit -m \"Add file a.txt\"\n '\n \n+test_expect_success 'cmd takes precedence over command' '\n+\ttest_when_finished \"git config --unset trailer.fix.cmd\" &&\n+\tgit config trailer.fix.ifExists \"replace\" &&\n+\tgit config trailer.fix.cmd \"test -n \\\"\\$1\\\" && git log -1 --oneline --format=\\\"%h (%aN)\\\" \\\n+\t--abbrev-commit --abbrev=14 \\\"\\$1\\\" || true\" &&\n+\tgit config trailer.fix.command \"git log -1 --oneline --format=\\\"%h (%s)\\\" \\\n+\t\t--abbrev-commit --abbrev=14 \\$ARG\" &&\n+\tFIXED=$(git log -1 --oneline --format=\"%h (%aN)\" --abbrev-commit --abbrev=14 HEAD) &&\n+\tcat complex_message_body >expected2 &&\n+\tsed -e \"s/ Z\\$/ /\" >>expected2 <<-EOF &&\n+\t\tFixes: $FIXED\n+\t\tAcked-by= Z\n+\t\tReviewed-by:\n+\t\tSigned-off-by: Z\n+\t\tSigned-off-by: A U Thor <author@example.com>\n+\tEOF\n+\tgit interpret-trailers --trailer \"review:\" --trailer \"fix=HEAD\" \\\n+\t\t<complex_message >actual2 &&\n+\ttest_cmp expected2 actual2\n+'\n+\n test_expect_success 'with command using $ARG' '\n \tgit config trailer.fix.ifExists \"replace\" &&\n \tgit config trailer.fix.command \"git log -1 --oneline --format=\\\"%h (%s)\\\" --abbrev-commit --abbrev=14 \\$ARG\" &&\ndiff --git a/trailer.c b/trailer.c\nindex be4e9726421c..2f5f8f5b4b59 100644\n--- a/trailer.c\n+++ b/trailer.c\n@@ -14,6 +14,7 @@ struct conf_info {\n \tchar *name;\n \tchar *key;\n \tchar *command;\n+\tchar *cmd;\n \tenum trailer_where where;\n \tenum trailer_if_exists if_exists;\n \tenum trailer_if_missing if_missing;\n@@ -127,8 +128,10 @@ static void free_arg_item(struct arg_item *item)\n \tfree(item->conf.name);\n \tfree(item->conf.key);\n \tfree(item->conf.command);\n+\tfree(item->conf.cmd);\n \tfree(item->token);\n-\tfree(item->value);\n+\tif (item->value)\n+\t\tFREE_AND_NULL(item->value);\n \tfree(item);\n }\n \n@@ -216,26 +219,35 @@ static int check_if_different(struct trailer_item *in_tok,\n \treturn 1;\n }\n \n-static char *apply_command(const char *command, const char *arg)\n+static char *apply_command(struct conf_info *conf, const char *arg)\n {\n \tstruct strbuf cmd = STRBUF_INIT;\n \tstruct strbuf buf = STRBUF_INIT;\n \tstruct child_process cp = CHILD_PROCESS_INIT;\n \tchar *result;\n \n-\tstrbuf_addstr(&cmd, command);\n-\tif (arg)\n-\t\tstrbuf_replace(&cmd, TRAILER_ARG_STRING, arg);\n-\n-\tstrvec_push(&cp.args, cmd.buf);\n+\tif (conf->cmd) {\n+\t\tstrbuf_addstr(&cmd, conf->cmd);\n+\t\tstrvec_push(&cp.args, cmd.buf);\n+\t\tif (arg)\n+\t\t\tstrvec_push(&cp.args, arg);\n+\t} else if (conf->command) {\n+\t\tstrbuf_addstr(&cmd, conf->command);\n+\t\tif (arg)\n+\t\t\tstrbuf_replace(&cmd, TRAILER_ARG_STRING, arg);\n+\t\tstrvec_push(&cp.args, cmd.buf);\n+\t}\n \tcp.env = local_repo_env;\n \tcp.no_stdin = 1;\n \tcp.use_shell = 1;\n \n \tif (capture_command(&cp, &buf, 1024)) {\n-\t\terror(_(\"running trailer command '%s' failed\"), cmd.buf);\n \t\tstrbuf_release(&buf);\n-\t\tresult = xstrdup(\"\");\n+\t\tif (!conf->cmd || arg) {\n+\t\t\terror(_(\"running trailer command '%s' failed\"), cmd.buf);\n+\t\t\tresult = xstrdup(\"\");\n+\t\t} else\n+\t\t\tresult = NULL;\n \t} else {\n \t\tstrbuf_trim(&buf);\n \t\tresult = strbuf_detach(&buf, NULL);\n@@ -247,9 +259,11 @@ static char *apply_command(const char *command, const char *arg)\n \n static void apply_item_command(struct trailer_item *in_tok, struct arg_item *arg_tok)\n {\n-\tif (arg_tok->conf.command) {\n-\t\tconst char *arg;\n-\t\tif (arg_tok->value && arg_tok->value[0]) {\n+\tif (arg_tok->conf.command || arg_tok->conf.cmd) {\n+\t\tconst char *arg = NULL;\n+\n+\t\tif ((arg_tok->value && arg_tok->value[0]) ||\n+\t\t   (arg_tok->conf.cmd && !arg_tok->value)) {\n \t\t\targ = arg_tok->value;\n \t\t} else {\n \t\t\tif (in_tok && in_tok->value)\n@@ -257,8 +271,9 @@ static void apply_item_command(struct trailer_item *in_tok, struct arg_item *arg\n \t\t\telse\n \t\t\t\targ = xstrdup(\"\");\n \t\t}\n-\t\targ_tok->value = apply_command(arg_tok->conf.command, arg);\n-\t\tfree((char *)arg);\n+\t\targ_tok->value = apply_command(&arg_tok->conf, arg);\n+\t\tif (arg)\n+\t\t\tfree((char *)arg);\n \t}\n }\n \n@@ -273,16 +288,28 @@ static void apply_arg_if_exists(struct trailer_item *in_tok,\n \t\tbreak;\n \tcase EXISTS_REPLACE:\n \t\tapply_item_command(in_tok, arg_tok);\n+\t\tif (!arg_tok->value) {\n+\t\t\tfree_arg_item(arg_tok);\n+\t\t\treturn;\n+\t\t}\n \t\tadd_arg_to_input_list(on_tok, arg_tok);\n \t\tlist_del(&in_tok->list);\n \t\tfree_trailer_item(in_tok);\n \t\tbreak;\n \tcase EXISTS_ADD:\n \t\tapply_item_command(in_tok, arg_tok);\n+\t\tif (!arg_tok->value) {\n+\t\t\tfree_arg_item(arg_tok);\n+\t\t\treturn;\n+\t\t}\n \t\tadd_arg_to_input_list(on_tok, arg_tok);\n \t\tbreak;\n \tcase EXISTS_ADD_IF_DIFFERENT:\n \t\tapply_item_command(in_tok, arg_tok);\n+\t\tif (!arg_tok->value) {\n+\t\t\tfree_arg_item(arg_tok);\n+\t\t\treturn;\n+\t\t}\n \t\tif (check_if_different(in_tok, arg_tok, 1, head))\n \t\t\tadd_arg_to_input_list(on_tok, arg_tok);\n \t\telse\n@@ -290,6 +317,10 @@ static void apply_arg_if_exists(struct trailer_item *in_tok,\n \t\tbreak;\n \tcase EXISTS_ADD_IF_DIFFERENT_NEIGHBOR:\n \t\tapply_item_command(in_tok, arg_tok);\n+\t\tif (!arg_tok->value) {\n+\t\t\tfree_arg_item(arg_tok);\n+\t\t\treturn;\n+\t\t}\n \t\tif (check_if_different(on_tok, arg_tok, 0, head))\n \t\t\tadd_arg_to_input_list(on_tok, arg_tok);\n \t\telse\n@@ -314,6 +345,10 @@ static void apply_arg_if_missing(struct list_head *head,\n \tcase MISSING_ADD:\n \t\twhere = arg_tok->conf.where;\n \t\tapply_item_command(NULL, arg_tok);\n+\t\tif (!arg_tok->value) {\n+\t\t\tfree_arg_item(arg_tok);\n+\t\t\treturn;\n+\t\t}\n \t\tto_add = trailer_from_arg(arg_tok);\n \t\tif (after_or_end(where))\n \t\t\tlist_add_tail(&to_add->list, head);\n@@ -430,6 +465,7 @@ static void duplicate_conf(struct conf_info *dst, const struct conf_info *src)\n \tdst->name = xstrdup_or_null(src->name);\n \tdst->key = xstrdup_or_null(src->key);\n \tdst->command = xstrdup_or_null(src->command);\n+\tdst->cmd = xstrdup_or_null(src->cmd);\n }\n \n static struct arg_item *get_conf_item(const char *name)\n@@ -454,8 +490,8 @@ static struct arg_item *get_conf_item(const char *name)\n \treturn item;\n }\n \n-enum trailer_info_type { TRAILER_KEY, TRAILER_COMMAND, TRAILER_WHERE,\n-\t\t\t TRAILER_IF_EXISTS, TRAILER_IF_MISSING };\n+enum trailer_info_type { TRAILER_KEY, TRAILER_COMMAND, TRAILER_CMD,\n+\t\t\tTRAILER_WHERE, TRAILER_IF_EXISTS, TRAILER_IF_MISSING };\n \n static struct {\n \tconst char *name;\n@@ -463,6 +499,7 @@ static struct {\n } trailer_config_items[] = {\n \t{ \"key\", TRAILER_KEY },\n \t{ \"command\", TRAILER_COMMAND },\n+\t{ \"cmd\", TRAILER_CMD },\n \t{ \"where\", TRAILER_WHERE },\n \t{ \"ifexists\", TRAILER_IF_EXISTS },\n \t{ \"ifmissing\", TRAILER_IF_MISSING }\n@@ -542,6 +579,11 @@ static int git_trailer_config(const char *conf_key, const char *value, void *cb)\n \t\t\twarning(_(\"more than one %s\"), conf_key);\n \t\tconf->command = xstrdup(value);\n \t\tbreak;\n+\tcase TRAILER_CMD:\n+\t\tif (conf->cmd)\n+\t\t\twarning(_(\"more than one %s\"), conf_key);\n+\t\tconf->cmd = xstrdup(value);\n+\t\tbreak;\n \tcase TRAILER_WHERE:\n \t\tif (trailer_set_where(&conf->where, value))\n \t\t\twarning(_(\"unknown value '%s' for key '%s'\"), value, conf_key);\n@@ -713,6 +755,11 @@ static void process_command_line_args(struct list_head *arg_head,\n \t\t\t\t     xstrdup(token_from_item(item, NULL)),\n \t\t\t\t     xstrdup(\"\"),\n \t\t\t\t     &item->conf, NULL);\n+\t\telse if (item->conf.cmd)\n+\t\t\tadd_arg_item(arg_head,\n+\t\t\t\t     xstrdup(token_from_item(item, NULL)),\n+\t\t\t\t     NULL,\n+\t\t\t\t     &item->conf, NULL);\n \t}\n \n \t/* Add an arg item for each trailer on the command line */\n-- \ngitgitgadget\n"},{"id":"422195","messageId":"xmqq5z0kbl8x.fsf@gitster.g","threadId":"55371","inReplyTo":"pull.913.v11.git.1618672417.gitgitgadget@gmail.com","subject":"Re: [PATCH v11 0/2] [GSOC] trailer: add new .cmd config option","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2021-04-17T22:26:54Z","receivedAt":"2021-04-17T22:32:08Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"\"ZheNing Hu via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n\n> In https://lore.kernel.org/git/xmqqv99i4ck2.fsf@gitster.g/ Junio and\n> Christian talked about the problem of using strbuf_replace() to replace\n> $ARG:\n>\n>  1. if the user's script has more than one $ARG, only the first one will be\n>     replaced, which is incorrected.\n>  2. $ARG is textually replaced without shell syntax, which may result a\n>     broken command when $ARG include some unmatching single quote, very\n>     unsafe.\n>\n> Now pass trailer value as $1 to the trailer command with another\n> trailer.<token>.cmd config, to solve these above problems.\n>\n> We are now writing documents that are more readable and correct than before.\n\nHere is a good spot to summarize what changed since the previous\nround.\n\nIt seems that this now has \"exit non-zero to tell the caller not to\nadd the trailer for this execuation\".  Is that the only change you\nmade?\n\nI was hoping that we'd declare victory with what was in v10 (with\npossibly typos and minor stylistic fixes if needed---I no longer\nremember details), let it go through the usual course of cooking in\n'next' and merged down to 'master', and then after the dust settles,\nwe'd be adding this \"by exiting with non-zero status, scripts can\nsignal a trailer not to be added for a particular invocation\" as a\nnew feature, if it turns out to be necessary.\n\nBut let's see what's new in this iteration.\n\n\n>       +#!/bin/sh\n>      -+test -n \"$1\" && git shortlog -s --author=\"$1\" HEAD || true\n>      ++if test \"$#\" != 1\n>      ++then\n>      ++\texit 1\n>      ++else\n>      ++\ttest -n \"$1\" && git shortlog -s --author=\"$1\" HEAD || true\n>      ++fi\n\nI find this dubious.  Why not\n\n\tif test \"$#\" != 1 || test -z \"$1\"\n\tthen\n\t\texit 1\n\telse\n\t\tgit shortlog -s --author=\"$1\" HEAD\n\tfi\n\nThat is, if you happened to give an empty string, your version gives\n\"\" to <value> and returns success, letting a trailer \"cnt:\" with\nempty value.  Is that what we really want?\n\n>       +$ git config trailer.cnt.key \"Commit-count: \"\n>       +$ git config trailer.cnt.ifExists \"addIfDifferentNeighbor\"\n>       +$ git config trailer.cnt.cmd \"~/bin/gcount\"\n>       +$ git interpret-trailers --trailer=\"cnt:Junio\" --trailer=\"cnt:Linus Torvalds\"<<EOF\n>       +> subject\n>      -+> \n>      ++>\n>       +> message\n>      -+> \n>      ++>\n>       +> EOF\n>       +subject\n>       +\n>      @@ Documentation/git-interpret-trailers.txt: subject\n>       +------------\n>       +$ cat ~/bin/glog-grep\n>       +#!/bin/sh\n>      -+test -n \"$1\" && git log --grep \"$1\" --pretty=reference -1 || true\n>      ++if test \"$#\" != 1\n>      ++then\n>      ++\texit 1\n>      ++else\n>      ++\ttest -n \"$1\" && git log --grep \"$1\" --pretty=reference -1 || true\n>      ++fi\n\nDitto.\n\n>      + \tif (capture_command(&cp, &buf, 1024)) {\n>      +-\t\terror(_(\"running trailer command '%s' failed\"), cmd.buf);\n>      + \t\tstrbuf_release(&buf);\n>      +-\t\tresult = xstrdup(\"\");\n>      ++\t\tif (!conf->cmd || arg) {\n>      ++\t\t\terror(_(\"running trailer command '%s' failed\"), cmd.buf);\n\nI am not sure about this part.  If .cmd (the new style) exits with a\nnon-zero status for user-supplied --trailer=<token>:<value> (because\nit did not like the <value>), is that \"running failed\"?  The script\nis expected to express yes/no with its exit status, so I would say it\nis not failing, but successfully expressed its displeasure and vetoed\nthe particular trailer from getting added.  IOW, \"|| arg\" part in\nthe condition feels iffy to me.\n\n>      ++\t\t\tresult = xstrdup(\"\");\n>      ++\t\t} else\n>      ++\t\t\tresult = NULL;\n>      + \t} else {\n>      + \t\tstrbuf_trim(&buf);\n>      + \t\tresult = strbuf_detach(&buf, NULL);\n\nOK.\n"},{"id":"422201","messageId":"CAOLTT8RKCV+Kpya-_AVjuVGWzs1WtGS8n_+sD0FVzwEpeXGwCw@mail.gmail.com","threadId":"55371","inReplyTo":"xmqq5z0kbl8x.fsf@gitster.g","subject":"Re: [PATCH v11 0/2] [GSOC] trailer: add new .cmd config option","fromName":"ZheNing Hu","fromEmail":"adlternative@gmail.com","sentAt":"2021-04-18T07:47:54Z","receivedAt":"2021-04-18T07:48:13Z","isPatch":true,"sender":{"key":"adlternative@gmail.com","avatar":"https://avatars.githubusercontent.com/u/58138461?v=4"},"body":"Junio C Hamano <gitster@pobox.com> 于2021年4月18日周日 上午6:26写道：\n>\n> \"ZheNing Hu via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n>\n> > In https://lore.kernel.org/git/xmqqv99i4ck2.fsf@gitster.g/ Junio and\n> > Christian talked about the problem of using strbuf_replace() to replace\n> > $ARG:\n> >\n> >  1. if the user's script has more than one $ARG, only the first one will be\n> >     replaced, which is incorrected.\n> >  2. $ARG is textually replaced without shell syntax, which may result a\n> >     broken command when $ARG include some unmatching single quote, very\n> >     unsafe.\n> >\n> > Now pass trailer value as $1 to the trailer command with another\n> > trailer.<token>.cmd config, to solve these above problems.\n> >\n> > We are now writing documents that are more readable and correct than before.\n>\n> Here is a good spot to summarize what changed since the previous\n> round.\n>\n> It seems that this now has \"exit non-zero to tell the caller not to\n> add the trailer for this execuation\".  Is that the only change you\n> made?\n>\n\nYes, I think the more accurate one should be \"exit non-zero to tell\nthe caller not to add the trailer for this implicit execuation\", You also\nsaid below, it may not be so good.\n\n> I was hoping that we'd declare victory with what was in v10 (with\n> possibly typos and minor stylistic fixes if needed---I no longer\n> remember details), let it go through the usual course of cooking in\n> 'next' and merged down to 'master', and then after the dust settles,\n> we'd be adding this \"by exiting with non-zero status, scripts can\n> signal a trailer not to be added for a particular invocation\" as a\n> new feature, if it turns out to be necessary.\n>\n\nThanks for your and Christian's help this month!\n\nOK, I understand, then I can wait for a while until `trailer_cmd` merge\nto master.\n\n> But let's see what's new in this iteration.\n>\n>\n> >       +#!/bin/sh\n> >      -+test -n \"$1\" && git shortlog -s --author=\"$1\" HEAD || true\n> >      ++if test \"$#\" != 1\n> >      ++then\n> >      ++       exit 1\n> >      ++else\n> >      ++       test -n \"$1\" && git shortlog -s --author=\"$1\" HEAD || true\n> >      ++fi\n>\n> I find this dubious.  Why not\n>\n>         if test \"$#\" != 1 || test -z \"$1\"\n>         then\n>                 exit 1\n>         else\n>                 git shortlog -s --author=\"$1\" HEAD\n>         fi\n>\n> That is, if you happened to give an empty string, your version gives\n> \"\" to <value> and returns success, letting a trailer \"cnt:\" with\n> empty value.  Is that what we really want?\n\nIf it's the user use `--trailer=\"cnt:\"` instread of command implict running,\nI think keep it is right.\n\nIn fact, it should be said that it is equivalent to exit(0) if the user use\n`--trailer=\"cnt:\"`.\n\n>\n> >       +$ git config trailer.cnt.key \"Commit-count: \"\n> >       +$ git config trailer.cnt.ifExists \"addIfDifferentNeighbor\"\n> >       +$ git config trailer.cnt.cmd \"~/bin/gcount\"\n> >       +$ git interpret-trailers --trailer=\"cnt:Junio\" --trailer=\"cnt:Linus Torvalds\"<<EOF\n> >       +> subject\n> >      -+>\n> >      ++>\n> >       +> message\n> >      -+>\n> >      ++>\n> >       +> EOF\n> >       +subject\n> >       +\n> >      @@ Documentation/git-interpret-trailers.txt: subject\n> >       +------------\n> >       +$ cat ~/bin/glog-grep\n> >       +#!/bin/sh\n> >      -+test -n \"$1\" && git log --grep \"$1\" --pretty=reference -1 || true\n> >      ++if test \"$#\" != 1\n> >      ++then\n> >      ++       exit 1\n> >      ++else\n> >      ++       test -n \"$1\" && git log --grep \"$1\" --pretty=reference -1 || true\n> >      ++fi\n>\n> Ditto.\n>\n> >      +        if (capture_command(&cp, &buf, 1024)) {\n> >      +-               error(_(\"running trailer command '%s' failed\"), cmd.buf);\n> >      +                strbuf_release(&buf);\n> >      +-               result = xstrdup(\"\");\n> >      ++               if (!conf->cmd || arg) {\n> >      ++                       error(_(\"running trailer command '%s' failed\"), cmd.buf);\n>\n> I am not sure about this part.  If .cmd (the new style) exits with a\n> non-zero status for user-supplied --trailer=<token>:<value> (because\n> it did not like the <value>), is that \"running failed\"?  The script\n> is expected to express yes/no with its exit status, so I would say it\n> is not failing, but successfully expressed its displeasure and vetoed\n> the particular trailer from getting added.  IOW, \"|| arg\" part in\n> the condition feels iffy to me.\n>\n\nWell, you mean we can take advantage of non-zero exits instead of\njust removing implicitly executed content. I argee with you, this\nplace is worth change.\n\n> >      ++                       result = xstrdup(\"\");\n> >      ++               } else\n> >      ++                       result = NULL;\n> >      +        } else {\n> >      +                strbuf_trim(&buf);\n> >      +                result = strbuf_detach(&buf, NULL);\n>\n> OK.\n\nThanks.\n--\nZheNing Hu\n"},{"id":"422507","messageId":"xmqqfszk1ot6.fsf@gitster.g","threadId":"55371","inReplyTo":"CAOLTT8RKCV+Kpya-_AVjuVGWzs1WtGS8n_+sD0FVzwEpeXGwCw@mail.gmail.com","subject":"Re: [PATCH v11 0/2] [GSOC] trailer: add new .cmd config option","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2021-04-21T00:09:09Z","receivedAt":"2021-04-21T00:09:13Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"ZheNing Hu <adlternative@gmail.com> writes:\n\n> OK, I understand, then I can wait for a while until `trailer_cmd` merge\n> to master.\n>\n>> But let's see what's new in this iteration.\n>>\n>>\n>> >       +#!/bin/sh\n>> >      -+test -n \"$1\" && git shortlog -s --author=\"$1\" HEAD || true\n>> >      ++if test \"$#\" != 1\n>> >      ++then\n>> >      ++       exit 1\n>> >      ++else\n>> >      ++       test -n \"$1\" && git shortlog -s --author=\"$1\" HEAD || true\n>> >      ++fi\n>>\n>> I find this dubious.  Why not\n>>\n>>         if test \"$#\" != 1 || test -z \"$1\"\n>>         then\n>>                 exit 1\n>>         else\n>>                 git shortlog -s --author=\"$1\" HEAD\n>>         fi\n>>\n>> That is, if you happened to give an empty string, your version gives\n>> \"\" to <value> and returns success, letting a trailer \"cnt:\" with\n>> empty value.  Is that what we really want?\n>\n> If it's the user use `--trailer=\"cnt:\"` instread of command implict running,\n> I think keep it is right.\n\nNo, if you give an empty string, you'd end up running \"shortlog\"\nwith --author=\"\" and give whatever random number it comes up with,\nwhich I do not think is what you would want.\n\nThat is why --trailer=cnt: without name to match --author can be\nrejected with \"exit 1\" to demonstrate the feature.  The .cmd can\nsquelch not just the \"unasked for extra invocation\", but invocation\nfrom the command line whose <value> was bogus, unlike the .runmode\nfeature we've seen proposed earlier.\n\n>> >      +        if (capture_command(&cp, &buf, 1024)) {\n>> >      +-               error(_(\"running trailer command '%s' failed\"), cmd.buf);\n>> >      +                strbuf_release(&buf);\n>> >      +-               result = xstrdup(\"\");\n>> >      ++               if (!conf->cmd || arg) {\n>> >      ++                       error(_(\"running trailer command '%s' failed\"), cmd.buf);\n>>\n>> I am not sure about this part.  If .cmd (the new style) exits with a\n>> non-zero status for user-supplied --trailer=<token>:<value> (because\n>> it did not like the <value>), is that \"running failed\"?  The script\n>> is expected to express yes/no with its exit status, so I would say it\n>> is not failing, but successfully expressed its displeasure and vetoed\n>> the particular trailer from getting added.  IOW, \"|| arg\" part in\n>> the condition feels iffy to me.\n>\n> Well, you mean we can take advantage of non-zero exits instead of\n> just removing implicitly executed content. I argee with you, this\n> place is worth change.\n\nYup, that is what I meant.\n\nIn any case, let's see how well the base topic fares.\n\nThanks.\n"},{"id":"422526","messageId":"CAOLTT8QUA+m1W6-v=ZA205SZo8G5GBKMzJHBzU8DuQSTKiPUBw@mail.gmail.com","threadId":"55371","inReplyTo":"xmqqfszk1ot6.fsf@gitster.g","subject":"Re: [PATCH v11 0/2] [GSOC] trailer: add new .cmd config option","fromName":"ZheNing Hu","fromEmail":"adlternative@gmail.com","sentAt":"2021-04-21T05:47:38Z","receivedAt":"2021-04-21T05:47:52Z","isPatch":true,"sender":{"key":"adlternative@gmail.com","avatar":"https://avatars.githubusercontent.com/u/58138461?v=4"},"body":"Junio C Hamano <gitster@pobox.com> 于2021年4月21日周三 上午8:09写道：\n>\n> ZheNing Hu <adlternative@gmail.com> writes:\n>\n> > OK, I understand, then I can wait for a while until `trailer_cmd` merge\n> > to master.\n> >\n> >> But let's see what's new in this iteration.\n> >>\n> >>\n> >> >       +#!/bin/sh\n> >> >      -+test -n \"$1\" && git shortlog -s --author=\"$1\" HEAD || true\n> >> >      ++if test \"$#\" != 1\n> >> >      ++then\n> >> >      ++       exit 1\n> >> >      ++else\n> >> >      ++       test -n \"$1\" && git shortlog -s --author=\"$1\" HEAD || true\n> >> >      ++fi\n> >>\n> >> I find this dubious.  Why not\n> >>\n> >>         if test \"$#\" != 1 || test -z \"$1\"\n> >>         then\n> >>                 exit 1\n> >>         else\n> >>                 git shortlog -s --author=\"$1\" HEAD\n> >>         fi\n> >>\n> >> That is, if you happened to give an empty string, your version gives\n> >> \"\" to <value> and returns success, letting a trailer \"cnt:\" with\n> >> empty value.  Is that what we really want?\n> >\n> > If it's the user use `--trailer=\"cnt:\"` instread of command implict running,\n> > I think keep it is right.\n>\n> No, if you give an empty string, you'd end up running \"shortlog\"\n> with --author=\"\" and give whatever random number it comes up with,\n> which I do not think is what you would want.\n>\n> That is why --trailer=cnt: without name to match --author can be\n> rejected with \"exit 1\" to demonstrate the feature.  The .cmd can\n> squelch not just the \"unasked for extra invocation\", but invocation\n> from the command line whose <value> was bogus, unlike the .runmode\n> feature we've seen proposed earlier.\n>\n\nI admit that your idea makes sense, but we actually have another requirement:\nConstruct a trailer with an empty value.\n\nThe Commit-Count example above may not be good, Let’s take a look at the\nSigned-off-by.\n\ne.g. `--trailer=\"sign:\"`, we expect to output a \"Signed-off-by: \",\nthen we can fill it\nwith the \"name <email>\" pair we want, this is when we shouldn't return non-zero\nand suppress its output.\n\n> >> >      +        if (capture_command(&cp, &buf, 1024)) {\n> >> >      +-               error(_(\"running trailer command '%s' failed\"), cmd.buf);\n> >> >      +                strbuf_release(&buf);\n> >> >      +-               result = xstrdup(\"\");\n> >> >      ++               if (!conf->cmd || arg) {\n> >> >      ++                       error(_(\"running trailer command '%s' failed\"), cmd.buf);\n> >>\n> >> I am not sure about this part.  If .cmd (the new style) exits with a\n> >> non-zero status for user-supplied --trailer=<token>:<value> (because\n> >> it did not like the <value>), is that \"running failed\"?  The script\n> >> is expected to express yes/no with its exit status, so I would say it\n> >> is not failing, but successfully expressed its displeasure and vetoed\n> >> the particular trailer from getting added.  IOW, \"|| arg\" part in\n> >> the condition feels iffy to me.\n> >\n> > Well, you mean we can take advantage of non-zero exits instead of\n> > just removing implicitly executed content. I argee with you, this\n> > place is worth change.\n>\n> Yup, that is what I meant.\n>\n> In any case, let's see how well the base topic fares.\n>\n\nYes, I don't know if Christian agrees with temporary situation.\n\n> Thanks.\n\nThanks.\n--\nZheNing Hu\n"},{"id":"422645","messageId":"xmqq1rb3xl3t.fsf@gitster.g","threadId":"55371","inReplyTo":"CAOLTT8QUA+m1W6-v=ZA205SZo8G5GBKMzJHBzU8DuQSTKiPUBw@mail.gmail.com","subject":"Re: [PATCH v11 0/2] [GSOC] trailer: add new .cmd config option","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2021-04-21T23:40:22Z","receivedAt":"2021-04-21T23:40:26Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"ZheNing Hu <adlternative@gmail.com> writes:\n\n> I admit that your idea makes sense, but we actually have another requirement:\n> Construct a trailer with an empty value.\n\nIt can be done with a different script given to .cmd, which would\nsay \"exit 0\" when allowing an empty string given as its input to\nappear in the output.\n\nI was reacting what the \"count\" example does, and found that\ncounting commits by all authors (that is what an empty string would\nmatch when given to --author=\"\") somewhat illogical in the context\nof that example.\n\nAfter all, these examples are to pique readers' interest by\ndemonstrating what the mechanism can do and how it can be used, and\nfor this feature, I think showing that\n\n (1) we can squelch the output from unasked-for execution;\n\n (2) we can reject --trailer=<key>:<value> when we do not like the\n     given <value>;\n\n (3) we can insert the trailer with the value we compute (and it is\n     OK for the computed result happens to be an empty string).\n\nshould be plenty sufficient.\n"},{"id":"422671","messageId":"CAOLTT8TciU7rvmEgXGMuVCMDN6HjMadL-L=Q9kej6=j6ktJHwA@mail.gmail.com","threadId":"55371","inReplyTo":"xmqq1rb3xl3t.fsf@gitster.g","subject":"Re: [PATCH v11 0/2] [GSOC] trailer: add new .cmd config option","fromName":"ZheNing Hu","fromEmail":"adlternative@gmail.com","sentAt":"2021-04-22T09:20:54Z","receivedAt":"2021-04-22T09:21:09Z","isPatch":true,"sender":{"key":"adlternative@gmail.com","avatar":"https://avatars.githubusercontent.com/u/58138461?v=4"},"body":"Junio C Hamano <gitster@pobox.com> 于2021年4月22日周四 上午7:40写道：\n>\n> ZheNing Hu <adlternative@gmail.com> writes:\n>\n> > I admit that your idea makes sense, but we actually have another requirement:\n> > Construct a trailer with an empty value.\n>\n> It can be done with a different script given to .cmd, which would\n> say \"exit 0\" when allowing an empty string given as its input to\n> appear in the output.\n>\n\nNow I think that we should keep those trailers which ask for a\n\"name <email>\" pair, like \"Helped-by\", \"Signed-off-by\", when we\nprovide a \"help:\",\"sign:\" in command line, This allows the user to\ndynamically fill in the \"name <email>\" pair of other people in the\ncommit message later. It is worthwhile for users to exit with exit(0).\n\nBut those dispensable things like \"Commit-Count\", It must depend\non a person's statistics in the git repository. So \"cnt:\" is meaningless,\nusers' script can let it exit(1).\n\n> I was reacting what the \"count\" example does, and found that\n> counting commits by all authors (that is what an empty string would\n> match when given to --author=\"\") somewhat illogical in the context\n> of that example.\n>\n\nThe \"Commit-Count\" in the example here can only target a specific person,\nwhich has great limitations.\n\nI have a bold idea:\n\nOur current --trailer can only fill in one data item, and we don't\nexpect it to fill\nin multiple rows. something like \"Commit-Count\", we hope to count the number of\ncommits from everyone. But as we can see:\n\nCommit-count: 7 Linus Arver\n  1117  Linus Torvalds\n\nSo If we have the opportunity to capture every line or every \"block\" of content,\nand feed it to git interpret-trailer, maybe we can get something like:\n\nCommit-count: 7 Linus Arver\nCommit-count: 1117  Linus Torvalds\n\nThis will definitely make it easy for us to generate a lot of trailer at once.\nFor example, a newbie like me, after making a patch, want to --cc all authors\nof one file, maybe I only need a command to get it.\n\nI don't know if it's a bit whimsical.\n\n> After all, these examples are to pique readers' interest by\n> demonstrating what the mechanism can do and how it can be used, and\n> for this feature, I think showing that\n>\n>  (1) we can squelch the output from unasked-for execution;\n>\n>  (2) we can reject --trailer=<key>:<value> when we do not like the\n>      given <value>;\n>\n>  (3) we can insert the trailer with the value we compute (and it is\n>      OK for the computed result happens to be an empty string).\n>\n> should be plenty sufficient.\n\nOK. I will add these three examples in the new patch (when .cmd merge to\nmaster).\n\nThanks.\n--\nZheNing Hu\n"},{"id":"423058","messageId":"xmqq35vcfchn.fsf@gitster.g","threadId":"55371","inReplyTo":"CAOLTT8TciU7rvmEgXGMuVCMDN6HjMadL-L=Q9kej6=j6ktJHwA@mail.gmail.com","subject":"Re: [PATCH v11 0/2] [GSOC] trailer: add new .cmd config option","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2021-04-27T06:49:40Z","receivedAt":"2021-04-27T06:49:44Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"ZheNing Hu <adlternative@gmail.com> writes:\n\n> Now I think that we should keep those trailers which ask for a\n> \"name <email>\" pair, like \"Helped-by\", \"Signed-off-by\", when we\n> provide a \"help:\",\"sign:\" in command line, This allows the user to\n> dynamically fill in the \"name <email>\" pair of other people in the\n> commit message later. It is worthwhile for users to exit with exit(0).\n>\n> But those dispensable things like \"Commit-Count\", It must depend\n> on a person's statistics in the git repository. So \"cnt:\" is meaningless,\n> users' script can let it exit(1).\n\nPerhaps, but at this point what you think (or what I think) does not\nmatter.  That was the whole point of letting .cmd script signal Git\nif the result from the invocation should be kept or discarded with\nits exit status.  What would be sufficient here for us to do is to\nagree that it would be good to have a minimal set (perhaps a pair)\nof examples to demonstrate that the script can choose to keep or\ndiscard a meaningless trailer entry with its exit status.\n"},{"id":"423102","messageId":"CAOLTT8QrX3sk1U6WM983pachj8q4fmW7jxEViXd4-Y-aSR1JeQ@mail.gmail.com","threadId":"55371","inReplyTo":"xmqq35vcfchn.fsf@gitster.g","subject":"Re: [PATCH v11 0/2] [GSOC] trailer: add new .cmd config option","fromName":"ZheNing Hu","fromEmail":"adlternative@gmail.com","sentAt":"2021-04-27T12:24:32Z","receivedAt":"2021-04-27T12:24:47Z","isPatch":true,"sender":{"key":"adlternative@gmail.com","avatar":"https://avatars.githubusercontent.com/u/58138461?v=4"},"body":"Junio C Hamano <gitster@pobox.com> 于2021年4月27日周二 下午2:49写道：\n>\n> ZheNing Hu <adlternative@gmail.com> writes:\n>\n> > Now I think that we should keep those trailers which ask for a\n> > \"name <email>\" pair, like \"Helped-by\", \"Signed-off-by\", when we\n> > provide a \"help:\",\"sign:\" in command line, This allows the user to\n> > dynamically fill in the \"name <email>\" pair of other people in the\n> > commit message later. It is worthwhile for users to exit with exit(0).\n> >\n> > But those dispensable things like \"Commit-Count\", It must depend\n> > on a person's statistics in the git repository. So \"cnt:\" is meaningless,\n> > users' script can let it exit(1).\n>\n> Perhaps, but at this point what you think (or what I think) does not\n> matter.  That was the whole point of letting .cmd script signal Git\n> if the result from the invocation should be kept or discarded with\n> its exit status.  What would be sufficient here for us to do is to\n> agree that it would be good to have a minimal set (perhaps a pair)\n> of examples to demonstrate that the script can choose to keep or\n> discard a meaningless trailer entry with its exit status.\n\nYes, I argee.\nDue to previous attempts, it seems that such an example is well given:\n\"Commit-Count\" is the trailer that should be discarded.\n\"Signed-off-by\" is the trailer worth be kept.\n\nThanks.\n--\nZheNing Hu\n"},{"id":"423504","messageId":"pull.913.v12.git.1620056466.gitgitgadget@gmail.com","threadId":"55371","inReplyTo":"pull.913.v11.git.1618672417.gitgitgadget@gmail.com","subject":"[PATCH v12 0/2] [GSOC] trailer: add new .cmd config option","fromName":"ZheNing Hu via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2021-05-03T15:41:03Z","receivedAt":"2021-05-03T15:41:13Z","isPatch":true,"sender":{"key":"adlternative@gmail.com","avatar":"https://avatars.githubusercontent.com/u/58138461?v=4"},"body":"In https://lore.kernel.org/git/xmqqv99i4ck2.fsf@gitster.g/ Junio and\nChristian talked about the problem of using strbuf_replace() to replace\n$ARG:\n\n 1. if the user's script has more than one $ARG, only the first one will be\n    replaced, which is incorrected.\n 2. $ARG is textually replaced without shell syntax, which may result a\n    broken command when $ARG include some unmatching single quote, very\n    unsafe.\n\nNow pass trailer value as $1 to the trailer command with another\ntrailer.<token>.cmd config, to solve these above problems.\n\nWe are now writing documents that are more readable and correct than before.\n\nChange from last version: Change docs example \"Count-count\" to \"Helped-by\".\n\nZheNing Hu (2):\n  [GSOC] docs: correct descript of trailer.<token>.command\n  [GSOC] trailer: add new .cmd config option\n\n Documentation/git-interpret-trailers.txt | 94 ++++++++++++++++++++----\n t/t7513-interpret-trailers.sh            | 84 +++++++++++++++++++++\n trailer.c                                | 35 ++++++---\n 3 files changed, 187 insertions(+), 26 deletions(-)\n\n\nbase-commit: 142430338477d9d1bb25be66267225fb58498d92\nPublished-As: https://github.com/gitgitgadget/git/releases/tag/pr-913%2Fadlternative%2Ftrailer-pass-ARG-env-v12\nFetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-913/adlternative/trailer-pass-ARG-env-v12\nPull-Request: https://github.com/gitgitgadget/git/pull/913\n\nRange-diff vs v11:\n\n 1:  34210e5bd3da ! 1:  8129ef6c476b [GSOC] docs: correct description of .command\n     @@ Metadata\n      Author: ZheNing Hu <adlternative@gmail.com>\n      \n       ## Commit message ##\n     -    [GSOC] docs: correct description of .command\n     +    [GSOC] docs: correct descript of trailer.<token>.command\n      \n          In the original documentation of `trailer.<token>.command`,\n          some descriptions are easily misunderstood. So let's modify\n 2:  9c0fc91aba24 ! 2:  edb7f1961ddb [GSOC] trailer: add new .cmd config option\n     @@ Commit message\n          replaced with the value given to the `interpret-trailer`\n          command for the token in a '--trailer <token>=<value>' argument.\n      \n     -    This has two downsides:\n     +    This has three downsides:\n      \n          * The use of $ARG in the mechanism misleads the users that\n          the value is passed in the shell variable, and tempt them\n     @@ Commit message\n          a broken command that is not syntactically correct (or\n          worse).\n      \n     +    * The first occurrence of substring `$ARG` will be replaced\n     +    with the empty string, in the command when the command is\n     +    first called to add a trailer with the specified <token>.\n     +    This is a bad design, the nature of automatic execution\n     +    causes it to add a trailer that we don't expect.\n     +\n          Introduce a new `trailer.<token>.cmd` configuration that\n          takes higher precedence to deprecate and eventually remove\n          `trailer.<token>.command`, which passes the value as an\n     @@ Commit message\n          refer to the value as positional argument, $1, in their\n          scripts. At the same time, in order to allow\n          `git interpret-trailers` to better simulate the behavior\n     -    of `git command -s`, the first implicitly executed command\n     -    will not pass positional parameters, users can use this\n     -    feature to suppress its output.\n     +    of `git command -s`, 'trailer.<token>.cmd' will not\n     +    automatically execute.\n      \n          Helped-by: Junio C Hamano <gitster@pobox.com>\n          Helped-by: Christian Couder <christian.couder@gmail.com>\n     @@ Documentation/git-interpret-trailers.txt: leading and trailing whitespace trimme\n      -occurrence of substring `$ARG` in the command. This way the\n      -command can produce a <value> computed from the <value> passed\n      -in the '--trailer <token>=<value>' argument.\n     -+of these arguments, if any, will be passed to the command as its\n     -+first argument. This way the command can produce a <value> computed\n     -+from the <value> passed in the '--trailer <token>=<value>' argument.\n     - +\n     +-+\n      -For consistency, the first occurrence of substring `$ARG` is\n      -also replaced, this time with the empty string, in the command\n      -when the command is first called to add a trailer with the\n      -specified <token>.\n     -+It is worth mentioning that the command is first called to add a\n     -+trailer with the specified <token> and without positional argument.\n     -+Users can make use of this output when they need automatically add\n     -+some trailers. On the other hand, users can use a trick to suppress\n     -+this output by judging whether the number of positional parameters\n     -+is equal to one, if it is true, execute the commands, otherwise exit\n     -+with non-zero to suppress the output.\n     ++of these arguments, if any, will be passed to the command as its\n     ++first argument. This way the command can produce a <value> computed\n     ++from the <value> passed in the '--trailer <token>=<value>' argument.\n       \n       EXAMPLES\n       --------\n     @@ Documentation/git-interpret-trailers.txt: subject\n       Fix #42\n       ------------\n       \n     -+* Configure a 'cnt' trailer with a cmd use a global script `gcount`\n     -+to record commit counts of a specified author and show how it works:\n     ++* Configure a 'help' trailer with a cmd use a script `glog-find-author`\n     ++  which search specified author identity from git log in git repository\n     ++  and show how it works:\n      ++\n      +------------\n     -+$ cat ~/bin/gcount\n     ++$ cat ~/bin/glog-find-author\n      +#!/bin/sh\n     -+if test \"$#\" != 1\n     -+then\n     -+\texit 1\n     -+else\n     -+\ttest -n \"$1\" && git shortlog -s --author=\"$1\" HEAD || true\n     -+fi\n     -+$ git config trailer.cnt.key \"Commit-count: \"\n     -+$ git config trailer.cnt.ifExists \"addIfDifferentNeighbor\"\n     -+$ git config trailer.cnt.cmd \"~/bin/gcount\"\n     -+$ git interpret-trailers --trailer=\"cnt:Junio\" --trailer=\"cnt:Linus Torvalds\"<<EOF\n     ++test -n \"$1\" && git log --author=\"$1\" --pretty=\"%an <%ae>\" -1 || true\n     ++$ git config trailer.help.key \"Helped-by: \"\n     ++$ git config trailer.help.ifExists \"addIfDifferentNeighbor\"\n     ++$ git config trailer.help.cmd \"~/bin/glog-find-author\"\n     ++$ git interpret-trailers --trailer=\"help:Junio\" --trailer=\"help:Couder\" <<EOF\n      +> subject\n     -+>\n     ++> \n      +> message\n     -+>\n     ++> \n      +> EOF\n      +subject\n      +\n      +message\n      +\n     -+Commit-count: 22484     Junio C Hamano\n     -+Commit-count: 1117      Linus Torvalds\n     ++Helped-by: Junio C Hamano <gitster@pobox.com>\n     ++Helped-by: Christian Couder <christian.couder@gmail.com>\n      +------------\n      +\n     -+* Configure a 'ref' trailer with a cmd use a global script `glog-grep`\n     ++* Configure a 'ref' trailer with a cmd use a script `glog-grep`\n      +  to grep last relevant commit from git log in the git repository\n      +  and show how it works:\n      ++\n      +------------\n      +$ cat ~/bin/glog-grep\n      +#!/bin/sh\n     -+if test \"$#\" != 1\n     -+then\n     -+\texit 1\n     -+else\n     -+\ttest -n \"$1\" && git log --grep \"$1\" --pretty=reference -1 || true\n     -+fi\n     ++test -n \"$1\" && git log --grep \"$1\" --pretty=reference -1 || true\n      +$ git config trailer.ref.key \"Reference-to: \"\n      +$ git config trailer.ref.ifExists \"replace\"\n      +$ git config trailer.ref.cmd \"~/bin/glog-grep\"\n      +$ git interpret-trailers --trailer=\"ref:Add copyright notices.\" <<EOF\n      +> subject\n     -+>\n     ++> \n      +> message\n     -+>\n     ++> \n      +> EOF\n      +subject\n      +\n     @@ t/t7513-interpret-trailers.sh: test_expect_success 'setup' '\n      +\tgit config trailer.bug.cmd \"echo \\\"maybe is\\\"\" &&\n      +\tcat >expected2 <<-EOF &&\n      +\n     -+\tBug-maker: maybe is\n      +\tBug-maker: maybe is him\n      +\tBug-maker: maybe is me\n      +\tEOF\n     @@ t/t7513-interpret-trailers.sh: test_expect_success 'setup' '\n      +\tgit config trailer.bug.cmd \"echo \\\"\\$1\\\" is\" &&\n      +\tcat >expected2 <<-EOF &&\n      +\n     -+\tBug-maker: is\n      +\tBug-maker: him is him\n      +\tBug-maker: me is me\n      +\tEOF\n     @@ t/t7513-interpret-trailers.sh: test_expect_success 'setup' '\n      +\tEOF\n      +\tcat >echoscript <<-EOF &&\n      +\t#!/bin/sh\n     -+\tif test \"\\$#\" != 1\n     -+\tthen\n     -+\t\texit 1\n     -+\telse\n     -+\t\techo who is \"\\$1\"\n     -+\tfi\n     ++\techo who is \"\\$1\"\n      +\tEOF\n      +\tchmod +x echoscript &&\n      +\tgit interpret-trailers --trailer \"bug: him\" --trailer \"bug:me\" \\\n      +\t\t>actual2 &&\n      +\ttest_cmp expected2 actual2\n      +'\n     -+\n     -+test_expect_success 'with cmd, $1 and without --trailer' '\n     -+\ttest_when_finished \"git config --remove-section trailer.bug\" &&\n     -+\ttest_when_finished \"git config --remove-section trailer.gub\" &&\n     -+\tgit config trailer.bug.key \"Bug-maker: \" &&\n     -+\tgit config trailer.bug.ifExists \"replace\" &&\n     -+\tgit config trailer.bug.cmd \"./echoscript\" &&\n     -+\tgit config trailer.gub.key \"Gub-maker: \" &&\n     -+\tgit config trailer.gub.ifExists \"replace\" &&\n     -+\tgit config trailer.gub.cmd \"./echoscript2\" &&\n     -+\tcat >expected2 <<-EOF &&\n     -+\n     -+\tGub-maker: si ohw\n     -+\tEOF\n     -+\tcat >echoscript <<-EOF &&\n     -+\t#!/bin/sh\n     -+\tif test \"\\$#\" != 1\n     -+\tthen\n     -+\t\texit 1\n     -+\telse\n     -+\t\techo who is \"\\$1\"\n     -+\tfi\n     -+\tEOF\n     -+\tcat >echoscript2 <<-EOF &&\n     -+\t\techo si ohw \"\\$1\"\n     -+\tEOF\n     -+\tchmod +x echoscript &&\n     -+\tchmod +x echoscript2 &&\n     -+\tgit interpret-trailers >actual2 &&\n     -+\ttest_cmp expected2 actual2\n     -+'\n      +\n       test_expect_success 'without config' '\n       \tsed -e \"s/ Z\\$/ /\" >expected <<-\\EOF &&\n     @@ trailer.c: static void free_arg_item(struct arg_item *item)\n       \tfree(item->conf.command);\n      +\tfree(item->conf.cmd);\n       \tfree(item->token);\n     --\tfree(item->value);\n     -+\tif (item->value)\n     -+\t\tFREE_AND_NULL(item->value);\n     + \tfree(item->value);\n       \tfree(item);\n     - }\n     - \n      @@ trailer.c: static int check_if_different(struct trailer_item *in_tok,\n       \treturn 1;\n       }\n     @@ trailer.c: static int check_if_different(struct trailer_item *in_tok,\n       \tcp.env = local_repo_env;\n       \tcp.no_stdin = 1;\n       \tcp.use_shell = 1;\n     - \n     - \tif (capture_command(&cp, &buf, 1024)) {\n     --\t\terror(_(\"running trailer command '%s' failed\"), cmd.buf);\n     - \t\tstrbuf_release(&buf);\n     --\t\tresult = xstrdup(\"\");\n     -+\t\tif (!conf->cmd || arg) {\n     -+\t\t\terror(_(\"running trailer command '%s' failed\"), cmd.buf);\n     -+\t\t\tresult = xstrdup(\"\");\n     -+\t\t} else\n     -+\t\t\tresult = NULL;\n     - \t} else {\n     - \t\tstrbuf_trim(&buf);\n     - \t\tresult = strbuf_detach(&buf, NULL);\n      @@ trailer.c: static char *apply_command(const char *command, const char *arg)\n       \n       static void apply_item_command(struct trailer_item *in_tok, struct arg_item *arg_tok)\n       {\n      -\tif (arg_tok->conf.command) {\n     --\t\tconst char *arg;\n     --\t\tif (arg_tok->value && arg_tok->value[0]) {\n      +\tif (arg_tok->conf.command || arg_tok->conf.cmd) {\n     -+\t\tconst char *arg = NULL;\n     -+\n     -+\t\tif ((arg_tok->value && arg_tok->value[0]) ||\n     -+\t\t   (arg_tok->conf.cmd && !arg_tok->value)) {\n     + \t\tconst char *arg;\n     + \t\tif (arg_tok->value && arg_tok->value[0]) {\n       \t\t\targ = arg_tok->value;\n     - \t\t} else {\n     - \t\t\tif (in_tok && in_tok->value)\n      @@ trailer.c: static void apply_item_command(struct trailer_item *in_tok, struct arg_item *arg\n       \t\t\telse\n       \t\t\t\targ = xstrdup(\"\");\n       \t\t}\n      -\t\targ_tok->value = apply_command(arg_tok->conf.command, arg);\n     --\t\tfree((char *)arg);\n      +\t\targ_tok->value = apply_command(&arg_tok->conf, arg);\n     -+\t\tif (arg)\n     -+\t\t\tfree((char *)arg);\n     + \t\tfree((char *)arg);\n       \t}\n       }\n     - \n     -@@ trailer.c: static void apply_arg_if_exists(struct trailer_item *in_tok,\n     - \t\tbreak;\n     - \tcase EXISTS_REPLACE:\n     - \t\tapply_item_command(in_tok, arg_tok);\n     -+\t\tif (!arg_tok->value) {\n     -+\t\t\tfree_arg_item(arg_tok);\n     -+\t\t\treturn;\n     -+\t\t}\n     - \t\tadd_arg_to_input_list(on_tok, arg_tok);\n     - \t\tlist_del(&in_tok->list);\n     - \t\tfree_trailer_item(in_tok);\n     - \t\tbreak;\n     - \tcase EXISTS_ADD:\n     - \t\tapply_item_command(in_tok, arg_tok);\n     -+\t\tif (!arg_tok->value) {\n     -+\t\t\tfree_arg_item(arg_tok);\n     -+\t\t\treturn;\n     -+\t\t}\n     - \t\tadd_arg_to_input_list(on_tok, arg_tok);\n     - \t\tbreak;\n     - \tcase EXISTS_ADD_IF_DIFFERENT:\n     - \t\tapply_item_command(in_tok, arg_tok);\n     -+\t\tif (!arg_tok->value) {\n     -+\t\t\tfree_arg_item(arg_tok);\n     -+\t\t\treturn;\n     -+\t\t}\n     - \t\tif (check_if_different(in_tok, arg_tok, 1, head))\n     - \t\t\tadd_arg_to_input_list(on_tok, arg_tok);\n     - \t\telse\n     -@@ trailer.c: static void apply_arg_if_exists(struct trailer_item *in_tok,\n     - \t\tbreak;\n     - \tcase EXISTS_ADD_IF_DIFFERENT_NEIGHBOR:\n     - \t\tapply_item_command(in_tok, arg_tok);\n     -+\t\tif (!arg_tok->value) {\n     -+\t\t\tfree_arg_item(arg_tok);\n     -+\t\t\treturn;\n     -+\t\t}\n     - \t\tif (check_if_different(on_tok, arg_tok, 0, head))\n     - \t\t\tadd_arg_to_input_list(on_tok, arg_tok);\n     - \t\telse\n     -@@ trailer.c: static void apply_arg_if_missing(struct list_head *head,\n     - \tcase MISSING_ADD:\n     - \t\twhere = arg_tok->conf.where;\n     - \t\tapply_item_command(NULL, arg_tok);\n     -+\t\tif (!arg_tok->value) {\n     -+\t\t\tfree_arg_item(arg_tok);\n     -+\t\t\treturn;\n     -+\t\t}\n     - \t\tto_add = trailer_from_arg(arg_tok);\n     - \t\tif (after_or_end(where))\n     - \t\t\tlist_add_tail(&to_add->list, head);\n      @@ trailer.c: static void duplicate_conf(struct conf_info *dst, const struct conf_info *src)\n       \tdst->name = xstrdup_or_null(src->name);\n       \tdst->key = xstrdup_or_null(src->key);\n     @@ trailer.c: static int git_trailer_config(const char *conf_key, const char *value\n       \tcase TRAILER_WHERE:\n       \t\tif (trailer_set_where(&conf->where, value))\n       \t\t\twarning(_(\"unknown value '%s' for key '%s'\"), value, conf_key);\n     -@@ trailer.c: static void process_command_line_args(struct list_head *arg_head,\n     - \t\t\t\t     xstrdup(token_from_item(item, NULL)),\n     - \t\t\t\t     xstrdup(\"\"),\n     - \t\t\t\t     &item->conf, NULL);\n     -+\t\telse if (item->conf.cmd)\n     -+\t\t\tadd_arg_item(arg_head,\n     -+\t\t\t\t     xstrdup(token_from_item(item, NULL)),\n     -+\t\t\t\t     NULL,\n     -+\t\t\t\t     &item->conf, NULL);\n     - \t}\n     - \n     - \t/* Add an arg item for each trailer on the command line */\n\n-- \ngitgitgadget\n"},{"id":"423506","messageId":"edb7f1961ddbcd64bdc35769c88d6b7be57bb016.1620056466.git.gitgitgadget@gmail.com","threadId":"55371","inReplyTo":"pull.913.v12.git.1620056466.gitgitgadget@gmail.com","subject":"[PATCH v12 2/2] [GSOC] trailer: add new .cmd config option","fromName":"ZheNing Hu via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2021-05-03T15:41:05Z","receivedAt":"2021-05-03T15:41:16Z","isPatch":true,"sender":{"key":"adlternative@gmail.com","avatar":"https://avatars.githubusercontent.com/u/58138461?v=4"},"body":"From: ZheNing Hu <adlternative@gmail.com>\n\nThe `trailer.<token>.command` configuration variable\nspecifies a command (run via the shell, so it does not have\nto be a single name or path to the command, but can be a\nshell script), and the first occurrence of substring $ARG is\nreplaced with the value given to the `interpret-trailer`\ncommand for the token in a '--trailer <token>=<value>' argument.\n\nThis has three downsides:\n\n* The use of $ARG in the mechanism misleads the users that\nthe value is passed in the shell variable, and tempt them\nto use $ARG more than once, but that would not work, as\nthe second and subsequent $ARG are not replaced.\n\n* Because $ARG is textually replaced without regard to the\nshell language syntax, even '$ARG' (inside a single-quote\npair), which a user would expect to stay intact, would be\nreplaced, and worse, if the value had an unmatched single\nquote (imagine a name like \"O'Connor\", substituted into\nNAME='$ARG' to make it NAME='O'Connor'), it would result in\na broken command that is not syntactically correct (or\nworse).\n\n* The first occurrence of substring `$ARG` will be replaced\nwith the empty string, in the command when the command is\nfirst called to add a trailer with the specified <token>.\nThis is a bad design, the nature of automatic execution\ncauses it to add a trailer that we don't expect.\n\nIntroduce a new `trailer.<token>.cmd` configuration that\ntakes higher precedence to deprecate and eventually remove\n`trailer.<token>.command`, which passes the value as an\nargument to the command.  Instead of \"$ARG\", users can\nrefer to the value as positional argument, $1, in their\nscripts. At the same time, in order to allow\n`git interpret-trailers` to better simulate the behavior\nof `git command -s`, 'trailer.<token>.cmd' will not\nautomatically execute.\n\nHelped-by: Junio C Hamano <gitster@pobox.com>\nHelped-by: Christian Couder <christian.couder@gmail.com>\nSigned-off-by: ZheNing Hu <adlternative@gmail.com>\n---\n Documentation/git-interpret-trailers.txt | 75 ++++++++++++++++++---\n t/t7513-interpret-trailers.sh            | 84 ++++++++++++++++++++++++\n trailer.c                                | 35 +++++++---\n 3 files changed, 175 insertions(+), 19 deletions(-)\n\ndiff --git a/Documentation/git-interpret-trailers.txt b/Documentation/git-interpret-trailers.txt\nindex 6f2a7a130464..1f76e0270053 100644\n--- a/Documentation/git-interpret-trailers.txt\n+++ b/Documentation/git-interpret-trailers.txt\n@@ -232,6 +232,20 @@ trailer.<token>.ifmissing::\n \tthat option for trailers with the specified <token>.\n \n trailer.<token>.command::\n+\tThis option behaves in the same way as 'trailer.<token>.cmd', except\n+\tthat it doesn't pass anything as argument to the specified command.\n+\tInstead the first occurrence of substring $ARG is replaced by the\n+\tvalue that would be passed as argument.\n++\n+The 'trailer.<token>.command' option has been deprecated in favor of\n+'trailer.<token>.cmd' due to the fact that $ARG in the user's command is\n+only replaced once and that the original way of replacing $ARG is not safe.\n++\n+When both 'trailer.<token>.cmd' and 'trailer.<token>.command' are given\n+for the same <token>, 'trailer.<token>.cmd' is used and\n+'trailer.<token>.command' is ignored.\n+\n+trailer.<token>.cmd::\n \tThis option can be used to specify a shell command that will be called:\n \tonce to automatically add a trailer with the specified <token>, and then\n \teach time a '--trailer <token>=<value>' argument to modify the <value> of\n@@ -247,15 +261,9 @@ leading and trailing whitespace trimmed off.\n If some '--trailer <token>=<value>' arguments are also passed\n on the command line, the command is called again once for each\n of these arguments with the same <token>. And the <value> part\n-of these arguments, if any, will be used to replace the first\n-occurrence of substring `$ARG` in the command. This way the\n-command can produce a <value> computed from the <value> passed\n-in the '--trailer <token>=<value>' argument.\n-+\n-For consistency, the first occurrence of substring `$ARG` is\n-also replaced, this time with the empty string, in the command\n-when the command is first called to add a trailer with the\n-specified <token>.\n+of these arguments, if any, will be passed to the command as its\n+first argument. This way the command can produce a <value> computed\n+from the <value> passed in the '--trailer <token>=<value>' argument.\n \n EXAMPLES\n --------\n@@ -338,6 +346,55 @@ subject\n Fix #42\n ------------\n \n+* Configure a 'help' trailer with a cmd use a script `glog-find-author`\n+  which search specified author identity from git log in git repository\n+  and show how it works:\n++\n+------------\n+$ cat ~/bin/glog-find-author\n+#!/bin/sh\n+test -n \"$1\" && git log --author=\"$1\" --pretty=\"%an <%ae>\" -1 || true\n+$ git config trailer.help.key \"Helped-by: \"\n+$ git config trailer.help.ifExists \"addIfDifferentNeighbor\"\n+$ git config trailer.help.cmd \"~/bin/glog-find-author\"\n+$ git interpret-trailers --trailer=\"help:Junio\" --trailer=\"help:Couder\" <<EOF\n+> subject\n+> \n+> message\n+> \n+> EOF\n+subject\n+\n+message\n+\n+Helped-by: Junio C Hamano <gitster@pobox.com>\n+Helped-by: Christian Couder <christian.couder@gmail.com>\n+------------\n+\n+* Configure a 'ref' trailer with a cmd use a script `glog-grep`\n+  to grep last relevant commit from git log in the git repository\n+  and show how it works:\n++\n+------------\n+$ cat ~/bin/glog-grep\n+#!/bin/sh\n+test -n \"$1\" && git log --grep \"$1\" --pretty=reference -1 || true\n+$ git config trailer.ref.key \"Reference-to: \"\n+$ git config trailer.ref.ifExists \"replace\"\n+$ git config trailer.ref.cmd \"~/bin/glog-grep\"\n+$ git interpret-trailers --trailer=\"ref:Add copyright notices.\" <<EOF\n+> subject\n+> \n+> message\n+> \n+> EOF\n+subject\n+\n+message\n+\n+Reference-to: 8bc9a0c769 (Add copyright notices., 2005-04-07)\n+------------\n+\n * Configure a 'see' trailer with a command to show the subject of a\n   commit that is related, and show how it works:\n +\ndiff --git a/t/t7513-interpret-trailers.sh b/t/t7513-interpret-trailers.sh\nindex 6602790b5f4c..04885d0a5e5c 100755\n--- a/t/t7513-interpret-trailers.sh\n+++ b/t/t7513-interpret-trailers.sh\n@@ -51,6 +51,69 @@ test_expect_success 'setup' '\n \tEOF\n '\n \n+test_expect_success 'with cmd' '\n+\ttest_when_finished \"git config --remove-section trailer.bug\" &&\n+\tgit config trailer.bug.key \"Bug-maker: \" &&\n+\tgit config trailer.bug.ifExists \"add\" &&\n+\tgit config trailer.bug.cmd \"echo \\\"maybe is\\\"\" &&\n+\tcat >expected2 <<-EOF &&\n+\n+\tBug-maker: maybe is him\n+\tBug-maker: maybe is me\n+\tEOF\n+\tgit interpret-trailers --trailer \"bug: him\" --trailer \"bug:me\" \\\n+\t\t>actual2 &&\n+\ttest_cmp expected2 actual2\n+'\n+\n+test_expect_success 'with cmd and $1' '\n+\ttest_when_finished \"git config --remove-section trailer.bug\" &&\n+\tgit config trailer.bug.key \"Bug-maker: \" &&\n+\tgit config trailer.bug.ifExists \"add\" &&\n+\tgit config trailer.bug.cmd \"echo \\\"\\$1\\\" is\" &&\n+\tcat >expected2 <<-EOF &&\n+\n+\tBug-maker: him is him\n+\tBug-maker: me is me\n+\tEOF\n+\tgit interpret-trailers --trailer \"bug: him\" --trailer \"bug:me\" \\\n+\t\t>actual2 &&\n+\ttest_cmp expected2 actual2\n+'\n+\n+test_expect_success 'with cmd and $1 with sh -c' '\n+\ttest_when_finished \"git config --remove-section trailer.bug\" &&\n+\tgit config trailer.bug.key \"Bug-maker: \" &&\n+\tgit config trailer.bug.ifExists \"replace\" &&\n+\tgit config trailer.bug.cmd \"sh -c \\\"echo who is \\\"\\$1\\\"\\\"\" &&\n+\tcat >expected2 <<-EOF &&\n+\n+\tBug-maker: who is me\n+\tEOF\n+\tgit interpret-trailers --trailer \"bug: him\" --trailer \"bug:me\" \\\n+\t\t>actual2 &&\n+\ttest_cmp expected2 actual2\n+'\n+\n+test_expect_success 'with cmd and $1 with shell script' '\n+\ttest_when_finished \"git config --remove-section trailer.bug\" &&\n+\tgit config trailer.bug.key \"Bug-maker: \" &&\n+\tgit config trailer.bug.ifExists \"replace\" &&\n+\tgit config trailer.bug.cmd \"./echoscript\" &&\n+\tcat >expected2 <<-EOF &&\n+\n+\tBug-maker: who is me\n+\tEOF\n+\tcat >echoscript <<-EOF &&\n+\t#!/bin/sh\n+\techo who is \"\\$1\"\n+\tEOF\n+\tchmod +x echoscript &&\n+\tgit interpret-trailers --trailer \"bug: him\" --trailer \"bug:me\" \\\n+\t\t>actual2 &&\n+\ttest_cmp expected2 actual2\n+'\n+\n test_expect_success 'without config' '\n \tsed -e \"s/ Z\\$/ /\" >expected <<-\\EOF &&\n \n@@ -1274,6 +1337,27 @@ test_expect_success 'setup a commit' '\n \tgit commit -m \"Add file a.txt\"\n '\n \n+test_expect_success 'cmd takes precedence over command' '\n+\ttest_when_finished \"git config --unset trailer.fix.cmd\" &&\n+\tgit config trailer.fix.ifExists \"replace\" &&\n+\tgit config trailer.fix.cmd \"test -n \\\"\\$1\\\" && git log -1 --oneline --format=\\\"%h (%aN)\\\" \\\n+\t--abbrev-commit --abbrev=14 \\\"\\$1\\\" || true\" &&\n+\tgit config trailer.fix.command \"git log -1 --oneline --format=\\\"%h (%s)\\\" \\\n+\t\t--abbrev-commit --abbrev=14 \\$ARG\" &&\n+\tFIXED=$(git log -1 --oneline --format=\"%h (%aN)\" --abbrev-commit --abbrev=14 HEAD) &&\n+\tcat complex_message_body >expected2 &&\n+\tsed -e \"s/ Z\\$/ /\" >>expected2 <<-EOF &&\n+\t\tFixes: $FIXED\n+\t\tAcked-by= Z\n+\t\tReviewed-by:\n+\t\tSigned-off-by: Z\n+\t\tSigned-off-by: A U Thor <author@example.com>\n+\tEOF\n+\tgit interpret-trailers --trailer \"review:\" --trailer \"fix=HEAD\" \\\n+\t\t<complex_message >actual2 &&\n+\ttest_cmp expected2 actual2\n+'\n+\n test_expect_success 'with command using $ARG' '\n \tgit config trailer.fix.ifExists \"replace\" &&\n \tgit config trailer.fix.command \"git log -1 --oneline --format=\\\"%h (%s)\\\" --abbrev-commit --abbrev=14 \\$ARG\" &&\ndiff --git a/trailer.c b/trailer.c\nindex be4e9726421c..7c7cb61a945c 100644\n--- a/trailer.c\n+++ b/trailer.c\n@@ -14,6 +14,7 @@ struct conf_info {\n \tchar *name;\n \tchar *key;\n \tchar *command;\n+\tchar *cmd;\n \tenum trailer_where where;\n \tenum trailer_if_exists if_exists;\n \tenum trailer_if_missing if_missing;\n@@ -127,6 +128,7 @@ static void free_arg_item(struct arg_item *item)\n \tfree(item->conf.name);\n \tfree(item->conf.key);\n \tfree(item->conf.command);\n+\tfree(item->conf.cmd);\n \tfree(item->token);\n \tfree(item->value);\n \tfree(item);\n@@ -216,18 +218,24 @@ static int check_if_different(struct trailer_item *in_tok,\n \treturn 1;\n }\n \n-static char *apply_command(const char *command, const char *arg)\n+static char *apply_command(struct conf_info *conf, const char *arg)\n {\n \tstruct strbuf cmd = STRBUF_INIT;\n \tstruct strbuf buf = STRBUF_INIT;\n \tstruct child_process cp = CHILD_PROCESS_INIT;\n \tchar *result;\n \n-\tstrbuf_addstr(&cmd, command);\n-\tif (arg)\n-\t\tstrbuf_replace(&cmd, TRAILER_ARG_STRING, arg);\n-\n-\tstrvec_push(&cp.args, cmd.buf);\n+\tif (conf->cmd) {\n+\t\tstrbuf_addstr(&cmd, conf->cmd);\n+\t\tstrvec_push(&cp.args, cmd.buf);\n+\t\tif (arg)\n+\t\t\tstrvec_push(&cp.args, arg);\n+\t} else if (conf->command) {\n+\t\tstrbuf_addstr(&cmd, conf->command);\n+\t\tif (arg)\n+\t\t\tstrbuf_replace(&cmd, TRAILER_ARG_STRING, arg);\n+\t\tstrvec_push(&cp.args, cmd.buf);\n+\t}\n \tcp.env = local_repo_env;\n \tcp.no_stdin = 1;\n \tcp.use_shell = 1;\n@@ -247,7 +255,7 @@ static char *apply_command(const char *command, const char *arg)\n \n static void apply_item_command(struct trailer_item *in_tok, struct arg_item *arg_tok)\n {\n-\tif (arg_tok->conf.command) {\n+\tif (arg_tok->conf.command || arg_tok->conf.cmd) {\n \t\tconst char *arg;\n \t\tif (arg_tok->value && arg_tok->value[0]) {\n \t\t\targ = arg_tok->value;\n@@ -257,7 +265,7 @@ static void apply_item_command(struct trailer_item *in_tok, struct arg_item *arg\n \t\t\telse\n \t\t\t\targ = xstrdup(\"\");\n \t\t}\n-\t\targ_tok->value = apply_command(arg_tok->conf.command, arg);\n+\t\targ_tok->value = apply_command(&arg_tok->conf, arg);\n \t\tfree((char *)arg);\n \t}\n }\n@@ -430,6 +438,7 @@ static void duplicate_conf(struct conf_info *dst, const struct conf_info *src)\n \tdst->name = xstrdup_or_null(src->name);\n \tdst->key = xstrdup_or_null(src->key);\n \tdst->command = xstrdup_or_null(src->command);\n+\tdst->cmd = xstrdup_or_null(src->cmd);\n }\n \n static struct arg_item *get_conf_item(const char *name)\n@@ -454,8 +463,8 @@ static struct arg_item *get_conf_item(const char *name)\n \treturn item;\n }\n \n-enum trailer_info_type { TRAILER_KEY, TRAILER_COMMAND, TRAILER_WHERE,\n-\t\t\t TRAILER_IF_EXISTS, TRAILER_IF_MISSING };\n+enum trailer_info_type { TRAILER_KEY, TRAILER_COMMAND, TRAILER_CMD,\n+\t\t\tTRAILER_WHERE, TRAILER_IF_EXISTS, TRAILER_IF_MISSING };\n \n static struct {\n \tconst char *name;\n@@ -463,6 +472,7 @@ static struct {\n } trailer_config_items[] = {\n \t{ \"key\", TRAILER_KEY },\n \t{ \"command\", TRAILER_COMMAND },\n+\t{ \"cmd\", TRAILER_CMD },\n \t{ \"where\", TRAILER_WHERE },\n \t{ \"ifexists\", TRAILER_IF_EXISTS },\n \t{ \"ifmissing\", TRAILER_IF_MISSING }\n@@ -542,6 +552,11 @@ static int git_trailer_config(const char *conf_key, const char *value, void *cb)\n \t\t\twarning(_(\"more than one %s\"), conf_key);\n \t\tconf->command = xstrdup(value);\n \t\tbreak;\n+\tcase TRAILER_CMD:\n+\t\tif (conf->cmd)\n+\t\t\twarning(_(\"more than one %s\"), conf_key);\n+\t\tconf->cmd = xstrdup(value);\n+\t\tbreak;\n \tcase TRAILER_WHERE:\n \t\tif (trailer_set_where(&conf->where, value))\n \t\t\twarning(_(\"unknown value '%s' for key '%s'\"), value, conf_key);\n-- \ngitgitgadget\n"},{"id":"423505","messageId":"8129ef6c476b4f35be59eae71367de5b83888068.1620056466.git.gitgitgadget@gmail.com","threadId":"55371","inReplyTo":"pull.913.v12.git.1620056466.gitgitgadget@gmail.com","subject":"[PATCH v12 1/2] [GSOC] docs: correct descript of trailer.<token>.command","fromName":"ZheNing Hu via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2021-05-03T15:41:04Z","receivedAt":"2021-05-03T15:41:17Z","isPatch":true,"sender":{"key":"adlternative@gmail.com","avatar":"https://avatars.githubusercontent.com/u/58138461?v=4"},"body":"From: ZheNing Hu <adlternative@gmail.com>\n\nIn the original documentation of `trailer.<token>.command`,\nsome descriptions are easily misunderstood. So let's modify\nit to increase its readability.\n\nIn addition, clarify that `$ARG` in command can only be\nreplaced once.\n\nSigned-off-by: ZheNing Hu <adlternative@gmail.com>\n---\n Documentation/git-interpret-trailers.txt | 37 ++++++++++++++----------\n 1 file changed, 21 insertions(+), 16 deletions(-)\n\ndiff --git a/Documentation/git-interpret-trailers.txt b/Documentation/git-interpret-trailers.txt\nindex 96ec6499f001..6f2a7a130464 100644\n--- a/Documentation/git-interpret-trailers.txt\n+++ b/Documentation/git-interpret-trailers.txt\n@@ -232,25 +232,30 @@ trailer.<token>.ifmissing::\n \tthat option for trailers with the specified <token>.\n \n trailer.<token>.command::\n-\tThis option can be used to specify a shell command that will\n-\tbe called to automatically add or modify a trailer with the\n-\tspecified <token>.\n+\tThis option can be used to specify a shell command that will be called:\n+\tonce to automatically add a trailer with the specified <token>, and then\n+\teach time a '--trailer <token>=<value>' argument to modify the <value> of\n+\tthe trailer that this option would produce.\n +\n-When this option is specified, the behavior is as if a special\n-'<token>=<value>' argument were added at the beginning of the command\n-line, where <value> is taken to be the standard output of the\n-specified command with any leading and trailing whitespace trimmed\n-off.\n+When the specified command is first called to add a trailer\n+with the specified <token>, the behavior is as if a special\n+'--trailer <token>=<value>' argument was added at the beginning\n+of the \"git interpret-trailers\" command, where <value>\n+is taken to be the standard output of the command with any\n+leading and trailing whitespace trimmed off.\n +\n-If the command contains the `$ARG` string, this string will be\n-replaced with the <value> part of an existing trailer with the same\n-<token>, if any, before the command is launched.\n+If some '--trailer <token>=<value>' arguments are also passed\n+on the command line, the command is called again once for each\n+of these arguments with the same <token>. And the <value> part\n+of these arguments, if any, will be used to replace the first\n+occurrence of substring `$ARG` in the command. This way the\n+command can produce a <value> computed from the <value> passed\n+in the '--trailer <token>=<value>' argument.\n +\n-If some '<token>=<value>' arguments are also passed on the command\n-line, when a 'trailer.<token>.command' is configured, the command will\n-also be executed for each of these arguments. And the <value> part of\n-these arguments, if any, will be used to replace the `$ARG` string in\n-the command.\n+For consistency, the first occurrence of substring `$ARG` is\n+also replaced, this time with the empty string, in the command\n+when the command is first called to add a trailer with the\n+specified <token>.\n \n EXAMPLES\n --------\n-- \ngitgitgadget\n\n"}]}