{"thread":{"id":"61376","subject":"[PATCH] builtin/tag.c: add --trailer arg","startedAt":"2024-04-29T04:31:20Z","lastAt":"2024-05-07T17:06:24Z","messageCount":37,"participants":["John Passaro via GitGitGadget","Patrick Steinhardt","John Passaro","Junio C Hamano"],"isPatch":true,"patchVersion":1,"patchTotal":null},"messages":[{"id":"493580","messageId":"pull.1723.git.1714365076246.gitgitgadget@gmail.com","threadId":"61376","inReplyTo":null,"subject":"[PATCH] builtin/tag.c: add --trailer arg","fromName":"John Passaro via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2024-04-29T04:31:15Z","receivedAt":"2024-04-29T04:31:20Z","isPatch":true,"sender":{"key":"john.a.passaro@gmail.com","avatar":"https://avatars.githubusercontent.com/u/6754005?v=4"},"body":"From: John Passaro <john.a.passaro@gmail.com>\n\nTeach git-tag to accept --trailer option to add trailers to annotated\ntag messages, like git-commit.\n\nSigned-off-by: John Passaro <john.a.passaro@gmail.com>\n---\n    builtin/tag.c: add --trailer arg\n\nPublished-As: https://github.com/gitgitgadget/git/releases/tag/pr-1723%2Fjpassaro%2Fjp%2Ftag-trailer-arg-v1\nFetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-1723/jpassaro/jp/tag-trailer-arg-v1\nPull-Request: https://github.com/gitgitgadget/git/pull/1723\n\n Documentation/git-tag.txt |  18 ++++++-\n builtin/tag.c             |  59 +++++++++++++++++----\n t/t7004-tag.sh            | 104 ++++++++++++++++++++++++++++++++++++++\n 3 files changed, 169 insertions(+), 12 deletions(-)\n\ndiff --git a/Documentation/git-tag.txt b/Documentation/git-tag.txt\nindex 5fe519c31ec..79b0a7e9644 100644\n--- a/Documentation/git-tag.txt\n+++ b/Documentation/git-tag.txt\n@@ -10,6 +10,7 @@ SYNOPSIS\n --------\n [verse]\n 'git tag' [-a | -s | -u <key-id>] [-f] [-m <msg> | -F <file>] [-e]\n+\t[(--trailer <token>[(=|:)<value>])...]\n \t<tagname> [<commit> | <object>]\n 'git tag' -d <tagname>...\n 'git tag' [-n[<num>]] -l [--contains <commit>] [--no-contains <commit>]\n@@ -31,8 +32,8 @@ creates a 'tag' object, and requires a tag message.  Unless\n `-m <msg>` or `-F <file>` is given, an editor is started for the user to type\n in the tag message.\n \n-If `-m <msg>` or `-F <file>` is given and `-a`, `-s`, and `-u <key-id>`\n-are absent, `-a` is implied.\n+If `-m <msg>` or `-F <file>` or `--trailer <token>[=<value>]` is given\n+and `-a`, `-s`, and `-u <key-id>` are absent, `-a` is implied.\n \n Otherwise, a tag reference that points directly at the given object\n (i.e., a lightweight tag) is created.\n@@ -178,6 +179,19 @@ This option is only applicable when listing tags without annotation lines.\n \tImplies `-a` if none of `-a`, `-s`, or `-u <key-id>`\n \tis given.\n \n+--trailer <token>[(=|:)<value>]::\n+\tSpecify a (<token>, <value>) pair that should be applied as a\n+\ttrailer. (e.g. `git tag --trailer \"Signed-off-by:T A Ger \\\n+\t<tagger@example.com>\" --trailer \"Helped-by:C O Mitter \\\n+\t<committer@example.com>\"` will add the \"Signed-off-by\" trailer\n+\tand the \"Helped-by\" trailer to the tag message.)\n+\tThe `trailer.*` configuration variables\n+\t(linkgit:git-interpret-trailers[1]) can be used to define if\n+\ta duplicated trailer is omitted, where in the run of trailers\n+\teach trailer would appear, and other details.\n+\tThe trailers can be seen in `git tag --list` using\n+\t`--format=\"%(trailers)\"` placeholder.\n+\n -e::\n --edit::\n \tThe message taken from file with `-F` and command line with\ndiff --git a/builtin/tag.c b/builtin/tag.c\nindex 9a33cb50b45..0334a5d15ec 100644\n--- a/builtin/tag.c\n+++ b/builtin/tag.c\n@@ -28,9 +28,11 @@\n #include \"date.h\"\n #include \"write-or-die.h\"\n #include \"object-file-convert.h\"\n+#include \"run-command.h\"\n \n static const char * const git_tag_usage[] = {\n \tN_(\"git tag [-a | -s | -u <key-id>] [-f] [-m <msg> | -F <file>] [-e]\\n\"\n+\t   \"        [(--trailer <token>[(=|:)<value>])...]\\n\"\n \t   \"        <tagname> [<commit> | <object>]\"),\n \tN_(\"git tag -d <tagname>...\"),\n \tN_(\"git tag [-n[<num>]] -l [--contains <commit>] [--no-contains <commit>]\\n\"\n@@ -290,10 +292,11 @@ static const char message_advice_nested_tag[] =\n static void create_tag(const struct object_id *object, const char *object_ref,\n \t\t       const char *tag,\n \t\t       struct strbuf *buf, struct create_tag_options *opt,\n-\t\t       struct object_id *prev, struct object_id *result, char *path)\n+\t\t       struct object_id *prev, struct object_id *result, struct strvec *trailer_args, char *path)\n {\n \tenum object_type type;\n \tstruct strbuf header = STRBUF_INIT;\n+\tint should_edit;\n \n \ttype = oid_object_info(the_repository, object, NULL);\n \tif (type <= OBJ_NONE)\n@@ -313,14 +316,18 @@ static void create_tag(const struct object_id *object, const char *object_ref,\n \t\t    tag,\n \t\t    git_committer_info(IDENT_STRICT));\n \n-\tif (!opt->message_given || opt->use_editor) {\n+\tshould_edit = opt->use_editor || !opt->message_given;\n+\tif (should_edit || trailer_args->nr) {\n \t\tint fd;\n \n \t\t/* write the template message before editing: */\n \t\tfd = xopen(path, O_CREAT | O_TRUNC | O_WRONLY, 0600);\n \n-\t\tif (opt->message_given) {\n+\t\tif (opt->message_given && buf->len) {\n \t\t\twrite_or_die(fd, buf->buf, buf->len);\n+\t\t\tif (trailer_args->nr && buf->buf[buf->len-1] != '\\n') {\n+\t\t\t\twrite_or_die(fd, \"\\n\", 1);\n+\t\t\t}\n \t\t\tstrbuf_reset(buf);\n \t\t} else if (!is_null_oid(prev)) {\n \t\t\twrite_tag_body(fd, prev);\n@@ -338,10 +345,31 @@ static void create_tag(const struct object_id *object, const char *object_ref,\n \t\t}\n \t\tclose(fd);\n \n-\t\tif (launch_editor(path, buf, NULL)) {\n-\t\t\tfprintf(stderr,\n-\t\t\t_(\"Please supply the message using either -m or -F option.\\n\"));\n-\t\t\texit(1);\n+\t\tif (trailer_args->nr) {\n+\t\t\tstruct child_process run_trailer = CHILD_PROCESS_INIT;\n+\n+\t\t\tstrvec_pushl(&run_trailer.args, \"interpret-trailers\",\n+\t\t\t\t     \"--in-place\", \"--no-divider\",\n+\t\t\t\t     path, NULL);\n+\t\t\tstrvec_pushv(&run_trailer.args, trailer_args->v);\n+\t\t\trun_trailer.git_cmd = 1;\n+\t\t\tif (run_command(&run_trailer))\n+\t\t\t\tdie(_(\"unable to pass trailers to --trailers\"));\n+\t\t}\n+\n+\t\tif (should_edit) {\n+\t\t\tif (launch_editor(path, buf, NULL)) {\n+\t\t\t\tfprintf(stderr,\n+\t\t\t\t_(\"Please supply the message using either -m or -F option.\\n\"));\n+\t\t\t\texit(1);\n+\t\t\t}\n+\t\t} else if (trailer_args->nr) {\n+\t\t\tstrbuf_reset(buf);\n+\t\t\tif (strbuf_read_file(buf, path, 0) < 0) {\n+\t\t\t\tfprintf(stderr,\n+\t\t\t\t\t\t_(\"Please supply the message using either -m or -F option.\\n\"));\n+\t\t\t\texit(1);\n+\t\t\t}\n \t\t}\n \t}\n \n@@ -416,6 +444,14 @@ struct msg_arg {\n \tstruct strbuf buf;\n };\n \n+static int opt_pass_trailer(const struct option *opt, const char *arg, int unset)\n+{\n+\tBUG_ON_OPT_NEG(unset);\n+\n+\tstrvec_pushl(opt->value, \"--trailer\", arg, NULL);\n+\treturn 0;\n+}\n+\n static int parse_msg_arg(const struct option *opt, const char *arg, int unset)\n {\n \tstruct msg_arg *msg = opt->value;\n@@ -463,6 +499,7 @@ int cmd_tag(int argc, const char **argv, const char *prefix)\n \tstruct ref_sorting *sorting;\n \tstruct string_list sorting_options = STRING_LIST_INIT_DUP;\n \tstruct ref_format format = REF_FORMAT_INIT;\n+\tstruct strvec trailer_args = STRVEC_INIT;\n \tint icase = 0;\n \tint edit_flag = 0;\n \tstruct option options[] = {\n@@ -479,6 +516,7 @@ int cmd_tag(int argc, const char **argv, const char *prefix)\n \t\tOPT_CALLBACK_F('m', \"message\", &msg, N_(\"message\"),\n \t\t\t       N_(\"tag message\"), PARSE_OPT_NONEG, parse_msg_arg),\n \t\tOPT_FILENAME('F', \"file\", &msgfile, N_(\"read message from file\")),\n+\t\tOPT_CALLBACK_F(0, \"trailer\", &trailer_args, N_(\"trailer\"), N_(\"add custom trailer(s)\"), PARSE_OPT_NONEG, opt_pass_trailer),\n \t\tOPT_BOOL('e', \"edit\", &edit_flag, N_(\"force edit of tag message\")),\n \t\tOPT_BOOL('s', \"sign\", &opt.sign, N_(\"annotated and GPG-signed tag\")),\n \t\tOPT_CLEANUP(&cleanup_arg),\n@@ -548,7 +586,7 @@ int cmd_tag(int argc, const char **argv, const char *prefix)\n \t\topt.sign = 1;\n \t\tset_signing_key(keyid);\n \t}\n-\tcreate_tag_object = (opt.sign || annotate || msg.given || msgfile);\n+\tcreate_tag_object = (opt.sign || annotate || msg.given || msgfile || edit_flag || trailer_args.nr);\n \n \tif ((create_tag_object || force) && (cmdmode != 0))\n \t\tusage_with_options(git_tag_usage, options);\n@@ -635,7 +673,7 @@ int cmd_tag(int argc, const char **argv, const char *prefix)\n \telse if (!force)\n \t\tdie(_(\"tag '%s' already exists\"), tag);\n \n-\topt.message_given = msg.given || msgfile;\n+\topt.message_given = msg.given || (msgfile != NULL);\n \topt.use_editor = edit_flag;\n \n \tif (!cleanup_arg || !strcmp(cleanup_arg, \"strip\"))\n@@ -653,7 +691,7 @@ int cmd_tag(int argc, const char **argv, const char *prefix)\n \t\tif (force_sign_annotate && !annotate)\n \t\t\topt.sign = 1;\n \t\tpath = git_pathdup(\"TAG_EDITMSG\");\n-\t\tcreate_tag(&object, object_ref, tag, &buf, &opt, &prev, &object,\n+\t\tcreate_tag(&object, object_ref, tag, &buf, &opt, &prev, &object, &trailer_args,\n \t\t\t   path);\n \t}\n \n@@ -686,6 +724,7 @@ int cmd_tag(int argc, const char **argv, const char *prefix)\n \tstrbuf_release(&reflog_msg);\n \tstrbuf_release(&msg.buf);\n \tstrbuf_release(&err);\n+\tstrvec_clear(&trailer_args);\n \tfree(msgfile);\n \treturn ret;\n }\ndiff --git a/t/t7004-tag.sh b/t/t7004-tag.sh\nindex 696866d7794..364db2b4685 100755\n--- a/t/t7004-tag.sh\n+++ b/t/t7004-tag.sh\n@@ -668,6 +668,105 @@ test_expect_success \\\n \ttest_cmp expect actual\n '\n \n+# trailers\n+\n+get_tag_header tag-with-inline-message-and-trailers $commit commit $time >expect\n+cat >>expect <<EOF\n+create tag with trailers\n+\n+my-trailer: here\n+alt-trailer: there\n+EOF\n+test_expect_success 'create tag with -m and --trailer' '\n+\tgit tag -m \"create tag with trailers\"  --trailer my-trailer=here --trailer alt-trailer=there tag-with-inline-message-and-trailers &&\n+\tget_tag_msg tag-with-inline-message-and-trailers >actual &&\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'list tag extracting trailers' '\n+\tcat >expect <<-\\EOF &&\n+\tmy-trailer: here\n+\talt-trailer: there\n+\n+\tEOF\n+\tgit tag --list --format=\"%(trailers)\" tag-with-inline-message-and-trailers >actual &&\n+\ttest_cmp expect actual\n+'\n+\n+echo 'create tag from message file using --trailer' >messagefilewithnotrailers\n+get_tag_header tag-with-file-message-and-trailers $commit commit $time >expect\n+cat >>expect <<EOF\n+create tag from message file using --trailer\n+\n+my-trailer: here\n+alt-trailer: there\n+EOF\n+test_expect_success 'create tag with -F and --trailer' '\n+\tgit tag -F messagefilewithnotrailers  --trailer my-trailer=here --trailer alt-trailer=there tag-with-file-message-and-trailers &&\n+\tget_tag_msg tag-with-file-message-and-trailers >actual &&\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'set up editor' '\n+\twrite_script fakeeditor <<-\\EOF\n+\tsed -e \"1s/^/EDITED: /g\" <\"$1\" >\"$1-\"\n+\tmv \"$1-\" \"$1\"\n+\tEOF\n+'\n+\n+get_tag_header tag-with-edited-inline-message-and-trailers $commit commit $time >expect\n+cat >>expect <<EOF\n+EDITED: create tag with trailers\n+\n+my-trailer: here\n+alt-trailer: there\n+EOF\n+test_expect_success 'create tag with -m and --trailer and --edit' '\n+\tGIT_EDITOR=./fakeeditor git tag --edit -m \"create tag with trailers\"  --trailer my-trailer=here --trailer alt-trailer=there tag-with-edited-inline-message-and-trailers &&\n+\tget_tag_msg tag-with-edited-inline-message-and-trailers >actual &&\n+\ttest_cmp expect actual\n+'\n+\n+echo 'create tag from message file using --trailer' >messagefilewithnotrailers\n+get_tag_header tag-with-edited-file-message-and-trailers $commit commit $time >expect\n+cat >>expect <<EOF\n+EDITED: create tag from message file using --trailer\n+\n+my-trailer: here\n+alt-trailer: there\n+EOF\n+test_expect_success 'create tag with -F and --trailer and --edit' '\n+\tGIT_EDITOR=./fakeeditor git tag --edit -F messagefilewithnotrailers  --trailer my-trailer=here --trailer alt-trailer=there tag-with-edited-file-message-and-trailers &&\n+\tget_tag_msg tag-with-edited-file-message-and-trailers >actual &&\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'set up editor' '\n+\twrite_script fakeeditor <<-\\EOF\n+\techo \"add a line\" >\"$1-\"\n+\techo >>\"$1-\"\n+\tcat <\"$1\" >>\"$1-\"\n+\tmv \"$1-\" \"$1\"\n+\tEOF\n+'\n+\n+get_tag_header tag-with-trailers-and-no-message $commit commit $time >expect\n+cat >>expect <<EOF\n+add a line\n+\n+my-trailer: here\n+alt-trailer: there\n+EOF\n+test_expect_success 'create annotated tag and force editor when only --trailer is given' '\n+\tGIT_EDITOR=./fakeeditor git tag --trailer my-trailer=here --trailer alt-trailer=there tag-with-trailers-and-no-message &&\n+\tget_tag_msg tag-with-trailers-and-no-message >actual &&\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'bad editor causes panic when only --trailer is given' '\n+\ttest_must_fail env GIT_EDITOR=false git tag --trailer my-trailer=here tag-will-not-exist\n+'\n+\n # listing messages for annotated non-signed tags:\n \n test_expect_success \\\n@@ -810,6 +909,11 @@ test_expect_success 'git tag --format with ahead-behind' '\n \trefs/tags/tag-lines 0 1 !\n \trefs/tags/tag-one-line 0 1 !\n \trefs/tags/tag-right 0 0 !\n+\trefs/tags/tag-with-edited-file-message-and-trailers 0 1 !\n+\trefs/tags/tag-with-edited-inline-message-and-trailers 0 1 !\n+\trefs/tags/tag-with-file-message-and-trailers 0 1 !\n+\trefs/tags/tag-with-inline-message-and-trailers 0 1 !\n+\trefs/tags/tag-with-trailers-and-no-message 0 1 !\n \trefs/tags/tag-zero-lines 0 1 !\n \tEOF\n \tgit tag -l --format=\"%(refname) %(ahead-behind:HEAD) !\" >actual 2>err &&\n\nbase-commit: e326e520101dcf43a0499c3adc2df7eca30add2d\n-- \ngitgitgadget\n"},{"id":"493605","messageId":"Zi9DGYwlT7VnW7oj@tanuki","threadId":"61376","inReplyTo":"pull.1723.git.1714365076246.gitgitgadget@gmail.com","subject":"Re: [PATCH] builtin/tag.c: add --trailer arg","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2024-04-29T06:50:01Z","receivedAt":"2024-04-29T06:50:07Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"On Mon, Apr 29, 2024 at 04:31:15AM +0000, John Passaro via GitGitGadget wrote:\n> From: John Passaro <john.a.passaro@gmail.com>\n> \n> Teach git-tag to accept --trailer option to add trailers to annotated\n> tag messages, like git-commit.\n> \n> Signed-off-by: John Passaro <john.a.passaro@gmail.com>\n\nThis feels like a sensible addition to me indeed, thanks!\n\n[snip]\n> diff --git a/builtin/tag.c b/builtin/tag.c\n> index 9a33cb50b45..0334a5d15ec 100644\n> --- a/builtin/tag.c\n> +++ b/builtin/tag.c\n> @@ -28,9 +28,11 @@\n>  #include \"date.h\"\n>  #include \"write-or-die.h\"\n>  #include \"object-file-convert.h\"\n> +#include \"run-command.h\"\n>  \n>  static const char * const git_tag_usage[] = {\n>  \tN_(\"git tag [-a | -s | -u <key-id>] [-f] [-m <msg> | -F <file>] [-e]\\n\"\n> +\t   \"        [(--trailer <token>[(=|:)<value>])...]\\n\"\n>  \t   \"        <tagname> [<commit> | <object>]\"),\n>  \tN_(\"git tag -d <tagname>...\"),\n>  \tN_(\"git tag [-n[<num>]] -l [--contains <commit>] [--no-contains <commit>]\\n\"\n> @@ -290,10 +292,11 @@ static const char message_advice_nested_tag[] =\n>  static void create_tag(const struct object_id *object, const char *object_ref,\n>  \t\t       const char *tag,\n>  \t\t       struct strbuf *buf, struct create_tag_options *opt,\n> -\t\t       struct object_id *prev, struct object_id *result, char *path)\n> +\t\t       struct object_id *prev, struct object_id *result, struct strvec *trailer_args, char *path)\n\nThis line is overly long now, let's break it.\n\n>  {\n>  \tenum object_type type;\n>  \tstruct strbuf header = STRBUF_INIT;\n> +\tint should_edit;\n>  \n>  \ttype = oid_object_info(the_repository, object, NULL);\n>  \tif (type <= OBJ_NONE)\n> @@ -313,14 +316,18 @@ static void create_tag(const struct object_id *object, const char *object_ref,\n>  \t\t    tag,\n>  \t\t    git_committer_info(IDENT_STRICT));\n>  \n> -\tif (!opt->message_given || opt->use_editor) {\n> +\tshould_edit = opt->use_editor || !opt->message_given;\n> +\tif (should_edit || trailer_args->nr) {\n>  \t\tint fd;\n>  \n>  \t\t/* write the template message before editing: */\n>  \t\tfd = xopen(path, O_CREAT | O_TRUNC | O_WRONLY, 0600);\n>  \n> -\t\tif (opt->message_given) {\n> +\t\tif (opt->message_given && buf->len) {\n>  \t\t\twrite_or_die(fd, buf->buf, buf->len);\n> +\t\t\tif (trailer_args->nr && buf->buf[buf->len-1] != '\\n') {\n> +\t\t\t\twrite_or_die(fd, \"\\n\", 1);\n> +\t\t\t}\n\nWe avoid braces around single-line statements.\n\nI was also wondering whether we can simplify this to:\n\n    if (opt->message_given && buf->len) {\n        strbuf_complete(buf, '\\n');\n        write_or_die(fd, buf->buf, buf->len);\n    }\n\nOr does changing `buf` cause problems for us?\n\n>  \t\t\tstrbuf_reset(buf);\n>  \t\t} else if (!is_null_oid(prev)) {\n>  \t\t\twrite_tag_body(fd, prev);\n> @@ -338,10 +345,31 @@ static void create_tag(const struct object_id *object, const char *object_ref,\n>  \t\t}\n>  \t\tclose(fd);\n>  \n> -\t\tif (launch_editor(path, buf, NULL)) {\n> -\t\t\tfprintf(stderr,\n> -\t\t\t_(\"Please supply the message using either -m or -F option.\\n\"));\n> -\t\t\texit(1);\n> +\t\tif (trailer_args->nr) {\n> +\t\t\tstruct child_process run_trailer = CHILD_PROCESS_INIT;\n> +\n> +\t\t\tstrvec_pushl(&run_trailer.args, \"interpret-trailers\",\n> +\t\t\t\t     \"--in-place\", \"--no-divider\",\n> +\t\t\t\t     path, NULL);\n> +\t\t\tstrvec_pushv(&run_trailer.args, trailer_args->v);\n> +\t\t\trun_trailer.git_cmd = 1;\n> +\t\t\tif (run_command(&run_trailer))\n> +\t\t\t\tdie(_(\"unable to pass trailers to --trailers\"));\n> +\t\t}\n\nThis part is copied from `builtin/commit.c`. Would it make sense to move\nit into a function `amend_trailers_to_file()` or similar that we add to\nour trailer API so that we can avoid the code duplication?\n\n> +\t\tif (should_edit) {\n> +\t\t\tif (launch_editor(path, buf, NULL)) {\n> +\t\t\t\tfprintf(stderr,\n> +\t\t\t\t_(\"Please supply the message using either -m or -F option.\\n\"));\n> +\t\t\t\texit(1);\n> +\t\t\t}\n\nI know you simply re-indented the block here, but let's also fix the\nindentation of the second argument to fprintf(3P) while at it.\n\n> +\t\t} else if (trailer_args->nr) {\n> +\t\t\tstrbuf_reset(buf);\n> +\t\t\tif (strbuf_read_file(buf, path, 0) < 0) {\n> +\t\t\t\tfprintf(stderr,\n> +\t\t\t\t\t\t_(\"Please supply the message using either -m or -F option.\\n\"));\n> +\t\t\t\texit(1);\n> +\t\t\t}\n>  \t\t}\n>  \t}\n>  \n> @@ -416,6 +444,14 @@ struct msg_arg {\n>  \tstruct strbuf buf;\n>  };\n>  \n> +static int opt_pass_trailer(const struct option *opt, const char *arg, int unset)\n> +{\n> +\tBUG_ON_OPT_NEG(unset);\n> +\n> +\tstrvec_pushl(opt->value, \"--trailer\", arg, NULL);\n> +\treturn 0;\n> +}\n> +\n>  static int parse_msg_arg(const struct option *opt, const char *arg, int unset)\n>  {\n>  \tstruct msg_arg *msg = opt->value;\n> @@ -463,6 +499,7 @@ int cmd_tag(int argc, const char **argv, const char *prefix)\n>  \tstruct ref_sorting *sorting;\n>  \tstruct string_list sorting_options = STRING_LIST_INIT_DUP;\n>  \tstruct ref_format format = REF_FORMAT_INIT;\n> +\tstruct strvec trailer_args = STRVEC_INIT;\n>  \tint icase = 0;\n>  \tint edit_flag = 0;\n>  \tstruct option options[] = {\n> @@ -479,6 +516,7 @@ int cmd_tag(int argc, const char **argv, const char *prefix)\n>  \t\tOPT_CALLBACK_F('m', \"message\", &msg, N_(\"message\"),\n>  \t\t\t       N_(\"tag message\"), PARSE_OPT_NONEG, parse_msg_arg),\n>  \t\tOPT_FILENAME('F', \"file\", &msgfile, N_(\"read message from file\")),\n> +\t\tOPT_CALLBACK_F(0, \"trailer\", &trailer_args, N_(\"trailer\"), N_(\"add custom trailer(s)\"), PARSE_OPT_NONEG, opt_pass_trailer),\n>  \t\tOPT_BOOL('e', \"edit\", &edit_flag, N_(\"force edit of tag message\")),\n>  \t\tOPT_BOOL('s', \"sign\", &opt.sign, N_(\"annotated and GPG-signed tag\")),\n>  \t\tOPT_CLEANUP(&cleanup_arg),\n> @@ -548,7 +586,7 @@ int cmd_tag(int argc, const char **argv, const char *prefix)\n>  \t\topt.sign = 1;\n>  \t\tset_signing_key(keyid);\n>  \t}\n> -\tcreate_tag_object = (opt.sign || annotate || msg.given || msgfile);\n> +\tcreate_tag_object = (opt.sign || annotate || msg.given || msgfile || edit_flag || trailer_args.nr);\n>  \n>  \tif ((create_tag_object || force) && (cmdmode != 0))\n>  \t\tusage_with_options(git_tag_usage, options);\n> @@ -635,7 +673,7 @@ int cmd_tag(int argc, const char **argv, const char *prefix)\n>  \telse if (!force)\n>  \t\tdie(_(\"tag '%s' already exists\"), tag);\n>  \n> -\topt.message_given = msg.given || msgfile;\n> +\topt.message_given = msg.given || (msgfile != NULL);\n>  \topt.use_editor = edit_flag;\n\nBesides being not required, this change also violates our coding style\nwhere we don't explicitly check for NULL pointers.\n\n>  \tif (!cleanup_arg || !strcmp(cleanup_arg, \"strip\"))\n> @@ -653,7 +691,7 @@ int cmd_tag(int argc, const char **argv, const char *prefix)\n>  \t\tif (force_sign_annotate && !annotate)\n>  \t\t\topt.sign = 1;\n>  \t\tpath = git_pathdup(\"TAG_EDITMSG\");\n> -\t\tcreate_tag(&object, object_ref, tag, &buf, &opt, &prev, &object,\n> +\t\tcreate_tag(&object, object_ref, tag, &buf, &opt, &prev, &object, &trailer_args,\n>  \t\t\t   path);\n\nNit: let's move `&trailer_args` to the next line to not make it overly\nlong.\n\n>  \t}\n>  \n> @@ -686,6 +724,7 @@ int cmd_tag(int argc, const char **argv, const char *prefix)\n>  \tstrbuf_release(&reflog_msg);\n>  \tstrbuf_release(&msg.buf);\n>  \tstrbuf_release(&err);\n> +\tstrvec_clear(&trailer_args);\n>  \tfree(msgfile);\n>  \treturn ret;\n>  }\n> diff --git a/t/t7004-tag.sh b/t/t7004-tag.sh\n> index 696866d7794..364db2b4685 100755\n> --- a/t/t7004-tag.sh\n> +++ b/t/t7004-tag.sh\n> @@ -668,6 +668,105 @@ test_expect_success \\\n>  \ttest_cmp expect actual\n>  '\n>  \n> +# trailers\n> +\n> +get_tag_header tag-with-inline-message-and-trailers $commit commit $time >expect\n> +cat >>expect <<EOF\n> +create tag with trailers\n> +\n> +my-trailer: here\n> +alt-trailer: there\n> +EOF\n\nYou probably just follow precedent in this file, but our modern coding\nstyle sets up the `expect` file inside of the test body itself. You also\ndo it like that in other tests, so let's be consistent.\n\nThe same comment applies to other tests, as well.\n\nPatrick\n"},{"id":"493636","messageId":"CAJdN7Kh3cMHR+DwM9Rsojfrg_Xj00igRgYX2-ot-zvJeEk3c7g@mail.gmail.com","threadId":"61376","inReplyTo":"Zi9DGYwlT7VnW7oj@tanuki","subject":"Re: [PATCH] builtin/tag.c: add --trailer arg","fromName":"John Passaro","fromEmail":"john.a.passaro@gmail.com","sentAt":"2024-04-29T14:50:56Z","receivedAt":"2024-04-29T14:51:35Z","isPatch":true,"sender":{"key":"john.a.passaro@gmail.com","avatar":"https://avatars.githubusercontent.com/u/6754005?v=4"},"body":"On Mon, Apr 29, 2024 at 2:50 AM Patrick Steinhardt <ps@pks.im> wrote:\n>\n> On Mon, Apr 29, 2024 at 04:31:15AM +0000, John Passaro via GitGitGadget wrote:\n> > From: John Passaro <john.a.passaro@gmail.com>\n> >\n> > Teach git-tag to accept --trailer option to add trailers to annotated\n> > tag messages, like git-commit.\n> >\n> > Signed-off-by: John Passaro <john.a.passaro@gmail.com>\n>\n> This feels like a sensible addition to me indeed, thanks!\n>\n\n\nThanks, and thank you for the thoughtful feedback.\nI have incorporated most of it on the github PR branch\n(https://github.com/gitgitgadget/git/pull/1723).\nBefore submitting a new patch I had a couple of questions.\n\n[snip]\n\n> > @@ -313,14 +316,18 @@ static void create_tag(const struct object_id *object, const char *object_ref,\n> >                   tag,\n> >                   git_committer_info(IDENT_STRICT));\n> >\n> > -     if (!opt->message_given || opt->use_editor) {\n> > +     should_edit = opt->use_editor || !opt->message_given;\n> > +     if (should_edit || trailer_args->nr) {\n> >               int fd;\n> >\n> >               /* write the template message before editing: */\n> >               fd = xopen(path, O_CREAT | O_TRUNC | O_WRONLY, 0600);\n> >\n> > -             if (opt->message_given) {\n> > +             if (opt->message_given && buf->len) {\n> >                       write_or_die(fd, buf->buf, buf->len);\n> > +                     if (trailer_args->nr && buf->buf[buf->len-1] != '\\n') {\n> > +                             write_or_die(fd, \"\\n\", 1);\n> > +                     }\n>\n> We avoid braces around single-line statements.\n>\n> I was also wondering whether we can simplify this to:\n>\n>     if (opt->message_given && buf->len) {\n>         strbuf_complete(buf, '\\n');\n>         write_or_die(fd, buf->buf, buf->len);\n>     }\n>\n> Or does changing `buf` cause problems for us?\n\nChanging `buf\n\n>\n> >                       strbuf_reset(buf);\n> >               } else if (!is_null_oid(prev)) {\n> >                       write_tag_body(fd, prev);\n> > @@ -338,10 +345,31 @@ static void create_tag(const struct object_id *object, const char *object_ref,\n> >               }\n> >               close(fd);\n> >\n> > -             if (launch_editor(path, buf, NULL)) {\n> > -                     fprintf(stderr,\n> > -                     _(\"Please supply the message using either -m or -F option.\\n\"));\n> > -                     exit(1);\n> > +             if (trailer_args->nr) {\n> > +                     struct child_process run_trailer = CHILD_PROCESS_INIT;\n> > +\n> > +                     strvec_pushl(&run_trailer.args, \"interpret-trailers\",\n> > +                                  \"--in-place\", \"--no-divider\",\n> > +                                  path, NULL);\n> > +                     strvec_pushv(&run_trailer.args, trailer_args->v);\n> > +                     run_trailer.git_cmd = 1;\n> > +                     if (run_command(&run_trailer))\n> > +                             die(_(\"unable to pass trailers to --trailers\"));\n> > +             }\n>\n> This part is copied from `builtin/commit.c`. Would it make sense to move\n> it into a function `amend_trailers_to_file()` or similar that we add to\n> our trailer API so that we can avoid the code duplication?\n>\n> > +             if (should_edit) {\n> > +                     if (launch_editor(path, buf, NULL)) {\n> > +                             fprintf(stderr,\n> > +                             _(\"Please supply the message using either -m or -F option.\\n\"));\n> > +                             exit(1);\n> > +                     }\n>\n> I know you simply re-indented the block here, but let's also fix the\n> indentation of the second argument to fprintf(3P) while at it.\n>\n> > +             } else if (trailer_args->nr) {\n> > +                     strbuf_reset(buf);\n> > +                     if (strbuf_read_file(buf, path, 0) < 0) {\n> > +                             fprintf(stderr,\n> > +                                             _(\"Please supply the message using either -m or -F option.\\n\"));\n> > +                             exit(1);\n> > +                     }\n> >               }\n> >       }\n> >\n> > @@ -416,6 +444,14 @@ struct msg_arg {\n> >       struct strbuf buf;\n> >  };\n> >\n> > +static int opt_pass_trailer(const struct option *opt, const char *arg, int unset)\n> > +{\n> > +     BUG_ON_OPT_NEG(unset);\n> > +\n> > +     strvec_pushl(opt->value, \"--trailer\", arg, NULL);\n> > +     return 0;\n> > +}\n> > +\n> >  static int parse_msg_arg(const struct option *opt, const char *arg, int unset)\n> >  {\n> >       struct msg_arg *msg = opt->value;\n> > @@ -463,6 +499,7 @@ int cmd_tag(int argc, const char **argv, const char *prefix)\n> >       struct ref_sorting *sorting;\n> >       struct string_list sorting_options = STRING_LIST_INIT_DUP;\n> >       struct ref_format format = REF_FORMAT_INIT;\n> > +     struct strvec trailer_args = STRVEC_INIT;\n> >       int icase = 0;\n> >       int edit_flag = 0;\n> >       struct option options[] = {\n> > @@ -479,6 +516,7 @@ int cmd_tag(int argc, const char **argv, const char *prefix)\n> >               OPT_CALLBACK_F('m', \"message\", &msg, N_(\"message\"),\n> >                              N_(\"tag message\"), PARSE_OPT_NONEG, parse_msg_arg),\n> >               OPT_FILENAME('F', \"file\", &msgfile, N_(\"read message from file\")),\n> > +             OPT_CALLBACK_F(0, \"trailer\", &trailer_args, N_(\"trailer\"), N_(\"add custom trailer(s)\"), PARSE_OPT_NONEG, opt_pass_trailer),\n> >               OPT_BOOL('e', \"edit\", &edit_flag, N_(\"force edit of tag message\")),\n> >               OPT_BOOL('s', \"sign\", &opt.sign, N_(\"annotated and GPG-signed tag\")),\n> >               OPT_CLEANUP(&cleanup_arg),\n> > @@ -548,7 +586,7 @@ int cmd_tag(int argc, const char **argv, const char *prefix)\n> >               opt.sign = 1;\n> >               set_signing_key(keyid);\n> >       }\n> > -     create_tag_object = (opt.sign || annotate || msg.given || msgfile);\n> > +     create_tag_object = (opt.sign || annotate || msg.given || msgfile || edit_flag || trailer_args.nr);\n> >\n> >       if ((create_tag_object || force) && (cmdmode != 0))\n> >               usage_with_options(git_tag_usage, options);\n> > @@ -635,7 +673,7 @@ int cmd_tag(int argc, const char **argv, const char *prefix)\n> >       else if (!force)\n> >               die(_(\"tag '%s' already exists\"), tag);\n> >\n> > -     opt.message_given = msg.given || msgfile;\n> > +     opt.message_given = msg.given || (msgfile != NULL);\n> >       opt.use_editor = edit_flag;\n>\n> Besides being not required, this change also violates our coding style\n> where we don't explicitly check for NULL pointers.\n>\n> >       if (!cleanup_arg || !strcmp(cleanup_arg, \"strip\"))\n> > @@ -653,7 +691,7 @@ int cmd_tag(int argc, const char **argv, const char *prefix)\n> >               if (force_sign_annotate && !annotate)\n> >                       opt.sign = 1;\n> >               path = git_pathdup(\"TAG_EDITMSG\");\n> > -             create_tag(&object, object_ref, tag, &buf, &opt, &prev, &object,\n> > +             create_tag(&object, object_ref, tag, &buf, &opt, &prev, &object, &trailer_args,\n> >                          path);\n>\n> Nit: let's move `&trailer_args` to the next line to not make it overly\n> long.\n>\n> >       }\n> >\n> > @@ -686,6 +724,7 @@ int cmd_tag(int argc, const char **argv, const char *prefix)\n> >       strbuf_release(&reflog_msg);\n> >       strbuf_release(&msg.buf);\n> >       strbuf_release(&err);\n> > +     strvec_clear(&trailer_args);\n> >       free(msgfile);\n> >       return ret;\n> >  }\n> > diff --git a/t/t7004-tag.sh b/t/t7004-tag.sh\n> > index 696866d7794..364db2b4685 100755\n> > --- a/t/t7004-tag.sh\n> > +++ b/t/t7004-tag.sh\n> > @@ -668,6 +668,105 @@ test_expect_success \\\n> >       test_cmp expect actual\n> >  '\n> >\n> > +# trailers\n> > +\n> > +get_tag_header tag-with-inline-message-and-trailers $commit commit $time >expect\n> > +cat >>expect <<EOF\n> > +create tag with trailers\n> > +\n> > +my-trailer: here\n> > +alt-trailer: there\n> > +EOF\n>\n> You probably just follow precedent in this file, but our modern coding\n> style sets up the `expect` file inside of the test body itself. You also\n> do it like that in other tests, so let's be consistent.\n>\n> The same comment applies to other tests, as well.\n>\n> Patrick\n"},{"id":"493638","messageId":"CAJdN7KhCesU-j=yyM5Sk0sMj8W2Gk=LJc-Dzf8-2+gjhkgA4HQ@mail.gmail.com","threadId":"61376","inReplyTo":"Zi9DGYwlT7VnW7oj@tanuki","subject":"Re: [PATCH] builtin/tag.c: add --trailer arg","fromName":"John Passaro","fromEmail":"john.a.passaro@gmail.com","sentAt":"2024-04-29T15:05:36Z","receivedAt":"2024-04-29T15:06:15Z","isPatch":true,"sender":{"key":"john.a.passaro@gmail.com","avatar":"https://avatars.githubusercontent.com/u/6754005?v=4"},"body":"apologies for the half-cocked message before, please disregard!\n\nOn Mon, Apr 29, 2024 at 2:50 AM Patrick Steinhardt <ps@pks.im> wrote:\n>\n> On Mon, Apr 29, 2024 at 04:31:15AM +0000, John Passaro via GitGitGadget wrote:\n> > From: John Passaro <john.a.passaro@gmail.com>\n> >\n> > Teach git-tag to accept --trailer option to add trailers to annotated\n> > tag messages, like git-commit.\n> >\n> > Signed-off-by: John Passaro <john.a.passaro@gmail.com>\n>\n> This feels like a sensible addition to me indeed, thanks!\n\nThanks, and thank you for the thoughtful feedback.\nI have incorporated most of it on the github PR branch\n(https://github.com/gitgitgadget/git/pull/1723).\nBefore submitting a new patch I had a couple of questions.\n\n[snip]\n\n> > @@ -313,14 +316,18 @@ static void create_tag(const struct object_id *object, const char *object_ref,\n> >                   tag,\n> >                   git_committer_info(IDENT_STRICT));\n> >\n> > -     if (!opt->message_given || opt->use_editor) {\n> > +     should_edit = opt->use_editor || !opt->message_given;\n> > +     if (should_edit || trailer_args->nr) {\n> >               int fd;\n> >\n> >               /* write the template message before editing: */\n> >               fd = xopen(path, O_CREAT | O_TRUNC | O_WRONLY, 0600);\n> >\n> > -             if (opt->message_given) {\n> > +             if (opt->message_given && buf->len) {\n> >                       write_or_die(fd, buf->buf, buf->len);\n> > +                     if (trailer_args->nr && buf->buf[buf->len-1] != '\\n') {\n> > +                             write_or_die(fd, \"\\n\", 1);\n> > +                     }\n>\n> We avoid braces around single-line statements.\n>\n> I was also wondering whether we can simplify this to:\n>\n>     if (opt->message_given && buf->len) {\n>         strbuf_complete(buf, '\\n');\n>         write_or_die(fd, buf->buf, buf->len);\n>     }\n>\n> Or does changing `buf` cause problems for us?\n>\n> >                       strbuf_reset(buf);\n\nI think altering `buf` does not cause problems precisely\nbecause we do it on the next line here with strbuf_reset().\nstrbuf_complete() seems like a clearly better choice.\nTwo questions:\n* should it be done conditionally on `trailer_args->nr` as I did\n  here, or should it be unconditional?\n* You left strbuf_reset() out of your snippet - i don't know for sure\n  why it was there in the first place, should it stay?\n\n\n> >               } else if (!is_null_oid(prev)) {\n> >                       write_tag_body(fd, prev);\n> > @@ -338,10 +345,31 @@ static void create_tag(const struct object_id *object, const char *object_ref,\n> >               }\n> >               close(fd);\n> >\n> > -             if (launch_editor(path, buf, NULL)) {\n> > -                     fprintf(stderr,\n> > -                     _(\"Please supply the message using either -m or -F option.\\n\"));\n> > -                     exit(1);\n> > +             if (trailer_args->nr) {\n> > +                     struct child_process run_trailer = CHILD_PROCESS_INIT;\n> > +\n> > +                     strvec_pushl(&run_trailer.args, \"interpret-trailers\",\n> > +                                  \"--in-place\", \"--no-divider\",\n> > +                                  path, NULL);\n> > +                     strvec_pushv(&run_trailer.args, trailer_args->v);\n> > +                     run_trailer.git_cmd = 1;\n> > +                     if (run_command(&run_trailer))\n> > +                             die(_(\"unable to pass trailers to --trailers\"));\n> > +             }\n>\n> This part is copied from `builtin/commit.c`. Would it make sense to move\n> it into a function `amend_trailers_to_file()` or similar that we add to\n> our trailer API so that we can avoid the code duplication?\n\nIt seems to me the duplication goes further than that.\nIdeally I would love to have a unified approach to the problem of\ncombining `-m`, `-F`, `-e`, and `--trailer` generally. That would change\none existing nit I have in git-tag, which is that with `-m`, `-F`,\nor `-fae` to replace an existing tag, it doesn't add commentary guidance\nthe way git-commit does.\n\nThat's a bigger change than I'm comfortable taking on and it's\narguably beyond the scope of this ticket. Question here is - first,\nis such a consolidation possible in the future, and second, if it were,\nwould this refactor (dedicated function for augmenting a strbuf/file\nwith trailers) still be valuable?\n\n\n> >               } else if (!is_null_oid(prev)) {\n> >                       write_tag_body(fd, prev);\n> > @@ -338,10 +345,31 @@ static void create_tag(const struct object_id *object, const char *object_ref,\n> >               }\n> >               close(fd);\n> >\n> > -             if (launch_editor(path, buf, NULL)) {\n> > -                     fprintf(stderr,\n> > -                     _(\"Please supply the message using either -m or -F option.\\n\"));\n> > -                     exit(1);\n> > +             if (trailer_args->nr) {\n> > +                     struct child_process run_trailer = CHILD_PROCESS_INIT;\n> > +\n> > +                     strvec_pushl(&run_trailer.args, \"interpret-trailers\",\n> > +                                  \"--in-place\", \"--no-divider\",\n> > +                                  path, NULL);\n> > +                     strvec_pushv(&run_trailer.args, trailer_args->v);\n> > +                     run_trailer.git_cmd = 1;\n> > +                     if (run_command(&run_trailer))\n> > +                             die(_(\"unable to pass trailers to --trailers\"));\n> > +             }\n>\n> This part is copied from `builtin/commit.c`. Would it make sense to move\n> it into a function `amend_trailers_to_file()` or similar that we add to\n> our trailer API so that we can avoid the code duplication?\n>\n> > +             if (should_edit) {\n> > +                     if (launch_editor(path, buf, NULL)) {\n> > +                             fprintf(stderr,\n> > +                             _(\"Please supply the message using either -m or -F option.\\n\"));\n> > +                             exit(1);\n> > +                     }\n>\n> I know you simply re-indented the block here, but let's also fix the\n> indentation of the second argument to fprintf(3P) while at it.\n>\n> > +             } else if (trailer_args->nr) {\n> > +                     strbuf_reset(buf);\n> > +                     if (strbuf_read_file(buf, path, 0) < 0) {\n> > +                             fprintf(stderr,\n> > +                                             _(\"Please supply the message using either -m or -F option.\\n\"));\n> > +                             exit(1);\n> > +                     }\n> >               }\n> >       }\n> >\n> > @@ -416,6 +444,14 @@ struct msg_arg {\n> >       struct strbuf buf;\n> >  };\n> >\n> > +static int opt_pass_trailer(const struct option *opt, const char *arg, int unset)\n> > +{\n> > +     BUG_ON_OPT_NEG(unset);\n> > +\n> > +     strvec_pushl(opt->value, \"--trailer\", arg, NULL);\n> > +     return 0;\n> > +}\n> > +\n> >  static int parse_msg_arg(const struct option *opt, const char *arg, int unset)\n> >  {\n> >       struct msg_arg *msg = opt->value;\n> > @@ -463,6 +499,7 @@ int cmd_tag(int argc, const char **argv, const char *prefix)\n> >       struct ref_sorting *sorting;\n> >       struct string_list sorting_options = STRING_LIST_INIT_DUP;\n> >       struct ref_format format = REF_FORMAT_INIT;\n> > +     struct strvec trailer_args = STRVEC_INIT;\n> >       int icase = 0;\n> >       int edit_flag = 0;\n> >       struct option options[] = {\n> > @@ -479,6 +516,7 @@ int cmd_tag(int argc, const char **argv, const char *prefix)\n> >               OPT_CALLBACK_F('m', \"message\", &msg, N_(\"message\"),\n> >                              N_(\"tag message\"), PARSE_OPT_NONEG, parse_msg_arg),\n> >               OPT_FILENAME('F', \"file\", &msgfile, N_(\"read message from file\")),\n> > +             OPT_CALLBACK_F(0, \"trailer\", &trailer_args, N_(\"trailer\"), N_(\"add custom trailer(s)\"), PARSE_OPT_NONEG, opt_pass_trailer),\n> >               OPT_BOOL('e', \"edit\", &edit_flag, N_(\"force edit of tag message\")),\n> >               OPT_BOOL('s', \"sign\", &opt.sign, N_(\"annotated and GPG-signed tag\")),\n> >               OPT_CLEANUP(&cleanup_arg),\n> > @@ -548,7 +586,7 @@ int cmd_tag(int argc, const char **argv, const char *prefix)\n> >               opt.sign = 1;\n> >               set_signing_key(keyid);\n> >       }\n> > -     create_tag_object = (opt.sign || annotate || msg.given || msgfile);\n> > +     create_tag_object = (opt.sign || annotate || msg.given || msgfile || edit_flag || trailer_args.nr);\n> >\n> >       if ((create_tag_object || force) && (cmdmode != 0))\n> >               usage_with_options(git_tag_usage, options);\n> > @@ -635,7 +673,7 @@ int cmd_tag(int argc, const char **argv, const char *prefix)\n> >       else if (!force)\n> >               die(_(\"tag '%s' already exists\"), tag);\n> >\n> > -     opt.message_given = msg.given || msgfile;\n> > +     opt.message_given = msg.given || (msgfile != NULL);\n> >       opt.use_editor = edit_flag;\n>\n> Besides being not required, this change also violates our coding style\n> where we don't explicitly check for NULL pointers.\n>\n> >       if (!cleanup_arg || !strcmp(cleanup_arg, \"strip\"))\n> > @@ -653,7 +691,7 @@ int cmd_tag(int argc, const char **argv, const char *prefix)\n> >               if (force_sign_annotate && !annotate)\n> >                       opt.sign = 1;\n> >               path = git_pathdup(\"TAG_EDITMSG\");\n> > -             create_tag(&object, object_ref, tag, &buf, &opt, &prev, &object,\n> > +             create_tag(&object, object_ref, tag, &buf, &opt, &prev, &object, &trailer_args,\n> >                          path);\n>\n> Nit: let's move `&trailer_args` to the next line to not make it overly\n> long.\n>\n> >       }\n> >\n> > @@ -686,6 +724,7 @@ int cmd_tag(int argc, const char **argv, const char *prefix)\n> >       strbuf_release(&reflog_msg);\n> >       strbuf_release(&msg.buf);\n> >       strbuf_release(&err);\n> > +     strvec_clear(&trailer_args);\n> >       free(msgfile);\n> >       return ret;\n> >  }\n> > diff --git a/t/t7004-tag.sh b/t/t7004-tag.sh\n> > index 696866d7794..364db2b4685 100755\n> > --- a/t/t7004-tag.sh\n> > +++ b/t/t7004-tag.sh\n> > @@ -668,6 +668,105 @@ test_expect_success \\\n> >       test_cmp expect actual\n> >  '\n> >\n> > +# trailers\n> > +\n> > +get_tag_header tag-with-inline-message-and-trailers $commit commit $time >expect\n> > +cat >>expect <<EOF\n> > +create tag with trailers\n> > +\n> > +my-trailer: here\n> > +alt-trailer: there\n> > +EOF\n>\n> You probably just follow precedent in this file, but our modern coding\n> style sets up the `expect` file inside of the test body itself. You also\n> do it like that in other tests, so let's be consistent.\n>\n> The same comment applies to other tests, as well.\n>\n> Patrick\n"},{"id":"493641","messageId":"xmqqplu8yyp2.fsf@gitster.g","threadId":"61376","inReplyTo":"Zi9DGYwlT7VnW7oj@tanuki","subject":"Re: [PATCH] builtin/tag.c: add --trailer arg","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2024-04-29T15:29:29Z","receivedAt":"2024-04-29T15:29:36Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Patrick Steinhardt <ps@pks.im> writes:\n\n> On Mon, Apr 29, 2024 at 04:31:15AM +0000, John Passaro via GitGitGadget wrote:\n>> From: John Passaro <john.a.passaro@gmail.com>\n>> \n>> Teach git-tag to accept --trailer option to add trailers to annotated\n>> tag messages, like git-commit.\n>> \n>> Signed-off-by: John Passaro <john.a.passaro@gmail.com>\n>\n> This feels like a sensible addition to me indeed, thanks!\n\nAt the surface level, I tend to agree, but I am of two minds,\nespecially around the \"-s\" option, though.  \"commit -s\" is to work\nwith the \"Signed-off-by\" trailer, but \"tag -s\" is not.\n\nMore importantly, I doubt that many trailers we commonly see in the\ncomit objects, like \"Acked-by\", \"Reviewed-by\", or even \"CC\", are\napplicable in the context of tags.  So I am ambivalent.\n\nIf we were to adop this new feature, your review already has done a\nvery good job and I see room for adding nothing more on the\nimplementation.\n\nThanks, both.\n\n> [snip]\n>> diff --git a/builtin/tag.c b/builtin/tag.c\n>> index 9a33cb50b45..0334a5d15ec 100644\n>> --- a/builtin/tag.c\n>> +++ b/builtin/tag.c\n>> @@ -28,9 +28,11 @@\n>>  #include \"date.h\"\n>>  #include \"write-or-die.h\"\n>>  #include \"object-file-convert.h\"\n>> +#include \"run-command.h\"\n>>  \n>>  static const char * const git_tag_usage[] = {\n>>  \tN_(\"git tag [-a | -s | -u <key-id>] [-f] [-m <msg> | -F <file>] [-e]\\n\"\n>> +\t   \"        [(--trailer <token>[(=|:)<value>])...]\\n\"\n>>  \t   \"        <tagname> [<commit> | <object>]\"),\n>>  \tN_(\"git tag -d <tagname>...\"),\n>>  \tN_(\"git tag [-n[<num>]] -l [--contains <commit>] [--no-contains <commit>]\\n\"\n>> @@ -290,10 +292,11 @@ static const char message_advice_nested_tag[] =\n>>  static void create_tag(const struct object_id *object, const char *object_ref,\n>>  \t\t       const char *tag,\n>>  \t\t       struct strbuf *buf, struct create_tag_options *opt,\n>> -\t\t       struct object_id *prev, struct object_id *result, char *path)\n>> +\t\t       struct object_id *prev, struct object_id *result, struct strvec *trailer_args, char *path)\n>\n> This line is overly long now, let's break it.\n>\n>>  {\n>>  \tenum object_type type;\n>>  \tstruct strbuf header = STRBUF_INIT;\n>> +\tint should_edit;\n>>  \n>>  \ttype = oid_object_info(the_repository, object, NULL);\n>>  \tif (type <= OBJ_NONE)\n>> @@ -313,14 +316,18 @@ static void create_tag(const struct object_id *object, const char *object_ref,\n>>  \t\t    tag,\n>>  \t\t    git_committer_info(IDENT_STRICT));\n>>  \n>> -\tif (!opt->message_given || opt->use_editor) {\n>> +\tshould_edit = opt->use_editor || !opt->message_given;\n>> +\tif (should_edit || trailer_args->nr) {\n>>  \t\tint fd;\n>>  \n>>  \t\t/* write the template message before editing: */\n>>  \t\tfd = xopen(path, O_CREAT | O_TRUNC | O_WRONLY, 0600);\n>>  \n>> -\t\tif (opt->message_given) {\n>> +\t\tif (opt->message_given && buf->len) {\n>>  \t\t\twrite_or_die(fd, buf->buf, buf->len);\n>> +\t\t\tif (trailer_args->nr && buf->buf[buf->len-1] != '\\n') {\n>> +\t\t\t\twrite_or_die(fd, \"\\n\", 1);\n>> +\t\t\t}\n>\n> We avoid braces around single-line statements.\n>\n> I was also wondering whether we can simplify this to:\n>\n>     if (opt->message_given && buf->len) {\n>         strbuf_complete(buf, '\\n');\n>         write_or_die(fd, buf->buf, buf->len);\n>     }\n>\n> Or does changing `buf` cause problems for us?\n>\n>>  \t\t\tstrbuf_reset(buf);\n>>  \t\t} else if (!is_null_oid(prev)) {\n>>  \t\t\twrite_tag_body(fd, prev);\n>> @@ -338,10 +345,31 @@ static void create_tag(const struct object_id *object, const char *object_ref,\n>>  \t\t}\n>>  \t\tclose(fd);\n>>  \n>> -\t\tif (launch_editor(path, buf, NULL)) {\n>> -\t\t\tfprintf(stderr,\n>> -\t\t\t_(\"Please supply the message using either -m or -F option.\\n\"));\n>> -\t\t\texit(1);\n>> +\t\tif (trailer_args->nr) {\n>> +\t\t\tstruct child_process run_trailer = CHILD_PROCESS_INIT;\n>> +\n>> +\t\t\tstrvec_pushl(&run_trailer.args, \"interpret-trailers\",\n>> +\t\t\t\t     \"--in-place\", \"--no-divider\",\n>> +\t\t\t\t     path, NULL);\n>> +\t\t\tstrvec_pushv(&run_trailer.args, trailer_args->v);\n>> +\t\t\trun_trailer.git_cmd = 1;\n>> +\t\t\tif (run_command(&run_trailer))\n>> +\t\t\t\tdie(_(\"unable to pass trailers to --trailers\"));\n>> +\t\t}\n>\n> This part is copied from `builtin/commit.c`. Would it make sense to move\n> it into a function `amend_trailers_to_file()` or similar that we add to\n> our trailer API so that we can avoid the code duplication?\n>\n>> +\t\tif (should_edit) {\n>> +\t\t\tif (launch_editor(path, buf, NULL)) {\n>> +\t\t\t\tfprintf(stderr,\n>> +\t\t\t\t_(\"Please supply the message using either -m or -F option.\\n\"));\n>> +\t\t\t\texit(1);\n>> +\t\t\t}\n>\n> I know you simply re-indented the block here, but let's also fix the\n> indentation of the second argument to fprintf(3P) while at it.\n>\n>> +\t\t} else if (trailer_args->nr) {\n>> +\t\t\tstrbuf_reset(buf);\n>> +\t\t\tif (strbuf_read_file(buf, path, 0) < 0) {\n>> +\t\t\t\tfprintf(stderr,\n>> +\t\t\t\t\t\t_(\"Please supply the message using either -m or -F option.\\n\"));\n>> +\t\t\t\texit(1);\n>> +\t\t\t}\n>>  \t\t}\n>>  \t}\n>>  \n>> @@ -416,6 +444,14 @@ struct msg_arg {\n>>  \tstruct strbuf buf;\n>>  };\n>>  \n>> +static int opt_pass_trailer(const struct option *opt, const char *arg, int unset)\n>> +{\n>> +\tBUG_ON_OPT_NEG(unset);\n>> +\n>> +\tstrvec_pushl(opt->value, \"--trailer\", arg, NULL);\n>> +\treturn 0;\n>> +}\n>> +\n>>  static int parse_msg_arg(const struct option *opt, const char *arg, int unset)\n>>  {\n>>  \tstruct msg_arg *msg = opt->value;\n>> @@ -463,6 +499,7 @@ int cmd_tag(int argc, const char **argv, const char *prefix)\n>>  \tstruct ref_sorting *sorting;\n>>  \tstruct string_list sorting_options = STRING_LIST_INIT_DUP;\n>>  \tstruct ref_format format = REF_FORMAT_INIT;\n>> +\tstruct strvec trailer_args = STRVEC_INIT;\n>>  \tint icase = 0;\n>>  \tint edit_flag = 0;\n>>  \tstruct option options[] = {\n>> @@ -479,6 +516,7 @@ int cmd_tag(int argc, const char **argv, const char *prefix)\n>>  \t\tOPT_CALLBACK_F('m', \"message\", &msg, N_(\"message\"),\n>>  \t\t\t       N_(\"tag message\"), PARSE_OPT_NONEG, parse_msg_arg),\n>>  \t\tOPT_FILENAME('F', \"file\", &msgfile, N_(\"read message from file\")),\n>> +\t\tOPT_CALLBACK_F(0, \"trailer\", &trailer_args, N_(\"trailer\"), N_(\"add custom trailer(s)\"), PARSE_OPT_NONEG, opt_pass_trailer),\n>>  \t\tOPT_BOOL('e', \"edit\", &edit_flag, N_(\"force edit of tag message\")),\n>>  \t\tOPT_BOOL('s', \"sign\", &opt.sign, N_(\"annotated and GPG-signed tag\")),\n>>  \t\tOPT_CLEANUP(&cleanup_arg),\n>> @@ -548,7 +586,7 @@ int cmd_tag(int argc, const char **argv, const char *prefix)\n>>  \t\topt.sign = 1;\n>>  \t\tset_signing_key(keyid);\n>>  \t}\n>> -\tcreate_tag_object = (opt.sign || annotate || msg.given || msgfile);\n>> +\tcreate_tag_object = (opt.sign || annotate || msg.given || msgfile || edit_flag || trailer_args.nr);\n>>  \n>>  \tif ((create_tag_object || force) && (cmdmode != 0))\n>>  \t\tusage_with_options(git_tag_usage, options);\n>> @@ -635,7 +673,7 @@ int cmd_tag(int argc, const char **argv, const char *prefix)\n>>  \telse if (!force)\n>>  \t\tdie(_(\"tag '%s' already exists\"), tag);\n>>  \n>> -\topt.message_given = msg.given || msgfile;\n>> +\topt.message_given = msg.given || (msgfile != NULL);\n>>  \topt.use_editor = edit_flag;\n>\n> Besides being not required, this change also violates our coding style\n> where we don't explicitly check for NULL pointers.\n>\n>>  \tif (!cleanup_arg || !strcmp(cleanup_arg, \"strip\"))\n>> @@ -653,7 +691,7 @@ int cmd_tag(int argc, const char **argv, const char *prefix)\n>>  \t\tif (force_sign_annotate && !annotate)\n>>  \t\t\topt.sign = 1;\n>>  \t\tpath = git_pathdup(\"TAG_EDITMSG\");\n>> -\t\tcreate_tag(&object, object_ref, tag, &buf, &opt, &prev, &object,\n>> +\t\tcreate_tag(&object, object_ref, tag, &buf, &opt, &prev, &object, &trailer_args,\n>>  \t\t\t   path);\n>\n> Nit: let's move `&trailer_args` to the next line to not make it overly\n> long.\n>\n>>  \t}\n>>  \n>> @@ -686,6 +724,7 @@ int cmd_tag(int argc, const char **argv, const char *prefix)\n>>  \tstrbuf_release(&reflog_msg);\n>>  \tstrbuf_release(&msg.buf);\n>>  \tstrbuf_release(&err);\n>> +\tstrvec_clear(&trailer_args);\n>>  \tfree(msgfile);\n>>  \treturn ret;\n>>  }\n>> diff --git a/t/t7004-tag.sh b/t/t7004-tag.sh\n>> index 696866d7794..364db2b4685 100755\n>> --- a/t/t7004-tag.sh\n>> +++ b/t/t7004-tag.sh\n>> @@ -668,6 +668,105 @@ test_expect_success \\\n>>  \ttest_cmp expect actual\n>>  '\n>>  \n>> +# trailers\n>> +\n>> +get_tag_header tag-with-inline-message-and-trailers $commit commit $time >expect\n>> +cat >>expect <<EOF\n>> +create tag with trailers\n>> +\n>> +my-trailer: here\n>> +alt-trailer: there\n>> +EOF\n>\n> You probably just follow precedent in this file, but our modern coding\n> style sets up the `expect` file inside of the test body itself. You also\n> do it like that in other tests, so let's be consistent.\n>\n> The same comment applies to other tests, as well.\n>\n> Patrick\n"},{"id":"493649","messageId":"CAJdN7Kiep79YeN2GCbjdz9HhHif0nyEDUf51EKcESzR-DOBZQw@mail.gmail.com","threadId":"61376","inReplyTo":"xmqqplu8yyp2.fsf@gitster.g","subject":"Re: [PATCH] builtin/tag.c: add --trailer arg","fromName":"John Passaro","fromEmail":"john.a.passaro@gmail.com","sentAt":"2024-04-29T16:38:16Z","receivedAt":"2024-04-29T16:38:56Z","isPatch":true,"sender":{"key":"john.a.passaro@gmail.com","avatar":"https://avatars.githubusercontent.com/u/6754005?v=4"},"body":"On Mon, Apr 29, 2024 at 11:29 AM Junio C Hamano <gitster@pobox.com> wrote:\n>\n> Patrick Steinhardt <ps@pks.im> writes:\n>\n> > On Mon, Apr 29, 2024 at 04:31:15AM +0000, John Passaro via GitGitGadget wrote:\n> >> From: John Passaro <john.a.passaro@gmail.com>\n> >>\n> >> Teach git-tag to accept --trailer option to add trailers to annotated\n> >> tag messages, like git-commit.\n> >>\n> >> Signed-off-by: John Passaro <john.a.passaro@gmail.com>\n> >\n> > This feels like a sensible addition to me indeed, thanks!\n>\n> At the surface level, I tend to agree, but I am of two minds,\n> especially around the \"-s\" option, though.  \"commit -s\" is to work\n> with the \"Signed-off-by\" trailer, but \"tag -s\" is not.\n>\n> More importantly, I doubt that many trailers we commonly see in the\n> comit objects, like \"Acked-by\", \"Reviewed-by\", or even \"CC\", are\n> applicable in the context of tags.  So I am ambivalent.\n\nA couple of words on the motivation here. First, by way of --list\n--format=\"%(trailer)\",\ngit-tag arguably has read-side support for trailers already; adding write\nsupport seems pretty reasonable. Second, even though not all the trailers\nbroadly used for commits are an obvious fit for tags, some still are -\n\"Signed-off-by\" for one would seem plausibly useful. In my team's usage (which\ninspired this change), tag trailers have emerged as a convenient way to pass\nmachine-readable metadata to CICD.\n\n\n>\n> If we were to adop this new feature, your review already has done a\n> very good job and I see room for adding nothing more on the\n> implementation.\n>\n> Thanks, both.\n>\n> > [snip]\n> >> diff --git a/builtin/tag.c b/builtin/tag.c\n> >> index 9a33cb50b45..0334a5d15ec 100644\n> >> --- a/builtin/tag.c\n> >> +++ b/builtin/tag.c\n> >> @@ -28,9 +28,11 @@\n> >>  #include \"date.h\"\n> >>  #include \"write-or-die.h\"\n> >>  #include \"object-file-convert.h\"\n> >> +#include \"run-command.h\"\n> >>\n> >>  static const char * const git_tag_usage[] = {\n> >>      N_(\"git tag [-a | -s | -u <key-id>] [-f] [-m <msg> | -F <file>] [-e]\\n\"\n> >> +       \"        [(--trailer <token>[(=|:)<value>])...]\\n\"\n> >>         \"        <tagname> [<commit> | <object>]\"),\n> >>      N_(\"git tag -d <tagname>...\"),\n> >>      N_(\"git tag [-n[<num>]] -l [--contains <commit>] [--no-contains <commit>]\\n\"\n> >> @@ -290,10 +292,11 @@ static const char message_advice_nested_tag[] =\n> >>  static void create_tag(const struct object_id *object, const char *object_ref,\n> >>                     const char *tag,\n> >>                     struct strbuf *buf, struct create_tag_options *opt,\n> >> -                   struct object_id *prev, struct object_id *result, char *path)\n> >> +                   struct object_id *prev, struct object_id *result, struct strvec *trailer_args, char *path)\n> >\n> > This line is overly long now, let's break it.\n> >\n> >>  {\n> >>      enum object_type type;\n> >>      struct strbuf header = STRBUF_INIT;\n> >> +    int should_edit;\n> >>\n> >>      type = oid_object_info(the_repository, object, NULL);\n> >>      if (type <= OBJ_NONE)\n> >> @@ -313,14 +316,18 @@ static void create_tag(const struct object_id *object, const char *object_ref,\n> >>                  tag,\n> >>                  git_committer_info(IDENT_STRICT));\n> >>\n> >> -    if (!opt->message_given || opt->use_editor) {\n> >> +    should_edit = opt->use_editor || !opt->message_given;\n> >> +    if (should_edit || trailer_args->nr) {\n> >>              int fd;\n> >>\n> >>              /* write the template message before editing: */\n> >>              fd = xopen(path, O_CREAT | O_TRUNC | O_WRONLY, 0600);\n> >>\n> >> -            if (opt->message_given) {\n> >> +            if (opt->message_given && buf->len) {\n> >>                      write_or_die(fd, buf->buf, buf->len);\n> >> +                    if (trailer_args->nr && buf->buf[buf->len-1] != '\\n') {\n> >> +                            write_or_die(fd, \"\\n\", 1);\n> >> +                    }\n> >\n> > We avoid braces around single-line statements.\n> >\n> > I was also wondering whether we can simplify this to:\n> >\n> >     if (opt->message_given && buf->len) {\n> >         strbuf_complete(buf, '\\n');\n> >         write_or_die(fd, buf->buf, buf->len);\n> >     }\n> >\n> > Or does changing `buf` cause problems for us?\n> >\n> >>                      strbuf_reset(buf);\n> >>              } else if (!is_null_oid(prev)) {\n> >>                      write_tag_body(fd, prev);\n> >> @@ -338,10 +345,31 @@ static void create_tag(const struct object_id *object, const char *object_ref,\n> >>              }\n> >>              close(fd);\n> >>\n> >> -            if (launch_editor(path, buf, NULL)) {\n> >> -                    fprintf(stderr,\n> >> -                    _(\"Please supply the message using either -m or -F option.\\n\"));\n> >> -                    exit(1);\n> >> +            if (trailer_args->nr) {\n> >> +                    struct child_process run_trailer = CHILD_PROCESS_INIT;\n> >> +\n> >> +                    strvec_pushl(&run_trailer.args, \"interpret-trailers\",\n> >> +                                 \"--in-place\", \"--no-divider\",\n> >> +                                 path, NULL);\n> >> +                    strvec_pushv(&run_trailer.args, trailer_args->v);\n> >> +                    run_trailer.git_cmd = 1;\n> >> +                    if (run_command(&run_trailer))\n> >> +                            die(_(\"unable to pass trailers to --trailers\"));\n> >> +            }\n> >\n> > This part is copied from `builtin/commit.c`. Would it make sense to move\n> > it into a function `amend_trailers_to_file()` or similar that we add to\n> > our trailer API so that we can avoid the code duplication?\n> >\n> >> +            if (should_edit) {\n> >> +                    if (launch_editor(path, buf, NULL)) {\n> >> +                            fprintf(stderr,\n> >> +                            _(\"Please supply the message using either -m or -F option.\\n\"));\n> >> +                            exit(1);\n> >> +                    }\n> >\n> > I know you simply re-indented the block here, but let's also fix the\n> > indentation of the second argument to fprintf(3P) while at it.\n> >\n> >> +            } else if (trailer_args->nr) {\n> >> +                    strbuf_reset(buf);\n> >> +                    if (strbuf_read_file(buf, path, 0) < 0) {\n> >> +                            fprintf(stderr,\n> >> +                                            _(\"Please supply the message using either -m or -F option.\\n\"));\n> >> +                            exit(1);\n> >> +                    }\n> >>              }\n> >>      }\n> >>\n> >> @@ -416,6 +444,14 @@ struct msg_arg {\n> >>      struct strbuf buf;\n> >>  };\n> >>\n> >> +static int opt_pass_trailer(const struct option *opt, const char *arg, int unset)\n> >> +{\n> >> +    BUG_ON_OPT_NEG(unset);\n> >> +\n> >> +    strvec_pushl(opt->value, \"--trailer\", arg, NULL);\n> >> +    return 0;\n> >> +}\n> >> +\n> >>  static int parse_msg_arg(const struct option *opt, const char *arg, int unset)\n> >>  {\n> >>      struct msg_arg *msg = opt->value;\n> >> @@ -463,6 +499,7 @@ int cmd_tag(int argc, const char **argv, const char *prefix)\n> >>      struct ref_sorting *sorting;\n> >>      struct string_list sorting_options = STRING_LIST_INIT_DUP;\n> >>      struct ref_format format = REF_FORMAT_INIT;\n> >> +    struct strvec trailer_args = STRVEC_INIT;\n> >>      int icase = 0;\n> >>      int edit_flag = 0;\n> >>      struct option options[] = {\n> >> @@ -479,6 +516,7 @@ int cmd_tag(int argc, const char **argv, const char *prefix)\n> >>              OPT_CALLBACK_F('m', \"message\", &msg, N_(\"message\"),\n> >>                             N_(\"tag message\"), PARSE_OPT_NONEG, parse_msg_arg),\n> >>              OPT_FILENAME('F', \"file\", &msgfile, N_(\"read message from file\")),\n> >> +            OPT_CALLBACK_F(0, \"trailer\", &trailer_args, N_(\"trailer\"), N_(\"add custom trailer(s)\"), PARSE_OPT_NONEG, opt_pass_trailer),\n> >>              OPT_BOOL('e', \"edit\", &edit_flag, N_(\"force edit of tag message\")),\n> >>              OPT_BOOL('s', \"sign\", &opt.sign, N_(\"annotated and GPG-signed tag\")),\n> >>              OPT_CLEANUP(&cleanup_arg),\n> >> @@ -548,7 +586,7 @@ int cmd_tag(int argc, const char **argv, const char *prefix)\n> >>              opt.sign = 1;\n> >>              set_signing_key(keyid);\n> >>      }\n> >> -    create_tag_object = (opt.sign || annotate || msg.given || msgfile);\n> >> +    create_tag_object = (opt.sign || annotate || msg.given || msgfile || edit_flag || trailer_args.nr);\n> >>\n> >>      if ((create_tag_object || force) && (cmdmode != 0))\n> >>              usage_with_options(git_tag_usage, options);\n> >> @@ -635,7 +673,7 @@ int cmd_tag(int argc, const char **argv, const char *prefix)\n> >>      else if (!force)\n> >>              die(_(\"tag '%s' already exists\"), tag);\n> >>\n> >> -    opt.message_given = msg.given || msgfile;\n> >> +    opt.message_given = msg.given || (msgfile != NULL);\n> >>      opt.use_editor = edit_flag;\n> >\n> > Besides being not required, this change also violates our coding style\n> > where we don't explicitly check for NULL pointers.\n> >\n> >>      if (!cleanup_arg || !strcmp(cleanup_arg, \"strip\"))\n> >> @@ -653,7 +691,7 @@ int cmd_tag(int argc, const char **argv, const char *prefix)\n> >>              if (force_sign_annotate && !annotate)\n> >>                      opt.sign = 1;\n> >>              path = git_pathdup(\"TAG_EDITMSG\");\n> >> -            create_tag(&object, object_ref, tag, &buf, &opt, &prev, &object,\n> >> +            create_tag(&object, object_ref, tag, &buf, &opt, &prev, &object, &trailer_args,\n> >>                         path);\n> >\n> > Nit: let's move `&trailer_args` to the next line to not make it overly\n> > long.\n> >\n> >>      }\n> >>\n> >> @@ -686,6 +724,7 @@ int cmd_tag(int argc, const char **argv, const char *prefix)\n> >>      strbuf_release(&reflog_msg);\n> >>      strbuf_release(&msg.buf);\n> >>      strbuf_release(&err);\n> >> +    strvec_clear(&trailer_args);\n> >>      free(msgfile);\n> >>      return ret;\n> >>  }\n> >> diff --git a/t/t7004-tag.sh b/t/t7004-tag.sh\n> >> index 696866d7794..364db2b4685 100755\n> >> --- a/t/t7004-tag.sh\n> >> +++ b/t/t7004-tag.sh\n> >> @@ -668,6 +668,105 @@ test_expect_success \\\n> >>      test_cmp expect actual\n> >>  '\n> >>\n> >> +# trailers\n> >> +\n> >> +get_tag_header tag-with-inline-message-and-trailers $commit commit $time >expect\n> >> +cat >>expect <<EOF\n> >> +create tag with trailers\n> >> +\n> >> +my-trailer: here\n> >> +alt-trailer: there\n> >> +EOF\n> >\n> > You probably just follow precedent in this file, but our modern coding\n> > style sets up the `expect` file inside of the test body itself. You also\n> > do it like that in other tests, so let's be consistent.\n> >\n> > The same comment applies to other tests, as well.\n> >\n> > Patrick\n"},{"id":"493656","messageId":"pull.1723.v2.git.1714409638089.gitgitgadget@gmail.com","threadId":"61376","inReplyTo":"pull.1723.git.1714365076246.gitgitgadget@gmail.com","subject":"[PATCH v2] builtin/tag.c: add --trailer arg","fromName":"John Passaro via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2024-04-29T16:53:57Z","receivedAt":"2024-04-29T16:54:02Z","isPatch":true,"sender":{"key":"john.a.passaro@gmail.com","avatar":"https://avatars.githubusercontent.com/u/6754005?v=4"},"body":"From: John Passaro <john.a.passaro@gmail.com>\n\nTeach git-tag to accept --trailer option to add trailers to annotated\ntag messages, like git-commit. Move the code that git-commit uses for\ntrailers to the trailer.h API, so it can be re-used for git-tag.\n\nSigned-off-by: John Passaro <john.a.passaro@gmail.com>\n---\n    builtin/tag.c: add --trailer arg\n    \n    cc: Patrick Steinhardt ps@pks.im cc: John Passaro\n    john.a.passaro@gmail.com\n\nPublished-As: https://github.com/gitgitgadget/git/releases/tag/pr-1723%2Fjpassaro%2Fjp%2Ftag-trailer-arg-v2\nFetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-1723/jpassaro/jp/tag-trailer-arg-v2\nPull-Request: https://github.com/gitgitgadget/git/pull/1723\n\nRange-diff vs v1:\n\n 1:  02d7a0f035e ! 1:  d4beb7cd67e builtin/tag.c: add --trailer arg\n     @@ Commit message\n          builtin/tag.c: add --trailer arg\n      \n          Teach git-tag to accept --trailer option to add trailers to annotated\n     -    tag messages, like git-commit.\n     +    tag messages, like git-commit. Move the code that git-commit uses for\n     +    trailers to the trailer.h API, so it can be re-used for git-tag.\n      \n          Signed-off-by: John Passaro <john.a.passaro@gmail.com>\n      \n     @@ Documentation/git-tag.txt: This option is only applicable when listing tags with\n       --edit::\n       \tThe message taken from file with `-F` and command line with\n      \n     + ## builtin/commit.c ##\n     +@@\n     + #include \"commit-reach.h\"\n     + #include \"commit-graph.h\"\n     + #include \"pretty.h\"\n     ++#include \"trailer.h\"\n     + \n     + static const char * const builtin_commit_usage[] = {\n     + \tN_(\"git commit [-a | --interactive | --patch] [-s] [-v] [-u<mode>] [--amend]\\n\"\n     +@@ builtin/commit.c: static int prepare_to_commit(const char *index_file, const char *prefix,\n     + \tfclose(s->fp);\n     + \n     + \tif (trailer_args.nr) {\n     +-\t\tstruct child_process run_trailer = CHILD_PROCESS_INIT;\n     +-\n     +-\t\tstrvec_pushl(&run_trailer.args, \"interpret-trailers\",\n     +-\t\t\t     \"--in-place\", \"--no-divider\",\n     +-\t\t\t     git_path_commit_editmsg(), NULL);\n     +-\t\tstrvec_pushv(&run_trailer.args, trailer_args.v);\n     +-\t\trun_trailer.git_cmd = 1;\n     +-\t\tif (run_command(&run_trailer))\n     ++\t\tif (amend_file_with_trailers(git_path_commit_editmsg(), &trailer_args))\n     + \t\t\tdie(_(\"unable to pass trailers to --trailers\"));\n     + \t\tstrvec_clear(&trailer_args);\n     + \t}\n     +\n       ## builtin/tag.c ##\n      @@\n       #include \"date.h\"\n       #include \"write-or-die.h\"\n       #include \"object-file-convert.h\"\n     -+#include \"run-command.h\"\n     ++#include \"trailer.h\"\n       \n       static const char * const git_tag_usage[] = {\n       \tN_(\"git tag [-a | -s | -u <key-id>] [-f] [-m <msg> | -F <file>] [-e]\\n\"\n     @@ builtin/tag.c: static const char message_advice_nested_tag[] =\n       \t\t       const char *tag,\n       \t\t       struct strbuf *buf, struct create_tag_options *opt,\n      -\t\t       struct object_id *prev, struct object_id *result, char *path)\n     -+\t\t       struct object_id *prev, struct object_id *result, struct strvec *trailer_args, char *path)\n     ++\t\t       struct object_id *prev, struct object_id *result,\n     ++\t\t       struct strvec *trailer_args, char *path)\n       {\n       \tenum object_type type;\n       \tstruct strbuf header = STRBUF_INIT;\n     @@ builtin/tag.c: static void create_tag(const struct object_id *object, const char\n       \n      -\t\tif (opt->message_given) {\n      +\t\tif (opt->message_given && buf->len) {\n     ++\t\t\tstrbuf_complete(buf, '\\n');\n       \t\t\twrite_or_die(fd, buf->buf, buf->len);\n     -+\t\t\tif (trailer_args->nr && buf->buf[buf->len-1] != '\\n') {\n     -+\t\t\t\twrite_or_die(fd, \"\\n\", 1);\n     -+\t\t\t}\n       \t\t\tstrbuf_reset(buf);\n       \t\t} else if (!is_null_oid(prev)) {\n     - \t\t\twrite_tag_body(fd, prev);\n      @@ builtin/tag.c: static void create_tag(const struct object_id *object, const char *object_ref,\n       \t\t}\n       \t\tclose(fd);\n     @@ builtin/tag.c: static void create_tag(const struct object_id *object, const char\n      -\t\t\tfprintf(stderr,\n      -\t\t\t_(\"Please supply the message using either -m or -F option.\\n\"));\n      -\t\t\texit(1);\n     -+\t\tif (trailer_args->nr) {\n     -+\t\t\tstruct child_process run_trailer = CHILD_PROCESS_INIT;\n     -+\n     -+\t\t\tstrvec_pushl(&run_trailer.args, \"interpret-trailers\",\n     -+\t\t\t\t     \"--in-place\", \"--no-divider\",\n     -+\t\t\t\t     path, NULL);\n     -+\t\t\tstrvec_pushv(&run_trailer.args, trailer_args->v);\n     -+\t\t\trun_trailer.git_cmd = 1;\n     -+\t\t\tif (run_command(&run_trailer))\n     -+\t\t\t\tdie(_(\"unable to pass trailers to --trailers\"));\n     -+\t\t}\n     ++\t\tif (trailer_args->nr && amend_file_with_trailers(path, trailer_args))\n     ++\t\t\tdie(_(\"unable to pass trailers to --trailers\"));\n      +\n      +\t\tif (should_edit) {\n      +\t\t\tif (launch_editor(path, buf, NULL)) {\n      +\t\t\t\tfprintf(stderr,\n     -+\t\t\t\t_(\"Please supply the message using either -m or -F option.\\n\"));\n     ++\t\t\t\t\t_(\"Please supply the message using either -m or -F option.\\n\"));\n      +\t\t\t\texit(1);\n      +\t\t\t}\n      +\t\t} else if (trailer_args->nr) {\n      +\t\t\tstrbuf_reset(buf);\n      +\t\t\tif (strbuf_read_file(buf, path, 0) < 0) {\n      +\t\t\t\tfprintf(stderr,\n     -+\t\t\t\t\t\t_(\"Please supply the message using either -m or -F option.\\n\"));\n     ++\t\t\t\t\t_(\"Please supply the message using either -m or -F option.\\n\"));\n      +\t\t\t\texit(1);\n      +\t\t\t}\n       \t\t}\n     @@ builtin/tag.c: int cmd_tag(int argc, const char **argv, const char *prefix)\n       \t\tOPT_CALLBACK_F('m', \"message\", &msg, N_(\"message\"),\n       \t\t\t       N_(\"tag message\"), PARSE_OPT_NONEG, parse_msg_arg),\n       \t\tOPT_FILENAME('F', \"file\", &msgfile, N_(\"read message from file\")),\n     -+\t\tOPT_CALLBACK_F(0, \"trailer\", &trailer_args, N_(\"trailer\"), N_(\"add custom trailer(s)\"), PARSE_OPT_NONEG, opt_pass_trailer),\n     ++\t\tOPT_CALLBACK_F(0, \"trailer\", &trailer_args, N_(\"trailer\"), N_(\"add custom trailer(s)\"),\n     ++\t\t\t\tPARSE_OPT_NONEG, opt_pass_trailer),\n       \t\tOPT_BOOL('e', \"edit\", &edit_flag, N_(\"force edit of tag message\")),\n       \t\tOPT_BOOL('s', \"sign\", &opt.sign, N_(\"annotated and GPG-signed tag\")),\n       \t\tOPT_CLEANUP(&cleanup_arg),\n     @@ builtin/tag.c: int cmd_tag(int argc, const char **argv, const char *prefix)\n       \t\tset_signing_key(keyid);\n       \t}\n      -\tcreate_tag_object = (opt.sign || annotate || msg.given || msgfile);\n     -+\tcreate_tag_object = (opt.sign || annotate || msg.given || msgfile || edit_flag || trailer_args.nr);\n     ++\tcreate_tag_object = (opt.sign || annotate || msg.given || msgfile ||\n     ++\t\t\t     edit_flag || trailer_args.nr);\n       \n       \tif ((create_tag_object || force) && (cmdmode != 0))\n       \t\tusage_with_options(git_tag_usage, options);\n      @@ builtin/tag.c: int cmd_tag(int argc, const char **argv, const char *prefix)\n     - \telse if (!force)\n     - \t\tdie(_(\"tag '%s' already exists\"), tag);\n     - \n     --\topt.message_given = msg.given || msgfile;\n     -+\topt.message_given = msg.given || (msgfile != NULL);\n     - \topt.use_editor = edit_flag;\n     - \n     - \tif (!cleanup_arg || !strcmp(cleanup_arg, \"strip\"))\n     -@@ builtin/tag.c: int cmd_tag(int argc, const char **argv, const char *prefix)\n     - \t\tif (force_sign_annotate && !annotate)\n       \t\t\topt.sign = 1;\n       \t\tpath = git_pathdup(\"TAG_EDITMSG\");\n     --\t\tcreate_tag(&object, object_ref, tag, &buf, &opt, &prev, &object,\n     -+\t\tcreate_tag(&object, object_ref, tag, &buf, &opt, &prev, &object, &trailer_args,\n     - \t\t\t   path);\n     + \t\tcreate_tag(&object, object_ref, tag, &buf, &opt, &prev, &object,\n     +-\t\t\t   path);\n     ++\t\t\t   &trailer_args, path);\n       \t}\n       \n     + \ttransaction = ref_transaction_begin(&err);\n      @@ builtin/tag.c: int cmd_tag(int argc, const char **argv, const char *prefix)\n       \tstrbuf_release(&reflog_msg);\n       \tstrbuf_release(&msg.buf);\n     @@ t/t7004-tag.sh: test_expect_success \\\n       \n      +# trailers\n      +\n     -+get_tag_header tag-with-inline-message-and-trailers $commit commit $time >expect\n     -+cat >>expect <<EOF\n     -+create tag with trailers\n     -+\n     -+my-trailer: here\n     -+alt-trailer: there\n     -+EOF\n      +test_expect_success 'create tag with -m and --trailer' '\n     -+\tgit tag -m \"create tag with trailers\"  --trailer my-trailer=here --trailer alt-trailer=there tag-with-inline-message-and-trailers &&\n     ++\tget_tag_header tag-with-inline-message-and-trailers $commit commit $time >expect &&\n     ++\tcat >>expect <<-\\EOF &&\n     ++\tcreate tag with trailers\n     ++\n     ++\tmy-trailer: here\n     ++\talt-trailer: there\n     ++\tEOF\n     ++\tgit tag -m \"create tag with trailers\" \\\n     ++\t\t--trailer my-trailer=here \\\n     ++\t\t--trailer alt-trailer=there \\\n     ++\t\ttag-with-inline-message-and-trailers &&\n      +\tget_tag_msg tag-with-inline-message-and-trailers >actual &&\n      +\ttest_cmp expect actual\n      +'\n     @@ t/t7004-tag.sh: test_expect_success \\\n      +\ttest_cmp expect actual\n      +'\n      +\n     -+echo 'create tag from message file using --trailer' >messagefilewithnotrailers\n     -+get_tag_header tag-with-file-message-and-trailers $commit commit $time >expect\n     -+cat >>expect <<EOF\n     -+create tag from message file using --trailer\n     -+\n     -+my-trailer: here\n     -+alt-trailer: there\n     -+EOF\n      +test_expect_success 'create tag with -F and --trailer' '\n     -+\tgit tag -F messagefilewithnotrailers  --trailer my-trailer=here --trailer alt-trailer=there tag-with-file-message-and-trailers &&\n     ++\techo \"create tag from message file using --trailer\" >messagefilewithnotrailers &&\n     ++\tget_tag_header tag-with-file-message-and-trailers $commit commit $time >expect &&\n     ++\tcat >>expect <<-\\EOF &&\n     ++\tcreate tag from message file using --trailer\n     ++\n     ++\tmy-trailer: here\n     ++\talt-trailer: there\n     ++\tEOF\n     ++\tgit tag -F messagefilewithnotrailers \\\n     ++\t\t--trailer my-trailer=here \\\n     ++\t\t--trailer alt-trailer=there \\\n     ++\t\ttag-with-file-message-and-trailers &&\n      +\tget_tag_msg tag-with-file-message-and-trailers >actual &&\n      +\ttest_cmp expect actual\n      +'\n      +\n     -+test_expect_success 'set up editor' '\n     -+\twrite_script fakeeditor <<-\\EOF\n     ++test_expect_success 'create tag with -m and --trailer and --edit' '\n     ++\twrite_script fakeeditor <<-\\EOF &&\n      +\tsed -e \"1s/^/EDITED: /g\" <\"$1\" >\"$1-\"\n      +\tmv \"$1-\" \"$1\"\n      +\tEOF\n     -+'\n     -+\n     -+get_tag_header tag-with-edited-inline-message-and-trailers $commit commit $time >expect\n     -+cat >>expect <<EOF\n     -+EDITED: create tag with trailers\n     ++\tget_tag_header tag-with-edited-inline-message-and-trailers $commit commit $time >expect &&\n     ++\tcat >>expect <<-\\EOF &&\n     ++\tEDITED: create tag with trailers\n      +\n     -+my-trailer: here\n     -+alt-trailer: there\n     -+EOF\n     -+test_expect_success 'create tag with -m and --trailer and --edit' '\n     -+\tGIT_EDITOR=./fakeeditor git tag --edit -m \"create tag with trailers\"  --trailer my-trailer=here --trailer alt-trailer=there tag-with-edited-inline-message-and-trailers &&\n     ++\tmy-trailer: here\n     ++\talt-trailer: there\n     ++\tEOF\n     ++\tGIT_EDITOR=./fakeeditor git tag --edit \\\n     ++\t\t-m \"create tag with trailers\" \\\n     ++\t\t--trailer my-trailer=here \\\n     ++\t\t--trailer alt-trailer=there \\\n     ++\t\ttag-with-edited-inline-message-and-trailers &&\n      +\tget_tag_msg tag-with-edited-inline-message-and-trailers >actual &&\n      +\ttest_cmp expect actual\n      +'\n      +\n     -+echo 'create tag from message file using --trailer' >messagefilewithnotrailers\n     -+get_tag_header tag-with-edited-file-message-and-trailers $commit commit $time >expect\n     -+cat >>expect <<EOF\n     -+EDITED: create tag from message file using --trailer\n     -+\n     -+my-trailer: here\n     -+alt-trailer: there\n     -+EOF\n      +test_expect_success 'create tag with -F and --trailer and --edit' '\n     -+\tGIT_EDITOR=./fakeeditor git tag --edit -F messagefilewithnotrailers  --trailer my-trailer=here --trailer alt-trailer=there tag-with-edited-file-message-and-trailers &&\n     ++\techo \"create tag from message file using --trailer\" >messagefilewithnotrailers &&\n     ++\tget_tag_header tag-with-edited-file-message-and-trailers $commit commit $time >expect &&\n     ++\tcat >>expect <<-\\EOF &&\n     ++\tEDITED: create tag from message file using --trailer\n     ++\n     ++\tmy-trailer: here\n     ++\talt-trailer: there\n     ++\tEOF\n     ++\tGIT_EDITOR=./fakeeditor git tag --edit \\\n     ++\t\t-F messagefilewithnotrailers \\\n     ++\t\t--trailer my-trailer=here \\\n     ++\t\t--trailer alt-trailer=there \\\n     ++\t\ttag-with-edited-file-message-and-trailers &&\n      +\tget_tag_msg tag-with-edited-file-message-and-trailers >actual &&\n      +\ttest_cmp expect actual\n      +'\n      +\n     -+test_expect_success 'set up editor' '\n     -+\twrite_script fakeeditor <<-\\EOF\n     ++test_expect_success 'create annotated tag and force editor when only --trailer is given' '\n     ++\twrite_script fakeeditor <<-\\EOF &&\n      +\techo \"add a line\" >\"$1-\"\n     -+\techo >>\"$1-\"\n      +\tcat <\"$1\" >>\"$1-\"\n      +\tmv \"$1-\" \"$1\"\n      +\tEOF\n     -+'\n     -+\n     -+get_tag_header tag-with-trailers-and-no-message $commit commit $time >expect\n     -+cat >>expect <<EOF\n     -+add a line\n     ++\tget_tag_header tag-with-trailers-and-no-message $commit commit $time >expect &&\n     ++\tcat >>expect <<-\\EOF &&\n     ++\tadd a line\n      +\n     -+my-trailer: here\n     -+alt-trailer: there\n     -+EOF\n     -+test_expect_success 'create annotated tag and force editor when only --trailer is given' '\n     -+\tGIT_EDITOR=./fakeeditor git tag --trailer my-trailer=here --trailer alt-trailer=there tag-with-trailers-and-no-message &&\n     ++\tmy-trailer: here\n     ++\talt-trailer: there\n     ++\tEOF\n     ++\tGIT_EDITOR=./fakeeditor git tag \\\n     ++\t\t--trailer my-trailer=here \\\n     ++\t\t--trailer alt-trailer=there \\\n     ++\t\ttag-with-trailers-and-no-message &&\n      +\tget_tag_msg tag-with-trailers-and-no-message >actual &&\n      +\ttest_cmp expect actual\n      +'\n     @@ t/t7004-tag.sh: test_expect_success 'git tag --format with ahead-behind' '\n       \trefs/tags/tag-zero-lines 0 1 !\n       \tEOF\n       \tgit tag -l --format=\"%(refname) %(ahead-behind:HEAD) !\" >actual 2>err &&\n     +\n     + ## trailer.c ##\n     +@@\n     + #include \"commit.h\"\n     + #include \"trailer.h\"\n     + #include \"list.h\"\n     ++#include \"run-command.h\"\n     + /*\n     +  * Copyright (c) 2013, 2014 Christian Couder <chriscool@tuxfamily.org>\n     +  */\n     +@@ trailer.c: void trailer_iterator_release(struct trailer_iterator *iter)\n     + \tstrbuf_release(&iter->val);\n     + \tstrbuf_release(&iter->key);\n     + }\n     ++\n     ++int amend_file_with_trailers(const char *path, struct strvec const* trailer_args) {\n     ++\tstruct child_process run_trailer = CHILD_PROCESS_INIT;\n     ++\n     ++\trun_trailer.git_cmd = 1;\n     ++\tstrvec_pushl(&run_trailer.args, \"interpret-trailers\",\n     ++\t\t     \"--in-place\", \"--no-divider\",\n     ++\t\t     path, NULL);\n     ++\tstrvec_pushv(&run_trailer.args, trailer_args->v);\n     ++\treturn run_command(&run_trailer);\n     ++}\n     +\n     + ## trailer.h ##\n     +@@\n     + \n     + #include \"list.h\"\n     + #include \"strbuf.h\"\n     ++#include \"strvec.h\"\n     + \n     + enum trailer_where {\n     + \tWHERE_DEFAULT,\n     +@@ trailer.h: int trailer_iterator_advance(struct trailer_iterator *iter);\n     +  */\n     + void trailer_iterator_release(struct trailer_iterator *iter);\n     + \n     ++/*\n     ++ * Augment a file to add trailers to it by running git-interpret-trailers.\n     ++ * This calls run_command() and its return value is the same (i.e. 0 for\n     ++ * success, various non-zero for other errors). See run-command.h.\n     ++ */\n     ++int amend_file_with_trailers(const char *path, struct strvec const* trailer_args);\n     ++\n     + #endif /* TRAILER_H */\n\n\n Documentation/git-tag.txt |  18 +++++-\n builtin/commit.c          |  10 +---\n builtin/tag.c             |  49 +++++++++++++---\n t/t7004-tag.sh            | 114 ++++++++++++++++++++++++++++++++++++++\n trailer.c                 |  12 ++++\n trailer.h                 |   8 +++\n 6 files changed, 192 insertions(+), 19 deletions(-)\n\ndiff --git a/Documentation/git-tag.txt b/Documentation/git-tag.txt\nindex 5fe519c31ec..79b0a7e9644 100644\n--- a/Documentation/git-tag.txt\n+++ b/Documentation/git-tag.txt\n@@ -10,6 +10,7 @@ SYNOPSIS\n --------\n [verse]\n 'git tag' [-a | -s | -u <key-id>] [-f] [-m <msg> | -F <file>] [-e]\n+\t[(--trailer <token>[(=|:)<value>])...]\n \t<tagname> [<commit> | <object>]\n 'git tag' -d <tagname>...\n 'git tag' [-n[<num>]] -l [--contains <commit>] [--no-contains <commit>]\n@@ -31,8 +32,8 @@ creates a 'tag' object, and requires a tag message.  Unless\n `-m <msg>` or `-F <file>` is given, an editor is started for the user to type\n in the tag message.\n \n-If `-m <msg>` or `-F <file>` is given and `-a`, `-s`, and `-u <key-id>`\n-are absent, `-a` is implied.\n+If `-m <msg>` or `-F <file>` or `--trailer <token>[=<value>]` is given\n+and `-a`, `-s`, and `-u <key-id>` are absent, `-a` is implied.\n \n Otherwise, a tag reference that points directly at the given object\n (i.e., a lightweight tag) is created.\n@@ -178,6 +179,19 @@ This option is only applicable when listing tags without annotation lines.\n \tImplies `-a` if none of `-a`, `-s`, or `-u <key-id>`\n \tis given.\n \n+--trailer <token>[(=|:)<value>]::\n+\tSpecify a (<token>, <value>) pair that should be applied as a\n+\ttrailer. (e.g. `git tag --trailer \"Signed-off-by:T A Ger \\\n+\t<tagger@example.com>\" --trailer \"Helped-by:C O Mitter \\\n+\t<committer@example.com>\"` will add the \"Signed-off-by\" trailer\n+\tand the \"Helped-by\" trailer to the tag message.)\n+\tThe `trailer.*` configuration variables\n+\t(linkgit:git-interpret-trailers[1]) can be used to define if\n+\ta duplicated trailer is omitted, where in the run of trailers\n+\teach trailer would appear, and other details.\n+\tThe trailers can be seen in `git tag --list` using\n+\t`--format=\"%(trailers)\"` placeholder.\n+\n -e::\n --edit::\n \tThe message taken from file with `-F` and command line with\ndiff --git a/builtin/commit.c b/builtin/commit.c\nindex 6e1484446b0..a1cbc128429 100644\n--- a/builtin/commit.c\n+++ b/builtin/commit.c\n@@ -38,6 +38,7 @@\n #include \"commit-reach.h\"\n #include \"commit-graph.h\"\n #include \"pretty.h\"\n+#include \"trailer.h\"\n \n static const char * const builtin_commit_usage[] = {\n \tN_(\"git commit [-a | --interactive | --patch] [-s] [-v] [-u<mode>] [--amend]\\n\"\n@@ -1038,14 +1039,7 @@ static int prepare_to_commit(const char *index_file, const char *prefix,\n \tfclose(s->fp);\n \n \tif (trailer_args.nr) {\n-\t\tstruct child_process run_trailer = CHILD_PROCESS_INIT;\n-\n-\t\tstrvec_pushl(&run_trailer.args, \"interpret-trailers\",\n-\t\t\t     \"--in-place\", \"--no-divider\",\n-\t\t\t     git_path_commit_editmsg(), NULL);\n-\t\tstrvec_pushv(&run_trailer.args, trailer_args.v);\n-\t\trun_trailer.git_cmd = 1;\n-\t\tif (run_command(&run_trailer))\n+\t\tif (amend_file_with_trailers(git_path_commit_editmsg(), &trailer_args))\n \t\t\tdie(_(\"unable to pass trailers to --trailers\"));\n \t\tstrvec_clear(&trailer_args);\n \t}\ndiff --git a/builtin/tag.c b/builtin/tag.c\nindex 9a33cb50b45..0a029fb8c30 100644\n--- a/builtin/tag.c\n+++ b/builtin/tag.c\n@@ -28,9 +28,11 @@\n #include \"date.h\"\n #include \"write-or-die.h\"\n #include \"object-file-convert.h\"\n+#include \"trailer.h\"\n \n static const char * const git_tag_usage[] = {\n \tN_(\"git tag [-a | -s | -u <key-id>] [-f] [-m <msg> | -F <file>] [-e]\\n\"\n+\t   \"        [(--trailer <token>[(=|:)<value>])...]\\n\"\n \t   \"        <tagname> [<commit> | <object>]\"),\n \tN_(\"git tag -d <tagname>...\"),\n \tN_(\"git tag [-n[<num>]] -l [--contains <commit>] [--no-contains <commit>]\\n\"\n@@ -290,10 +292,12 @@ static const char message_advice_nested_tag[] =\n static void create_tag(const struct object_id *object, const char *object_ref,\n \t\t       const char *tag,\n \t\t       struct strbuf *buf, struct create_tag_options *opt,\n-\t\t       struct object_id *prev, struct object_id *result, char *path)\n+\t\t       struct object_id *prev, struct object_id *result,\n+\t\t       struct strvec *trailer_args, char *path)\n {\n \tenum object_type type;\n \tstruct strbuf header = STRBUF_INIT;\n+\tint should_edit;\n \n \ttype = oid_object_info(the_repository, object, NULL);\n \tif (type <= OBJ_NONE)\n@@ -313,13 +317,15 @@ static void create_tag(const struct object_id *object, const char *object_ref,\n \t\t    tag,\n \t\t    git_committer_info(IDENT_STRICT));\n \n-\tif (!opt->message_given || opt->use_editor) {\n+\tshould_edit = opt->use_editor || !opt->message_given;\n+\tif (should_edit || trailer_args->nr) {\n \t\tint fd;\n \n \t\t/* write the template message before editing: */\n \t\tfd = xopen(path, O_CREAT | O_TRUNC | O_WRONLY, 0600);\n \n-\t\tif (opt->message_given) {\n+\t\tif (opt->message_given && buf->len) {\n+\t\t\tstrbuf_complete(buf, '\\n');\n \t\t\twrite_or_die(fd, buf->buf, buf->len);\n \t\t\tstrbuf_reset(buf);\n \t\t} else if (!is_null_oid(prev)) {\n@@ -338,10 +344,22 @@ static void create_tag(const struct object_id *object, const char *object_ref,\n \t\t}\n \t\tclose(fd);\n \n-\t\tif (launch_editor(path, buf, NULL)) {\n-\t\t\tfprintf(stderr,\n-\t\t\t_(\"Please supply the message using either -m or -F option.\\n\"));\n-\t\t\texit(1);\n+\t\tif (trailer_args->nr && amend_file_with_trailers(path, trailer_args))\n+\t\t\tdie(_(\"unable to pass trailers to --trailers\"));\n+\n+\t\tif (should_edit) {\n+\t\t\tif (launch_editor(path, buf, NULL)) {\n+\t\t\t\tfprintf(stderr,\n+\t\t\t\t\t_(\"Please supply the message using either -m or -F option.\\n\"));\n+\t\t\t\texit(1);\n+\t\t\t}\n+\t\t} else if (trailer_args->nr) {\n+\t\t\tstrbuf_reset(buf);\n+\t\t\tif (strbuf_read_file(buf, path, 0) < 0) {\n+\t\t\t\tfprintf(stderr,\n+\t\t\t\t\t_(\"Please supply the message using either -m or -F option.\\n\"));\n+\t\t\t\texit(1);\n+\t\t\t}\n \t\t}\n \t}\n \n@@ -416,6 +434,14 @@ struct msg_arg {\n \tstruct strbuf buf;\n };\n \n+static int opt_pass_trailer(const struct option *opt, const char *arg, int unset)\n+{\n+\tBUG_ON_OPT_NEG(unset);\n+\n+\tstrvec_pushl(opt->value, \"--trailer\", arg, NULL);\n+\treturn 0;\n+}\n+\n static int parse_msg_arg(const struct option *opt, const char *arg, int unset)\n {\n \tstruct msg_arg *msg = opt->value;\n@@ -463,6 +489,7 @@ int cmd_tag(int argc, const char **argv, const char *prefix)\n \tstruct ref_sorting *sorting;\n \tstruct string_list sorting_options = STRING_LIST_INIT_DUP;\n \tstruct ref_format format = REF_FORMAT_INIT;\n+\tstruct strvec trailer_args = STRVEC_INIT;\n \tint icase = 0;\n \tint edit_flag = 0;\n \tstruct option options[] = {\n@@ -479,6 +506,8 @@ int cmd_tag(int argc, const char **argv, const char *prefix)\n \t\tOPT_CALLBACK_F('m', \"message\", &msg, N_(\"message\"),\n \t\t\t       N_(\"tag message\"), PARSE_OPT_NONEG, parse_msg_arg),\n \t\tOPT_FILENAME('F', \"file\", &msgfile, N_(\"read message from file\")),\n+\t\tOPT_CALLBACK_F(0, \"trailer\", &trailer_args, N_(\"trailer\"), N_(\"add custom trailer(s)\"),\n+\t\t\t\tPARSE_OPT_NONEG, opt_pass_trailer),\n \t\tOPT_BOOL('e', \"edit\", &edit_flag, N_(\"force edit of tag message\")),\n \t\tOPT_BOOL('s', \"sign\", &opt.sign, N_(\"annotated and GPG-signed tag\")),\n \t\tOPT_CLEANUP(&cleanup_arg),\n@@ -548,7 +577,8 @@ int cmd_tag(int argc, const char **argv, const char *prefix)\n \t\topt.sign = 1;\n \t\tset_signing_key(keyid);\n \t}\n-\tcreate_tag_object = (opt.sign || annotate || msg.given || msgfile);\n+\tcreate_tag_object = (opt.sign || annotate || msg.given || msgfile ||\n+\t\t\t     edit_flag || trailer_args.nr);\n \n \tif ((create_tag_object || force) && (cmdmode != 0))\n \t\tusage_with_options(git_tag_usage, options);\n@@ -654,7 +684,7 @@ int cmd_tag(int argc, const char **argv, const char *prefix)\n \t\t\topt.sign = 1;\n \t\tpath = git_pathdup(\"TAG_EDITMSG\");\n \t\tcreate_tag(&object, object_ref, tag, &buf, &opt, &prev, &object,\n-\t\t\t   path);\n+\t\t\t   &trailer_args, path);\n \t}\n \n \ttransaction = ref_transaction_begin(&err);\n@@ -686,6 +716,7 @@ int cmd_tag(int argc, const char **argv, const char *prefix)\n \tstrbuf_release(&reflog_msg);\n \tstrbuf_release(&msg.buf);\n \tstrbuf_release(&err);\n+\tstrvec_clear(&trailer_args);\n \tfree(msgfile);\n \treturn ret;\n }\ndiff --git a/t/t7004-tag.sh b/t/t7004-tag.sh\nindex 696866d7794..fa6336edf98 100755\n--- a/t/t7004-tag.sh\n+++ b/t/t7004-tag.sh\n@@ -668,6 +668,115 @@ test_expect_success \\\n \ttest_cmp expect actual\n '\n \n+# trailers\n+\n+test_expect_success 'create tag with -m and --trailer' '\n+\tget_tag_header tag-with-inline-message-and-trailers $commit commit $time >expect &&\n+\tcat >>expect <<-\\EOF &&\n+\tcreate tag with trailers\n+\n+\tmy-trailer: here\n+\talt-trailer: there\n+\tEOF\n+\tgit tag -m \"create tag with trailers\" \\\n+\t\t--trailer my-trailer=here \\\n+\t\t--trailer alt-trailer=there \\\n+\t\ttag-with-inline-message-and-trailers &&\n+\tget_tag_msg tag-with-inline-message-and-trailers >actual &&\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'list tag extracting trailers' '\n+\tcat >expect <<-\\EOF &&\n+\tmy-trailer: here\n+\talt-trailer: there\n+\n+\tEOF\n+\tgit tag --list --format=\"%(trailers)\" tag-with-inline-message-and-trailers >actual &&\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'create tag with -F and --trailer' '\n+\techo \"create tag from message file using --trailer\" >messagefilewithnotrailers &&\n+\tget_tag_header tag-with-file-message-and-trailers $commit commit $time >expect &&\n+\tcat >>expect <<-\\EOF &&\n+\tcreate tag from message file using --trailer\n+\n+\tmy-trailer: here\n+\talt-trailer: there\n+\tEOF\n+\tgit tag -F messagefilewithnotrailers \\\n+\t\t--trailer my-trailer=here \\\n+\t\t--trailer alt-trailer=there \\\n+\t\ttag-with-file-message-and-trailers &&\n+\tget_tag_msg tag-with-file-message-and-trailers >actual &&\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'create tag with -m and --trailer and --edit' '\n+\twrite_script fakeeditor <<-\\EOF &&\n+\tsed -e \"1s/^/EDITED: /g\" <\"$1\" >\"$1-\"\n+\tmv \"$1-\" \"$1\"\n+\tEOF\n+\tget_tag_header tag-with-edited-inline-message-and-trailers $commit commit $time >expect &&\n+\tcat >>expect <<-\\EOF &&\n+\tEDITED: create tag with trailers\n+\n+\tmy-trailer: here\n+\talt-trailer: there\n+\tEOF\n+\tGIT_EDITOR=./fakeeditor git tag --edit \\\n+\t\t-m \"create tag with trailers\" \\\n+\t\t--trailer my-trailer=here \\\n+\t\t--trailer alt-trailer=there \\\n+\t\ttag-with-edited-inline-message-and-trailers &&\n+\tget_tag_msg tag-with-edited-inline-message-and-trailers >actual &&\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'create tag with -F and --trailer and --edit' '\n+\techo \"create tag from message file using --trailer\" >messagefilewithnotrailers &&\n+\tget_tag_header tag-with-edited-file-message-and-trailers $commit commit $time >expect &&\n+\tcat >>expect <<-\\EOF &&\n+\tEDITED: create tag from message file using --trailer\n+\n+\tmy-trailer: here\n+\talt-trailer: there\n+\tEOF\n+\tGIT_EDITOR=./fakeeditor git tag --edit \\\n+\t\t-F messagefilewithnotrailers \\\n+\t\t--trailer my-trailer=here \\\n+\t\t--trailer alt-trailer=there \\\n+\t\ttag-with-edited-file-message-and-trailers &&\n+\tget_tag_msg tag-with-edited-file-message-and-trailers >actual &&\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'create annotated tag and force editor when only --trailer is given' '\n+\twrite_script fakeeditor <<-\\EOF &&\n+\techo \"add a line\" >\"$1-\"\n+\tcat <\"$1\" >>\"$1-\"\n+\tmv \"$1-\" \"$1\"\n+\tEOF\n+\tget_tag_header tag-with-trailers-and-no-message $commit commit $time >expect &&\n+\tcat >>expect <<-\\EOF &&\n+\tadd a line\n+\n+\tmy-trailer: here\n+\talt-trailer: there\n+\tEOF\n+\tGIT_EDITOR=./fakeeditor git tag \\\n+\t\t--trailer my-trailer=here \\\n+\t\t--trailer alt-trailer=there \\\n+\t\ttag-with-trailers-and-no-message &&\n+\tget_tag_msg tag-with-trailers-and-no-message >actual &&\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'bad editor causes panic when only --trailer is given' '\n+\ttest_must_fail env GIT_EDITOR=false git tag --trailer my-trailer=here tag-will-not-exist\n+'\n+\n # listing messages for annotated non-signed tags:\n \n test_expect_success \\\n@@ -810,6 +919,11 @@ test_expect_success 'git tag --format with ahead-behind' '\n \trefs/tags/tag-lines 0 1 !\n \trefs/tags/tag-one-line 0 1 !\n \trefs/tags/tag-right 0 0 !\n+\trefs/tags/tag-with-edited-file-message-and-trailers 0 1 !\n+\trefs/tags/tag-with-edited-inline-message-and-trailers 0 1 !\n+\trefs/tags/tag-with-file-message-and-trailers 0 1 !\n+\trefs/tags/tag-with-inline-message-and-trailers 0 1 !\n+\trefs/tags/tag-with-trailers-and-no-message 0 1 !\n \trefs/tags/tag-zero-lines 0 1 !\n \tEOF\n \tgit tag -l --format=\"%(refname) %(ahead-behind:HEAD) !\" >actual 2>err &&\ndiff --git a/trailer.c b/trailer.c\nindex c72ae687099..843c378199e 100644\n--- a/trailer.c\n+++ b/trailer.c\n@@ -7,6 +7,7 @@\n #include \"commit.h\"\n #include \"trailer.h\"\n #include \"list.h\"\n+#include \"run-command.h\"\n /*\n  * Copyright (c) 2013, 2014 Christian Couder <chriscool@tuxfamily.org>\n  */\n@@ -1170,3 +1171,14 @@ void trailer_iterator_release(struct trailer_iterator *iter)\n \tstrbuf_release(&iter->val);\n \tstrbuf_release(&iter->key);\n }\n+\n+int amend_file_with_trailers(const char *path, struct strvec const* trailer_args) {\n+\tstruct child_process run_trailer = CHILD_PROCESS_INIT;\n+\n+\trun_trailer.git_cmd = 1;\n+\tstrvec_pushl(&run_trailer.args, \"interpret-trailers\",\n+\t\t     \"--in-place\", \"--no-divider\",\n+\t\t     path, NULL);\n+\tstrvec_pushv(&run_trailer.args, trailer_args->v);\n+\treturn run_command(&run_trailer);\n+}\ndiff --git a/trailer.h b/trailer.h\nindex 9f42aa75994..55f85b008ee 100644\n--- a/trailer.h\n+++ b/trailer.h\n@@ -3,6 +3,7 @@\n \n #include \"list.h\"\n #include \"strbuf.h\"\n+#include \"strvec.h\"\n \n enum trailer_where {\n \tWHERE_DEFAULT,\n@@ -158,4 +159,11 @@ int trailer_iterator_advance(struct trailer_iterator *iter);\n  */\n void trailer_iterator_release(struct trailer_iterator *iter);\n \n+/*\n+ * Augment a file to add trailers to it by running git-interpret-trailers.\n+ * This calls run_command() and its return value is the same (i.e. 0 for\n+ * success, various non-zero for other errors). See run-command.h.\n+ */\n+int amend_file_with_trailers(const char *path, struct strvec const* trailer_args);\n+\n #endif /* TRAILER_H */\n\nbase-commit: e326e520101dcf43a0499c3adc2df7eca30add2d\n-- \ngitgitgadget\n"},{"id":"493657","messageId":"xmqqil00w168.fsf@gitster.g","threadId":"61376","inReplyTo":"CAJdN7Kiep79YeN2GCbjdz9HhHif0nyEDUf51EKcESzR-DOBZQw@mail.gmail.com","subject":"Re: [PATCH] builtin/tag.c: add --trailer arg","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2024-04-29T17:04:15Z","receivedAt":"2024-04-29T17:04:24Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"John Passaro <john.a.passaro@gmail.com> writes:\n\n>> More importantly, I doubt that many trailers we commonly see in the\n>> comit objects, like \"Acked-by\", \"Reviewed-by\", or even \"CC\", are\n>> applicable in the context of tags.  So I am ambivalent.\n>\n> A couple of words on the motivation here. First, by way of --list\n> --format=\"%(trailer)\",\n> git-tag arguably has read-side support for trailers already; adding write\n> support seems pretty reasonable. Second, even though not all the trailers\n> broadly used for commits are an obvious fit for tags, some still are -\n> \"Signed-off-by\" for one would seem plausibly useful. In my team's usage (which\n> inspired this change), tag trailers have emerged as a convenient way to pass\n> machine-readable metadata to CICD.\n\nThat is a good thing to describe in the proposed log message.\n\nIf a reviewer feels puzzled by a commit and did not get the\nmotivation from the proposed log message, there is a good chance\nthat the motivation is not well described to help future developers\nwho find this commit in \"git log\" output.\n\nThanks.\n"},{"id":"493659","messageId":"xmqqedaow100.fsf@gitster.g","threadId":"61376","inReplyTo":"CAJdN7KhCesU-j=yyM5Sk0sMj8W2Gk=LJc-Dzf8-2+gjhkgA4HQ@mail.gmail.com","subject":"Re: [PATCH] builtin/tag.c: add --trailer arg","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2024-04-29T17:07:59Z","receivedAt":"2024-04-29T17:08:06Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"John Passaro <john.a.passaro@gmail.com> writes:\n\n> It seems to me the duplication goes further than that.\n> Ideally I would love to have a unified approach to the problem of\n> combining `-m`, `-F`, `-e`, and `--trailer` generally. That would change\n> one existing nit I have in git-tag, which is that with `-m`, `-F`,\n> or `-fae` to replace an existing tag, it doesn't add commentary guidance\n> the way git-commit does.\n>\n> That's a bigger change than I'm comfortable taking on and it's\n> arguably beyond the scope of this ticket. Question here is - first,\n> is such a consolidation possible in the future, and second, if it were,\n> would this refactor (dedicated function for augmenting a strbuf/file\n> with trailers) still be valuable?\n\nThere is no doubt it would be, if it were ;-)\n\nAt least, it should be straight-forward to turn the copying and\npasting in this patch to a proper refactoring into a common helper\nfunction as Patrick suggested and add it to the trailer API.  It\nshould become a separate, preparatory clean-up patch on a two-patch\nseries, upon which the main \"now we have a reusable piece split out\nof 'git commit --trailer' implementation, let's use it to teach the\nsame '--trailer' to 'git tag'\" patch can come.\n\nThanks.\n"},{"id":"493671","messageId":"pull.1723.v3.git.1714416863.gitgitgadget@gmail.com","threadId":"61376","inReplyTo":"pull.1723.v2.git.1714409638089.gitgitgadget@gmail.com","subject":"[PATCH v3 0/3] builtin/tag.c: add --trailer option","fromName":"John Passaro via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2024-04-29T18:54:20Z","receivedAt":"2024-04-29T18:54:33Z","isPatch":true,"sender":{"key":"john.a.passaro@gmail.com","avatar":"https://avatars.githubusercontent.com/u/6754005?v=4"},"body":"Follow-up patch taking welcome feedback from Patrick and JCH.\n\nSince git-tag --list --format=\"%(trailers)\" can interpret trailers from\nannotated tag messages, it seems natural to support --trailer when writing a\nnew tag message.\n\ngit-commit accomplishes this by taking --trailer arguments and passing them\nto git interpret-trailer. This patch series refactors that logic and uses it\nto implement --trailer on git-tag.\n\nAlso Included are updates to the i18n files, since the git-tag patch changes\nsome strings that are subject to translation. If I am out of my lane here,\nplease feel free to separate or leave out the i18n patch.\n\nJohn Passaro (3):\n  builtin/commit.c: refactor --trailer logic\n  builtin/tag.c: add --trailer arg\n  po: update git-tag translations\n\n Documentation/git-tag.txt |  18 +++++-\n builtin/commit.c          |  20 +------\n builtin/tag.c             |  41 +++++++++++---\n po/bg.po                  |   2 +\n po/ca.po                  |   4 +-\n po/de.po                  |   2 +\n po/el.po                  |   9 ++-\n po/es.po                  |  14 +++--\n po/fr.po                  |   2 +\n po/id.po                  |   2 +\n po/it.po                  |   6 +-\n po/ko.po                  |  10 ++--\n po/pl.po                  |   6 +-\n po/ru.po                  |   1 +\n po/sv.po                  |   2 +\n po/tr.po                  |   2 +\n po/uk.po                  |   2 +\n po/vi.po                  |   2 +\n po/zh_CN.po               |   2 +\n po/zh_TW.po               |   2 +\n t/t7004-tag.sh            | 114 ++++++++++++++++++++++++++++++++++++++\n trailer.c                 |  12 ++++\n trailer.h                 |   8 +++\n 23 files changed, 237 insertions(+), 46 deletions(-)\n\n\nbase-commit: 786a3e4b8d754d2b14b1208b98eeb0a554ef19a8\nPublished-As: https://github.com/gitgitgadget/git/releases/tag/pr-1723%2Fjpassaro%2Fjp%2Ftag-trailer-arg-v3\nFetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-1723/jpassaro/jp/tag-trailer-arg-v3\nPull-Request: https://github.com/gitgitgadget/git/pull/1723\n\nRange-diff vs v2:\n\n -:  ----------- > 1:  0c9517f434a builtin/commit.c: refactor --trailer logic\n 1:  d4beb7cd67e ! 2:  5b6239167b8 builtin/tag.c: add --trailer arg\n     @@ Metadata\n       ## Commit message ##\n          builtin/tag.c: add --trailer arg\n      \n     -    Teach git-tag to accept --trailer option to add trailers to annotated\n     -    tag messages, like git-commit. Move the code that git-commit uses for\n     -    trailers to the trailer.h API, so it can be re-used for git-tag.\n     +    git-tag currently supports interpreting trailers from an annotated tag\n     +    message, using --list --format=\"%(trailers)\". There is no ergonomic way\n     +    to add trailers to an annotated tag message.\n     +\n     +    In a previous patch, we refactored git-commit's implementation of its\n     +    --trailer arg to the trailer.h API. Let's use that new function to teach\n     +    git-tag the same --trailer argument, emulating as much of git-commit's\n     +    behavior as much as possible.\n      \n          Signed-off-by: John Passaro <john.a.passaro@gmail.com>\n     +    Helped-by: Patrick Steinhardt <ps@pks.im>\n      \n       ## Documentation/git-tag.txt ##\n      @@ Documentation/git-tag.txt: SYNOPSIS\n     @@ Documentation/git-tag.txt: This option is only applicable when listing tags with\n       --edit::\n       \tThe message taken from file with `-F` and command line with\n      \n     - ## builtin/commit.c ##\n     -@@\n     - #include \"commit-reach.h\"\n     - #include \"commit-graph.h\"\n     - #include \"pretty.h\"\n     -+#include \"trailer.h\"\n     - \n     - static const char * const builtin_commit_usage[] = {\n     - \tN_(\"git commit [-a | --interactive | --patch] [-s] [-v] [-u<mode>] [--amend]\\n\"\n     -@@ builtin/commit.c: static int prepare_to_commit(const char *index_file, const char *prefix,\n     - \tfclose(s->fp);\n     - \n     - \tif (trailer_args.nr) {\n     --\t\tstruct child_process run_trailer = CHILD_PROCESS_INIT;\n     --\n     --\t\tstrvec_pushl(&run_trailer.args, \"interpret-trailers\",\n     --\t\t\t     \"--in-place\", \"--no-divider\",\n     --\t\t\t     git_path_commit_editmsg(), NULL);\n     --\t\tstrvec_pushv(&run_trailer.args, trailer_args.v);\n     --\t\trun_trailer.git_cmd = 1;\n     --\t\tif (run_command(&run_trailer))\n     -+\t\tif (amend_file_with_trailers(git_path_commit_editmsg(), &trailer_args))\n     - \t\t\tdie(_(\"unable to pass trailers to --trailers\"));\n     - \t\tstrvec_clear(&trailer_args);\n     - \t}\n     -\n       ## builtin/tag.c ##\n      @@\n       #include \"date.h\"\n     @@ builtin/tag.c: static void create_tag(const struct object_id *object, const char\n       \t\t}\n       \t}\n       \n     -@@ builtin/tag.c: struct msg_arg {\n     - \tstruct strbuf buf;\n     - };\n     - \n     -+static int opt_pass_trailer(const struct option *opt, const char *arg, int unset)\n     -+{\n     -+\tBUG_ON_OPT_NEG(unset);\n     -+\n     -+\tstrvec_pushl(opt->value, \"--trailer\", arg, NULL);\n     -+\treturn 0;\n     -+}\n     -+\n     - static int parse_msg_arg(const struct option *opt, const char *arg, int unset)\n     - {\n     - \tstruct msg_arg *msg = opt->value;\n      @@ builtin/tag.c: int cmd_tag(int argc, const char **argv, const char *prefix)\n       \tstruct ref_sorting *sorting;\n       \tstruct string_list sorting_options = STRING_LIST_INIT_DUP;\n     @@ builtin/tag.c: int cmd_tag(int argc, const char **argv, const char *prefix)\n       \t\tOPT_CALLBACK_F('m', \"message\", &msg, N_(\"message\"),\n       \t\t\t       N_(\"tag message\"), PARSE_OPT_NONEG, parse_msg_arg),\n       \t\tOPT_FILENAME('F', \"file\", &msgfile, N_(\"read message from file\")),\n     -+\t\tOPT_CALLBACK_F(0, \"trailer\", &trailer_args, N_(\"trailer\"), N_(\"add custom trailer(s)\"),\n     -+\t\t\t\tPARSE_OPT_NONEG, opt_pass_trailer),\n     ++\t\tOPT_PASSTHRU_ARGV(0, \"trailer\", &trailer_args, N_(\"trailer\"),\n     ++\t\t\t\t  N_(\"add custom trailer(s)\"), PARSE_OPT_NONEG),\n       \t\tOPT_BOOL('e', \"edit\", &edit_flag, N_(\"force edit of tag message\")),\n       \t\tOPT_BOOL('s', \"sign\", &opt.sign, N_(\"annotated and GPG-signed tag\")),\n       \t\tOPT_CLEANUP(&cleanup_arg),\n     @@ t/t7004-tag.sh: test_expect_success 'git tag --format with ahead-behind' '\n       \trefs/tags/tag-zero-lines 0 1 !\n       \tEOF\n       \tgit tag -l --format=\"%(refname) %(ahead-behind:HEAD) !\" >actual 2>err &&\n     -\n     - ## trailer.c ##\n     -@@\n     - #include \"commit.h\"\n     - #include \"trailer.h\"\n     - #include \"list.h\"\n     -+#include \"run-command.h\"\n     - /*\n     -  * Copyright (c) 2013, 2014 Christian Couder <chriscool@tuxfamily.org>\n     -  */\n     -@@ trailer.c: void trailer_iterator_release(struct trailer_iterator *iter)\n     - \tstrbuf_release(&iter->val);\n     - \tstrbuf_release(&iter->key);\n     - }\n     -+\n     -+int amend_file_with_trailers(const char *path, struct strvec const* trailer_args) {\n     -+\tstruct child_process run_trailer = CHILD_PROCESS_INIT;\n     -+\n     -+\trun_trailer.git_cmd = 1;\n     -+\tstrvec_pushl(&run_trailer.args, \"interpret-trailers\",\n     -+\t\t     \"--in-place\", \"--no-divider\",\n     -+\t\t     path, NULL);\n     -+\tstrvec_pushv(&run_trailer.args, trailer_args->v);\n     -+\treturn run_command(&run_trailer);\n     -+}\n     -\n     - ## trailer.h ##\n     -@@\n     - \n     - #include \"list.h\"\n     - #include \"strbuf.h\"\n     -+#include \"strvec.h\"\n     - \n     - enum trailer_where {\n     - \tWHERE_DEFAULT,\n     -@@ trailer.h: int trailer_iterator_advance(struct trailer_iterator *iter);\n     -  */\n     - void trailer_iterator_release(struct trailer_iterator *iter);\n     - \n     -+/*\n     -+ * Augment a file to add trailers to it by running git-interpret-trailers.\n     -+ * This calls run_command() and its return value is the same (i.e. 0 for\n     -+ * success, various non-zero for other errors). See run-command.h.\n     -+ */\n     -+int amend_file_with_trailers(const char *path, struct strvec const* trailer_args);\n     -+\n     - #endif /* TRAILER_H */\n -:  ----------- > 3:  d5335e30b0b po: update git-tag translations\n\n-- \ngitgitgadget\n"},{"id":"493672","messageId":"0c9517f434aa5456dbde129f0514e3e3f50a095d.1714416865.git.gitgitgadget@gmail.com","threadId":"61376","inReplyTo":"pull.1723.v3.git.1714416863.gitgitgadget@gmail.com","subject":"[PATCH v3 1/3] builtin/commit.c: refactor --trailer logic","fromName":"John Passaro via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2024-04-29T18:54:21Z","receivedAt":"2024-04-29T18:54:34Z","isPatch":true,"sender":{"key":"john.a.passaro@gmail.com","avatar":"https://avatars.githubusercontent.com/u/6754005?v=4"},"body":"From: John Passaro <john.a.passaro@gmail.com>\n\ngit-commit adds user trailers to the commit message by passing its\n`--trailer` arguments to a child process running `git-interpret-trailers\n--in-place`. This logic is broadly useful, not just for git-commit but\nfor other commands constructing message bodies (e.g. git-tag).\n\nLet's move this logic from git-commit to a new function in the trailer\nAPI, so that it can be re-used in other commands.\n\nAdditionally, replace git-commit's bespoke callback for --trailer with\nthe standard OPT_PASSTHRU_ARGV macro. This bespoke callback was only\nadding its values to a strvec and sanity-checking that `unset` is always\nfalse; both of these are already implemented in the parse-option API.\n\nSigned-off-by: John Passaro <john.a.passaro@gmail.com>\nHelped-by: Patrick Steinhardt <ps@pks.im>\nHelped-by: Junio C Hamano <gitster@pobox.com>\n---\n builtin/commit.c | 20 +++-----------------\n trailer.c        | 12 ++++++++++++\n trailer.h        |  8 ++++++++\n 3 files changed, 23 insertions(+), 17 deletions(-)\n\ndiff --git a/builtin/commit.c b/builtin/commit.c\nindex 6e1484446b0..63cd090b6f2 100644\n--- a/builtin/commit.c\n+++ b/builtin/commit.c\n@@ -38,6 +38,7 @@\n #include \"commit-reach.h\"\n #include \"commit-graph.h\"\n #include \"pretty.h\"\n+#include \"trailer.h\"\n \n static const char * const builtin_commit_usage[] = {\n \tN_(\"git commit [-a | --interactive | --patch] [-s] [-v] [-u<mode>] [--amend]\\n\"\n@@ -142,14 +143,6 @@ static struct strbuf message = STRBUF_INIT;\n \n static enum wt_status_format status_format = STATUS_FORMAT_UNSPECIFIED;\n \n-static int opt_pass_trailer(const struct option *opt, const char *arg, int unset)\n-{\n-\tBUG_ON_OPT_NEG(unset);\n-\n-\tstrvec_pushl(opt->value, \"--trailer\", arg, NULL);\n-\treturn 0;\n-}\n-\n static int opt_parse_porcelain(const struct option *opt, const char *arg, int unset)\n {\n \tenum wt_status_format *value = (enum wt_status_format *)opt->value;\n@@ -1038,14 +1031,7 @@ static int prepare_to_commit(const char *index_file, const char *prefix,\n \tfclose(s->fp);\n \n \tif (trailer_args.nr) {\n-\t\tstruct child_process run_trailer = CHILD_PROCESS_INIT;\n-\n-\t\tstrvec_pushl(&run_trailer.args, \"interpret-trailers\",\n-\t\t\t     \"--in-place\", \"--no-divider\",\n-\t\t\t     git_path_commit_editmsg(), NULL);\n-\t\tstrvec_pushv(&run_trailer.args, trailer_args.v);\n-\t\trun_trailer.git_cmd = 1;\n-\t\tif (run_command(&run_trailer))\n+\t\tif (amend_file_with_trailers(git_path_commit_editmsg(), &trailer_args))\n \t\t\tdie(_(\"unable to pass trailers to --trailers\"));\n \t\tstrvec_clear(&trailer_args);\n \t}\n@@ -1673,7 +1659,7 @@ int cmd_commit(int argc, const char **argv, const char *prefix)\n \t\tOPT_STRING(0, \"fixup\", &fixup_message, N_(\"[(amend|reword):]commit\"), N_(\"use autosquash formatted message to fixup or amend/reword specified commit\")),\n \t\tOPT_STRING(0, \"squash\", &squash_message, N_(\"commit\"), N_(\"use autosquash formatted message to squash specified commit\")),\n \t\tOPT_BOOL(0, \"reset-author\", &renew_authorship, N_(\"the commit is authored by me now (used with -C/-c/--amend)\")),\n-\t\tOPT_CALLBACK_F(0, \"trailer\", &trailer_args, N_(\"trailer\"), N_(\"add custom trailer(s)\"), PARSE_OPT_NONEG, opt_pass_trailer),\n+\t\tOPT_PASSTHRU_ARGV(0, \"trailer\", &trailer_args, N_(\"trailer\"), N_(\"add custom trailer(s)\"), PARSE_OPT_NONEG),\n \t\tOPT_BOOL('s', \"signoff\", &signoff, N_(\"add a Signed-off-by trailer\")),\n \t\tOPT_FILENAME('t', \"template\", &template_file, N_(\"use specified template file\")),\n \t\tOPT_BOOL('e', \"edit\", &edit_flag, N_(\"force edit of commit\")),\ndiff --git a/trailer.c b/trailer.c\nindex c72ae687099..843c378199e 100644\n--- a/trailer.c\n+++ b/trailer.c\n@@ -7,6 +7,7 @@\n #include \"commit.h\"\n #include \"trailer.h\"\n #include \"list.h\"\n+#include \"run-command.h\"\n /*\n  * Copyright (c) 2013, 2014 Christian Couder <chriscool@tuxfamily.org>\n  */\n@@ -1170,3 +1171,14 @@ void trailer_iterator_release(struct trailer_iterator *iter)\n \tstrbuf_release(&iter->val);\n \tstrbuf_release(&iter->key);\n }\n+\n+int amend_file_with_trailers(const char *path, struct strvec const* trailer_args) {\n+\tstruct child_process run_trailer = CHILD_PROCESS_INIT;\n+\n+\trun_trailer.git_cmd = 1;\n+\tstrvec_pushl(&run_trailer.args, \"interpret-trailers\",\n+\t\t     \"--in-place\", \"--no-divider\",\n+\t\t     path, NULL);\n+\tstrvec_pushv(&run_trailer.args, trailer_args->v);\n+\treturn run_command(&run_trailer);\n+}\ndiff --git a/trailer.h b/trailer.h\nindex 9f42aa75994..55f85b008ee 100644\n--- a/trailer.h\n+++ b/trailer.h\n@@ -3,6 +3,7 @@\n \n #include \"list.h\"\n #include \"strbuf.h\"\n+#include \"strvec.h\"\n \n enum trailer_where {\n \tWHERE_DEFAULT,\n@@ -158,4 +159,11 @@ int trailer_iterator_advance(struct trailer_iterator *iter);\n  */\n void trailer_iterator_release(struct trailer_iterator *iter);\n \n+/*\n+ * Augment a file to add trailers to it by running git-interpret-trailers.\n+ * This calls run_command() and its return value is the same (i.e. 0 for\n+ * success, various non-zero for other errors). See run-command.h.\n+ */\n+int amend_file_with_trailers(const char *path, struct strvec const* trailer_args);\n+\n #endif /* TRAILER_H */\n-- \ngitgitgadget\n\n"},{"id":"493674","messageId":"5b6239167b8d7c26f96e5c23d0d82b7a3a9b01fe.1714416865.git.gitgitgadget@gmail.com","threadId":"61376","inReplyTo":"pull.1723.v3.git.1714416863.gitgitgadget@gmail.com","subject":"[PATCH v3 2/3] builtin/tag.c: add --trailer arg","fromName":"John Passaro via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2024-04-29T18:54:22Z","receivedAt":"2024-04-29T18:54:35Z","isPatch":true,"sender":{"key":"john.a.passaro@gmail.com","avatar":"https://avatars.githubusercontent.com/u/6754005?v=4"},"body":"From: John Passaro <john.a.passaro@gmail.com>\n\ngit-tag currently supports interpreting trailers from an annotated tag\nmessage, using --list --format=\"%(trailers)\". There is no ergonomic way\nto add trailers to an annotated tag message.\n\nIn a previous patch, we refactored git-commit's implementation of its\n--trailer arg to the trailer.h API. Let's use that new function to teach\ngit-tag the same --trailer argument, emulating as much of git-commit's\nbehavior as much as possible.\n\nSigned-off-by: John Passaro <john.a.passaro@gmail.com>\nHelped-by: Patrick Steinhardt <ps@pks.im>\n---\n Documentation/git-tag.txt |  18 +++++-\n builtin/tag.c             |  41 +++++++++++---\n t/t7004-tag.sh            | 114 ++++++++++++++++++++++++++++++++++++++\n 3 files changed, 162 insertions(+), 11 deletions(-)\n\ndiff --git a/Documentation/git-tag.txt b/Documentation/git-tag.txt\nindex 5fe519c31ec..79b0a7e9644 100644\n--- a/Documentation/git-tag.txt\n+++ b/Documentation/git-tag.txt\n@@ -10,6 +10,7 @@ SYNOPSIS\n --------\n [verse]\n 'git tag' [-a | -s | -u <key-id>] [-f] [-m <msg> | -F <file>] [-e]\n+\t[(--trailer <token>[(=|:)<value>])...]\n \t<tagname> [<commit> | <object>]\n 'git tag' -d <tagname>...\n 'git tag' [-n[<num>]] -l [--contains <commit>] [--no-contains <commit>]\n@@ -31,8 +32,8 @@ creates a 'tag' object, and requires a tag message.  Unless\n `-m <msg>` or `-F <file>` is given, an editor is started for the user to type\n in the tag message.\n \n-If `-m <msg>` or `-F <file>` is given and `-a`, `-s`, and `-u <key-id>`\n-are absent, `-a` is implied.\n+If `-m <msg>` or `-F <file>` or `--trailer <token>[=<value>]` is given\n+and `-a`, `-s`, and `-u <key-id>` are absent, `-a` is implied.\n \n Otherwise, a tag reference that points directly at the given object\n (i.e., a lightweight tag) is created.\n@@ -178,6 +179,19 @@ This option is only applicable when listing tags without annotation lines.\n \tImplies `-a` if none of `-a`, `-s`, or `-u <key-id>`\n \tis given.\n \n+--trailer <token>[(=|:)<value>]::\n+\tSpecify a (<token>, <value>) pair that should be applied as a\n+\ttrailer. (e.g. `git tag --trailer \"Signed-off-by:T A Ger \\\n+\t<tagger@example.com>\" --trailer \"Helped-by:C O Mitter \\\n+\t<committer@example.com>\"` will add the \"Signed-off-by\" trailer\n+\tand the \"Helped-by\" trailer to the tag message.)\n+\tThe `trailer.*` configuration variables\n+\t(linkgit:git-interpret-trailers[1]) can be used to define if\n+\ta duplicated trailer is omitted, where in the run of trailers\n+\teach trailer would appear, and other details.\n+\tThe trailers can be seen in `git tag --list` using\n+\t`--format=\"%(trailers)\"` placeholder.\n+\n -e::\n --edit::\n \tThe message taken from file with `-F` and command line with\ndiff --git a/builtin/tag.c b/builtin/tag.c\nindex 9a33cb50b45..1ae7ea50b3f 100644\n--- a/builtin/tag.c\n+++ b/builtin/tag.c\n@@ -28,9 +28,11 @@\n #include \"date.h\"\n #include \"write-or-die.h\"\n #include \"object-file-convert.h\"\n+#include \"trailer.h\"\n \n static const char * const git_tag_usage[] = {\n \tN_(\"git tag [-a | -s | -u <key-id>] [-f] [-m <msg> | -F <file>] [-e]\\n\"\n+\t   \"        [(--trailer <token>[(=|:)<value>])...]\\n\"\n \t   \"        <tagname> [<commit> | <object>]\"),\n \tN_(\"git tag -d <tagname>...\"),\n \tN_(\"git tag [-n[<num>]] -l [--contains <commit>] [--no-contains <commit>]\\n\"\n@@ -290,10 +292,12 @@ static const char message_advice_nested_tag[] =\n static void create_tag(const struct object_id *object, const char *object_ref,\n \t\t       const char *tag,\n \t\t       struct strbuf *buf, struct create_tag_options *opt,\n-\t\t       struct object_id *prev, struct object_id *result, char *path)\n+\t\t       struct object_id *prev, struct object_id *result,\n+\t\t       struct strvec *trailer_args, char *path)\n {\n \tenum object_type type;\n \tstruct strbuf header = STRBUF_INIT;\n+\tint should_edit;\n \n \ttype = oid_object_info(the_repository, object, NULL);\n \tif (type <= OBJ_NONE)\n@@ -313,13 +317,15 @@ static void create_tag(const struct object_id *object, const char *object_ref,\n \t\t    tag,\n \t\t    git_committer_info(IDENT_STRICT));\n \n-\tif (!opt->message_given || opt->use_editor) {\n+\tshould_edit = opt->use_editor || !opt->message_given;\n+\tif (should_edit || trailer_args->nr) {\n \t\tint fd;\n \n \t\t/* write the template message before editing: */\n \t\tfd = xopen(path, O_CREAT | O_TRUNC | O_WRONLY, 0600);\n \n-\t\tif (opt->message_given) {\n+\t\tif (opt->message_given && buf->len) {\n+\t\t\tstrbuf_complete(buf, '\\n');\n \t\t\twrite_or_die(fd, buf->buf, buf->len);\n \t\t\tstrbuf_reset(buf);\n \t\t} else if (!is_null_oid(prev)) {\n@@ -338,10 +344,22 @@ static void create_tag(const struct object_id *object, const char *object_ref,\n \t\t}\n \t\tclose(fd);\n \n-\t\tif (launch_editor(path, buf, NULL)) {\n-\t\t\tfprintf(stderr,\n-\t\t\t_(\"Please supply the message using either -m or -F option.\\n\"));\n-\t\t\texit(1);\n+\t\tif (trailer_args->nr && amend_file_with_trailers(path, trailer_args))\n+\t\t\tdie(_(\"unable to pass trailers to --trailers\"));\n+\n+\t\tif (should_edit) {\n+\t\t\tif (launch_editor(path, buf, NULL)) {\n+\t\t\t\tfprintf(stderr,\n+\t\t\t\t\t_(\"Please supply the message using either -m or -F option.\\n\"));\n+\t\t\t\texit(1);\n+\t\t\t}\n+\t\t} else if (trailer_args->nr) {\n+\t\t\tstrbuf_reset(buf);\n+\t\t\tif (strbuf_read_file(buf, path, 0) < 0) {\n+\t\t\t\tfprintf(stderr,\n+\t\t\t\t\t_(\"Please supply the message using either -m or -F option.\\n\"));\n+\t\t\t\texit(1);\n+\t\t\t}\n \t\t}\n \t}\n \n@@ -463,6 +481,7 @@ int cmd_tag(int argc, const char **argv, const char *prefix)\n \tstruct ref_sorting *sorting;\n \tstruct string_list sorting_options = STRING_LIST_INIT_DUP;\n \tstruct ref_format format = REF_FORMAT_INIT;\n+\tstruct strvec trailer_args = STRVEC_INIT;\n \tint icase = 0;\n \tint edit_flag = 0;\n \tstruct option options[] = {\n@@ -479,6 +498,8 @@ int cmd_tag(int argc, const char **argv, const char *prefix)\n \t\tOPT_CALLBACK_F('m', \"message\", &msg, N_(\"message\"),\n \t\t\t       N_(\"tag message\"), PARSE_OPT_NONEG, parse_msg_arg),\n \t\tOPT_FILENAME('F', \"file\", &msgfile, N_(\"read message from file\")),\n+\t\tOPT_PASSTHRU_ARGV(0, \"trailer\", &trailer_args, N_(\"trailer\"),\n+\t\t\t\t  N_(\"add custom trailer(s)\"), PARSE_OPT_NONEG),\n \t\tOPT_BOOL('e', \"edit\", &edit_flag, N_(\"force edit of tag message\")),\n \t\tOPT_BOOL('s', \"sign\", &opt.sign, N_(\"annotated and GPG-signed tag\")),\n \t\tOPT_CLEANUP(&cleanup_arg),\n@@ -548,7 +569,8 @@ int cmd_tag(int argc, const char **argv, const char *prefix)\n \t\topt.sign = 1;\n \t\tset_signing_key(keyid);\n \t}\n-\tcreate_tag_object = (opt.sign || annotate || msg.given || msgfile);\n+\tcreate_tag_object = (opt.sign || annotate || msg.given || msgfile ||\n+\t\t\t     edit_flag || trailer_args.nr);\n \n \tif ((create_tag_object || force) && (cmdmode != 0))\n \t\tusage_with_options(git_tag_usage, options);\n@@ -654,7 +676,7 @@ int cmd_tag(int argc, const char **argv, const char *prefix)\n \t\t\topt.sign = 1;\n \t\tpath = git_pathdup(\"TAG_EDITMSG\");\n \t\tcreate_tag(&object, object_ref, tag, &buf, &opt, &prev, &object,\n-\t\t\t   path);\n+\t\t\t   &trailer_args, path);\n \t}\n \n \ttransaction = ref_transaction_begin(&err);\n@@ -686,6 +708,7 @@ int cmd_tag(int argc, const char **argv, const char *prefix)\n \tstrbuf_release(&reflog_msg);\n \tstrbuf_release(&msg.buf);\n \tstrbuf_release(&err);\n+\tstrvec_clear(&trailer_args);\n \tfree(msgfile);\n \treturn ret;\n }\ndiff --git a/t/t7004-tag.sh b/t/t7004-tag.sh\nindex 696866d7794..fa6336edf98 100755\n--- a/t/t7004-tag.sh\n+++ b/t/t7004-tag.sh\n@@ -668,6 +668,115 @@ test_expect_success \\\n \ttest_cmp expect actual\n '\n \n+# trailers\n+\n+test_expect_success 'create tag with -m and --trailer' '\n+\tget_tag_header tag-with-inline-message-and-trailers $commit commit $time >expect &&\n+\tcat >>expect <<-\\EOF &&\n+\tcreate tag with trailers\n+\n+\tmy-trailer: here\n+\talt-trailer: there\n+\tEOF\n+\tgit tag -m \"create tag with trailers\" \\\n+\t\t--trailer my-trailer=here \\\n+\t\t--trailer alt-trailer=there \\\n+\t\ttag-with-inline-message-and-trailers &&\n+\tget_tag_msg tag-with-inline-message-and-trailers >actual &&\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'list tag extracting trailers' '\n+\tcat >expect <<-\\EOF &&\n+\tmy-trailer: here\n+\talt-trailer: there\n+\n+\tEOF\n+\tgit tag --list --format=\"%(trailers)\" tag-with-inline-message-and-trailers >actual &&\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'create tag with -F and --trailer' '\n+\techo \"create tag from message file using --trailer\" >messagefilewithnotrailers &&\n+\tget_tag_header tag-with-file-message-and-trailers $commit commit $time >expect &&\n+\tcat >>expect <<-\\EOF &&\n+\tcreate tag from message file using --trailer\n+\n+\tmy-trailer: here\n+\talt-trailer: there\n+\tEOF\n+\tgit tag -F messagefilewithnotrailers \\\n+\t\t--trailer my-trailer=here \\\n+\t\t--trailer alt-trailer=there \\\n+\t\ttag-with-file-message-and-trailers &&\n+\tget_tag_msg tag-with-file-message-and-trailers >actual &&\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'create tag with -m and --trailer and --edit' '\n+\twrite_script fakeeditor <<-\\EOF &&\n+\tsed -e \"1s/^/EDITED: /g\" <\"$1\" >\"$1-\"\n+\tmv \"$1-\" \"$1\"\n+\tEOF\n+\tget_tag_header tag-with-edited-inline-message-and-trailers $commit commit $time >expect &&\n+\tcat >>expect <<-\\EOF &&\n+\tEDITED: create tag with trailers\n+\n+\tmy-trailer: here\n+\talt-trailer: there\n+\tEOF\n+\tGIT_EDITOR=./fakeeditor git tag --edit \\\n+\t\t-m \"create tag with trailers\" \\\n+\t\t--trailer my-trailer=here \\\n+\t\t--trailer alt-trailer=there \\\n+\t\ttag-with-edited-inline-message-and-trailers &&\n+\tget_tag_msg tag-with-edited-inline-message-and-trailers >actual &&\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'create tag with -F and --trailer and --edit' '\n+\techo \"create tag from message file using --trailer\" >messagefilewithnotrailers &&\n+\tget_tag_header tag-with-edited-file-message-and-trailers $commit commit $time >expect &&\n+\tcat >>expect <<-\\EOF &&\n+\tEDITED: create tag from message file using --trailer\n+\n+\tmy-trailer: here\n+\talt-trailer: there\n+\tEOF\n+\tGIT_EDITOR=./fakeeditor git tag --edit \\\n+\t\t-F messagefilewithnotrailers \\\n+\t\t--trailer my-trailer=here \\\n+\t\t--trailer alt-trailer=there \\\n+\t\ttag-with-edited-file-message-and-trailers &&\n+\tget_tag_msg tag-with-edited-file-message-and-trailers >actual &&\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'create annotated tag and force editor when only --trailer is given' '\n+\twrite_script fakeeditor <<-\\EOF &&\n+\techo \"add a line\" >\"$1-\"\n+\tcat <\"$1\" >>\"$1-\"\n+\tmv \"$1-\" \"$1\"\n+\tEOF\n+\tget_tag_header tag-with-trailers-and-no-message $commit commit $time >expect &&\n+\tcat >>expect <<-\\EOF &&\n+\tadd a line\n+\n+\tmy-trailer: here\n+\talt-trailer: there\n+\tEOF\n+\tGIT_EDITOR=./fakeeditor git tag \\\n+\t\t--trailer my-trailer=here \\\n+\t\t--trailer alt-trailer=there \\\n+\t\ttag-with-trailers-and-no-message &&\n+\tget_tag_msg tag-with-trailers-and-no-message >actual &&\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'bad editor causes panic when only --trailer is given' '\n+\ttest_must_fail env GIT_EDITOR=false git tag --trailer my-trailer=here tag-will-not-exist\n+'\n+\n # listing messages for annotated non-signed tags:\n \n test_expect_success \\\n@@ -810,6 +919,11 @@ test_expect_success 'git tag --format with ahead-behind' '\n \trefs/tags/tag-lines 0 1 !\n \trefs/tags/tag-one-line 0 1 !\n \trefs/tags/tag-right 0 0 !\n+\trefs/tags/tag-with-edited-file-message-and-trailers 0 1 !\n+\trefs/tags/tag-with-edited-inline-message-and-trailers 0 1 !\n+\trefs/tags/tag-with-file-message-and-trailers 0 1 !\n+\trefs/tags/tag-with-inline-message-and-trailers 0 1 !\n+\trefs/tags/tag-with-trailers-and-no-message 0 1 !\n \trefs/tags/tag-zero-lines 0 1 !\n \tEOF\n \tgit tag -l --format=\"%(refname) %(ahead-behind:HEAD) !\" >actual 2>err &&\n-- \ngitgitgadget\n\n"},{"id":"493673","messageId":"d5335e30b0bf8f126f5bd3ac71d8a0afb3fa86f6.1714416865.git.gitgitgadget@gmail.com","threadId":"61376","inReplyTo":"pull.1723.v3.git.1714416863.gitgitgadget@gmail.com","subject":"[PATCH v3 3/3] po: update git-tag translations","fromName":"John Passaro via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2024-04-29T18:54:23Z","receivedAt":"2024-04-29T18:54:36Z","isPatch":true,"sender":{"key":"john.a.passaro@gmail.com","avatar":"https://avatars.githubusercontent.com/u/6754005?v=4"},"body":"From: John Passaro <john.a.passaro@gmail.com>\n\nUpdate translation files to translate the updated git-tag help headline\nwith its new --trailer option.\n\nAlso perform some related cleanup. Most notably, update git-tag headline\nin files where it references a stale version: i.e. where we are still\nattempting to translate the former one-line description of git-tag that\ndescribed the argument as \"[<head>]\", instead of current \"[<commit> |\n<object>]\".\n\nIn both cases, reuse translations found elsewhere in the file.\n\nSigned-off-by: John Passaro <john.a.passaro@gmail.com>\n---\n po/bg.po    |  2 ++\n po/ca.po    |  4 +++-\n po/de.po    |  2 ++\n po/el.po    |  9 ++++++---\n po/es.po    | 14 ++++++++------\n po/fr.po    |  2 ++\n po/id.po    |  2 ++\n po/it.po    |  6 ++++--\n po/ko.po    | 10 ++++++----\n po/pl.po    |  6 ++++--\n po/ru.po    |  1 +\n po/sv.po    |  2 ++\n po/tr.po    |  2 ++\n po/uk.po    |  2 ++\n po/vi.po    |  2 ++\n po/zh_CN.po |  2 ++\n po/zh_TW.po |  2 ++\n 17 files changed, 52 insertions(+), 18 deletions(-)\n\ndiff --git a/po/bg.po b/po/bg.po\nindex a7cbc617a53..d2fe6c87186 100644\n--- a/po/bg.po\n+++ b/po/bg.po\n@@ -13555,9 +13555,11 @@ msgstr \"причина за обновяването\"\n \n msgid \"\"\n \"git tag [-a | -s | -u <key-id>] [-f] [-m <msg> | -F <file>] [-e]\\n\"\n+\"        [(--trailer <token>[(=|:)<value>])...]\\n\"\n \"        <tagname> [<commit> | <object>]\"\n msgstr \"\"\n \"git tag [-a|-s|-u ИДЕНТИФИКАТОР_НА_КЛЮЧ] [-f] [-m СЪОБЩЕНИЕ|-F ФАЙЛ] [-e]\\n\"\n+\"        [(--trailer ЛЕКСЕМА[(=|:)СТОЙНОСТ])…]\\n\"\n \"        ЕТИКЕТ [ПОДАВАНЕ|ОБЕКТ]\"\n \n msgid \"git tag -d <tagname>...\"\ndiff --git a/po/ca.po b/po/ca.po\nindex bcb4da80fb9..8c8f52985b7 100644\n--- a/po/ca.po\n+++ b/po/ca.po\n@@ -13114,10 +13114,12 @@ msgstr \"raó de l'actualització\"\n \n msgid \"\"\n \"git tag [-a | -s | -u <key-id>] [-f] [-m <msg> | -F <file>] [-e]\\n\"\n+\"        [(--trailer <token>[(=|:)<value>])...]\\n\"\n \"        <tagname> [<commit> | <object>]\"\n msgstr \"\"\n \"git tag [-a | -s | -u <key-id>] [-f] [-m <msg> | -F <fitxer>] [-e]\\n\"\n-\"        <tagname> [<comissió> | <objecte>]\"\n+\"        [(--trailer <token>[(=|:)<value>])...]\\n\"\n+\"        <nom-d'etiqueta> [<comissió> | <objecte>]\"\n \n msgid \"git tag -d <tagname>...\"\n msgstr \"git tag -d <nom-d'etiqueta>...\"\ndiff --git a/po/de.po b/po/de.po\nindex 29465665976..5380f294920 100644\n--- a/po/de.po\n+++ b/po/de.po\n@@ -13297,9 +13297,11 @@ msgstr \"Grund für die Aktualisierung\"\n \n msgid \"\"\n \"git tag [-a | -s | -u <key-id>] [-f] [-m <msg> | -F <file>] [-e]\\n\"\n+\"        [(--trailer <token>[(=|:)<value>])...]\\n\"\n \"        <tagname> [<commit> | <object>]\"\n msgstr \"\"\n \"git tag [-a | -s | -u <Key-ID>] [-f] [-m <Beschreibung> | -F <Datei>] [-e]\\n\"\n+\"        [(--trailer <Token>[(=|:)<Wert>])...]\\n\"\n \"        <Tagname> [<Commit> | <Objekt>]\"\n \n msgid \"git tag -d <tagname>...\"\ndiff --git a/po/el.po b/po/el.po\nindex 703f46d0c7c..210304b75da 100644\n--- a/po/el.po\n+++ b/po/el.po\n@@ -18093,11 +18093,14 @@ msgstr \"\"\n \n #: builtin/tag.c:25\n msgid \"\"\n-\"git tag [-a | -s | -u <key-id>] [-f] [-m <msg> | -F <file>] <tagname> \"\n-\"[<head>]\"\n+\"git tag [-a | -s | -u <key-id>] [-f] [-m <msg> | -F <file>]\\n\"\n+\"        [(--trailer <token>[(=|:)<value>])...]\\n\"\n+\"        <tagname> [<commit> | <object>]\"\n msgstr \"\"\n \"git tag [-a | -s | -u <αναγνωριστικό κλειδί>] [-f] [-m <μήνυμα> | -F \"\n-\"<αρχείο>] <όνομα ετικέτας> [<head>]\"\n+\"<αρχείο>]\\n\"\n+\"        [(--trailer <token>[(=|:)<value>])...]\\n\"\n+\"        <όνομα ετικέτας> [<υποβολή> | <αντικείμενο>]\"\n \n #: builtin/tag.c:26\n msgid \"git tag -d <tagname>...\"\ndiff --git a/po/es.po b/po/es.po\nindex 1ff5ff3911d..b7b15ad6e4b 100644\n--- a/po/es.po\n+++ b/po/es.po\n@@ -12682,10 +12682,12 @@ msgstr \"razón de la actualización\"\n \n msgid \"\"\n \"git tag [-a | -s | -u <key-id>] [-f] [-m <msg> | -F <file>]\\n\"\n-\"        <tagname> [<head>]\"\n+\"        [(--trailer <token>[(=|:)<value>])...]\\n\"\n+\"        <tagname> [<commit> | <object>]\"\n msgstr \"\"\n-\"git tag [-a | -s | -u <key-id>] [-f] [-m <msg> | -F <file>]\\n\"\n-\"        <tagname> [<head>]\"\n+\"git tag [-a | -s | -u <key-id>] [-f] [-m <msg> | -F <archivo>]\\n\"\n+\"        [(--trailer <token>[(=|:)<valor>])...]\\n\"\n+\"        <nombre-de-tag> [<commit> | <objeto>]\"\n \n msgid \"git tag -d <tagname>...\"\n msgstr \"git tag -d <nombre-de-tag>...\"\n@@ -12697,8 +12699,8 @@ msgid \"\"\n \"[<pattern>...]\"\n msgstr \"\"\n \"git tag -l [-n[<num>]] [--contains <commit>] [--no-contains <commit>] [--\"\n-\"points-at <object>]\\n\"\n-\"        [--format=<format>] [--merged <commit>] [--no-merged <commit>] \"\n+\"points-at <objeto>]\\n\"\n+\"        [--format=<formato>] [--merged <commit>] [--no-merged <commit>] \"\n \"[<pattern>...]\"\n \n msgid \"git tag -v [--format=<format>] <tagname>...\"\n@@ -18461,7 +18463,7 @@ msgstr \"%%(body) no toma ningún argumento\"\n \n #, c-format\n msgid \"expected %%(trailers:key=<value>)\"\n-msgstr \"se esperaba %%(trailers:key=<value>)\"\n+msgstr \"se esperaba %%(trailers:key=<valor>)\"\n \n #, c-format\n msgid \"unknown %%(trailers) argument: %s\"\ndiff --git a/po/fr.po b/po/fr.po\nindex 837a695485a..2f7a345c74a 100644\n--- a/po/fr.po\n+++ b/po/fr.po\n@@ -13266,9 +13266,11 @@ msgstr \"raison de la mise à jour\"\n \n msgid \"\"\n \"git tag [-a | -s | -u <key-id>] [-f] [-m <msg> | -F <file>] [-e]\\n\"\n+\"        [(--trailer <token>[(=|:)<value>])...]\\n\"\n \"        <tagname> [<commit> | <object>]\"\n msgstr \"\"\n \"git tag [-a | -s | -u <id-clé>] [-f] [-m <msg> | -F <fichier>] [-e]\\n\"\n+\"        [(--trailer <symbole>[(=|:)<valeur>])...]\\n\"\n \"        <nom-d-étiquette> [<commit> | <objet>]\"\n \n msgid \"git tag -d <tagname>...\"\ndiff --git a/po/id.po b/po/id.po\nindex 2dc4b79e8a5..85b5631b3c7 100644\n--- a/po/id.po\n+++ b/po/id.po\n@@ -16230,9 +16230,11 @@ msgstr \"alasan pembaruan\"\n #: builtin/tag.c\n msgid \"\"\n \"git tag [-a | -s | -u <key-id>] [-f] [-m <msg> | -F <file>] [-e]\\n\"\n+\"        [(--trailer <token>[(=|:)<value>])...]\\n\"\n \"        <tagname> [<commit> | <object>]\"\n msgstr \"\"\n \"git tag [-a | -s | -u <id kunci>] [-f] [-m <pesan> | -F <berkas>] [-e]\\n\"\n+\"        [(--trailer <token>[(=|:)<nilai>])...]\\n\"\n \"        <nama tag> [<komit> | <objek>]\"\n \n #: builtin/tag.c\ndiff --git a/po/it.po b/po/it.po\nindex c31560834d8..c4ceb8b2444 100644\n--- a/po/it.po\n+++ b/po/it.po\n@@ -22486,10 +22486,12 @@ msgstr \"motivo dell'aggiornamento\"\n #: builtin/tag.c:25\n msgid \"\"\n \"git tag [-a | -s | -u <key-id>] [-f] [-m <msg> | -F <file>]\\n\"\n-\"\\t\\t<tagname> [<head>]\"\n+\"        [(--trailer <token>[(=|:)<value>])...]\\n\"\n+\"        <tagname> [<commit> | <object>]\"\n msgstr \"\"\n \"git tag [-a | -s | -u <ID chiave>] [-f] [-m <messaggio> | -F <file>]\\n\"\n-\"\\t\\t<nome tag> [<head>]\"\n+\"        [(--trailer <token>[(=|:)<valore>])...]\\n\"\n+\"        <nome tag> [<commit> | <oggetto>]\"\n \n #: builtin/tag.c:27\n msgid \"git tag -d <tagname>...\"\ndiff --git a/po/ko.po b/po/ko.po\nindex 5d190e52380..7484304eddb 100644\n--- a/po/ko.po\n+++ b/po/ko.po\n@@ -14397,11 +14397,13 @@ msgstr \"업데이트의 이유\"\n \n #: builtin/tag.c:24\n msgid \"\"\n-\"git tag [-a | -s | -u <key-id>] [-f] [-m <msg> | -F <file>] <tagname> \"\n-\"[<head>]\"\n+\"git tag [-a | -s | -u <key-id>] [-f] [-m <msg> | -F <file>]\\n\"\n+\"        [(--trailer <token>[(=|:)<value>])...]\\n\"\n+\"        <tagname> [<commit> | <object>]\"\n msgstr \"\"\n-\"git tag [-a | -s | -u <키-ID>] [-f] [-m <메시지> | -F <파일>] <태그이름>\\n\"\n-\"\\t\\t[<헤드>]\"\n+\"git tag [-a | -s | -u <키-ID>] [-f] [-m <메시지> | -F <파일>]\\n\"\n+\"        [(--trailer <토큰>[(=|:)<값>])...]\\n\"\n+\"        <태그이름> [<커밋> | <오브젝트>]\"\n \n #: builtin/tag.c:25\n msgid \"git tag -d <tagname>...\"\ndiff --git a/po/pl.po b/po/pl.po\nindex 0ec127e14cc..287354b17b4 100644\n--- a/po/pl.po\n+++ b/po/pl.po\n@@ -23549,10 +23549,12 @@ msgstr \"powód aktualizacji\"\n #: builtin/tag.c:25\n msgid \"\"\n \"git tag [-a | -s | -u <key-id>] [-f] [-m <msg> | -F <file>]\\n\"\n-\"        <tagname> [<head>]\"\n+\"        [(--trailer <token>[(=|:)<value>])...]\\n\"\n+\"        <tagname> [<commit> | <object>]\"\n msgstr \"\"\n \"git tag [-a | -s | -u <id-klucza>] [-f] [-m <komunikat> | -F <plik>]\\n\"\n-\"        <tag> [<czoło>]\"\n+\"        [(--trailer <klucz>[(=|:)<wartość>])...]\\n\"\n+\"        <nazwa-tagu> [<zapis> | <obiekt>]\"\n \n #: builtin/tag.c:27\n msgid \"git tag -d <tagname>...\"\ndiff --git a/po/ru.po b/po/ru.po\nindex 3e56eb546ea..d357bc30c2c 100644\n--- a/po/ru.po\n+++ b/po/ru.po\n@@ -12490,6 +12490,7 @@ msgstr \"причина обновления\"\n \n msgid \"\"\n \"git tag [-a | -s | -u <key-id>] [-f] [-m <msg> | -F <file>] [-e]\\n\"\n+\"        [(--trailer <token>[(=|:)<value>])...]\\n\"\n \"        <tagname> [<commit> | <object>]\"\n msgstr \"\"\n \ndiff --git a/po/sv.po b/po/sv.po\nindex ad1aad94fff..1f878ee28bc 100644\n--- a/po/sv.po\n+++ b/po/sv.po\n@@ -12835,9 +12835,11 @@ msgstr \"skäl till uppdateringen\"\n \n msgid \"\"\n \"git tag [-a | -s | -u <key-id>] [-f] [-m <msg> | -F <file>] [-e]\\n\"\n+\"        [(--trailer <token>[(=|:)<value>])...]\\n\"\n \"        <tagname> [<commit> | <object>]\"\n msgstr \"\"\n \"git tag [-a | -s | -u <nyckel-id>] [-f] [-m <medd> | -F <fil>] [-e]\\n\"\n+\"        [(--trailer <symbol>[(=|:)<värde>])...]\\n\"\n \"        <taggnamn> [<incheckning> | <objekt>]\"\n \n msgid \"git tag -d <tagname>...\"\ndiff --git a/po/tr.po b/po/tr.po\nindex 0e220e1c1cd..8c506c34a9e 100644\n--- a/po/tr.po\n+++ b/po/tr.po\n@@ -12975,10 +12975,12 @@ msgstr \"güncelleme nedeni\"\n \n msgid \"\"\n \"git tag [-a | -s | -u <key-id>] [-f] [-m <msg> | -F <file>] [-e]\\n\"\n+\"        [(--trailer <token>[(=|:)<value>])...]\\n\"\n \"        <tagname> [<commit> | <object>]\"\n msgstr \"\"\n \"git tag [-a | -s | -u <anahtar-kimliği>] [-f] [-m <ileti> | -F <dosya>] [-\"\n \"e]\\n\"\n+\"        [(--trailer <jeton>[(=|:)<değer>])...]\\n\"\n \"        <etiket-adı> [<işleme> | <nesne>]\"\n \n msgid \"git tag -d <tagname>...\"\ndiff --git a/po/uk.po b/po/uk.po\nindex 528a3dc6f76..e3b6441e3c1 100644\n--- a/po/uk.po\n+++ b/po/uk.po\n@@ -13086,10 +13086,12 @@ msgstr \"причина оновлення\"\n \n msgid \"\"\n \"git tag [-a | -s | -u <key-id>] [-f] [-m <msg> | -F <file>] [-e]\\n\"\n+\"        [(--trailer <token>[(=|:)<value>])...]\\n\"\n \"        <tagname> [<commit> | <object>]\"\n msgstr \"\"\n \"git tag [-a | -s | -u <ідентифікатор-ключа>] [-f] [-m <допис> | -F <файл>] [-\"\n \"e]\\n\"\n+\"        [(--trailer <токен>[(=|:)<значення>])...]\\n\"\n \"        <назва-тегу> [<коміт> | <об’єкт>]\"\n \n msgid \"git tag -d <tagname>...\"\ndiff --git a/po/vi.po b/po/vi.po\nindex 965e79e9658..0b206309f9b 100644\n--- a/po/vi.po\n+++ b/po/vi.po\n@@ -12915,9 +12915,11 @@ msgstr \"lý do cập nhật\"\n \n msgid \"\"\n \"git tag [-a | -s | -u <key-id>] [-f] [-m <msg> | -F <file>] [-e]\\n\"\n+\"        [(--trailer <token>[(=|:)<value>])...]\\n\"\n \"        <tagname> [<commit> | <object>]\"\n msgstr \"\"\n \"git tag [-a | -s | -u <key-id>] [-f] [-m <msg> | -F <tập-tin>] [-e]\\n\"\n+\"        [(--trailer <thẻ>[(=|:)<giá-trị>])...]\\n\"\n \"        <tên-thẻ> [<lần chuyển giao> | <đối tượng> ]\"\n \n msgid \"git tag -d <tagname>...\"\ndiff --git a/po/zh_CN.po b/po/zh_CN.po\nindex 4838c19b0be..aaedddcd864 100644\n--- a/po/zh_CN.po\n+++ b/po/zh_CN.po\n@@ -16033,9 +16033,11 @@ msgstr \"更新的原因\"\n #: builtin/tag.c\n msgid \"\"\n \"git tag [-a | -s | -u <key-id>] [-f] [-m <msg> | -F <file>] [-e]\\n\"\n+\"        [(--trailer <token>[(=|:)<value>])...]\\n\"\n \"        <tagname> [<commit> | <object>]\"\n msgstr \"\"\n \"git tag [-a | -s | -u <私钥 ID>] [-f] [-m <消息> | -F <文件>] [-e]\\n\"\n+\"        [(--trailer <键>[(=|:)<值>])...]\\n\"\n \"        <标签名> [<提交> | <对象>]\"\n \n #: builtin/tag.c\ndiff --git a/po/zh_TW.po b/po/zh_TW.po\nindex f554381a7af..168591c141e 100644\n--- a/po/zh_TW.po\n+++ b/po/zh_TW.po\n@@ -15911,9 +15911,11 @@ msgstr \"更新的原因\"\n #: builtin/tag.c\n msgid \"\"\n \"git tag [-a | -s | -u <key-id>] [-f] [-m <msg> | -F <file>] [-e]\\n\"\n+\"        [(--trailer <token>[(=|:)<value>])...]\\n\"\n \"        <tagname> [<commit> | <object>]\"\n msgstr \"\"\n \"git tag [-a | -s | -u <key-id>] [-f] [-m <msg> | -F <file>] [-e]\\n\"\n+\"        [(--trailer <token>[(=|:)<value>])...]\\n\"\n \"        <tagname> [<commit> | <object>]\"\n \n #: builtin/tag.c\n-- \ngitgitgadget\n"},{"id":"493676","messageId":"xmqqjzkgug6x.fsf@gitster.g","threadId":"61376","inReplyTo":"d5335e30b0bf8f126f5bd3ac71d8a0afb3fa86f6.1714416865.git.gitgitgadget@gmail.com","subject":"Re: [PATCH v3 3/3] po: update git-tag translations","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2024-04-29T19:22:46Z","receivedAt":"2024-04-29T19:22:54Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"\"John Passaro via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n\n> In both cases, reuse translations found elsewhere in the file.\n\nWhen each l10n team sees an updated code and translations together,\nwill they have a chance to easily (1) notice that there are\ntranslations for their language that came from outside the normal\nprocess (namely, this patch), and (2) double-check such a\ntranslated text that came from sidelines are reasonable?\n\nI am wondering if we should be doing some trick that involve use of\ncomments like \"# fuzzy\" in the file here.  While I applaud your good\nintention to help reduce the workload for translators, unless you\nbelong to all of the l10n teams for these languages (note: I did not\nsay \"unless you are fluent in these languages\" here---I am playing\nsafe by assuming that l10n teams would want to have a chance to\nverify even a translation that comes from a native that comes\noutside the usual workflow of the l10n groups), I would really want\nto avoid a change like this go unnoticed by l10n teams (iow, as long\nas we are sure they will notice and have chance to verify, I am\nfine with a change like this).\n\nThanks.\n\n> Signed-off-by: John Passaro <john.a.passaro@gmail.com>\n> ---\n>  po/bg.po    |  2 ++\n>  po/ca.po    |  4 +++-\n>  po/de.po    |  2 ++\n>  po/el.po    |  9 ++++++---\n>  po/es.po    | 14 ++++++++------\n>  po/fr.po    |  2 ++\n>  po/id.po    |  2 ++\n>  po/it.po    |  6 ++++--\n>  po/ko.po    | 10 ++++++----\n>  po/pl.po    |  6 ++++--\n>  po/ru.po    |  1 +\n>  po/sv.po    |  2 ++\n>  po/tr.po    |  2 ++\n>  po/uk.po    |  2 ++\n>  po/vi.po    |  2 ++\n>  po/zh_CN.po |  2 ++\n>  po/zh_TW.po |  2 ++\n>  17 files changed, 52 insertions(+), 18 deletions(-)\n>\n> diff --git a/po/bg.po b/po/bg.po\n> index a7cbc617a53..d2fe6c87186 100644\n> --- a/po/bg.po\n> +++ b/po/bg.po\n> @@ -13555,9 +13555,11 @@ msgstr \"причина за обновяването\"\n>  \n>  msgid \"\"\n>  \"git tag [-a | -s | -u <key-id>] [-f] [-m <msg> | -F <file>] [-e]\\n\"\n> +\"        [(--trailer <token>[(=|:)<value>])...]\\n\"\n>  \"        <tagname> [<commit> | <object>]\"\n>  msgstr \"\"\n>  \"git tag [-a|-s|-u ИДЕНТИФИКАТОР_НА_КЛЮЧ] [-f] [-m СЪОБЩЕНИЕ|-F ФАЙЛ] [-e]\\n\"\n> +\"        [(--trailer ЛЕКСЕМА[(=|:)СТОЙНОСТ])…]\\n\"\n>  \"        ЕТИКЕТ [ПОДАВАНЕ|ОБЕКТ]\"\n>  \n>  msgid \"git tag -d <tagname>...\"\n> diff --git a/po/ca.po b/po/ca.po\n> index bcb4da80fb9..8c8f52985b7 100644\n> --- a/po/ca.po\n> +++ b/po/ca.po\n> @@ -13114,10 +13114,12 @@ msgstr \"raó de l'actualització\"\n>  \n>  msgid \"\"\n>  \"git tag [-a | -s | -u <key-id>] [-f] [-m <msg> | -F <file>] [-e]\\n\"\n> +\"        [(--trailer <token>[(=|:)<value>])...]\\n\"\n>  \"        <tagname> [<commit> | <object>]\"\n>  msgstr \"\"\n>  \"git tag [-a | -s | -u <key-id>] [-f] [-m <msg> | -F <fitxer>] [-e]\\n\"\n> -\"        <tagname> [<comissió> | <objecte>]\"\n> +\"        [(--trailer <token>[(=|:)<value>])...]\\n\"\n> +\"        <nom-d'etiqueta> [<comissió> | <objecte>]\"\n>  \n>  msgid \"git tag -d <tagname>...\"\n>  msgstr \"git tag -d <nom-d'etiqueta>...\"\n> diff --git a/po/de.po b/po/de.po\n> index 29465665976..5380f294920 100644\n> --- a/po/de.po\n> +++ b/po/de.po\n> @@ -13297,9 +13297,11 @@ msgstr \"Grund für die Aktualisierung\"\n>  \n>  msgid \"\"\n>  \"git tag [-a | -s | -u <key-id>] [-f] [-m <msg> | -F <file>] [-e]\\n\"\n> +\"        [(--trailer <token>[(=|:)<value>])...]\\n\"\n>  \"        <tagname> [<commit> | <object>]\"\n>  msgstr \"\"\n>  \"git tag [-a | -s | -u <Key-ID>] [-f] [-m <Beschreibung> | -F <Datei>] [-e]\\n\"\n> +\"        [(--trailer <Token>[(=|:)<Wert>])...]\\n\"\n>  \"        <Tagname> [<Commit> | <Objekt>]\"\n>  \n>  msgid \"git tag -d <tagname>...\"\n> diff --git a/po/el.po b/po/el.po\n> index 703f46d0c7c..210304b75da 100644\n> --- a/po/el.po\n> +++ b/po/el.po\n> @@ -18093,11 +18093,14 @@ msgstr \"\"\n>  \n>  #: builtin/tag.c:25\n>  msgid \"\"\n> -\"git tag [-a | -s | -u <key-id>] [-f] [-m <msg> | -F <file>] <tagname> \"\n> -\"[<head>]\"\n> +\"git tag [-a | -s | -u <key-id>] [-f] [-m <msg> | -F <file>]\\n\"\n> +\"        [(--trailer <token>[(=|:)<value>])...]\\n\"\n> +\"        <tagname> [<commit> | <object>]\"\n>  msgstr \"\"\n>  \"git tag [-a | -s | -u <αναγνωριστικό κλειδί>] [-f] [-m <μήνυμα> | -F \"\n> -\"<αρχείο>] <όνομα ετικέτας> [<head>]\"\n> +\"<αρχείο>]\\n\"\n> +\"        [(--trailer <token>[(=|:)<value>])...]\\n\"\n> +\"        <όνομα ετικέτας> [<υποβολή> | <αντικείμενο>]\"\n>  \n>  #: builtin/tag.c:26\n>  msgid \"git tag -d <tagname>...\"\n> diff --git a/po/es.po b/po/es.po\n> index 1ff5ff3911d..b7b15ad6e4b 100644\n> --- a/po/es.po\n> +++ b/po/es.po\n> @@ -12682,10 +12682,12 @@ msgstr \"razón de la actualización\"\n>  \n>  msgid \"\"\n>  \"git tag [-a | -s | -u <key-id>] [-f] [-m <msg> | -F <file>]\\n\"\n> -\"        <tagname> [<head>]\"\n> +\"        [(--trailer <token>[(=|:)<value>])...]\\n\"\n> +\"        <tagname> [<commit> | <object>]\"\n>  msgstr \"\"\n> -\"git tag [-a | -s | -u <key-id>] [-f] [-m <msg> | -F <file>]\\n\"\n> -\"        <tagname> [<head>]\"\n> +\"git tag [-a | -s | -u <key-id>] [-f] [-m <msg> | -F <archivo>]\\n\"\n> +\"        [(--trailer <token>[(=|:)<valor>])...]\\n\"\n> +\"        <nombre-de-tag> [<commit> | <objeto>]\"\n>  \n>  msgid \"git tag -d <tagname>...\"\n>  msgstr \"git tag -d <nombre-de-tag>...\"\n> @@ -12697,8 +12699,8 @@ msgid \"\"\n>  \"[<pattern>...]\"\n>  msgstr \"\"\n>  \"git tag -l [-n[<num>]] [--contains <commit>] [--no-contains <commit>] [--\"\n> -\"points-at <object>]\\n\"\n> -\"        [--format=<format>] [--merged <commit>] [--no-merged <commit>] \"\n> +\"points-at <objeto>]\\n\"\n> +\"        [--format=<formato>] [--merged <commit>] [--no-merged <commit>] \"\n>  \"[<pattern>...]\"\n>  \n>  msgid \"git tag -v [--format=<format>] <tagname>...\"\n> @@ -18461,7 +18463,7 @@ msgstr \"%%(body) no toma ningún argumento\"\n>  \n>  #, c-format\n>  msgid \"expected %%(trailers:key=<value>)\"\n> -msgstr \"se esperaba %%(trailers:key=<value>)\"\n> +msgstr \"se esperaba %%(trailers:key=<valor>)\"\n>  \n>  #, c-format\n>  msgid \"unknown %%(trailers) argument: %s\"\n> diff --git a/po/fr.po b/po/fr.po\n> index 837a695485a..2f7a345c74a 100644\n> --- a/po/fr.po\n> +++ b/po/fr.po\n> @@ -13266,9 +13266,11 @@ msgstr \"raison de la mise à jour\"\n>  \n>  msgid \"\"\n>  \"git tag [-a | -s | -u <key-id>] [-f] [-m <msg> | -F <file>] [-e]\\n\"\n> +\"        [(--trailer <token>[(=|:)<value>])...]\\n\"\n>  \"        <tagname> [<commit> | <object>]\"\n>  msgstr \"\"\n>  \"git tag [-a | -s | -u <id-clé>] [-f] [-m <msg> | -F <fichier>] [-e]\\n\"\n> +\"        [(--trailer <symbole>[(=|:)<valeur>])...]\\n\"\n>  \"        <nom-d-étiquette> [<commit> | <objet>]\"\n>  \n>  msgid \"git tag -d <tagname>...\"\n> diff --git a/po/id.po b/po/id.po\n> index 2dc4b79e8a5..85b5631b3c7 100644\n> --- a/po/id.po\n> +++ b/po/id.po\n> @@ -16230,9 +16230,11 @@ msgstr \"alasan pembaruan\"\n>  #: builtin/tag.c\n>  msgid \"\"\n>  \"git tag [-a | -s | -u <key-id>] [-f] [-m <msg> | -F <file>] [-e]\\n\"\n> +\"        [(--trailer <token>[(=|:)<value>])...]\\n\"\n>  \"        <tagname> [<commit> | <object>]\"\n>  msgstr \"\"\n>  \"git tag [-a | -s | -u <id kunci>] [-f] [-m <pesan> | -F <berkas>] [-e]\\n\"\n> +\"        [(--trailer <token>[(=|:)<nilai>])...]\\n\"\n>  \"        <nama tag> [<komit> | <objek>]\"\n>  \n>  #: builtin/tag.c\n> diff --git a/po/it.po b/po/it.po\n> index c31560834d8..c4ceb8b2444 100644\n> --- a/po/it.po\n> +++ b/po/it.po\n> @@ -22486,10 +22486,12 @@ msgstr \"motivo dell'aggiornamento\"\n>  #: builtin/tag.c:25\n>  msgid \"\"\n>  \"git tag [-a | -s | -u <key-id>] [-f] [-m <msg> | -F <file>]\\n\"\n> -\"\\t\\t<tagname> [<head>]\"\n> +\"        [(--trailer <token>[(=|:)<value>])...]\\n\"\n> +\"        <tagname> [<commit> | <object>]\"\n>  msgstr \"\"\n>  \"git tag [-a | -s | -u <ID chiave>] [-f] [-m <messaggio> | -F <file>]\\n\"\n> -\"\\t\\t<nome tag> [<head>]\"\n> +\"        [(--trailer <token>[(=|:)<valore>])...]\\n\"\n> +\"        <nome tag> [<commit> | <oggetto>]\"\n>  \n>  #: builtin/tag.c:27\n>  msgid \"git tag -d <tagname>...\"\n> diff --git a/po/ko.po b/po/ko.po\n> index 5d190e52380..7484304eddb 100644\n> --- a/po/ko.po\n> +++ b/po/ko.po\n> @@ -14397,11 +14397,13 @@ msgstr \"업데이트의 이유\"\n>  \n>  #: builtin/tag.c:24\n>  msgid \"\"\n> -\"git tag [-a | -s | -u <key-id>] [-f] [-m <msg> | -F <file>] <tagname> \"\n> -\"[<head>]\"\n> +\"git tag [-a | -s | -u <key-id>] [-f] [-m <msg> | -F <file>]\\n\"\n> +\"        [(--trailer <token>[(=|:)<value>])...]\\n\"\n> +\"        <tagname> [<commit> | <object>]\"\n>  msgstr \"\"\n> -\"git tag [-a | -s | -u <키-ID>] [-f] [-m <메시지> | -F <파일>] <태그이름>\\n\"\n> -\"\\t\\t[<헤드>]\"\n> +\"git tag [-a | -s | -u <키-ID>] [-f] [-m <메시지> | -F <파일>]\\n\"\n> +\"        [(--trailer <토큰>[(=|:)<값>])...]\\n\"\n> +\"        <태그이름> [<커밋> | <오브젝트>]\"\n>  \n>  #: builtin/tag.c:25\n>  msgid \"git tag -d <tagname>...\"\n> diff --git a/po/pl.po b/po/pl.po\n> index 0ec127e14cc..287354b17b4 100644\n> --- a/po/pl.po\n> +++ b/po/pl.po\n> @@ -23549,10 +23549,12 @@ msgstr \"powód aktualizacji\"\n>  #: builtin/tag.c:25\n>  msgid \"\"\n>  \"git tag [-a | -s | -u <key-id>] [-f] [-m <msg> | -F <file>]\\n\"\n> -\"        <tagname> [<head>]\"\n> +\"        [(--trailer <token>[(=|:)<value>])...]\\n\"\n> +\"        <tagname> [<commit> | <object>]\"\n>  msgstr \"\"\n>  \"git tag [-a | -s | -u <id-klucza>] [-f] [-m <komunikat> | -F <plik>]\\n\"\n> -\"        <tag> [<czoło>]\"\n> +\"        [(--trailer <klucz>[(=|:)<wartość>])...]\\n\"\n> +\"        <nazwa-tagu> [<zapis> | <obiekt>]\"\n>  \n>  #: builtin/tag.c:27\n>  msgid \"git tag -d <tagname>...\"\n> diff --git a/po/ru.po b/po/ru.po\n> index 3e56eb546ea..d357bc30c2c 100644\n> --- a/po/ru.po\n> +++ b/po/ru.po\n> @@ -12490,6 +12490,7 @@ msgstr \"причина обновления\"\n>  \n>  msgid \"\"\n>  \"git tag [-a | -s | -u <key-id>] [-f] [-m <msg> | -F <file>] [-e]\\n\"\n> +\"        [(--trailer <token>[(=|:)<value>])...]\\n\"\n>  \"        <tagname> [<commit> | <object>]\"\n>  msgstr \"\"\n>  \n> diff --git a/po/sv.po b/po/sv.po\n> index ad1aad94fff..1f878ee28bc 100644\n> --- a/po/sv.po\n> +++ b/po/sv.po\n> @@ -12835,9 +12835,11 @@ msgstr \"skäl till uppdateringen\"\n>  \n>  msgid \"\"\n>  \"git tag [-a | -s | -u <key-id>] [-f] [-m <msg> | -F <file>] [-e]\\n\"\n> +\"        [(--trailer <token>[(=|:)<value>])...]\\n\"\n>  \"        <tagname> [<commit> | <object>]\"\n>  msgstr \"\"\n>  \"git tag [-a | -s | -u <nyckel-id>] [-f] [-m <medd> | -F <fil>] [-e]\\n\"\n> +\"        [(--trailer <symbol>[(=|:)<värde>])...]\\n\"\n>  \"        <taggnamn> [<incheckning> | <objekt>]\"\n>  \n>  msgid \"git tag -d <tagname>...\"\n> diff --git a/po/tr.po b/po/tr.po\n> index 0e220e1c1cd..8c506c34a9e 100644\n> --- a/po/tr.po\n> +++ b/po/tr.po\n> @@ -12975,10 +12975,12 @@ msgstr \"güncelleme nedeni\"\n>  \n>  msgid \"\"\n>  \"git tag [-a | -s | -u <key-id>] [-f] [-m <msg> | -F <file>] [-e]\\n\"\n> +\"        [(--trailer <token>[(=|:)<value>])...]\\n\"\n>  \"        <tagname> [<commit> | <object>]\"\n>  msgstr \"\"\n>  \"git tag [-a | -s | -u <anahtar-kimliği>] [-f] [-m <ileti> | -F <dosya>] [-\"\n>  \"e]\\n\"\n> +\"        [(--trailer <jeton>[(=|:)<değer>])...]\\n\"\n>  \"        <etiket-adı> [<işleme> | <nesne>]\"\n>  \n>  msgid \"git tag -d <tagname>...\"\n> diff --git a/po/uk.po b/po/uk.po\n> index 528a3dc6f76..e3b6441e3c1 100644\n> --- a/po/uk.po\n> +++ b/po/uk.po\n> @@ -13086,10 +13086,12 @@ msgstr \"причина оновлення\"\n>  \n>  msgid \"\"\n>  \"git tag [-a | -s | -u <key-id>] [-f] [-m <msg> | -F <file>] [-e]\\n\"\n> +\"        [(--trailer <token>[(=|:)<value>])...]\\n\"\n>  \"        <tagname> [<commit> | <object>]\"\n>  msgstr \"\"\n>  \"git tag [-a | -s | -u <ідентифікатор-ключа>] [-f] [-m <допис> | -F <файл>] [-\"\n>  \"e]\\n\"\n> +\"        [(--trailer <токен>[(=|:)<значення>])...]\\n\"\n>  \"        <назва-тегу> [<коміт> | <об’єкт>]\"\n>  \n>  msgid \"git tag -d <tagname>...\"\n> diff --git a/po/vi.po b/po/vi.po\n> index 965e79e9658..0b206309f9b 100644\n> --- a/po/vi.po\n> +++ b/po/vi.po\n> @@ -12915,9 +12915,11 @@ msgstr \"lý do cập nhật\"\n>  \n>  msgid \"\"\n>  \"git tag [-a | -s | -u <key-id>] [-f] [-m <msg> | -F <file>] [-e]\\n\"\n> +\"        [(--trailer <token>[(=|:)<value>])...]\\n\"\n>  \"        <tagname> [<commit> | <object>]\"\n>  msgstr \"\"\n>  \"git tag [-a | -s | -u <key-id>] [-f] [-m <msg> | -F <tập-tin>] [-e]\\n\"\n> +\"        [(--trailer <thẻ>[(=|:)<giá-trị>])...]\\n\"\n>  \"        <tên-thẻ> [<lần chuyển giao> | <đối tượng> ]\"\n>  \n>  msgid \"git tag -d <tagname>...\"\n> diff --git a/po/zh_CN.po b/po/zh_CN.po\n> index 4838c19b0be..aaedddcd864 100644\n> --- a/po/zh_CN.po\n> +++ b/po/zh_CN.po\n> @@ -16033,9 +16033,11 @@ msgstr \"更新的原因\"\n>  #: builtin/tag.c\n>  msgid \"\"\n>  \"git tag [-a | -s | -u <key-id>] [-f] [-m <msg> | -F <file>] [-e]\\n\"\n> +\"        [(--trailer <token>[(=|:)<value>])...]\\n\"\n>  \"        <tagname> [<commit> | <object>]\"\n>  msgstr \"\"\n>  \"git tag [-a | -s | -u <私钥 ID>] [-f] [-m <消息> | -F <文件>] [-e]\\n\"\n> +\"        [(--trailer <键>[(=|:)<值>])...]\\n\"\n>  \"        <标签名> [<提交> | <对象>]\"\n>  \n>  #: builtin/tag.c\n> diff --git a/po/zh_TW.po b/po/zh_TW.po\n> index f554381a7af..168591c141e 100644\n> --- a/po/zh_TW.po\n> +++ b/po/zh_TW.po\n> @@ -15911,9 +15911,11 @@ msgstr \"更新的原因\"\n>  #: builtin/tag.c\n>  msgid \"\"\n>  \"git tag [-a | -s | -u <key-id>] [-f] [-m <msg> | -F <file>] [-e]\\n\"\n> +\"        [(--trailer <token>[(=|:)<value>])...]\\n\"\n>  \"        <tagname> [<commit> | <object>]\"\n>  msgstr \"\"\n>  \"git tag [-a | -s | -u <key-id>] [-f] [-m <msg> | -F <file>] [-e]\\n\"\n> +\"        [(--trailer <token>[(=|:)<value>])...]\\n\"\n>  \"        <tagname> [<commit> | <object>]\"\n>  \n>  #: builtin/tag.c\n"},{"id":"493678","messageId":"CAJdN7Ki-7bB80H21hx9fQmRNxFsZjwZNWOUHG3KuZY4a3_Nf3g@mail.gmail.com","threadId":"61376","inReplyTo":"xmqqjzkgug6x.fsf@gitster.g","subject":"Re: [PATCH v3 3/3] po: update git-tag translations","fromName":"John Passaro","fromEmail":"john.a.passaro@gmail.com","sentAt":"2024-04-29T19:28:38Z","receivedAt":"2024-04-29T19:29:16Z","isPatch":true,"sender":{"key":"john.a.passaro@gmail.com","avatar":"https://avatars.githubusercontent.com/u/6754005?v=4"},"body":"Fair enough. I thought there might be some process making this\nunnecessary and it seems that is so. Sounds like the best move is to\nreject this patch, or at least to consider it separately.\n\nI assume that if the other two patches LGTY that you can easily queue\nthem up without this one. If that's not so, let me know and I'll\nresubmit the series without l10n changes.\n\nOn Mon, Apr 29, 2024 at 3:22 PM Junio C Hamano <gitster@pobox.com> wrote:\n>\n> \"John Passaro via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n>\n> > In both cases, reuse translations found elsewhere in the file.\n>\n> When each l10n team sees an updated code and translations together,\n> will they have a chance to easily (1) notice that there are\n> translations for their language that came from outside the normal\n> process (namely, this patch), and (2) double-check such a\n> translated text that came from sidelines are reasonable?\n>\n> I am wondering if we should be doing some trick that involve use of\n> comments like \"# fuzzy\" in the file here.  While I applaud your good\n> intention to help reduce the workload for translators, unless you\n> belong to all of the l10n teams for these languages (note: I did not\n> say \"unless you are fluent in these languages\" here---I am playing\n> safe by assuming that l10n teams would want to have a chance to\n> verify even a translation that comes from a native that comes\n> outside the usual workflow of the l10n groups), I would really want\n> to avoid a change like this go unnoticed by l10n teams (iow, as long\n> as we are sure they will notice and have chance to verify, I am\n> fine with a change like this).\n>\n> Thanks.\n>\n> > Signed-off-by: John Passaro <john.a.passaro@gmail.com>\n> > ---\n> >  po/bg.po    |  2 ++\n> >  po/ca.po    |  4 +++-\n> >  po/de.po    |  2 ++\n> >  po/el.po    |  9 ++++++---\n> >  po/es.po    | 14 ++++++++------\n> >  po/fr.po    |  2 ++\n> >  po/id.po    |  2 ++\n> >  po/it.po    |  6 ++++--\n> >  po/ko.po    | 10 ++++++----\n> >  po/pl.po    |  6 ++++--\n> >  po/ru.po    |  1 +\n> >  po/sv.po    |  2 ++\n> >  po/tr.po    |  2 ++\n> >  po/uk.po    |  2 ++\n> >  po/vi.po    |  2 ++\n> >  po/zh_CN.po |  2 ++\n> >  po/zh_TW.po |  2 ++\n> >  17 files changed, 52 insertions(+), 18 deletions(-)\n> >\n> > diff --git a/po/bg.po b/po/bg.po\n> > index a7cbc617a53..d2fe6c87186 100644\n> > --- a/po/bg.po\n> > +++ b/po/bg.po\n> > @@ -13555,9 +13555,11 @@ msgstr \"причина за обновяването\"\n> >\n> >  msgid \"\"\n> >  \"git tag [-a | -s | -u <key-id>] [-f] [-m <msg> | -F <file>] [-e]\\n\"\n> > +\"        [(--trailer <token>[(=|:)<value>])...]\\n\"\n> >  \"        <tagname> [<commit> | <object>]\"\n> >  msgstr \"\"\n> >  \"git tag [-a|-s|-u ИДЕНТИФИКАТОР_НА_КЛЮЧ] [-f] [-m СЪОБЩЕНИЕ|-F ФАЙЛ] [-e]\\n\"\n> > +\"        [(--trailer ЛЕКСЕМА[(=|:)СТОЙНОСТ])…]\\n\"\n> >  \"        ЕТИКЕТ [ПОДАВАНЕ|ОБЕКТ]\"\n> >\n> >  msgid \"git tag -d <tagname>...\"\n> > diff --git a/po/ca.po b/po/ca.po\n> > index bcb4da80fb9..8c8f52985b7 100644\n> > --- a/po/ca.po\n> > +++ b/po/ca.po\n> > @@ -13114,10 +13114,12 @@ msgstr \"raó de l'actualització\"\n> >\n> >  msgid \"\"\n> >  \"git tag [-a | -s | -u <key-id>] [-f] [-m <msg> | -F <file>] [-e]\\n\"\n> > +\"        [(--trailer <token>[(=|:)<value>])...]\\n\"\n> >  \"        <tagname> [<commit> | <object>]\"\n> >  msgstr \"\"\n> >  \"git tag [-a | -s | -u <key-id>] [-f] [-m <msg> | -F <fitxer>] [-e]\\n\"\n> > -\"        <tagname> [<comissió> | <objecte>]\"\n> > +\"        [(--trailer <token>[(=|:)<value>])...]\\n\"\n> > +\"        <nom-d'etiqueta> [<comissió> | <objecte>]\"\n> >\n> >  msgid \"git tag -d <tagname>...\"\n> >  msgstr \"git tag -d <nom-d'etiqueta>...\"\n> > diff --git a/po/de.po b/po/de.po\n> > index 29465665976..5380f294920 100644\n> > --- a/po/de.po\n> > +++ b/po/de.po\n> > @@ -13297,9 +13297,11 @@ msgstr \"Grund für die Aktualisierung\"\n> >\n> >  msgid \"\"\n> >  \"git tag [-a | -s | -u <key-id>] [-f] [-m <msg> | -F <file>] [-e]\\n\"\n> > +\"        [(--trailer <token>[(=|:)<value>])...]\\n\"\n> >  \"        <tagname> [<commit> | <object>]\"\n> >  msgstr \"\"\n> >  \"git tag [-a | -s | -u <Key-ID>] [-f] [-m <Beschreibung> | -F <Datei>] [-e]\\n\"\n> > +\"        [(--trailer <Token>[(=|:)<Wert>])...]\\n\"\n> >  \"        <Tagname> [<Commit> | <Objekt>]\"\n> >\n> >  msgid \"git tag -d <tagname>...\"\n> > diff --git a/po/el.po b/po/el.po\n> > index 703f46d0c7c..210304b75da 100644\n> > --- a/po/el.po\n> > +++ b/po/el.po\n> > @@ -18093,11 +18093,14 @@ msgstr \"\"\n> >\n> >  #: builtin/tag.c:25\n> >  msgid \"\"\n> > -\"git tag [-a | -s | -u <key-id>] [-f] [-m <msg> | -F <file>] <tagname> \"\n> > -\"[<head>]\"\n> > +\"git tag [-a | -s | -u <key-id>] [-f] [-m <msg> | -F <file>]\\n\"\n> > +\"        [(--trailer <token>[(=|:)<value>])...]\\n\"\n> > +\"        <tagname> [<commit> | <object>]\"\n> >  msgstr \"\"\n> >  \"git tag [-a | -s | -u <αναγνωριστικό κλειδί>] [-f] [-m <μήνυμα> | -F \"\n> > -\"<αρχείο>] <όνομα ετικέτας> [<head>]\"\n> > +\"<αρχείο>]\\n\"\n> > +\"        [(--trailer <token>[(=|:)<value>])...]\\n\"\n> > +\"        <όνομα ετικέτας> [<υποβολή> | <αντικείμενο>]\"\n> >\n> >  #: builtin/tag.c:26\n> >  msgid \"git tag -d <tagname>...\"\n> > diff --git a/po/es.po b/po/es.po\n> > index 1ff5ff3911d..b7b15ad6e4b 100644\n> > --- a/po/es.po\n> > +++ b/po/es.po\n> > @@ -12682,10 +12682,12 @@ msgstr \"razón de la actualización\"\n> >\n> >  msgid \"\"\n> >  \"git tag [-a | -s | -u <key-id>] [-f] [-m <msg> | -F <file>]\\n\"\n> > -\"        <tagname> [<head>]\"\n> > +\"        [(--trailer <token>[(=|:)<value>])...]\\n\"\n> > +\"        <tagname> [<commit> | <object>]\"\n> >  msgstr \"\"\n> > -\"git tag [-a | -s | -u <key-id>] [-f] [-m <msg> | -F <file>]\\n\"\n> > -\"        <tagname> [<head>]\"\n> > +\"git tag [-a | -s | -u <key-id>] [-f] [-m <msg> | -F <archivo>]\\n\"\n> > +\"        [(--trailer <token>[(=|:)<valor>])...]\\n\"\n> > +\"        <nombre-de-tag> [<commit> | <objeto>]\"\n> >\n> >  msgid \"git tag -d <tagname>...\"\n> >  msgstr \"git tag -d <nombre-de-tag>...\"\n> > @@ -12697,8 +12699,8 @@ msgid \"\"\n> >  \"[<pattern>...]\"\n> >  msgstr \"\"\n> >  \"git tag -l [-n[<num>]] [--contains <commit>] [--no-contains <commit>] [--\"\n> > -\"points-at <object>]\\n\"\n> > -\"        [--format=<format>] [--merged <commit>] [--no-merged <commit>] \"\n> > +\"points-at <objeto>]\\n\"\n> > +\"        [--format=<formato>] [--merged <commit>] [--no-merged <commit>] \"\n> >  \"[<pattern>...]\"\n> >\n> >  msgid \"git tag -v [--format=<format>] <tagname>...\"\n> > @@ -18461,7 +18463,7 @@ msgstr \"%%(body) no toma ningún argumento\"\n> >\n> >  #, c-format\n> >  msgid \"expected %%(trailers:key=<value>)\"\n> > -msgstr \"se esperaba %%(trailers:key=<value>)\"\n> > +msgstr \"se esperaba %%(trailers:key=<valor>)\"\n> >\n> >  #, c-format\n> >  msgid \"unknown %%(trailers) argument: %s\"\n> > diff --git a/po/fr.po b/po/fr.po\n> > index 837a695485a..2f7a345c74a 100644\n> > --- a/po/fr.po\n> > +++ b/po/fr.po\n> > @@ -13266,9 +13266,11 @@ msgstr \"raison de la mise à jour\"\n> >\n> >  msgid \"\"\n> >  \"git tag [-a | -s | -u <key-id>] [-f] [-m <msg> | -F <file>] [-e]\\n\"\n> > +\"        [(--trailer <token>[(=|:)<value>])...]\\n\"\n> >  \"        <tagname> [<commit> | <object>]\"\n> >  msgstr \"\"\n> >  \"git tag [-a | -s | -u <id-clé>] [-f] [-m <msg> | -F <fichier>] [-e]\\n\"\n> > +\"        [(--trailer <symbole>[(=|:)<valeur>])...]\\n\"\n> >  \"        <nom-d-étiquette> [<commit> | <objet>]\"\n> >\n> >  msgid \"git tag -d <tagname>...\"\n> > diff --git a/po/id.po b/po/id.po\n> > index 2dc4b79e8a5..85b5631b3c7 100644\n> > --- a/po/id.po\n> > +++ b/po/id.po\n> > @@ -16230,9 +16230,11 @@ msgstr \"alasan pembaruan\"\n> >  #: builtin/tag.c\n> >  msgid \"\"\n> >  \"git tag [-a | -s | -u <key-id>] [-f] [-m <msg> | -F <file>] [-e]\\n\"\n> > +\"        [(--trailer <token>[(=|:)<value>])...]\\n\"\n> >  \"        <tagname> [<commit> | <object>]\"\n> >  msgstr \"\"\n> >  \"git tag [-a | -s | -u <id kunci>] [-f] [-m <pesan> | -F <berkas>] [-e]\\n\"\n> > +\"        [(--trailer <token>[(=|:)<nilai>])...]\\n\"\n> >  \"        <nama tag> [<komit> | <objek>]\"\n> >\n> >  #: builtin/tag.c\n> > diff --git a/po/it.po b/po/it.po\n> > index c31560834d8..c4ceb8b2444 100644\n> > --- a/po/it.po\n> > +++ b/po/it.po\n> > @@ -22486,10 +22486,12 @@ msgstr \"motivo dell'aggiornamento\"\n> >  #: builtin/tag.c:25\n> >  msgid \"\"\n> >  \"git tag [-a | -s | -u <key-id>] [-f] [-m <msg> | -F <file>]\\n\"\n> > -\"\\t\\t<tagname> [<head>]\"\n> > +\"        [(--trailer <token>[(=|:)<value>])...]\\n\"\n> > +\"        <tagname> [<commit> | <object>]\"\n> >  msgstr \"\"\n> >  \"git tag [-a | -s | -u <ID chiave>] [-f] [-m <messaggio> | -F <file>]\\n\"\n> > -\"\\t\\t<nome tag> [<head>]\"\n> > +\"        [(--trailer <token>[(=|:)<valore>])...]\\n\"\n> > +\"        <nome tag> [<commit> | <oggetto>]\"\n> >\n> >  #: builtin/tag.c:27\n> >  msgid \"git tag -d <tagname>...\"\n> > diff --git a/po/ko.po b/po/ko.po\n> > index 5d190e52380..7484304eddb 100644\n> > --- a/po/ko.po\n> > +++ b/po/ko.po\n> > @@ -14397,11 +14397,13 @@ msgstr \"업데이트의 이유\"\n> >\n> >  #: builtin/tag.c:24\n> >  msgid \"\"\n> > -\"git tag [-a | -s | -u <key-id>] [-f] [-m <msg> | -F <file>] <tagname> \"\n> > -\"[<head>]\"\n> > +\"git tag [-a | -s | -u <key-id>] [-f] [-m <msg> | -F <file>]\\n\"\n> > +\"        [(--trailer <token>[(=|:)<value>])...]\\n\"\n> > +\"        <tagname> [<commit> | <object>]\"\n> >  msgstr \"\"\n> > -\"git tag [-a | -s | -u <키-ID>] [-f] [-m <메시지> | -F <파일>] <태그이름>\\n\"\n> > -\"\\t\\t[<헤드>]\"\n> > +\"git tag [-a | -s | -u <키-ID>] [-f] [-m <메시지> | -F <파일>]\\n\"\n> > +\"        [(--trailer <토큰>[(=|:)<값>])...]\\n\"\n> > +\"        <태그이름> [<커밋> | <오브젝트>]\"\n> >\n> >  #: builtin/tag.c:25\n> >  msgid \"git tag -d <tagname>...\"\n> > diff --git a/po/pl.po b/po/pl.po\n> > index 0ec127e14cc..287354b17b4 100644\n> > --- a/po/pl.po\n> > +++ b/po/pl.po\n> > @@ -23549,10 +23549,12 @@ msgstr \"powód aktualizacji\"\n> >  #: builtin/tag.c:25\n> >  msgid \"\"\n> >  \"git tag [-a | -s | -u <key-id>] [-f] [-m <msg> | -F <file>]\\n\"\n> > -\"        <tagname> [<head>]\"\n> > +\"        [(--trailer <token>[(=|:)<value>])...]\\n\"\n> > +\"        <tagname> [<commit> | <object>]\"\n> >  msgstr \"\"\n> >  \"git tag [-a | -s | -u <id-klucza>] [-f] [-m <komunikat> | -F <plik>]\\n\"\n> > -\"        <tag> [<czoło>]\"\n> > +\"        [(--trailer <klucz>[(=|:)<wartość>])...]\\n\"\n> > +\"        <nazwa-tagu> [<zapis> | <obiekt>]\"\n> >\n> >  #: builtin/tag.c:27\n> >  msgid \"git tag -d <tagname>...\"\n> > diff --git a/po/ru.po b/po/ru.po\n> > index 3e56eb546ea..d357bc30c2c 100644\n> > --- a/po/ru.po\n> > +++ b/po/ru.po\n> > @@ -12490,6 +12490,7 @@ msgstr \"причина обновления\"\n> >\n> >  msgid \"\"\n> >  \"git tag [-a | -s | -u <key-id>] [-f] [-m <msg> | -F <file>] [-e]\\n\"\n> > +\"        [(--trailer <token>[(=|:)<value>])...]\\n\"\n> >  \"        <tagname> [<commit> | <object>]\"\n> >  msgstr \"\"\n> >\n> > diff --git a/po/sv.po b/po/sv.po\n> > index ad1aad94fff..1f878ee28bc 100644\n> > --- a/po/sv.po\n> > +++ b/po/sv.po\n> > @@ -12835,9 +12835,11 @@ msgstr \"skäl till uppdateringen\"\n> >\n> >  msgid \"\"\n> >  \"git tag [-a | -s | -u <key-id>] [-f] [-m <msg> | -F <file>] [-e]\\n\"\n> > +\"        [(--trailer <token>[(=|:)<value>])...]\\n\"\n> >  \"        <tagname> [<commit> | <object>]\"\n> >  msgstr \"\"\n> >  \"git tag [-a | -s | -u <nyckel-id>] [-f] [-m <medd> | -F <fil>] [-e]\\n\"\n> > +\"        [(--trailer <symbol>[(=|:)<värde>])...]\\n\"\n> >  \"        <taggnamn> [<incheckning> | <objekt>]\"\n> >\n> >  msgid \"git tag -d <tagname>...\"\n> > diff --git a/po/tr.po b/po/tr.po\n> > index 0e220e1c1cd..8c506c34a9e 100644\n> > --- a/po/tr.po\n> > +++ b/po/tr.po\n> > @@ -12975,10 +12975,12 @@ msgstr \"güncelleme nedeni\"\n> >\n> >  msgid \"\"\n> >  \"git tag [-a | -s | -u <key-id>] [-f] [-m <msg> | -F <file>] [-e]\\n\"\n> > +\"        [(--trailer <token>[(=|:)<value>])...]\\n\"\n> >  \"        <tagname> [<commit> | <object>]\"\n> >  msgstr \"\"\n> >  \"git tag [-a | -s | -u <anahtar-kimliği>] [-f] [-m <ileti> | -F <dosya>] [-\"\n> >  \"e]\\n\"\n> > +\"        [(--trailer <jeton>[(=|:)<değer>])...]\\n\"\n> >  \"        <etiket-adı> [<işleme> | <nesne>]\"\n> >\n> >  msgid \"git tag -d <tagname>...\"\n> > diff --git a/po/uk.po b/po/uk.po\n> > index 528a3dc6f76..e3b6441e3c1 100644\n> > --- a/po/uk.po\n> > +++ b/po/uk.po\n> > @@ -13086,10 +13086,12 @@ msgstr \"причина оновлення\"\n> >\n> >  msgid \"\"\n> >  \"git tag [-a | -s | -u <key-id>] [-f] [-m <msg> | -F <file>] [-e]\\n\"\n> > +\"        [(--trailer <token>[(=|:)<value>])...]\\n\"\n> >  \"        <tagname> [<commit> | <object>]\"\n> >  msgstr \"\"\n> >  \"git tag [-a | -s | -u <ідентифікатор-ключа>] [-f] [-m <допис> | -F <файл>] [-\"\n> >  \"e]\\n\"\n> > +\"        [(--trailer <токен>[(=|:)<значення>])...]\\n\"\n> >  \"        <назва-тегу> [<коміт> | <об’єкт>]\"\n> >\n> >  msgid \"git tag -d <tagname>...\"\n> > diff --git a/po/vi.po b/po/vi.po\n> > index 965e79e9658..0b206309f9b 100644\n> > --- a/po/vi.po\n> > +++ b/po/vi.po\n> > @@ -12915,9 +12915,11 @@ msgstr \"lý do cập nhật\"\n> >\n> >  msgid \"\"\n> >  \"git tag [-a | -s | -u <key-id>] [-f] [-m <msg> | -F <file>] [-e]\\n\"\n> > +\"        [(--trailer <token>[(=|:)<value>])...]\\n\"\n> >  \"        <tagname> [<commit> | <object>]\"\n> >  msgstr \"\"\n> >  \"git tag [-a | -s | -u <key-id>] [-f] [-m <msg> | -F <tập-tin>] [-e]\\n\"\n> > +\"        [(--trailer <thẻ>[(=|:)<giá-trị>])...]\\n\"\n> >  \"        <tên-thẻ> [<lần chuyển giao> | <đối tượng> ]\"\n> >\n> >  msgid \"git tag -d <tagname>...\"\n> > diff --git a/po/zh_CN.po b/po/zh_CN.po\n> > index 4838c19b0be..aaedddcd864 100644\n> > --- a/po/zh_CN.po\n> > +++ b/po/zh_CN.po\n> > @@ -16033,9 +16033,11 @@ msgstr \"更新的原因\"\n> >  #: builtin/tag.c\n> >  msgid \"\"\n> >  \"git tag [-a | -s | -u <key-id>] [-f] [-m <msg> | -F <file>] [-e]\\n\"\n> > +\"        [(--trailer <token>[(=|:)<value>])...]\\n\"\n> >  \"        <tagname> [<commit> | <object>]\"\n> >  msgstr \"\"\n> >  \"git tag [-a | -s | -u <私钥 ID>] [-f] [-m <消息> | -F <文件>] [-e]\\n\"\n> > +\"        [(--trailer <键>[(=|:)<值>])...]\\n\"\n> >  \"        <标签名> [<提交> | <对象>]\"\n> >\n> >  #: builtin/tag.c\n> > diff --git a/po/zh_TW.po b/po/zh_TW.po\n> > index f554381a7af..168591c141e 100644\n> > --- a/po/zh_TW.po\n> > +++ b/po/zh_TW.po\n> > @@ -15911,9 +15911,11 @@ msgstr \"更新的原因\"\n> >  #: builtin/tag.c\n> >  msgid \"\"\n> >  \"git tag [-a | -s | -u <key-id>] [-f] [-m <msg> | -F <file>] [-e]\\n\"\n> > +\"        [(--trailer <token>[(=|:)<value>])...]\\n\"\n> >  \"        <tagname> [<commit> | <object>]\"\n> >  msgstr \"\"\n> >  \"git tag [-a | -s | -u <key-id>] [-f] [-m <msg> | -F <file>] [-e]\\n\"\n> > +\"        [(--trailer <token>[(=|:)<value>])...]\\n\"\n> >  \"        <tagname> [<commit> | <object>]\"\n> >\n> >  #: builtin/tag.c\n"},{"id":"493731","messageId":"ZjCHiPS8N-eSBrQV@tanuki","threadId":"61376","inReplyTo":"0c9517f434aa5456dbde129f0514e3e3f50a095d.1714416865.git.gitgitgadget@gmail.com","subject":"Re: [PATCH v3 1/3] builtin/commit.c: refactor --trailer logic","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2024-04-30T05:54:16Z","receivedAt":"2024-04-30T05:54:24Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"On Mon, Apr 29, 2024 at 06:54:21PM +0000, John Passaro via GitGitGadget wrote:\n> From: John Passaro <john.a.passaro@gmail.com>\n> \n> git-commit adds user trailers to the commit message by passing its\n> `--trailer` arguments to a child process running `git-interpret-trailers\n> --in-place`. This logic is broadly useful, not just for git-commit but\n> for other commands constructing message bodies (e.g. git-tag).\n> \n> Let's move this logic from git-commit to a new function in the trailer\n> API, so that it can be re-used in other commands.\n> \n> Additionally, replace git-commit's bespoke callback for --trailer with\n> the standard OPT_PASSTHRU_ARGV macro. This bespoke callback was only\n> adding its values to a strvec and sanity-checking that `unset` is always\n> false; both of these are already implemented in the parse-option API.\n> \n> Signed-off-by: John Passaro <john.a.passaro@gmail.com>\n> Helped-by: Patrick Steinhardt <ps@pks.im>\n> Helped-by: Junio C Hamano <gitster@pobox.com>\n\nYour signed-off-by should always go last.\n\n> ---\n>  builtin/commit.c | 20 +++-----------------\n>  trailer.c        | 12 ++++++++++++\n>  trailer.h        |  8 ++++++++\n>  3 files changed, 23 insertions(+), 17 deletions(-)\n> \n> diff --git a/builtin/commit.c b/builtin/commit.c\n> index 6e1484446b0..63cd090b6f2 100644\n> --- a/builtin/commit.c\n> +++ b/builtin/commit.c\n> @@ -38,6 +38,7 @@\n>  #include \"commit-reach.h\"\n>  #include \"commit-graph.h\"\n>  #include \"pretty.h\"\n> +#include \"trailer.h\"\n>  \n>  static const char * const builtin_commit_usage[] = {\n>  \tN_(\"git commit [-a | --interactive | --patch] [-s] [-v] [-u<mode>] [--amend]\\n\"\n> @@ -142,14 +143,6 @@ static struct strbuf message = STRBUF_INIT;\n>  \n>  static enum wt_status_format status_format = STATUS_FORMAT_UNSPECIFIED;\n>  \n> -static int opt_pass_trailer(const struct option *opt, const char *arg, int unset)\n> -{\n> -\tBUG_ON_OPT_NEG(unset);\n> -\n> -\tstrvec_pushl(opt->value, \"--trailer\", arg, NULL);\n> -\treturn 0;\n> -}\n> -\n\nNice to see this gone. I would have moved this refactoring into a\nseparate commit because it is completely unrelated to the new trailer\nfunction that you're introducing.\n\n>  static int opt_parse_porcelain(const struct option *opt, const char *arg, int unset)\n>  {\n>  \tenum wt_status_format *value = (enum wt_status_format *)opt->value;\n> @@ -1038,14 +1031,7 @@ static int prepare_to_commit(const char *index_file, const char *prefix,\n>  \tfclose(s->fp);\n>  \n>  \tif (trailer_args.nr) {\n> -\t\tstruct child_process run_trailer = CHILD_PROCESS_INIT;\n> -\n> -\t\tstrvec_pushl(&run_trailer.args, \"interpret-trailers\",\n> -\t\t\t     \"--in-place\", \"--no-divider\",\n> -\t\t\t     git_path_commit_editmsg(), NULL);\n> -\t\tstrvec_pushv(&run_trailer.args, trailer_args.v);\n> -\t\trun_trailer.git_cmd = 1;\n> -\t\tif (run_command(&run_trailer))\n> +\t\tif (amend_file_with_trailers(git_path_commit_editmsg(), &trailer_args))\n>  \t\t\tdie(_(\"unable to pass trailers to --trailers\"));\n>  \t\tstrvec_clear(&trailer_args);\n>  \t}\n> @@ -1673,7 +1659,7 @@ int cmd_commit(int argc, const char **argv, const char *prefix)\n>  \t\tOPT_STRING(0, \"fixup\", &fixup_message, N_(\"[(amend|reword):]commit\"), N_(\"use autosquash formatted message to fixup or amend/reword specified commit\")),\n>  \t\tOPT_STRING(0, \"squash\", &squash_message, N_(\"commit\"), N_(\"use autosquash formatted message to squash specified commit\")),\n>  \t\tOPT_BOOL(0, \"reset-author\", &renew_authorship, N_(\"the commit is authored by me now (used with -C/-c/--amend)\")),\n> -\t\tOPT_CALLBACK_F(0, \"trailer\", &trailer_args, N_(\"trailer\"), N_(\"add custom trailer(s)\"), PARSE_OPT_NONEG, opt_pass_trailer),\n> +\t\tOPT_PASSTHRU_ARGV(0, \"trailer\", &trailer_args, N_(\"trailer\"), N_(\"add custom trailer(s)\"), PARSE_OPT_NONEG),\n>  \t\tOPT_BOOL('s', \"signoff\", &signoff, N_(\"add a Signed-off-by trailer\")),\n>  \t\tOPT_FILENAME('t', \"template\", &template_file, N_(\"use specified template file\")),\n>  \t\tOPT_BOOL('e', \"edit\", &edit_flag, N_(\"force edit of commit\")),\n> diff --git a/trailer.c b/trailer.c\n> index c72ae687099..843c378199e 100644\n> --- a/trailer.c\n> +++ b/trailer.c\n> @@ -7,6 +7,7 @@\n>  #include \"commit.h\"\n>  #include \"trailer.h\"\n>  #include \"list.h\"\n> +#include \"run-command.h\"\n>  /*\n>   * Copyright (c) 2013, 2014 Christian Couder <chriscool@tuxfamily.org>\n>   */\n> @@ -1170,3 +1171,14 @@ void trailer_iterator_release(struct trailer_iterator *iter)\n>  \tstrbuf_release(&iter->val);\n>  \tstrbuf_release(&iter->key);\n>  }\n> +\n> +int amend_file_with_trailers(const char *path, struct strvec const* trailer_args) {\n\nI would have called this `amend_trailers_to_file()`, which feels a bit\neasier to understand. But I don't mind this much, your version should be\nokay, too.\n\nIn any case, the second argument should be `const struct strvec *`. For\none, the `const` should come first. Second, the `*` always sticks to the\nvariable name in our codebase.\n\n> +\tstruct child_process run_trailer = CHILD_PROCESS_INIT;\n> +\n> +\trun_trailer.git_cmd = 1;\n> +\tstrvec_pushl(&run_trailer.args, \"interpret-trailers\",\n> +\t\t     \"--in-place\", \"--no-divider\",\n> +\t\t     path, NULL);\n> +\tstrvec_pushv(&run_trailer.args, trailer_args->v);\n> +\treturn run_command(&run_trailer);\n> +}\n> diff --git a/trailer.h b/trailer.h\n> index 9f42aa75994..55f85b008ee 100644\n> --- a/trailer.h\n> +++ b/trailer.h\n> @@ -3,6 +3,7 @@\n>  \n>  #include \"list.h\"\n>  #include \"strbuf.h\"\n> +#include \"strvec.h\"\n\nArguably you don't have to include \"strvec.h\" here, but can instead add\na simple forward declaration `struct strvec`.\n\n>  enum trailer_where {\n>  \tWHERE_DEFAULT,\n> @@ -158,4 +159,11 @@ int trailer_iterator_advance(struct trailer_iterator *iter);\n>   */\n>  void trailer_iterator_release(struct trailer_iterator *iter);\n>  \n> +/*\n> + * Augment a file to add trailers to it by running git-interpret-trailers.\n> + * This calls run_command() and its return value is the same (i.e. 0 for\n> + * success, various non-zero for other errors). See run-command.h.\n> + */\n> +int amend_file_with_trailers(const char *path, struct strvec const* trailer_args);\n\nSame comments here regarding the second argument.\n\nPatrick\n"},{"id":"493732","messageId":"ZjCHju27OJTm8V1k@tanuki","threadId":"61376","inReplyTo":"5b6239167b8d7c26f96e5c23d0d82b7a3a9b01fe.1714416865.git.gitgitgadget@gmail.com","subject":"Re: [PATCH v3 2/3] builtin/tag.c: add --trailer arg","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2024-04-30T05:54:22Z","receivedAt":"2024-04-30T05:54:27Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"On Mon, Apr 29, 2024 at 06:54:22PM +0000, John Passaro via GitGitGadget wrote:\n> From: John Passaro <john.a.passaro@gmail.com>\n> \n> git-tag currently supports interpreting trailers from an annotated tag\n> message, using --list --format=\"%(trailers)\". There is no ergonomic way\n> to add trailers to an annotated tag message.\n> \n> In a previous patch, we refactored git-commit's implementation of its\n> --trailer arg to the trailer.h API. Let's use that new function to teach\n> git-tag the same --trailer argument, emulating as much of git-commit's\n> behavior as much as possible.\n> \n> Signed-off-by: John Passaro <john.a.passaro@gmail.com>\n> Helped-by: Patrick Steinhardt <ps@pks.im>\n\nSame comment here regarding the order of trailers.\n\nOther than that this commit looks good to me, thanks!\n\nPatrick\n"},{"id":"493780","messageId":"pull.1723.v4.git.1714488111.gitgitgadget@gmail.com","threadId":"61376","inReplyTo":"pull.1723.v3.git.1714416863.gitgitgadget@gmail.com","subject":"[PATCH v4 0/3] builtin/tag.c: add --trailer option","fromName":"John Passaro via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2024-04-30T14:41:48Z","receivedAt":"2024-04-30T14:41:57Z","isPatch":true,"sender":{"key":"john.a.passaro@gmail.com","avatar":"https://avatars.githubusercontent.com/u/6754005?v=4"},"body":"4th follow-up patch taking welcome feedback from Patrick and JCH. Net new\nchanges include separating from a 2-patch series to 3.\n\nSince git-tag --list --format=\"%(trailers)\" can interpret trailers from\nannotated tag messages, it seems natural to support --trailer when writing a\nnew tag message.\n\ngit-commit accomplishes this by taking --trailer arguments and passing them\nto git-interpret-trailer. This patch series refactors that logic and uses it\nto implement --trailer on git-tag.\n\nJohn Passaro (3):\n  builtin/commit.c: remove bespoke option callback\n  builtin/commit.c: refactor --trailer logic\n  builtin/tag.c: add --trailer option\n\n Documentation/git-tag.txt |  18 +++++-\n builtin/commit.c          |  20 +------\n builtin/tag.c             |  41 +++++++++++---\n t/t7004-tag.sh            | 114 ++++++++++++++++++++++++++++++++++++++\n trailer.c                 |  11 ++++\n trailer.h                 |   9 +++\n 6 files changed, 185 insertions(+), 28 deletions(-)\n\n\nbase-commit: 786a3e4b8d754d2b14b1208b98eeb0a554ef19a8\nPublished-As: https://github.com/gitgitgadget/git/releases/tag/pr-1723%2Fjpassaro%2Fjp%2Ftag-trailer-arg-v4\nFetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-1723/jpassaro/jp/tag-trailer-arg-v4\nPull-Request: https://github.com/gitgitgadget/git/pull/1723\n\nRange-diff vs v3:\n\n -:  ----------- > 1:  ce047c58aa8 builtin/commit.c: remove bespoke option callback\n 1:  0c9517f434a ! 2:  8f53a54bbfe builtin/commit.c: refactor --trailer logic\n     @@ Commit message\n          Let's move this logic from git-commit to a new function in the trailer\n          API, so that it can be re-used in other commands.\n      \n     -    Additionally, replace git-commit's bespoke callback for --trailer with\n     -    the standard OPT_PASSTHRU_ARGV macro. This bespoke callback was only\n     -    adding its values to a strvec and sanity-checking that `unset` is always\n     -    false; both of these are already implemented in the parse-option API.\n     -\n     -    Signed-off-by: John Passaro <john.a.passaro@gmail.com>\n          Helped-by: Patrick Steinhardt <ps@pks.im>\n          Helped-by: Junio C Hamano <gitster@pobox.com>\n     +    Signed-off-by: John Passaro <john.a.passaro@gmail.com>\n      \n       ## builtin/commit.c ##\n      @@\n     @@ builtin/commit.c\n       \n       static const char * const builtin_commit_usage[] = {\n       \tN_(\"git commit [-a | --interactive | --patch] [-s] [-v] [-u<mode>] [--amend]\\n\"\n     -@@ builtin/commit.c: static struct strbuf message = STRBUF_INIT;\n     - \n     - static enum wt_status_format status_format = STATUS_FORMAT_UNSPECIFIED;\n     - \n     --static int opt_pass_trailer(const struct option *opt, const char *arg, int unset)\n     --{\n     --\tBUG_ON_OPT_NEG(unset);\n     --\n     --\tstrvec_pushl(opt->value, \"--trailer\", arg, NULL);\n     --\treturn 0;\n     --}\n     --\n     - static int opt_parse_porcelain(const struct option *opt, const char *arg, int unset)\n     - {\n     - \tenum wt_status_format *value = (enum wt_status_format *)opt->value;\n      @@ builtin/commit.c: static int prepare_to_commit(const char *index_file, const char *prefix,\n       \tfclose(s->fp);\n       \n     @@ builtin/commit.c: static int prepare_to_commit(const char *index_file, const cha\n       \t\t\tdie(_(\"unable to pass trailers to --trailers\"));\n       \t\tstrvec_clear(&trailer_args);\n       \t}\n     -@@ builtin/commit.c: int cmd_commit(int argc, const char **argv, const char *prefix)\n     - \t\tOPT_STRING(0, \"fixup\", &fixup_message, N_(\"[(amend|reword):]commit\"), N_(\"use autosquash formatted message to fixup or amend/reword specified commit\")),\n     - \t\tOPT_STRING(0, \"squash\", &squash_message, N_(\"commit\"), N_(\"use autosquash formatted message to squash specified commit\")),\n     - \t\tOPT_BOOL(0, \"reset-author\", &renew_authorship, N_(\"the commit is authored by me now (used with -C/-c/--amend)\")),\n     --\t\tOPT_CALLBACK_F(0, \"trailer\", &trailer_args, N_(\"trailer\"), N_(\"add custom trailer(s)\"), PARSE_OPT_NONEG, opt_pass_trailer),\n     -+\t\tOPT_PASSTHRU_ARGV(0, \"trailer\", &trailer_args, N_(\"trailer\"), N_(\"add custom trailer(s)\"), PARSE_OPT_NONEG),\n     - \t\tOPT_BOOL('s', \"signoff\", &signoff, N_(\"add a Signed-off-by trailer\")),\n     - \t\tOPT_FILENAME('t', \"template\", &template_file, N_(\"use specified template file\")),\n     - \t\tOPT_BOOL('e', \"edit\", &edit_flag, N_(\"force edit of commit\")),\n      \n       ## trailer.c ##\n     -@@\n     - #include \"commit.h\"\n     - #include \"trailer.h\"\n     - #include \"list.h\"\n     -+#include \"run-command.h\"\n     - /*\n     -  * Copyright (c) 2013, 2014 Christian Couder <chriscool@tuxfamily.org>\n     -  */\n      @@ trailer.c: void trailer_iterator_release(struct trailer_iterator *iter)\n       \tstrbuf_release(&iter->val);\n       \tstrbuf_release(&iter->key);\n       }\n      +\n     -+int amend_file_with_trailers(const char *path, struct strvec const* trailer_args) {\n     ++int amend_file_with_trailers(const char *path, const struct strvec *trailer_args) {\n      +\tstruct child_process run_trailer = CHILD_PROCESS_INIT;\n      +\n      +\trun_trailer.git_cmd = 1;\n     @@ trailer.c: void trailer_iterator_release(struct trailer_iterator *iter)\n      \n       ## trailer.h ##\n      @@\n     - \n       #include \"list.h\"\n       #include \"strbuf.h\"\n     -+#include \"strvec.h\"\n       \n     ++struct strvec;\n     ++\n       enum trailer_where {\n       \tWHERE_DEFAULT,\n     + \tWHERE_END,\n      @@ trailer.h: int trailer_iterator_advance(struct trailer_iterator *iter);\n        */\n       void trailer_iterator_release(struct trailer_iterator *iter);\n     @@ trailer.h: int trailer_iterator_advance(struct trailer_iterator *iter);\n      + * This calls run_command() and its return value is the same (i.e. 0 for\n      + * success, various non-zero for other errors). See run-command.h.\n      + */\n     -+int amend_file_with_trailers(const char *path, struct strvec const* trailer_args);\n     ++int amend_file_with_trailers(const char *path, const struct strvec *trailer_args);\n      +\n       #endif /* TRAILER_H */\n 2:  5b6239167b8 ! 3:  f1d68337eda builtin/tag.c: add --trailer arg\n     @@ Metadata\n      Author: John Passaro <john.a.passaro@gmail.com>\n      \n       ## Commit message ##\n     -    builtin/tag.c: add --trailer arg\n     +    builtin/tag.c: add --trailer option\n      \n          git-tag currently supports interpreting trailers from an annotated tag\n     -    message, using --list --format=\"%(trailers)\". There is no ergonomic way\n     -    to add trailers to an annotated tag message.\n     +    message, using --list --format=\"%(trailers)\". However, to add a trailer\n     +    to a tag message, you must do so using `-F` or by editing the message.\n      \n     -    In a previous patch, we refactored git-commit's implementation of its\n     -    --trailer arg to the trailer.h API. Let's use that new function to teach\n     -    git-tag the same --trailer argument, emulating as much of git-commit's\n     -    behavior as much as possible.\n     +    In a previous patch, we moved git-commit's implementation of its\n     +    --trailer option to the trailer.h API. Let's use that new function to\n     +    teach git-tag the same --trailer option, emulating as much of\n     +    git-commit's behavior as much as possible.\n      \n     -    Signed-off-by: John Passaro <john.a.passaro@gmail.com>\n          Helped-by: Patrick Steinhardt <ps@pks.im>\n     +    Signed-off-by: John Passaro <john.a.passaro@gmail.com>\n      \n       ## Documentation/git-tag.txt ##\n      @@ Documentation/git-tag.txt: SYNOPSIS\n 3:  d5335e30b0b < -:  ----------- po: update git-tag translations\n\n-- \ngitgitgadget\n"},{"id":"493781","messageId":"ce047c58aa81cc5228ae9068d790585e012491c5.1714488111.git.gitgitgadget@gmail.com","threadId":"61376","inReplyTo":"pull.1723.v4.git.1714488111.gitgitgadget@gmail.com","subject":"[PATCH v4 1/3] builtin/commit.c: remove bespoke option callback","fromName":"John Passaro via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2024-04-30T14:41:49Z","receivedAt":"2024-04-30T14:41:58Z","isPatch":true,"sender":{"key":"john.a.passaro@gmail.com","avatar":"https://avatars.githubusercontent.com/u/6754005?v=4"},"body":"From: John Passaro <john.a.passaro@gmail.com>\n\nReplace git-commit's bespoke callback for --trailer with the standard\nOPT_PASSTHRU_ARGV macro. The bespoke callback was only adding its values\nto a strvec and sanity-checking that `unset` is always false; both of\nthese are already implemented in the parse-option API.\n\nSigned-off-by: John Passaro <john.a.passaro@gmail.com>\n---\n builtin/commit.c | 10 +---------\n 1 file changed, 1 insertion(+), 9 deletions(-)\n\ndiff --git a/builtin/commit.c b/builtin/commit.c\nindex 6e1484446b0..5a3248370db 100644\n--- a/builtin/commit.c\n+++ b/builtin/commit.c\n@@ -142,14 +142,6 @@ static struct strbuf message = STRBUF_INIT;\n \n static enum wt_status_format status_format = STATUS_FORMAT_UNSPECIFIED;\n \n-static int opt_pass_trailer(const struct option *opt, const char *arg, int unset)\n-{\n-\tBUG_ON_OPT_NEG(unset);\n-\n-\tstrvec_pushl(opt->value, \"--trailer\", arg, NULL);\n-\treturn 0;\n-}\n-\n static int opt_parse_porcelain(const struct option *opt, const char *arg, int unset)\n {\n \tenum wt_status_format *value = (enum wt_status_format *)opt->value;\n@@ -1673,7 +1665,7 @@ int cmd_commit(int argc, const char **argv, const char *prefix)\n \t\tOPT_STRING(0, \"fixup\", &fixup_message, N_(\"[(amend|reword):]commit\"), N_(\"use autosquash formatted message to fixup or amend/reword specified commit\")),\n \t\tOPT_STRING(0, \"squash\", &squash_message, N_(\"commit\"), N_(\"use autosquash formatted message to squash specified commit\")),\n \t\tOPT_BOOL(0, \"reset-author\", &renew_authorship, N_(\"the commit is authored by me now (used with -C/-c/--amend)\")),\n-\t\tOPT_CALLBACK_F(0, \"trailer\", &trailer_args, N_(\"trailer\"), N_(\"add custom trailer(s)\"), PARSE_OPT_NONEG, opt_pass_trailer),\n+\t\tOPT_PASSTHRU_ARGV(0, \"trailer\", &trailer_args, N_(\"trailer\"), N_(\"add custom trailer(s)\"), PARSE_OPT_NONEG),\n \t\tOPT_BOOL('s', \"signoff\", &signoff, N_(\"add a Signed-off-by trailer\")),\n \t\tOPT_FILENAME('t', \"template\", &template_file, N_(\"use specified template file\")),\n \t\tOPT_BOOL('e', \"edit\", &edit_flag, N_(\"force edit of commit\")),\n-- \ngitgitgadget\n\n"},{"id":"493782","messageId":"8f53a54bbfe9a952ae5e86216681eef2a2e916eb.1714488111.git.gitgitgadget@gmail.com","threadId":"61376","inReplyTo":"pull.1723.v4.git.1714488111.gitgitgadget@gmail.com","subject":"[PATCH v4 2/3] builtin/commit.c: refactor --trailer logic","fromName":"John Passaro via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2024-04-30T14:41:50Z","receivedAt":"2024-04-30T14:41:59Z","isPatch":true,"sender":{"key":"john.a.passaro@gmail.com","avatar":"https://avatars.githubusercontent.com/u/6754005?v=4"},"body":"From: John Passaro <john.a.passaro@gmail.com>\n\ngit-commit adds user trailers to the commit message by passing its\n`--trailer` arguments to a child process running `git-interpret-trailers\n--in-place`. This logic is broadly useful, not just for git-commit but\nfor other commands constructing message bodies (e.g. git-tag).\n\nLet's move this logic from git-commit to a new function in the trailer\nAPI, so that it can be re-used in other commands.\n\nHelped-by: Patrick Steinhardt <ps@pks.im>\nHelped-by: Junio C Hamano <gitster@pobox.com>\nSigned-off-by: John Passaro <john.a.passaro@gmail.com>\n---\n builtin/commit.c | 10 ++--------\n trailer.c        | 11 +++++++++++\n trailer.h        |  9 +++++++++\n 3 files changed, 22 insertions(+), 8 deletions(-)\n\ndiff --git a/builtin/commit.c b/builtin/commit.c\nindex 5a3248370db..63cd090b6f2 100644\n--- a/builtin/commit.c\n+++ b/builtin/commit.c\n@@ -38,6 +38,7 @@\n #include \"commit-reach.h\"\n #include \"commit-graph.h\"\n #include \"pretty.h\"\n+#include \"trailer.h\"\n \n static const char * const builtin_commit_usage[] = {\n \tN_(\"git commit [-a | --interactive | --patch] [-s] [-v] [-u<mode>] [--amend]\\n\"\n@@ -1030,14 +1031,7 @@ static int prepare_to_commit(const char *index_file, const char *prefix,\n \tfclose(s->fp);\n \n \tif (trailer_args.nr) {\n-\t\tstruct child_process run_trailer = CHILD_PROCESS_INIT;\n-\n-\t\tstrvec_pushl(&run_trailer.args, \"interpret-trailers\",\n-\t\t\t     \"--in-place\", \"--no-divider\",\n-\t\t\t     git_path_commit_editmsg(), NULL);\n-\t\tstrvec_pushv(&run_trailer.args, trailer_args.v);\n-\t\trun_trailer.git_cmd = 1;\n-\t\tif (run_command(&run_trailer))\n+\t\tif (amend_file_with_trailers(git_path_commit_editmsg(), &trailer_args))\n \t\t\tdie(_(\"unable to pass trailers to --trailers\"));\n \t\tstrvec_clear(&trailer_args);\n \t}\ndiff --git a/trailer.c b/trailer.c\nindex c72ae687099..ae0597d919e 100644\n--- a/trailer.c\n+++ b/trailer.c\n@@ -1170,3 +1170,14 @@ void trailer_iterator_release(struct trailer_iterator *iter)\n \tstrbuf_release(&iter->val);\n \tstrbuf_release(&iter->key);\n }\n+\n+int amend_file_with_trailers(const char *path, const struct strvec *trailer_args) {\n+\tstruct child_process run_trailer = CHILD_PROCESS_INIT;\n+\n+\trun_trailer.git_cmd = 1;\n+\tstrvec_pushl(&run_trailer.args, \"interpret-trailers\",\n+\t\t     \"--in-place\", \"--no-divider\",\n+\t\t     path, NULL);\n+\tstrvec_pushv(&run_trailer.args, trailer_args->v);\n+\treturn run_command(&run_trailer);\n+}\ndiff --git a/trailer.h b/trailer.h\nindex 9f42aa75994..c364405267a 100644\n--- a/trailer.h\n+++ b/trailer.h\n@@ -4,6 +4,8 @@\n #include \"list.h\"\n #include \"strbuf.h\"\n \n+struct strvec;\n+\n enum trailer_where {\n \tWHERE_DEFAULT,\n \tWHERE_END,\n@@ -158,4 +160,11 @@ int trailer_iterator_advance(struct trailer_iterator *iter);\n  */\n void trailer_iterator_release(struct trailer_iterator *iter);\n \n+/*\n+ * Augment a file to add trailers to it by running git-interpret-trailers.\n+ * This calls run_command() and its return value is the same (i.e. 0 for\n+ * success, various non-zero for other errors). See run-command.h.\n+ */\n+int amend_file_with_trailers(const char *path, const struct strvec *trailer_args);\n+\n #endif /* TRAILER_H */\n-- \ngitgitgadget\n\n"},{"id":"493783","messageId":"f1d68337edafd069ea9fbd0e0b834444dab655fd.1714488112.git.gitgitgadget@gmail.com","threadId":"61376","inReplyTo":"pull.1723.v4.git.1714488111.gitgitgadget@gmail.com","subject":"[PATCH v4 3/3] builtin/tag.c: add --trailer option","fromName":"John Passaro via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2024-04-30T14:41:51Z","receivedAt":"2024-04-30T14:42:00Z","isPatch":true,"sender":{"key":"john.a.passaro@gmail.com","avatar":"https://avatars.githubusercontent.com/u/6754005?v=4"},"body":"From: John Passaro <john.a.passaro@gmail.com>\n\ngit-tag currently supports interpreting trailers from an annotated tag\nmessage, using --list --format=\"%(trailers)\". However, to add a trailer\nto a tag message, you must do so using `-F` or by editing the message.\n\nIn a previous patch, we moved git-commit's implementation of its\n--trailer option to the trailer.h API. Let's use that new function to\nteach git-tag the same --trailer option, emulating as much of\ngit-commit's behavior as much as possible.\n\nHelped-by: Patrick Steinhardt <ps@pks.im>\nSigned-off-by: John Passaro <john.a.passaro@gmail.com>\n---\n Documentation/git-tag.txt |  18 +++++-\n builtin/tag.c             |  41 +++++++++++---\n t/t7004-tag.sh            | 114 ++++++++++++++++++++++++++++++++++++++\n 3 files changed, 162 insertions(+), 11 deletions(-)\n\ndiff --git a/Documentation/git-tag.txt b/Documentation/git-tag.txt\nindex 5fe519c31ec..79b0a7e9644 100644\n--- a/Documentation/git-tag.txt\n+++ b/Documentation/git-tag.txt\n@@ -10,6 +10,7 @@ SYNOPSIS\n --------\n [verse]\n 'git tag' [-a | -s | -u <key-id>] [-f] [-m <msg> | -F <file>] [-e]\n+\t[(--trailer <token>[(=|:)<value>])...]\n \t<tagname> [<commit> | <object>]\n 'git tag' -d <tagname>...\n 'git tag' [-n[<num>]] -l [--contains <commit>] [--no-contains <commit>]\n@@ -31,8 +32,8 @@ creates a 'tag' object, and requires a tag message.  Unless\n `-m <msg>` or `-F <file>` is given, an editor is started for the user to type\n in the tag message.\n \n-If `-m <msg>` or `-F <file>` is given and `-a`, `-s`, and `-u <key-id>`\n-are absent, `-a` is implied.\n+If `-m <msg>` or `-F <file>` or `--trailer <token>[=<value>]` is given\n+and `-a`, `-s`, and `-u <key-id>` are absent, `-a` is implied.\n \n Otherwise, a tag reference that points directly at the given object\n (i.e., a lightweight tag) is created.\n@@ -178,6 +179,19 @@ This option is only applicable when listing tags without annotation lines.\n \tImplies `-a` if none of `-a`, `-s`, or `-u <key-id>`\n \tis given.\n \n+--trailer <token>[(=|:)<value>]::\n+\tSpecify a (<token>, <value>) pair that should be applied as a\n+\ttrailer. (e.g. `git tag --trailer \"Signed-off-by:T A Ger \\\n+\t<tagger@example.com>\" --trailer \"Helped-by:C O Mitter \\\n+\t<committer@example.com>\"` will add the \"Signed-off-by\" trailer\n+\tand the \"Helped-by\" trailer to the tag message.)\n+\tThe `trailer.*` configuration variables\n+\t(linkgit:git-interpret-trailers[1]) can be used to define if\n+\ta duplicated trailer is omitted, where in the run of trailers\n+\teach trailer would appear, and other details.\n+\tThe trailers can be seen in `git tag --list` using\n+\t`--format=\"%(trailers)\"` placeholder.\n+\n -e::\n --edit::\n \tThe message taken from file with `-F` and command line with\ndiff --git a/builtin/tag.c b/builtin/tag.c\nindex 9a33cb50b45..1ae7ea50b3f 100644\n--- a/builtin/tag.c\n+++ b/builtin/tag.c\n@@ -28,9 +28,11 @@\n #include \"date.h\"\n #include \"write-or-die.h\"\n #include \"object-file-convert.h\"\n+#include \"trailer.h\"\n \n static const char * const git_tag_usage[] = {\n \tN_(\"git tag [-a | -s | -u <key-id>] [-f] [-m <msg> | -F <file>] [-e]\\n\"\n+\t   \"        [(--trailer <token>[(=|:)<value>])...]\\n\"\n \t   \"        <tagname> [<commit> | <object>]\"),\n \tN_(\"git tag -d <tagname>...\"),\n \tN_(\"git tag [-n[<num>]] -l [--contains <commit>] [--no-contains <commit>]\\n\"\n@@ -290,10 +292,12 @@ static const char message_advice_nested_tag[] =\n static void create_tag(const struct object_id *object, const char *object_ref,\n \t\t       const char *tag,\n \t\t       struct strbuf *buf, struct create_tag_options *opt,\n-\t\t       struct object_id *prev, struct object_id *result, char *path)\n+\t\t       struct object_id *prev, struct object_id *result,\n+\t\t       struct strvec *trailer_args, char *path)\n {\n \tenum object_type type;\n \tstruct strbuf header = STRBUF_INIT;\n+\tint should_edit;\n \n \ttype = oid_object_info(the_repository, object, NULL);\n \tif (type <= OBJ_NONE)\n@@ -313,13 +317,15 @@ static void create_tag(const struct object_id *object, const char *object_ref,\n \t\t    tag,\n \t\t    git_committer_info(IDENT_STRICT));\n \n-\tif (!opt->message_given || opt->use_editor) {\n+\tshould_edit = opt->use_editor || !opt->message_given;\n+\tif (should_edit || trailer_args->nr) {\n \t\tint fd;\n \n \t\t/* write the template message before editing: */\n \t\tfd = xopen(path, O_CREAT | O_TRUNC | O_WRONLY, 0600);\n \n-\t\tif (opt->message_given) {\n+\t\tif (opt->message_given && buf->len) {\n+\t\t\tstrbuf_complete(buf, '\\n');\n \t\t\twrite_or_die(fd, buf->buf, buf->len);\n \t\t\tstrbuf_reset(buf);\n \t\t} else if (!is_null_oid(prev)) {\n@@ -338,10 +344,22 @@ static void create_tag(const struct object_id *object, const char *object_ref,\n \t\t}\n \t\tclose(fd);\n \n-\t\tif (launch_editor(path, buf, NULL)) {\n-\t\t\tfprintf(stderr,\n-\t\t\t_(\"Please supply the message using either -m or -F option.\\n\"));\n-\t\t\texit(1);\n+\t\tif (trailer_args->nr && amend_file_with_trailers(path, trailer_args))\n+\t\t\tdie(_(\"unable to pass trailers to --trailers\"));\n+\n+\t\tif (should_edit) {\n+\t\t\tif (launch_editor(path, buf, NULL)) {\n+\t\t\t\tfprintf(stderr,\n+\t\t\t\t\t_(\"Please supply the message using either -m or -F option.\\n\"));\n+\t\t\t\texit(1);\n+\t\t\t}\n+\t\t} else if (trailer_args->nr) {\n+\t\t\tstrbuf_reset(buf);\n+\t\t\tif (strbuf_read_file(buf, path, 0) < 0) {\n+\t\t\t\tfprintf(stderr,\n+\t\t\t\t\t_(\"Please supply the message using either -m or -F option.\\n\"));\n+\t\t\t\texit(1);\n+\t\t\t}\n \t\t}\n \t}\n \n@@ -463,6 +481,7 @@ int cmd_tag(int argc, const char **argv, const char *prefix)\n \tstruct ref_sorting *sorting;\n \tstruct string_list sorting_options = STRING_LIST_INIT_DUP;\n \tstruct ref_format format = REF_FORMAT_INIT;\n+\tstruct strvec trailer_args = STRVEC_INIT;\n \tint icase = 0;\n \tint edit_flag = 0;\n \tstruct option options[] = {\n@@ -479,6 +498,8 @@ int cmd_tag(int argc, const char **argv, const char *prefix)\n \t\tOPT_CALLBACK_F('m', \"message\", &msg, N_(\"message\"),\n \t\t\t       N_(\"tag message\"), PARSE_OPT_NONEG, parse_msg_arg),\n \t\tOPT_FILENAME('F', \"file\", &msgfile, N_(\"read message from file\")),\n+\t\tOPT_PASSTHRU_ARGV(0, \"trailer\", &trailer_args, N_(\"trailer\"),\n+\t\t\t\t  N_(\"add custom trailer(s)\"), PARSE_OPT_NONEG),\n \t\tOPT_BOOL('e', \"edit\", &edit_flag, N_(\"force edit of tag message\")),\n \t\tOPT_BOOL('s', \"sign\", &opt.sign, N_(\"annotated and GPG-signed tag\")),\n \t\tOPT_CLEANUP(&cleanup_arg),\n@@ -548,7 +569,8 @@ int cmd_tag(int argc, const char **argv, const char *prefix)\n \t\topt.sign = 1;\n \t\tset_signing_key(keyid);\n \t}\n-\tcreate_tag_object = (opt.sign || annotate || msg.given || msgfile);\n+\tcreate_tag_object = (opt.sign || annotate || msg.given || msgfile ||\n+\t\t\t     edit_flag || trailer_args.nr);\n \n \tif ((create_tag_object || force) && (cmdmode != 0))\n \t\tusage_with_options(git_tag_usage, options);\n@@ -654,7 +676,7 @@ int cmd_tag(int argc, const char **argv, const char *prefix)\n \t\t\topt.sign = 1;\n \t\tpath = git_pathdup(\"TAG_EDITMSG\");\n \t\tcreate_tag(&object, object_ref, tag, &buf, &opt, &prev, &object,\n-\t\t\t   path);\n+\t\t\t   &trailer_args, path);\n \t}\n \n \ttransaction = ref_transaction_begin(&err);\n@@ -686,6 +708,7 @@ int cmd_tag(int argc, const char **argv, const char *prefix)\n \tstrbuf_release(&reflog_msg);\n \tstrbuf_release(&msg.buf);\n \tstrbuf_release(&err);\n+\tstrvec_clear(&trailer_args);\n \tfree(msgfile);\n \treturn ret;\n }\ndiff --git a/t/t7004-tag.sh b/t/t7004-tag.sh\nindex 696866d7794..fa6336edf98 100755\n--- a/t/t7004-tag.sh\n+++ b/t/t7004-tag.sh\n@@ -668,6 +668,115 @@ test_expect_success \\\n \ttest_cmp expect actual\n '\n \n+# trailers\n+\n+test_expect_success 'create tag with -m and --trailer' '\n+\tget_tag_header tag-with-inline-message-and-trailers $commit commit $time >expect &&\n+\tcat >>expect <<-\\EOF &&\n+\tcreate tag with trailers\n+\n+\tmy-trailer: here\n+\talt-trailer: there\n+\tEOF\n+\tgit tag -m \"create tag with trailers\" \\\n+\t\t--trailer my-trailer=here \\\n+\t\t--trailer alt-trailer=there \\\n+\t\ttag-with-inline-message-and-trailers &&\n+\tget_tag_msg tag-with-inline-message-and-trailers >actual &&\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'list tag extracting trailers' '\n+\tcat >expect <<-\\EOF &&\n+\tmy-trailer: here\n+\talt-trailer: there\n+\n+\tEOF\n+\tgit tag --list --format=\"%(trailers)\" tag-with-inline-message-and-trailers >actual &&\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'create tag with -F and --trailer' '\n+\techo \"create tag from message file using --trailer\" >messagefilewithnotrailers &&\n+\tget_tag_header tag-with-file-message-and-trailers $commit commit $time >expect &&\n+\tcat >>expect <<-\\EOF &&\n+\tcreate tag from message file using --trailer\n+\n+\tmy-trailer: here\n+\talt-trailer: there\n+\tEOF\n+\tgit tag -F messagefilewithnotrailers \\\n+\t\t--trailer my-trailer=here \\\n+\t\t--trailer alt-trailer=there \\\n+\t\ttag-with-file-message-and-trailers &&\n+\tget_tag_msg tag-with-file-message-and-trailers >actual &&\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'create tag with -m and --trailer and --edit' '\n+\twrite_script fakeeditor <<-\\EOF &&\n+\tsed -e \"1s/^/EDITED: /g\" <\"$1\" >\"$1-\"\n+\tmv \"$1-\" \"$1\"\n+\tEOF\n+\tget_tag_header tag-with-edited-inline-message-and-trailers $commit commit $time >expect &&\n+\tcat >>expect <<-\\EOF &&\n+\tEDITED: create tag with trailers\n+\n+\tmy-trailer: here\n+\talt-trailer: there\n+\tEOF\n+\tGIT_EDITOR=./fakeeditor git tag --edit \\\n+\t\t-m \"create tag with trailers\" \\\n+\t\t--trailer my-trailer=here \\\n+\t\t--trailer alt-trailer=there \\\n+\t\ttag-with-edited-inline-message-and-trailers &&\n+\tget_tag_msg tag-with-edited-inline-message-and-trailers >actual &&\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'create tag with -F and --trailer and --edit' '\n+\techo \"create tag from message file using --trailer\" >messagefilewithnotrailers &&\n+\tget_tag_header tag-with-edited-file-message-and-trailers $commit commit $time >expect &&\n+\tcat >>expect <<-\\EOF &&\n+\tEDITED: create tag from message file using --trailer\n+\n+\tmy-trailer: here\n+\talt-trailer: there\n+\tEOF\n+\tGIT_EDITOR=./fakeeditor git tag --edit \\\n+\t\t-F messagefilewithnotrailers \\\n+\t\t--trailer my-trailer=here \\\n+\t\t--trailer alt-trailer=there \\\n+\t\ttag-with-edited-file-message-and-trailers &&\n+\tget_tag_msg tag-with-edited-file-message-and-trailers >actual &&\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'create annotated tag and force editor when only --trailer is given' '\n+\twrite_script fakeeditor <<-\\EOF &&\n+\techo \"add a line\" >\"$1-\"\n+\tcat <\"$1\" >>\"$1-\"\n+\tmv \"$1-\" \"$1\"\n+\tEOF\n+\tget_tag_header tag-with-trailers-and-no-message $commit commit $time >expect &&\n+\tcat >>expect <<-\\EOF &&\n+\tadd a line\n+\n+\tmy-trailer: here\n+\talt-trailer: there\n+\tEOF\n+\tGIT_EDITOR=./fakeeditor git tag \\\n+\t\t--trailer my-trailer=here \\\n+\t\t--trailer alt-trailer=there \\\n+\t\ttag-with-trailers-and-no-message &&\n+\tget_tag_msg tag-with-trailers-and-no-message >actual &&\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'bad editor causes panic when only --trailer is given' '\n+\ttest_must_fail env GIT_EDITOR=false git tag --trailer my-trailer=here tag-will-not-exist\n+'\n+\n # listing messages for annotated non-signed tags:\n \n test_expect_success \\\n@@ -810,6 +919,11 @@ test_expect_success 'git tag --format with ahead-behind' '\n \trefs/tags/tag-lines 0 1 !\n \trefs/tags/tag-one-line 0 1 !\n \trefs/tags/tag-right 0 0 !\n+\trefs/tags/tag-with-edited-file-message-and-trailers 0 1 !\n+\trefs/tags/tag-with-edited-inline-message-and-trailers 0 1 !\n+\trefs/tags/tag-with-file-message-and-trailers 0 1 !\n+\trefs/tags/tag-with-inline-message-and-trailers 0 1 !\n+\trefs/tags/tag-with-trailers-and-no-message 0 1 !\n \trefs/tags/tag-zero-lines 0 1 !\n \tEOF\n \tgit tag -l --format=\"%(refname) %(ahead-behind:HEAD) !\" >actual 2>err &&\n-- \ngitgitgadget\n"},{"id":"493792","messageId":"xmqq7cgest5a.fsf@gitster.g","threadId":"61376","inReplyTo":"ZjCHiPS8N-eSBrQV@tanuki","subject":"Re: [PATCH v3 1/3] builtin/commit.c: refactor --trailer logic","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2024-04-30T16:38:09Z","receivedAt":"2024-04-30T16:38:16Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Patrick Steinhardt <ps@pks.im> writes:\n\n>> +int amend_file_with_trailers(const char *path, struct strvec const* trailer_args) {\n>\n> I would have called this `amend_trailers_to_file()`, which feels a bit\n> easier to understand. But I don't mind this much, your version should be\n> okay, too.\n\nI had the same reaction, but we are editing trailers in the file\n(without touching other things in the same file), so I would have\nsuggested \"in\" instead of \"to\" here.\n\nI agree with everything else you said you your review, and I do not\nthink I have anything to add to this step.\n\n> Arguably you don't have to include \"strvec.h\" here, but can instead add\n> a simple forward declaration `struct strvec`.\n\nSensible.\n\nThanks.\n"},{"id":"493795","messageId":"xmqqv83yrduw.fsf@gitster.g","threadId":"61376","inReplyTo":"5b6239167b8d7c26f96e5c23d0d82b7a3a9b01fe.1714416865.git.gitgitgadget@gmail.com","subject":"Re: [PATCH v3 2/3] builtin/tag.c: add --trailer arg","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2024-04-30T16:53:43Z","receivedAt":"2024-04-30T16:53:56Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"\"John Passaro via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n\n> From: John Passaro <john.a.passaro@gmail.com>\n>\n> git-tag currently supports interpreting trailers from an annotated tag\n> message, using --list --format=\"%(trailers)\". There is no ergonomic way\n> to add trailers to an annotated tag message.\n\nWell said.  Drop \"currently\", though.  The usual way to compose a\nlog message of this project is to\n\n - Give an observation on how the current system work in the present\n   tense (so no need to say \"Currently X is Y\", just \"X is Y\"), and\n   discuss what you perceive as a problem in it.\n\n - Propose a solution (optional---often, problem description\n   trivially leads to an obvious solution in reader's minds).\n\n - Give commands to the codebase to \"become like so\".\n\nin this order.\n\n> In a previous patch, we refactored git-commit's implementation of its\n> --trailer arg to the trailer.h API. Let's use that new function to teach\n> git-tag the same --trailer argument, emulating as much of git-commit's\n> behavior as much as possible.\n\nNicely described.\n\n> @@ -178,6 +179,19 @@ This option is only applicable when listing tags without annotation lines.\n>  \tImplies `-a` if none of `-a`, `-s`, or `-u <key-id>`\n>  \tis given.\n>  \n> +--trailer <token>[(=|:)<value>]::\n> +\tSpecify a (<token>, <value>) pair that should be applied as a\n> +\ttrailer. (e.g. `git tag --trailer \"Signed-off-by:T A Ger \\\n> +\t<tagger@example.com>\" --trailer \"Helped-by:C O Mitter \\\n> +\t<committer@example.com>\"` will add the \"Signed-off-by\" trailer\n> +\tand the \"Helped-by\" trailer to the tag message.)\n> +\tThe `trailer.*` configuration variables\n> +\t(linkgit:git-interpret-trailers[1]) can be used to define if\n> +\ta duplicated trailer is omitted, where in the run of trailers\n> +\teach trailer would appear, and other details.\n> +\tThe trailers can be seen in `git tag --list` using\n> +\t`--format=\"%(trailers)\"` placeholder.\n\nI can see this was copied-and-pasted from git-commit, but I am not\nsure if the ones used in the example are good fit for tag objects.\nWhat does Helped-by even mean in the context of an annotated tag?\n\n> @@ -290,10 +292,12 @@ static const char message_advice_nested_tag[] =\n>  static void create_tag(const struct object_id *object, const char *object_ref,\n>  \t\t       const char *tag,\n>  \t\t       struct strbuf *buf, struct create_tag_options *opt,\n> -\t\t       struct object_id *prev, struct object_id *result, char *path)\n> +\t\t       struct object_id *prev, struct object_id *result,\n> +\t\t       struct strvec *trailer_args, char *path)\n>  {\n>  \tenum object_type type;\n>  \tstruct strbuf header = STRBUF_INIT;\n> +\tint should_edit;\n>  \n>  \ttype = oid_object_info(the_repository, object, NULL);\n>  \tif (type <= OBJ_NONE)\n> @@ -313,13 +317,15 @@ static void create_tag(const struct object_id *object, const char *object_ref,\n>  \t\t    tag,\n>  \t\t    git_committer_info(IDENT_STRICT));\n>  \n> -\tif (!opt->message_given || opt->use_editor) {\n> +\tshould_edit = opt->use_editor || !opt->message_given;\n> +\tif (should_edit || trailer_args->nr) {\n>  \t\tint fd;\n>  \n>  \t\t/* write the template message before editing: */\n>  \t\tfd = xopen(path, O_CREAT | O_TRUNC | O_WRONLY, 0600);\n>  \n> -\t\tif (opt->message_given) {\n> +\t\tif (opt->message_given && buf->len) {\n> +\t\t\tstrbuf_complete(buf, '\\n');\n>  \t\t\twrite_or_die(fd, buf->buf, buf->len);\n>  \t\t\tstrbuf_reset(buf);\n>  \t\t} else if (!is_null_oid(prev)) {\n> @@ -338,10 +344,22 @@ static void create_tag(const struct object_id *object, const char *object_ref,\n>  \t\t}\n>  \t\tclose(fd);\n>  \n> -\t\tif (launch_editor(path, buf, NULL)) {\n> -\t\t\tfprintf(stderr,\n> -\t\t\t_(\"Please supply the message using either -m or -F option.\\n\"));\n> -\t\t\texit(1);\n> +\t\tif (trailer_args->nr && amend_file_with_trailers(path, trailer_args))\n> +\t\t\tdie(_(\"unable to pass trailers to --trailers\"));\n> +\n> +\t\tif (should_edit) {\n> +\t\t\tif (launch_editor(path, buf, NULL)) {\n> +\t\t\t\tfprintf(stderr,\n> +\t\t\t\t\t_(\"Please supply the message using either -m or -F option.\\n\"));\n> +\t\t\t\texit(1);\n> +\t\t\t}\n> +\t\t} else if (trailer_args->nr) {\n\nWhen both should_edit and trailer_args->nr are true, this block will\nnot be entered.  We first do the \"amend_file\" thing, and then run an\neditor on it, and that is the end of the story in that case.\n\nWhen we do not have should_edit (e.g., -m \"tag message\" is given),\nwe would have run \"amend_file\" thing on it to tweak the message,\nand we come in here.\n\n> +\t\t\tstrbuf_reset(buf);\n> +\t\t\tif (strbuf_read_file(buf, path, 0) < 0) {\n> +\t\t\t\tfprintf(stderr,\n> +\t\t\t\t\t_(\"Please supply the message using either -m or -F option.\\n\"));\n> +\t\t\t\texit(1);\n\nDoes this error message make sense here in this context?  The\nearlier one was introduced by 7198203a (editor.c: Libify\nlaunch_editor(), 2008-07-25)---after we fail to run the editor, as\nwe somehow seem to be unable to run an editor, we suggest the user\nto give us a message in other ways.  But this one is different.  The\nuser gave us in one of these other ways already instead of using an\neditor, but mucking with that with the \"amend_file\" thing somehow\nmade it unreadable.  Shouldn't it be more like\n\n\tdie_errno(_(\"failed to read '%s'\"), path);\n\nor something along that line?\n"},{"id":"493821","messageId":"CAJdN7Kggv-y8xRhdFanTyxa5wP-bG0omBPuETW6qijA67gn8oQ@mail.gmail.com","threadId":"61376","inReplyTo":"xmqqv83yrduw.fsf@gitster.g","subject":"Re: [PATCH v3 2/3] builtin/tag.c: add --trailer arg","fromName":"John Passaro","fromEmail":"john.a.passaro@gmail.com","sentAt":"2024-04-30T21:48:14Z","receivedAt":"2024-04-30T21:48:54Z","isPatch":true,"sender":{"key":"john.a.passaro@gmail.com","avatar":"https://avatars.githubusercontent.com/u/6754005?v=4"},"body":"On Tue, Apr 30, 2024 at 12:53 PM Junio C Hamano <gitster@pobox.com> wrote:\n>\n\nThanks for the feedback. Hoping for a couple points of clarification\nthen I'll put in one more version of this patch series.\n\n> \"John Passaro via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n>\n> > From: John Passaro <john.a.passaro@gmail.com>\n> >\n> > git-tag currently supports interpreting trailers from an annotated tag\n> > message, using --list --format=\"%(trailers)\". There is no ergonomic way\n> > to add trailers to an annotated tag message.\n>\n> Well said.  Drop \"currently\", though.  The usual way to compose a\n> log message of this project is to\n>\n>  - Give an observation on how the current system work in the present\n>    tense (so no need to say \"Currently X is Y\", just \"X is Y\"), and\n>    discuss what you perceive as a problem in it.\n>\n>  - Propose a solution (optional---often, problem description\n>    trivially leads to an obvious solution in reader's minds).\n>\n>  - Give commands to the codebase to \"become like so\".\n>\n> in this order.\n\nUnderstood. In the most recent version of this patch, I updated the\nmessage. However on second thought I think I'm gonna keep this on\nthe next submission of this patch (without \"currently\" of course).\n\n>\n> > In a previous patch, we refactored git-commit's implementation of its\n> > --trailer arg to the trailer.h API. Let's use that new function to teach\n> > git-tag the same --trailer argument, emulating as much of git-commit's\n> > behavior as much as possible.\n>\n> Nicely described.\n>\n> > @@ -178,6 +179,19 @@ This option is only applicable when listing tags without annotation lines.\n> >       Implies `-a` if none of `-a`, `-s`, or `-u <key-id>`\n> >       is given.\n> >\n> > +--trailer <token>[(=|:)<value>]::\n> > +     Specify a (<token>, <value>) pair that should be applied as a\n> > +     trailer. (e.g. `git tag --trailer \"Signed-off-by:T A Ger \\\n> > +     <tagger@example.com>\" --trailer \"Helped-by:C O Mitter \\\n> > +     <committer@example.com>\"` will add the \"Signed-off-by\" trailer\n> > +     and the \"Helped-by\" trailer to the tag message.)\n> > +     The `trailer.*` configuration variables\n> > +     (linkgit:git-interpret-trailers[1]) can be used to define if\n> > +     a duplicated trailer is omitted, where in the run of trailers\n> > +     each trailer would appear, and other details.\n> > +     The trailers can be seen in `git tag --list` using\n> > +     `--format=\"%(trailers)\"` placeholder.\n>\n> I can see this was copied-and-pasted from git-commit, but I am not\n> sure if the ones used in the example are good fit for tag objects.\n> What does Helped-by even mean in the context of an annotated tag?\n\nI can see that the git project itself doesn't typically add trailers to tags.\nIf y'all were in that habit I imagine this feature would already be\nimplemented :-)\nNonetheless Signed-off-by or Approved-by is easy to imagine, for example\nin an environment where multiple sign-offs are required (i.e. not just\nthe implicit\nsign-off of the tagger). So we could just leave that in and be done with it.\n\nI have a couple more hypothetical trailers that are both plausible and somewhat\ngeneric; do any of them seem expressive enough to include in the docs?\n\n* Tested-by: T E Ster <tester@example.com>\n* Testing-assigned-to: T E Ster <tester@example.com>\n* Scheduled-Deployment-Date: 2024-05-15 (or 1714500385 -05:00)\n* Deployment-assigned-to: Oscar P Staff <ops@example.com>\n* (for RC/alpha tags) Full-release-scheduled-for: 2024-06-05\n\nThere's also project-specific trailers. For example, on my team,\nwe use \"Deploy-Strategy: ...\" to tell CICD what deployment routines to run. This\nis pretty specific to us but worth calling out. Maybe could translate to a\ndocumentation example with something like \"<Project-specific-trailer>: foo\"\n\n> > @@ -338,10 +344,22 @@ static void create_tag(const struct object_id *object, const char *object_ref,\n> >               }\n> >               close(fd);\n> >\n> > -             if (launch_editor(path, buf, NULL)) {\n> > -                     fprintf(stderr,\n> > -                     _(\"Please supply the message using either -m or -F option.\\n\"));\n> > -                     exit(1);\n> > +             if (trailer_args->nr && amend_file_with_trailers(path, trailer_args))\n> > +                     die(_(\"unable to pass trailers to --trailers\"));\n> > +\n> > +             if (should_edit) {\n> > +                     if (launch_editor(path, buf, NULL)) {\n> > +                             fprintf(stderr,\n> > +                                     _(\"Please supply the message using either -m or -F option.\\n\"));\n> > +                             exit(1);\n> > +                     }\n> > +             } else if (trailer_args->nr) {\n>\n> When both should_edit and trailer_args->nr are true, this block will\n> not be entered.  We first do the \"amend_file\" thing, and then run an\n> editor on it, and that is the end of the story in that case.\n>\n> When we do not have should_edit (e.g., -m \"tag message\" is given),\n> we would have run \"amend_file\" thing on it to tweak the message,\n> and we come in here.\n>\n> > +                     strbuf_reset(buf);\n> > +                     if (strbuf_read_file(buf, path, 0) < 0) {\n> > +                             fprintf(stderr,\n> > +                                     _(\"Please supply the message using either -m or -F option.\\n\"));\n> > +                             exit(1);\n>\n> Does this error message make sense here in this context?  The\n> earlier one was introduced by 7198203a (editor.c: Libify\n> launch_editor(), 2008-07-25)---after we fail to run the editor, as\n> we somehow seem to be unable to run an editor, we suggest the user\n> to give us a message in other ways.  But this one is different.  The\n> user gave us in one of these other ways already instead of using an\n> editor, but mucking with that with the \"amend_file\" thing somehow\n> made it unreadable.  Shouldn't it be more like\n>\n>         die_errno(_(\"failed to read '%s'\"), path);\n>\n> or something along that line?\n\nI didn't realize that the first message is intended to augment more\nexpressive failure messages previously printed in launch_editor().\nKnowing that, your suggested message will point users in the right\ndirection much more effectively. Also i guess die() probably preferable\nsince unlike launch_editor(), which may signal non-exceptional failure,\nthis error is more likely to be a bug.\n\nHowever, in service of helping users find workarounds, shouldn't we tell them\n--trailer may be the culprit?\n\n> Failed to read '%s'. Try again without --trailer (use -e or -F to add trailers manually).\n"},{"id":"493823","messageId":"xmqq4jbixzg6.fsf@gitster.g","threadId":"61376","inReplyTo":"CAJdN7Kggv-y8xRhdFanTyxa5wP-bG0omBPuETW6qijA67gn8oQ@mail.gmail.com","subject":"Re: [PATCH v3 2/3] builtin/tag.c: add --trailer arg","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2024-04-30T22:23:05Z","receivedAt":"2024-04-30T22:23:12Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"John Passaro <john.a.passaro@gmail.com> writes:\n\n> There's also project-specific trailers. For example, on my team,\n> we use \"Deploy-Strategy: ...\" to tell CICD what deployment routines to run. This\n> is pretty specific to us but worth calling out. Maybe could translate to a\n> documentation example with something like \"<Project-specific-trailer>: foo\"\n\nThe last one that uses placeholders for both trailer tag and value\nmay be generic enough.\n\n> However, in service of helping users find workarounds, shouldn't we tell them\n> --trailer may be the culprit?\n>\n>> Failed to read '%s'. Try again without --trailer (use -e or -F to add trailers manually).\n\nI dunno.  \n\nIf -m/-F that wrote the original using the open/write_or_die/close\nsequence succeeded, the \"amend_file\" thing successfully spawned\n\"interpret-trailers --in-place\" and got control back, yet we fail\nto read that message back, it does not smell like a failure with\nthat \"--trailer\" option to me.  A failure with \"--trailer\" that\ncould be worked around would have been caught in \"amend_file\" thing,\nbefore the control reaches this point, no?\n\n"},{"id":"493873","messageId":"ZjMyRDDQ-uIx03H8@tanuki","threadId":"61376","inReplyTo":"ce047c58aa81cc5228ae9068d790585e012491c5.1714488111.git.gitgitgadget@gmail.com","subject":"Re: [PATCH v4 1/3] builtin/commit.c: remove bespoke option callback","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2024-05-02T06:27:16Z","receivedAt":"2024-05-02T06:27:21Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"On Tue, Apr 30, 2024 at 02:41:49PM +0000, John Passaro via GitGitGadget wrote:\n> From: John Passaro <john.a.passaro@gmail.com>\n> \n> Replace git-commit's bespoke callback for --trailer with the standard\n> OPT_PASSTHRU_ARGV macro. The bespoke callback was only adding its values\n> to a strvec and sanity-checking that `unset` is always false; both of\n> these are already implemented in the parse-option API.\n\nNit: I feel like saying \"bespoke\" is a bit vague. I would have said\nsomething like the following:\n\n    builtin/commit: use ARGV option to collect custom trailers\n\nIn any case, I don't think this is a huge deal and not worth a reroll on\nits own.\n\nPatrick\n"},{"id":"493874","messageId":"ZjMySkLSUKKEiF_H@tanuki","threadId":"61376","inReplyTo":"8f53a54bbfe9a952ae5e86216681eef2a2e916eb.1714488111.git.gitgitgadget@gmail.com","subject":"Re: [PATCH v4 2/3] builtin/commit.c: refactor --trailer logic","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2024-05-02T06:27:22Z","receivedAt":"2024-05-02T06:27:27Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"On Tue, Apr 30, 2024 at 02:41:50PM +0000, John Passaro via GitGitGadget wrote:\n> From: John Passaro <john.a.passaro@gmail.com>\n[snip]\n> diff --git a/trailer.c b/trailer.c\n> index c72ae687099..ae0597d919e 100644\n> --- a/trailer.c\n> +++ b/trailer.c\n> @@ -1170,3 +1170,14 @@ void trailer_iterator_release(struct trailer_iterator *iter)\n>  \tstrbuf_release(&iter->val);\n>  \tstrbuf_release(&iter->key);\n>  }\n> +\n> +int amend_file_with_trailers(const char *path, const struct strvec *trailer_args) {\n\nNit: the opening brace should go on the next line.\n\nOther than that this patch looks good to me.\n\nPatrick\n\n> +\tstruct child_process run_trailer = CHILD_PROCESS_INIT;\n> +\n> +\trun_trailer.git_cmd = 1;\n> +\tstrvec_pushl(&run_trailer.args, \"interpret-trailers\",\n> +\t\t     \"--in-place\", \"--no-divider\",\n> +\t\t     path, NULL);\n> +\tstrvec_pushv(&run_trailer.args, trailer_args->v);\n> +\treturn run_command(&run_trailer);\n> +}\n> diff --git a/trailer.h b/trailer.h\n> index 9f42aa75994..c364405267a 100644\n> --- a/trailer.h\n> +++ b/trailer.h\n> @@ -4,6 +4,8 @@\n>  #include \"list.h\"\n>  #include \"strbuf.h\"\n>  \n> +struct strvec;\n> +\n>  enum trailer_where {\n>  \tWHERE_DEFAULT,\n>  \tWHERE_END,\n> @@ -158,4 +160,11 @@ int trailer_iterator_advance(struct trailer_iterator *iter);\n>   */\n>  void trailer_iterator_release(struct trailer_iterator *iter);\n>  \n> +/*\n> + * Augment a file to add trailers to it by running git-interpret-trailers.\n> + * This calls run_command() and its return value is the same (i.e. 0 for\n> + * success, various non-zero for other errors). See run-command.h.\n> + */\n> +int amend_file_with_trailers(const char *path, const struct strvec *trailer_args);\n> +\n>  #endif /* TRAILER_H */\n> -- \n> gitgitgadget\n> \n"},{"id":"493875","messageId":"ZjMyTyOb1PoqP2DH@tanuki","threadId":"61376","inReplyTo":"pull.1723.v4.git.1714488111.gitgitgadget@gmail.com","subject":"Re: [PATCH v4 0/3] builtin/tag.c: add --trailer option","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2024-05-02T06:27:27Z","receivedAt":"2024-05-02T06:27:32Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"On Tue, Apr 30, 2024 at 02:41:48PM +0000, John Passaro via GitGitGadget wrote:\n> 4th follow-up patch taking welcome feedback from Patrick and JCH. Net new\n> changes include separating from a 2-patch series to 3.\n> \n> Since git-tag --list --format=\"%(trailers)\" can interpret trailers from\n> annotated tag messages, it seems natural to support --trailer when writing a\n> new tag message.\n> \n> git-commit accomplishes this by taking --trailer arguments and passing them\n> to git-interpret-trailer. This patch series refactors that logic and uses it\n> to implement --trailer on git-tag.\n\nThis version looks mostly good. There are two tiny nits, but other than\nthat I don't have anything else to add.\n\nPatrick\n"},{"id":"494096","messageId":"pull.1723.v5.git.1714934950.gitgitgadget@gmail.com","threadId":"61376","inReplyTo":"pull.1723.v4.git.1714488111.gitgitgadget@gmail.com","subject":"[PATCH v5 0/3] builtin/tag.c: add --trailer option","fromName":"John Passaro via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2024-05-05T18:49:07Z","receivedAt":"2024-05-05T18:49:14Z","isPatch":true,"sender":{"key":"john.a.passaro@gmail.com","avatar":"https://avatars.githubusercontent.com/u/6754005?v=4"},"body":"5th follow-up patch taking welcome feedback from Patrick and JCH. Net new\nchanges include suggested tweaks in documentation, error messaging, code\nformatting, and patch description.\n\nSince git-tag --list --format=\"%(trailers)\" can interpret trailers from\nannotated tag messages, it seems natural to support --trailer when writing a\nnew tag message.\n\ngit-commit accomplishes this by taking --trailer arguments and passing them\nto git-interpret-trailer. This patch series refactors that logic and uses it\nto implement --trailer on git-tag.\n\nJohn Passaro (3):\n  builtin/commit: use ARGV macro to collect trailers\n  builtin/commit: refactor --trailer logic\n  builtin/tag: add --trailer option\n\n Documentation/git-tag.txt |  16 +++++-\n builtin/commit.c          |  20 +------\n builtin/tag.c             |  38 ++++++++++---\n t/t7004-tag.sh            | 114 ++++++++++++++++++++++++++++++++++++++\n trailer.c                 |  12 ++++\n trailer.h                 |   9 +++\n 6 files changed, 181 insertions(+), 28 deletions(-)\n\n\nbase-commit: d4cc1ec35f3bcce816b69986ca41943f6ce21377\nPublished-As: https://github.com/gitgitgadget/git/releases/tag/pr-1723%2Fjpassaro%2Fjp%2Ftag-trailer-arg-v5\nFetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-1723/jpassaro/jp/tag-trailer-arg-v5\nPull-Request: https://github.com/gitgitgadget/git/pull/1723\n\nRange-diff vs v4:\n\n 1:  ce047c58aa8 ! 1:  85f45a57f35 builtin/commit.c: remove bespoke option callback\n     @@ Metadata\n      Author: John Passaro <john.a.passaro@gmail.com>\n      \n       ## Commit message ##\n     -    builtin/commit.c: remove bespoke option callback\n     +    builtin/commit: use ARGV macro to collect trailers\n      \n     -    Replace git-commit's bespoke callback for --trailer with the standard\n     -    OPT_PASSTHRU_ARGV macro. The bespoke callback was only adding its values\n     -    to a strvec and sanity-checking that `unset` is always false; both of\n     -    these are already implemented in the parse-option API.\n     +    Replace git-commit's callback for --trailer with the standard\n     +    OPT_PASSTHRU_ARGV macro. The callback only adds its values to a strvec\n     +    and sanity-checking that `unset` is always false; both of these are\n     +    already implemented in the parse-option API.\n      \n          Signed-off-by: John Passaro <john.a.passaro@gmail.com>\n      \n 2:  8f53a54bbfe ! 2:  75bb5cc0e8a builtin/commit.c: refactor --trailer logic\n     @@ Metadata\n      Author: John Passaro <john.a.passaro@gmail.com>\n      \n       ## Commit message ##\n     -    builtin/commit.c: refactor --trailer logic\n     +    builtin/commit: refactor --trailer logic\n      \n          git-commit adds user trailers to the commit message by passing its\n          `--trailer` arguments to a child process running `git-interpret-trailers\n     @@ trailer.c: void trailer_iterator_release(struct trailer_iterator *iter)\n       \tstrbuf_release(&iter->key);\n       }\n      +\n     -+int amend_file_with_trailers(const char *path, const struct strvec *trailer_args) {\n     ++int amend_file_with_trailers(const char *path, const struct strvec *trailer_args)\n     ++{\n      +\tstruct child_process run_trailer = CHILD_PROCESS_INIT;\n      +\n      +\trun_trailer.git_cmd = 1;\n 3:  f1d68337eda ! 3:  d8b3f6229bc builtin/tag.c: add --trailer option\n     @@ Metadata\n      Author: John Passaro <john.a.passaro@gmail.com>\n      \n       ## Commit message ##\n     -    builtin/tag.c: add --trailer option\n     +    builtin/tag: add --trailer option\n      \n     -    git-tag currently supports interpreting trailers from an annotated tag\n     -    message, using --list --format=\"%(trailers)\". However, to add a trailer\n     -    to a tag message, you must do so using `-F` or by editing the message.\n     +    git-tag supports interpreting trailers from an annotated tag message,\n     +    using --list --format=\"%(trailers)\". However, the available methods to\n     +    add a trailer to a tag message (namely -F or --editor) are not as\n     +    ergonomic.\n      \n          In a previous patch, we moved git-commit's implementation of its\n          --trailer option to the trailer.h API. Let's use that new function to\n     @@ Documentation/git-tag.txt: This option is only applicable when listing tags with\n       \n      +--trailer <token>[(=|:)<value>]::\n      +\tSpecify a (<token>, <value>) pair that should be applied as a\n     -+\ttrailer. (e.g. `git tag --trailer \"Signed-off-by:T A Ger \\\n     -+\t<tagger@example.com>\" --trailer \"Helped-by:C O Mitter \\\n     -+\t<committer@example.com>\"` will add the \"Signed-off-by\" trailer\n     -+\tand the \"Helped-by\" trailer to the tag message.)\n     ++\ttrailer. (e.g. `git tag --trailer \"Custom-Key: value\"`\n     ++\twill add a \"Custom-Key\" trailer to the tag message.)\n      +\tThe `trailer.*` configuration variables\n      +\t(linkgit:git-interpret-trailers[1]) can be used to define if\n      +\ta duplicated trailer is omitted, where in the run of trailers\n      +\teach trailer would appear, and other details.\n     -+\tThe trailers can be seen in `git tag --list` using\n     ++\tThe trailers can be extracted in `git tag --list`, using\n      +\t`--format=\"%(trailers)\"` placeholder.\n      +\n       -e::\n     @@ builtin/tag.c: static void create_tag(const struct object_id *object, const char\n      +\t\t\t}\n      +\t\t} else if (trailer_args->nr) {\n      +\t\t\tstrbuf_reset(buf);\n     -+\t\t\tif (strbuf_read_file(buf, path, 0) < 0) {\n     -+\t\t\t\tfprintf(stderr,\n     -+\t\t\t\t\t_(\"Please supply the message using either -m or -F option.\\n\"));\n     -+\t\t\t\texit(1);\n     -+\t\t\t}\n     ++\t\t\tif (strbuf_read_file(buf, path, 0) < 0)\n     ++\t\t\t\tdie_errno(_(\"failed to read '%s'\"), path);\n       \t\t}\n       \t}\n       \n\n-- \ngitgitgadget\n"},{"id":"494097","messageId":"85f45a57f3529a90324ded07856fca610f7b5eda.1714934950.git.gitgitgadget@gmail.com","threadId":"61376","inReplyTo":"pull.1723.v5.git.1714934950.gitgitgadget@gmail.com","subject":"[PATCH v5 1/3] builtin/commit: use ARGV macro to collect trailers","fromName":"John Passaro via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2024-05-05T18:49:08Z","receivedAt":"2024-05-05T18:49:15Z","isPatch":true,"sender":{"key":"john.a.passaro@gmail.com","avatar":"https://avatars.githubusercontent.com/u/6754005?v=4"},"body":"From: John Passaro <john.a.passaro@gmail.com>\n\nReplace git-commit's callback for --trailer with the standard\nOPT_PASSTHRU_ARGV macro. The callback only adds its values to a strvec\nand sanity-checking that `unset` is always false; both of these are\nalready implemented in the parse-option API.\n\nSigned-off-by: John Passaro <john.a.passaro@gmail.com>\n---\n builtin/commit.c | 10 +---------\n 1 file changed, 1 insertion(+), 9 deletions(-)\n\ndiff --git a/builtin/commit.c b/builtin/commit.c\nindex 6e1484446b0..5a3248370db 100644\n--- a/builtin/commit.c\n+++ b/builtin/commit.c\n@@ -142,14 +142,6 @@ static struct strbuf message = STRBUF_INIT;\n \n static enum wt_status_format status_format = STATUS_FORMAT_UNSPECIFIED;\n \n-static int opt_pass_trailer(const struct option *opt, const char *arg, int unset)\n-{\n-\tBUG_ON_OPT_NEG(unset);\n-\n-\tstrvec_pushl(opt->value, \"--trailer\", arg, NULL);\n-\treturn 0;\n-}\n-\n static int opt_parse_porcelain(const struct option *opt, const char *arg, int unset)\n {\n \tenum wt_status_format *value = (enum wt_status_format *)opt->value;\n@@ -1673,7 +1665,7 @@ int cmd_commit(int argc, const char **argv, const char *prefix)\n \t\tOPT_STRING(0, \"fixup\", &fixup_message, N_(\"[(amend|reword):]commit\"), N_(\"use autosquash formatted message to fixup or amend/reword specified commit\")),\n \t\tOPT_STRING(0, \"squash\", &squash_message, N_(\"commit\"), N_(\"use autosquash formatted message to squash specified commit\")),\n \t\tOPT_BOOL(0, \"reset-author\", &renew_authorship, N_(\"the commit is authored by me now (used with -C/-c/--amend)\")),\n-\t\tOPT_CALLBACK_F(0, \"trailer\", &trailer_args, N_(\"trailer\"), N_(\"add custom trailer(s)\"), PARSE_OPT_NONEG, opt_pass_trailer),\n+\t\tOPT_PASSTHRU_ARGV(0, \"trailer\", &trailer_args, N_(\"trailer\"), N_(\"add custom trailer(s)\"), PARSE_OPT_NONEG),\n \t\tOPT_BOOL('s', \"signoff\", &signoff, N_(\"add a Signed-off-by trailer\")),\n \t\tOPT_FILENAME('t', \"template\", &template_file, N_(\"use specified template file\")),\n \t\tOPT_BOOL('e', \"edit\", &edit_flag, N_(\"force edit of commit\")),\n-- \ngitgitgadget\n\n"},{"id":"494098","messageId":"75bb5cc0e8a8eac71983a5471d78f913550a21dd.1714934950.git.gitgitgadget@gmail.com","threadId":"61376","inReplyTo":"pull.1723.v5.git.1714934950.gitgitgadget@gmail.com","subject":"[PATCH v5 2/3] builtin/commit: refactor --trailer logic","fromName":"John Passaro via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2024-05-05T18:49:09Z","receivedAt":"2024-05-05T18:49:16Z","isPatch":true,"sender":{"key":"john.a.passaro@gmail.com","avatar":"https://avatars.githubusercontent.com/u/6754005?v=4"},"body":"From: John Passaro <john.a.passaro@gmail.com>\n\ngit-commit adds user trailers to the commit message by passing its\n`--trailer` arguments to a child process running `git-interpret-trailers\n--in-place`. This logic is broadly useful, not just for git-commit but\nfor other commands constructing message bodies (e.g. git-tag).\n\nLet's move this logic from git-commit to a new function in the trailer\nAPI, so that it can be re-used in other commands.\n\nHelped-by: Patrick Steinhardt <ps@pks.im>\nHelped-by: Junio C Hamano <gitster@pobox.com>\nSigned-off-by: John Passaro <john.a.passaro@gmail.com>\n---\n builtin/commit.c | 10 ++--------\n trailer.c        | 12 ++++++++++++\n trailer.h        |  9 +++++++++\n 3 files changed, 23 insertions(+), 8 deletions(-)\n\ndiff --git a/builtin/commit.c b/builtin/commit.c\nindex 5a3248370db..63cd090b6f2 100644\n--- a/builtin/commit.c\n+++ b/builtin/commit.c\n@@ -38,6 +38,7 @@\n #include \"commit-reach.h\"\n #include \"commit-graph.h\"\n #include \"pretty.h\"\n+#include \"trailer.h\"\n \n static const char * const builtin_commit_usage[] = {\n \tN_(\"git commit [-a | --interactive | --patch] [-s] [-v] [-u<mode>] [--amend]\\n\"\n@@ -1030,14 +1031,7 @@ static int prepare_to_commit(const char *index_file, const char *prefix,\n \tfclose(s->fp);\n \n \tif (trailer_args.nr) {\n-\t\tstruct child_process run_trailer = CHILD_PROCESS_INIT;\n-\n-\t\tstrvec_pushl(&run_trailer.args, \"interpret-trailers\",\n-\t\t\t     \"--in-place\", \"--no-divider\",\n-\t\t\t     git_path_commit_editmsg(), NULL);\n-\t\tstrvec_pushv(&run_trailer.args, trailer_args.v);\n-\t\trun_trailer.git_cmd = 1;\n-\t\tif (run_command(&run_trailer))\n+\t\tif (amend_file_with_trailers(git_path_commit_editmsg(), &trailer_args))\n \t\t\tdie(_(\"unable to pass trailers to --trailers\"));\n \t\tstrvec_clear(&trailer_args);\n \t}\ndiff --git a/trailer.c b/trailer.c\nindex c72ae687099..724cb78be5f 100644\n--- a/trailer.c\n+++ b/trailer.c\n@@ -1170,3 +1170,15 @@ void trailer_iterator_release(struct trailer_iterator *iter)\n \tstrbuf_release(&iter->val);\n \tstrbuf_release(&iter->key);\n }\n+\n+int amend_file_with_trailers(const char *path, const struct strvec *trailer_args)\n+{\n+\tstruct child_process run_trailer = CHILD_PROCESS_INIT;\n+\n+\trun_trailer.git_cmd = 1;\n+\tstrvec_pushl(&run_trailer.args, \"interpret-trailers\",\n+\t\t     \"--in-place\", \"--no-divider\",\n+\t\t     path, NULL);\n+\tstrvec_pushv(&run_trailer.args, trailer_args->v);\n+\treturn run_command(&run_trailer);\n+}\ndiff --git a/trailer.h b/trailer.h\nindex 9f42aa75994..c364405267a 100644\n--- a/trailer.h\n+++ b/trailer.h\n@@ -4,6 +4,8 @@\n #include \"list.h\"\n #include \"strbuf.h\"\n \n+struct strvec;\n+\n enum trailer_where {\n \tWHERE_DEFAULT,\n \tWHERE_END,\n@@ -158,4 +160,11 @@ int trailer_iterator_advance(struct trailer_iterator *iter);\n  */\n void trailer_iterator_release(struct trailer_iterator *iter);\n \n+/*\n+ * Augment a file to add trailers to it by running git-interpret-trailers.\n+ * This calls run_command() and its return value is the same (i.e. 0 for\n+ * success, various non-zero for other errors). See run-command.h.\n+ */\n+int amend_file_with_trailers(const char *path, const struct strvec *trailer_args);\n+\n #endif /* TRAILER_H */\n-- \ngitgitgadget\n\n"},{"id":"494099","messageId":"d8b3f6229bcdfb487bfb7941f54ce5cb6e44f088.1714934950.git.gitgitgadget@gmail.com","threadId":"61376","inReplyTo":"pull.1723.v5.git.1714934950.gitgitgadget@gmail.com","subject":"[PATCH v5 3/3] builtin/tag: add --trailer option","fromName":"John Passaro via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2024-05-05T18:49:10Z","receivedAt":"2024-05-05T18:49:17Z","isPatch":true,"sender":{"key":"john.a.passaro@gmail.com","avatar":"https://avatars.githubusercontent.com/u/6754005?v=4"},"body":"From: John Passaro <john.a.passaro@gmail.com>\n\ngit-tag supports interpreting trailers from an annotated tag message,\nusing --list --format=\"%(trailers)\". However, the available methods to\nadd a trailer to a tag message (namely -F or --editor) are not as\nergonomic.\n\nIn a previous patch, we moved git-commit's implementation of its\n--trailer option to the trailer.h API. Let's use that new function to\nteach git-tag the same --trailer option, emulating as much of\ngit-commit's behavior as much as possible.\n\nHelped-by: Patrick Steinhardt <ps@pks.im>\nSigned-off-by: John Passaro <john.a.passaro@gmail.com>\n---\n Documentation/git-tag.txt |  16 +++++-\n builtin/tag.c             |  38 ++++++++++---\n t/t7004-tag.sh            | 114 ++++++++++++++++++++++++++++++++++++++\n 3 files changed, 157 insertions(+), 11 deletions(-)\n\ndiff --git a/Documentation/git-tag.txt b/Documentation/git-tag.txt\nindex 5fe519c31ec..4494729f5e1 100644\n--- a/Documentation/git-tag.txt\n+++ b/Documentation/git-tag.txt\n@@ -10,6 +10,7 @@ SYNOPSIS\n --------\n [verse]\n 'git tag' [-a | -s | -u <key-id>] [-f] [-m <msg> | -F <file>] [-e]\n+\t[(--trailer <token>[(=|:)<value>])...]\n \t<tagname> [<commit> | <object>]\n 'git tag' -d <tagname>...\n 'git tag' [-n[<num>]] -l [--contains <commit>] [--no-contains <commit>]\n@@ -31,8 +32,8 @@ creates a 'tag' object, and requires a tag message.  Unless\n `-m <msg>` or `-F <file>` is given, an editor is started for the user to type\n in the tag message.\n \n-If `-m <msg>` or `-F <file>` is given and `-a`, `-s`, and `-u <key-id>`\n-are absent, `-a` is implied.\n+If `-m <msg>` or `-F <file>` or `--trailer <token>[=<value>]` is given\n+and `-a`, `-s`, and `-u <key-id>` are absent, `-a` is implied.\n \n Otherwise, a tag reference that points directly at the given object\n (i.e., a lightweight tag) is created.\n@@ -178,6 +179,17 @@ This option is only applicable when listing tags without annotation lines.\n \tImplies `-a` if none of `-a`, `-s`, or `-u <key-id>`\n \tis given.\n \n+--trailer <token>[(=|:)<value>]::\n+\tSpecify a (<token>, <value>) pair that should be applied as a\n+\ttrailer. (e.g. `git tag --trailer \"Custom-Key: value\"`\n+\twill add a \"Custom-Key\" trailer to the tag message.)\n+\tThe `trailer.*` configuration variables\n+\t(linkgit:git-interpret-trailers[1]) can be used to define if\n+\ta duplicated trailer is omitted, where in the run of trailers\n+\teach trailer would appear, and other details.\n+\tThe trailers can be extracted in `git tag --list`, using\n+\t`--format=\"%(trailers)\"` placeholder.\n+\n -e::\n --edit::\n \tThe message taken from file with `-F` and command line with\ndiff --git a/builtin/tag.c b/builtin/tag.c\nindex 9a33cb50b45..01b19a5b6c9 100644\n--- a/builtin/tag.c\n+++ b/builtin/tag.c\n@@ -28,9 +28,11 @@\n #include \"date.h\"\n #include \"write-or-die.h\"\n #include \"object-file-convert.h\"\n+#include \"trailer.h\"\n \n static const char * const git_tag_usage[] = {\n \tN_(\"git tag [-a | -s | -u <key-id>] [-f] [-m <msg> | -F <file>] [-e]\\n\"\n+\t   \"        [(--trailer <token>[(=|:)<value>])...]\\n\"\n \t   \"        <tagname> [<commit> | <object>]\"),\n \tN_(\"git tag -d <tagname>...\"),\n \tN_(\"git tag [-n[<num>]] -l [--contains <commit>] [--no-contains <commit>]\\n\"\n@@ -290,10 +292,12 @@ static const char message_advice_nested_tag[] =\n static void create_tag(const struct object_id *object, const char *object_ref,\n \t\t       const char *tag,\n \t\t       struct strbuf *buf, struct create_tag_options *opt,\n-\t\t       struct object_id *prev, struct object_id *result, char *path)\n+\t\t       struct object_id *prev, struct object_id *result,\n+\t\t       struct strvec *trailer_args, char *path)\n {\n \tenum object_type type;\n \tstruct strbuf header = STRBUF_INIT;\n+\tint should_edit;\n \n \ttype = oid_object_info(the_repository, object, NULL);\n \tif (type <= OBJ_NONE)\n@@ -313,13 +317,15 @@ static void create_tag(const struct object_id *object, const char *object_ref,\n \t\t    tag,\n \t\t    git_committer_info(IDENT_STRICT));\n \n-\tif (!opt->message_given || opt->use_editor) {\n+\tshould_edit = opt->use_editor || !opt->message_given;\n+\tif (should_edit || trailer_args->nr) {\n \t\tint fd;\n \n \t\t/* write the template message before editing: */\n \t\tfd = xopen(path, O_CREAT | O_TRUNC | O_WRONLY, 0600);\n \n-\t\tif (opt->message_given) {\n+\t\tif (opt->message_given && buf->len) {\n+\t\t\tstrbuf_complete(buf, '\\n');\n \t\t\twrite_or_die(fd, buf->buf, buf->len);\n \t\t\tstrbuf_reset(buf);\n \t\t} else if (!is_null_oid(prev)) {\n@@ -338,10 +344,19 @@ static void create_tag(const struct object_id *object, const char *object_ref,\n \t\t}\n \t\tclose(fd);\n \n-\t\tif (launch_editor(path, buf, NULL)) {\n-\t\t\tfprintf(stderr,\n-\t\t\t_(\"Please supply the message using either -m or -F option.\\n\"));\n-\t\t\texit(1);\n+\t\tif (trailer_args->nr && amend_file_with_trailers(path, trailer_args))\n+\t\t\tdie(_(\"unable to pass trailers to --trailers\"));\n+\n+\t\tif (should_edit) {\n+\t\t\tif (launch_editor(path, buf, NULL)) {\n+\t\t\t\tfprintf(stderr,\n+\t\t\t\t\t_(\"Please supply the message using either -m or -F option.\\n\"));\n+\t\t\t\texit(1);\n+\t\t\t}\n+\t\t} else if (trailer_args->nr) {\n+\t\t\tstrbuf_reset(buf);\n+\t\t\tif (strbuf_read_file(buf, path, 0) < 0)\n+\t\t\t\tdie_errno(_(\"failed to read '%s'\"), path);\n \t\t}\n \t}\n \n@@ -463,6 +478,7 @@ int cmd_tag(int argc, const char **argv, const char *prefix)\n \tstruct ref_sorting *sorting;\n \tstruct string_list sorting_options = STRING_LIST_INIT_DUP;\n \tstruct ref_format format = REF_FORMAT_INIT;\n+\tstruct strvec trailer_args = STRVEC_INIT;\n \tint icase = 0;\n \tint edit_flag = 0;\n \tstruct option options[] = {\n@@ -479,6 +495,8 @@ int cmd_tag(int argc, const char **argv, const char *prefix)\n \t\tOPT_CALLBACK_F('m', \"message\", &msg, N_(\"message\"),\n \t\t\t       N_(\"tag message\"), PARSE_OPT_NONEG, parse_msg_arg),\n \t\tOPT_FILENAME('F', \"file\", &msgfile, N_(\"read message from file\")),\n+\t\tOPT_PASSTHRU_ARGV(0, \"trailer\", &trailer_args, N_(\"trailer\"),\n+\t\t\t\t  N_(\"add custom trailer(s)\"), PARSE_OPT_NONEG),\n \t\tOPT_BOOL('e', \"edit\", &edit_flag, N_(\"force edit of tag message\")),\n \t\tOPT_BOOL('s', \"sign\", &opt.sign, N_(\"annotated and GPG-signed tag\")),\n \t\tOPT_CLEANUP(&cleanup_arg),\n@@ -548,7 +566,8 @@ int cmd_tag(int argc, const char **argv, const char *prefix)\n \t\topt.sign = 1;\n \t\tset_signing_key(keyid);\n \t}\n-\tcreate_tag_object = (opt.sign || annotate || msg.given || msgfile);\n+\tcreate_tag_object = (opt.sign || annotate || msg.given || msgfile ||\n+\t\t\t     edit_flag || trailer_args.nr);\n \n \tif ((create_tag_object || force) && (cmdmode != 0))\n \t\tusage_with_options(git_tag_usage, options);\n@@ -654,7 +673,7 @@ int cmd_tag(int argc, const char **argv, const char *prefix)\n \t\t\topt.sign = 1;\n \t\tpath = git_pathdup(\"TAG_EDITMSG\");\n \t\tcreate_tag(&object, object_ref, tag, &buf, &opt, &prev, &object,\n-\t\t\t   path);\n+\t\t\t   &trailer_args, path);\n \t}\n \n \ttransaction = ref_transaction_begin(&err);\n@@ -686,6 +705,7 @@ int cmd_tag(int argc, const char **argv, const char *prefix)\n \tstrbuf_release(&reflog_msg);\n \tstrbuf_release(&msg.buf);\n \tstrbuf_release(&err);\n+\tstrvec_clear(&trailer_args);\n \tfree(msgfile);\n \treturn ret;\n }\ndiff --git a/t/t7004-tag.sh b/t/t7004-tag.sh\nindex 696866d7794..fa6336edf98 100755\n--- a/t/t7004-tag.sh\n+++ b/t/t7004-tag.sh\n@@ -668,6 +668,115 @@ test_expect_success \\\n \ttest_cmp expect actual\n '\n \n+# trailers\n+\n+test_expect_success 'create tag with -m and --trailer' '\n+\tget_tag_header tag-with-inline-message-and-trailers $commit commit $time >expect &&\n+\tcat >>expect <<-\\EOF &&\n+\tcreate tag with trailers\n+\n+\tmy-trailer: here\n+\talt-trailer: there\n+\tEOF\n+\tgit tag -m \"create tag with trailers\" \\\n+\t\t--trailer my-trailer=here \\\n+\t\t--trailer alt-trailer=there \\\n+\t\ttag-with-inline-message-and-trailers &&\n+\tget_tag_msg tag-with-inline-message-and-trailers >actual &&\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'list tag extracting trailers' '\n+\tcat >expect <<-\\EOF &&\n+\tmy-trailer: here\n+\talt-trailer: there\n+\n+\tEOF\n+\tgit tag --list --format=\"%(trailers)\" tag-with-inline-message-and-trailers >actual &&\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'create tag with -F and --trailer' '\n+\techo \"create tag from message file using --trailer\" >messagefilewithnotrailers &&\n+\tget_tag_header tag-with-file-message-and-trailers $commit commit $time >expect &&\n+\tcat >>expect <<-\\EOF &&\n+\tcreate tag from message file using --trailer\n+\n+\tmy-trailer: here\n+\talt-trailer: there\n+\tEOF\n+\tgit tag -F messagefilewithnotrailers \\\n+\t\t--trailer my-trailer=here \\\n+\t\t--trailer alt-trailer=there \\\n+\t\ttag-with-file-message-and-trailers &&\n+\tget_tag_msg tag-with-file-message-and-trailers >actual &&\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'create tag with -m and --trailer and --edit' '\n+\twrite_script fakeeditor <<-\\EOF &&\n+\tsed -e \"1s/^/EDITED: /g\" <\"$1\" >\"$1-\"\n+\tmv \"$1-\" \"$1\"\n+\tEOF\n+\tget_tag_header tag-with-edited-inline-message-and-trailers $commit commit $time >expect &&\n+\tcat >>expect <<-\\EOF &&\n+\tEDITED: create tag with trailers\n+\n+\tmy-trailer: here\n+\talt-trailer: there\n+\tEOF\n+\tGIT_EDITOR=./fakeeditor git tag --edit \\\n+\t\t-m \"create tag with trailers\" \\\n+\t\t--trailer my-trailer=here \\\n+\t\t--trailer alt-trailer=there \\\n+\t\ttag-with-edited-inline-message-and-trailers &&\n+\tget_tag_msg tag-with-edited-inline-message-and-trailers >actual &&\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'create tag with -F and --trailer and --edit' '\n+\techo \"create tag from message file using --trailer\" >messagefilewithnotrailers &&\n+\tget_tag_header tag-with-edited-file-message-and-trailers $commit commit $time >expect &&\n+\tcat >>expect <<-\\EOF &&\n+\tEDITED: create tag from message file using --trailer\n+\n+\tmy-trailer: here\n+\talt-trailer: there\n+\tEOF\n+\tGIT_EDITOR=./fakeeditor git tag --edit \\\n+\t\t-F messagefilewithnotrailers \\\n+\t\t--trailer my-trailer=here \\\n+\t\t--trailer alt-trailer=there \\\n+\t\ttag-with-edited-file-message-and-trailers &&\n+\tget_tag_msg tag-with-edited-file-message-and-trailers >actual &&\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'create annotated tag and force editor when only --trailer is given' '\n+\twrite_script fakeeditor <<-\\EOF &&\n+\techo \"add a line\" >\"$1-\"\n+\tcat <\"$1\" >>\"$1-\"\n+\tmv \"$1-\" \"$1\"\n+\tEOF\n+\tget_tag_header tag-with-trailers-and-no-message $commit commit $time >expect &&\n+\tcat >>expect <<-\\EOF &&\n+\tadd a line\n+\n+\tmy-trailer: here\n+\talt-trailer: there\n+\tEOF\n+\tGIT_EDITOR=./fakeeditor git tag \\\n+\t\t--trailer my-trailer=here \\\n+\t\t--trailer alt-trailer=there \\\n+\t\ttag-with-trailers-and-no-message &&\n+\tget_tag_msg tag-with-trailers-and-no-message >actual &&\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'bad editor causes panic when only --trailer is given' '\n+\ttest_must_fail env GIT_EDITOR=false git tag --trailer my-trailer=here tag-will-not-exist\n+'\n+\n # listing messages for annotated non-signed tags:\n \n test_expect_success \\\n@@ -810,6 +919,11 @@ test_expect_success 'git tag --format with ahead-behind' '\n \trefs/tags/tag-lines 0 1 !\n \trefs/tags/tag-one-line 0 1 !\n \trefs/tags/tag-right 0 0 !\n+\trefs/tags/tag-with-edited-file-message-and-trailers 0 1 !\n+\trefs/tags/tag-with-edited-inline-message-and-trailers 0 1 !\n+\trefs/tags/tag-with-file-message-and-trailers 0 1 !\n+\trefs/tags/tag-with-inline-message-and-trailers 0 1 !\n+\trefs/tags/tag-with-trailers-and-no-message 0 1 !\n \trefs/tags/tag-zero-lines 0 1 !\n \tEOF\n \tgit tag -l --format=\"%(refname) %(ahead-behind:HEAD) !\" >actual 2>err &&\n-- \ngitgitgadget\n"},{"id":"494100","messageId":"CAJdN7Kj0nW2TSsEO4W4A6Oy_1sG-Tpz43d5+883G0LMvWeL2Gg@mail.gmail.com","threadId":"61376","inReplyTo":"xmqq4jbixzg6.fsf@gitster.g","subject":"Re: [PATCH v3 2/3] builtin/tag.c: add --trailer arg","fromName":"John Passaro","fromEmail":"john.a.passaro@gmail.com","sentAt":"2024-05-05T18:59:13Z","receivedAt":"2024-05-05T18:59:52Z","isPatch":true,"sender":{"key":"john.a.passaro@gmail.com","avatar":"https://avatars.githubusercontent.com/u/6754005?v=4"},"body":"On Tue, Apr 30, 2024 at 6:23 PM Junio C Hamano <gitster@pobox.com> wrote:\n>\n> John Passaro <john.a.passaro@gmail.com> writes:\n>\n> > There's also project-specific trailers. For example, on my team,\n> > we use \"Deploy-Strategy: ...\" to tell CICD what deployment routines to run. This\n> > is pretty specific to us but worth calling out. Maybe could translate to a\n> > documentation example with something like \"<Project-specific-trailer>: foo\"\n>\n> The last one that uses placeholders for both trailer tag and value\n> may be generic enough.\n>\n\ndone\n\n\n> > However, in service of helping users find workarounds, shouldn't we tell them\n> > --trailer may be the culprit?\n> >\n> >> Failed to read '%s'. Try again without --trailer (use -e or -F to add trailers manually).\n>\n> I dunno.\n>\n> If -m/-F that wrote the original using the open/write_or_die/close\n> sequence succeeded, the \"amend_file\" thing successfully spawned\n> \"interpret-trailers --in-place\" and got control back, yet we fail\n> to read that message back, it does not smell like a failure with\n> that \"--trailer\" option to me.  A failure with \"--trailer\" that\n> could be worked around would have been caught in \"amend_file\" thing,\n> before the control reaches this point, no?\n>\nI considered \"Failed to read '%s' after adding trailers to it\". But on\nreflection it's really pretty difficult to imagine why this would happen at\nall - like it could only really happen if the call to git-interpret-trailers\nsomehow corrupted the file. Probably best, as you say, not to speculate\nabout the cause when telling the user what happened and potentially\nmislead them.\n\nPlus this wording reduces load on the l10n teams in case this patch is\naccepted, and allows future changes to enable this code path for other\nreasons with minimal change.\n"},{"id":"494109","messageId":"ZjhtZb5nJZ8vlvLw@tanuki","threadId":"61376","inReplyTo":"pull.1723.v5.git.1714934950.gitgitgadget@gmail.com","subject":"Re: [PATCH v5 0/3] builtin/tag.c: add --trailer option","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2024-05-06T05:40:53Z","receivedAt":"2024-05-06T05:40:58Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"On Sun, May 05, 2024 at 06:49:07PM +0000, John Passaro via GitGitGadget wrote:\n> 5th follow-up patch taking welcome feedback from Patrick and JCH. Net new\n> changes include suggested tweaks in documentation, error messaging, code\n> formatting, and patch description.\n> \n> Since git-tag --list --format=\"%(trailers)\" can interpret trailers from\n> annotated tag messages, it seems natural to support --trailer when writing a\n> new tag message.\n> \n> git-commit accomplishes this by taking --trailer arguments and passing them\n> to git-interpret-trailer. This patch series refactors that logic and uses it\n> to implement --trailer on git-tag.\n\nThis version looks good to me, thanks!\n\nPatrick\n"},{"id":"494172","messageId":"xmqq34quzv3s.fsf@gitster.g","threadId":"61376","inReplyTo":"ZjhtZb5nJZ8vlvLw@tanuki","subject":"Re: [PATCH v5 0/3] builtin/tag.c: add --trailer option","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2024-05-06T17:52:07Z","receivedAt":"2024-05-06T17:52:10Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Patrick Steinhardt <ps@pks.im> writes:\n\n> On Sun, May 05, 2024 at 06:49:07PM +0000, John Passaro via GitGitGadget wrote:\n>> 5th follow-up patch taking welcome feedback from Patrick and JCH. Net new\n>> changes include suggested tweaks in documentation, error messaging, code\n>> formatting, and patch description.\n>> \n>> Since git-tag --list --format=\"%(trailers)\" can interpret trailers from\n>> annotated tag messages, it seems natural to support --trailer when writing a\n>> new tag message.\n>> \n>> git-commit accomplishes this by taking --trailer arguments and passing them\n>> to git-interpret-trailer. This patch series refactors that logic and uses it\n>> to implement --trailer on git-tag.\n>\n> This version looks good to me, thanks!\n\nThanks.  Will queue.\n"},{"id":"494277","messageId":"CAJdN7KhFtjWZfuGPsu2gWy7Dqth=PsVpA_d8923aNUqmQu8Phg@mail.gmail.com","threadId":"61376","inReplyTo":"85f45a57f3529a90324ded07856fca610f7b5eda.1714934950.git.gitgitgadget@gmail.com","subject":"Re: [PATCH v5 1/3] builtin/commit: use ARGV macro to collect trailers","fromName":"John Passaro","fromEmail":"john.a.passaro@gmail.com","sentAt":"2024-05-07T15:38:29Z","receivedAt":"2024-05-07T15:39:08Z","isPatch":true,"sender":{"key":"john.a.passaro@gmail.com","avatar":"https://avatars.githubusercontent.com/u/6754005?v=4"},"body":"On Sun, May 5, 2024 at 2:49 PM John Passaro via GitGitGadget\n<gitgitgadget@gmail.com> wrote:\n>\n\nThank you for queuing the patch series! Hopeful and excited that this\nmay go in a future release.\n\n> From: John Passaro <john.a.passaro@gmail.com>\n>\n> Replace git-commit's callback for --trailer with the standard\n> OPT_PASSTHRU_ARGV macro. The callback only adds its values to a strvec\n> and sanity-checking that `unset` is always false; both of these are\n> already implemented in the parse-option API.\n>\n> Signed-off-by: John Passaro <john.a.passaro@gmail.com>\n\nLooking over the patch series I notice that I left a grammar error in\nthis description,\nif the patch makes it to the \"next\" branch I hope you can amend the\nmessage first,\nas follows:\n\nReplace git-commit's callback for --trailer with the standard\nOPT_PASSTHRU_ARGV macro. The callback only adds its values to a strvec\nand sanity-checks that `unset` is always false; both of these are\nalready implemented in the parse-option API.\n\n> ---\n>  builtin/commit.c | 10 +---------\n>  1 file changed, 1 insertion(+), 9 deletions(-)\n>\n> diff --git a/builtin/commit.c b/builtin/commit.c\n> index 6e1484446b0..5a3248370db 100644\n> --- a/builtin/commit.c\n> +++ b/builtin/commit.c\n> @@ -142,14 +142,6 @@ static struct strbuf message = STRBUF_INIT;\n>\n>  static enum wt_status_format status_format = STATUS_FORMAT_UNSPECIFIED;\n>\n> -static int opt_pass_trailer(const struct option *opt, const char *arg, int unset)\n> -{\n> -       BUG_ON_OPT_NEG(unset);\n> -\n> -       strvec_pushl(opt->value, \"--trailer\", arg, NULL);\n> -       return 0;\n> -}\n> -\n>  static int opt_parse_porcelain(const struct option *opt, const char *arg, int unset)\n>  {\n>         enum wt_status_format *value = (enum wt_status_format *)opt->value;\n> @@ -1673,7 +1665,7 @@ int cmd_commit(int argc, const char **argv, const char *prefix)\n>                 OPT_STRING(0, \"fixup\", &fixup_message, N_(\"[(amend|reword):]commit\"), N_(\"use autosquash formatted message to fixup or amend/reword specified commit\")),\n>                 OPT_STRING(0, \"squash\", &squash_message, N_(\"commit\"), N_(\"use autosquash formatted message to squash specified commit\")),\n>                 OPT_BOOL(0, \"reset-author\", &renew_authorship, N_(\"the commit is authored by me now (used with -C/-c/--amend)\")),\n> -               OPT_CALLBACK_F(0, \"trailer\", &trailer_args, N_(\"trailer\"), N_(\"add custom trailer(s)\"), PARSE_OPT_NONEG, opt_pass_trailer),\n> +               OPT_PASSTHRU_ARGV(0, \"trailer\", &trailer_args, N_(\"trailer\"), N_(\"add custom trailer(s)\"), PARSE_OPT_NONEG),\n>                 OPT_BOOL('s', \"signoff\", &signoff, N_(\"add a Signed-off-by trailer\")),\n>                 OPT_FILENAME('t', \"template\", &template_file, N_(\"use specified template file\")),\n>                 OPT_BOOL('e', \"edit\", &edit_flag, N_(\"force edit of commit\")),\n> --\n> gitgitgadget\n>\n"},{"id":"494281","messageId":"xmqqttj9pn5m.fsf@gitster.g","threadId":"61376","inReplyTo":"CAJdN7KhFtjWZfuGPsu2gWy7Dqth=PsVpA_d8923aNUqmQu8Phg@mail.gmail.com","subject":"Re: [PATCH v5 1/3] builtin/commit: use ARGV macro to collect trailers","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2024-05-07T17:06:13Z","receivedAt":"2024-05-07T17:06:24Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"John Passaro <john.a.passaro@gmail.com> writes:\n\n>> From: John Passaro <john.a.passaro@gmail.com>\n>>\n>> Replace git-commit's callback for --trailer with the standard\n>> OPT_PASSTHRU_ARGV macro. The callback only adds its values to a strvec\n>> and sanity-checking that `unset` is always false; both of these are\n>> already implemented in the parse-option API.\n>>\n>> Signed-off-by: John Passaro <john.a.passaro@gmail.com>\n>\n> Looking over the patch series I notice that I left a grammar error in\n> this description,\n> if the patch makes it to the \"next\" branch I hope you can amend the\n> message first,\n> as follows:\n>\n> Replace git-commit's callback for --trailer with the standard\n> OPT_PASSTHRU_ARGV macro. The callback only adds its values to a strvec\n> and sanity-checks that `unset` is always false; both of these are\n> already implemented in the parse-option API.\n\nThanks for stopping me before the topic got merged to 'next'.\nAmended.\n"}]}