{"thread":{"id":"55288","subject":"[PATCH] [GSOC] commit: provides multiple common signatures","startedAt":"2021-03-11T07:16:54Z","lastAt":"2021-03-24T05:26:16Z","messageCount":84,"participants":["ZheNing Hu via GitGitGadget","Shourya Shukla","Junio C Hamano","ZheNing Hu","Christian Couder","Rafael Silva","Ævar Arnfjörð Bjarmason","Đoàn Trần Công Danh","Jeff King"],"isPatch":true,"patchVersion":1,"patchTotal":null},"messages":[{"id":"418832","messageId":"pull.901.git.1615446968597.gitgitgadget@gmail.com","threadId":"55288","inReplyTo":null,"subject":"[PATCH] [GSOC] commit: provides multiple common signatures","fromName":"ZheNing Hu via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2021-03-11T07:16:08Z","receivedAt":"2021-03-11T07:16:54Z","isPatch":true,"sender":{"key":"adlternative@gmail.com","avatar":"https://avatars.githubusercontent.com/u/58138461?v=4"},"body":"From: ZheNing Hu <adlternative@gmail.com>\n\nSimilar to \"Helped-by\", \"Reported-by\", \"Reviewed-by\", \"Mentored-by\"\nthese signatures are often seen in git commit messages. After\nreferring to the simple implementation of `commit --signoff`\nand `send-email -cc=\" commiter <email>\"`, I am considering\nwhether to provide multiple signature parameters from the\ncommand line. I think this might help maintainers and\ndevelopers directly uses the shell to provide these signatures\ninstead of multiple times repetitive writing those trailers\neach time.\n\nTo achieve this goal, i refactored the `append_signoff` design and\nprovided `append_message` and `append_message_string_list` interfaces,\nproviding new ways to generate those various signatures.\n\nUsers now can use `commit -H \"helper <eamil>\"` to generate \"Helped-by\" trailer,\n`commit -R \"reviewer <eamil>\"` to generate \"Reviewed-by\" trailer,\n`commit -r \"reporter <eamil> \"`to generate \"Reported-by\" trailer,\n`commit -M \"mentor <eamil>\"` to generate \"Mentored-by\" trailer.\n\nSigned-off-by: ZheNing Hu <adlternative@gmail.com>\n---\n    [GSOC] commit: provides multiple signatures from command line\n    \n    I don’t know if my idea will satisfy everyone, I'm also thinking about\n    whether we can provide a more generalized version (I think this idea can\n    be extended: because users and developers have other signature methods\n    that they want, such as \"Based-on-patch-by\") I hope someone can give me\n    pointers (on the correctness of ideas or codes)\n\nPublished-As: https://github.com/gitgitgadget/git/releases/tag/pr-901%2Fadlternative%2Fcommit-with-multiple-signatures-v1\nFetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-901/adlternative/commit-with-multiple-signatures-v1\nPull-Request: https://github.com/gitgitgadget/git/pull/901\n\n Documentation/git-commit.txt |  24 +++++++-\n builtin/commit.c             |  63 +++++++++++++++++++++\n sequencer.c                  |  40 +++++++++----\n sequencer.h                  |   4 ++\n t/t7502-commit-porcelain.sh  | 106 +++++++++++++++++++++++++++++++++++\n 5 files changed, 226 insertions(+), 11 deletions(-)\n\ndiff --git a/Documentation/git-commit.txt b/Documentation/git-commit.txt\nindex 17150fa7eabe..e1b528d70c1a 100644\n--- a/Documentation/git-commit.txt\n+++ b/Documentation/git-commit.txt\n@@ -14,7 +14,9 @@ SYNOPSIS\n \t   [--allow-empty-message] [--no-verify] [-e] [--author=<author>]\n \t   [--date=<date>] [--cleanup=<mode>] [--[no-]status]\n \t   [-i | -o] [--pathspec-from-file=<file> [--pathspec-file-nul]]\n-\t   [-S[<keyid>]] [--] [<pathspec>...]\n+\t   [-S[<keyid>]] [--] [<pathspec>...] [(-H|--helped-by)=<address>...]\n+\t   [(-R|--reviewed-by)=<address>...] [(-r|--reported-by)=<address>...]\n+\t   [(-M|--mentored)=<address>...]\n \n DESCRIPTION\n -----------\n@@ -166,6 +168,26 @@ The `-m` option is mutually exclusive with `-c`, `-C`, and `-F`.\n \n include::signoff-option.txt[]\n \n+-H=<address>...::\n+--helped-by=<address>...::\n+\tAdd one or more `Helped-by` trailer by the committer at the end of the commit\n+\tlog message.\n+\n+-R=<address>...::\n+--reviewed-by=<address>...::\n+\tAdd one or more `Reviewed-by` trailer by the committer at the end of the commit\n+\tlog message.\n+\n+-r=<address>...::\n+--reported-by=<address>...::\n+\tAdd one or more `Reported-by` trailer by the committer at the end of the commit\n+\tlog message.\n+\n+-M=<address>...::\n+--mentored-by=<address>...::\n+\tAdd one or more `Mentored-by` trailer by the committer at the end of the commit\n+\tlog message.\n+\n -n::\n --no-verify::\n \tThis option bypasses the pre-commit and commit-msg hooks.\ndiff --git a/builtin/commit.c b/builtin/commit.c\nindex 739110c5a7f6..4b312af03181 100644\n--- a/builtin/commit.c\n+++ b/builtin/commit.c\n@@ -113,6 +113,10 @@ static int config_commit_verbose = -1; /* unspecified */\n static int no_post_rewrite, allow_empty_message, pathspec_file_nul;\n static char *untracked_files_arg, *force_date, *ignore_submodule_arg, *ignored_arg;\n static char *sign_commit, *pathspec_from_file;\n+static struct string_list helped_by = STRING_LIST_INIT_NODUP;\n+static struct string_list mentored_by = STRING_LIST_INIT_NODUP;\n+static struct string_list reviewed_by = STRING_LIST_INIT_NODUP;\n+static struct string_list reported_by = STRING_LIST_INIT_NODUP;\n \n /*\n  * The default commit message cleanup mode will remove the lines\n@@ -829,6 +833,20 @@ static int prepare_to_commit(const char *index_file, const char *prefix,\n \tif (signoff)\n \t\tappend_signoff(&sb, ignore_non_trailer(sb.buf, sb.len), 0);\n \n+\tif(helped_by.items)\n+\t\tappend_message_string_list(&sb, \"Helped-by: \", &helped_by, ignore_non_trailer(sb.buf, sb.len), 0);\n+\tif(reviewed_by.items)\n+\t\tappend_message_string_list(&sb, \"Reviewed-by: \", &reviewed_by, ignore_non_trailer(sb.buf, sb.len), 0);\n+\tif(reported_by.items)\n+\t\tappend_message_string_list(&sb, \"Reported-by: \", &reported_by, ignore_non_trailer(sb.buf, sb.len), 0);\n+\tif(mentored_by.items)\n+\t\tappend_message_string_list(&sb, \"Mentored-by: \", &mentored_by, ignore_non_trailer(sb.buf, sb.len), 0);\n+\n+\tstring_list_clear(&helped_by, 0);\n+\tstring_list_clear(&reviewed_by, 0);\n+\tstring_list_clear(&reported_by, 0);\n+\tstring_list_clear(&mentored_by, 0);\n+\n \tif (fwrite(sb.buf, 1, sb.len, s->fp) < sb.len)\n \t\tdie_errno(_(\"could not write commit template\"));\n \n@@ -1490,6 +1508,42 @@ static int git_commit_config(const char *k, const char *v, void *cb)\n \treturn git_status_config(k, v, s);\n }\n \n+static int help_callback(const struct option *opt, const char *arg, int unset)\n+{\n+\tif (unset)\n+\t\tstring_list_clear(&helped_by, 0);\n+\telse\n+\t\tstring_list_append(&helped_by, arg);\n+\treturn 0;\n+}\n+\n+static int review_callback(const struct option *opt, const char *arg, int unset)\n+{\n+\tif (unset)\n+\t\tstring_list_clear(&reviewed_by, 0);\n+\telse\n+\t\tstring_list_append(&reviewed_by, arg);\n+\treturn 0;\n+}\n+\n+static int report_callback(const struct option *opt, const char *arg, int unset)\n+{\n+\tif (unset)\n+\t\tstring_list_clear(&reported_by, 0);\n+\telse\n+\t\tstring_list_append(&reported_by, arg);\n+\treturn 0;\n+}\n+\n+static int mentor_callback(const struct option *opt, const char *arg, int unset)\n+{\n+\tif (unset)\n+\t\tstring_list_clear(&mentored_by, 0);\n+\telse\n+\t\tstring_list_append(&mentored_by, arg);\n+\treturn 0;\n+}\n+\n int cmd_commit(int argc, const char **argv, const char *prefix)\n {\n \tstatic struct wt_status s;\n@@ -1507,6 +1561,10 @@ int cmd_commit(int argc, const char **argv, const char *prefix)\n \t\tOPT_STRING(0, \"fixup\", &fixup_message, N_(\"commit\"), N_(\"use autosquash formatted message to fixup 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('H', \"helped-by\", NULL, N_(\"email\"), N_(\"add a Helped-by trailer\"), help_callback),\n+\t\tOPT_CALLBACK('r', \"reported-by\", NULL, N_(\"email\"), N_(\"add a Reported-by trailer\"), report_callback),\n+\t\tOPT_CALLBACK('R', \"reviewed-by\", NULL, N_(\"email\"), N_(\"add a Reviewed-by trailer\"), review_callback),\n+\t\tOPT_CALLBACK('M', \"mentored-by\", NULL, N_(\"email\"), N_(\"add a Mentored-by trailer\"), mentor_callback),\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@@ -1561,6 +1619,11 @@ int cmd_commit(int argc, const char **argv, const char *prefix)\n \tstruct commit_extra_header *extra = NULL;\n \tstruct strbuf err = STRBUF_INIT;\n \n+\thelped_by.strdup_strings = 1;\n+\treviewed_by.strdup_strings = 1;\n+\treported_by.strdup_strings = 1;\n+\tmentored_by.strdup_strings = 1;\n+\n \tif (argc == 2 && !strcmp(argv[1], \"-h\"))\n \t\tusage_with_options(builtin_commit_usage, builtin_commit_options);\n \ndiff --git a/sequencer.c b/sequencer.c\nindex d2332d3e1787..528daf9df252 100644\n--- a/sequencer.c\n+++ b/sequencer.c\n@@ -4668,16 +4668,12 @@ int sequencer_pick_revisions(struct repository *r,\n \treturn res;\n }\n \n-void append_signoff(struct strbuf *msgbuf, size_t ignore_footer, unsigned flag)\n+void append_message(struct strbuf *msgbuf, struct strbuf *sob,\n+\t\t\tsize_t ignore_footer, unsigned flag)\n {\n \tunsigned no_dup_sob = flag & APPEND_SIGNOFF_DEDUP;\n-\tstruct strbuf sob = STRBUF_INIT;\n \tint has_footer;\n \n-\tstrbuf_addstr(&sob, sign_off_header);\n-\tstrbuf_addstr(&sob, fmt_name(WANT_COMMITTER_IDENT));\n-\tstrbuf_addch(&sob, '\\n');\n-\n \tif (!ignore_footer)\n \t\tstrbuf_complete_line(msgbuf);\n \n@@ -4685,11 +4681,11 @@ void append_signoff(struct strbuf *msgbuf, size_t ignore_footer, unsigned flag)\n \t * If the whole message buffer is equal to the sob, pretend that we\n \t * found a conforming footer with a matching sob\n \t */\n-\tif (msgbuf->len - ignore_footer == sob.len &&\n-\t    !strncmp(msgbuf->buf, sob.buf, sob.len))\n+\tif (msgbuf->len - ignore_footer == sob->len &&\n+\t    !strncmp(msgbuf->buf, sob->buf, sob->len))\n \t\thas_footer = 3;\n \telse\n-\t\thas_footer = has_conforming_footer(msgbuf, &sob, ignore_footer);\n+\t\thas_footer = has_conforming_footer(msgbuf, sob, ignore_footer);\n \n \tif (!has_footer) {\n \t\tconst char *append_newlines = NULL;\n@@ -4723,8 +4719,32 @@ void append_signoff(struct strbuf *msgbuf, size_t ignore_footer, unsigned flag)\n \n \tif (has_footer != 3 && (!no_dup_sob || has_footer != 2))\n \t\tstrbuf_splice(msgbuf, msgbuf->len - ignore_footer, 0,\n-\t\t\t\tsob.buf, sob.len);\n+\t\t\t\tsob->buf, sob->len);\n+}\n+\n+void append_signoff(struct strbuf *msgbuf, size_t ignore_footer, unsigned flag)\n+{\n+\tstruct strbuf sob = STRBUF_INIT;\n+\tstrbuf_addstr(&sob, sign_off_header);\n+\tstrbuf_addstr(&sob, fmt_name(WANT_COMMITTER_IDENT));\n+\tstrbuf_addch(&sob, '\\n');\n+\tappend_message(msgbuf, &sob, ignore_footer, flag);\n+\tstrbuf_release(&sob);\n+}\n \n+void append_message_string_list(struct strbuf *msgbuf, const char *header,\n+\t\tstruct string_list *sobs, size_t ignore_footer, unsigned flag) {\n+\tint i;\n+\tstruct strbuf sob = STRBUF_INIT;\n+\n+\tfor ( i = 0; i < sobs->nr; i++)\n+\t{\n+\t\tstrbuf_addstr(&sob, header);\n+\t\tstrbuf_addstr(&sob, sobs->items[i].string);\n+\t\tstrbuf_addch(&sob, '\\n');\n+\t\tappend_message(msgbuf, &sob, ignore_footer, flag);\n+\t\tstrbuf_reset(&sob);\n+\t}\n \tstrbuf_release(&sob);\n }\n \ndiff --git a/sequencer.h b/sequencer.h\nindex f8b2e4ab8527..b24e274f4c62 100644\n--- a/sequencer.h\n+++ b/sequencer.h\n@@ -174,6 +174,10 @@ int todo_list_rearrange_squash(struct todo_list *todo_list);\n  * and the new signoff will be spliced into the buffer before those bytes.\n  */\n void append_signoff(struct strbuf *msgbuf, size_t ignore_footer, unsigned flag);\n+void append_message(struct strbuf *msgbuf, struct strbuf *sob,\n+\t\tsize_t ignore_footer, unsigned flag);\n+void append_message_string_list(struct strbuf *msgbuf, const char*header,\n+\t\tstruct string_list *sobs, size_t ignore_footer, unsigned flag);\n \n void append_conflicts_hint(struct index_state *istate,\n \t\tstruct strbuf *msgbuf, enum commit_msg_cleanup_mode cleanup_mode);\ndiff --git a/t/t7502-commit-porcelain.sh b/t/t7502-commit-porcelain.sh\nindex 6396897cc818..40823152a51c 100755\n--- a/t/t7502-commit-porcelain.sh\n+++ b/t/t7502-commit-porcelain.sh\n@@ -154,6 +154,112 @@ test_expect_success 'sign off' '\n \n '\n \n+test_expect_success 'helped-by' '\n+\n+\t>file1 &&\n+\tgit add file1 &&\n+\tgit commit --helped-by=\"foo <bar@frotz>\" \\\n+\t--helped-by=\"foo2 <bar2@frotz>\" -m \"thank you\" &&\n+\tgit cat-file commit HEAD >commit.msg &&\n+\tsed -ne \"s/Helped-by: //p\" commit.msg >actual &&\n+\tcat >expected <<-\\EOF &&\n+\tfoo <bar@frotz>\n+\tfoo2 <bar2@frotz>\n+\tEOF\n+\ttest_cmp expected actual\n+\n+'\n+\n+test_expect_success 'reported-by' '\n+\n+\t>file2 &&\n+\tgit add file2 &&\n+\tgit commit --reported-by=\"foo <bar@frotz>\" \\\n+\t--reported-by=\"foo2 <bar2@frotz>\" -m \"thank you\" &&\n+\tgit cat-file commit HEAD >commit.msg &&\n+\tsed -ne \"s/Reported-by: //p\" commit.msg >actual &&\n+\tcat >expected <<-\\EOF &&\n+\tfoo <bar@frotz>\n+\tfoo2 <bar2@frotz>\n+\tEOF\n+\ttest_cmp expected actual\n+\n+'\n+\n+test_expect_success 'reviewed-by' '\n+\n+\t>file3 &&\n+\tgit add file3 &&\n+\tgit commit --reviewed-by=\"foo <bar@frotz>\" \\\n+\t--reviewed-by=\"foo2 <bar2@frotz>\" -m \"thank you\" &&\n+\tgit cat-file commit HEAD >commit.msg &&\n+\tsed -ne \"s/Reviewed-by: //p\" commit.msg >actual &&\n+\tcat >expected <<-\\EOF &&\n+\tfoo <bar@frotz>\n+\tfoo2 <bar2@frotz>\n+\tEOF\n+\ttest_cmp expected actual\n+\n+'\n+\n+test_expect_success 'mentored-by' '\n+\n+\t>file4 &&\n+\tgit add file4 &&\n+\tgit commit --mentored-by=\"foo <bar@frotz>\" \\\n+\t--mentored-by=\"foo2 <bar2@frotz>\" -m \"thank you\" &&\n+\tgit cat-file commit HEAD >commit.msg &&\n+\tsed -ne \"s/Mentored-by: //p\" commit.msg >actual &&\n+\tcat >expected <<-\\EOF &&\n+\tfoo <bar@frotz>\n+\tfoo2 <bar2@frotz>\n+\tEOF\n+\ttest_cmp expected actual\n+\n+'\n+\n+test_expect_success 'multiple signatures' '\n+\n+\t>file5 &&\n+\tgit add file5 &&\n+\tgit commit --helped-by=\"foo <bar@frotz>\" \\\n+\t--reviewed-by=\"foo2 <bar2@frotz>\" \\\n+\t--mentored-by=\"foo3 <bar3@frotz>\" \\\n+\t--reported-by=\"foo4 <bar4@frotz>\" -s -m \"thank you\" &&\n+\tgit cat-file commit HEAD >commit.msg &&\n+\tsed -e \"1,7d\" commit.msg >actual &&\n+\tcat >expected <<-\\EOF &&\n+\tSigned-off-by: C O Mitter <committer@example.com>\n+\tHelped-by: foo <bar@frotz>\n+\tReviewed-by: foo2 <bar2@frotz>\n+\tReported-by: foo4 <bar4@frotz>\n+\tMentored-by: foo3 <bar3@frotz>\n+\tEOF\n+\ttest_cmp expected actual\n+\n+'\n+\n+test_expect_success 'multiple signatures (use abbreviations)' '\n+\n+\t>file6 &&\n+\tgit add file6 &&\n+\tgit commit -H \"foo <bar@frotz>\" \\\n+\t-R \"foo2 <bar2@frotz>\" \\\n+\t-M \"foo3 <bar3@frotz>\" \\\n+\t-r \"foo4 <bar4@frotz>\" -s -m \"thank you\" &&\n+\tgit cat-file commit HEAD >commit.msg &&\n+\tsed -e \"1,7d\" commit.msg >actual &&\n+\tcat >expected <<-\\EOF &&\n+\tSigned-off-by: C O Mitter <committer@example.com>\n+\tHelped-by: foo <bar@frotz>\n+\tReviewed-by: foo2 <bar2@frotz>\n+\tReported-by: foo4 <bar4@frotz>\n+\tMentored-by: foo3 <bar3@frotz>\n+\tEOF\n+\ttest_cmp expected actual\n+\n+'\n+\n test_expect_success 'multiple -m' '\n \n \t>negative &&\n\nbase-commit: 13d7ab6b5d7929825b626f050b62a11241ea4945\n-- \ngitgitgadget\n"},{"id":"418853","messageId":"20210311150304.GA8169@konoha","threadId":"55288","inReplyTo":"pull.901.git.1615446968597.gitgitgadget@gmail.com","subject":"Re: [PATCH] [GSOC] commit: provides multiple common signatures","fromName":"Shourya Shukla","fromEmail":"periperidip@gmail.com","sentAt":"2021-03-11T15:03:04Z","receivedAt":"2021-03-11T15:04:02Z","isPatch":true,"sender":{"key":"periperidip@gmail.com","avatar":null},"body":"Hey!\n\nThe idea seems very useful to me though I am not sure what others feel\nabout this and whether Git is ready for such a thing or not. Keeping\nthis concern aside, I will still review the patch due to my own interest\nin it as well.\n\nOn 11/03 07:16, ZheNing Hu via GitGitGadget wrote:\n> From: ZheNing Hu <adlternative@gmail.com>\n> \n> Similar to \"Helped-by\", \"Reported-by\", \"Reviewed-by\", \"Mentored-by\"\n> these signatures are often seen in git commit messages. After\n\nI think it will be better to rephrase the line so that it is easier to\nunderstand. It took me a couple of reads to figure out what you meant.\n\nSomething like:\n\nSimilar to \"Signed-off-by\", trailers such as \"Reported-by\", \"Helped-by\"\nand \"Mentored-by\" are also seen in Git commits.\n\n> referring to the simple implementation of `commit --signoff`\n> and `send-email -cc=\" commiter <email>\"`, I am considering\n> whether to provide multiple signature parameters from the\n> command line. I think this might help maintainers and\n> developers directly uses the shell to provide these signatures\n> instead of multiple times repetitive writing those trailers\n> each time.\n\nThe above para is more appropriate for a cover letter than a commit\nmessage. Your thought process for the patch you sent is equally valuable\nbut this does not belong in the commit message. Commit messages are more\nabout what you did and any nuances that follow. You get me?\n\nUse 'git format-patch --cover-letter <...>' to create a cover letter for\nyour patch.\n\n> To achieve this goal, i refactored the `append_signoff` design and\n> provided `append_message` and `append_message_string_list` interfaces,\n> providing new ways to generate those various signatures.\n\ns/i/I\n\nAlso, commit messages are generally written in the imperative tense when\ndesciribing what you have done in the commit.\n\n> Users now can use `commit -H \"helper <eamil>\"` to generate \"Helped-by\" trailer,\n> `commit -R \"reviewer <eamil>\"` to generate \"Reviewed-by\" trailer,\n> `commit -r \"reporter <eamil> \"`to generate \"Reported-by\" trailer,\n> `commit -M \"mentor <eamil>\"` to generate \"Mentored-by\" trailer.\n\nMultiple typos.\n\n> Signed-off-by: ZheNing Hu <adlternative@gmail.com>\n\nSo, an improved commit message could be:\n\n-----8<-----\ncommit: support multiple trailers from the command line\n\nHistorically, Git has supported the 'Signed-off-by' commit trailer\nusing the '--signoff' and the '-s' option from the command line. Extend\nthis list to include other commonly used trailers viz., \"Helped-by\",\n\"Reviewed-by\", \"Reported-by\" and \"Mentored-by\". Introduce options '-H',\n'-R', '-r' and '-M' corresponding to the aforementioned trailers.\n\nWhile at it, add tests in the script 't7502-commit-porcelain.sh' and add\ninformation regarding these options in the Documentation of 'git\ncommit'.\n----->8-----\n\n>     [GSOC] commit: provides multiple signatures from command line\n>     \n>     I don’t know if my idea will satisfy everyone, I'm also thinking about\n>     whether we can provide a more generalized version (I think this idea can\n>     be extended: because users and developers have other signature methods\n>     that they want, such as \"Based-on-patch-by\") I hope someone can give me\n>     pointers (on the correctness of ideas or codes)\n\nIt will be great if we let the user add customised options for the\nrespective trailers but I think that trying to cater to such a large\naudience will only complicate the whole process and decrease the support\navialable for the options/command. Apart from this, I think that the\ntrailer \"Reviewed-by\" may not be that widely used and it would be better\nif we remove it from this patch.\n\n>  Documentation/git-commit.txt |  24 +++++++-\n>  builtin/commit.c             |  63 +++++++++++++++++++++\n>  sequencer.c                  |  40 +++++++++----\n>  sequencer.h                  |   4 ++\n>  t/t7502-commit-porcelain.sh  | 106 +++++++++++++++++++++++++++++++++++\n>  5 files changed, 226 insertions(+), 11 deletions(-)\n> \n> diff --git a/Documentation/git-commit.txt b/Documentation/git-commit.txt\n> index 17150fa7eabe..e1b528d70c1a 100644\n> --- a/Documentation/git-commit.txt\n> +++ b/Documentation/git-commit.txt\n> @@ -14,7 +14,9 @@ SYNOPSIS\n>  \t   [--allow-empty-message] [--no-verify] [-e] [--author=<author>]\n>  \t   [--date=<date>] [--cleanup=<mode>] [--[no-]status]\n>  \t   [-i | -o] [--pathspec-from-file=<file> [--pathspec-file-nul]]\n> -\t   [-S[<keyid>]] [--] [<pathspec>...]\n> +\t   [-S[<keyid>]] [--] [<pathspec>...] [(-H|--helped-by)=<address>...]\n> +\t   [(-R|--reviewed-by)=<address>...] [(-r|--reported-by)=<address>...]\n> +\t   [(-M|--mentored)=<address>...]\n>  \n>  DESCRIPTION\n>  -----------\n> @@ -166,6 +168,26 @@ The `-m` option is mutually exclusive with `-c`, `-C`, and `-F`.\n>  \n>  include::signoff-option.txt[]\n>  \n> +-H=<address>...::\n> +--helped-by=<address>...::\n> +\tAdd one or more `Helped-by` trailer by the committer at the end of the commit\n> +\tlog message.\n> +\n> +-R=<address>...::\n> +--reviewed-by=<address>...::\n> +\tAdd one or more `Reviewed-by` trailer by the committer at the end of the commit\n> +\tlog message.\n> +\n> +-r=<address>...::\n> +--reported-by=<address>...::\n> +\tAdd one or more `Reported-by` trailer by the committer at the end of the commit\n> +\tlog message.\n> +\n> +-M=<address>...::\n> +--mentored-by=<address>...::\n> +\tAdd one or more `Mentored-by` trailer by the committer at the end of the commit\n> +\tlog message.\n\nOh! I did not think you had added long forms of these options and was\nabout to comment on that. Good that you added. Do talk about them as\nwell in the commit message.\n\n>  -n::\n>  --no-verify::\n>  \tThis option bypasses the pre-commit and commit-msg hooks.\n> diff --git a/builtin/commit.c b/builtin/commit.c\n> index 739110c5a7f6..4b312af03181 100644\n> --- a/builtin/commit.c\n> +++ b/builtin/commit.c\n> @@ -113,6 +113,10 @@ static int config_commit_verbose = -1; /* unspecified */\n>  static int no_post_rewrite, allow_empty_message, pathspec_file_nul;\n>  static char *untracked_files_arg, *force_date, *ignore_submodule_arg, *ignored_arg;\n>  static char *sign_commit, *pathspec_from_file;\n> +static struct string_list helped_by = STRING_LIST_INIT_NODUP;\n> +static struct string_list mentored_by = STRING_LIST_INIT_NODUP;\n> +static struct string_list reviewed_by = STRING_LIST_INIT_NODUP;\n> +static struct string_list reported_by = STRING_LIST_INIT_NODUP;\n\nGood that you kept the variables as 'string_list's instead of a usual\n'char*'.\n\n>  /*\n>   * The default commit message cleanup mode will remove the lines\n> @@ -829,6 +833,20 @@ static int prepare_to_commit(const char *index_file, const char *prefix,\n>  \tif (signoff)\n>  \t\tappend_signoff(&sb, ignore_non_trailer(sb.buf, sb.len), 0);\n>  \n> +\tif(helped_by.items)\n> +\t\tappend_message_string_list(&sb, \"Helped-by: \", &helped_by, ignore_non_trailer(sb.buf, sb.len), 0);\n> +\tif(reviewed_by.items)\n> +\t\tappend_message_string_list(&sb, \"Reviewed-by: \", &reviewed_by, ignore_non_trailer(sb.buf, sb.len), 0);\n> +\tif(reported_by.items)\n> +\t\tappend_message_string_list(&sb, \"Reported-by: \", &reported_by, ignore_non_trailer(sb.buf, sb.len), 0);\n> +\tif(mentored_by.items)\n> +\t\tappend_message_string_list(&sb, \"Mentored-by: \", &mentored_by, ignore_non_trailer(sb.buf, sb.len), 0);\n\nI think this part will look prettier if you wrap around the text. For\ncode segments, the wrap around limit is 80 chars. For commit messages\nits 72 chars. Wrap around means that as soon as you hit N number of\ncharacters, you proceed to the next(new) line.\n\n> +\tstring_list_clear(&helped_by, 0);\n> +\tstring_list_clear(&reviewed_by, 0);\n> +\tstring_list_clear(&reported_by, 0);\n> +\tstring_list_clear(&mentored_by, 0);\n> +\n>  \tif (fwrite(sb.buf, 1, sb.len, s->fp) < sb.len)\n>  \t\tdie_errno(_(\"could not write commit template\"));\n>  \n> @@ -1490,6 +1508,42 @@ static int git_commit_config(const char *k, const char *v, void *cb)\n>  \treturn git_status_config(k, v, s);\n>  }\n>  \n> +static int help_callback(const struct option *opt, const char *arg, int unset)\n> +{\n> +\tif (unset)\n> +\t\tstring_list_clear(&helped_by, 0);\n> +\telse\n> +\t\tstring_list_append(&helped_by, arg);\n> +\treturn 0;\n> +}\n> +\n> +static int review_callback(const struct option *opt, const char *arg, int unset)\n> +{\n> +\tif (unset)\n> +\t\tstring_list_clear(&reviewed_by, 0);\n> +\telse\n> +\t\tstring_list_append(&reviewed_by, arg);\n> +\treturn 0;\n> +}\n> +\n> +static int report_callback(const struct option *opt, const char *arg, int unset)\n> +{\n> +\tif (unset)\n> +\t\tstring_list_clear(&reported_by, 0);\n> +\telse\n> +\t\tstring_list_append(&reported_by, arg);\n> +\treturn 0;\n> +}\n> +\n> +static int mentor_callback(const struct option *opt, const char *arg, int unset)\n> +{\n> +\tif (unset)\n> +\t\tstring_list_clear(&mentored_by, 0);\n> +\telse\n> +\t\tstring_list_append(&mentored_by, arg);\n> +\treturn 0;\n> +}\n>  int cmd_commit(int argc, const char **argv, const char *prefix)\n>  {\n>  \tstatic struct wt_status s;\n> @@ -1507,6 +1561,10 @@ int cmd_commit(int argc, const char **argv, const char *prefix)\n>  \t\tOPT_STRING(0, \"fixup\", &fixup_message, N_(\"commit\"), N_(\"use autosquash formatted message to fixup 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('H', \"helped-by\", NULL, N_(\"email\"), N_(\"add a Helped-by trailer\"), help_callback),\n> +\t\tOPT_CALLBACK('r', \"reported-by\", NULL, N_(\"email\"), N_(\"add a Reported-by trailer\"), report_callback),\n> +\t\tOPT_CALLBACK('R', \"reviewed-by\", NULL, N_(\"email\"), N_(\"add a Reviewed-by trailer\"), review_callback),\n> +\t\tOPT_CALLBACK('M', \"mentored-by\", NULL, N_(\"email\"), N_(\"add a Mentored-by trailer\"), mentor_callback),\n\nIt would be lovely if you could line wrap these above additions too!\n\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> @@ -1561,6 +1619,11 @@ int cmd_commit(int argc, const char **argv, const char *prefix)\n>  \tstruct commit_extra_header *extra = NULL;\n>  \tstruct strbuf err = STRBUF_INIT;\n>  \n> +\thelped_by.strdup_strings = 1;\n> +\treviewed_by.strdup_strings = 1;\n> +\treported_by.strdup_strings = 1;\n> +\tmentored_by.strdup_strings = 1;\n> +\n>  \tif (argc == 2 && !strcmp(argv[1], \"-h\"))\n>  \t\tusage_with_options(builtin_commit_usage, builtin_commit_options);\n>  \n> diff --git a/sequencer.c b/sequencer.c\n> index d2332d3e1787..528daf9df252 100644\n> --- a/sequencer.c\n> +++ b/sequencer.c\n> @@ -4668,16 +4668,12 @@ int sequencer_pick_revisions(struct repository *r,\n>  \treturn res;\n>  }\n>  \n> -void append_signoff(struct strbuf *msgbuf, size_t ignore_footer, unsigned flag)\n> +void append_message(struct strbuf *msgbuf, struct strbuf *sob,\n> +\t\t\tsize_t ignore_footer, unsigned flag)\n\nIts nice to see that you generalised the pre-exisiting function instead\nof creating a new one(s) for the new trailers. Good.\n\nIt will be better to name 'struct strbuf *sob' to something more\ngeneric, maybe 'struct strbuf *trailer' since 'sob' stands for\n'Signed-off-by' and since the function is becoming more generic, its\ncontents should too.\n\n>  {\n>  \tunsigned no_dup_sob = flag & APPEND_SIGNOFF_DEDUP;\n> -\tstruct strbuf sob = STRBUF_INIT;\n>  \tint has_footer;\n>  \n> -\tstrbuf_addstr(&sob, sign_off_header);\n> -\tstrbuf_addstr(&sob, fmt_name(WANT_COMMITTER_IDENT));\n> -\tstrbuf_addch(&sob, '\\n');\n> -\n>  \tif (!ignore_footer)\n>  \t\tstrbuf_complete_line(msgbuf);\n>  \n> @@ -4685,11 +4681,11 @@ void append_signoff(struct strbuf *msgbuf, size_t ignore_footer, unsigned flag)\n>  \t * If the whole message buffer is equal to the sob, pretend that we\n>  \t * found a conforming footer with a matching sob\n>  \t */\n> -\tif (msgbuf->len - ignore_footer == sob.len &&\n> -\t    !strncmp(msgbuf->buf, sob.buf, sob.len))\n> +\tif (msgbuf->len - ignore_footer == sob->len &&\n> +\t    !strncmp(msgbuf->buf, sob->buf, sob->len))\n>  \t\thas_footer = 3;\n>  \telse\n> -\t\thas_footer = has_conforming_footer(msgbuf, &sob, ignore_footer);\n> +\t\thas_footer = has_conforming_footer(msgbuf, sob, ignore_footer);\n\nAgain, rename the variables.\n\n>  \tif (!has_footer) {\n>  \t\tconst char *append_newlines = NULL;\n> @@ -4723,8 +4719,32 @@ void append_signoff(struct strbuf *msgbuf, size_t ignore_footer, unsigned flag)\n>  \n>  \tif (has_footer != 3 && (!no_dup_sob || has_footer != 2))\n>  \t\tstrbuf_splice(msgbuf, msgbuf->len - ignore_footer, 0,\n> -\t\t\t\tsob.buf, sob.len);\n> +\t\t\t\tsob->buf, sob->len);\n> +}\n> +void append_signoff(struct strbuf *msgbuf, size_t ignore_footer, unsigned flag)\n> +{\n> +\tstruct strbuf sob = STRBUF_INIT;\n> +\tstrbuf_addstr(&sob, sign_off_header);\n> +\tstrbuf_addstr(&sob, fmt_name(WANT_COMMITTER_IDENT));\n> +\tstrbuf_addch(&sob, '\\n');\n> +\tappend_message(msgbuf, &sob, ignore_footer, flag);\n> +\tstrbuf_release(&sob);\n> +}\n\nI think it will be nice if you could use a flag to denote whether the\nentity to be appended is a 'sign-off' or not. This way when say the\nvariable 'int signoff' is 1, then use the above part in the\n'append_message()' function otherwise go with the flow that exists.\n\n> +void append_message_string_list(struct strbuf *msgbuf, const char *header,\n> +\t\tstruct string_list *sobs, size_t ignore_footer, unsigned flag) {\n> +\tint i;\n> +\tstruct strbuf sob = STRBUF_INIT;\n> +\n> +\tfor ( i = 0; i < sobs->nr; i++)\n> +\t{\n> +\t\tstrbuf_addstr(&sob, header);\n> +\t\tstrbuf_addstr(&sob, sobs->items[i].string);\n> +\t\tstrbuf_addch(&sob, '\\n');\n> +\t\tappend_message(msgbuf, &sob, ignore_footer, flag);\n> +\t\tstrbuf_reset(&sob);\n> +\t}\n>  \tstrbuf_release(&sob);\n>  }\n\nSimilarly, here, if 'signoff = 0' then the above part goes on. Or\nanother alternative can be to create a 'append_message_helper()' and\nshift the current contents of 'append_message()' into that and use the\nabove function to work as per the value of signoff.\n\nSo a possible flow can be:\n\nvoid append_message_string_list(...params...) {\n\n\tif (signoff) {\n\t\t..excecute the necessary segments and call the\n\t\t'append_message_helper()' function...\n\t} else {\n\t\t..similarly here..\n\t}\n}\n\nThis way we save ourselves some extra functions.\n\n> diff --git a/sequencer.h b/sequencer.h\n> index f8b2e4ab8527..b24e274f4c62 100644\n> --- a/sequencer.h\n> +++ b/sequencer.h\n> @@ -174,6 +174,10 @@ int todo_list_rearrange_squash(struct todo_list *todo_list);\n>   * and the new signoff will be spliced into the buffer before those bytes.\n>   */\n>  void append_signoff(struct strbuf *msgbuf, size_t ignore_footer, unsigned flag);\n> +void append_message(struct strbuf *msgbuf, struct strbuf *sob,\n> +\t\tsize_t ignore_footer, unsigned flag);\n> +void append_message_string_list(struct strbuf *msgbuf, const char*header,\n> +\t\tstruct string_list *sobs, size_t ignore_footer, unsigned flag);\n>  \n>  void append_conflicts_hint(struct index_state *istate,\n>  \t\tstruct strbuf *msgbuf, enum commit_msg_cleanup_mode cleanup_mode);\n\nThen, these would have to be corrected too.\n\n> diff --git a/t/t7502-commit-porcelain.sh b/t/t7502-commit-porcelain.sh\n> index 6396897cc818..40823152a51c 100755\n> --- a/t/t7502-commit-porcelain.sh\n> +++ b/t/t7502-commit-porcelain.sh\n> @@ -154,6 +154,112 @@ test_expect_success 'sign off' '\n>  \n>  '\n\nI feel that the tests deserve a commit of their own. A follow-up commit\nin this patch.\n\n> +test_expect_success 'helped-by' '\n> +\n> +\t>file1 &&\n> +\tgit add file1 &&\n> +\tgit commit --helped-by=\"foo <bar@frotz>\" \\\n> +\t--helped-by=\"foo2 <bar2@frotz>\" -m \"thank you\" &&\n> +\tgit cat-file commit HEAD >commit.msg &&\n> +\tsed -ne \"s/Helped-by: //p\" commit.msg >actual &&\n> +\tcat >expected <<-\\EOF &&\n> +\tfoo <bar@frotz>\n> +\tfoo2 <bar2@frotz>\n> +\tEOF\n> +\ttest_cmp expected actual\n> +\n\nWas this extra line feed intentional? It looks odd here and other test\n(scripts) don't have this. Though, this test script seems to have many\ntests which have an extra line feed. My suggestion would be to eliminate\nit. Maybe this script is old and hasn't been reviewed for a cleanup in\na long time.\n\n> +'\n> +\n> +test_expect_success 'reported-by' '\n> +\n> +\t>file2 &&\n> +\tgit add file2 &&\n> +\tgit commit --reported-by=\"foo <bar@frotz>\" \\\n> +\t--reported-by=\"foo2 <bar2@frotz>\" -m \"thank you\" &&\n> +\tgit cat-file commit HEAD >commit.msg &&\n> +\tsed -ne \"s/Reported-by: //p\" commit.msg >actual &&\n> +\tcat >expected <<-\\EOF &&\n> +\tfoo <bar@frotz>\n> +\tfoo2 <bar2@frotz>\n> +\tEOF\n> +\ttest_cmp expected actual\n> +\n\nSimilarly here and in the parts that follow.\n\n<...>\n\nThis seems like a good patch to me but more experienced members will be\nable to comment better I think. Good job anyways.\n\nRegards,\nShourya Shukla\n\n"},{"id":"418882","messageId":"xmqq1rclfvl0.fsf@gitster.g","threadId":"55288","inReplyTo":"pull.901.git.1615446968597.gitgitgadget@gmail.com","subject":"Re: [PATCH] [GSOC] commit: provides multiple common signatures","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2021-03-11T17:28:59Z","receivedAt":"2021-03-11T17:30:08Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"\"ZheNing Hu via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n\n> From: ZheNing Hu <adlternative@gmail.com>\n>\n> Similar to \"Helped-by\", \"Reported-by\", \"Reviewed-by\", \"Mentored-by\"\n> these signatures are often seen in git commit messages. After\n> referring to the simple implementation of `commit --signoff`\n> and `send-email -cc=\" commiter <email>\"`, I am considering\n> whether to provide multiple signature parameters from the\n> command line.\n> + ...\n> +\t>file6 &&\n> +\tgit add file6 &&\n> +\tgit commit -H \"foo <bar@frotz>\" \\\n> +\t-R \"foo2 <bar2@frotz>\" \\\n> +\t-M \"foo3 <bar3@frotz>\" \\\n> +\t-r \"foo4 <bar4@frotz>\" -s -m \"thank you\" &&\n\nFirm NAK.\n\nEspecially, not in this form that squats on short-and-sweet single\nletter option names only to support the convention of this single\nproject (namely, Git).\n\ncf. https://lore.kernel.org/git/20200824061156.1929850-1-espeer@gmail.com/\n\nThanks.\n"},{"id":"418964","messageId":"CAOLTT8SpAOj51jqYUYqYwXaVKSn1fANvetauaG0z4etiBMzVEw@mail.gmail.com","threadId":"55288","inReplyTo":"20210311150304.GA8169@konoha","subject":"Re: [PATCH] [GSOC] commit: provides multiple common signatures","fromName":"ZheNing Hu","fromEmail":"adlternative@gmail.com","sentAt":"2021-03-12T11:41:13Z","receivedAt":"2021-03-12T11:41:59Z","isPatch":true,"sender":{"key":"adlternative@gmail.com","avatar":"https://avatars.githubusercontent.com/u/58138461?v=4"},"body":"Shourya Shukla <periperidip@gmail.com> 于2021年3月11日周四 下午11:05写道：\n>\n> Hey!\n>\n\nHi!\n\n> The idea seems very useful to me though I am not sure what others feel\n> about this and whether Git is ready for such a thing or not. Keeping\n> this concern aside, I will still review the patch due to my own interest\n> in it as well.\n>\n\nhaha, thank you! I'm glad that my whims can be recognized by you and help\ngit itself.\n\n> On 11/03 07:16, ZheNing Hu via GitGitGadget wrote:\n> > From: ZheNing Hu <adlternative@gmail.com>\n> >\n> > Similar to \"Helped-by\", \"Reported-by\", \"Reviewed-by\", \"Mentored-by\"\n> > these signatures are often seen in git commit messages. After\n>\n> I think it will be better to rephrase the line so that it is easier to\n> understand. It took me a couple of reads to figure out what you meant.\n>\n> Something like:\n>\n> Similar to \"Signed-off-by\", trailers such as \"Reported-by\", \"Helped-by\"\n> and \"Mentored-by\" are also seen in Git commits.\n>\n\nIt's exactly what you said.\nMy lack of English sometimes limits my expression.\n\n> > referring to the simple implementation of `commit --signoff`\n> > and `send-email -cc=\" commiter <email>\"`, I am considering\n> > whether to provide multiple signature parameters from the\n> > command line. I think this might help maintainers and\n> > developers directly uses the shell to provide these signatures\n> > instead of multiple times repetitive writing those trailers\n> > each time.\n>\n> The above para is more appropriate for a cover letter than a commit\n> message. Your thought process for the patch you sent is equally valuable\n> but this does not belong in the commit message. Commit messages are more\n> about what you did and any nuances that follow. You get me?\n>\n> Use 'git format-patch --cover-letter <...>' to create a cover letter for\n> your patch.\n>\n\nI understand it now, because I use GGG, I will put this content into the\nGGG conversation.\n\n> > To achieve this goal, i refactored the `append_signoff` design and\n> > provided `append_message` and `append_message_string_list` interfaces,\n> > providing new ways to generate those various signatures.\n>\n> s/i/I\n>\n> Also, commit messages are generally written in the imperative tense when\n> desciribing what you have done in the commit.\n>\n\nYou're right.\n\n> > Users now can use `commit -H \"helper <eamil>\"` to generate \"Helped-by\" trailer,\n> > `commit -R \"reviewer <eamil>\"` to generate \"Reviewed-by\" trailer,\n> > `commit -r \"reporter <eamil> \"`to generate \"Reported-by\" trailer,\n> > `commit -M \"mentor <eamil>\"` to generate \"Mentored-by\" trailer.\n>\n> Multiple typos.\n>\n> > Signed-off-by: ZheNing Hu <adlternative@gmail.com>\n>\n> So, an improved commit message could be:\n>\n> -----8<-----\n> commit: support multiple trailers from the command line\n>\n> Historically, Git has supported the 'Signed-off-by' commit trailer\n> using the '--signoff' and the '-s' option from the command line. Extend\n> this list to include other commonly used trailers viz., \"Helped-by\",\n> \"Reviewed-by\", \"Reported-by\" and \"Mentored-by\". Introduce options '-H',\n> '-R', '-r' and '-M' corresponding to the aforementioned trailers.\n>\n> While at it, add tests in the script 't7502-commit-porcelain.sh' and add\n> information regarding these options in the Documentation of 'git\n> commit'.\n\nI think this paragraph may not be needed. Junio said in reply to one\nof my previous patches that we may not need to add instructions about test\nfiles and documents, because `git log -p --stat` can clearly see that what we\nhave done in testing and documentation.\n\n> ----->8-----\n>\n> >     [GSOC] commit: provides multiple signatures from command line\n> >\n> >     I don’t know if my idea will satisfy everyone, I'm also thinking about\n> >     whether we can provide a more generalized version (I think this idea can\n> >     be extended: because users and developers have other signature methods\n> >     that they want, such as \"Based-on-patch-by\") I hope someone can give me\n> >     pointers (on the correctness of ideas or codes)\n>\n> It will be great if we let the user add customised options for the\n> respective trailers but I think that trying to cater to such a large\n> audience will only complicate the whole process and decrease the support\n> avialable for the options/command. Apart from this, I think that the\n> trailer \"Reviewed-by\" may not be that widely used and it would be better\n> if we remove it from this patch.\n\nOkay, I will delete it.\n\n>\n> >  Documentation/git-commit.txt |  24 +++++++-\n> >  builtin/commit.c             |  63 +++++++++++++++++++++\n> >  sequencer.c                  |  40 +++++++++----\n> >  sequencer.h                  |   4 ++\n> >  t/t7502-commit-porcelain.sh  | 106 +++++++++++++++++++++++++++++++++++\n> >  5 files changed, 226 insertions(+), 11 deletions(-)\n> >\n> > diff --git a/Documentation/git-commit.txt b/Documentation/git-commit.txt\n> > index 17150fa7eabe..e1b528d70c1a 100644\n> > --- a/Documentation/git-commit.txt\n> > +++ b/Documentation/git-commit.txt\n> > @@ -14,7 +14,9 @@ SYNOPSIS\n> >          [--allow-empty-message] [--no-verify] [-e] [--author=<author>]\n> >          [--date=<date>] [--cleanup=<mode>] [--[no-]status]\n> >          [-i | -o] [--pathspec-from-file=<file> [--pathspec-file-nul]]\n> > -        [-S[<keyid>]] [--] [<pathspec>...]\n> > +        [-S[<keyid>]] [--] [<pathspec>...] [(-H|--helped-by)=<address>...]\n> > +        [(-R|--reviewed-by)=<address>...] [(-r|--reported-by)=<address>...]\n> > +        [(-M|--mentored)=<address>...]\n> >\n> >  DESCRIPTION\n> >  -----------\n> > @@ -166,6 +168,26 @@ The `-m` option is mutually exclusive with `-c`, `-C`, and `-F`.\n> >\n> >  include::signoff-option.txt[]\n> >\n> > +-H=<address>...::\n> > +--helped-by=<address>...::\n> > +     Add one or more `Helped-by` trailer by the committer at the end of the commit\n> > +     log message.\n> > +\n> > +-R=<address>...::\n> > +--reviewed-by=<address>...::\n> > +     Add one or more `Reviewed-by` trailer by the committer at the end of the commit\n> > +     log message.\n> > +\n> > +-r=<address>...::\n> > +--reported-by=<address>...::\n> > +     Add one or more `Reported-by` trailer by the committer at the end of the commit\n> > +     log message.\n> > +\n> > +-M=<address>...::\n> > +--mentored-by=<address>...::\n> > +     Add one or more `Mentored-by` trailer by the committer at the end of the commit\n> > +     log message.\n>\n> Oh! I did not think you had added long forms of these options and was\n> about to comment on that. Good that you added. Do talk about them as\n> well in the commit message.\n>\n\nYes, I forgot to mention these long formats in the commit message.\n\n> >  -n::\n> >  --no-verify::\n> >       This option bypasses the pre-commit and commit-msg hooks.\n> > diff --git a/builtin/commit.c b/builtin/commit.c\n> > index 739110c5a7f6..4b312af03181 100644\n> > --- a/builtin/commit.c\n> > +++ b/builtin/commit.c\n> > @@ -113,6 +113,10 @@ static int config_commit_verbose = -1; /* unspecified */\n> >  static int no_post_rewrite, allow_empty_message, pathspec_file_nul;\n> >  static char *untracked_files_arg, *force_date, *ignore_submodule_arg, *ignored_arg;\n> >  static char *sign_commit, *pathspec_from_file;\n> > +static struct string_list helped_by = STRING_LIST_INIT_NODUP;\n> > +static struct string_list mentored_by = STRING_LIST_INIT_NODUP;\n> > +static struct string_list reviewed_by = STRING_LIST_INIT_NODUP;\n> > +static struct string_list reported_by = STRING_LIST_INIT_NODUP;\n>\n> Good that you kept the variables as 'string_list's instead of a usual\n> 'char*'.\n>\n> >  /*\n> >   * The default commit message cleanup mode will remove the lines\n> > @@ -829,6 +833,20 @@ static int prepare_to_commit(const char *index_file, const char *prefix,\n> >       if (signoff)\n> >               append_signoff(&sb, ignore_non_trailer(sb.buf, sb.len), 0);\n> >\n> > +     if(helped_by.items)\n> > +             append_message_string_list(&sb, \"Helped-by: \", &helped_by, ignore_non_trailer(sb.buf, sb.len), 0);\n> > +     if(reviewed_by.items)\n> > +             append_message_string_list(&sb, \"Reviewed-by: \", &reviewed_by, ignore_non_trailer(sb.buf, sb.len), 0);\n> > +     if(reported_by.items)\n> > +             append_message_string_list(&sb, \"Reported-by: \", &reported_by, ignore_non_trailer(sb.buf, sb.len), 0);\n> > +     if(mentored_by.items)\n> > +             append_message_string_list(&sb, \"Mentored-by: \", &mentored_by, ignore_non_trailer(sb.buf, sb.len), 0);\n>\n> I think this part will look prettier if you wrap around the text. For\n> code segments, the wrap around limit is 80 chars. For commit messages\n> its 72 chars. Wrap around means that as soon as you hit N number of\n> characters, you proceed to the next(new) line.\n>\n\nFine.\n\n> > +     string_list_clear(&helped_by, 0);\n> > +     string_list_clear(&reviewed_by, 0);\n> > +     string_list_clear(&reported_by, 0);\n> > +     string_list_clear(&mentored_by, 0);\n> > +\n> >       if (fwrite(sb.buf, 1, sb.len, s->fp) < sb.len)\n> >               die_errno(_(\"could not write commit template\"));\n> >\n> > @@ -1490,6 +1508,42 @@ static int git_commit_config(const char *k, const char *v, void *cb)\n> >       return git_status_config(k, v, s);\n> >  }\n> >\n> > +static int help_callback(const struct option *opt, const char *arg, int unset)\n> > +{\n> > +     if (unset)\n> > +             string_list_clear(&helped_by, 0);\n> > +     else\n> > +             string_list_append(&helped_by, arg);\n> > +     return 0;\n> > +}\n> > +\n> > +static int review_callback(const struct option *opt, const char *arg, int unset)\n> > +{\n> > +     if (unset)\n> > +             string_list_clear(&reviewed_by, 0);\n> > +     else\n> > +             string_list_append(&reviewed_by, arg);\n> > +     return 0;\n> > +}\n> > +\n> > +static int report_callback(const struct option *opt, const char *arg, int unset)\n> > +{\n> > +     if (unset)\n> > +             string_list_clear(&reported_by, 0);\n> > +     else\n> > +             string_list_append(&reported_by, arg);\n> > +     return 0;\n> > +}\n> > +\n> > +static int mentor_callback(const struct option *opt, const char *arg, int unset)\n> > +{\n> > +     if (unset)\n> > +             string_list_clear(&mentored_by, 0);\n> > +     else\n> > +             string_list_append(&mentored_by, arg);\n> > +     return 0;\n> > +}\n> >  int cmd_commit(int argc, const char **argv, const char *prefix)\n> >  {\n> >       static struct wt_status s;\n> > @@ -1507,6 +1561,10 @@ int cmd_commit(int argc, const char **argv, const char *prefix)\n> >               OPT_STRING(0, \"fixup\", &fixup_message, N_(\"commit\"), N_(\"use autosquash formatted message to fixup 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('H', \"helped-by\", NULL, N_(\"email\"), N_(\"add a Helped-by trailer\"), help_callback),\n> > +             OPT_CALLBACK('r', \"reported-by\", NULL, N_(\"email\"), N_(\"add a Reported-by trailer\"), report_callback),\n> > +             OPT_CALLBACK('R', \"reviewed-by\", NULL, N_(\"email\"), N_(\"add a Reviewed-by trailer\"), review_callback),\n> > +             OPT_CALLBACK('M', \"mentored-by\", NULL, N_(\"email\"), N_(\"add a Mentored-by trailer\"), mentor_callback),\n>\n> It would be lovely if you could line wrap these above additions too!\n>\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> > @@ -1561,6 +1619,11 @@ int cmd_commit(int argc, const char **argv, const char *prefix)\n> >       struct commit_extra_header *extra = NULL;\n> >       struct strbuf err = STRBUF_INIT;\n> >\n> > +     helped_by.strdup_strings = 1;\n> > +     reviewed_by.strdup_strings = 1;\n> > +     reported_by.strdup_strings = 1;\n> > +     mentored_by.strdup_strings = 1;\n> > +\n> >       if (argc == 2 && !strcmp(argv[1], \"-h\"))\n> >               usage_with_options(builtin_commit_usage, builtin_commit_options);\n> >\n> > diff --git a/sequencer.c b/sequencer.c\n> > index d2332d3e1787..528daf9df252 100644\n> > --- a/sequencer.c\n> > +++ b/sequencer.c\n> > @@ -4668,16 +4668,12 @@ int sequencer_pick_revisions(struct repository *r,\n> >       return res;\n> >  }\n> >\n> > -void append_signoff(struct strbuf *msgbuf, size_t ignore_footer, unsigned flag)\n> > +void append_message(struct strbuf *msgbuf, struct strbuf *sob,\n> > +                     size_t ignore_footer, unsigned flag)\n>\n> Its nice to see that you generalised the pre-exisiting function instead\n> of creating a new one(s) for the new trailers. Good.\n>\n> It will be better to name 'struct strbuf *sob' to something more\n> generic, maybe 'struct strbuf *trailer' since 'sob' stands for\n> 'Signed-off-by' and since the function is becoming more generic, its\n> contents should too.\n>\nWhen I wrote it, I didn’t realize that sob refers to the abbreviation of\n'Signed-off-by', I will change it.\n> >  {\n> >       unsigned no_dup_sob = flag & APPEND_SIGNOFF_DEDUP;\n> > -     struct strbuf sob = STRBUF_INIT;\n> >       int has_footer;\n> >\n> > -     strbuf_addstr(&sob, sign_off_header);\n> > -     strbuf_addstr(&sob, fmt_name(WANT_COMMITTER_IDENT));\n> > -     strbuf_addch(&sob, '\\n');\n> > -\n> >       if (!ignore_footer)\n> >               strbuf_complete_line(msgbuf);\n> >\n> > @@ -4685,11 +4681,11 @@ void append_signoff(struct strbuf *msgbuf, size_t ignore_footer, unsigned flag)\n> >        * If the whole message buffer is equal to the sob, pretend that we\n> >        * found a conforming footer with a matching sob\n> >        */\n> > -     if (msgbuf->len - ignore_footer == sob.len &&\n> > -         !strncmp(msgbuf->buf, sob.buf, sob.len))\n> > +     if (msgbuf->len - ignore_footer == sob->len &&\n> > +         !strncmp(msgbuf->buf, sob->buf, sob->len))\n> >               has_footer = 3;\n> >       else\n> > -             has_footer = has_conforming_footer(msgbuf, &sob, ignore_footer);\n> > +             has_footer = has_conforming_footer(msgbuf, sob, ignore_footer);\n>\n> Again, rename the variables.\n>\n> >       if (!has_footer) {\n> >               const char *append_newlines = NULL;\n> > @@ -4723,8 +4719,32 @@ void append_signoff(struct strbuf *msgbuf, size_t ignore_footer, unsigned flag)\n> >\n> >       if (has_footer != 3 && (!no_dup_sob || has_footer != 2))\n> >               strbuf_splice(msgbuf, msgbuf->len - ignore_footer, 0,\n> > -                             sob.buf, sob.len);\n> > +                             sob->buf, sob->len);\n> > +}\n> > +void append_signoff(struct strbuf *msgbuf, size_t ignore_footer, unsigned flag)\n> > +{\n> > +     struct strbuf sob = STRBUF_INIT;\n> > +     strbuf_addstr(&sob, sign_off_header);\n> > +     strbuf_addstr(&sob, fmt_name(WANT_COMMITTER_IDENT));\n> > +     strbuf_addch(&sob, '\\n');\n> > +     append_message(msgbuf, &sob, ignore_footer, flag);\n> > +     strbuf_release(&sob);\n> > +}\n>\n> I think it will be nice if you could use a flag to denote whether the\n> entity to be appended is a 'sign-off' or not. This way when say the\n> variable 'int signoff' is 1, then use the above part in the\n> 'append_message()' function otherwise go with the flow that exists.\n>\n> > +void append_message_string_list(struct strbuf *msgbuf, const char *header,\n> > +             struct string_list *sobs, size_t ignore_footer, unsigned flag) {\n> > +     int i;\n> > +     struct strbuf sob = STRBUF_INIT;\n> > +\n> > +     for ( i = 0; i < sobs->nr; i++)\n> > +     {\n> > +             strbuf_addstr(&sob, header);\n> > +             strbuf_addstr(&sob, sobs->items[i].string);\n> > +             strbuf_addch(&sob, '\\n');\n> > +             append_message(msgbuf, &sob, ignore_footer, flag);\n> > +             strbuf_reset(&sob);\n> > +     }\n> >       strbuf_release(&sob);\n> >  }\n>\n> Similarly, here, if 'signoff = 0' then the above part goes on. Or\n> another alternative can be to create a 'append_message_helper()' and\n> shift the current contents of 'append_message()' into that and use the\n> above function to work as per the value of signoff.\n>\n> So a possible flow can be:\n>\n> void append_message_string_list(...params...) {\n>\n>         if (signoff) {\n>                 ..excecute the necessary segments and call the\n>                 'append_message_helper()' function...\n>         } else {\n>                 ..similarly here..\n>         }\n> }\n>\n> This way we save ourselves some extra functions.\n\nMay some thing like this:\n\nif (signoff)\n        append_message_string_list(&sb, \"Signed-off-by: \", NULL,\n                   ignore_non_trailer(sb.buf, sb.len), 0);\n\nAnd then we could judge in `append_message_string_list`\nif the arguement `string_list *trailers` set to NULL. If so,\nwe do something similar to `append_signoff`, otherwise,\nwe carry out other situations.\n\nSo that we can delete the `append_signoff` and user can only\nneed to call  `append_message_string_list` in any where, I don't\n know if it is better.\n\n>\n> > diff --git a/sequencer.h b/sequencer.h\n> > index f8b2e4ab8527..b24e274f4c62 100644\n> > --- a/sequencer.h\n> > +++ b/sequencer.h\n> > @@ -174,6 +174,10 @@ int todo_list_rearrange_squash(struct todo_list *todo_list);\n> >   * and the new signoff will be spliced into the buffer before those bytes.\n> >   */\n> >  void append_signoff(struct strbuf *msgbuf, size_t ignore_footer, unsigned flag);\n> > +void append_message(struct strbuf *msgbuf, struct strbuf *sob,\n> > +             size_t ignore_footer, unsigned flag);\n> > +void append_message_string_list(struct strbuf *msgbuf, const char*header,\n> > +             struct string_list *sobs, size_t ignore_footer, unsigned flag);\n> >\n> >  void append_conflicts_hint(struct index_state *istate,\n> >               struct strbuf *msgbuf, enum commit_msg_cleanup_mode cleanup_mode);\n>\n> Then, these would have to be corrected too.\n>\n> > diff --git a/t/t7502-commit-porcelain.sh b/t/t7502-commit-porcelain.sh\n> > index 6396897cc818..40823152a51c 100755\n> > --- a/t/t7502-commit-porcelain.sh\n> > +++ b/t/t7502-commit-porcelain.sh\n> > @@ -154,6 +154,112 @@ test_expect_success 'sign off' '\n> >\n> >  '\n>\n> I feel that the tests deserve a commit of their own. A follow-up commit\n> in this patch.\n>\n> > +test_expect_success 'helped-by' '\n> > +\n> > +     >file1 &&\n> > +     git add file1 &&\n> > +     git commit --helped-by=\"foo <bar@frotz>\" \\\n> > +     --helped-by=\"foo2 <bar2@frotz>\" -m \"thank you\" &&\n> > +     git cat-file commit HEAD >commit.msg &&\n> > +     sed -ne \"s/Helped-by: //p\" commit.msg >actual &&\n> > +     cat >expected <<-\\EOF &&\n> > +     foo <bar@frotz>\n> > +     foo2 <bar2@frotz>\n> > +     EOF\n> > +     test_cmp expected actual\n> > +\n>\n> Was this extra line feed intentional? It looks odd here and other test\n> (scripts) don't have this. Though, this test script seems to have many\n> tests which have an extra line feed. My suggestion would be to eliminate\n> it. Maybe this script is old and hasn't been reviewed for a cleanup in\n> a long time.\n>\n\nI might want to keep the same format with the last \"sign off\" test. Now it\nseems that this is not necessary.\n\n> > +'\n> > +\n> > +test_expect_success 'reported-by' '\n> > +\n> > +     >file2 &&\n> > +     git add file2 &&\n> > +     git commit --reported-by=\"foo <bar@frotz>\" \\\n> > +     --reported-by=\"foo2 <bar2@frotz>\" -m \"thank you\" &&\n> > +     git cat-file commit HEAD >commit.msg &&\n> > +     sed -ne \"s/Reported-by: //p\" commit.msg >actual &&\n> > +     cat >expected <<-\\EOF &&\n> > +     foo <bar@frotz>\n> > +     foo2 <bar2@frotz>\n> > +     EOF\n> > +     test_cmp expected actual\n> > +\n>\n> Similarly here and in the parts that follow.\n>\n> <...>\n>\n> This seems like a good patch to me but more experienced members will be\n> able to comment better I think. Good job anyways.\n>\n\nYour comments and encouragement are of great help to me :)\n\n> Regards,\n> Shourya Shukla\n>\n\nRegards,\nZheNing Hu\n"},{"id":"418967","messageId":"CAOLTT8RWCnaWLbVKdbVtVMSPAgvvCpv_PG3uaOi2gb3_=xwz9w@mail.gmail.com","threadId":"55288","inReplyTo":"xmqq1rclfvl0.fsf@gitster.g","subject":"Re: [PATCH] [GSOC] commit: provides multiple common signatures","fromName":"ZheNing Hu","fromEmail":"adlternative@gmail.com","sentAt":"2021-03-12T12:01:01Z","receivedAt":"2021-03-12T12:01:50Z","isPatch":true,"sender":{"key":"adlternative@gmail.com","avatar":"https://avatars.githubusercontent.com/u/58138461?v=4"},"body":"Junio C Hamano <gitster@pobox.com> 于2021年3月12日周五 上午1:29写道：\n\n> Firm NAK.\n>\n> Especially, not in this form that squats on short-and-sweet single\n> letter option names only to support the convention of this single\n> project (namely, Git).\n>\n> cf. https://lore.kernel.org/git/20200824061156.1929850-1-espeer@gmail.com/\n>\n> Thanks.\n\nWell, Junio, you point is It's also worth thinking about, This\n\"trailer =<trailer>:<argtrailer>\" is probably the more general\nversion of the tailer I want to provide to commit, Then I may\nneed to know how Christian Couder `trailer.c` work first,or can\nyou give a little directionality guidance?\n\nThanks.\n"},{"id":"418969","messageId":"CAOLTT8TB3Ko7qEfDaE2xSxjB8LJ7GR1R9innBkstCxRr5QpLoQ@mail.gmail.com","threadId":"55288","inReplyTo":"xmqq1rclfvl0.fsf@gitster.g","subject":"Re: [PATCH] [GSOC] commit: provides multiple common signatures","fromName":"ZheNing Hu","fromEmail":"adlternative@gmail.com","sentAt":"2021-03-12T13:22:52Z","receivedAt":"2021-03-12T13:24:08Z","isPatch":true,"sender":{"key":"adlternative@gmail.com","avatar":"https://avatars.githubusercontent.com/u/58138461?v=4"},"body":"Junio C Hamano <gitster@pobox.com> 于2021年3月12日周五 上午1:29写道：\n> Firm NAK.\n>\n> Especially, not in this form that squats on short-and-sweet single\n> letter option names only to support the convention of this single\n> project (namely, Git).\n>\n> cf. https://lore.kernel.org/git/20200824061156.1929850-1-espeer@gmail.com/\n>\n> Thanks.\n\nNow I understand that We have a git command now:\n\n`git interpret-trailers --in-place --where=end \\\n./a.txt --trailer \"Signed-off-by:adl <adlternative@gmail.com> \" `\ncan add the trailers to specified file, so now maybe I only\nneed to provide `--trailer` for commit, right?\n"},{"id":"418974","messageId":"pull.901.v2.git.1615564478029.gitgitgadget@gmail.com","threadId":"55288","inReplyTo":"pull.901.git.1615446968597.gitgitgadget@gmail.com","subject":"[PATCH v2] [GSOC] commit: add trailer command","fromName":"ZheNing Hu via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2021-03-12T15:54:37Z","receivedAt":"2021-03-12T15:55:14Z","isPatch":true,"sender":{"key":"adlternative@gmail.com","avatar":"https://avatars.githubusercontent.com/u/58138461?v=4"},"body":"From: ZheNing Hu <adlternative@gmail.com>\n\nHistorically, Git has supported the 'Signed-off-by' commit trailer\nusing the '--signoff' and the '-s' option from the command line.\nBut users may need to provide richer trailer information from the\ncommand line such as \"Helped-by\", \"Reported-by\", \"Mentored-by\",\n\nNow use `--trailer <token>[(=|:)<value>]` pass the trailers to\n`interpret-trailers` and generate trailers in commit messages.\n\nSigned-off-by: ZheNing Hu <adlternative@gmail.com>\n---\n    [GSOC] commit: provides multiple signatures from command line\n    \n    Now maintainers or developers can also use commit\n    --trailer=\"Signed-off-by:commiter<email>\" from the command line to\n    provide trailers to commit messages. This solution may be more\n    generalized than v1.\n\nPublished-As: https://github.com/gitgitgadget/git/releases/tag/pr-901%2Fadlternative%2Fcommit-with-multiple-signatures-v2\nFetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-901/adlternative/commit-with-multiple-signatures-v2\nPull-Request: https://github.com/gitgitgadget/git/pull/901\n\nRange-diff vs v1:\n\n 1:  e9389e72ac65 ! 1:  4c507d17db4f [GSOC] commit: provides multiple common signatures\n     @@ Metadata\n      Author: ZheNing Hu <adlternative@gmail.com>\n      \n       ## Commit message ##\n     -    [GSOC] commit: provides multiple common signatures\n     +    [GSOC] commit: add trailer command\n      \n     -    Similar to \"Helped-by\", \"Reported-by\", \"Reviewed-by\", \"Mentored-by\"\n     -    these signatures are often seen in git commit messages. After\n     -    referring to the simple implementation of `commit --signoff`\n     -    and `send-email -cc=\" commiter <email>\"`, I am considering\n     -    whether to provide multiple signature parameters from the\n     -    command line. I think this might help maintainers and\n     -    developers directly uses the shell to provide these signatures\n     -    instead of multiple times repetitive writing those trailers\n     -    each time.\n     +    Historically, Git has supported the 'Signed-off-by' commit trailer\n     +    using the '--signoff' and the '-s' option from the command line.\n     +    But users may need to provide richer trailer information from the\n     +    command line such as \"Helped-by\", \"Reported-by\", \"Mentored-by\",\n      \n     -    To achieve this goal, i refactored the `append_signoff` design and\n     -    provided `append_message` and `append_message_string_list` interfaces,\n     -    providing new ways to generate those various signatures.\n     -\n     -    Users now can use `commit -H \"helper <eamil>\"` to generate \"Helped-by\" trailer,\n     -    `commit -R \"reviewer <eamil>\"` to generate \"Reviewed-by\" trailer,\n     -    `commit -r \"reporter <eamil> \"`to generate \"Reported-by\" trailer,\n     -    `commit -M \"mentor <eamil>\"` to generate \"Mentored-by\" trailer.\n     +    Now use `--trailer <token>[(=|:)<value>]` pass the trailers to\n     +    `interpret-trailers` and generate trailers in commit messages.\n      \n          Signed-off-by: ZheNing Hu <adlternative@gmail.com>\n      \n     @@ Documentation/git-commit.txt: SYNOPSIS\n       \t   [--date=<date>] [--cleanup=<mode>] [--[no-]status]\n       \t   [-i | -o] [--pathspec-from-file=<file> [--pathspec-file-nul]]\n      -\t   [-S[<keyid>]] [--] [<pathspec>...]\n     -+\t   [-S[<keyid>]] [--] [<pathspec>...] [(-H|--helped-by)=<address>...]\n     -+\t   [(-R|--reviewed-by)=<address>...] [(-r|--reported-by)=<address>...]\n     -+\t   [(-M|--mentored)=<address>...]\n     ++\t   [-S[<keyid>]] [--] [<pathspec>...] [(--trailer <token>[(=|:)<value>])...]\n       \n       DESCRIPTION\n       -----------\n     @@ Documentation/git-commit.txt: The `-m` option is mutually exclusive with `-c`, `\n       \n       include::signoff-option.txt[]\n       \n     -+-H=<address>...::\n     -+--helped-by=<address>...::\n     -+\tAdd one or more `Helped-by` trailer by the committer at the end of the commit\n     -+\tlog message.\n     -+\n     -+-R=<address>...::\n     -+--reviewed-by=<address>...::\n     -+\tAdd one or more `Reviewed-by` trailer by the committer at the end of the commit\n     -+\tlog message.\n     -+\n     -+-r=<address>...::\n     -+--reported-by=<address>...::\n     -+\tAdd one or more `Reported-by` trailer by the committer at the end of the commit\n     -+\tlog message.\n     -+\n     -+-M=<address>...::\n     -+--mentored-by=<address>...::\n     -+\tAdd one or more `Mentored-by` trailer by the committer at the end of the commit\n     -+\tlog message.\n     ++--trailer <token>[(=|:)<value>]::\n     ++\tSpecify a (<token>, <value>) pair that should be applied as a\n     ++\ttrailer. (e.g.  `git commit --trailer \"Signed-off-by:C O Mitter <committer@example.com>\" \\\n     ++\t--trailer \"Helped-by:C O Mitter <committer@example.com>\"`will add the \"Signed-off\" trailer\n     ++\tand the \"Helped-by\" trailer in the commit message.)\n      +\n       -n::\n       --no-verify::\n     @@ builtin/commit.c: static int config_commit_verbose = -1; /* unspecified */\n       static int no_post_rewrite, allow_empty_message, pathspec_file_nul;\n       static char *untracked_files_arg, *force_date, *ignore_submodule_arg, *ignored_arg;\n       static char *sign_commit, *pathspec_from_file;\n     -+static struct string_list helped_by = STRING_LIST_INIT_NODUP;\n     -+static struct string_list mentored_by = STRING_LIST_INIT_NODUP;\n     -+static struct string_list reviewed_by = STRING_LIST_INIT_NODUP;\n     -+static struct string_list reported_by = STRING_LIST_INIT_NODUP;\n     ++struct child_process run_trailer = CHILD_PROCESS_INIT;\n     ++static const char *trailer;\n       \n       /*\n        * The default commit message cleanup mode will remove the lines\n     -@@ builtin/commit.c: static int prepare_to_commit(const char *index_file, const char *prefix,\n     - \tif (signoff)\n     - \t\tappend_signoff(&sb, ignore_non_trailer(sb.buf, sb.len), 0);\n     - \n     -+\tif(helped_by.items)\n     -+\t\tappend_message_string_list(&sb, \"Helped-by: \", &helped_by, ignore_non_trailer(sb.buf, sb.len), 0);\n     -+\tif(reviewed_by.items)\n     -+\t\tappend_message_string_list(&sb, \"Reviewed-by: \", &reviewed_by, ignore_non_trailer(sb.buf, sb.len), 0);\n     -+\tif(reported_by.items)\n     -+\t\tappend_message_string_list(&sb, \"Reported-by: \", &reported_by, ignore_non_trailer(sb.buf, sb.len), 0);\n     -+\tif(mentored_by.items)\n     -+\t\tappend_message_string_list(&sb, \"Mentored-by: \", &mentored_by, ignore_non_trailer(sb.buf, sb.len), 0);\n     -+\n     -+\tstring_list_clear(&helped_by, 0);\n     -+\tstring_list_clear(&reviewed_by, 0);\n     -+\tstring_list_clear(&reported_by, 0);\n     -+\tstring_list_clear(&mentored_by, 0);\n     -+\n     - \tif (fwrite(sb.buf, 1, sb.len, s->fp) < sb.len)\n     - \t\tdie_errno(_(\"could not write commit template\"));\n     +@@ builtin/commit.c: static struct strbuf message = STRBUF_INIT;\n       \n     -@@ builtin/commit.c: static int git_commit_config(const char *k, const char *v, void *cb)\n     - \treturn git_status_config(k, v, s);\n     - }\n     + static enum wt_status_format status_format = STATUS_FORMAT_UNSPECIFIED;\n       \n     -+static int help_callback(const struct option *opt, const char *arg, int unset)\n     -+{\n     -+\tif (unset)\n     -+\t\tstring_list_clear(&helped_by, 0);\n     -+\telse\n     -+\t\tstring_list_append(&helped_by, arg);\n     -+\treturn 0;\n     -+}\n     -+\n     -+static int review_callback(const struct option *opt, const char *arg, int unset)\n     ++static int opt_pass_trailer(const struct option *opt, const char *arg, int unset)\n      +{\n     -+\tif (unset)\n     -+\t\tstring_list_clear(&reviewed_by, 0);\n     -+\telse\n     -+\t\tstring_list_append(&reviewed_by, arg);\n     -+\treturn 0;\n     -+}\n     -+\n     -+static int report_callback(const struct option *opt, const char *arg, int unset)\n     -+{\n     -+\tif (unset)\n     -+\t\tstring_list_clear(&reported_by, 0);\n     -+\telse\n     -+\t\tstring_list_append(&reported_by, arg);\n     -+\treturn 0;\n     -+}\n     -+\n     -+static int mentor_callback(const struct option *opt, const char *arg, int unset)\n     -+{\n     -+\tif (unset)\n     -+\t\tstring_list_clear(&mentored_by, 0);\n     -+\telse\n     -+\t\tstring_list_append(&mentored_by, arg);\n     ++\tif (unset) {\n     ++\t\tstrvec_clear(&run_trailer.args);\n     ++\t\treturn -1;\n     ++\t}\n     ++\trun_trailer.git_cmd = 1;\n     ++\tstrvec_pushl(&run_trailer.args, \"--trailer\", arg, NULL);\n      +\treturn 0;\n      +}\n      +\n     - int cmd_commit(int argc, const char **argv, const char *prefix)\n     + static int opt_parse_porcelain(const struct option *opt, const char *arg, int unset)\n       {\n     - \tstatic struct wt_status s;\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     + \n     + \tfclose(s->fp);\n     + \n     ++\trun_command(&run_trailer);\n     ++\n     + \t/*\n     + \t * Reject an attempt to record a non-merge empty commit without\n     + \t * explicit --allow-empty. In the cherry-pick case, it may be\n      @@ builtin/commit.c: int cmd_commit(int argc, const char **argv, const char *prefix)\n       \t\tOPT_STRING(0, \"fixup\", &fixup_message, N_(\"commit\"), N_(\"use autosquash formatted message to fixup 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('H', \"helped-by\", NULL, N_(\"email\"), N_(\"add a Helped-by trailer\"), help_callback),\n     -+\t\tOPT_CALLBACK('r', \"reported-by\", NULL, N_(\"email\"), N_(\"add a Reported-by trailer\"), report_callback),\n     -+\t\tOPT_CALLBACK('R', \"reviewed-by\", NULL, N_(\"email\"), N_(\"add a Reviewed-by trailer\"), review_callback),\n     -+\t\tOPT_CALLBACK('M', \"mentored-by\", NULL, N_(\"email\"), N_(\"add a Mentored-by trailer\"), mentor_callback),\n     ++\t\tOPT_CALLBACK(0, \"trailer\", &trailer, N_(\"trailer\"), N_(\"trailer(s) to add\"), opt_pass_trailer),\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      @@ builtin/commit.c: int cmd_commit(int argc, const char **argv, const char *prefix)\n     - \tstruct commit_extra_header *extra = NULL;\n     - \tstruct strbuf err = STRBUF_INIT;\n     - \n     -+\thelped_by.strdup_strings = 1;\n     -+\treviewed_by.strdup_strings = 1;\n     -+\treported_by.strdup_strings = 1;\n     -+\tmentored_by.strdup_strings = 1;\n     -+\n     - \tif (argc == 2 && !strcmp(argv[1], \"-h\"))\n     - \t\tusage_with_options(builtin_commit_usage, builtin_commit_options);\n     - \n     -\n     - ## sequencer.c ##\n     -@@ sequencer.c: int sequencer_pick_revisions(struct repository *r,\n     - \treturn res;\n     - }\n     - \n     --void append_signoff(struct strbuf *msgbuf, size_t ignore_footer, unsigned flag)\n     -+void append_message(struct strbuf *msgbuf, struct strbuf *sob,\n     -+\t\t\tsize_t ignore_footer, unsigned flag)\n     - {\n     - \tunsigned no_dup_sob = flag & APPEND_SIGNOFF_DEDUP;\n     --\tstruct strbuf sob = STRBUF_INIT;\n     - \tint has_footer;\n     - \n     --\tstrbuf_addstr(&sob, sign_off_header);\n     --\tstrbuf_addstr(&sob, fmt_name(WANT_COMMITTER_IDENT));\n     --\tstrbuf_addch(&sob, '\\n');\n     --\n     - \tif (!ignore_footer)\n     - \t\tstrbuf_complete_line(msgbuf);\n     - \n     -@@ sequencer.c: void append_signoff(struct strbuf *msgbuf, size_t ignore_footer, unsigned flag)\n     - \t * If the whole message buffer is equal to the sob, pretend that we\n     - \t * found a conforming footer with a matching sob\n     - \t */\n     --\tif (msgbuf->len - ignore_footer == sob.len &&\n     --\t    !strncmp(msgbuf->buf, sob.buf, sob.len))\n     -+\tif (msgbuf->len - ignore_footer == sob->len &&\n     -+\t    !strncmp(msgbuf->buf, sob->buf, sob->len))\n     - \t\thas_footer = 3;\n     - \telse\n     --\t\thas_footer = has_conforming_footer(msgbuf, &sob, ignore_footer);\n     -+\t\thas_footer = has_conforming_footer(msgbuf, sob, ignore_footer);\n     - \n     - \tif (!has_footer) {\n     - \t\tconst char *append_newlines = NULL;\n     -@@ sequencer.c: void append_signoff(struct strbuf *msgbuf, size_t ignore_footer, unsigned flag)\n     - \n     - \tif (has_footer != 3 && (!no_dup_sob || has_footer != 2))\n     - \t\tstrbuf_splice(msgbuf, msgbuf->len - ignore_footer, 0,\n     --\t\t\t\tsob.buf, sob.len);\n     -+\t\t\t\tsob->buf, sob->len);\n     -+}\n     -+\n     -+void append_signoff(struct strbuf *msgbuf, size_t ignore_footer, unsigned flag)\n     -+{\n     -+\tstruct strbuf sob = STRBUF_INIT;\n     -+\tstrbuf_addstr(&sob, sign_off_header);\n     -+\tstrbuf_addstr(&sob, fmt_name(WANT_COMMITTER_IDENT));\n     -+\tstrbuf_addch(&sob, '\\n');\n     -+\tappend_message(msgbuf, &sob, ignore_footer, flag);\n     -+\tstrbuf_release(&sob);\n     -+}\n     - \n     -+void append_message_string_list(struct strbuf *msgbuf, const char *header,\n     -+\t\tstruct string_list *sobs, size_t ignore_footer, unsigned flag) {\n     -+\tint i;\n     -+\tstruct strbuf sob = STRBUF_INIT;\n     -+\n     -+\tfor ( i = 0; i < sobs->nr; i++)\n     -+\t{\n     -+\t\tstrbuf_addstr(&sob, header);\n     -+\t\tstrbuf_addstr(&sob, sobs->items[i].string);\n     -+\t\tstrbuf_addch(&sob, '\\n');\n     -+\t\tappend_message(msgbuf, &sob, ignore_footer, flag);\n     -+\t\tstrbuf_reset(&sob);\n     -+\t}\n     - \tstrbuf_release(&sob);\n     - }\n     - \n     -\n     - ## sequencer.h ##\n     -@@ sequencer.h: int todo_list_rearrange_squash(struct todo_list *todo_list);\n     -  * and the new signoff will be spliced into the buffer before those bytes.\n     -  */\n     - void append_signoff(struct strbuf *msgbuf, size_t ignore_footer, unsigned flag);\n     -+void append_message(struct strbuf *msgbuf, struct strbuf *sob,\n     -+\t\tsize_t ignore_footer, unsigned flag);\n     -+void append_message_string_list(struct strbuf *msgbuf, const char*header,\n     -+\t\tstruct string_list *sobs, size_t ignore_footer, unsigned flag);\n     - \n     - void append_conflicts_hint(struct index_state *istate,\n     - \t\tstruct strbuf *msgbuf, enum commit_msg_cleanup_mode cleanup_mode);\n     + \t\t\tdie(_(\"could not parse HEAD commit\"));\n     + \t}\n     + \tverbose = -1; /* unspecified */\n     ++\tstrvec_pushl(&run_trailer.args, \"interpret-trailers\",\n     ++\t\t\"--in-place\", \"--where=end\", git_path_commit_editmsg(), NULL);\n     + \targc = parse_and_validate_options(argc, argv, builtin_commit_options,\n     + \t\t\t\t\t  builtin_commit_usage,\n     + \t\t\t\t\t  prefix, current_head, &s);\n      \n       ## t/t7502-commit-porcelain.sh ##\n      @@ t/t7502-commit-porcelain.sh: test_expect_success 'sign off' '\n       \n       '\n       \n     -+test_expect_success 'helped-by' '\n     -+\n     ++test_expect_success 'trailer' '\n      +\t>file1 &&\n      +\tgit add file1 &&\n     -+\tgit commit --helped-by=\"foo <bar@frotz>\" \\\n     -+\t--helped-by=\"foo2 <bar2@frotz>\" -m \"thank you\" &&\n     -+\tgit cat-file commit HEAD >commit.msg &&\n     -+\tsed -ne \"s/Helped-by: //p\" commit.msg >actual &&\n     -+\tcat >expected <<-\\EOF &&\n     -+\tfoo <bar@frotz>\n     -+\tfoo2 <bar2@frotz>\n     -+\tEOF\n     -+\ttest_cmp expected actual\n     -+\n     -+'\n     -+\n     -+test_expect_success 'reported-by' '\n     -+\n     -+\t>file2 &&\n     -+\tgit add file2 &&\n     -+\tgit commit --reported-by=\"foo <bar@frotz>\" \\\n     -+\t--reported-by=\"foo2 <bar2@frotz>\" -m \"thank you\" &&\n     -+\tgit cat-file commit HEAD >commit.msg &&\n     -+\tsed -ne \"s/Reported-by: //p\" commit.msg >actual &&\n     -+\tcat >expected <<-\\EOF &&\n     -+\tfoo <bar@frotz>\n     -+\tfoo2 <bar2@frotz>\n     -+\tEOF\n     -+\ttest_cmp expected actual\n     -+\n     -+'\n     -+\n     -+test_expect_success 'reviewed-by' '\n     -+\n     -+\t>file3 &&\n     -+\tgit add file3 &&\n     -+\tgit commit --reviewed-by=\"foo <bar@frotz>\" \\\n     -+\t--reviewed-by=\"foo2 <bar2@frotz>\" -m \"thank you\" &&\n     -+\tgit cat-file commit HEAD >commit.msg &&\n     -+\tsed -ne \"s/Reviewed-by: //p\" commit.msg >actual &&\n     -+\tcat >expected <<-\\EOF &&\n     -+\tfoo <bar@frotz>\n     -+\tfoo2 <bar2@frotz>\n     -+\tEOF\n     -+\ttest_cmp expected actual\n     -+\n     -+'\n     -+\n     -+test_expect_success 'mentored-by' '\n     -+\n     -+\t>file4 &&\n     -+\tgit add file4 &&\n     -+\tgit commit --mentored-by=\"foo <bar@frotz>\" \\\n     -+\t--mentored-by=\"foo2 <bar2@frotz>\" -m \"thank you\" &&\n     -+\tgit cat-file commit HEAD >commit.msg &&\n     -+\tsed -ne \"s/Mentored-by: //p\" commit.msg >actual &&\n     -+\tcat >expected <<-\\EOF &&\n     -+\tfoo <bar@frotz>\n     -+\tfoo2 <bar2@frotz>\n     -+\tEOF\n     -+\ttest_cmp expected actual\n     -+\n     -+'\n     -+\n     -+test_expect_success 'multiple signatures' '\n     -+\n     -+\t>file5 &&\n     -+\tgit add file5 &&\n     -+\tgit commit --helped-by=\"foo <bar@frotz>\" \\\n     -+\t--reviewed-by=\"foo2 <bar2@frotz>\" \\\n     -+\t--mentored-by=\"foo3 <bar3@frotz>\" \\\n     -+\t--reported-by=\"foo4 <bar4@frotz>\" -s -m \"thank you\" &&\n     ++\tgit commit -s --trailer \"Signed-off-by:C O Mitter1 <committer1@example.com>\" \\\n     ++\t--trailer \"Helped-by:C O Mitter2 <committer2@example.com>\"  \\\n     ++\t--trailer \"Reported-by:C O Mitter3 <committer3@example.com>\" \\\n     ++\t--trailer \"Mentored-by:C O Mitter4 <committer4@example.com>\" \\\n     ++\t-m \"hello\" &&\n      +\tgit cat-file commit HEAD >commit.msg &&\n      +\tsed -e \"1,7d\" commit.msg >actual &&\n      +\tcat >expected <<-\\EOF &&\n      +\tSigned-off-by: C O Mitter <committer@example.com>\n     -+\tHelped-by: foo <bar@frotz>\n     -+\tReviewed-by: foo2 <bar2@frotz>\n     -+\tReported-by: foo4 <bar4@frotz>\n     -+\tMentored-by: foo3 <bar3@frotz>\n     ++\tSigned-off-by: C O Mitter1 <committer1@example.com>\n     ++\tHelped-by: C O Mitter2 <committer2@example.com>\n     ++\tReported-by: C O Mitter3 <committer3@example.com>\n     ++\tMentored-by: C O Mitter4 <committer4@example.com>\n      +\tEOF\n      +\ttest_cmp expected actual\n     -+\n     -+'\n     -+\n     -+test_expect_success 'multiple signatures (use abbreviations)' '\n     -+\n     -+\t>file6 &&\n     -+\tgit add file6 &&\n     -+\tgit commit -H \"foo <bar@frotz>\" \\\n     -+\t-R \"foo2 <bar2@frotz>\" \\\n     -+\t-M \"foo3 <bar3@frotz>\" \\\n     -+\t-r \"foo4 <bar4@frotz>\" -s -m \"thank you\" &&\n     -+\tgit cat-file commit HEAD >commit.msg &&\n     -+\tsed -e \"1,7d\" commit.msg >actual &&\n     -+\tcat >expected <<-\\EOF &&\n     -+\tSigned-off-by: C O Mitter <committer@example.com>\n     -+\tHelped-by: foo <bar@frotz>\n     -+\tReviewed-by: foo2 <bar2@frotz>\n     -+\tReported-by: foo4 <bar4@frotz>\n     -+\tMentored-by: foo3 <bar3@frotz>\n     -+\tEOF\n     -+\ttest_cmp expected actual\n     -+\n      +'\n      +\n       test_expect_success 'multiple -m' '\n\n\n Documentation/git-commit.txt |  8 +++++++-\n builtin/commit.c             | 18 ++++++++++++++++++\n t/t7502-commit-porcelain.sh  | 20 ++++++++++++++++++++\n 3 files changed, 45 insertions(+), 1 deletion(-)\n\ndiff --git a/Documentation/git-commit.txt b/Documentation/git-commit.txt\nindex 17150fa7eabe..764513a3f287 100644\n--- a/Documentation/git-commit.txt\n+++ b/Documentation/git-commit.txt\n@@ -14,7 +14,7 @@ SYNOPSIS\n \t   [--allow-empty-message] [--no-verify] [-e] [--author=<author>]\n \t   [--date=<date>] [--cleanup=<mode>] [--[no-]status]\n \t   [-i | -o] [--pathspec-from-file=<file> [--pathspec-file-nul]]\n-\t   [-S[<keyid>]] [--] [<pathspec>...]\n+\t   [-S[<keyid>]] [--] [<pathspec>...] [(--trailer <token>[(=|:)<value>])...]\n \n DESCRIPTION\n -----------\n@@ -166,6 +166,12 @@ The `-m` option is mutually exclusive with `-c`, `-C`, and `-F`.\n \n include::signoff-option.txt[]\n \n+--trailer <token>[(=|:)<value>]::\n+\tSpecify a (<token>, <value>) pair that should be applied as a\n+\ttrailer. (e.g.  `git commit --trailer \"Signed-off-by:C O Mitter <committer@example.com>\" \\\n+\t--trailer \"Helped-by:C O Mitter <committer@example.com>\"`will add the \"Signed-off\" trailer\n+\tand the \"Helped-by\" trailer in the commit message.)\n+\n -n::\n --no-verify::\n \tThis option bypasses the pre-commit and commit-msg hooks.\ndiff --git a/builtin/commit.c b/builtin/commit.c\nindex 739110c5a7f6..abbd136b27f0 100644\n--- a/builtin/commit.c\n+++ b/builtin/commit.c\n@@ -113,6 +113,8 @@ static int config_commit_verbose = -1; /* unspecified */\n static int no_post_rewrite, allow_empty_message, pathspec_file_nul;\n static char *untracked_files_arg, *force_date, *ignore_submodule_arg, *ignored_arg;\n static char *sign_commit, *pathspec_from_file;\n+struct child_process run_trailer = CHILD_PROCESS_INIT;\n+static const char *trailer;\n \n /*\n  * The default commit message cleanup mode will remove the lines\n@@ -131,6 +133,17 @@ 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+\tif (unset) {\n+\t\tstrvec_clear(&run_trailer.args);\n+\t\treturn -1;\n+\t}\n+\trun_trailer.git_cmd = 1;\n+\tstrvec_pushl(&run_trailer.args, \"--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@@ -958,6 +971,8 @@ static int prepare_to_commit(const char *index_file, const char *prefix,\n \n \tfclose(s->fp);\n \n+\trun_command(&run_trailer);\n+\n \t/*\n \t * Reject an attempt to record a non-merge empty commit without\n \t * explicit --allow-empty. In the cherry-pick case, it may be\n@@ -1507,6 +1522,7 @@ int cmd_commit(int argc, const char **argv, const char *prefix)\n \t\tOPT_STRING(0, \"fixup\", &fixup_message, N_(\"commit\"), N_(\"use autosquash formatted message to fixup 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(0, \"trailer\", &trailer, N_(\"trailer\"), N_(\"trailer(s) to add\"), opt_pass_trailer),\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@@ -1577,6 +1593,8 @@ int cmd_commit(int argc, const char **argv, const char *prefix)\n \t\t\tdie(_(\"could not parse HEAD commit\"));\n \t}\n \tverbose = -1; /* unspecified */\n+\tstrvec_pushl(&run_trailer.args, \"interpret-trailers\",\n+\t\t\"--in-place\", \"--where=end\", git_path_commit_editmsg(), NULL);\n \targc = parse_and_validate_options(argc, argv, builtin_commit_options,\n \t\t\t\t\t  builtin_commit_usage,\n \t\t\t\t\t  prefix, current_head, &s);\ndiff --git a/t/t7502-commit-porcelain.sh b/t/t7502-commit-porcelain.sh\nindex 6396897cc818..4b9ac4587d17 100755\n--- a/t/t7502-commit-porcelain.sh\n+++ b/t/t7502-commit-porcelain.sh\n@@ -154,6 +154,26 @@ test_expect_success 'sign off' '\n \n '\n \n+test_expect_success 'trailer' '\n+\t>file1 &&\n+\tgit add file1 &&\n+\tgit commit -s --trailer \"Signed-off-by:C O Mitter1 <committer1@example.com>\" \\\n+\t--trailer \"Helped-by:C O Mitter2 <committer2@example.com>\"  \\\n+\t--trailer \"Reported-by:C O Mitter3 <committer3@example.com>\" \\\n+\t--trailer \"Mentored-by:C O Mitter4 <committer4@example.com>\" \\\n+\t-m \"hello\" &&\n+\tgit cat-file commit HEAD >commit.msg &&\n+\tsed -e \"1,7d\" commit.msg >actual &&\n+\tcat >expected <<-\\EOF &&\n+\tSigned-off-by: C O Mitter <committer@example.com>\n+\tSigned-off-by: C O Mitter1 <committer1@example.com>\n+\tHelped-by: C O Mitter2 <committer2@example.com>\n+\tReported-by: C O Mitter3 <committer3@example.com>\n+\tMentored-by: C O Mitter4 <committer4@example.com>\n+\tEOF\n+\ttest_cmp expected actual\n+'\n+\n test_expect_success 'multiple -m' '\n \n \t>negative &&\n\nbase-commit: 13d7ab6b5d7929825b626f050b62a11241ea4945\n-- \ngitgitgadget\n"},{"id":"419070","messageId":"CAP8UFD2_SSza-zsoqS_ugQBryiTvm0PqzrJDAuriT2jmjHM-uA@mail.gmail.com","threadId":"55288","inReplyTo":"pull.901.v2.git.1615564478029.gitgitgadget@gmail.com","subject":"Re: [PATCH v2] [GSOC] commit: add trailer command","fromName":"Christian Couder","fromEmail":"christian.couder@gmail.com","sentAt":"2021-03-14T04:19:01Z","receivedAt":"2021-03-14T04:20:13Z","isPatch":true,"sender":{"key":"christian.couder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/208954?v=4"},"body":"On Fri, Mar 12, 2021 at 4:59 PM ZheNing Hu via GitGitGadget\n<gitgitgadget@gmail.com> wrote:\n>\n> From: ZheNing Hu <adlternative@gmail.com>\n>\n> Historically, Git has supported the 'Signed-off-by' commit trailer\n> using the '--signoff' and the '-s' option from the command line.\n> But users may need to provide richer trailer information from the\n> command line such as \"Helped-by\", \"Reported-by\", \"Mentored-by\",\n\nNit: not sure that \"richer\" is the proper word here. I would just use\n\"other\" instead.\n\n> Now use `--trailer <token>[(=|:)<value>]` pass the trailers to\n> `interpret-trailers` and generate trailers in commit messages.\n\nThe subject says \"add trailer command\" while here you say \"use\". So\nwhich one is it? Does \"--trailer\" already exist, and we are just going\nto use it? Or will this patch series actually \"add\" it?\n\nLooking at the existing options and the code of this patch series, the\npatch series actually adds the \"--trailer\" (not \"trailer\") option, so\n\"add\" or \"implement\" would be clearer than \"use\".\n\nSo maybe something like the following might be better:\n\n\"Now implement a new `--trailer <token>[(=|:)<value>]` option to pass\nother trailers to `interpret-trailers` and insert them into commit\nmessages.\"\n\nAlso something like \"--trailer\" is usually called an option (or\nsometimes a flag), not a command (especially not when the word is not\na verb, and when the new feature isn't a new exclusive mode of\noperation). So something like \"commit: add --trailer option\" might be\na better subject.\n\n> Signed-off-by: ZheNing Hu <adlternative@gmail.com>\n> ---\n>     [GSOC] commit: provides multiple signatures from command line\n\nIt looks like this is using the subject of a patch that previously\nattempted to add features with a similar purpose. I don't think you\nneed to put it there, or if you want to refer to it, I think it might\nbe better to be a bit more explicit, for example like:\n\n\"This patch replaces my previous attempt to provide similar features\nin a patch called: [GSOC] commit: provides multiple signatures from\ncommand line.\"\n\n>     Now maintainers or developers can also use commit\n>     --trailer=\"Signed-off-by:commiter<email>\" from the command line to\n>     provide trailers to commit messages. This solution may be more\n>     generalized than v1.\n\nOk, I agree that it's a good idea to have a good generic solution\nfirst, before having specialized options for specific trailers.\n\n> Published-As: https://github.com/gitgitgadget/git/releases/tag/pr-901%2Fadlternative%2Fcommit-with-multiple-signatures-v2\n> Fetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-901/adlternative/commit-with-multiple-signatures-v2\n> Pull-Request: https://github.com/gitgitgadget/git/pull/901\n>\n> Range-diff vs v1:\n\nIf this patch series has very few code and commit messages in common\nwith a previous attempt at implementing similar features, it might be\nbetter to make it a new patch series rather than a v2. This could\navoid sending range-diffs that are mostly useless.\n"},{"id":"419071","messageId":"CAOLTT8SVW1Yy9emwpdxX6sw5cwDFV6fkJNugRtASzA9=vOrBbQ@mail.gmail.com","threadId":"55288","inReplyTo":"CAP8UFD2_SSza-zsoqS_ugQBryiTvm0PqzrJDAuriT2jmjHM-uA@mail.gmail.com","subject":"Re: [PATCH v2] [GSOC] commit: add trailer command","fromName":"ZheNing Hu","fromEmail":"adlternative@gmail.com","sentAt":"2021-03-14T07:09:41Z","receivedAt":"2021-03-14T07:10:37Z","isPatch":true,"sender":{"key":"adlternative@gmail.com","avatar":"https://avatars.githubusercontent.com/u/58138461?v=4"},"body":"Christian Couder <christian.couder@gmail.com> 于2021年3月14日周日 下午12:19写道：\n>\n> On Fri, Mar 12, 2021 at 4:59 PM ZheNing Hu via GitGitGadget\n> <gitgitgadget@gmail.com> wrote:\n> >\n> > From: ZheNing Hu <adlternative@gmail.com>\n> >\n> > Historically, Git has supported the 'Signed-off-by' commit trailer\n> > using the '--signoff' and the '-s' option from the command line.\n> > But users may need to provide richer trailer information from the\n> > command line such as \"Helped-by\", \"Reported-by\", \"Mentored-by\",\n>\n> Nit: not sure that \"richer\" is the proper word here. I would just use\n> \"other\" instead.\n>\n\nOK.\n\n> > Now use `--trailer <token>[(=|:)<value>]` pass the trailers to\n> > `interpret-trailers` and generate trailers in commit messages.\n>\n> The subject says \"add trailer command\" while here you say \"use\". So\n> which one is it? Does \"--trailer\" already exist, and we are just going\n> to use it? Or will this patch series actually \"add\" it?\n>\n> Looking at the existing options and the code of this patch series, the\n> patch series actually adds the \"--trailer\" (not \"trailer\") option, so\n> \"add\" or \"implement\" would be clearer than \"use\".\n>\n\nYou're right. \"add\" will be more accurate in this situation.\n\n> So maybe something like the following might be better:\n>\n> \"Now implement a new `--trailer <token>[(=|:)<value>]` option to pass\n> other trailers to `interpret-trailers` and insert them into commit\n> messages.\"\n>\n> Also something like \"--trailer\" is usually called an option (or\n> sometimes a flag), not a command (especially not when the word is not\n> a verb, and when the new feature isn't a new exclusive mode of\n> operation). So something like \"commit: add --trailer option\" might be\n> a better subject.\n>\n> > Signed-off-by: ZheNing Hu <adlternative@gmail.com>\n> > ---\n> >     [GSOC] commit: provides multiple signatures from command line\n>\n> It looks like this is using the subject of a patch that previously\n> attempted to add features with a similar purpose. I don't think you\n> need to put it there, or if you want to refer to it, I think it might\n> be better to be a bit more explicit, for example like:\n>\n> \"This patch replaces my previous attempt to provide similar features\n> in a patch called: [GSOC] commit: provides multiple signatures from\n> command line.\"\n>\n\nI may have thought that the effect of the two patch was closer so did not\nchange it.\n\n> >     Now maintainers or developers can also use commit\n> >     --trailer=\"Signed-off-by:commiter<email>\" from the command line to\n> >     provide trailers to commit messages. This solution may be more\n> >     generalized than v1.\n>\n> Ok, I agree that it's a good idea to have a good generic solution\n> first, before having specialized options for specific trailers.\n>\n\nThanks. :)\n\n> > Published-As: https://github.com/gitgitgadget/git/releases/tag/pr-901%2Fadlternative%2Fcommit-with-multiple-signatures-v2\n> > Fetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-901/adlternative/commit-with-multiple-signatures-v2\n> > Pull-Request: https://github.com/gitgitgadget/git/pull/901\n> >\n> > Range-diff vs v1:\n>\n> If this patch series has very few code and commit messages in common\n> with a previous attempt at implementing similar features, it might be\n> better to make it a new patch series rather than a v2. This could\n> avoid sending range-diffs that are mostly useless.\n\nThank you for these pertinent suggestions. I will pay more attention.\n"},{"id":"419073","messageId":"pull.901.v3.git.1615726978059.gitgitgadget@gmail.com","threadId":"55288","inReplyTo":"pull.901.v2.git.1615564478029.gitgitgadget@gmail.com","subject":"[PATCH v3] [GSOC] commit: add --trailer option","fromName":"ZheNing Hu via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2021-03-14T13:02:57Z","receivedAt":"2021-03-14T13:03:58Z","isPatch":true,"sender":{"key":"adlternative@gmail.com","avatar":"https://avatars.githubusercontent.com/u/58138461?v=4"},"body":"From: ZheNing Hu <adlternative@gmail.com>\n\nHistorically, Git has supported the 'Signed-off-by' commit trailer\nusing the '--signoff' and the '-s' option from the command line.\nBut users may need to provide other trailer information from the\ncommand line such as \"Helped-by\", \"Reported-by\", \"Mentored-by\",\n\nNow implement a new `--trailer <token>[(=|:)<value>]` option to pass\nother trailers to `interpret-trailers` and insert them into commit\nmessages.\n\nSigned-off-by: ZheNing Hu <adlternative@gmail.com>\n---\n    [GSOC] commit: add --trailer option\n    \n    Now maintainers or developers can also use commit\n    --trailer=\"Signed-off-by:commiter<email>\" from the command line to\n    provide trailers to commit messages. This solution may be more\n    generalized than v1.\n\nPublished-As: https://github.com/gitgitgadget/git/releases/tag/pr-901%2Fadlternative%2Fcommit-with-multiple-signatures-v3\nFetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-901/adlternative/commit-with-multiple-signatures-v3\nPull-Request: https://github.com/gitgitgadget/git/pull/901\n\nRange-diff vs v2:\n\n 1:  4c507d17db4f ! 1:  b4e161a98f8b [GSOC] commit: add trailer command\n     @@ Metadata\n      Author: ZheNing Hu <adlternative@gmail.com>\n      \n       ## Commit message ##\n     -    [GSOC] commit: add trailer command\n     +    [GSOC] commit: add --trailer option\n      \n          Historically, Git has supported the 'Signed-off-by' commit trailer\n          using the '--signoff' and the '-s' option from the command line.\n     -    But users may need to provide richer trailer information from the\n     +    But users may need to provide other trailer information from the\n          command line such as \"Helped-by\", \"Reported-by\", \"Mentored-by\",\n      \n     -    Now use `--trailer <token>[(=|:)<value>]` pass the trailers to\n     -    `interpret-trailers` and generate trailers in commit messages.\n     +    Now implement a new `--trailer <token>[(=|:)<value>]` option to pass\n     +    other trailers to `interpret-trailers` and insert them into commit\n     +    messages.\n      \n          Signed-off-by: ZheNing Hu <adlternative@gmail.com>\n      \n\n\n Documentation/git-commit.txt |  8 +++++++-\n builtin/commit.c             | 18 ++++++++++++++++++\n t/t7502-commit-porcelain.sh  | 20 ++++++++++++++++++++\n 3 files changed, 45 insertions(+), 1 deletion(-)\n\ndiff --git a/Documentation/git-commit.txt b/Documentation/git-commit.txt\nindex 17150fa7eabe..764513a3f287 100644\n--- a/Documentation/git-commit.txt\n+++ b/Documentation/git-commit.txt\n@@ -14,7 +14,7 @@ SYNOPSIS\n \t   [--allow-empty-message] [--no-verify] [-e] [--author=<author>]\n \t   [--date=<date>] [--cleanup=<mode>] [--[no-]status]\n \t   [-i | -o] [--pathspec-from-file=<file> [--pathspec-file-nul]]\n-\t   [-S[<keyid>]] [--] [<pathspec>...]\n+\t   [-S[<keyid>]] [--] [<pathspec>...] [(--trailer <token>[(=|:)<value>])...]\n \n DESCRIPTION\n -----------\n@@ -166,6 +166,12 @@ The `-m` option is mutually exclusive with `-c`, `-C`, and `-F`.\n \n include::signoff-option.txt[]\n \n+--trailer <token>[(=|:)<value>]::\n+\tSpecify a (<token>, <value>) pair that should be applied as a\n+\ttrailer. (e.g.  `git commit --trailer \"Signed-off-by:C O Mitter <committer@example.com>\" \\\n+\t--trailer \"Helped-by:C O Mitter <committer@example.com>\"`will add the \"Signed-off\" trailer\n+\tand the \"Helped-by\" trailer in the commit message.)\n+\n -n::\n --no-verify::\n \tThis option bypasses the pre-commit and commit-msg hooks.\ndiff --git a/builtin/commit.c b/builtin/commit.c\nindex 739110c5a7f6..abbd136b27f0 100644\n--- a/builtin/commit.c\n+++ b/builtin/commit.c\n@@ -113,6 +113,8 @@ static int config_commit_verbose = -1; /* unspecified */\n static int no_post_rewrite, allow_empty_message, pathspec_file_nul;\n static char *untracked_files_arg, *force_date, *ignore_submodule_arg, *ignored_arg;\n static char *sign_commit, *pathspec_from_file;\n+struct child_process run_trailer = CHILD_PROCESS_INIT;\n+static const char *trailer;\n \n /*\n  * The default commit message cleanup mode will remove the lines\n@@ -131,6 +133,17 @@ 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+\tif (unset) {\n+\t\tstrvec_clear(&run_trailer.args);\n+\t\treturn -1;\n+\t}\n+\trun_trailer.git_cmd = 1;\n+\tstrvec_pushl(&run_trailer.args, \"--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@@ -958,6 +971,8 @@ static int prepare_to_commit(const char *index_file, const char *prefix,\n \n \tfclose(s->fp);\n \n+\trun_command(&run_trailer);\n+\n \t/*\n \t * Reject an attempt to record a non-merge empty commit without\n \t * explicit --allow-empty. In the cherry-pick case, it may be\n@@ -1507,6 +1522,7 @@ int cmd_commit(int argc, const char **argv, const char *prefix)\n \t\tOPT_STRING(0, \"fixup\", &fixup_message, N_(\"commit\"), N_(\"use autosquash formatted message to fixup 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(0, \"trailer\", &trailer, N_(\"trailer\"), N_(\"trailer(s) to add\"), opt_pass_trailer),\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@@ -1577,6 +1593,8 @@ int cmd_commit(int argc, const char **argv, const char *prefix)\n \t\t\tdie(_(\"could not parse HEAD commit\"));\n \t}\n \tverbose = -1; /* unspecified */\n+\tstrvec_pushl(&run_trailer.args, \"interpret-trailers\",\n+\t\t\"--in-place\", \"--where=end\", git_path_commit_editmsg(), NULL);\n \targc = parse_and_validate_options(argc, argv, builtin_commit_options,\n \t\t\t\t\t  builtin_commit_usage,\n \t\t\t\t\t  prefix, current_head, &s);\ndiff --git a/t/t7502-commit-porcelain.sh b/t/t7502-commit-porcelain.sh\nindex 6396897cc818..4b9ac4587d17 100755\n--- a/t/t7502-commit-porcelain.sh\n+++ b/t/t7502-commit-porcelain.sh\n@@ -154,6 +154,26 @@ test_expect_success 'sign off' '\n \n '\n \n+test_expect_success 'trailer' '\n+\t>file1 &&\n+\tgit add file1 &&\n+\tgit commit -s --trailer \"Signed-off-by:C O Mitter1 <committer1@example.com>\" \\\n+\t--trailer \"Helped-by:C O Mitter2 <committer2@example.com>\"  \\\n+\t--trailer \"Reported-by:C O Mitter3 <committer3@example.com>\" \\\n+\t--trailer \"Mentored-by:C O Mitter4 <committer4@example.com>\" \\\n+\t-m \"hello\" &&\n+\tgit cat-file commit HEAD >commit.msg &&\n+\tsed -e \"1,7d\" commit.msg >actual &&\n+\tcat >expected <<-\\EOF &&\n+\tSigned-off-by: C O Mitter <committer@example.com>\n+\tSigned-off-by: C O Mitter1 <committer1@example.com>\n+\tHelped-by: C O Mitter2 <committer2@example.com>\n+\tReported-by: C O Mitter3 <committer3@example.com>\n+\tMentored-by: C O Mitter4 <committer4@example.com>\n+\tEOF\n+\ttest_cmp expected actual\n+'\n+\n test_expect_success 'multiple -m' '\n \n \t>negative &&\n\nbase-commit: 13d7ab6b5d7929825b626f050b62a11241ea4945\n-- \ngitgitgadget\n"},{"id":"419074","messageId":"87czw1yj6q.fsf@gmail.com","threadId":"55288","inReplyTo":"pull.901.v3.git.1615726978059.gitgitgadget@gmail.com","subject":"Re: [PATCH v3] [GSOC] commit: add --trailer option","fromName":"Rafael Silva","fromEmail":"rafaeloliveira.cs@gmail.com","sentAt":"2021-03-14T13:10:33Z","receivedAt":"2021-03-14T13:47:19Z","isPatch":true,"sender":{"key":"rafaeloliveira.cs@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5935135?v=4"},"body":"\n\"ZheNing Hu via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n\n> diff --git a/builtin/commit.c b/builtin/commit.c\n> index 739110c5a7f6..abbd136b27f0 100644\n> --- a/builtin/commit.c\n> +++ b/builtin/commit.c\n> @@ -113,6 +113,8 @@ static int config_commit_verbose = -1; /* unspecified */\n>  static int no_post_rewrite, allow_empty_message, pathspec_file_nul;\n>  static char *untracked_files_arg, *force_date, *ignore_submodule_arg, *ignored_arg;\n>  static char *sign_commit, *pathspec_from_file;\n> +struct child_process run_trailer = CHILD_PROCESS_INIT;\n> +static const char *trailer;\n>  \n>  /*\n>   * The default commit message cleanup mode will remove the lines\n> @@ -131,6 +133,17 @@ 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> +\tif (unset) {\n> +\t\tstrvec_clear(&run_trailer.args);\n> +\t\treturn -1;\n> +\t}\n> +\trun_trailer.git_cmd = 1;\n> +\tstrvec_pushl(&run_trailer.args, \"--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> @@ -958,6 +971,8 @@ static int prepare_to_commit(const char *index_file, const char *prefix,\n>  \n>  \tfclose(s->fp);\n>  \n> +\trun_command(&run_trailer);\n> +\n\nThere is slight problem with running the command unconditionally.\nIf no --trailer is passed, then the opt_pass_trailer() backend\nis never called, which consequently will not set the trailer\ncommand \".git_cmd\" option to 1.\n\nThis will lead the run_command() API to not interpret the command as git\ninternal, and attempt to launch as an usual command \"interpret-trailers\"\nthat will likely not exist or launch an unwanted command that is not\npart of the GIT suite.\n\nThis can be seen by running `git commit` without any options:\n\n     $ ./bin-wrappers/git -C /tmp/test commit\n     error: cannot run interpret-trailers: No such file or directory\n     ...\n\nThe `.git_cmd` should be set to true before running the command.\n\n(the above output is from a built version with v2.31.0-rc2 + this patch\n for confirmation).\n\n\nFurthermore, in my opinion, we shouldn't even bother to run the command\nif no --trailer is passed, otherwise, we always be paying the cost of\nlaunching an OS process regardless if the user doesn't want to add\ntrailers in theirs projects.\n\nWith that said and based on this current implementation, maybe an\nimproved version will look like:\n\n        if (run_trailer.args.nr) {\n                run_trailer.git_cmd = 1;\n                run_command(&run_trailer);\n        }\n\nNaturally the `git_cmd = 1` will be removed from opt_pass_trailer()\nfunction as it won't be necessary. As minor bonus, we don't end up\nsetting the value for every new --trailer :).\n\n>  \t/*\n>  \t * Reject an attempt to record a non-merge empty commit without\n>  \t * explicit --allow-empty. In the cherry-pick case, it may be\n> @@ -1507,6 +1522,7 @@ int cmd_commit(int argc, const char **argv, const char *prefix)\n>  \t\tOPT_STRING(0, \"fixup\", &fixup_message, N_(\"commit\"), N_(\"use autosquash formatted message to fixup 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(0, \"trailer\", &trailer, N_(\"trailer\"), N_(\"trailer(s) to add\"), opt_pass_trailer),\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> @@ -1577,6 +1593,8 @@ int cmd_commit(int argc, const char **argv, const char *prefix)\n>  \t\t\tdie(_(\"could not parse HEAD commit\"));\n>  \t}\n>  \tverbose = -1; /* unspecified */\n> +\tstrvec_pushl(&run_trailer.args, \"interpret-trailers\",\n> +\t\t\"--in-place\", \"--where=end\", git_path_commit_editmsg(), NULL);\n\nStyle: The \"--in-place\" part should be aligned with the parentheses\nmuch the like the following line with the \"argc = parse_and_validate_options....\".\n\nFor example:\n\n\tstrvec_pushl(&run_trailer.args, \"interpret-trailers\",\n\t\t     \"--in-place\", \"--where=end\", .....\n\n>  \targc = parse_and_validate_options(argc, argv, builtin_commit_options,\n>  \t\t\t\t\t  builtin_commit_usage,\n>  \t\t\t\t\t  prefix, current_head, &s);\n> diff --git a/t/t7502-commit-porcelain.sh b/t/t7502-commit-porcelain.sh\n> index 6396897cc818..4b9ac4587d17 100755\n> --- a/t/t7502-commit-porcelain.sh\n> +++ b/t/t7502-commit-porcelain.sh\n> @@ -154,6 +154,26 @@ test_expect_success 'sign off' '\n>  \n>  '\n>  \n> +test_expect_success 'trailer' '\n> +\t>file1 &&\n> +\tgit add file1 &&\n> +\tgit commit -s --trailer \"Signed-off-by:C O Mitter1 <committer1@example.com>\" \\\n> +\t--trailer \"Helped-by:C O Mitter2 <committer2@example.com>\"  \\\n> +\t--trailer \"Reported-by:C O Mitter3 <committer3@example.com>\" \\\n> +\t--trailer \"Mentored-by:C O Mitter4 <committer4@example.com>\" \\\n> +\t-m \"hello\" &&\n\nPerhaps here, the --trailer lines and \"-m hello\" option should be\nindent in order to make it clear that these option are part of the\n\"git commit\" from the above line, something like this:\n\n\tgit commit -s --trailer \"Signed-off-by:C O Mitter1 <committer1@example.com>\" \\\n\t        --trailer \"Helped-by:C O Mitter2 <committer2@example.com>\"  \\\n\t        --trailer \"Reported-by:C O Mitter3 <committer3@example.com>\" \\\n\t        --trailer \"Mentored-by:C O Mitter4 <committer4@example.com>\" \\\n\t        -m \"hello\" &&\n\n> +\tgit cat-file commit HEAD >commit.msg &&\n> +\tsed -e \"1,7d\" commit.msg >actual &&\n> +\tcat >expected <<-\\EOF &&\n> +\tSigned-off-by: C O Mitter <committer@example.com>\n> +\tSigned-off-by: C O Mitter1 <committer1@example.com>\n> +\tHelped-by: C O Mitter2 <committer2@example.com>\n> +\tReported-by: C O Mitter3 <committer3@example.com>\n> +\tMentored-by: C O Mitter4 <committer4@example.com>\n> +\tEOF\n> +\ttest_cmp expected actual\n> +'\n> +\n>  test_expect_success 'multiple -m' '\n\n\nHope these comments are useful.\n-- \nThanks\nRafael\n"},{"id":"419077","messageId":"CAOLTT8RKg_yMnxQ7FXX=bytAEST8KMKbCyeP6LemRdZvvQ5Kpg@mail.gmail.com","threadId":"55288","inReplyTo":"87czw1yj6q.fsf@gmail.com","subject":"Re: [PATCH v3] [GSOC] commit: add --trailer option","fromName":"ZheNing Hu","fromEmail":"adlternative@gmail.com","sentAt":"2021-03-14T14:13:53Z","receivedAt":"2021-03-14T14:17:22Z","isPatch":true,"sender":{"key":"adlternative@gmail.com","avatar":"https://avatars.githubusercontent.com/u/58138461?v=4"},"body":"Rafael Silva <rafaeloliveira.cs@gmail.com> 于2021年3月14日周日 下午9:46写道：\n>\n>\n> \"ZheNing Hu via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n>\n> > diff --git a/builtin/commit.c b/builtin/commit.c\n> > index 739110c5a7f6..abbd136b27f0 100644\n> > --- a/builtin/commit.c\n> > +++ b/builtin/commit.c\n> > @@ -113,6 +113,8 @@ static int config_commit_verbose = -1; /* unspecified */\n> >  static int no_post_rewrite, allow_empty_message, pathspec_file_nul;\n> >  static char *untracked_files_arg, *force_date, *ignore_submodule_arg, *ignored_arg;\n> >  static char *sign_commit, *pathspec_from_file;\n> > +struct child_process run_trailer = CHILD_PROCESS_INIT;\n> > +static const char *trailer;\n> >\n> >  /*\n> >   * The default commit message cleanup mode will remove the lines\n> > @@ -131,6 +133,17 @@ 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> > +     if (unset) {\n> > +             strvec_clear(&run_trailer.args);\n> > +             return -1;\n> > +     }\n> > +     run_trailer.git_cmd = 1;\n> > +     strvec_pushl(&run_trailer.args, \"--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> > @@ -958,6 +971,8 @@ static int prepare_to_commit(const char *index_file, const char *prefix,\n> >\n> >       fclose(s->fp);\n> >\n> > +     run_command(&run_trailer);\n> > +\n>\n> There is slight problem with running the command unconditionally.\n> If no --trailer is passed, then the opt_pass_trailer() backend\n> is never called, which consequently will not set the trailer\n> command \".git_cmd\" option to 1.\n>\n> This will lead the run_command() API to not interpret the command as git\n> internal, and attempt to launch as an usual command \"interpret-trailers\"\n> that will likely not exist or launch an unwanted command that is not\n> part of the GIT suite.\n>\n> This can be seen by running `git commit` without any options:\n>\n>      $ ./bin-wrappers/git -C /tmp/test commit\n>      error: cannot run interpret-trailers: No such file or directory\n>      ...\n>\n> The `.git_cmd` should be set to true before running the command.\n>\n> (the above output is from a built version with v2.31.0-rc2 + this patch\n>  for confirmation).\n>\n>\n> Furthermore, in my opinion, we shouldn't even bother to run the command\n> if no --trailer is passed, otherwise, we always be paying the cost of\n> launching an OS process regardless if the user doesn't want to add\n> trailers in theirs projects.\n>\n> With that said and based on this current implementation, maybe an\n> improved version will look like:\n>\n>         if (run_trailer.args.nr) {\n>                 run_trailer.git_cmd = 1;\n>                 run_command(&run_trailer);\n>         }\n>\n> Naturally the `git_cmd = 1` will be removed from opt_pass_trailer()\n> function as it won't be necessary. As minor bonus, we don't end up\n> setting the value for every new --trailer :).\n\nThank you, I didn't notice the problem before :).\n\n>\n> >       /*\n> >        * Reject an attempt to record a non-merge empty commit without\n> >        * explicit --allow-empty. In the cherry-pick case, it may be\n> > @@ -1507,6 +1522,7 @@ int cmd_commit(int argc, const char **argv, const char *prefix)\n> >               OPT_STRING(0, \"fixup\", &fixup_message, N_(\"commit\"), N_(\"use autosquash formatted message to fixup 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(0, \"trailer\", &trailer, N_(\"trailer\"), N_(\"trailer(s) to add\"), opt_pass_trailer),\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> > @@ -1577,6 +1593,8 @@ int cmd_commit(int argc, const char **argv, const char *prefix)\n> >                       die(_(\"could not parse HEAD commit\"));\n> >       }\n> >       verbose = -1; /* unspecified */\n> > +     strvec_pushl(&run_trailer.args, \"interpret-trailers\",\n> > +             \"--in-place\", \"--where=end\", git_path_commit_editmsg(), NULL);\n>\n> Style: The \"--in-place\" part should be aligned with the parentheses\n> much the like the following line with the \"argc = parse_and_validate_options....\".\n>\n\nWell, the CodingGuidelines say it's both vaild. But you are right,\ntry to keep the same format as nearby code will be better.\n\n> For example:\n>\n>         strvec_pushl(&run_trailer.args, \"interpret-trailers\",\n>                      \"--in-place\", \"--where=end\", .....\n>\n> >       argc = parse_and_validate_options(argc, argv, builtin_commit_options,\n> >                                         builtin_commit_usage,\n> >                                         prefix, current_head, &s);\n> > diff --git a/t/t7502-commit-porcelain.sh b/t/t7502-commit-porcelain.sh\n> > index 6396897cc818..4b9ac4587d17 100755\n> > --- a/t/t7502-commit-porcelain.sh\n> > +++ b/t/t7502-commit-porcelain.sh\n> > @@ -154,6 +154,26 @@ test_expect_success 'sign off' '\n> >\n> >  '\n> >\n> > +test_expect_success 'trailer' '\n> > +     >file1 &&\n> > +     git add file1 &&\n> > +     git commit -s --trailer \"Signed-off-by:C O Mitter1 <committer1@example.com>\" \\\n> > +     --trailer \"Helped-by:C O Mitter2 <committer2@example.com>\"  \\\n> > +     --trailer \"Reported-by:C O Mitter3 <committer3@example.com>\" \\\n> > +     --trailer \"Mentored-by:C O Mitter4 <committer4@example.com>\" \\\n> > +     -m \"hello\" &&\n>\n> Perhaps here, the --trailer lines and \"-m hello\" option should be\n> indent in order to make it clear that these option are part of the\n> \"git commit\" from the above line, something like this:\n>\n>         git commit -s --trailer \"Signed-off-by:C O Mitter1 <committer1@example.com>\" \\\n>                 --trailer \"Helped-by:C O Mitter2 <committer2@example.com>\"  \\\n>                 --trailer \"Reported-by:C O Mitter3 <committer3@example.com>\" \\\n>                 --trailer \"Mentored-by:C O Mitter4 <committer4@example.com>\" \\\n>                 -m \"hello\" &&\n>\n> > +     git cat-file commit HEAD >commit.msg &&\n> > +     sed -e \"1,7d\" commit.msg >actual &&\n> > +     cat >expected <<-\\EOF &&\n> > +     Signed-off-by: C O Mitter <committer@example.com>\n> > +     Signed-off-by: C O Mitter1 <committer1@example.com>\n> > +     Helped-by: C O Mitter2 <committer2@example.com>\n> > +     Reported-by: C O Mitter3 <committer3@example.com>\n> > +     Mentored-by: C O Mitter4 <committer4@example.com>\n> > +     EOF\n> > +     test_cmp expected actual\n> > +'\n> > +\n> >  test_expect_success 'multiple -m' '\n>\n>\n> Hope these comments are useful.\n> --\n> Thanks\n> Rafael\n\n I am gratitude to you for helping me.\n--\nThanks\nZheNing Hu\n"},{"id":"419079","messageId":"pull.901.v4.git.1615737505834.gitgitgadget@gmail.com","threadId":"55288","inReplyTo":"pull.901.v3.git.1615726978059.gitgitgadget@gmail.com","subject":"[PATCH v4] [GSOC] commit: add --trailer option","fromName":"ZheNing Hu via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2021-03-14T15:58:25Z","receivedAt":"2021-03-14T15:59:32Z","isPatch":true,"sender":{"key":"adlternative@gmail.com","avatar":"https://avatars.githubusercontent.com/u/58138461?v=4"},"body":"From: ZheNing Hu <adlternative@gmail.com>\n\nHistorically, Git has supported the 'Signed-off-by' commit trailer\nusing the '--signoff' and the '-s' option from the command line.\nBut users may need to provide other trailer information from the\ncommand line such as \"Helped-by\", \"Reported-by\", \"Mentored-by\",\n\nNow implement a new `--trailer <token>[(=|:)<value>]` option to pass\nother trailers to `interpret-trailers` and insert them into commit\nmessages.\n\nSigned-off-by: ZheNing Hu <adlternative@gmail.com>\n---\n    [GSOC] commit: add --trailer option\n    \n    Now maintainers or developers can also use commit\n    --trailer=\"Signed-off-by:commiter<email>\" from the command line to\n    provide trailers to commit messages. This solution may be more\n    generalized than v1.\n\nPublished-As: https://github.com/gitgitgadget/git/releases/tag/pr-901%2Fadlternative%2Fcommit-with-multiple-signatures-v4\nFetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-901/adlternative/commit-with-multiple-signatures-v4\nPull-Request: https://github.com/gitgitgadget/git/pull/901\n\nRange-diff vs v3:\n\n 1:  b4e161a98f8b ! 1:  dc507553ef4f [GSOC] commit: add --trailer option\n     @@ Documentation/git-commit.txt: The `-m` option is mutually exclusive with `-c`, `\n       \n      +--trailer <token>[(=|:)<value>]::\n      +\tSpecify a (<token>, <value>) pair that should be applied as a\n     -+\ttrailer. (e.g.  `git commit --trailer \"Signed-off-by:C O Mitter <committer@example.com>\" \\\n     -+\t--trailer \"Helped-by:C O Mitter <committer@example.com>\"`will add the \"Signed-off\" trailer\n     ++\ttrailer. (e.g. `git commit --trailer \"Signed-off-by:C O Mitter \\\n     ++\t<committer@example.com>\" --trailer \"Helped-by:C O Mitter \\\n     ++\t<committer@example.com>\"` will add the \"Signed-off\" trailer\n      +\tand the \"Helped-by\" trailer in the commit message.)\n      +\n       -n::\n     @@ builtin/commit.c: static struct strbuf message = STRBUF_INIT;\n      +\t\tstrvec_clear(&run_trailer.args);\n      +\t\treturn -1;\n      +\t}\n     -+\trun_trailer.git_cmd = 1;\n      +\tstrvec_pushl(&run_trailer.args, \"--trailer\", arg, NULL);\n      +\treturn 0;\n      +}\n     @@ builtin/commit.c: static int prepare_to_commit(const char *index_file, const cha\n       \n       \tfclose(s->fp);\n       \n     -+\trun_command(&run_trailer);\n     ++\tif (run_trailer.args.nr != 4) {\n     ++\t\trun_trailer.git_cmd = 1;\n     ++\t\trun_command(&run_trailer);\n     ++\t} else\n     ++\t\tstrvec_clear(&run_trailer.args);\n      +\n       \t/*\n       \t * Reject an attempt to record a non-merge empty commit without\n     @@ builtin/commit.c: int cmd_commit(int argc, const char **argv, const char *prefix\n       \t}\n       \tverbose = -1; /* unspecified */\n      +\tstrvec_pushl(&run_trailer.args, \"interpret-trailers\",\n     -+\t\t\"--in-place\", \"--where=end\", git_path_commit_editmsg(), NULL);\n     ++\t\t    \"--in-place\", \"--where=end\", git_path_commit_editmsg(), NULL);\n       \targc = parse_and_validate_options(argc, argv, builtin_commit_options,\n       \t\t\t\t\t  builtin_commit_usage,\n       \t\t\t\t\t  prefix, current_head, &s);\n     @@ t/t7502-commit-porcelain.sh: test_expect_success 'sign off' '\n      +\t>file1 &&\n      +\tgit add file1 &&\n      +\tgit commit -s --trailer \"Signed-off-by:C O Mitter1 <committer1@example.com>\" \\\n     -+\t--trailer \"Helped-by:C O Mitter2 <committer2@example.com>\"  \\\n     -+\t--trailer \"Reported-by:C O Mitter3 <committer3@example.com>\" \\\n     -+\t--trailer \"Mentored-by:C O Mitter4 <committer4@example.com>\" \\\n     -+\t-m \"hello\" &&\n     ++\t\t--trailer \"Helped-by:C O Mitter2 <committer2@example.com>\"  \\\n     ++\t\t--trailer \"Reported-by:C O Mitter3 <committer3@example.com>\" \\\n     ++\t\t--trailer \"Mentored-by:C O Mitter4 <committer4@example.com>\" \\\n     ++\t\t-m \"hello\" &&\n      +\tgit cat-file commit HEAD >commit.msg &&\n      +\tsed -e \"1,7d\" commit.msg >actual &&\n      +\tcat >expected <<-\\EOF &&\n\n\n Documentation/git-commit.txt |  9 ++++++++-\n builtin/commit.c             | 21 +++++++++++++++++++++\n t/t7502-commit-porcelain.sh  | 20 ++++++++++++++++++++\n 3 files changed, 49 insertions(+), 1 deletion(-)\n\ndiff --git a/Documentation/git-commit.txt b/Documentation/git-commit.txt\nindex 17150fa7eabe..73a7507db47f 100644\n--- a/Documentation/git-commit.txt\n+++ b/Documentation/git-commit.txt\n@@ -14,7 +14,7 @@ SYNOPSIS\n \t   [--allow-empty-message] [--no-verify] [-e] [--author=<author>]\n \t   [--date=<date>] [--cleanup=<mode>] [--[no-]status]\n \t   [-i | -o] [--pathspec-from-file=<file> [--pathspec-file-nul]]\n-\t   [-S[<keyid>]] [--] [<pathspec>...]\n+\t   [-S[<keyid>]] [--] [<pathspec>...] [(--trailer <token>[(=|:)<value>])...]\n \n DESCRIPTION\n -----------\n@@ -166,6 +166,13 @@ The `-m` option is mutually exclusive with `-c`, `-C`, and `-F`.\n \n include::signoff-option.txt[]\n \n+--trailer <token>[(=|:)<value>]::\n+\tSpecify a (<token>, <value>) pair that should be applied as a\n+\ttrailer. (e.g. `git commit --trailer \"Signed-off-by:C O Mitter \\\n+\t<committer@example.com>\" --trailer \"Helped-by:C O Mitter \\\n+\t<committer@example.com>\"` will add the \"Signed-off\" trailer\n+\tand the \"Helped-by\" trailer in the commit message.)\n+\n -n::\n --no-verify::\n \tThis option bypasses the pre-commit and commit-msg hooks.\ndiff --git a/builtin/commit.c b/builtin/commit.c\nindex 739110c5a7f6..b35ae5c9790d 100644\n--- a/builtin/commit.c\n+++ b/builtin/commit.c\n@@ -113,6 +113,8 @@ static int config_commit_verbose = -1; /* unspecified */\n static int no_post_rewrite, allow_empty_message, pathspec_file_nul;\n static char *untracked_files_arg, *force_date, *ignore_submodule_arg, *ignored_arg;\n static char *sign_commit, *pathspec_from_file;\n+struct child_process run_trailer = CHILD_PROCESS_INIT;\n+static const char *trailer;\n \n /*\n  * The default commit message cleanup mode will remove the lines\n@@ -131,6 +133,16 @@ 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+\tif (unset) {\n+\t\tstrvec_clear(&run_trailer.args);\n+\t\treturn -1;\n+\t}\n+\tstrvec_pushl(&run_trailer.args, \"--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@@ -958,6 +970,12 @@ static int prepare_to_commit(const char *index_file, const char *prefix,\n \n \tfclose(s->fp);\n \n+\tif (run_trailer.args.nr != 4) {\n+\t\trun_trailer.git_cmd = 1;\n+\t\trun_command(&run_trailer);\n+\t} else\n+\t\tstrvec_clear(&run_trailer.args);\n+\n \t/*\n \t * Reject an attempt to record a non-merge empty commit without\n \t * explicit --allow-empty. In the cherry-pick case, it may be\n@@ -1507,6 +1525,7 @@ int cmd_commit(int argc, const char **argv, const char *prefix)\n \t\tOPT_STRING(0, \"fixup\", &fixup_message, N_(\"commit\"), N_(\"use autosquash formatted message to fixup 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(0, \"trailer\", &trailer, N_(\"trailer\"), N_(\"trailer(s) to add\"), opt_pass_trailer),\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@@ -1577,6 +1596,8 @@ int cmd_commit(int argc, const char **argv, const char *prefix)\n \t\t\tdie(_(\"could not parse HEAD commit\"));\n \t}\n \tverbose = -1; /* unspecified */\n+\tstrvec_pushl(&run_trailer.args, \"interpret-trailers\",\n+\t\t    \"--in-place\", \"--where=end\", git_path_commit_editmsg(), NULL);\n \targc = parse_and_validate_options(argc, argv, builtin_commit_options,\n \t\t\t\t\t  builtin_commit_usage,\n \t\t\t\t\t  prefix, current_head, &s);\ndiff --git a/t/t7502-commit-porcelain.sh b/t/t7502-commit-porcelain.sh\nindex 6396897cc818..0acf23799931 100755\n--- a/t/t7502-commit-porcelain.sh\n+++ b/t/t7502-commit-porcelain.sh\n@@ -154,6 +154,26 @@ test_expect_success 'sign off' '\n \n '\n \n+test_expect_success 'trailer' '\n+\t>file1 &&\n+\tgit add file1 &&\n+\tgit commit -s --trailer \"Signed-off-by:C O Mitter1 <committer1@example.com>\" \\\n+\t\t--trailer \"Helped-by:C O Mitter2 <committer2@example.com>\"  \\\n+\t\t--trailer \"Reported-by:C O Mitter3 <committer3@example.com>\" \\\n+\t\t--trailer \"Mentored-by:C O Mitter4 <committer4@example.com>\" \\\n+\t\t-m \"hello\" &&\n+\tgit cat-file commit HEAD >commit.msg &&\n+\tsed -e \"1,7d\" commit.msg >actual &&\n+\tcat >expected <<-\\EOF &&\n+\tSigned-off-by: C O Mitter <committer@example.com>\n+\tSigned-off-by: C O Mitter1 <committer1@example.com>\n+\tHelped-by: C O Mitter2 <committer2@example.com>\n+\tReported-by: C O Mitter3 <committer3@example.com>\n+\tMentored-by: C O Mitter4 <committer4@example.com>\n+\tEOF\n+\ttest_cmp expected actual\n+'\n+\n test_expect_success 'multiple -m' '\n \n \t>negative &&\n\nbase-commit: 13d7ab6b5d7929825b626f050b62a11241ea4945\n-- \ngitgitgadget\n"},{"id":"419112","messageId":"xmqq7dm94amz.fsf@gitster.g","threadId":"55288","inReplyTo":"CAP8UFD2_SSza-zsoqS_ugQBryiTvm0PqzrJDAuriT2jmjHM-uA@mail.gmail.com","subject":"Re: [PATCH v2] [GSOC] commit: add trailer command","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2021-03-14T22:45:56Z","receivedAt":"2021-03-14T22:46:38Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Christian Couder <christian.couder@gmail.com> writes:\n\n> Ok, I agree that it's a good idea to have a good generic solution\n> first, before having specialized options for specific trailers.\n>\n>> Published-As: https://github.com/gitgitgadget/git/releases/tag/pr-901%2Fadlternative%2Fcommit-with-multiple-signatures-v2\n>> Fetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-901/adlternative/commit-with-multiple-signatures-v2\n>> Pull-Request: https://github.com/gitgitgadget/git/pull/901\n>>\n>> Range-diff vs v1:\n>\n> If this patch series has very few code and commit messages in common\n> with a previous attempt at implementing similar features, it might be\n> better to make it a new patch series rather than a v2. This could\n> avoid sending range-diffs that are mostly useless.\n\nThanks for all the good suggestions.\n\n"},{"id":"419116","messageId":"xmqqh7ld2syx.fsf@gitster.g","threadId":"55288","inReplyTo":"pull.901.v4.git.1615737505834.gitgitgadget@gmail.com","subject":"Re: [PATCH v4] [GSOC] commit: add --trailer option","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2021-03-14T23:52:54Z","receivedAt":"2021-03-14T23:53:56Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"\"ZheNing Hu via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n\n> @@ -958,6 +970,12 @@ static int prepare_to_commit(const char *index_file, const char *prefix,\n>  \n>  \tfclose(s->fp);\n>  \n> +\tif (run_trailer.args.nr != 4) {\n> +\t\trun_trailer.git_cmd = 1;\n> +\t\trun_command(&run_trailer);\n\nThis hardcoded magic 4 is very brittle.  It probably makes sense to\nhave another string vector, that is used only to accumulate\n--trailer arguments and nothing else, and check if that string\nvector is empty here.\n\nIOW this part would become ...\n\n\tif (trailer_args.nr) {\n\t\tstrvec_pushl(&run_trailer.args,\t\"interpret-trailers\", \n\t\t\t     \"--in-place\", ...);\n\t\tstrvec_pushv(&run_trailer.args, trailer_args.v);\n\t\trun_trailer.git_cmd = 1;\n\t\trun_command(&run_trailer);\n\t}\n\n> +\t} else\n> +\t\tstrvec_clear(&run_trailer.args);\n\n... and there is no need to have \"else\" that won't need to do\nanything.\n\n>  \t/*\n>  \t * Reject an attempt to record a non-merge empty commit without\n>  \t * explicit --allow-empty. In the cherry-pick case, it may be\n> @@ -1507,6 +1525,7 @@ int cmd_commit(int argc, const char **argv, const char *prefix)\n>  \t\tOPT_STRING(0, \"fixup\", &fixup_message, N_(\"commit\"), N_(\"use autosquash formatted message to fixup 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(0, \"trailer\", &trailer, N_(\"trailer\"), N_(\"trailer(s) to add\"), opt_pass_trailer),\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> @@ -1577,6 +1596,8 @@ int cmd_commit(int argc, const char **argv, const char *prefix)\n>  \t\t\tdie(_(\"could not parse HEAD commit\"));\n>  \t}\n>  \tverbose = -1; /* unspecified */\n> +\tstrvec_pushl(&run_trailer.args, \"interpret-trailers\",\n> +\t\t    \"--in-place\", \"--where=end\", git_path_commit_editmsg(), NULL);\n\nAnd this line will be gone.\n\n>  \targc = parse_and_validate_options(argc, argv, builtin_commit_options,\n>  \t\t\t\t\t  builtin_commit_usage,\n>  \t\t\t\t\t  prefix, current_head, &s);\n"},{"id":"419117","messageId":"CAOLTT8RNKFC_NzZXGvE4Zz+NmVHCUAn-jZFu-0Nm=7JAS0jBjA@mail.gmail.com","threadId":"55288","inReplyTo":"xmqqh7ld2syx.fsf@gitster.g","subject":"Re: [PATCH v4] [GSOC] commit: add --trailer option","fromName":"ZheNing Hu","fromEmail":"adlternative@gmail.com","sentAt":"2021-03-15T01:27:28Z","receivedAt":"2021-03-15T01:28:26Z","isPatch":true,"sender":{"key":"adlternative@gmail.com","avatar":"https://avatars.githubusercontent.com/u/58138461?v=4"},"body":"Junio C Hamano <gitster@pobox.com> 于2021年3月15日周一 上午7:52写道：\n> IOW this part would become ...\n>\n>         if (trailer_args.nr) {\n>                 strvec_pushl(&run_trailer.args, \"interpret-trailers\",\n>                              \"--in-place\", ...);\n>                 strvec_pushv(&run_trailer.args, trailer_args.v);\n>                 run_trailer.git_cmd = 1;\n>                 run_command(&run_trailer);\n>         }\n>\n> > +     } else\n> > +             strvec_clear(&run_trailer.args);\n>\n> ... and there is no need to have \"else\" that won't need to do\n> anything.\n\nYes, but we also should clear \"trailer_args\" in \"else\" here, and check the\nreturn value of the \"run_command()\" for clear \"run_trailer.args\".\n\n>\n> >       verbose = -1; /* unspecified */\n> > +     strvec_pushl(&run_trailer.args, \"interpret-trailers\",\n> > +                 \"--in-place\", \"--where=end\", git_path_commit_editmsg(), NULL);\n>\n> And this line will be gone.\n>\n\nIndeed so.\n\nThanks.\n"},{"id":"419118","messageId":"pull.901.v5.git.1615778692784.gitgitgadget@gmail.com","threadId":"55288","inReplyTo":"pull.901.v4.git.1615737505834.gitgitgadget@gmail.com","subject":"[PATCH v5] [GSOC] commit: add --trailer option","fromName":"ZheNing Hu via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2021-03-15T03:24:52Z","receivedAt":"2021-03-15T03:25:48Z","isPatch":true,"sender":{"key":"adlternative@gmail.com","avatar":"https://avatars.githubusercontent.com/u/58138461?v=4"},"body":"From: ZheNing Hu <adlternative@gmail.com>\n\nHistorically, Git has supported the 'Signed-off-by' commit trailer\nusing the '--signoff' and the '-s' option from the command line.\nBut users may need to provide other trailer information from the\ncommand line such as \"Helped-by\", \"Reported-by\", \"Mentored-by\",\n\nNow implement a new `--trailer <token>[(=|:)<value>]` option to pass\nother trailers to `interpret-trailers` and insert them into commit\nmessages.\n\nSigned-off-by: ZheNing Hu <adlternative@gmail.com>\n---\n    [GSOC] commit: add --trailer option\n    \n    Now maintainers or developers can also use commit\n    --trailer=\"Signed-off-by:commiter<email>\" from the command line to\n    provide trailers to commit messages. This solution may be more\n    generalized than v1.\n\nPublished-As: https://github.com/gitgitgadget/git/releases/tag/pr-901%2Fadlternative%2Fcommit-with-multiple-signatures-v5\nFetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-901/adlternative/commit-with-multiple-signatures-v5\nPull-Request: https://github.com/gitgitgadget/git/pull/901\n\nRange-diff vs v4:\n\n 1:  dc507553ef4f ! 1:  ca91accb2852 [GSOC] commit: add --trailer option\n     @@ builtin/commit.c: static int config_commit_verbose = -1; /* unspecified */\n       static char *untracked_files_arg, *force_date, *ignore_submodule_arg, *ignored_arg;\n       static char *sign_commit, *pathspec_from_file;\n      +struct child_process run_trailer = CHILD_PROCESS_INIT;\n     ++struct strvec trailer_args = STRVEC_INIT;\n      +static const char *trailer;\n       \n       /*\n     @@ builtin/commit.c: static struct strbuf message = STRBUF_INIT;\n      +static int opt_pass_trailer(const struct option *opt, const char *arg, int unset)\n      +{\n      +\tif (unset) {\n     -+\t\tstrvec_clear(&run_trailer.args);\n     ++\t\tstrvec_clear(&trailer_args);\n      +\t\treturn -1;\n      +\t}\n     -+\tstrvec_pushl(&run_trailer.args, \"--trailer\", arg, NULL);\n     ++\tstrvec_pushl(&trailer_args, \"--trailer\", arg, NULL);\n      +\treturn 0;\n      +}\n      +\n     @@ builtin/commit.c: static int prepare_to_commit(const char *index_file, const cha\n       \n       \tfclose(s->fp);\n       \n     -+\tif (run_trailer.args.nr != 4) {\n     ++\tif (trailer_args.nr) {\n     ++\t\tstrvec_pushl(&run_trailer.args, \"interpret-trailers\",\n     ++\t\t\t     \"--in-place\", \"--where=end\", git_path_commit_editmsg(), NULL);\n     ++\t\tstrvec_pushv(&run_trailer.args, trailer_args.v);\n      +\t\trun_trailer.git_cmd = 1;\n     -+\t\trun_command(&run_trailer);\n     -+\t} else\n     -+\t\tstrvec_clear(&run_trailer.args);\n     ++\t\tif (run_command(&run_trailer))\n     ++\t\t\tstrvec_clear(&run_trailer.args);\n     ++\t\tstrvec_clear(&trailer_args);\n     ++\t}\n      +\n       \t/*\n       \t * Reject an attempt to record a non-merge empty commit without\n     @@ builtin/commit.c: int cmd_commit(int argc, const char **argv, const char *prefix\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     -@@ builtin/commit.c: int cmd_commit(int argc, const char **argv, const char *prefix)\n     - \t\t\tdie(_(\"could not parse HEAD commit\"));\n     - \t}\n     - \tverbose = -1; /* unspecified */\n     -+\tstrvec_pushl(&run_trailer.args, \"interpret-trailers\",\n     -+\t\t    \"--in-place\", \"--where=end\", git_path_commit_editmsg(), NULL);\n     - \targc = parse_and_validate_options(argc, argv, builtin_commit_options,\n     - \t\t\t\t\t  builtin_commit_usage,\n     - \t\t\t\t\t  prefix, current_head, &s);\n      \n       ## t/t7502-commit-porcelain.sh ##\n      @@ t/t7502-commit-porcelain.sh: test_expect_success 'sign off' '\n\n\n Documentation/git-commit.txt |  9 ++++++++-\n builtin/commit.c             | 24 ++++++++++++++++++++++++\n t/t7502-commit-porcelain.sh  | 20 ++++++++++++++++++++\n 3 files changed, 52 insertions(+), 1 deletion(-)\n\ndiff --git a/Documentation/git-commit.txt b/Documentation/git-commit.txt\nindex 17150fa7eabe..73a7507db47f 100644\n--- a/Documentation/git-commit.txt\n+++ b/Documentation/git-commit.txt\n@@ -14,7 +14,7 @@ SYNOPSIS\n \t   [--allow-empty-message] [--no-verify] [-e] [--author=<author>]\n \t   [--date=<date>] [--cleanup=<mode>] [--[no-]status]\n \t   [-i | -o] [--pathspec-from-file=<file> [--pathspec-file-nul]]\n-\t   [-S[<keyid>]] [--] [<pathspec>...]\n+\t   [-S[<keyid>]] [--] [<pathspec>...] [(--trailer <token>[(=|:)<value>])...]\n \n DESCRIPTION\n -----------\n@@ -166,6 +166,13 @@ The `-m` option is mutually exclusive with `-c`, `-C`, and `-F`.\n \n include::signoff-option.txt[]\n \n+--trailer <token>[(=|:)<value>]::\n+\tSpecify a (<token>, <value>) pair that should be applied as a\n+\ttrailer. (e.g. `git commit --trailer \"Signed-off-by:C O Mitter \\\n+\t<committer@example.com>\" --trailer \"Helped-by:C O Mitter \\\n+\t<committer@example.com>\"` will add the \"Signed-off\" trailer\n+\tand the \"Helped-by\" trailer in the commit message.)\n+\n -n::\n --no-verify::\n \tThis option bypasses the pre-commit and commit-msg hooks.\ndiff --git a/builtin/commit.c b/builtin/commit.c\nindex 739110c5a7f6..24fccb561398 100644\n--- a/builtin/commit.c\n+++ b/builtin/commit.c\n@@ -113,6 +113,9 @@ static int config_commit_verbose = -1; /* unspecified */\n static int no_post_rewrite, allow_empty_message, pathspec_file_nul;\n static char *untracked_files_arg, *force_date, *ignore_submodule_arg, *ignored_arg;\n static char *sign_commit, *pathspec_from_file;\n+struct child_process run_trailer = CHILD_PROCESS_INIT;\n+struct strvec trailer_args = STRVEC_INIT;\n+static const char *trailer;\n \n /*\n  * The default commit message cleanup mode will remove the lines\n@@ -131,6 +134,16 @@ 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+\tif (unset) {\n+\t\tstrvec_clear(&trailer_args);\n+\t\treturn -1;\n+\t}\n+\tstrvec_pushl(&trailer_args, \"--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@@ -958,6 +971,16 @@ static int prepare_to_commit(const char *index_file, const char *prefix,\n \n \tfclose(s->fp);\n \n+\tif (trailer_args.nr) {\n+\t\tstrvec_pushl(&run_trailer.args, \"interpret-trailers\",\n+\t\t\t     \"--in-place\", \"--where=end\", 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\t\tstrvec_clear(&run_trailer.args);\n+\t\tstrvec_clear(&trailer_args);\n+\t}\n+\n \t/*\n \t * Reject an attempt to record a non-merge empty commit without\n \t * explicit --allow-empty. In the cherry-pick case, it may be\n@@ -1507,6 +1530,7 @@ int cmd_commit(int argc, const char **argv, const char *prefix)\n \t\tOPT_STRING(0, \"fixup\", &fixup_message, N_(\"commit\"), N_(\"use autosquash formatted message to fixup 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(0, \"trailer\", &trailer, N_(\"trailer\"), N_(\"trailer(s) to add\"), opt_pass_trailer),\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/t/t7502-commit-porcelain.sh b/t/t7502-commit-porcelain.sh\nindex 6396897cc818..0acf23799931 100755\n--- a/t/t7502-commit-porcelain.sh\n+++ b/t/t7502-commit-porcelain.sh\n@@ -154,6 +154,26 @@ test_expect_success 'sign off' '\n \n '\n \n+test_expect_success 'trailer' '\n+\t>file1 &&\n+\tgit add file1 &&\n+\tgit commit -s --trailer \"Signed-off-by:C O Mitter1 <committer1@example.com>\" \\\n+\t\t--trailer \"Helped-by:C O Mitter2 <committer2@example.com>\"  \\\n+\t\t--trailer \"Reported-by:C O Mitter3 <committer3@example.com>\" \\\n+\t\t--trailer \"Mentored-by:C O Mitter4 <committer4@example.com>\" \\\n+\t\t-m \"hello\" &&\n+\tgit cat-file commit HEAD >commit.msg &&\n+\tsed -e \"1,7d\" commit.msg >actual &&\n+\tcat >expected <<-\\EOF &&\n+\tSigned-off-by: C O Mitter <committer@example.com>\n+\tSigned-off-by: C O Mitter1 <committer1@example.com>\n+\tHelped-by: C O Mitter2 <committer2@example.com>\n+\tReported-by: C O Mitter3 <committer3@example.com>\n+\tMentored-by: C O Mitter4 <committer4@example.com>\n+\tEOF\n+\ttest_cmp expected actual\n+'\n+\n test_expect_success 'multiple -m' '\n \n \t>negative &&\n\nbase-commit: 13d7ab6b5d7929825b626f050b62a11241ea4945\n-- \ngitgitgadget\n"},{"id":"419119","messageId":"xmqqczw12fqg.fsf@gitster.g","threadId":"55288","inReplyTo":"pull.901.v4.git.1615737505834.gitgitgadget@gmail.com","subject":"Re: [PATCH v4] [GSOC] commit: add --trailer option","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2021-03-15T04:38:47Z","receivedAt":"2021-03-15T04:39:42Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"\"ZheNing Hu via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n\nOne thing I forgot to mention.\n\n> +static int opt_pass_trailer(const struct option *opt, const char *arg, int unset)\n> +{\n> +\tif (unset) {\n> +\t\tstrvec_clear(&run_trailer.args);\n> +\t\treturn -1;\n\nWhat input is this part of the code designed to handle, and what\noutcome does it want to show to the user?\n\nDoes the test added in this patch cover this codepath, where unset\nis true?\n\nI think this codepath is reached when the command line says\n\"--no-trailer\", and I am guessing that by clearing the code wants to\nsay \"ignore the accumulated trailers and start accumulating afresh\",\ni.e.\n\n    git commit --trailer='T1: V1' --trailer='T2: V2' \\\n\t\t--no-trailer \\\n\t\t--trailer='T3: V3'\n\nwould want to add only the \"T3: V3\" trailer; which is a sensible\ndesign, but I do not think returning -1 would be compatible with\nthat design.\n\nIf on the other hand the code wants to say \"--no-trailer is a\ncommand line error\", then using PARSE_OPT_NONEG to let the\nparse-options API issue an error message and die would be more\nappropriate.  That is an often used pattern for an option that can\nappear on the command line only once without accumulating, which may\nbe less appropriate for the \"--trailer\", though.\n\n> +\t}\n> +\tstrvec_pushl(&run_trailer.args, \"--trailer\", arg, NULL);\n> +\treturn 0;\n> +}\n\n"},{"id":"419120","messageId":"xmqq8s6p2fkn.fsf@gitster.g","threadId":"55288","inReplyTo":"CAOLTT8RNKFC_NzZXGvE4Zz+NmVHCUAn-jZFu-0Nm=7JAS0jBjA@mail.gmail.com","subject":"Re: [PATCH v4] [GSOC] commit: add --trailer option","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2021-03-15T04:42:16Z","receivedAt":"2021-03-15T04:42:56Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"ZheNing Hu <adlternative@gmail.com> writes:\n\n> Junio C Hamano <gitster@pobox.com> 于2021年3月15日周一 上午7:52写道：\n>> IOW this part would become ...\n>>\n>>         if (trailer_args.nr) {\n>>                 strvec_pushl(&run_trailer.args, \"interpret-trailers\",\n>>                              \"--in-place\", ...);\n>>                 strvec_pushv(&run_trailer.args, trailer_args.v);\n>>                 run_trailer.git_cmd = 1;\n>>                 run_command(&run_trailer);\n>>         }\n>>\n>> > +     } else\n>> > +             strvec_clear(&run_trailer.args);\n>>\n>> ... and there is no need to have \"else\" that won't need to do\n>> anything.\n>\n> Yes, but we also should clear \"trailer_args\" in \"else\" here, and check the\n> return value of the \"run_command()\" for clear \"run_trailer.args\".\n\nNo.  If you introduce the separate strvec, the \"else\" clause runs\nonly when trailer_args haven't got anything, so there is nothing to\nclear.\n\n"},{"id":"419121","messageId":"CAOLTT8T9X8d-cY1sCmiFAKRo5t4pbxoLjjt8+Nm1htaKV6D6DA@mail.gmail.com","threadId":"55288","inReplyTo":"xmqqczw12fqg.fsf@gitster.g","subject":"Re: [PATCH v4] [GSOC] commit: add --trailer option","fromName":"ZheNing Hu","fromEmail":"adlternative@gmail.com","sentAt":"2021-03-15T05:11:07Z","receivedAt":"2021-03-15T05:25:49Z","isPatch":true,"sender":{"key":"adlternative@gmail.com","avatar":"https://avatars.githubusercontent.com/u/58138461?v=4"},"body":"Junio C Hamano <gitster@pobox.com> 于2021年3月15日周一 下午12:38写道：\n>\n> \"ZheNing Hu via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n>\n> One thing I forgot to mention.\n>\n> > +static int opt_pass_trailer(const struct option *opt, const char *arg, int unset)\n> > +{\n> > +     if (unset) {\n> > +             strvec_clear(&run_trailer.args);\n> > +             return -1;\n>\n> What input is this part of the code designed to handle, and what\n> outcome does it want to show to the user?\n>\n> Does the test added in this patch cover this codepath, where unset\n> is true?\n>\n> I think this codepath is reached when the command line says\n> \"--no-trailer\", and I am guessing that by clearing the code wants to\n> say \"ignore the accumulated trailers and start accumulating afresh\",\n> i.e.\n>\n>     git commit --trailer='T1: V1' --trailer='T2: V2' \\\n>                 --no-trailer \\\n>                 --trailer='T3: V3'\n>\n> would want to add only the \"T3: V3\" trailer; which is a sensible\n> design, but I do not think returning -1 would be compatible with\n> that design.\n>\n> If on the other hand the code wants to say \"--no-trailer is a\n> command line error\", then using PARSE_OPT_NONEG to let the\n> parse-options API issue an error message and die would be more\n> appropriate.  That is an often used pattern for an option that can\n> appear on the command line only once without accumulating, which may\n> be less appropriate for the \"--trailer\", though.\n>\n\nAs you said, what I want to express is \"--no-trailer is acommand line error\".\n`--no-trailer` may don't have any big benefits for users.\nI will follow you suggections.\n> > +     }\n> > +     strvec_pushl(&run_trailer.args, \"--trailer\", arg, NULL);\n> > +     return 0;\n> > +}\n>\n"},{"id":"419122","messageId":"CAOLTT8TFYZ1fTFv1LzbQgNs_hnV3jmYCEU5oL_GRUQRR=Q7UUg@mail.gmail.com","threadId":"55288","inReplyTo":"xmqq8s6p2fkn.fsf@gitster.g","subject":"Re: [PATCH v4] [GSOC] commit: add --trailer option","fromName":"ZheNing Hu","fromEmail":"adlternative@gmail.com","sentAt":"2021-03-15T05:14:32Z","receivedAt":"2021-03-15T05:25:49Z","isPatch":true,"sender":{"key":"adlternative@gmail.com","avatar":"https://avatars.githubusercontent.com/u/58138461?v=4"},"body":"Junio C Hamano <gitster@pobox.com> 于2021年3月15日周一 下午12:42写道：\n>\n> ZheNing Hu <adlternative@gmail.com> writes:\n>\n> > Junio C Hamano <gitster@pobox.com> 于2021年3月15日周一 上午7:52写道：\n> >> IOW this part would become ...\n> >>\n> >>         if (trailer_args.nr) {\n> >>                 strvec_pushl(&run_trailer.args, \"interpret-trailers\",\n> >>                              \"--in-place\", ...);\n> >>                 strvec_pushv(&run_trailer.args, trailer_args.v);\n> >>                 run_trailer.git_cmd = 1;\n> >>                 run_command(&run_trailer);\n> >>         }\n> >>\n> >> > +     } else\n> >> > +             strvec_clear(&run_trailer.args);\n> >>\n> >> ... and there is no need to have \"else\" that won't need to do\n> >> anything.\n> >\n> > Yes, but we also should clear \"trailer_args\" in \"else\" here, and check the\n> > return value of the \"run_command()\" for clear \"run_trailer.args\".\n>\n> No.  If you introduce the separate strvec, the \"else\" clause runs\n> only when trailer_args haven't got anything, so there is nothing to\n> clear.\n>\nI admit I was wrong before. But this may be the right thing to do.\n+       if (trailer_args.nr) {\n+               strvec_pushl(&run_trailer.args, \"interpret-trailers\",\n+                            \"--in-place\", \"--where=end\",\ngit_path_commit_editmsg(), NULL);\n+               strvec_pushv(&run_trailer.args, trailer_args.v);\n+               run_trailer.git_cmd = 1;\n+               if (run_command(&run_trailer))\n+                       strvec_clear(&run_trailer.args);\n+               strvec_clear(&trailer_args);\n+       }\n"},{"id":"419123","messageId":"CAP8UFD3mpu6eTqifnwUV4uHoAASaQnxX21CacOn4dGSp+juVKw@mail.gmail.com","threadId":"55288","inReplyTo":"pull.901.v5.git.1615778692784.gitgitgadget@gmail.com","subject":"Re: [PATCH v5] [GSOC] commit: add --trailer option","fromName":"Christian Couder","fromEmail":"christian.couder@gmail.com","sentAt":"2021-03-15T05:33:17Z","receivedAt":"2021-03-15T05:34:27Z","isPatch":true,"sender":{"key":"christian.couder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/208954?v=4"},"body":"On Mon, Mar 15, 2021 at 4:24 AM ZheNing Hu via GitGitGadget\n<gitgitgadget@gmail.com> wrote:\n\n> diff --git a/builtin/commit.c b/builtin/commit.c\n> index 739110c5a7f6..24fccb561398 100644\n> --- a/builtin/commit.c\n> +++ b/builtin/commit.c\n> @@ -113,6 +113,9 @@ static int config_commit_verbose = -1; /* unspecified */\n>  static int no_post_rewrite, allow_empty_message, pathspec_file_nul;\n>  static char *untracked_files_arg, *force_date, *ignore_submodule_arg, *ignored_arg;\n>  static char *sign_commit, *pathspec_from_file;\n> +struct child_process run_trailer = CHILD_PROCESS_INIT;\n\nIt seems to me that `run_trailer` is used only in the `if\n(trailer_args.nr) {...}` block, so it could be declared there instead\nof as a global variable.\n\n> +struct strvec trailer_args = STRVEC_INIT;\n> +static const char *trailer;\n\nI am not sure that this `trailer`variable is really needed. It seems\nto be used only as the third argument to OPT_CALLBACK(), but there are\nother places in the code base where we pass NULL as the third\nargument.\n"},{"id":"419124","messageId":"CAP8UFD2tz-apz+C8+Q+P4-8zZOq7zDsupkX31EHyT0msH3eX0A@mail.gmail.com","threadId":"55288","inReplyTo":"CAP8UFD3mpu6eTqifnwUV4uHoAASaQnxX21CacOn4dGSp+juVKw@mail.gmail.com","subject":"Re: [PATCH v5] [GSOC] commit: add --trailer option","fromName":"Christian Couder","fromEmail":"christian.couder@gmail.com","sentAt":"2021-03-15T05:41:54Z","receivedAt":"2021-03-15T05:42:58Z","isPatch":true,"sender":{"key":"christian.couder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/208954?v=4"},"body":"On Mon, Mar 15, 2021 at 6:33 AM Christian Couder\n<christian.couder@gmail.com> wrote:\n>\n> On Mon, Mar 15, 2021 at 4:24 AM ZheNing Hu via GitGitGadget\n> <gitgitgadget@gmail.com> wrote:\n>\n> > diff --git a/builtin/commit.c b/builtin/commit.c\n> > index 739110c5a7f6..24fccb561398 100644\n> > --- a/builtin/commit.c\n> > +++ b/builtin/commit.c\n> > @@ -113,6 +113,9 @@ static int config_commit_verbose = -1; /* unspecified */\n> >  static int no_post_rewrite, allow_empty_message, pathspec_file_nul;\n> >  static char *untracked_files_arg, *force_date, *ignore_submodule_arg, *ignored_arg;\n> >  static char *sign_commit, *pathspec_from_file;\n> > +struct child_process run_trailer = CHILD_PROCESS_INIT;\n>\n> It seems to me that `run_trailer` is used only in the `if\n> (trailer_args.nr) {...}` block, so it could be declared there instead\n> of as a global variable.\n>\n> > +struct strvec trailer_args = STRVEC_INIT;\n\nAlso you might want to add \"static\" in front of \"struct strvec\" in the\nabove line.\n"},{"id":"419125","messageId":"CAOLTT8R2qmmp=-2fRdVQJN6LkrhCcM_SfL46MALPwtycJ_LH9A@mail.gmail.com","threadId":"55288","inReplyTo":"CAP8UFD3mpu6eTqifnwUV4uHoAASaQnxX21CacOn4dGSp+juVKw@mail.gmail.com","subject":"Re: [PATCH v5] [GSOC] commit: add --trailer option","fromName":"ZheNing Hu","fromEmail":"adlternative@gmail.com","sentAt":"2021-03-15T05:46:28Z","receivedAt":"2021-03-15T05:47:19Z","isPatch":true,"sender":{"key":"adlternative@gmail.com","avatar":"https://avatars.githubusercontent.com/u/58138461?v=4"},"body":"Christian Couder <christian.couder@gmail.com> 于2021年3月15日周一 下午1:33写道：\n>\n> On Mon, Mar 15, 2021 at 4:24 AM ZheNing Hu via GitGitGadget\n> <gitgitgadget@gmail.com> wrote:\n>\n> > diff --git a/builtin/commit.c b/builtin/commit.c\n> > index 739110c5a7f6..24fccb561398 100644\n> > --- a/builtin/commit.c\n> > +++ b/builtin/commit.c\n> > @@ -113,6 +113,9 @@ static int config_commit_verbose = -1; /* unspecified */\n> >  static int no_post_rewrite, allow_empty_message, pathspec_file_nul;\n> >  static char *untracked_files_arg, *force_date, *ignore_submodule_arg, *ignored_arg;\n> >  static char *sign_commit, *pathspec_from_file;\n> > +struct child_process run_trailer = CHILD_PROCESS_INIT;\n>\n> It seems to me that `run_trailer` is used only in the `if\n> (trailer_args.nr) {...}` block, so it could be declared there instead\n> of as a global variable.\n>\n> > +struct strvec trailer_args = STRVEC_INIT;\n> > +static const char *trailer;\n>\n> I am not sure that this `trailer`variable is really needed. It seems\n> to be used only as the third argument to OPT_CALLBACK(), but there are\n> other places in the code base where we pass NULL as the third\n> argument.\n\nGood advice.\n"},{"id":"419127","messageId":"pull.901.v6.git.1615790151073.gitgitgadget@gmail.com","threadId":"55288","inReplyTo":"pull.901.v5.git.1615778692784.gitgitgadget@gmail.com","subject":"[PATCH v6] [GSOC] commit: add --trailer option","fromName":"ZheNing Hu via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2021-03-15T06:35:50Z","receivedAt":"2021-03-15T06:37:00Z","isPatch":true,"sender":{"key":"adlternative@gmail.com","avatar":"https://avatars.githubusercontent.com/u/58138461?v=4"},"body":"From: ZheNing Hu <adlternative@gmail.com>\n\nHistorically, Git has supported the 'Signed-off-by' commit trailer\nusing the '--signoff' and the '-s' option from the command line.\nBut users may need to provide other trailer information from the\ncommand line such as \"Helped-by\", \"Reported-by\", \"Mentored-by\",\n\nNow implement a new `--trailer <token>[(=|:)<value>]` option to pass\nother trailers to `interpret-trailers` and insert them into commit\nmessages.\n\nSigned-off-by: ZheNing Hu <adlternative@gmail.com>\n---\n    [GSOC] commit: add --trailer option\n    \n    Now maintainers or developers can also use commit\n    --trailer=\"Signed-off-by:commiter<email>\" from the command line to\n    provide trailers to commit messages. This solution may be more\n    generalized than v1.\n\nPublished-As: https://github.com/gitgitgadget/git/releases/tag/pr-901%2Fadlternative%2Fcommit-with-multiple-signatures-v6\nFetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-901/adlternative/commit-with-multiple-signatures-v6\nPull-Request: https://github.com/gitgitgadget/git/pull/901\n\nRange-diff vs v5:\n\n 1:  ca91accb2852 ! 1:  c99ce75da792 [GSOC] commit: add --trailer option\n     @@ builtin/commit.c: static int config_commit_verbose = -1; /* unspecified */\n       static int no_post_rewrite, allow_empty_message, pathspec_file_nul;\n       static char *untracked_files_arg, *force_date, *ignore_submodule_arg, *ignored_arg;\n       static char *sign_commit, *pathspec_from_file;\n     -+struct child_process run_trailer = CHILD_PROCESS_INIT;\n      +struct strvec trailer_args = STRVEC_INIT;\n     -+static const char *trailer;\n       \n       /*\n        * The default commit message cleanup mode will remove the lines\n     @@ builtin/commit.c: static struct strbuf message = STRBUF_INIT;\n       \n      +static int opt_pass_trailer(const struct option *opt, const char *arg, int unset)\n      +{\n     -+\tif (unset) {\n     -+\t\tstrvec_clear(&trailer_args);\n     -+\t\treturn -1;\n     -+\t}\n     ++\tBUG_ON_OPT_NEG(unset);\n     ++\n      +\tstrvec_pushl(&trailer_args, \"--trailer\", arg, NULL);\n      +\treturn 0;\n      +}\n     @@ builtin/commit.c: static int prepare_to_commit(const char *index_file, const cha\n       \tfclose(s->fp);\n       \n      +\tif (trailer_args.nr) {\n     ++\t\tstatic struct child_process run_trailer = CHILD_PROCESS_INIT;\n     ++\n      +\t\tstrvec_pushl(&run_trailer.args, \"interpret-trailers\",\n      +\t\t\t     \"--in-place\", \"--where=end\", git_path_commit_editmsg(), NULL);\n      +\t\tstrvec_pushv(&run_trailer.args, trailer_args.v);\n     @@ builtin/commit.c: int cmd_commit(int argc, const char **argv, const char *prefix\n       \t\tOPT_STRING(0, \"fixup\", &fixup_message, N_(\"commit\"), N_(\"use autosquash formatted message to fixup 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(0, \"trailer\", &trailer, N_(\"trailer\"), N_(\"trailer(s) to add\"), opt_pass_trailer),\n     ++\t\tOPT_CALLBACK_F(0, \"trailer\", NULL, N_(\"trailer\"), N_(\"trailer(s) to add\"), PARSE_OPT_NONEG, opt_pass_trailer),\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\n Documentation/git-commit.txt |  9 ++++++++-\n builtin/commit.c             | 22 ++++++++++++++++++++++\n t/t7502-commit-porcelain.sh  | 20 ++++++++++++++++++++\n 3 files changed, 50 insertions(+), 1 deletion(-)\n\ndiff --git a/Documentation/git-commit.txt b/Documentation/git-commit.txt\nindex 17150fa7eabe..73a7507db47f 100644\n--- a/Documentation/git-commit.txt\n+++ b/Documentation/git-commit.txt\n@@ -14,7 +14,7 @@ SYNOPSIS\n \t   [--allow-empty-message] [--no-verify] [-e] [--author=<author>]\n \t   [--date=<date>] [--cleanup=<mode>] [--[no-]status]\n \t   [-i | -o] [--pathspec-from-file=<file> [--pathspec-file-nul]]\n-\t   [-S[<keyid>]] [--] [<pathspec>...]\n+\t   [-S[<keyid>]] [--] [<pathspec>...] [(--trailer <token>[(=|:)<value>])...]\n \n DESCRIPTION\n -----------\n@@ -166,6 +166,13 @@ The `-m` option is mutually exclusive with `-c`, `-C`, and `-F`.\n \n include::signoff-option.txt[]\n \n+--trailer <token>[(=|:)<value>]::\n+\tSpecify a (<token>, <value>) pair that should be applied as a\n+\ttrailer. (e.g. `git commit --trailer \"Signed-off-by:C O Mitter \\\n+\t<committer@example.com>\" --trailer \"Helped-by:C O Mitter \\\n+\t<committer@example.com>\"` will add the \"Signed-off\" trailer\n+\tand the \"Helped-by\" trailer in the commit message.)\n+\n -n::\n --no-verify::\n \tThis option bypasses the pre-commit and commit-msg hooks.\ndiff --git a/builtin/commit.c b/builtin/commit.c\nindex 739110c5a7f6..726d2f4eaf5d 100644\n--- a/builtin/commit.c\n+++ b/builtin/commit.c\n@@ -113,6 +113,7 @@ static int config_commit_verbose = -1; /* unspecified */\n static int no_post_rewrite, allow_empty_message, pathspec_file_nul;\n static char *untracked_files_arg, *force_date, *ignore_submodule_arg, *ignored_arg;\n static char *sign_commit, *pathspec_from_file;\n+struct strvec trailer_args = STRVEC_INIT;\n \n /*\n  * The default commit message cleanup mode will remove the lines\n@@ -131,6 +132,14 @@ 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(&trailer_args, \"--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@@ -958,6 +967,18 @@ static int prepare_to_commit(const char *index_file, const char *prefix,\n \n \tfclose(s->fp);\n \n+\tif (trailer_args.nr) {\n+\t\tstatic struct child_process run_trailer = CHILD_PROCESS_INIT;\n+\n+\t\tstrvec_pushl(&run_trailer.args, \"interpret-trailers\",\n+\t\t\t     \"--in-place\", \"--where=end\", 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\t\tstrvec_clear(&run_trailer.args);\n+\t\tstrvec_clear(&trailer_args);\n+\t}\n+\n \t/*\n \t * Reject an attempt to record a non-merge empty commit without\n \t * explicit --allow-empty. In the cherry-pick case, it may be\n@@ -1507,6 +1528,7 @@ int cmd_commit(int argc, const char **argv, const char *prefix)\n \t\tOPT_STRING(0, \"fixup\", &fixup_message, N_(\"commit\"), N_(\"use autosquash formatted message to fixup 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\", NULL, N_(\"trailer\"), N_(\"trailer(s) to add\"), PARSE_OPT_NONEG, opt_pass_trailer),\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/t/t7502-commit-porcelain.sh b/t/t7502-commit-porcelain.sh\nindex 6396897cc818..0acf23799931 100755\n--- a/t/t7502-commit-porcelain.sh\n+++ b/t/t7502-commit-porcelain.sh\n@@ -154,6 +154,26 @@ test_expect_success 'sign off' '\n \n '\n \n+test_expect_success 'trailer' '\n+\t>file1 &&\n+\tgit add file1 &&\n+\tgit commit -s --trailer \"Signed-off-by:C O Mitter1 <committer1@example.com>\" \\\n+\t\t--trailer \"Helped-by:C O Mitter2 <committer2@example.com>\"  \\\n+\t\t--trailer \"Reported-by:C O Mitter3 <committer3@example.com>\" \\\n+\t\t--trailer \"Mentored-by:C O Mitter4 <committer4@example.com>\" \\\n+\t\t-m \"hello\" &&\n+\tgit cat-file commit HEAD >commit.msg &&\n+\tsed -e \"1,7d\" commit.msg >actual &&\n+\tcat >expected <<-\\EOF &&\n+\tSigned-off-by: C O Mitter <committer@example.com>\n+\tSigned-off-by: C O Mitter1 <committer1@example.com>\n+\tHelped-by: C O Mitter2 <committer2@example.com>\n+\tReported-by: C O Mitter3 <committer3@example.com>\n+\tMentored-by: C O Mitter4 <committer4@example.com>\n+\tEOF\n+\ttest_cmp expected actual\n+'\n+\n test_expect_success 'multiple -m' '\n \n \t>negative &&\n\nbase-commit: 13d7ab6b5d7929825b626f050b62a11241ea4945\n-- \ngitgitgadget\n"},{"id":"419136","messageId":"CAP8UFD1W+oPwXzjq8SFVRTusJiw-3jvMTmBjLzK6PH6=GN+QwA@mail.gmail.com","threadId":"55288","inReplyTo":"pull.901.v6.git.1615790151073.gitgitgadget@gmail.com","subject":"Re: [PATCH v6] [GSOC] commit: add --trailer option","fromName":"Christian Couder","fromEmail":"christian.couder@gmail.com","sentAt":"2021-03-15T08:02:51Z","receivedAt":"2021-03-15T08:03:37Z","isPatch":true,"sender":{"key":"christian.couder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/208954?v=4"},"body":"On Mon, Mar 15, 2021 at 7:35 AM ZheNing Hu via GitGitGadget\n<gitgitgadget@gmail.com> wrote:\n>\n> From: ZheNing Hu <adlternative@gmail.com>\n>\n> Historically, Git has supported the 'Signed-off-by' commit trailer\n> using the '--signoff' and the '-s' option from the command line.\n> But users may need to provide other trailer information from the\n> command line such as \"Helped-by\", \"Reported-by\", \"Mentored-by\",\n>\n> Now implement a new `--trailer <token>[(=|:)<value>]` option to pass\n> other trailers to `interpret-trailers` and insert them into commit\n> messages.\n>\n> Signed-off-by: ZheNing Hu <adlternative@gmail.com>\n> ---\n>     [GSOC] commit: add --trailer option\n>\n>     Now maintainers or developers can also use commit\n>     --trailer=\"Signed-off-by:commiter<email>\" from the command line to\n>     provide trailers to commit messages. This solution may be more\n>     generalized than v1.\n>\n> Published-As: https://github.com/gitgitgadget/git/releases/tag/pr-901%2Fadlternative%2Fcommit-with-multiple-signatures-v6\n> Fetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-901/adlternative/commit-with-multiple-signatures-v6\n> Pull-Request: https://github.com/gitgitgadget/git/pull/901\n>\n> Range-diff vs v5:\n>\n>  1:  ca91accb2852 ! 1:  c99ce75da792 [GSOC] commit: add --trailer option\n>      @@ builtin/commit.c: static int config_commit_verbose = -1; /* unspecified */\n>        static int no_post_rewrite, allow_empty_message, pathspec_file_nul;\n>        static char *untracked_files_arg, *force_date, *ignore_submodule_arg, *ignored_arg;\n>        static char *sign_commit, *pathspec_from_file;\n>      -+struct child_process run_trailer = CHILD_PROCESS_INIT;\n>       +struct strvec trailer_args = STRVEC_INIT;\n\nI suggested added \"static\" in front of the above line, like this:\n\nstatic struct strvec trailer_args = STRVEC_INIT;\n\nYou can check with `git grep '^static struct strvec' '*.c'` and `git\ngrep '^struct strvec' '*.c'` that we use \"static\" when declaring a\n'struct strvec' globally.\n\n[...]\n\n>       + if (trailer_args.nr) {\n>      ++         static struct child_process run_trailer = CHILD_PROCESS_INIT;\n\nI didn't suggest using \"static\" here.\n\n>      ++\n>       +         strvec_pushl(&run_trailer.args, \"interpret-trailers\",\n\nThanks for working on this!\n"},{"id":"419138","messageId":"CAOLTT8SFnnyfYvNJxApujw3-3eJyPEKXYOyoiM7QqCf8CzTD3Q@mail.gmail.com","threadId":"55288","inReplyTo":"CAP8UFD1W+oPwXzjq8SFVRTusJiw-3jvMTmBjLzK6PH6=GN+QwA@mail.gmail.com","subject":"Re: [PATCH v6] [GSOC] commit: add --trailer option","fromName":"ZheNing Hu","fromEmail":"adlternative@gmail.com","sentAt":"2021-03-15T08:21:30Z","receivedAt":"2021-03-15T08:22:16Z","isPatch":true,"sender":{"key":"adlternative@gmail.com","avatar":"https://avatars.githubusercontent.com/u/58138461?v=4"},"body":"Christian Couder <christian.couder@gmail.com> 于2021年3月15日周一 下午4:03写道：\n>\n> On Mon, Mar 15, 2021 at 7:35 AM ZheNing Hu via GitGitGadget\n> <gitgitgadget@gmail.com> wrote:\n> >\n> > From: ZheNing Hu <adlternative@gmail.com>\n> >\n> > Historically, Git has supported the 'Signed-off-by' commit trailer\n> > using the '--signoff' and the '-s' option from the command line.\n> > But users may need to provide other trailer information from the\n> > command line such as \"Helped-by\", \"Reported-by\", \"Mentored-by\",\n> >\n> > Now implement a new `--trailer <token>[(=|:)<value>]` option to pass\n> > other trailers to `interpret-trailers` and insert them into commit\n> > messages.\n> >\n> > Signed-off-by: ZheNing Hu <adlternative@gmail.com>\n> > ---\n> >     [GSOC] commit: add --trailer option\n> >\n> >     Now maintainers or developers can also use commit\n> >     --trailer=\"Signed-off-by:commiter<email>\" from the command line to\n> >     provide trailers to commit messages. This solution may be more\n> >     generalized than v1.\n> >\n> > Published-As: https://github.com/gitgitgadget/git/releases/tag/pr-901%2Fadlternative%2Fcommit-with-multiple-signatures-v6\n> > Fetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-901/adlternative/commit-with-multiple-signatures-v6\n> > Pull-Request: https://github.com/gitgitgadget/git/pull/901\n> >\n> > Range-diff vs v5:\n> >\n> >  1:  ca91accb2852 ! 1:  c99ce75da792 [GSOC] commit: add --trailer option\n> >      @@ builtin/commit.c: static int config_commit_verbose = -1; /* unspecified */\n> >        static int no_post_rewrite, allow_empty_message, pathspec_file_nul;\n> >        static char *untracked_files_arg, *force_date, *ignore_submodule_arg, *ignored_arg;\n> >        static char *sign_commit, *pathspec_from_file;\n> >      -+struct child_process run_trailer = CHILD_PROCESS_INIT;\n> >       +struct strvec trailer_args = STRVEC_INIT;\n>\n> I suggested added \"static\" in front of the above line, like this:\n>\n> static struct strvec trailer_args = STRVEC_INIT;\n>\n> You can check with `git grep '^static struct strvec' '*.c'` and `git\n> grep '^struct strvec' '*.c'` that we use \"static\" when declaring a\n> 'struct strvec' globally.\n>\n> [...]\n>\n> >       + if (trailer_args.nr) {\n> >      ++         static struct child_process run_trailer = CHILD_PROCESS_INIT;\n>\n> I didn't suggest using \"static\" here.\n>\n> >      ++\n> >       +         strvec_pushl(&run_trailer.args, \"interpret-trailers\",\n>\n> Thanks for working on this!\n\nWell, I was too eager to submit it.\nThere are many rules for git projects to explore.\nThanks.\n"},{"id":"419139","messageId":"pull.901.v7.git.1615799304883.gitgitgadget@gmail.com","threadId":"55288","inReplyTo":"pull.901.v6.git.1615790151073.gitgitgadget@gmail.com","subject":"[PATCH v7] [GSOC] commit: add --trailer option","fromName":"ZheNing Hu via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2021-03-15T09:08:24Z","receivedAt":"2021-03-15T09:09:00Z","isPatch":true,"sender":{"key":"adlternative@gmail.com","avatar":"https://avatars.githubusercontent.com/u/58138461?v=4"},"body":"From: ZheNing Hu <adlternative@gmail.com>\n\nHistorically, Git has supported the 'Signed-off-by' commit trailer\nusing the '--signoff' and the '-s' option from the command line.\nBut users may need to provide other trailer information from the\ncommand line such as \"Helped-by\", \"Reported-by\", \"Mentored-by\",\n\nNow implement a new `--trailer <token>[(=|:)<value>]` option to pass\nother trailers to `interpret-trailers` and insert them into commit\nmessages.\n\nSigned-off-by: ZheNing Hu <adlternative@gmail.com>\n---\n    [GSOC] commit: add --trailer option\n    \n    Now maintainers or developers can also use commit\n    --trailer=\"Signed-off-by:commiter<email>\" from the command line to\n    provide trailers to commit messages. This solution may be more\n    generalized than v1.\n\nPublished-As: https://github.com/gitgitgadget/git/releases/tag/pr-901%2Fadlternative%2Fcommit-with-multiple-signatures-v7\nFetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-901/adlternative/commit-with-multiple-signatures-v7\nPull-Request: https://github.com/gitgitgadget/git/pull/901\n\nRange-diff vs v6:\n\n 1:  c99ce75da792 ! 1:  3ce8e8cac24a [GSOC] commit: add --trailer option\n     @@ builtin/commit.c: static int config_commit_verbose = -1; /* unspecified */\n       static int no_post_rewrite, allow_empty_message, pathspec_file_nul;\n       static char *untracked_files_arg, *force_date, *ignore_submodule_arg, *ignored_arg;\n       static char *sign_commit, *pathspec_from_file;\n     -+struct strvec trailer_args = STRVEC_INIT;\n     ++static struct strvec trailer_args = STRVEC_INIT;\n       \n       /*\n        * The default commit message cleanup mode will remove the lines\n     @@ builtin/commit.c: static int prepare_to_commit(const char *index_file, const cha\n       \tfclose(s->fp);\n       \n      +\tif (trailer_args.nr) {\n     -+\t\tstatic struct child_process run_trailer = CHILD_PROCESS_INIT;\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\", \"--where=end\", git_path_commit_editmsg(), NULL);\n\n\n Documentation/git-commit.txt |  9 ++++++++-\n builtin/commit.c             | 22 ++++++++++++++++++++++\n t/t7502-commit-porcelain.sh  | 20 ++++++++++++++++++++\n 3 files changed, 50 insertions(+), 1 deletion(-)\n\ndiff --git a/Documentation/git-commit.txt b/Documentation/git-commit.txt\nindex 17150fa7eabe..73a7507db47f 100644\n--- a/Documentation/git-commit.txt\n+++ b/Documentation/git-commit.txt\n@@ -14,7 +14,7 @@ SYNOPSIS\n \t   [--allow-empty-message] [--no-verify] [-e] [--author=<author>]\n \t   [--date=<date>] [--cleanup=<mode>] [--[no-]status]\n \t   [-i | -o] [--pathspec-from-file=<file> [--pathspec-file-nul]]\n-\t   [-S[<keyid>]] [--] [<pathspec>...]\n+\t   [-S[<keyid>]] [--] [<pathspec>...] [(--trailer <token>[(=|:)<value>])...]\n \n DESCRIPTION\n -----------\n@@ -166,6 +166,13 @@ The `-m` option is mutually exclusive with `-c`, `-C`, and `-F`.\n \n include::signoff-option.txt[]\n \n+--trailer <token>[(=|:)<value>]::\n+\tSpecify a (<token>, <value>) pair that should be applied as a\n+\ttrailer. (e.g. `git commit --trailer \"Signed-off-by:C O Mitter \\\n+\t<committer@example.com>\" --trailer \"Helped-by:C O Mitter \\\n+\t<committer@example.com>\"` will add the \"Signed-off\" trailer\n+\tand the \"Helped-by\" trailer in the commit message.)\n+\n -n::\n --no-verify::\n \tThis option bypasses the pre-commit and commit-msg hooks.\ndiff --git a/builtin/commit.c b/builtin/commit.c\nindex 739110c5a7f6..0ddd1891f71c 100644\n--- a/builtin/commit.c\n+++ b/builtin/commit.c\n@@ -113,6 +113,7 @@ static int config_commit_verbose = -1; /* unspecified */\n static int no_post_rewrite, allow_empty_message, pathspec_file_nul;\n static char *untracked_files_arg, *force_date, *ignore_submodule_arg, *ignored_arg;\n static char *sign_commit, *pathspec_from_file;\n+static struct strvec trailer_args = STRVEC_INIT;\n \n /*\n  * The default commit message cleanup mode will remove the lines\n@@ -131,6 +132,14 @@ 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(&trailer_args, \"--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@@ -958,6 +967,18 @@ static int prepare_to_commit(const char *index_file, const char *prefix,\n \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\", \"--where=end\", 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\t\tstrvec_clear(&run_trailer.args);\n+\t\tstrvec_clear(&trailer_args);\n+\t}\n+\n \t/*\n \t * Reject an attempt to record a non-merge empty commit without\n \t * explicit --allow-empty. In the cherry-pick case, it may be\n@@ -1507,6 +1528,7 @@ int cmd_commit(int argc, const char **argv, const char *prefix)\n \t\tOPT_STRING(0, \"fixup\", &fixup_message, N_(\"commit\"), N_(\"use autosquash formatted message to fixup 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\", NULL, N_(\"trailer\"), N_(\"trailer(s) to add\"), PARSE_OPT_NONEG, opt_pass_trailer),\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/t/t7502-commit-porcelain.sh b/t/t7502-commit-porcelain.sh\nindex 6396897cc818..0acf23799931 100755\n--- a/t/t7502-commit-porcelain.sh\n+++ b/t/t7502-commit-porcelain.sh\n@@ -154,6 +154,26 @@ test_expect_success 'sign off' '\n \n '\n \n+test_expect_success 'trailer' '\n+\t>file1 &&\n+\tgit add file1 &&\n+\tgit commit -s --trailer \"Signed-off-by:C O Mitter1 <committer1@example.com>\" \\\n+\t\t--trailer \"Helped-by:C O Mitter2 <committer2@example.com>\"  \\\n+\t\t--trailer \"Reported-by:C O Mitter3 <committer3@example.com>\" \\\n+\t\t--trailer \"Mentored-by:C O Mitter4 <committer4@example.com>\" \\\n+\t\t-m \"hello\" &&\n+\tgit cat-file commit HEAD >commit.msg &&\n+\tsed -e \"1,7d\" commit.msg >actual &&\n+\tcat >expected <<-\\EOF &&\n+\tSigned-off-by: C O Mitter <committer@example.com>\n+\tSigned-off-by: C O Mitter1 <committer1@example.com>\n+\tHelped-by: C O Mitter2 <committer2@example.com>\n+\tReported-by: C O Mitter3 <committer3@example.com>\n+\tMentored-by: C O Mitter4 <committer4@example.com>\n+\tEOF\n+\ttest_cmp expected actual\n+'\n+\n test_expect_success 'multiple -m' '\n \n \t>negative &&\n\nbase-commit: 13d7ab6b5d7929825b626f050b62a11241ea4945\n-- \ngitgitgadget\n"},{"id":"419141","messageId":"CAP8UFD1+Z=hV_6dLbg7fh4EntBdWdheO6gvbQjKtZFRgOAsrzw@mail.gmail.com","threadId":"55288","inReplyTo":"pull.901.v7.git.1615799304883.gitgitgadget@gmail.com","subject":"Re: [PATCH v7] [GSOC] commit: add --trailer option","fromName":"Christian Couder","fromEmail":"christian.couder@gmail.com","sentAt":"2021-03-15T10:00:30Z","receivedAt":"2021-03-15T10:01:41Z","isPatch":true,"sender":{"key":"christian.couder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/208954?v=4"},"body":"On Mon, Mar 15, 2021 at 10:08 AM ZheNing Hu via GitGitGadget\n<gitgitgadget@gmail.com> wrote:\n\n> @@ -166,6 +166,13 @@ The `-m` option is mutually exclusive with `-c`, `-C`, and `-F`.\n>\n>  include::signoff-option.txt[]\n>\n> +--trailer <token>[(=|:)<value>]::\n> +       Specify a (<token>, <value>) pair that should be applied as a\n> +       trailer. (e.g. `git commit --trailer \"Signed-off-by:C O Mitter \\\n> +       <committer@example.com>\" --trailer \"Helped-by:C O Mitter \\\n> +       <committer@example.com>\"` will add the \"Signed-off\" trailer\n\ns/\"Signed-off\"/\"Signed-off-by\"/\n\n> +       and the \"Helped-by\" trailer in the commit message.)\n\nIt might be useful to point users to git interpret-trailers'\ndocumentation, for example by adding: \"See\nlinkgit:git-interpret-trailers[1].\"\n\nOtherwise, this looks good to me!\n"},{"id":"419142","messageId":"CAP8UFD2CNAW4o8BF8NLA0pwWzNmBwZJAP7L5SGSib+LcVbSLBA@mail.gmail.com","threadId":"55288","inReplyTo":"pull.901.v7.git.1615799304883.gitgitgadget@gmail.com","subject":"Re: [PATCH v7] [GSOC] commit: add --trailer option","fromName":"Christian Couder","fromEmail":"christian.couder@gmail.com","sentAt":"2021-03-15T10:14:00Z","receivedAt":"2021-03-15T10:15:09Z","isPatch":true,"sender":{"key":"christian.couder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/208954?v=4"},"body":"On Mon, Mar 15, 2021 at 10:08 AM ZheNing Hu via GitGitGadget\n<gitgitgadget@gmail.com> wrote:\n\n>       + if (trailer_args.nr) {\n>      -+         static struct child_process run_trailer = CHILD_PROCESS_INIT;\n>      ++         struct child_process run_trailer = CHILD_PROCESS_INIT;\n>       +\n>       +         strvec_pushl(&run_trailer.args, \"interpret-trailers\",\n>       +                      \"--in-place\", \"--where=end\", git_path_commit_editmsg(), NULL);\n\nActually I don't think \"--where=end\" should be used here. \"end\" is the\ndefault for the \"trailer.where\" config variable, so by default if\nnothing has been configured, it will work as if \"--where=end\" was\npassed above.\n\nIf a user has configured \"trailer.where\" or trailer.<token>.where,\nthen this should be respected. And users should be able to override\nsuch config variable using for example:\n\ngit -c trailer.where=start commit --trailer \"Signed-off-by:C O Mitter\n<committer@example.com>\"\n"},{"id":"419145","messageId":"CAOLTT8QTxnykacdDaMjZMkEqTHPSrPz6HH-bSgbECo5tUgf5Gg@mail.gmail.com","threadId":"55288","inReplyTo":"CAP8UFD2CNAW4o8BF8NLA0pwWzNmBwZJAP7L5SGSib+LcVbSLBA@mail.gmail.com","subject":"Re: [PATCH v7] [GSOC] commit: add --trailer option","fromName":"ZheNing Hu","fromEmail":"adlternative@gmail.com","sentAt":"2021-03-15T11:32:34Z","receivedAt":"2021-03-15T11:33:36Z","isPatch":true,"sender":{"key":"adlternative@gmail.com","avatar":"https://avatars.githubusercontent.com/u/58138461?v=4"},"body":"Christian Couder <christian.couder@gmail.com> 于2021年3月15日周一 下午6:14写道：\n>\n> On Mon, Mar 15, 2021 at 10:08 AM ZheNing Hu via GitGitGadget\n> <gitgitgadget@gmail.com> wrote:\n>\n> >       + if (trailer_args.nr) {\n> >      -+         static struct child_process run_trailer = CHILD_PROCESS_INIT;\n> >      ++         struct child_process run_trailer = CHILD_PROCESS_INIT;\n> >       +\n> >       +         strvec_pushl(&run_trailer.args, \"interpret-trailers\",\n> >       +                      \"--in-place\", \"--where=end\", git_path_commit_editmsg(), NULL);\n>\n> Actually I don't think \"--where=end\" should be used here. \"end\" is the\n> default for the \"trailer.where\" config variable, so by default if\n> nothing has been configured, it will work as if \"--where=end\" was\n> passed above.\n>\n> If a user has configured \"trailer.where\" or trailer.<token>.where,\n> then this should be respected. And users should be able to override\n> such config variable using for example:\n>\n> git -c trailer.where=start commit --trailer \"Signed-off-by:C O Mitter\n> <committer@example.com>\"\n\nThanks for reminding, generally speaking, we will put the trailer at the\nend of the commit messages.Take trailers in start, this should be\nsomething I haven't considered.\n\nI notice another question:\nif we commit this again with same trailer (even same email or same commiter)\n`--trailer` will not work again, because in `interpret_trailers`,\n\"if-exists\" default\nset to \"addIfDifferentNeighbor\", I addvice enforce use \"if-exists=\"add\".\n\nThanks.\n"},{"id":"419148","messageId":"pull.901.v8.git.1615813658.gitgitgadget@gmail.com","threadId":"55288","inReplyTo":"pull.901.v7.git.1615799304883.gitgitgadget@gmail.com","subject":"[PATCH v8 0/2] [GSOC] commit: add --trailer option","fromName":"ZheNing Hu via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2021-03-15T13:07:35Z","receivedAt":"2021-03-15T13:08:24Z","isPatch":true,"sender":{"key":"adlternative@gmail.com","avatar":"https://avatars.githubusercontent.com/u/58138461?v=4"},"body":"Now maintainers or developers can also use commit\n--trailer=\"Signed-off-by:commiter<email>\" from the command line to provide\ntrailers to commit messages. This solution may be more generalized than v1.\n\nZheNing Hu (2):\n  [GSOC] commit: add --trailer option\n  interpret_trailers: for three options parse add warning\n\n Documentation/git-commit.txt |  9 ++++++++-\n builtin/commit.c             | 23 +++++++++++++++++++++++\n builtin/interpret-trailers.c | 25 ++++++++++++++++++++++---\n t/t7502-commit-porcelain.sh  | 20 ++++++++++++++++++++\n 4 files changed, 73 insertions(+), 4 deletions(-)\n\n\nbase-commit: 13d7ab6b5d7929825b626f050b62a11241ea4945\nPublished-As: https://github.com/gitgitgadget/git/releases/tag/pr-901%2Fadlternative%2Fcommit-with-multiple-signatures-v8\nFetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-901/adlternative/commit-with-multiple-signatures-v8\nPull-Request: https://github.com/gitgitgadget/git/pull/901\n\nRange-diff vs v7:\n\n 1:  3ce8e8cac24a ! 1:  f81b6e66a6ba [GSOC] commit: add --trailer option\n     @@ Documentation/git-commit.txt: The `-m` option is mutually exclusive with `-c`, `\n      +\tSpecify a (<token>, <value>) pair that should be applied as a\n      +\ttrailer. (e.g. `git commit --trailer \"Signed-off-by:C O Mitter \\\n      +\t<committer@example.com>\" --trailer \"Helped-by:C O Mitter \\\n     -+\t<committer@example.com>\"` will add the \"Signed-off\" trailer\n     ++\t<committer@example.com>\"` will add the \"Signed-off-by\" trailer\n      +\tand the \"Helped-by\" trailer in the commit message.)\n     -+\n     ++\tSee linkgit:git-interpret-trailers[1] for details.\n       -n::\n       --no-verify::\n       \tThis option bypasses the pre-commit and commit-msg hooks.\n     @@ builtin/commit.c: static int prepare_to_commit(const char *index_file, const cha\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\", \"--where=end\", git_path_commit_editmsg(), NULL);\n     ++\t\t\t     \"--in-place\", \"--if-exists=add\",\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 -:  ------------ > 2:  68e0bd9e2d6f interpret_trailers: for three options parse add warning\n\n-- \ngitgitgadget\n"},{"id":"419149","messageId":"f81b6e66a6ba6b2fc8e6307dbc5fba9cddcff181.1615813658.git.gitgitgadget@gmail.com","threadId":"55288","inReplyTo":"pull.901.v8.git.1615813658.gitgitgadget@gmail.com","subject":"[PATCH v8 1/2] [GSOC] commit: add --trailer option","fromName":"ZheNing Hu via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2021-03-15T13:07:36Z","receivedAt":"2021-03-15T13:08:24Z","isPatch":true,"sender":{"key":"adlternative@gmail.com","avatar":"https://avatars.githubusercontent.com/u/58138461?v=4"},"body":"From: ZheNing Hu <adlternative@gmail.com>\n\nHistorically, Git has supported the 'Signed-off-by' commit trailer\nusing the '--signoff' and the '-s' option from the command line.\nBut users may need to provide other trailer information from the\ncommand line such as \"Helped-by\", \"Reported-by\", \"Mentored-by\",\n\nNow implement a new `--trailer <token>[(=|:)<value>]` option to pass\nother trailers to `interpret-trailers` and insert them into commit\nmessages.\n\nSigned-off-by: ZheNing Hu <adlternative@gmail.com>\n---\n Documentation/git-commit.txt |  9 ++++++++-\n builtin/commit.c             | 23 +++++++++++++++++++++++\n t/t7502-commit-porcelain.sh  | 20 ++++++++++++++++++++\n 3 files changed, 51 insertions(+), 1 deletion(-)\n\ndiff --git a/Documentation/git-commit.txt b/Documentation/git-commit.txt\nindex 17150fa7eabe..f74d320f0c96 100644\n--- a/Documentation/git-commit.txt\n+++ b/Documentation/git-commit.txt\n@@ -14,7 +14,7 @@ SYNOPSIS\n \t   [--allow-empty-message] [--no-verify] [-e] [--author=<author>]\n \t   [--date=<date>] [--cleanup=<mode>] [--[no-]status]\n \t   [-i | -o] [--pathspec-from-file=<file> [--pathspec-file-nul]]\n-\t   [-S[<keyid>]] [--] [<pathspec>...]\n+\t   [-S[<keyid>]] [--] [<pathspec>...] [(--trailer <token>[(=|:)<value>])...]\n \n DESCRIPTION\n -----------\n@@ -166,6 +166,13 @@ The `-m` option is mutually exclusive with `-c`, `-C`, and `-F`.\n \n include::signoff-option.txt[]\n \n+--trailer <token>[(=|:)<value>]::\n+\tSpecify a (<token>, <value>) pair that should be applied as a\n+\ttrailer. (e.g. `git commit --trailer \"Signed-off-by:C O Mitter \\\n+\t<committer@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 in the commit message.)\n+\tSee linkgit:git-interpret-trailers[1] for details.\n -n::\n --no-verify::\n \tThis option bypasses the pre-commit and commit-msg hooks.\ndiff --git a/builtin/commit.c b/builtin/commit.c\nindex 739110c5a7f6..250e93d977df 100644\n--- a/builtin/commit.c\n+++ b/builtin/commit.c\n@@ -113,6 +113,7 @@ static int config_commit_verbose = -1; /* unspecified */\n static int no_post_rewrite, allow_empty_message, pathspec_file_nul;\n static char *untracked_files_arg, *force_date, *ignore_submodule_arg, *ignored_arg;\n static char *sign_commit, *pathspec_from_file;\n+static struct strvec trailer_args = STRVEC_INIT;\n \n /*\n  * The default commit message cleanup mode will remove the lines\n@@ -131,6 +132,14 @@ 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(&trailer_args, \"--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@@ -958,6 +967,19 @@ static int prepare_to_commit(const char *index_file, const char *prefix,\n \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\", \"--if-exists=add\",\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\t\tstrvec_clear(&run_trailer.args);\n+\t\tstrvec_clear(&trailer_args);\n+\t}\n+\n \t/*\n \t * Reject an attempt to record a non-merge empty commit without\n \t * explicit --allow-empty. In the cherry-pick case, it may be\n@@ -1507,6 +1529,7 @@ int cmd_commit(int argc, const char **argv, const char *prefix)\n \t\tOPT_STRING(0, \"fixup\", &fixup_message, N_(\"commit\"), N_(\"use autosquash formatted message to fixup 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\", NULL, N_(\"trailer\"), N_(\"trailer(s) to add\"), PARSE_OPT_NONEG, opt_pass_trailer),\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/t/t7502-commit-porcelain.sh b/t/t7502-commit-porcelain.sh\nindex 6396897cc818..0acf23799931 100755\n--- a/t/t7502-commit-porcelain.sh\n+++ b/t/t7502-commit-porcelain.sh\n@@ -154,6 +154,26 @@ test_expect_success 'sign off' '\n \n '\n \n+test_expect_success 'trailer' '\n+\t>file1 &&\n+\tgit add file1 &&\n+\tgit commit -s --trailer \"Signed-off-by:C O Mitter1 <committer1@example.com>\" \\\n+\t\t--trailer \"Helped-by:C O Mitter2 <committer2@example.com>\"  \\\n+\t\t--trailer \"Reported-by:C O Mitter3 <committer3@example.com>\" \\\n+\t\t--trailer \"Mentored-by:C O Mitter4 <committer4@example.com>\" \\\n+\t\t-m \"hello\" &&\n+\tgit cat-file commit HEAD >commit.msg &&\n+\tsed -e \"1,7d\" commit.msg >actual &&\n+\tcat >expected <<-\\EOF &&\n+\tSigned-off-by: C O Mitter <committer@example.com>\n+\tSigned-off-by: C O Mitter1 <committer1@example.com>\n+\tHelped-by: C O Mitter2 <committer2@example.com>\n+\tReported-by: C O Mitter3 <committer3@example.com>\n+\tMentored-by: C O Mitter4 <committer4@example.com>\n+\tEOF\n+\ttest_cmp expected actual\n+'\n+\n test_expect_success 'multiple -m' '\n \n \t>negative &&\n-- \ngitgitgadget\n\n"},{"id":"419150","messageId":"68e0bd9e2d6f0a89d60db730eb77507d6a17a5ae.1615813658.git.gitgitgadget@gmail.com","threadId":"55288","inReplyTo":"pull.901.v8.git.1615813658.gitgitgadget@gmail.com","subject":"[PATCH v8 2/2] interpret_trailers: for three options parse add warning","fromName":"ZheNing Hu via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2021-03-15T13:07:37Z","receivedAt":"2021-03-15T13:08:25Z","isPatch":true,"sender":{"key":"adlternative@gmail.com","avatar":"https://avatars.githubusercontent.com/u/58138461?v=4"},"body":"From: ZheNing Hu <adlternative@gmail.com>\n\nWhen using `interpret-trailers`, if user accidentally fill in the\nwrong option values (e.g. `--if-exists=addd` or `--where=begin` or\n`--if-missing=do-nothing`), git will exit quietly, and the user may\nnot even know what happened.\n\nSo lets provides warnings when git parsing this three options:\n\"unknown value '%s' for key ('where'|'--if-exist'|'--if-missing')\".\nThis will remind the user :\"Oh, it was because of my spelling error.\"\n(or using a synonym for error).\n\nSigned-off-by: ZheNing Hu <adlternative@gmail.com>\n---\n builtin/interpret-trailers.c | 25 ++++++++++++++++++++++---\n 1 file changed, 22 insertions(+), 3 deletions(-)\n\ndiff --git a/builtin/interpret-trailers.c b/builtin/interpret-trailers.c\nindex 84748eafc01b..6d8e86a44d43 100644\n--- a/builtin/interpret-trailers.c\n+++ b/builtin/interpret-trailers.c\n@@ -24,19 +24,38 @@ static enum trailer_if_missing if_missing;\n static int option_parse_where(const struct option *opt,\n \t\t\t      const char *arg, int unset)\n {\n-\treturn trailer_set_where(&where, arg);\n+\tint ret;\n+\n+\tret = trailer_set_where(&where, arg);\n+\tif (ret)\n+\t\twarning(_(\"unknown value '%s' for key 'where'\"),\n+\t\t\targ);\n+\treturn ret;\n+\n }\n \n static int option_parse_if_exists(const struct option *opt,\n \t\t\t\t  const char *arg, int unset)\n {\n-\treturn trailer_set_if_exists(&if_exists, arg);\n+\tint ret;\n+\n+\tret = trailer_set_if_exists(&if_exists, arg);\n+\tif (ret)\n+\t\twarning(_(\"unknown value '%s' for key 'if_exists'\"),\n+\t\t\targ);\n+\treturn ret;\n }\n \n static int option_parse_if_missing(const struct option *opt,\n \t\t\t\t   const char *arg, int unset)\n {\n-\treturn trailer_set_if_missing(&if_missing, arg);\n+\tint ret;\n+\n+\tret = trailer_set_if_missing(&if_missing, arg);\n+\tif (ret)\n+\t\twarning(_(\"unknown value '%s' for key 'if_missing'\"),\n+\t\t\targ);\n+\treturn ret;\n }\n \n static void new_trailers_clear(struct list_head *trailers)\n-- \ngitgitgadget\n"},{"id":"419274","messageId":"CAP8UFD0+xVTD4t=dgAx0YYxw5G5VinQL1VR+5KW+GypNg1o=Kg@mail.gmail.com","threadId":"55288","inReplyTo":"CAOLTT8QTxnykacdDaMjZMkEqTHPSrPz6HH-bSgbECo5tUgf5Gg@mail.gmail.com","subject":"Re: [PATCH v7] [GSOC] commit: add --trailer option","fromName":"Christian Couder","fromEmail":"christian.couder@gmail.com","sentAt":"2021-03-16T05:37:32Z","receivedAt":"2021-03-16T05:38:47Z","isPatch":true,"sender":{"key":"christian.couder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/208954?v=4"},"body":"On Mon, Mar 15, 2021 at 12:32 PM ZheNing Hu <adlternative@gmail.com> wrote:\n>\n> Christian Couder <christian.couder@gmail.com> 于2021年3月15日周一 下午6:14写道：\n> >\n> > On Mon, Mar 15, 2021 at 10:08 AM ZheNing Hu via GitGitGadget\n> > <gitgitgadget@gmail.com> wrote:\n> >\n> > >       + if (trailer_args.nr) {\n> > >      -+         static struct child_process run_trailer = CHILD_PROCESS_INIT;\n> > >      ++         struct child_process run_trailer = CHILD_PROCESS_INIT;\n> > >       +\n> > >       +         strvec_pushl(&run_trailer.args, \"interpret-trailers\",\n> > >       +                      \"--in-place\", \"--where=end\", git_path_commit_editmsg(), NULL);\n> >\n> > Actually I don't think \"--where=end\" should be used here. \"end\" is the\n> > default for the \"trailer.where\" config variable, so by default if\n> > nothing has been configured, it will work as if \"--where=end\" was\n> > passed above.\n> >\n> > If a user has configured \"trailer.where\" or trailer.<token>.where,\n> > then this should be respected. And users should be able to override\n> > such config variable using for example:\n> >\n> > git -c trailer.where=start commit --trailer \"Signed-off-by:C O Mitter\n> > <committer@example.com>\"\n>\n> Thanks for reminding, generally speaking, we will put the trailer at the\n> end of the commit messages.Take trailers in start, this should be\n> something I haven't considered.\n\nIn general what I want to say is that `git interpret-trailers` should\nbe considered to have sensible defaults, that can possibly be\noverridden using a number of config variables (or the git -c ...\nmechanism) which is a good thing. If something in it doesn't work\nwell, it's possible to improve it of course. Otherwise it's better to\njust fully take advantage of it.\n\n> I notice another question:\n> if we commit this again with same trailer (even same email or same commiter)\n> `--trailer` will not work again, because in `interpret_trailers`,\n> \"if-exists\" default\n> set to \"addIfDifferentNeighbor\", I addvice enforce use \"if-exists=\"add\".\n\nI don't agree with using \"--if-exists=add\". I think the default to not\nadd a trailer line if it would be just above or below the same line is\nbetter, as doing that wouldn't add much information. It's better to\nencourage people to use trailers in a meaningful way.\n\nAnd again if we use \"--if-exists=add\", then people who would want to\ntake advantage of `git interpret-trailers` to customize what happens\nwhen the trailer already exists would not be able to do it.\n\nFor example if we don't use \"--if-exists=add\", then:\n\n- people who want to customize what happens when the trailer already\nexists can do it with for example:\n\ngit -c trailer.ifexists=addIfDifferent commit --trailer\n\"Signed-off-by:C O Mitter <committer@example.com>\"\n\n- which means that people who want the \"--if-exists=add\" behavior can\nstill have it with:\n\ngit -c trailer.ifexists=add commit --trailer \"Signed-off-by:C O Mitter\n<committer@example.com>\"\n\nWhile if we use \"--if-exists=add\", then using `git -c\ntrailer.ifexists=... commit ...` will not customize anything as the\n\"--if-exists=add\" command line option will override any config\ncustomization.\n"},{"id":"419278","messageId":"CAP8UFD0tgXi6Hq42dzsAAGMOhvLxqaUuBqex0CreyRb_XD5rxQ@mail.gmail.com","threadId":"55288","inReplyTo":"68e0bd9e2d6f0a89d60db730eb77507d6a17a5ae.1615813658.git.gitgitgadget@gmail.com","subject":"Re: [PATCH v8 2/2] interpret_trailers: for three options parse add warning","fromName":"Christian Couder","fromEmail":"christian.couder@gmail.com","sentAt":"2021-03-16T05:53:25Z","receivedAt":"2021-03-16T05:54:16Z","isPatch":true,"sender":{"key":"christian.couder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/208954?v=4"},"body":"On Mon, Mar 15, 2021 at 2:07 PM ZheNing Hu via GitGitGadget\n<gitgitgadget@gmail.com> wrote:\n>\n> From: ZheNing Hu <adlternative@gmail.com>\n>\n> When using `interpret-trailers`, if user accidentally fill in the\n> wrong option values (e.g. `--if-exists=addd` or `--where=begin` or\n> `--if-missing=do-nothing`), git will exit quietly, and the user may\n> not even know what happened.\n>\n> So lets provides warnings when git parsing this three options:\n> \"unknown value '%s' for key ('where'|'--if-exist'|'--if-missing')\".\n> This will remind the user :\"Oh, it was because of my spelling error.\"\n> (or using a synonym for error).\n\nI am not against this, but I just want to say that when I previously\nworked on `interpret-trailers` I think I implemented or suggested such\nwarnings, but they were rejected.\n\nI think the reason they were rejected was to improve compatibility\nwith future versions of Git where more options would be implemented.\n\nFor example if in a few years someone implements `--where=middle` and\nsome people use it in a script like this:\n\ngit interpret-trailers --where=middle --trailer foo=bar\n\nThen when such a script would be used with a recent version of Git it\nwould work well, while if it would be used with an old version of Git\nit would emit warnings. And these warnings might actually be more\nannoying than the fact that the trailer is not put in the middle.\n\nI might be wrong and there might have been other reasons though. Also\nthings might have changed since that time, as not many options if any\nhave been added since then.\n"},{"id":"419287","messageId":"CAOLTT8Q9YX=Yw22xx4GYGf-2fPd6_UfqGCyxOtK4au8000etCQ@mail.gmail.com","threadId":"55288","inReplyTo":"CAP8UFD0+xVTD4t=dgAx0YYxw5G5VinQL1VR+5KW+GypNg1o=Kg@mail.gmail.com","subject":"Re: [PATCH v7] [GSOC] commit: add --trailer option","fromName":"ZheNing Hu","fromEmail":"adlternative@gmail.com","sentAt":"2021-03-16T08:35:24Z","receivedAt":"2021-03-16T08:36:31Z","isPatch":true,"sender":{"key":"adlternative@gmail.com","avatar":"https://avatars.githubusercontent.com/u/58138461?v=4"},"body":"Christian Couder <christian.couder@gmail.com> 于2021年3月16日周二 下午1:37写道：\n\n> > Thanks for reminding, generally speaking, we will put the trailer at the\n> > end of the commit messages.Take trailers in start, this should be\n> > something I haven't considered.\n>\n> In general what I want to say is that `git interpret-trailers` should\n> be considered to have sensible defaults, that can possibly be\n> overridden using a number of config variables (or the git -c ...\n> mechanism) which is a good thing. If something in it doesn't work\n> well, it's possible to improve it of course. Otherwise it's better to\n> just fully take advantage of it.\n>\n> > I notice another question:\n> > if we commit this again with same trailer (even same email or same commiter)\n> > `--trailer` will not work again, because in `interpret_trailers`,\n> > \"if-exists\" default\n> > set to \"addIfDifferentNeighbor\", I addvice enforce use \"if-exists=\"add\".\n>\n> I don't agree with using \"--if-exists=add\". I think the default to not\n> add a trailer line if it would be just above or below the same line is\n> better, as doing that wouldn't add much information. It's better to\n> encourage people to use trailers in a meaningful way.\n>\n> And again if we use \"--if-exists=add\", then people who would want to\n> take advantage of `git interpret-trailers` to customize what happens\n> when the trailer already exists would not be able to do it.\n>\n> For example if we don't use \"--if-exists=add\", then:\n>\n> - people who want to customize what happens when the trailer already\n> exists can do it with for example:\n>\n> git -c trailer.ifexists=addIfDifferent commit --trailer\n> \"Signed-off-by:C O Mitter <committer@example.com>\"\n>\n> - which means that people who want the \"--if-exists=add\" behavior can\n> still have it with:\n>\n> git -c trailer.ifexists=add commit --trailer \"Signed-off-by:C O Mitter\n> <committer@example.com>\"\n>\n> While if we use \"--if-exists=add\", then using `git -c\n> trailer.ifexists=... commit ...` will not customize anything as the\n> \"--if-exists=add\" command line option will override any config\n> customization.\n\nWell, I see what you mean, this will keep git better flexibility,\ngive users more personalized configuration options.\nAnd this should be more in line with git design philosophy,\nI will follow your suggestion.\n"},{"id":"419292","messageId":"CAOLTT8Ti0+QJTUahUxOJ8fmq5aNTVnaSm1GyXaOJAAk3fD=49Q@mail.gmail.com","threadId":"55288","inReplyTo":"CAP8UFD0tgXi6Hq42dzsAAGMOhvLxqaUuBqex0CreyRb_XD5rxQ@mail.gmail.com","subject":"Re: [PATCH v8 2/2] interpret_trailers: for three options parse add warning","fromName":"ZheNing Hu","fromEmail":"adlternative@gmail.com","sentAt":"2021-03-16T09:11:45Z","receivedAt":"2021-03-16T09:13:51Z","isPatch":true,"sender":{"key":"adlternative@gmail.com","avatar":"https://avatars.githubusercontent.com/u/58138461?v=4"},"body":"Christian Couder <christian.couder@gmail.com> 于2021年3月16日周二 下午1:53写道：\n> I am not against this, but I just want to say that when I previously\n> worked on `interpret-trailers` I think I implemented or suggested such\n> warnings, but they were rejected.\n>\n> I think the reason they were rejected was to improve compatibility\n> with future versions of Git where more options would be implemented.\n>\n> For example if in a few years someone implements `--where=middle` and\n> some people use it in a script like this:\n>\n> git interpret-trailers --where=middle --trailer foo=bar\n>\n> Then when such a script would be used with a recent version of Git it\n> would work well, while if it would be used with an old version of Git\n> it would emit warnings. And these warnings might actually be more\n> annoying than the fact that the trailer is not put in the middle.\n>\n\nWell, this is indeed a situation I did not foresee. As a user, I don't see an\nerror | warning reminder which may make me a little confused.\n\nAt the same time, some sub-command like `git cat-file`, If user give a wrong\nformat:\n\n$  git cat-file  --batch-check=\"%(deltabases)\"\n\ngit will tell us, \"fatal: unknown format element: deltabases\".\nIt's easy for user to check.\n\n> I might be wrong and there might have been other reasons though. Also\n> things might have changed since that time, as not many options if any\n> have been added since then.\n\nThanks for your patiently explanation.\nIf these warnings are really unnecessary, I will drop this patch.\n"},{"id":"419294","messageId":"pull.901.v9.git.1615891183320.gitgitgadget@gmail.com","threadId":"55288","inReplyTo":"pull.901.v8.git.1615813658.gitgitgadget@gmail.com","subject":"[PATCH v9] [GSOC] commit: add --trailer option","fromName":"ZheNing Hu via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2021-03-16T10:39:42Z","receivedAt":"2021-03-16T10:40:37Z","isPatch":true,"sender":{"key":"adlternative@gmail.com","avatar":"https://avatars.githubusercontent.com/u/58138461?v=4"},"body":"From: ZheNing Hu <adlternative@gmail.com>\n\nHistorically, Git has supported the 'Signed-off-by' commit trailer\nusing the '--signoff' and the '-s' option from the command line.\nBut users may need to provide other trailer information from the\ncommand line such as \"Helped-by\", \"Reported-by\", \"Mentored-by\",\n\nNow implement a new `--trailer <token>[(=|:)<value>]` option to pass\nother trailers to `interpret-trailers` and insert them into commit\nmessages.\n\nSigned-off-by: ZheNing Hu <adlternative@gmail.com>\n---\n    [GSOC] commit: add --trailer option\n    \n    Now maintainers or developers can also use commit\n    --trailer=\"Signed-off-by:commiter<email>\" from the command line to\n    provide trailers to commit messages. This solution may be more\n    generalized than v1.\n\nPublished-As: https://github.com/gitgitgadget/git/releases/tag/pr-901%2Fadlternative%2Fcommit-with-multiple-signatures-v9\nFetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-901/adlternative/commit-with-multiple-signatures-v9\nPull-Request: https://github.com/gitgitgadget/git/pull/901\n\nRange-diff vs v8:\n\n 1:  f81b6e66a6ba ! 1:  e524c4ba5dc1 [GSOC] commit: add --trailer option\n     @@ builtin/commit.c: static int prepare_to_commit(const char *index_file, const cha\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\", \"--if-exists=add\",\n     -+\t\t\t     git_path_commit_editmsg(), NULL);\n     ++\t\t\t     \"--in-place\", 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 2:  68e0bd9e2d6f < -:  ------------ interpret_trailers: for three options parse add warning\n\n\n Documentation/git-commit.txt |  9 ++++++++-\n builtin/commit.c             | 22 ++++++++++++++++++++++\n t/t7502-commit-porcelain.sh  | 20 ++++++++++++++++++++\n 3 files changed, 50 insertions(+), 1 deletion(-)\n\ndiff --git a/Documentation/git-commit.txt b/Documentation/git-commit.txt\nindex 17150fa7eabe..f74d320f0c96 100644\n--- a/Documentation/git-commit.txt\n+++ b/Documentation/git-commit.txt\n@@ -14,7 +14,7 @@ SYNOPSIS\n \t   [--allow-empty-message] [--no-verify] [-e] [--author=<author>]\n \t   [--date=<date>] [--cleanup=<mode>] [--[no-]status]\n \t   [-i | -o] [--pathspec-from-file=<file> [--pathspec-file-nul]]\n-\t   [-S[<keyid>]] [--] [<pathspec>...]\n+\t   [-S[<keyid>]] [--] [<pathspec>...] [(--trailer <token>[(=|:)<value>])...]\n \n DESCRIPTION\n -----------\n@@ -166,6 +166,13 @@ The `-m` option is mutually exclusive with `-c`, `-C`, and `-F`.\n \n include::signoff-option.txt[]\n \n+--trailer <token>[(=|:)<value>]::\n+\tSpecify a (<token>, <value>) pair that should be applied as a\n+\ttrailer. (e.g. `git commit --trailer \"Signed-off-by:C O Mitter \\\n+\t<committer@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 in the commit message.)\n+\tSee linkgit:git-interpret-trailers[1] for details.\n -n::\n --no-verify::\n \tThis option bypasses the pre-commit and commit-msg hooks.\ndiff --git a/builtin/commit.c b/builtin/commit.c\nindex 739110c5a7f6..a002330d9d56 100644\n--- a/builtin/commit.c\n+++ b/builtin/commit.c\n@@ -113,6 +113,7 @@ static int config_commit_verbose = -1; /* unspecified */\n static int no_post_rewrite, allow_empty_message, pathspec_file_nul;\n static char *untracked_files_arg, *force_date, *ignore_submodule_arg, *ignored_arg;\n static char *sign_commit, *pathspec_from_file;\n+static struct strvec trailer_args = STRVEC_INIT;\n \n /*\n  * The default commit message cleanup mode will remove the lines\n@@ -131,6 +132,14 @@ 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(&trailer_args, \"--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@@ -958,6 +967,18 @@ static int prepare_to_commit(const char *index_file, const char *prefix,\n \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\", 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\t\tstrvec_clear(&run_trailer.args);\n+\t\tstrvec_clear(&trailer_args);\n+\t}\n+\n \t/*\n \t * Reject an attempt to record a non-merge empty commit without\n \t * explicit --allow-empty. In the cherry-pick case, it may be\n@@ -1507,6 +1528,7 @@ int cmd_commit(int argc, const char **argv, const char *prefix)\n \t\tOPT_STRING(0, \"fixup\", &fixup_message, N_(\"commit\"), N_(\"use autosquash formatted message to fixup 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\", NULL, N_(\"trailer\"), N_(\"trailer(s) to add\"), PARSE_OPT_NONEG, opt_pass_trailer),\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/t/t7502-commit-porcelain.sh b/t/t7502-commit-porcelain.sh\nindex 6396897cc818..0acf23799931 100755\n--- a/t/t7502-commit-porcelain.sh\n+++ b/t/t7502-commit-porcelain.sh\n@@ -154,6 +154,26 @@ test_expect_success 'sign off' '\n \n '\n \n+test_expect_success 'trailer' '\n+\t>file1 &&\n+\tgit add file1 &&\n+\tgit commit -s --trailer \"Signed-off-by:C O Mitter1 <committer1@example.com>\" \\\n+\t\t--trailer \"Helped-by:C O Mitter2 <committer2@example.com>\"  \\\n+\t\t--trailer \"Reported-by:C O Mitter3 <committer3@example.com>\" \\\n+\t\t--trailer \"Mentored-by:C O Mitter4 <committer4@example.com>\" \\\n+\t\t-m \"hello\" &&\n+\tgit cat-file commit HEAD >commit.msg &&\n+\tsed -e \"1,7d\" commit.msg >actual &&\n+\tcat >expected <<-\\EOF &&\n+\tSigned-off-by: C O Mitter <committer@example.com>\n+\tSigned-off-by: C O Mitter1 <committer1@example.com>\n+\tHelped-by: C O Mitter2 <committer2@example.com>\n+\tReported-by: C O Mitter3 <committer3@example.com>\n+\tMentored-by: C O Mitter4 <committer4@example.com>\n+\tEOF\n+\ttest_cmp expected actual\n+'\n+\n test_expect_success 'multiple -m' '\n \n \t>negative &&\n\nbase-commit: 13d7ab6b5d7929825b626f050b62a11241ea4945\n-- \ngitgitgadget\n"},{"id":"419298","messageId":"87zgz3dzvl.fsf@evledraar.gmail.com","threadId":"55288","inReplyTo":"f81b6e66a6ba6b2fc8e6307dbc5fba9cddcff181.1615813658.git.gitgitgadget@gmail.com","subject":"Re: [PATCH v8 1/2] [GSOC] commit: add --trailer option","fromName":"Ævar Arnfjörð Bjarmason","fromEmail":"avarab@gmail.com","sentAt":"2021-03-16T12:52:46Z","receivedAt":"2021-03-16T12:53:40Z","isPatch":true,"sender":{"key":"avarab@gmail.com","avatar":"https://avatars.githubusercontent.com/u/45301?v=4"},"body":"\nOn Mon, Mar 15 2021, ZheNing Hu via GitGitGadget wrote:\n\n> From: ZheNing Hu <adlternative@gmail.com>\n>\n> Historically, Git has supported the 'Signed-off-by' commit trailer\n> using the '--signoff' and the '-s' option from the command line.\n> But users may need to provide other trailer information from the\n> command line such as \"Helped-by\", \"Reported-by\", \"Mentored-by\",\n>\n> Now implement a new `--trailer <token>[(=|:)<value>]` option to pass\n> other trailers to `interpret-trailers` and insert them into commit\n> messages.\n\nI paged through v1-v8, looking mostly good so far. Some comments:\n\n> Signed-off-by: ZheNing Hu <adlternative@gmail.com>\n> ---\n>  Documentation/git-commit.txt |  9 ++++++++-\n>  builtin/commit.c             | 23 +++++++++++++++++++++++\n>  t/t7502-commit-porcelain.sh  | 20 ++++++++++++++++++++\n>  3 files changed, 51 insertions(+), 1 deletion(-)\n>\n> diff --git a/Documentation/git-commit.txt b/Documentation/git-commit.txt\n> index 17150fa7eabe..f74d320f0c96 100644\n> --- a/Documentation/git-commit.txt\n> +++ b/Documentation/git-commit.txt\n> @@ -14,7 +14,7 @@ SYNOPSIS\n>  \t   [--allow-empty-message] [--no-verify] [-e] [--author=<author>]\n>  \t   [--date=<date>] [--cleanup=<mode>] [--[no-]status]\n>  \t   [-i | -o] [--pathspec-from-file=<file> [--pathspec-file-nul]]\n> -\t   [-S[<keyid>]] [--] [<pathspec>...]\n> +\t   [-S[<keyid>]] [--] [<pathspec>...] [(--trailer <token>[(=|:)<value>])...]\n>  \n>  DESCRIPTION\n>  -----------\n> @@ -166,6 +166,13 @@ The `-m` option is mutually exclusive with `-c`, `-C`, and `-F`.\n>  \n>  include::signoff-option.txt[]\n>  \n> +--trailer <token>[(=|:)<value>]::\n> +\tSpecify a (<token>, <value>) pair that should be applied as a\n> +\ttrailer. (e.g. `git commit --trailer \"Signed-off-by:C O Mitter \\\n> +\t<committer@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 in the commit message.)\n> +\tSee linkgit:git-interpret-trailers[1] for details.\n>  -n::\n>  --no-verify::\n>  \tThis option bypasses the pre-commit and commit-msg hooks.\n> diff --git a/builtin/commit.c b/builtin/commit.c\n> index 739110c5a7f6..250e93d977df 100644\n> --- a/builtin/commit.c\n> +++ b/builtin/commit.c\n> @@ -113,6 +113,7 @@ static int config_commit_verbose = -1; /* unspecified */\n>  static int no_post_rewrite, allow_empty_message, pathspec_file_nul;\n>  static char *untracked_files_arg, *force_date, *ignore_submodule_arg, *ignored_arg;\n>  static char *sign_commit, *pathspec_from_file;\n> +static struct strvec trailer_args = STRVEC_INIT;\n>  \n>  /*\n>   * The default commit message cleanup mode will remove the lines\n> @@ -131,6 +132,14 @@ 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(&trailer_args, \"--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> @@ -958,6 +967,19 @@ static int prepare_to_commit(const char *index_file, const char *prefix,\n>  \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\", \"--if-exists=add\",\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\t\tstrvec_clear(&run_trailer.args);\n\nThis is git-commit, shouldn't we die() here instead of ignoring errors\nin sub-processes?\n\n> +\t\tstrvec_clear(&trailer_args);\n> +\t}\n> +\n>  \t/*\n>  \t * Reject an attempt to record a non-merge empty commit without\n>  \t * explicit --allow-empty. In the cherry-pick case, it may be\n> @@ -1507,6 +1529,7 @@ int cmd_commit(int argc, const char **argv, const char *prefix)\n>  \t\tOPT_STRING(0, \"fixup\", &fixup_message, N_(\"commit\"), N_(\"use autosquash formatted message to fixup 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\", NULL, N_(\"trailer\"), N_(\"trailer(s) to add\"), PARSE_OPT_NONEG, opt_pass_trailer),\n>  \t\tOPT_BOOL('s', \"signoff\", &signoff, N_(\"add a Signed-off-by trailer\")),\n\nNot required for this change, but perhaps a change here to N_() (if we\ncan get it to fit) + doc update saying that we prefer\n--trailer=\"Signed-Off-By: to --signoff\"? More on that later.\n\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/t/t7502-commit-porcelain.sh b/t/t7502-commit-porcelain.sh\n> index 6396897cc818..0acf23799931 100755\n> --- a/t/t7502-commit-porcelain.sh\n> +++ b/t/t7502-commit-porcelain.sh\n> @@ -154,6 +154,26 @@ test_expect_success 'sign off' '\n>  \n>  '\n>  \n> +test_expect_success 'trailer' '\n> +\t>file1 &&\n> +\tgit add file1 &&\n> +\tgit commit -s --trailer \"Signed-off-by:C O Mitter1 <committer1@example.com>\" \\\n> +\t\t--trailer \"Helped-by:C O Mitter2 <committer2@example.com>\"  \\\n> +\t\t--trailer \"Reported-by:C O Mitter3 <committer3@example.com>\" \\\n> +\t\t--trailer \"Mentored-by:C O Mitter4 <committer4@example.com>\" \\\n> +\t\t-m \"hello\" &&\n> +\tgit cat-file commit HEAD >commit.msg &&\n> +\tsed -e \"1,7d\" commit.msg >actual &&\n> +\tcat >expected <<-\\EOF &&\n> +\tSigned-off-by: C O Mitter <committer@example.com>\n> +\tSigned-off-by: C O Mitter1 <committer1@example.com>\n> +\tHelped-by: C O Mitter2 <committer2@example.com>\n> +\tReported-by: C O Mitter3 <committer3@example.com>\n> +\tMentored-by: C O Mitter4 <committer4@example.com>\n> +\tEOF\n> +\ttest_cmp expected actual\n> +'\n> +\n\nHow does this interact with cases where the user has configured\n\"trailer.separators\" to have a value that doesn't contain \":\"?  I\nhaven't tested, but my reading of git-interpret-trailers(1) is that if\nyou supplied \"=\" instead that case would just work:\n\n    By default only : is recognized as a trailer separator, except that\n    = is always accepted on the command line for compatibility with\n    other git commands.\n\nI don't know if that does the right thing in the presence of\n--if-exists=add.\n\nSo it would be good to update these tests so you test:\n\n * For the --if-exists=add case at all, there's no tests for it\n   now. I.e. add some trailers manually to the commit (via -F or\n   whatever) and then see if they get added to, replacet etc.\n\n * Ditto but for the user having configured trailer.separators (see the\n   test_config helper for how to set config in a test). I.e. if it's \"=\"\n   does adding trailers work, how about if it's \"=\" on the CLI but the\n   config/commit message has \";\" instead of \":\" or something?\n\n * Hrm, actually I think tweaking \"-c trailer.ifexists\" won't work at\n   all, since the CLI switch would override it. I honestly don't know,\n   but why not not supply it and keep the addIfDifferentNeighbor\n   default?\n\n   If it's essential that seems like a good test / documentation\n   addition...\n\n * For the above -c ... case I can't think of a good way to deal with it\n   that doesn't involve pulling in git_trailer_config() into\n   git_commit_config(), but perhaps the least nasty way is to just set a\n   flag in git_commit_config() if we see a \"trailer.ifexists\" flag, and\n   if so don't provide \"--if-exists=add\", if there's no config (this\n   will include \"git -c ... commit\" we set provide \"--if-exists=add\" )\n   or as noted above, maybe we can skip the whole thing and use the\n   addIfDifferentNeighbor default.\n\nAnd, not needed for this patch but worth thinking about:\n\n * We pass through --trailer to git-interpret-trailers, what should we\n   do about the other options? Should git-commit eventually support\n   --trailer-where and pass it along as --where to\n   git-interpret-trailers, or is \"git -c trailer.where=... commit\" good\n   enough?\n\n * It would be good to test for and document if that \"-c trailer.*\"\n   trick works (no reason it shouldn't). I.e. to add something like this\n   after what you have (along with tests, and check if it's even true):\n\n       Only the `--trailer` argument to\n       linkgit:git-interpret-trailers[1] is supported. Other\n       pass-through switches may be added in the future, but currently\n       you'll need to pass arguments to\n       linkgit:git-interpret-trailers[1] along as config, e.g. `git -c\n       trailer.where=start commit [...] --trailer=[...]`.\n      \n * We have a longer-term goal of having the .mailmap apply to trailers,\n   it would be nice if git-interpret-trailers had some fuzzy-matching to\n   check if the RHS of a trailer is a name/E-Mail pair, and if so did\n   stricter validation on it with the ident functions we use for fsck\n   etc. (that's copied & subtly different in several different places in\n   the codebase, unfortunately[1]).\n\nMore thoughts:\n\n * Having written all the above I checked how --signoff is implemented.\n\n   It seems to me to be a good idea to (at least for testing) convert\n   the --signoff trailer to your implementation. We have plenty of tests\n   for it, does migrating it over pass or fail those?\n\n * I also agree with Junio that we shouldn't have a --fixed-by or\n   whatever and wouldn't add --signoff today, but it seems very useful\n   to me to have a shortcut like:\n\n       --trailer \"Signed-off-by\"\n\n   I.e. omitting the value, or:\n\n      --trailer \"Signed-off-by=\"\n\n   Or some other thing we deem sufficiently useful/sane\n   syntax/unambiguous.n\n\n   Then the value would be provided by fmt_name(WANT_COMMITTER_IDENT)\n   just as we do in append_signoff() now. I think a *very common* case\n   for this would be something like:\n\n       git commit --amend -v --trailer \"Reviewed-by\"\n\n   And it would be useful to help that along and not have to do:\n\n       git commit --amend -v --trailer \"Reviewed-by=$(git config user.name) <$(git config user.email)>\"\n\n   Or worse yet, manually typo your name/e-mail address, as I'm sure I\n   and many others will inevitably do when using this option...\n\n1. https://lore.kernel.org/git/87bld8ov9q.fsf@evledraar.gmail.com/\n"},{"id":"419443","messageId":"CAOLTT8RyCvs8bbedPaRSo44o566Tk1MK9BeLx=-APnFsHJtejw@mail.gmail.com","threadId":"55288","inReplyTo":"87zgz3dzvl.fsf@evledraar.gmail.com","subject":"Re: [PATCH v8 1/2] [GSOC] commit: add --trailer option","fromName":"ZheNing Hu","fromEmail":"adlternative@gmail.com","sentAt":"2021-03-17T02:01:37Z","receivedAt":"2021-03-17T02:02:49Z","isPatch":true,"sender":{"key":"adlternative@gmail.com","avatar":"https://avatars.githubusercontent.com/u/58138461?v=4"},"body":"Ævar Arnfjörð Bjarmason <avarab@gmail.com> 于2021年3月16日周二 下午8:52写道：\n> > +             if (run_command(&run_trailer))\n> > +                     strvec_clear(&run_trailer.args);\n>\n> This is git-commit, shouldn't we die() here instead of ignoring errors\n> in sub-processes?\n\nAfter thinking about it carefully, your opinion is more\nreasonable, because if the user uses the wrong `--trailer`\nand does not get the information he needs, I think he will\nhave to use `--amend` to modify, and `die()` can exit\nthis commit directly.\n\n>\n> > +             strvec_clear(&trailer_args);\n> > +     }\n> > +\n> >       /*\n> >        * Reject an attempt to record a non-merge empty commit without\n> >        * explicit --allow-empty. In the cherry-pick case, it may be\n> > @@ -1507,6 +1529,7 @@ int cmd_commit(int argc, const char **argv, const char *prefix)\n> >               OPT_STRING(0, \"fixup\", &fixup_message, N_(\"commit\"), N_(\"use autosquash formatted message to fixup 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\", NULL, N_(\"trailer\"), N_(\"trailer(s) to add\"), PARSE_OPT_NONEG, opt_pass_trailer),\n> >               OPT_BOOL('s', \"signoff\", &signoff, N_(\"add a Signed-off-by trailer\")),\n>\n> Not required for this change, but perhaps a change here to N_() (if we\n> can get it to fit) + doc update saying that we prefer\n> --trailer=\"Signed-Off-By: to --signoff\"? More on that later.\n>\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> > diff --git a/t/t7502-commit-porcelain.sh b/t/t7502-commit-porcelain.sh\n> > index 6396897cc818..0acf23799931 100755\n> > --- a/t/t7502-commit-porcelain.sh\n> > +++ b/t/t7502-commit-porcelain.sh\n> > @@ -154,6 +154,26 @@ test_expect_success 'sign off' '\n> >\n> >  '\n> >\n> > +test_expect_success 'trailer' '\n> > +     >file1 &&\n> > +     git add file1 &&\n> > +     git commit -s --trailer \"Signed-off-by:C O Mitter1 <committer1@example.com>\" \\\n> > +             --trailer \"Helped-by:C O Mitter2 <committer2@example.com>\"  \\\n> > +             --trailer \"Reported-by:C O Mitter3 <committer3@example.com>\" \\\n> > +             --trailer \"Mentored-by:C O Mitter4 <committer4@example.com>\" \\\n> > +             -m \"hello\" &&\n> > +     git cat-file commit HEAD >commit.msg &&\n> > +     sed -e \"1,7d\" commit.msg >actual &&\n> > +     cat >expected <<-\\EOF &&\n> > +     Signed-off-by: C O Mitter <committer@example.com>\n> > +     Signed-off-by: C O Mitter1 <committer1@example.com>\n> > +     Helped-by: C O Mitter2 <committer2@example.com>\n> > +     Reported-by: C O Mitter3 <committer3@example.com>\n> > +     Mentored-by: C O Mitter4 <committer4@example.com>\n> > +     EOF\n> > +     test_cmp expected actual\n> > +'\n> > +\n>\n> How does this interact with cases where the user has configured\n> \"trailer.separators\" to have a value that doesn't contain \":\"?  I\n> haven't tested, but my reading of git-interpret-trailers(1) is that if\n> you supplied \"=\" instead that case would just work:\n>\n>     By default only : is recognized as a trailer separator, except that\n>     = is always accepted on the command line for compatibility with\n>     other git commands.\n>\nBut interpret_trailers interface allow us use \"=\" instead of other separators.\n\nI did a simple test and modified the configuration \"trailer.separators\"\nand it still works. Now things are good here:\n\n$ git -c trailer.separators=\"@\" commit --trailer=\"Signed-off-by=C O <email>\"\n\nor\n\n$ git -c trailer.separators=\"@\" commit --trailer=\"Signed-off-by@C O <email>\"\n\nBoth can work normally,\n\n--trailer=\"Signed-off-by@ C O <email>\"\n\nwill output in the commit message.\n\n> I don't know if that does the right thing in the presence of\n> --if-exists=add.\n>\n\nYesterday, Christian Couder and I had already discussed this issue:\nYour idea is correct, I should not add \"--if-exists = add\",  this will destroy\nthe user's rights to configure by using `git -c trailer.if-exist`.\n\n> So it would be good to update these tests so you test:\n>\n>  * For the --if-exists=add case at all, there's no tests for it\n>    now. I.e. add some trailers manually to the commit (via -F or\n>    whatever) and then see if they get added to, replacet etc.\n>\n>  * Ditto but for the user having configured trailer.separators (see the\n>    test_config helper for how to set config in a test). I.e. if it's \"=\"\n>    does adding trailers work, how about if it's \"=\" on the CLI but the\n>    config/commit message has \";\" instead of \":\" or something?\n>\n\nAs mentioned above, it works normally.\n\n>  * Hrm, actually I think tweaking \"-c trailer.ifexists\" won't work at\n>    all, since the CLI switch would override it. I honestly don't know,\n>    but why not not supply it and keep the addIfDifferentNeighbor\n>    default?\n>\n>    If it's essential that seems like a good test / documentation\n>    addition...\n>\n>  * For the above -c ... case I can't think of a good way to deal with it\n>    that doesn't involve pulling in git_trailer_config() into\n>    git_commit_config(), but perhaps the least nasty way is to just set a\n>    flag in git_commit_config() if we see a \"trailer.ifexists\" flag, and\n>    if so don't provide \"--if-exists=add\", if there's no config (this\n>    will include \"git -c ... commit\" we set provide \"--if-exists=add\" )\n>    or as noted above, maybe we can skip the whole thing and use the\n>    addIfDifferentNeighbor default.\n>\n\nHas been restored to the default settings.\n\n> And, not needed for this patch but worth thinking about:\n>\n>  * We pass through --trailer to git-interpret-trailers, what should we\n>    do about the other options? Should git-commit eventually support\n>    --trailer-where and pass it along as --where to\n>    git-interpret-trailers, or is \"git -c trailer.where=... commit\" good\n>    enough?\n>\nLogically speaking, `interpret_trailers` should be dedicated to `commit`\nor other sub-commands that require trailers.\n\nBut I think that in the later stage, the parse_options of the `cmd_commit`\ncan keep the unrecognized options, and then these choices can be directly\npassed to the `interpret_trailers` backend.\n\n>  * It would be good to test for and document if that \"-c trailer.*\"\n>    trick works (no reason it shouldn't). I.e. to add something like this\n>    after what you have (along with tests, and check if it's even true):\n>\n\nI haven't tested them for the time being, but I will do it.\n\n>        Only the `--trailer` argument to\n>        linkgit:git-interpret-trailers[1] is supported. Other\n>        pass-through switches may be added in the future, but currently\n>        you'll need to pass arguments to\n>        linkgit:git-interpret-trailers[1] along as config, e.g. `git -c\n>        trailer.where=start commit [...] --trailer=[...]`.\n>\n\nI think this is worth writing in the documentation.\n\n>  * We have a longer-term goal of having the .mailmap apply to trailers,\n>    it would be nice if git-interpret-trailers had some fuzzy-matching to\n>    check if the RHS of a trailer is a name/E-Mail pair, and if so did\n>    stricter validation on it with the ident functions we use for fsck\n>    etc. (that's copied & subtly different in several different places in\n>    the codebase, unfortunately[1]).\n>\n\nI may not know much about fuzzy-matching, which may be worth studying later.\n\n> More thoughts:\n>\n>  * Having written all the above I checked how --signoff is implemented.\n>\n>    It seems to me to be a good idea to (at least for testing) convert\n>    the --signoff trailer to your implementation. We have plenty of tests\n>    for it, does migrating it over pass or fail those?\n>\nI don’t know how to migrating yet, it may take a long time.\nEven I think I can leave it as #leftoverbit later.\n\n>  * I also agree with Junio that we shouldn't have a --fixed-by or\n>    whatever and wouldn't add --signoff today, but it seems very useful\n>    to me to have a shortcut like:\n>\n>        --trailer \"Signed-off-by\"\n>\n>    I.e. omitting the value, or:\n>\n>       --trailer \"Signed-off-by=\"\n>\n>    Or some other thing we deem sufficiently useful/sane\n>    syntax/unambiguous.n\n>\n>    Then the value would be provided by fmt_name(WANT_COMMITTER_IDENT)\n>    just as we do in append_signoff() now. I think a *very common* case\n>    for this would be something like:\n>\n>        git commit --amend -v --trailer \"Reviewed-by\"\n>\n>    And it would be useful to help that along and not have to do:\n>\n>        git commit --amend -v --trailer \"Reviewed-by=$(git config user.name) <$(git config user.email)>\"\n>\n>    Or worse yet, manually typo your name/e-mail address, as I'm sure I\n>    and many others will inevitably do when using this option...\n>\nI think this idea is very good and easy to implement.\nWe only need to do a simple string match when we get the \"trailer\" string,\nIf it can be completed, it can indeed bring great convenience to users.\n\n> 1. https://lore.kernel.org/git/87bld8ov9q.fsf@evledraar.gmail.com/\n\nThanks, Ævar Arnfjörð Bjarmason!\n\n--\nZheNing Hu\n"},{"id":"419445","messageId":"20210317052641.GA8212@konoha","threadId":"55288","inReplyTo":"pull.901.v9.git.1615891183320.gitgitgadget@gmail.com","subject":"Re: [PATCH v9] [GSOC] commit: add --trailer option","fromName":"Shourya Shukla","fromEmail":"periperidip@gmail.com","sentAt":"2021-03-17T05:26:41Z","receivedAt":"2021-03-17T05:27:34Z","isPatch":true,"sender":{"key":"periperidip@gmail.com","avatar":null},"body":"On 16/03 10:39, ZheNing Hu via GitGitGadget wrote:\n\nHey ZheNing!\n\n> From: ZheNing Hu <adlternative@gmail.com>\n> \n> Historically, Git has supported the 'Signed-off-by' commit trailer\n> using the '--signoff' and the '-s' option from the command line.\n> But users may need to provide other trailer information from the\n> command line such as \"Helped-by\", \"Reported-by\", \"Mentored-by\",\n> \n> Now implement a new `--trailer <token>[(=|:)<value>]` option to pass\n> other trailers to `interpret-trailers` and insert them into commit\n> messages.\n> \n> Signed-off-by: ZheNing Hu <adlternative@gmail.com>\n\nI have been away for a while and directly seeing a V9 of this patch\nfeels great! Its good that you have worked upon the patch. The above\napproach seems good to me!\n\n>  \t/*\n>  \t * Reject an attempt to record a non-merge empty commit without\n>  \t * explicit --allow-empty. In the cherry-pick case, it may be\n> @@ -1507,6 +1528,7 @@ int cmd_commit(int argc, const char **argv, const char *prefix)\n>  \t\tOPT_STRING(0, \"fixup\", &fixup_message, N_(\"commit\"), N_(\"use autosquash formatted message to fixup 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\", NULL, N_(\"trailer\"), N_(\"trailer(s) to add\"), PARSE_OPT_NONEG, opt_pass_trailer),\n\nI feel that a better option description could be offered? Maybe\nsomething like: 'add custom trailer(s)'.\n\n\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\nI have not yet gone through the the V2-V8s but I have a comment not\nassociated with the contents of the patch. I feel that you should wait a\nlittle before posting a new version of the patch. I see that V4-V8 are\nput up in almost 3 hour gaps. This isn't technically wrong or prohibited\nby the communication rules of the List but I feel that posting a patch\nin such short intervals makes it hard to review and unnecessarily\nincreases the versions of the patch. The reviewer lags behind the patch\nseries in fact.\n\nWhat you could do instead is post one patch per day instead of 3-4 in\none single day so that your patches get thorough reviews. This way, you\nwon't create 3-4 new versions of the patch containing not-so-many\nsignificant changes. You get me?\n\nAlso, in your reply on the V1 here:\nhttps://lore.kernel.org/git/CAOLTT8SpAOj51jqYUYqYwXaVKSn1fANvetauaG0z4etiBMzVEw@mail.gmail.com/\n\nI read:\n\n> It's exactly what you said.\n> My lack of English sometimes limits my expression.\n\nIt is okay please do not worry. Neither do we have English as our first\nlanguage nor have we ever communicated this much with an English\nspeaking audience online. I struggled initially too especially with many\nAmerican terms used here. You will get the hang of it soon.\n\nKeep contributing!\n\nRegards,\nShourya Shukla\n\n"},{"id":"419446","messageId":"CAOLTT8RaGiMudqn7ezV_fJUg01pZZUPyx9xSaFwSzX255MbpSw@mail.gmail.com","threadId":"55288","inReplyTo":"20210317052641.GA8212@konoha","subject":"Re: [PATCH v9] [GSOC] commit: add --trailer option","fromName":"ZheNing Hu","fromEmail":"adlternative@gmail.com","sentAt":"2021-03-17T06:06:01Z","receivedAt":"2021-03-17T06:07:05Z","isPatch":true,"sender":{"key":"adlternative@gmail.com","avatar":"https://avatars.githubusercontent.com/u/58138461?v=4"},"body":"Shourya Shukla <periperidip@gmail.com> 于2021年3月17日周三 下午1:26写道：\n>\n> On 16/03 10:39, ZheNing Hu via GitGitGadget wrote:\n>\n> Hey ZheNing!\n>\n>\n> I have been away for a while and directly seeing a V9 of this patch\n> feels great! Its good that you have worked upon the patch. The above\n> approach seems good to me!\n>\nHi! :-)\n> >       /*\n> >        * Reject an attempt to record a non-merge empty commit without\n> >        * explicit --allow-empty. In the cherry-pick case, it may be\n> > @@ -1507,6 +1528,7 @@ int cmd_commit(int argc, const char **argv, const char *prefix)\n> >               OPT_STRING(0, \"fixup\", &fixup_message, N_(\"commit\"), N_(\"use autosquash formatted message to fixup 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\", NULL, N_(\"trailer\"), N_(\"trailer(s) to add\"), PARSE_OPT_NONEG, opt_pass_trailer),\n>\n> I feel that a better option description could be offered? Maybe\n> something like: 'add custom trailer(s)'.\n>\n\nFeel ok.\n\n>\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> I have not yet gone through the the V2-V8s but I have a comment not\n> associated with the contents of the patch. I feel that you should wait a\n> little before posting a new version of the patch. I see that V4-V8 are\n> put up in almost 3 hour gaps. This isn't technically wrong or prohibited\n> by the communication rules of the List but I feel that posting a patch\n> in such short intervals makes it hard to review and unnecessarily\n> increases the versions of the patch. The reviewer lags behind the patch\n> series in fact.\n>\n> What you could do instead is post one patch per day instead of 3-4 in\n> one single day so that your patches get thorough reviews. This way, you\n> won't create 3-4 new versions of the patch containing not-so-many\n> significant changes. You get me?\n>\n\nGet it. Especially seeing Ævar Arnfjörð Bjarmason give me comments\nbehind the new iterations I posted, I knew that I might be sending new\nversions too frequently.\n\n> Also, in your reply on the V1 here:\n> https://lore.kernel.org/git/CAOLTT8SpAOj51jqYUYqYwXaVKSn1fANvetauaG0z4etiBMzVEw@mail.gmail.com/\n>\n> I read:\n>\n> > It's exactly what you said.\n> > My lack of English sometimes limits my expression.\n>\n> It is okay please do not worry. Neither do we have English as our first\n> language nor have we ever communicated this much with an English\n> speaking audience online. I struggled initially too especially with many\n> American terms used here. You will get the hang of it soon.\n>\n> Keep contributing!\n>\n\nThank you for your encouragement. Many people come to discuss and\nshare some ideas in English with me every day. It is a cool thing in itself.\n\n> Regards,\n> Shourya Shukla\n>\n\nRegards,\nZheNing Hu\n"},{"id":"419448","messageId":"87wnu6dwxx.fsf@evledraar.gmail.com","threadId":"55288","inReplyTo":"CAOLTT8RyCvs8bbedPaRSo44o566Tk1MK9BeLx=-APnFsHJtejw@mail.gmail.com","subject":"Re: [PATCH v8 1/2] [GSOC] commit: add --trailer option","fromName":"Ævar Arnfjörð Bjarmason","fromEmail":"avarab@gmail.com","sentAt":"2021-03-17T08:08:26Z","receivedAt":"2021-03-17T08:09:03Z","isPatch":true,"sender":{"key":"avarab@gmail.com","avatar":"https://avatars.githubusercontent.com/u/45301?v=4"},"body":"\nOn Wed, Mar 17 2021, ZheNing Hu wrote:\n\n> Ævar Arnfjörð Bjarmason <avarab@gmail.com> 于2021年3月16日周二 下午8:52写道：\n>> > +             if (run_command(&run_trailer))\n>> > +                     strvec_clear(&run_trailer.args);\n>>\n>> This is git-commit, shouldn't we die() here instead of ignoring errors\n>> in sub-processes?\n>\n> After thinking about it carefully, your opinion is more\n> reasonable, because if the user uses the wrong `--trailer`\n> and does not get the information he needs, I think he will\n> have to use `--amend` to modify, and `die()` can exit\n> this commit directly.\n\nYeah, we don't want to silently lose data.\n\n>>\n>> > +             strvec_clear(&trailer_args);\n>> > +     }\n>> > +\n>> >       /*\n>> >        * Reject an attempt to record a non-merge empty commit without\n>> >        * explicit --allow-empty. In the cherry-pick case, it may be\n>> > @@ -1507,6 +1529,7 @@ int cmd_commit(int argc, const char **argv, const char *prefix)\n>> >               OPT_STRING(0, \"fixup\", &fixup_message, N_(\"commit\"), N_(\"use autosquash formatted message to fixup 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\", NULL, N_(\"trailer\"), N_(\"trailer(s) to add\"), PARSE_OPT_NONEG, opt_pass_trailer),\n>> >               OPT_BOOL('s', \"signoff\", &signoff, N_(\"add a Signed-off-by trailer\")),\n>>\n>> Not required for this change, but perhaps a change here to N_() (if we\n>> can get it to fit) + doc update saying that we prefer\n>> --trailer=\"Signed-Off-By: to --signoff\"? More on that later.\n>>\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>> > diff --git a/t/t7502-commit-porcelain.sh b/t/t7502-commit-porcelain.sh\n>> > index 6396897cc818..0acf23799931 100755\n>> > --- a/t/t7502-commit-porcelain.sh\n>> > +++ b/t/t7502-commit-porcelain.sh\n>> > @@ -154,6 +154,26 @@ test_expect_success 'sign off' '\n>> >\n>> >  '\n>> >\n>> > +test_expect_success 'trailer' '\n>> > +     >file1 &&\n>> > +     git add file1 &&\n>> > +     git commit -s --trailer \"Signed-off-by:C O Mitter1 <committer1@example.com>\" \\\n>> > +             --trailer \"Helped-by:C O Mitter2 <committer2@example.com>\"  \\\n>> > +             --trailer \"Reported-by:C O Mitter3 <committer3@example.com>\" \\\n>> > +             --trailer \"Mentored-by:C O Mitter4 <committer4@example.com>\" \\\n>> > +             -m \"hello\" &&\n>> > +     git cat-file commit HEAD >commit.msg &&\n>> > +     sed -e \"1,7d\" commit.msg >actual &&\n>> > +     cat >expected <<-\\EOF &&\n>> > +     Signed-off-by: C O Mitter <committer@example.com>\n>> > +     Signed-off-by: C O Mitter1 <committer1@example.com>\n>> > +     Helped-by: C O Mitter2 <committer2@example.com>\n>> > +     Reported-by: C O Mitter3 <committer3@example.com>\n>> > +     Mentored-by: C O Mitter4 <committer4@example.com>\n>> > +     EOF\n>> > +     test_cmp expected actual\n>> > +'\n>> > +\n>>\n>> How does this interact with cases where the user has configured\n>> \"trailer.separators\" to have a value that doesn't contain \":\"?  I\n>> haven't tested, but my reading of git-interpret-trailers(1) is that if\n>> you supplied \"=\" instead that case would just work:\n>>\n>>     By default only : is recognized as a trailer separator, except that\n>>     = is always accepted on the command line for compatibility with\n>>     other git commands.\n>>\n> But interpret_trailers interface allow us use \"=\" instead of other separators.\n>\n> I did a simple test and modified the configuration \"trailer.separators\"\n> and it still works. Now things are good here:\n>\n> $ git -c trailer.separators=\"@\" commit --trailer=\"Signed-off-by=C O <email>\"\n>\n> or\n>\n> $ git -c trailer.separators=\"@\" commit --trailer=\"Signed-off-by@C O <email>\"\n>\n> Both can work normally,\n>\n> --trailer=\"Signed-off-by@ C O <email>\"\n>\n> will output in the commit message.\n>\n>> I don't know if that does the right thing in the presence of\n>> --if-exists=add.\n>>\n>\n> Yesterday, Christian Couder and I had already discussed this issue:\n> Your idea is correct, I should not add \"--if-exists = add\",  this will destroy\n> the user's rights to configure by using `git -c trailer.if-exist`.\n>\n>> So it would be good to update these tests so you test:\n>>\n>>  * For the --if-exists=add case at all, there's no tests for it\n>>    now. I.e. add some trailers manually to the commit (via -F or\n>>    whatever) and then see if they get added to, replacet etc.\n>>\n>>  * Ditto but for the user having configured trailer.separators (see the\n>>    test_config helper for how to set config in a test). I.e. if it's \"=\"\n>>    does adding trailers work, how about if it's \"=\" on the CLI but the\n>>    config/commit message has \";\" instead of \":\" or something?\n>>\n>\n> As mentioned above, it works normally.\n>\n>>  * Hrm, actually I think tweaking \"-c trailer.ifexists\" won't work at\n>>    all, since the CLI switch would override it. I honestly don't know,\n>>    but why not not supply it and keep the addIfDifferentNeighbor\n>>    default?\n>>\n>>    If it's essential that seems like a good test / documentation\n>>    addition...\n>>\n>>  * For the above -c ... case I can't think of a good way to deal with it\n>>    that doesn't involve pulling in git_trailer_config() into\n>>    git_commit_config(), but perhaps the least nasty way is to just set a\n>>    flag in git_commit_config() if we see a \"trailer.ifexists\" flag, and\n>>    if so don't provide \"--if-exists=add\", if there's no config (this\n>>    will include \"git -c ... commit\" we set provide \"--if-exists=add\" )\n>>    or as noted above, maybe we can skip the whole thing and use the\n>>    addIfDifferentNeighbor default.\n>>\n>\n> Has been restored to the default settings.\n\nTo clarify: What I really mean is for all these things you've tested:\nlet's add those to the tests as part of the patch.\n\n>> And, not needed for this patch but worth thinking about:\n>>\n>>  * We pass through --trailer to git-interpret-trailers, what should we\n>>    do about the other options? Should git-commit eventually support\n>>    --trailer-where and pass it along as --where to\n>>    git-interpret-trailers, or is \"git -c trailer.where=... commit\" good\n>>    enough?\n>>\n> Logically speaking, `interpret_trailers` should be dedicated to `commit`\n> or other sub-commands that require trailers.\n>\n> But I think that in the later stage, the parse_options of the `cmd_commit`\n> can keep the unrecognized options, and then these choices can be directly\n> passed to the `interpret_trailers` backend.\n\nWe have this interaction with e.g. range-diff and \"log\", it's often\nsurprising. You add an option to one command and it appears in the\nother.\n\n>>  * It would be good to test for and document if that \"-c trailer.*\"\n>>    trick works (no reason it shouldn't). I.e. to add something like this\n>>    after what you have (along with tests, and check if it's even true):\n>>\n>\n> I haven't tested them for the time being, but I will do it.\n>\n>>        Only the `--trailer` argument to\n>>        linkgit:git-interpret-trailers[1] is supported. Other\n>>        pass-through switches may be added in the future, but currently\n>>        you'll need to pass arguments to\n>>        linkgit:git-interpret-trailers[1] along as config, e.g. `git -c\n>>        trailer.where=start commit [...] --trailer=[...]`.\n>>\n>\n> I think this is worth writing in the documentation.\n>\n>>  * We have a longer-term goal of having the .mailmap apply to trailers,\n>>    it would be nice if git-interpret-trailers had some fuzzy-matching to\n>>    check if the RHS of a trailer is a name/E-Mail pair, and if so did\n>>    stricter validation on it with the ident functions we use for fsck\n>>    etc. (that's copied & subtly different in several different places in\n>>    the codebase, unfortunately[1]).\n>>\n>\n> I may not know much about fuzzy-matching, which may be worth studying later.\n>\n>> More thoughts:\n>>\n>>  * Having written all the above I checked how --signoff is implemented.\n>>\n>>    It seems to me to be a good idea to (at least for testing) convert\n>>    the --signoff trailer to your implementation. We have plenty of tests\n>>    for it, does migrating it over pass or fail those?\n>>\n> I don’t know how to migrating yet, it may take a long time.\n> Even I think I can leave it as #leftoverbit later.\n\nSure, I mean (having looked at it) that at least for your own local\ntesting it would make sense to change it (even if just search-replacing\nthe --signoff in the test suite) to see if it behaves as you\nexpect. I.e. does the --trailer behavior mirror --signoff?\n\n>>  * I also agree with Junio that we shouldn't have a --fixed-by or\n>>    whatever and wouldn't add --signoff today, but it seems very useful\n>>    to me to have a shortcut like:\n>>\n>>        --trailer \"Signed-off-by\"\n>>\n>>    I.e. omitting the value, or:\n>>\n>>       --trailer \"Signed-off-by=\"\n>>\n>>    Or some other thing we deem sufficiently useful/sane\n>>    syntax/unambiguous.n\n>>\n>>    Then the value would be provided by fmt_name(WANT_COMMITTER_IDENT)\n>>    just as we do in append_signoff() now. I think a *very common* case\n>>    for this would be something like:\n>>\n>>        git commit --amend -v --trailer \"Reviewed-by\"\n>>\n>>    And it would be useful to help that along and not have to do:\n>>\n>>        git commit --amend -v --trailer \"Reviewed-by=$(git config user.name) <$(git config user.email)>\"\n>>\n>>    Or worse yet, manually typo your name/e-mail address, as I'm sure I\n>>    and many others will inevitably do when using this option...\n>>\n> I think this idea is very good and easy to implement.\n> We only need to do a simple string match when we get the \"trailer\" string,\n> If it can be completed, it can indeed bring great convenience to users.\n>\n>> 1. https://lore.kernel.org/git/87bld8ov9q.fsf@evledraar.gmail.com/\n>\n> Thanks, Ævar Arnfjörð Bjarmason!\n\nAnd thanks for working on this.\n"},{"id":"419471","messageId":"CAOLTT8SFd=Zg01kR9rvTO6yh+tSS5VbcLBjAcB+d_DX_NY_HOw@mail.gmail.com","threadId":"55288","inReplyTo":"87wnu6dwxx.fsf@evledraar.gmail.com","subject":"Re: [PATCH v8 1/2] [GSOC] commit: add --trailer option","fromName":"ZheNing Hu","fromEmail":"adlternative@gmail.com","sentAt":"2021-03-17T13:54:37Z","receivedAt":"2021-03-17T13:55:58Z","isPatch":true,"sender":{"key":"adlternative@gmail.com","avatar":"https://avatars.githubusercontent.com/u/58138461?v=4"},"body":"Ævar Arnfjörð Bjarmason <avarab@gmail.com> 于2021年3月17日周三 下午4:08写道：\n> > Logically speaking, `interpret_trailers` should be dedicated to `commit`\n> > or other sub-commands that require trailers.\n> >\n> > But I think that in the later stage, the parse_options of the `cmd_commit`\n> > can keep the unrecognized options, and then these choices can be directly\n> > passed to the `interpret_trailers` backend.\n>\n> We have this interaction with e.g. range-diff and \"log\", it's often\n> surprising. You add an option to one command and it appears in the\n> other.\n>\n\nAll right, I'm wrong, I may have reference to an wrong experience\nof `difftool`-->`diff`.\n\n> >>    It seems to me to be a good idea to (at least for testing) convert\n> >>    the --signoff trailer to your implementation. We have plenty of tests\n> >>    for it, does migrating it over pass or fail those?\n> >>\n> > I don’t know how to migrating yet, it may take a long time.\n> > Even I think I can leave it as #leftoverbit later.\n>\n> Sure, I mean (having looked at it) that at least for your own local\n> testing it would make sense to change it (even if just search-replacing\n> the --signoff in the test suite) to see if it behaves as you\n> expect. I.e. does the --trailer behavior mirror --signoff?\n>\n> >>  * I also agree with Junio that we shouldn't have a --fixed-by or\n> >>    whatever and wouldn't add --signoff today, but it seems very useful\n> >>    to me to have a shortcut like:\n> >>\n> >>        --trailer \"Signed-off-by\"\n> >>\n> >>    I.e. omitting the value, or:\n> >>\n> >>       --trailer \"Signed-off-by=\"\n> >>\n> >>    Or some other thing we deem sufficiently useful/sane\n> >>    syntax/unambiguous.n\n> >>\n> >>    Then the value would be provided by fmt_name(WANT_COMMITTER_IDENT)\n> >>    just as we do in append_signoff() now. I think a *very common* case\n> >>    for this would be something like:\n> >>\n> >>        git commit --amend -v --trailer \"Reviewed-by\"\n> >>\n> >>    And it would be useful to help that along and not have to do:\n> >>\n> >>        git commit --amend -v --trailer \"Reviewed-by=$(git config user.name) <$(git config user.email)>\"\n> >>\n> >>    Or worse yet, manually typo your name/e-mail address, as I'm sure I\n> >>    and many others will inevitably do when using this option...\n> >>\n\nWell, that's what I think here:\n\nNow we can go through:\n\n$ git -c trailer.signoff.key = \"Signed-off-by\" commit --trailer\n\"signoff = commiter <email>\"\n\nto get a trailer: \"Signed-off-by: commiter <email>\", this means we\ncan't just do simple string\nmatching in `cmd_commit` to replace `--trailer=\"Signed-off-by\"` or\n`--trailer=\"Reviewed-by\"` to\nuser's own identity, to replace the trailers which have omitting value\n we passed in, but I think\nwe can provide a new option to `commit` which can mandatory that\ntrailers with no value can be\n replaced with the identity of the user.\n\ne.g.\n\n$ git -c trailer.signoff.key = \"Signed-off-by\" commit --trailer\n\"signoff\" --trailer \"Helped-by\" \\\n --trailer \"Helped-by = C <E>\" --own_ident\n\nwill output like this:\n\nSigned-off-by: $(git config user.name) <$(git config user.email)>\nSigned-off-by: $(git config user.name) <$(git config user.email)>\nHelped-by: $(git config user.name) <$(git config user.email)>\nHelped-by: C <E>\n\nI don't know if this idea is good, I will try to do it first.\n\nThanks.\n"},{"id":"419627","messageId":"949faf9ee56a3146a643ba634771779efa89bbc5.1616066156.git.gitgitgadget@gmail.com","threadId":"55288","inReplyTo":"pull.901.v10.git.1616066156.gitgitgadget@gmail.com","subject":"[PATCH v10 1/3] [GSOC] commit: add --trailer option","fromName":"ZheNing Hu via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2021-03-18T11:15:54Z","receivedAt":"2021-03-18T11:16:42Z","isPatch":true,"sender":{"key":"adlternative@gmail.com","avatar":"https://avatars.githubusercontent.com/u/58138461?v=4"},"body":"From: ZheNing Hu <adlternative@gmail.com>\n\nHistorically, Git has supported the 'Signed-off-by' commit trailer\nusing the '--signoff' and the '-s' option from the command line.\nBut users may need to provide other trailer information from the\ncommand line such as \"Helped-by\", \"Reported-by\", \"Mentored-by\",\n\nNow implement a new `--trailer <token>[(=|:)<value>]` option to pass\nother trailers to `interpret-trailers` and insert them into commit\nmessages.\n\nSigned-off-by: ZheNing Hu <adlternative@gmail.com>\n---\n Documentation/git-commit.txt |  10 +-\n builtin/commit.c             |  23 +++\n t/t7502-commit-porcelain.sh  | 336 +++++++++++++++++++++++++++++++++++\n 3 files changed, 368 insertions(+), 1 deletion(-)\n\ndiff --git a/Documentation/git-commit.txt b/Documentation/git-commit.txt\nindex 17150fa7eabe..c5de981cd40d 100644\n--- a/Documentation/git-commit.txt\n+++ b/Documentation/git-commit.txt\n@@ -14,7 +14,7 @@ SYNOPSIS\n \t   [--allow-empty-message] [--no-verify] [-e] [--author=<author>]\n \t   [--date=<date>] [--cleanup=<mode>] [--[no-]status]\n \t   [-i | -o] [--pathspec-from-file=<file> [--pathspec-file-nul]]\n-\t   [-S[<keyid>]] [--] [<pathspec>...]\n+\t   [-S[<keyid>]] [--] [<pathspec>...] [(--trailer <token>[(=|:)<value>])...]\n \n DESCRIPTION\n -----------\n@@ -166,6 +166,14 @@ The `-m` option is mutually exclusive with `-c`, `-C`, and `-F`.\n \n include::signoff-option.txt[]\n \n+--trailer <token>[(=|:)<value>]::\n+\tSpecify a (<token>, <value>) pair that should be applied as a\n+\ttrailer. (e.g. `git commit --trailer \"Signed-off-by:C O Mitter \\\n+\t<committer@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 in the commit message.)\n+\tUse `git -c trailer.* commit --trailer` to make the appropriate\n+\tconfiguration. See linkgit:git-interpret-trailers[1] for details.\n -n::\n --no-verify::\n \tThis option bypasses the pre-commit and commit-msg hooks.\ndiff --git a/builtin/commit.c b/builtin/commit.c\nindex 739110c5a7f6..7a79aae48f43 100644\n--- a/builtin/commit.c\n+++ b/builtin/commit.c\n@@ -113,6 +113,7 @@ static int config_commit_verbose = -1; /* unspecified */\n static int no_post_rewrite, allow_empty_message, pathspec_file_nul;\n static char *untracked_files_arg, *force_date, *ignore_submodule_arg, *ignored_arg;\n static char *sign_commit, *pathspec_from_file;\n+static struct strvec trailer_args = STRVEC_INIT;\n \n /*\n  * The default commit message cleanup mode will remove the lines\n@@ -131,6 +132,14 @@ 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(&trailer_args, \"--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@@ -958,6 +967,19 @@ static int prepare_to_commit(const char *index_file, const char *prefix,\n \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\", 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\t\tdie(_(\"unable to pass tailers to --trailers\"));\n+\t\t}\n+\t\tstrvec_clear(&trailer_args);\n+\t}\n+\n \t/*\n \t * Reject an attempt to record a non-merge empty commit without\n \t * explicit --allow-empty. In the cherry-pick case, it may be\n@@ -1507,6 +1529,7 @@ int cmd_commit(int argc, const char **argv, const char *prefix)\n \t\tOPT_STRING(0, \"fixup\", &fixup_message, N_(\"commit\"), N_(\"use autosquash formatted message to fixup 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\", NULL, N_(\"trailer\"), N_(\"add custom trailer(s)\"), PARSE_OPT_NONEG, opt_pass_trailer),\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/t/t7502-commit-porcelain.sh b/t/t7502-commit-porcelain.sh\nindex 6396897cc818..6df71fa00bcb 100755\n--- a/t/t7502-commit-porcelain.sh\n+++ b/t/t7502-commit-porcelain.sh\n@@ -154,6 +154,342 @@ test_expect_success 'sign off' '\n \n '\n \n+test_expect_success 'commit --trailer without -c' '\n+\techo \"fun\" >>file &&\n+\tgit add file &&\n+\tcat >expected <<-\\EOF &&\n+\n+\tSigned-off-by: C O Mitter <committer@example.com>\n+\tSigned-off-by: C1 E1\n+\tHelped-by: C2 E2\n+\tReported-by: C3 E3\n+\tMentored-by: C4 E4\n+\tEOF\n+\tgit commit -s --trailer \"Signed-off-by:C1 E1 \" \\\n+\t\t--trailer \"Helped-by:C2 E2 \" \\\n+\t\t--trailer \"Reported-by:C3 E3\" \\\n+\t\t--trailer \"Mentored-by:C4 E4\" \\\n+\t\t-m \"hello\" &&\n+\tgit cat-file commit HEAD >commit.msg &&\n+\tsed -e \"1,6d\" commit.msg >actual &&\n+\ttest_cmp expected actual\n+'\n+\n+test_expect_success 'commit --trailer with -c and \"replace\" as ifexists' '\n+\techo \"fun\" >>file1 &&\n+\tgit add file1 &&\n+\tcat >expected <<-\\EOF &&\n+\n+\tSigned-off-by: C O Mitter <committer@example.com>\n+\tSigned-off-by: C1 E1\n+\tReported-by: C3 E3\n+\tMentored-by: C4 E4\n+\tHelped-by: C3 E3\n+\tEOF\n+\tgit -c trailer.ifexists=\"replace\" \\\n+\t\tcommit --trailer \"Mentored-by: C4 E4\" \\\n+\t\t --trailer \"Helped-by: C3 E3\" \\\n+\t\t--amend &&\n+\tgit cat-file commit HEAD >commit.msg &&\n+\tsed -e \"1,6d\" commit.msg >actual &&\n+\ttest_cmp expected actual\n+'\n+\n+test_expect_success 'commit --trailer with -c and \"add\" as ifexists' '\n+\techo \"fun\" >>file1 &&\n+\tgit add file1 &&\n+\tcat >expected <<-\\EOF &&\n+\n+\tSigned-off-by: C O Mitter <committer@example.com>\n+\tSigned-off-by: C1 E1\n+\tReported-by: C3 E3\n+\tMentored-by: C4 E4\n+\tHelped-by: C3 E3\n+\tHelped-by: C3 E3\n+\tHelped-by: C3 E3\n+\tEOF\n+\tgit -c trailer.ifexists=\"add\" \\\n+\t\tcommit --trailer \"Helped-by: C3 E3\" \\\n+\t\t--trailer \"Helped-by: C3 E3\" \\\n+\t\t--amend &&\n+\tgit cat-file commit HEAD >commit.msg &&\n+\tsed -e \"1,6d\" commit.msg >actual &&\n+\ttest_cmp expected actual\n+'\n+\n+test_expect_success 'commit --trailer with -c and \"donothing\" as ifexists' '\n+\techo \"fun\" >>file1 &&\n+\tgit add file1 &&\n+\tcat >expected <<-\\EOF &&\n+\n+\tSigned-off-by: C O Mitter <committer@example.com>\n+\tSigned-off-by: C1 E1\n+\tReported-by: C3 E3\n+\tMentored-by: C4 E4\n+\tHelped-by: C3 E3\n+\tHelped-by: C3 E3\n+\tHelped-by: C3 E3\n+\tReviewed-by: C6 E6\n+\tEOF\n+\tgit -c trailer.ifexists=\"donothing\" \\\n+\t\tcommit --trailer \"Mentored-by: C5 E5\" \\\n+\t\t--trailer \"Reviewed-by: C6 E6\" \\\n+\t\t--amend &&\n+\tgit cat-file commit HEAD >commit.msg &&\n+\tsed -e \"1,6d\" commit.msg >actual &&\n+\ttest_cmp expected actual\n+'\n+\n+test_expect_success 'commit --trailer with -c and \"addIfDifferent\" as ifexists' '\n+\techo \"fun\" >>file1 &&\n+\tgit add file1 &&\n+\tcat >expected <<-\\EOF &&\n+\n+\tSigned-off-by: C O Mitter <committer@example.com>\n+\tSigned-off-by: C1 E1\n+\tReported-by: C3 E3\n+\tMentored-by: C4 E4\n+\tHelped-by: C3 E3\n+\tHelped-by: C3 E3\n+\tHelped-by: C3 E3\n+\tReviewed-by: C6 E6\n+\tReported-by: C5 E5\n+\tEOF\n+\tgit -c trailer.ifexists=\"addIfDifferent\" \\\n+\t\tcommit --trailer \"Reviewed-by: C6 E6\" \\\n+\t\t--trailer \"Reported-by: C5 E5\" \\\n+\t\t--amend &&\n+\tgit cat-file commit HEAD >commit.msg &&\n+\tsed -e \"1,6d\" commit.msg >actual &&\n+\ttest_cmp expected actual\n+'\n+\n+test_expect_success 'commit --trailer with -c and \"addIfDifferentNeighbor\" as ifexists' '\n+\techo \"fun\" >>file1 &&\n+\tgit add file1 &&\n+\tcat >expected <<-\\EOF &&\n+\n+\tSigned-off-by: C O Mitter <committer@example.com>\n+\tSigned-off-by: C1 E1\n+\tReported-by: C3 E3\n+\tMentored-by: C4 E4\n+\tHelped-by: C3 E3\n+\tHelped-by: C3 E3\n+\tHelped-by: C3 E3\n+\tReviewed-by: C6 E6\n+\tReported-by: C5 E5\n+\tEOF\n+\tgit -c trailer.ifexists=\"addIfDifferent\" \\\n+\t\tcommit --trailer \"Reported-by: C5 E5\" \\\n+\t\t--trailer \"Reviewed-by: C6 E6\" \\\n+\t\t--amend &&\n+\tgit cat-file commit HEAD >commit.msg &&\n+\tsed -e \"1,6d\" commit.msg >actual &&\n+\ttest_cmp expected actual\n+'\n+\n+test_expect_success 'commit --trailer with -c and \"end\" as where' '\n+\techo \"fun\" >>file1 &&\n+\tgit add file1 &&\n+\tcat >expected <<-\\EOF &&\n+\n+\tSigned-off-by: C O Mitter <committer@example.com>\n+\tSigned-off-by: C1 E1\n+\tReported-by: C3 E3\n+\tMentored-by: C4 E4\n+\tHelped-by: C3 E3\n+\tHelped-by: C3 E3\n+\tHelped-by: C3 E3\n+\tReviewed-by: C6 E6\n+\tReported-by: C5 E5\n+\tReported-by: C7 E7\n+\tEOF\n+\tgit -c trailer.where=\"end\" \\\n+\t\tcommit --trailer \"Reported-by: C5 E5\" \\\n+\t\t--trailer \"Reported-by: C7 E7\" \\\n+\t\t--amend &&\n+\tgit cat-file commit HEAD >commit.msg &&\n+\tsed -e \"1,6d\" commit.msg >actual &&\n+\ttest_cmp expected actual\n+'\n+\n+test_expect_success 'commit --trailer with -c and \"start\" as where' '\n+\techo \"fun\" >>file1 &&\n+\tgit add file1 &&\n+\tcat >expected <<-\\EOF &&\n+\n+\tSigned-off-by: C8 E8\n+\tSigned-off-by: C O Mitter <committer@example.com>\n+\tSigned-off-by: C1 E1\n+\tReported-by: C3 E3\n+\tMentored-by: C4 E4\n+\tHelped-by: C3 E3\n+\tHelped-by: C3 E3\n+\tHelped-by: C3 E3\n+\tReviewed-by: C6 E6\n+\tReported-by: C5 E5\n+\tReported-by: C7 E7\n+\tEOF\n+\tgit -c trailer.where=\"start\" \\\n+\t\tcommit --trailer \"Signed-off-by: C8 E8\" \\\n+\t\t--trailer \"Signed-off-by: C8 E8\" \\\n+\t\t--amend &&\n+\tgit cat-file commit HEAD >commit.msg &&\n+\tsed -e \"1,6d\" commit.msg >actual &&\n+\ttest_cmp expected actual\n+'\n+\n+test_expect_success 'commit --trailer with -c and \"after\" as where' '\n+\techo \"fun\" >>file1 &&\n+\tgit add file1 &&\n+\tcat >expected <<-\\EOF &&\n+\n+\tSigned-off-by: C8 E8\n+\tSigned-off-by: C O Mitter <committer@example.com>\n+\tSigned-off-by: C1 E1\n+\tReported-by: C3 E3\n+\tMentored-by: C4 E4\n+\tMentored-by: C9 E9\n+\tHelped-by: C3 E3\n+\tHelped-by: C3 E3\n+\tHelped-by: C3 E3\n+\tReviewed-by: C6 E6\n+\tReported-by: C5 E5\n+\tReported-by: C7 E7\n+\tReported-by: C10 E10\n+\tEOF\n+\tgit -c trailer.where=\"after\" \\\n+\t\tcommit --trailer \"Mentored-by: C9 E9\" \\\n+\t\t--trailer \"Reported-by: C10 E10\" \\\n+\t\t--amend &&\n+\tgit cat-file commit HEAD >commit.msg &&\n+\tsed -e \"1,6d\" commit.msg >actual &&\n+\ttest_cmp expected actual\n+'\n+\n+test_expect_success 'commit --trailer with -c and \"before\" as where' '\n+\techo \"fun\" >>file1 &&\n+\tgit add file1 &&\n+\tcat >expected <<-\\EOF &&\n+\n+\tSigned-off-by: C8 E8\n+\tSigned-off-by: C O Mitter <committer@example.com>\n+\tSigned-off-by: C1 E1\n+\tReported-by: C11 E11\n+\tReported-by: C3 E3\n+\tMentored-by: C4 E4\n+\tMentored-by: C9 E9\n+\tHelped-by: C12 E12\n+\tHelped-by: C3 E3\n+\tHelped-by: C3 E3\n+\tHelped-by: C3 E3\n+\tReviewed-by: C6 E6\n+\tReported-by: C5 E5\n+\tReported-by: C7 E7\n+\tReported-by: C10 E10\n+\tEOF\n+\tgit -c trailer.where=\"before\" \\\n+\t\tcommit --trailer \"Helped-by: C12 E12\" \\\n+\t\t--trailer \"Reported-by: C11 E11\" \\\n+\t\t--amend &&\n+\tgit cat-file commit HEAD >commit.msg &&\n+\tsed -e \"1,6d\" commit.msg >actual &&\n+\ttest_cmp expected actual\n+'\n+\n+test_expect_success 'commit --trailer with -c and \"donothing\" as ifmissing' '\n+\techo \"fun\" >>file1 &&\n+\tgit add file1 &&\n+\tcat >expected <<-\\EOF &&\n+\n+\tSigned-off-by: C8 E8\n+\tSigned-off-by: C O Mitter <committer@example.com>\n+\tSigned-off-by: C1 E1\n+\tReported-by: C11 E11\n+\tReported-by: C3 E3\n+\tMentored-by: C4 E4\n+\tMentored-by: C9 E9\n+\tHelped-by: C12 E12\n+\tHelped-by: C3 E3\n+\tHelped-by: C3 E3\n+\tHelped-by: C3 E3\n+\tReviewed-by: C6 E6\n+\tReported-by: C5 E5\n+\tReported-by: C7 E7\n+\tReported-by: C10 E10\n+\tHelped-by: C12 E12\n+\tEOF\n+\tgit -c trailer.ifmissing=\"donothing\" \\\n+\t\tcommit --trailer \"Helped-by: C12 E12\" \\\n+\t\t--trailer \"Based-by: C13 E13\" \\\n+\t\t--amend &&\n+\tgit cat-file commit HEAD >commit.msg &&\n+\tsed -e \"1,6d\" commit.msg >actual &&\n+\ttest_cmp expected actual\n+'\n+\n+test_expect_success 'commit --trailer with -c and \"add\" as ifmissing' '\n+\techo \"fun\" >>file1 &&\n+\tgit add file1 &&\n+\tcat >expected <<-\\EOF &&\n+\n+\tSigned-off-by: C8 E8\n+\tSigned-off-by: C O Mitter <committer@example.com>\n+\tSigned-off-by: C1 E1\n+\tReported-by: C11 E11\n+\tReported-by: C3 E3\n+\tMentored-by: C4 E4\n+\tMentored-by: C9 E9\n+\tHelped-by: C12 E12\n+\tHelped-by: C3 E3\n+\tHelped-by: C3 E3\n+\tHelped-by: C3 E3\n+\tReviewed-by: C6 E6\n+\tReported-by: C5 E5\n+\tReported-by: C7 E7\n+\tReported-by: C10 E10\n+\tHelped-by: C12 E12\n+\tBased-by: C13 E13\n+\tEOF\n+\tgit -c trailer.ifmissing=\"add\" \\\n+\t\tcommit --trailer \"Helped-by: C12 E12\" \\\n+\t\t--trailer \"Based-by: C13 E13\" \\\n+\t\t--amend &&\n+\tgit cat-file commit HEAD >commit.msg &&\n+\tsed -e \"1,6d\" commit.msg >actual &&\n+\ttest_cmp expected actual\n+'\n+\n+test_expect_success 'commit --trailer with -c and \"=\" as separators' '\n+\techo \"fun\" >>file1 &&\n+\tgit add file1 &&\n+\tcat >expected <<-\\EOF &&\n+\n+\t\tAcked-by= Peff\n+\tEOF\n+\tgit -c trailer.separators=\"=\" \\\n+\t\t-c trailer.ack.key=\"Acked-by= \" \\\n+\t\tcommit --trailer \"ack = Peff\" -m \"hello\" &&\n+\tgit cat-file commit HEAD >commit.msg &&\n+\tsed -e \"1,6d\" commit.msg >actual &&\n+\ttest_cmp expected actual\n+'\n+\n+test_expect_success 'commit --trailer with -c and \":=#\" as separators' '\n+\techo \"fun\" >>file1 &&\n+\tgit add file1 &&\n+\tcat >expected <<-\\EOF &&\n+\n+\t\tBug #42\n+\tEOF\n+\tgit -c trailer.separators=\":=#\" \\\n+\t\t-c trailer.bug.key=\"Bug #\" \\\n+\t\tcommit --trailer \"bug = 42\" -m \"I hate bug\" &&\n+\tgit cat-file commit HEAD >commit.msg &&\n+\tsed -e \"1,6d\" commit.msg >actual &&\n+\ttest_cmp expected actual\n+'\n+\n test_expect_success 'multiple -m' '\n \n \t>negative &&\n-- \ngitgitgadget\n\n"},{"id":"419628","messageId":"42590e95deeece6ba65e0432c3a59746e717fee3.1616066156.git.gitgitgadget@gmail.com","threadId":"55288","inReplyTo":"pull.901.v10.git.1616066156.gitgitgadget@gmail.com","subject":"[PATCH v10 2/3] interpret-trailers: add own-identity option","fromName":"ZheNing Hu via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2021-03-18T11:15:55Z","receivedAt":"2021-03-18T11:16:43Z","isPatch":true,"sender":{"key":"adlternative@gmail.com","avatar":"https://avatars.githubusercontent.com/u/58138461?v=4"},"body":"From: ZheNing Hu <adlternative@gmail.com>\n\nBeacuse `git commit --trailer=\"Signed-off-by: \\\n$(git config user.name) <$(git config user.email)>\"`\nis difficult for users to add their own identities,\nso teach interpret-trailers a new option `--own-identity`\nwhich allow those trailers with no value add the user’s own\nidentity. This will help the use of `commit --trailer` as\neasy as `--signoff`.\n\nSigned-off-by: ZheNing Hu <adlternative@gmail.com>\n---\n Documentation/git-interpret-trailers.txt | 14 ++++++++++++++\n builtin/interpret-trailers.c             |  6 ++++--\n t/t7513-interpret-trailers.sh            | 12 ++++++++++++\n trailer.c                                | 18 ++++++++++++++++--\n trailer.h                                |  3 ++-\n 5 files changed, 48 insertions(+), 5 deletions(-)\n\ndiff --git a/Documentation/git-interpret-trailers.txt b/Documentation/git-interpret-trailers.txt\nindex 96ec6499f001..ace44446c3e3 100644\n--- a/Documentation/git-interpret-trailers.txt\n+++ b/Documentation/git-interpret-trailers.txt\n@@ -84,6 +84,19 @@ OPTIONS\n \ttrailer to the input messages. See the description of this\n \tcommand.\n \n+--own-identity::\n+\tUsed with `--trailer`. Those trailers without value with the\n+\t`--own-identity` option all will add the user's own identity.\n+\tFor example,` git interpret-trailers --trailer \"A:B\" --trailer \\\n+\t\"Signed-off-by\" --trailer \"Helped-by\" --own-identity --inplace a.txt`\n+\twill output:\n+\t\"\n+\tA:B\n+\tSigned-off-by: C O Mitter <committer@example.com>\n+\tHelped-by: C O Mitter <committer@example.com>\n+\t\"\n+\tin `a.txt`.\n+\n --where <placement>::\n --no-where::\n \tSpecify where all new trailers will be added.  A setting\n@@ -131,6 +144,7 @@ OPTIONS\n \twhen you know your input contains just the commit message itself\n \t(and not an email or the output of `git format-patch`).\n \n+\n CONFIGURATION VARIABLES\n -----------------------\n \ndiff --git a/builtin/interpret-trailers.c b/builtin/interpret-trailers.c\nindex 84748eafc01b..be7f502a58d7 100644\n--- a/builtin/interpret-trailers.c\n+++ b/builtin/interpret-trailers.c\n@@ -47,6 +47,7 @@ static void new_trailers_clear(struct list_head *trailers)\n \tlist_for_each_safe(pos, tmp, trailers) {\n \t\titem = list_entry(pos, struct new_trailer_item, list);\n \t\tlist_del(pos);\n+\t\tfree(item->text);\n \t\tfree(item);\n \t}\n }\n@@ -66,7 +67,7 @@ static int option_parse_trailer(const struct option *opt,\n \t\treturn -1;\n \n \titem = xmalloc(sizeof(*item));\n-\titem->text = arg;\n+\titem->text = xstrdup(arg);\n \titem->where = where;\n \titem->if_exists = if_exists;\n \titem->if_missing = if_missing;\n@@ -94,7 +95,8 @@ int cmd_interpret_trailers(int argc, const char **argv, const char *prefix)\n \tstruct option options[] = {\n \t\tOPT_BOOL(0, \"in-place\", &opts.in_place, N_(\"edit files in place\")),\n \t\tOPT_BOOL(0, \"trim-empty\", &opts.trim_empty, N_(\"trim empty trailers\")),\n-\n+\t\tOPT_BOOL(0, \"own-identity\", &opts.own_identity,\n+\t\t\t     N_(\"specify the user's own identity for omitted trailers value\")),\n \t\tOPT_CALLBACK(0, \"where\", NULL, N_(\"action\"),\n \t\t\t     N_(\"where to place the new trailer\"), option_parse_where),\n \t\tOPT_CALLBACK(0, \"if-exists\", NULL, N_(\"action\"),\ndiff --git a/t/t7513-interpret-trailers.sh b/t/t7513-interpret-trailers.sh\nindex 6602790b5f4c..f82cee93bfb2 100755\n--- a/t/t7513-interpret-trailers.sh\n+++ b/t/t7513-interpret-trailers.sh\n@@ -63,6 +63,18 @@ test_expect_success 'without config' '\n \ttest_cmp expected actual\n '\n \n+test_expect_success 'without config with --own-identity' '\n+\tcat >expected <<-\\EOF &&\n+\n+\tAcked-by: A B <C>\n+\tHelped-by: C O Mitter <committer@example.com>\n+\tSigned-off-by: C O Mitter <committer@example.com>\n+\tEOF\n+\tgit interpret-trailers --trailer \"Acked-by: A B <C>\" --trailer \"Helped-by\" \\\n+\t--trailer \"Signed-off-by\" --own-identity empty >actual &&\n+\ttest_cmp expected actual\n+'\n+\n test_expect_success 'without config in another order' '\n \tsed -e \"s/ Z\\$/ /\" >expected <<-\\EOF &&\n \ndiff --git a/trailer.c b/trailer.c\nindex 249ed618ed8e..cd4f85e71c9a 100644\n--- a/trailer.c\n+++ b/trailer.c\n@@ -690,8 +690,18 @@ static void add_arg_item(struct list_head *arg_head, char *tok, char *val,\n \tlist_add_tail(&new_item->list, arg_head);\n }\n \n+static void add_user_own_identity(struct new_trailer_item *item)\n+{\n+\tstruct strbuf buf = STRBUF_INIT;\n+\tstrbuf_addstr(&buf, item->text);\n+\tstrbuf_add(&buf, \"=\", 1);\n+\tstrbuf_addstr(&buf, fmt_name(WANT_COMMITTER_IDENT));\n+\tfree(item->text);\n+\titem->text = buf.buf;\n+}\n+\n static void process_command_line_args(struct list_head *arg_head,\n-\t\t\t\t      struct list_head *new_trailer_head)\n+\t\t\t\t      struct list_head *new_trailer_head, int own_identity)\n {\n \tstruct arg_item *item;\n \tstruct strbuf tok = STRBUF_INIT;\n@@ -728,6 +738,10 @@ static void process_command_line_args(struct list_head *arg_head,\n \t\t\terror(_(\"empty trailer token in trailer '%.*s'\"),\n \t\t\t      (int) sb.len, sb.buf);\n \t\t\tstrbuf_release(&sb);\n+\t\t} else if (separator_pos == -1 && own_identity) {\n+\t\t\t\tadd_user_own_identity(tr);\n+\t\t\t\tpos = pos->prev;\n+\t\t\t\tcontinue;\n \t\t} else {\n \t\t\tparse_trailer(&tok, &val, &conf, tr->text,\n \t\t\t\t      separator_pos);\n@@ -1048,7 +1062,7 @@ void process_trailers(const char *file,\n \n \tif (!opts->only_input) {\n \t\tLIST_HEAD(arg_head);\n-\t\tprocess_command_line_args(&arg_head, new_trailer_head);\n+\t\tprocess_command_line_args(&arg_head, new_trailer_head ,opts->own_identity);\n \t\tprocess_trailers_lists(&head, &arg_head);\n \t}\n \ndiff --git a/trailer.h b/trailer.h\nindex 795d2fccfd95..235dfdfa1978 100644\n--- a/trailer.h\n+++ b/trailer.h\n@@ -57,7 +57,7 @@ struct trailer_info {\n struct new_trailer_item {\n \tstruct list_head list;\n \n-\tconst char *text;\n+\tchar *text;\n \n \tenum trailer_where where;\n \tenum trailer_if_exists if_exists;\n@@ -73,6 +73,7 @@ struct process_trailer_options {\n \tint no_divider;\n \tint key_only;\n \tint value_only;\n+\tint own_identity;\n \tconst struct strbuf *separator;\n \tconst struct strbuf *key_value_separator;\n \tint (*filter)(const struct strbuf *, void *);\n-- \ngitgitgadget\n\n"},{"id":"419629","messageId":"pull.901.v10.git.1616066156.gitgitgadget@gmail.com","threadId":"55288","inReplyTo":"pull.901.v9.git.1615891183320.gitgitgadget@gmail.com","subject":"[PATCH v10 0/3] [GSOC] commit: add --trailer option","fromName":"ZheNing Hu via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2021-03-18T11:15:53Z","receivedAt":"2021-03-18T11:16:43Z","isPatch":true,"sender":{"key":"adlternative@gmail.com","avatar":"https://avatars.githubusercontent.com/u/58138461?v=4"},"body":"Now maintainers or developers can also use commit\n--trailer=\"Signed-off-by:commiter<email>\" from the command line to provide\ntrailers to commit messages. This solution may be more generalized than v1.\n\nZheNing Hu (3):\n  [GSOC] commit: add --trailer option\n  interpret-trailers: add own-identity option\n  commit: add own-identity option\n\n Documentation/git-commit.txt             |  25 +-\n Documentation/git-interpret-trailers.txt |  14 +\n builtin/commit.c                         |  31 ++\n builtin/interpret-trailers.c             |   6 +-\n t/t7501-commit-basic-functionality.sh    |  91 ++++++\n t/t7502-commit-porcelain.sh              | 356 +++++++++++++++++++++++\n t/t7513-interpret-trailers.sh            |  12 +\n trailer.c                                |  18 +-\n trailer.h                                |   3 +-\n 9 files changed, 550 insertions(+), 6 deletions(-)\n\n\nbase-commit: 13d7ab6b5d7929825b626f050b62a11241ea4945\nPublished-As: https://github.com/gitgitgadget/git/releases/tag/pr-901%2Fadlternative%2Fcommit-with-multiple-signatures-v10\nFetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-901/adlternative/commit-with-multiple-signatures-v10\nPull-Request: https://github.com/gitgitgadget/git/pull/901\n\nRange-diff vs v9:\n\n 1:  e524c4ba5dc1 ! 1:  949faf9ee56a [GSOC] commit: add --trailer option\n     @@ Documentation/git-commit.txt: The `-m` option is mutually exclusive with `-c`, `\n      +\t<committer@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 in the commit message.)\n     -+\tSee linkgit:git-interpret-trailers[1] for details.\n     ++\tUse `git -c trailer.* commit --trailer` to make the appropriate\n     ++\tconfiguration. See linkgit:git-interpret-trailers[1] for details.\n       -n::\n       --no-verify::\n       \tThis option bypasses the pre-commit and commit-msg hooks.\n     @@ builtin/commit.c: static int prepare_to_commit(const char *index_file, const cha\n      +\t\t\t     \"--in-place\", 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\t\tstrvec_clear(&run_trailer.args);\n     ++\t\tif (run_command(&run_trailer)) {\n     ++\t\t\tdie(_(\"unable to pass tailers to --trailers\"));\n     ++\t\t}\n      +\t\tstrvec_clear(&trailer_args);\n      +\t}\n      +\n     @@ builtin/commit.c: int cmd_commit(int argc, const char **argv, const char *prefix\n       \t\tOPT_STRING(0, \"fixup\", &fixup_message, N_(\"commit\"), N_(\"use autosquash formatted message to fixup 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\", NULL, N_(\"trailer\"), N_(\"trailer(s) to add\"), PARSE_OPT_NONEG, opt_pass_trailer),\n     ++\t\tOPT_CALLBACK_F(0, \"trailer\", NULL, N_(\"trailer\"), N_(\"add custom trailer(s)\"), PARSE_OPT_NONEG, opt_pass_trailer),\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     @@ t/t7502-commit-porcelain.sh: test_expect_success 'sign off' '\n       \n       '\n       \n     -+test_expect_success 'trailer' '\n     -+\t>file1 &&\n     -+\tgit add file1 &&\n     -+\tgit commit -s --trailer \"Signed-off-by:C O Mitter1 <committer1@example.com>\" \\\n     -+\t\t--trailer \"Helped-by:C O Mitter2 <committer2@example.com>\"  \\\n     -+\t\t--trailer \"Reported-by:C O Mitter3 <committer3@example.com>\" \\\n     -+\t\t--trailer \"Mentored-by:C O Mitter4 <committer4@example.com>\" \\\n     ++test_expect_success 'commit --trailer without -c' '\n     ++\techo \"fun\" >>file &&\n     ++\tgit add file &&\n     ++\tcat >expected <<-\\EOF &&\n     ++\n     ++\tSigned-off-by: C O Mitter <committer@example.com>\n     ++\tSigned-off-by: C1 E1\n     ++\tHelped-by: C2 E2\n     ++\tReported-by: C3 E3\n     ++\tMentored-by: C4 E4\n     ++\tEOF\n     ++\tgit commit -s --trailer \"Signed-off-by:C1 E1 \" \\\n     ++\t\t--trailer \"Helped-by:C2 E2 \" \\\n     ++\t\t--trailer \"Reported-by:C3 E3\" \\\n     ++\t\t--trailer \"Mentored-by:C4 E4\" \\\n      +\t\t-m \"hello\" &&\n      +\tgit cat-file commit HEAD >commit.msg &&\n     -+\tsed -e \"1,7d\" commit.msg >actual &&\n     ++\tsed -e \"1,6d\" commit.msg >actual &&\n     ++\ttest_cmp expected actual\n     ++'\n     ++\n     ++test_expect_success 'commit --trailer with -c and \"replace\" as ifexists' '\n     ++\techo \"fun\" >>file1 &&\n     ++\tgit add file1 &&\n     ++\tcat >expected <<-\\EOF &&\n     ++\n     ++\tSigned-off-by: C O Mitter <committer@example.com>\n     ++\tSigned-off-by: C1 E1\n     ++\tReported-by: C3 E3\n     ++\tMentored-by: C4 E4\n     ++\tHelped-by: C3 E3\n     ++\tEOF\n     ++\tgit -c trailer.ifexists=\"replace\" \\\n     ++\t\tcommit --trailer \"Mentored-by: C4 E4\" \\\n     ++\t\t --trailer \"Helped-by: C3 E3\" \\\n     ++\t\t--amend &&\n     ++\tgit cat-file commit HEAD >commit.msg &&\n     ++\tsed -e \"1,6d\" commit.msg >actual &&\n     ++\ttest_cmp expected actual\n     ++'\n     ++\n     ++test_expect_success 'commit --trailer with -c and \"add\" as ifexists' '\n     ++\techo \"fun\" >>file1 &&\n     ++\tgit add file1 &&\n     ++\tcat >expected <<-\\EOF &&\n     ++\n     ++\tSigned-off-by: C O Mitter <committer@example.com>\n     ++\tSigned-off-by: C1 E1\n     ++\tReported-by: C3 E3\n     ++\tMentored-by: C4 E4\n     ++\tHelped-by: C3 E3\n     ++\tHelped-by: C3 E3\n     ++\tHelped-by: C3 E3\n     ++\tEOF\n     ++\tgit -c trailer.ifexists=\"add\" \\\n     ++\t\tcommit --trailer \"Helped-by: C3 E3\" \\\n     ++\t\t--trailer \"Helped-by: C3 E3\" \\\n     ++\t\t--amend &&\n     ++\tgit cat-file commit HEAD >commit.msg &&\n     ++\tsed -e \"1,6d\" commit.msg >actual &&\n     ++\ttest_cmp expected actual\n     ++'\n     ++\n     ++test_expect_success 'commit --trailer with -c and \"donothing\" as ifexists' '\n     ++\techo \"fun\" >>file1 &&\n     ++\tgit add file1 &&\n      +\tcat >expected <<-\\EOF &&\n     ++\n      +\tSigned-off-by: C O Mitter <committer@example.com>\n     -+\tSigned-off-by: C O Mitter1 <committer1@example.com>\n     -+\tHelped-by: C O Mitter2 <committer2@example.com>\n     -+\tReported-by: C O Mitter3 <committer3@example.com>\n     -+\tMentored-by: C O Mitter4 <committer4@example.com>\n     ++\tSigned-off-by: C1 E1\n     ++\tReported-by: C3 E3\n     ++\tMentored-by: C4 E4\n     ++\tHelped-by: C3 E3\n     ++\tHelped-by: C3 E3\n     ++\tHelped-by: C3 E3\n     ++\tReviewed-by: C6 E6\n      +\tEOF\n     ++\tgit -c trailer.ifexists=\"donothing\" \\\n     ++\t\tcommit --trailer \"Mentored-by: C5 E5\" \\\n     ++\t\t--trailer \"Reviewed-by: C6 E6\" \\\n     ++\t\t--amend &&\n     ++\tgit cat-file commit HEAD >commit.msg &&\n     ++\tsed -e \"1,6d\" commit.msg >actual &&\n     ++\ttest_cmp expected actual\n     ++'\n     ++\n     ++test_expect_success 'commit --trailer with -c and \"addIfDifferent\" as ifexists' '\n     ++\techo \"fun\" >>file1 &&\n     ++\tgit add file1 &&\n     ++\tcat >expected <<-\\EOF &&\n     ++\n     ++\tSigned-off-by: C O Mitter <committer@example.com>\n     ++\tSigned-off-by: C1 E1\n     ++\tReported-by: C3 E3\n     ++\tMentored-by: C4 E4\n     ++\tHelped-by: C3 E3\n     ++\tHelped-by: C3 E3\n     ++\tHelped-by: C3 E3\n     ++\tReviewed-by: C6 E6\n     ++\tReported-by: C5 E5\n     ++\tEOF\n     ++\tgit -c trailer.ifexists=\"addIfDifferent\" \\\n     ++\t\tcommit --trailer \"Reviewed-by: C6 E6\" \\\n     ++\t\t--trailer \"Reported-by: C5 E5\" \\\n     ++\t\t--amend &&\n     ++\tgit cat-file commit HEAD >commit.msg &&\n     ++\tsed -e \"1,6d\" commit.msg >actual &&\n     ++\ttest_cmp expected actual\n     ++'\n     ++\n     ++test_expect_success 'commit --trailer with -c and \"addIfDifferentNeighbor\" as ifexists' '\n     ++\techo \"fun\" >>file1 &&\n     ++\tgit add file1 &&\n     ++\tcat >expected <<-\\EOF &&\n     ++\n     ++\tSigned-off-by: C O Mitter <committer@example.com>\n     ++\tSigned-off-by: C1 E1\n     ++\tReported-by: C3 E3\n     ++\tMentored-by: C4 E4\n     ++\tHelped-by: C3 E3\n     ++\tHelped-by: C3 E3\n     ++\tHelped-by: C3 E3\n     ++\tReviewed-by: C6 E6\n     ++\tReported-by: C5 E5\n     ++\tEOF\n     ++\tgit -c trailer.ifexists=\"addIfDifferent\" \\\n     ++\t\tcommit --trailer \"Reported-by: C5 E5\" \\\n     ++\t\t--trailer \"Reviewed-by: C6 E6\" \\\n     ++\t\t--amend &&\n     ++\tgit cat-file commit HEAD >commit.msg &&\n     ++\tsed -e \"1,6d\" commit.msg >actual &&\n     ++\ttest_cmp expected actual\n     ++'\n     ++\n     ++test_expect_success 'commit --trailer with -c and \"end\" as where' '\n     ++\techo \"fun\" >>file1 &&\n     ++\tgit add file1 &&\n     ++\tcat >expected <<-\\EOF &&\n     ++\n     ++\tSigned-off-by: C O Mitter <committer@example.com>\n     ++\tSigned-off-by: C1 E1\n     ++\tReported-by: C3 E3\n     ++\tMentored-by: C4 E4\n     ++\tHelped-by: C3 E3\n     ++\tHelped-by: C3 E3\n     ++\tHelped-by: C3 E3\n     ++\tReviewed-by: C6 E6\n     ++\tReported-by: C5 E5\n     ++\tReported-by: C7 E7\n     ++\tEOF\n     ++\tgit -c trailer.where=\"end\" \\\n     ++\t\tcommit --trailer \"Reported-by: C5 E5\" \\\n     ++\t\t--trailer \"Reported-by: C7 E7\" \\\n     ++\t\t--amend &&\n     ++\tgit cat-file commit HEAD >commit.msg &&\n     ++\tsed -e \"1,6d\" commit.msg >actual &&\n     ++\ttest_cmp expected actual\n     ++'\n     ++\n     ++test_expect_success 'commit --trailer with -c and \"start\" as where' '\n     ++\techo \"fun\" >>file1 &&\n     ++\tgit add file1 &&\n     ++\tcat >expected <<-\\EOF &&\n     ++\n     ++\tSigned-off-by: C8 E8\n     ++\tSigned-off-by: C O Mitter <committer@example.com>\n     ++\tSigned-off-by: C1 E1\n     ++\tReported-by: C3 E3\n     ++\tMentored-by: C4 E4\n     ++\tHelped-by: C3 E3\n     ++\tHelped-by: C3 E3\n     ++\tHelped-by: C3 E3\n     ++\tReviewed-by: C6 E6\n     ++\tReported-by: C5 E5\n     ++\tReported-by: C7 E7\n     ++\tEOF\n     ++\tgit -c trailer.where=\"start\" \\\n     ++\t\tcommit --trailer \"Signed-off-by: C8 E8\" \\\n     ++\t\t--trailer \"Signed-off-by: C8 E8\" \\\n     ++\t\t--amend &&\n     ++\tgit cat-file commit HEAD >commit.msg &&\n     ++\tsed -e \"1,6d\" commit.msg >actual &&\n     ++\ttest_cmp expected actual\n     ++'\n     ++\n     ++test_expect_success 'commit --trailer with -c and \"after\" as where' '\n     ++\techo \"fun\" >>file1 &&\n     ++\tgit add file1 &&\n     ++\tcat >expected <<-\\EOF &&\n     ++\n     ++\tSigned-off-by: C8 E8\n     ++\tSigned-off-by: C O Mitter <committer@example.com>\n     ++\tSigned-off-by: C1 E1\n     ++\tReported-by: C3 E3\n     ++\tMentored-by: C4 E4\n     ++\tMentored-by: C9 E9\n     ++\tHelped-by: C3 E3\n     ++\tHelped-by: C3 E3\n     ++\tHelped-by: C3 E3\n     ++\tReviewed-by: C6 E6\n     ++\tReported-by: C5 E5\n     ++\tReported-by: C7 E7\n     ++\tReported-by: C10 E10\n     ++\tEOF\n     ++\tgit -c trailer.where=\"after\" \\\n     ++\t\tcommit --trailer \"Mentored-by: C9 E9\" \\\n     ++\t\t--trailer \"Reported-by: C10 E10\" \\\n     ++\t\t--amend &&\n     ++\tgit cat-file commit HEAD >commit.msg &&\n     ++\tsed -e \"1,6d\" commit.msg >actual &&\n     ++\ttest_cmp expected actual\n     ++'\n     ++\n     ++test_expect_success 'commit --trailer with -c and \"before\" as where' '\n     ++\techo \"fun\" >>file1 &&\n     ++\tgit add file1 &&\n     ++\tcat >expected <<-\\EOF &&\n     ++\n     ++\tSigned-off-by: C8 E8\n     ++\tSigned-off-by: C O Mitter <committer@example.com>\n     ++\tSigned-off-by: C1 E1\n     ++\tReported-by: C11 E11\n     ++\tReported-by: C3 E3\n     ++\tMentored-by: C4 E4\n     ++\tMentored-by: C9 E9\n     ++\tHelped-by: C12 E12\n     ++\tHelped-by: C3 E3\n     ++\tHelped-by: C3 E3\n     ++\tHelped-by: C3 E3\n     ++\tReviewed-by: C6 E6\n     ++\tReported-by: C5 E5\n     ++\tReported-by: C7 E7\n     ++\tReported-by: C10 E10\n     ++\tEOF\n     ++\tgit -c trailer.where=\"before\" \\\n     ++\t\tcommit --trailer \"Helped-by: C12 E12\" \\\n     ++\t\t--trailer \"Reported-by: C11 E11\" \\\n     ++\t\t--amend &&\n     ++\tgit cat-file commit HEAD >commit.msg &&\n     ++\tsed -e \"1,6d\" commit.msg >actual &&\n     ++\ttest_cmp expected actual\n     ++'\n     ++\n     ++test_expect_success 'commit --trailer with -c and \"donothing\" as ifmissing' '\n     ++\techo \"fun\" >>file1 &&\n     ++\tgit add file1 &&\n     ++\tcat >expected <<-\\EOF &&\n     ++\n     ++\tSigned-off-by: C8 E8\n     ++\tSigned-off-by: C O Mitter <committer@example.com>\n     ++\tSigned-off-by: C1 E1\n     ++\tReported-by: C11 E11\n     ++\tReported-by: C3 E3\n     ++\tMentored-by: C4 E4\n     ++\tMentored-by: C9 E9\n     ++\tHelped-by: C12 E12\n     ++\tHelped-by: C3 E3\n     ++\tHelped-by: C3 E3\n     ++\tHelped-by: C3 E3\n     ++\tReviewed-by: C6 E6\n     ++\tReported-by: C5 E5\n     ++\tReported-by: C7 E7\n     ++\tReported-by: C10 E10\n     ++\tHelped-by: C12 E12\n     ++\tEOF\n     ++\tgit -c trailer.ifmissing=\"donothing\" \\\n     ++\t\tcommit --trailer \"Helped-by: C12 E12\" \\\n     ++\t\t--trailer \"Based-by: C13 E13\" \\\n     ++\t\t--amend &&\n     ++\tgit cat-file commit HEAD >commit.msg &&\n     ++\tsed -e \"1,6d\" commit.msg >actual &&\n     ++\ttest_cmp expected actual\n     ++'\n     ++\n     ++test_expect_success 'commit --trailer with -c and \"add\" as ifmissing' '\n     ++\techo \"fun\" >>file1 &&\n     ++\tgit add file1 &&\n     ++\tcat >expected <<-\\EOF &&\n     ++\n     ++\tSigned-off-by: C8 E8\n     ++\tSigned-off-by: C O Mitter <committer@example.com>\n     ++\tSigned-off-by: C1 E1\n     ++\tReported-by: C11 E11\n     ++\tReported-by: C3 E3\n     ++\tMentored-by: C4 E4\n     ++\tMentored-by: C9 E9\n     ++\tHelped-by: C12 E12\n     ++\tHelped-by: C3 E3\n     ++\tHelped-by: C3 E3\n     ++\tHelped-by: C3 E3\n     ++\tReviewed-by: C6 E6\n     ++\tReported-by: C5 E5\n     ++\tReported-by: C7 E7\n     ++\tReported-by: C10 E10\n     ++\tHelped-by: C12 E12\n     ++\tBased-by: C13 E13\n     ++\tEOF\n     ++\tgit -c trailer.ifmissing=\"add\" \\\n     ++\t\tcommit --trailer \"Helped-by: C12 E12\" \\\n     ++\t\t--trailer \"Based-by: C13 E13\" \\\n     ++\t\t--amend &&\n     ++\tgit cat-file commit HEAD >commit.msg &&\n     ++\tsed -e \"1,6d\" commit.msg >actual &&\n     ++\ttest_cmp expected actual\n     ++'\n     ++\n     ++test_expect_success 'commit --trailer with -c and \"=\" as separators' '\n     ++\techo \"fun\" >>file1 &&\n     ++\tgit add file1 &&\n     ++\tcat >expected <<-\\EOF &&\n     ++\n     ++\t\tAcked-by= Peff\n     ++\tEOF\n     ++\tgit -c trailer.separators=\"=\" \\\n     ++\t\t-c trailer.ack.key=\"Acked-by= \" \\\n     ++\t\tcommit --trailer \"ack = Peff\" -m \"hello\" &&\n     ++\tgit cat-file commit HEAD >commit.msg &&\n     ++\tsed -e \"1,6d\" commit.msg >actual &&\n     ++\ttest_cmp expected actual\n     ++'\n     ++\n     ++test_expect_success 'commit --trailer with -c and \":=#\" as separators' '\n     ++\techo \"fun\" >>file1 &&\n     ++\tgit add file1 &&\n     ++\tcat >expected <<-\\EOF &&\n     ++\n     ++\t\tBug #42\n     ++\tEOF\n     ++\tgit -c trailer.separators=\":=#\" \\\n     ++\t\t-c trailer.bug.key=\"Bug #\" \\\n     ++\t\tcommit --trailer \"bug = 42\" -m \"I hate bug\" &&\n     ++\tgit cat-file commit HEAD >commit.msg &&\n     ++\tsed -e \"1,6d\" commit.msg >actual &&\n      +\ttest_cmp expected actual\n      +'\n      +\n -:  ------------ > 2:  42590e95deee interpret-trailers: add own-identity option\n -:  ------------ > 3:  2dfcc20f0e9f commit: add own-identity option\n\n-- \ngitgitgadget\n"},{"id":"419630","messageId":"2dfcc20f0e9faa1aa08aa524127ffef44bd98182.1616066157.git.gitgitgadget@gmail.com","threadId":"55288","inReplyTo":"pull.901.v10.git.1616066156.gitgitgadget@gmail.com","subject":"[PATCH v10 3/3] commit: add own-identity option","fromName":"ZheNing Hu via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2021-03-18T11:15:56Z","receivedAt":"2021-03-18T11:16:44Z","isPatch":true,"sender":{"key":"adlternative@gmail.com","avatar":"https://avatars.githubusercontent.com/u/58138461?v=4"},"body":"From: ZheNing Hu <adlternative@gmail.com>\n\nUse the newly added option `--own-identity` in `interpret-trailers`,\nimplement new commit option `--own-identity` to allow those trailers\nwith no value add the user’s own identity. Using the `--own-identity`\noption, users can directly use `--trailer=\"Signed-off-by\"` to generate\na signoff trailer with their own identities in commit messages,\n\nThe effect is basically the same as the `--signoff` option. However, users\ncan add more useful options at the same time. e.g. `--trailer=\"Helped-by\"\n--own-identity` can general `Helped-by: C O Mitter <committer@example.com>`;\nOr through appropriate configuration:\n`git -c trailer.signoff.key=\"Signed-off-by\" commit --trailer=\"signoff\" \\\n--own-identity` can also general their needs trailers with their\nfavorite keys and their own identities.\n\nSigned-off-by: ZheNing Hu <adlternative@gmail.com>\n---\n Documentation/git-commit.txt          | 15 +++++\n builtin/commit.c                      |  8 +++\n t/t7501-commit-basic-functionality.sh | 91 +++++++++++++++++++++++++++\n t/t7502-commit-porcelain.sh           | 20 ++++++\n 4 files changed, 134 insertions(+)\n\ndiff --git a/Documentation/git-commit.txt b/Documentation/git-commit.txt\nindex c5de981cd40d..393c424e8184 100644\n--- a/Documentation/git-commit.txt\n+++ b/Documentation/git-commit.txt\n@@ -174,6 +174,21 @@ include::signoff-option.txt[]\n \tand the \"Helped-by\" trailer in the commit message.)\n \tUse `git -c trailer.* commit --trailer` to make the appropriate\n \tconfiguration. See linkgit:git-interpret-trailers[1] for details.\n+\n+--own-identity::\n+\tUsed with `--trailer`. Those trailers without value with the\n+\t`--own-identity` option all will add the user's own identity.\n+\tFor example, `git commit --trailer \\\n+\t\"A:B\" --trailer\t\"Signed-off-by\" --trailer \"Helped-by\" --own-identity`,\n+\twill output:\n+\t\"\n+\tA:B\n+\tSigned-off-by: C O Mitter <committer@example.com>\n+\tHelped-by: C O Mitter <committer@example.com>\n+\t\"\n+\tin commit messages.\n+\tSee linkgit:git-interpret-trailers[1]for details.\n+\n -n::\n --no-verify::\n \tThis option bypasses the pre-commit and commit-msg hooks.\ndiff --git a/builtin/commit.c b/builtin/commit.c\nindex 7a79aae48f43..0310c187ec07 100644\n--- a/builtin/commit.c\n+++ b/builtin/commit.c\n@@ -114,6 +114,7 @@ static int no_post_rewrite, allow_empty_message, pathspec_file_nul;\n static char *untracked_files_arg, *force_date, *ignore_submodule_arg, *ignored_arg;\n static char *sign_commit, *pathspec_from_file;\n static struct strvec trailer_args = STRVEC_INIT;\n+static int own_identity;\n \n /*\n  * The default commit message cleanup mode will remove the lines\n@@ -972,6 +973,8 @@ static int prepare_to_commit(const char *index_file, const char *prefix,\n \n \t\tstrvec_pushl(&run_trailer.args, \"interpret-trailers\",\n \t\t\t     \"--in-place\", git_path_commit_editmsg(), NULL);\n+\t\tif (own_identity)\n+\t\t\tstrvec_push(&run_trailer.args, \"--own-identity\");\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@@ -1530,6 +1533,8 @@ int cmd_commit(int argc, const char **argv, const char *prefix)\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\", NULL, N_(\"trailer\"), N_(\"add custom trailer(s)\"), PARSE_OPT_NONEG, opt_pass_trailer),\n+\t\tOPT_BOOL(0, \"own-identity\", &own_identity,\n+\t\t\t     N_(\"specify the user's own identity for omitted trailers value\")),\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@@ -1606,6 +1611,9 @@ int cmd_commit(int argc, const char **argv, const char *prefix)\n \tif (verbose == -1)\n \t\tverbose = (config_commit_verbose < 0) ? 0 : config_commit_verbose;\n \n+\tif (own_identity && !trailer_args.nr)\n+\t\tdie(_(\"--own_identity requires --trailer\"));\n+\n \tif (dry_run)\n \t\treturn dry_run_commit(argv, prefix, current_head, &s);\n \tindex_file = prepare_index(argv, prefix, current_head, 0);\ndiff --git a/t/t7501-commit-basic-functionality.sh b/t/t7501-commit-basic-functionality.sh\nindex 512ae2781fe2..7ff74cf81e75 100755\n--- a/t/t7501-commit-basic-functionality.sh\n+++ b/t/t7501-commit-basic-functionality.sh\n@@ -423,6 +423,25 @@ test_expect_success 'sign off (1)' '\n \n '\n \n+test_expect_success '--trailer=\"signoff\" (1)' '\n+\n+\techo 1 >>positive &&\n+\tgit add positive &&\n+\tgit -c trailer.signoff.key=\"Signed-off-by\" \\\n+\t\tcommit --trailer=\"signoff\" --own-identity \\\n+\t\t-m \"thank you\" &&\n+\tgit cat-file commit HEAD >commit &&\n+\tsed -e \"1,/^\\$/d\" commit >actual &&\n+\t(\n+\t\techo thank you &&\n+\t\techo &&\n+\t\tgit var GIT_COMMITTER_IDENT >ident &&\n+\t\tsed -e \"s/>.*/>/\" -e \"s/^/Signed-off-by: /\" ident\n+\t) >expected &&\n+\ttest_cmp expected actual\n+\n+'\n+\n test_expect_success 'sign off (2)' '\n \n \techo 2 >positive &&\n@@ -444,6 +463,30 @@ $existing\" &&\n \n '\n \n+test_expect_success '--trailer=\"signoff\" (2)' '\n+\n+\techo 2 >>positive &&\n+\tgit add positive &&\n+\texisting=\"Signed-off-by: Watch This <watchthis@example.com>\" &&\n+\tgit -c trailer.signoff.key=\"Signed-off-by\" \\\n+\t\tcommit --trailer=\"signoff\" --own-identity \\\n+\t\t-m \"thank you\n+\n+$existing\" &&\n+\tgit cat-file commit HEAD >commit &&\n+\tsed -e \"1,/^\\$/d\" commit >actual &&\n+\t(\n+\t\techo thank you &&\n+\t\techo &&\n+\t\techo $existing &&\n+\t\tgit var GIT_COMMITTER_IDENT >ident &&\n+\t\tsed -e \"s/>.*/>/\" -e \"s/^/Signed-off-by: /\" ident\n+\t) >expected &&\n+\ttest_cmp expected actual\n+\n+'\n+\n+\n test_expect_success 'signoff gap' '\n \n \techo 3 >positive &&\n@@ -464,6 +507,29 @@ $alt\" &&\n \ttest_cmp expected actual\n '\n \n+test_expect_success '--trailer=\"signoff\" gap' '\n+\n+\techo 3 >>positive &&\n+\tgit add positive &&\n+\talt=\"Alt-RFC-822-Header: Value\" &&\n+\tgit -c trailer.signoff.key=\"Signed-off-by\" \\\n+\t\tcommit --trailer=\"signoff\" --own-identity \\\n+\t\t-m \"welcome\n+\n+$alt\" &&\n+\tgit cat-file commit HEAD >commit &&\n+\tsed -e \"1,/^\\$/d\" commit >actual &&\n+\t(\n+\t\techo welcome &&\n+\t\techo &&\n+\t\techo $alt &&\n+\t\tgit var GIT_COMMITTER_IDENT >ident &&\n+\t\tsed -e \"s/>.*/>/\" -e \"s/^/Signed-off-by: /\" ident\n+\t) >expected &&\n+\ttest_cmp expected actual\n+'\n+\n+\n test_expect_success 'signoff gap 2' '\n \n \techo 4 >positive &&\n@@ -487,6 +553,31 @@ $alt\" &&\n \ttest_cmp expected actual\n '\n \n+test_expect_success '--trailer=\"signoff\" gap 2' '\n+\n+\techo 4 >>positive &&\n+\tgit add positive &&\n+\talt=\"fixed: 34\" &&\n+\tgit -c trailer.signoff.key=\"Signed-off-by\" \\\n+\t\tcommit --trailer=\"signoff\" --own-identity \\\n+\t\t-m \"welcome\n+\n+We have now\n+$alt\" &&\n+\tgit cat-file commit HEAD >commit &&\n+\tsed -e \"1,/^\\$/d\" commit >actual &&\n+\t(\n+\t\techo welcome &&\n+\t\techo &&\n+\t\techo We have now &&\n+\t\techo $alt &&\n+\t\techo &&\n+\t\tgit var GIT_COMMITTER_IDENT >ident &&\n+\t\tsed -e \"s/>.*/>/\" -e \"s/^/Signed-off-by: /\" ident\n+\t) >expected &&\n+\ttest_cmp expected actual\n+'\n+\n test_expect_success 'signoff respects trailer config' '\n \n \techo 5 >positive &&\ndiff --git a/t/t7502-commit-porcelain.sh b/t/t7502-commit-porcelain.sh\nindex 6df71fa00bcb..1628a1c1e7d6 100755\n--- a/t/t7502-commit-porcelain.sh\n+++ b/t/t7502-commit-porcelain.sh\n@@ -490,6 +490,26 @@ test_expect_success 'commit --trailer with -c and \":=#\" as separators' '\n \ttest_cmp expected actual\n '\n \n+test_expect_success 'commit --trailer with -c and --own-identity' '\n+\techo \"fun\" >>file1 &&\n+\tgit add file1 &&\n+\tcat >expected <<-\\EOF &&\n+\n+\tSigned-off-by: C O Mitter <committer@example.com>\n+\tEOF\n+\tgit -c trailer.signoff.key=\"Signed-off-by: \" \\\n+\t\tcommit --trailer \"signoff\" --own-identity -m \"abc\" &&\n+\tgit cat-file commit HEAD >commit.msg &&\n+\tsed -e \"1,6d\" commit.msg >actual &&\n+\ttest_cmp expected actual\n+'\n+\n+test_expect_success 'commit --own-identity without --trailer' '\n+\techo \"fun\" >>file1 &&\n+\tgit add file1 &&\n+\ttest_must_fail git -c commit --own-identity -m \"abc\"\n+'\n+\n test_expect_success 'multiple -m' '\n \n \t>negative &&\n-- \ngitgitgadget\n"},{"id":"419636","messageId":"CAP8UFD0UJvRn6VN5NaYiCK__Z=A_PTowpNxu8wi=9Byh+jJdFA@mail.gmail.com","threadId":"55288","inReplyTo":"pull.901.v10.git.1616066156.gitgitgadget@gmail.com","subject":"Re: [PATCH v10 0/3] [GSOC] commit: add --trailer option","fromName":"Christian Couder","fromEmail":"christian.couder@gmail.com","sentAt":"2021-03-18T13:47:51Z","receivedAt":"2021-03-18T13:48:50Z","isPatch":true,"sender":{"key":"christian.couder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/208954?v=4"},"body":"On Thu, Mar 18, 2021 at 12:15 PM ZheNing Hu via GitGitGadget\n<gitgitgadget@gmail.com> wrote:\n>\n> Now maintainers or developers can also use commit\n> --trailer=\"Signed-off-by:commiter<email>\" from the command line to provide\n> trailers to commit messages. This solution may be more generalized than v1.\n>\n> ZheNing Hu (3):\n>   [GSOC] commit: add --trailer option\n>   interpret-trailers: add own-identity option\n>   commit: add own-identity option\n\nI see that you discussed an own-identity option in this thread\nrecently, but this is version 10 of a patch series, so I don't think\nit's a good idea to introduce new features at this point. I think it's\nbetter to not tie the work that has already been done on polishing the\nfirst patch to new patches implementing an additional feature. The\nadditional feature can be discussed and worked on in its own patch\nseries building on top of this one.\n\nAlso it feels strange that only the first patch has \"[GSOC]\".\n"},{"id":"419637","messageId":"CAOLTT8TyxR0yFz2yPiPoGYZMMLb8PWi3A9C=QwhEeHvOWkWuPA@mail.gmail.com","threadId":"55288","inReplyTo":"CAP8UFD0UJvRn6VN5NaYiCK__Z=A_PTowpNxu8wi=9Byh+jJdFA@mail.gmail.com","subject":"Re: [PATCH v10 0/3] [GSOC] commit: add --trailer option","fromName":"ZheNing Hu","fromEmail":"adlternative@gmail.com","sentAt":"2021-03-18T15:27:40Z","receivedAt":"2021-03-18T15:28:50Z","isPatch":true,"sender":{"key":"adlternative@gmail.com","avatar":"https://avatars.githubusercontent.com/u/58138461?v=4"},"body":"Christian Couder <christian.couder@gmail.com> 于2021年3月18日周四 下午9:48写道：\n>\n> On Thu, Mar 18, 2021 at 12:15 PM ZheNing Hu via GitGitGadget\n> <gitgitgadget@gmail.com> wrote:\n> >\n> > Now maintainers or developers can also use commit\n> > --trailer=\"Signed-off-by:commiter<email>\" from the command line to provide\n> > trailers to commit messages. This solution may be more generalized than v1.\n> >\n> > ZheNing Hu (3):\n> >   [GSOC] commit: add --trailer option\n> >   interpret-trailers: add own-identity option\n> >   commit: add own-identity option\n>\n> I see that you discussed an own-identity option in this thread\n> recently, but this is version 10 of a patch series, so I don't think\n> it's a good idea to introduce new features at this point. I think it's\n> better to not tie the work that has already been done on polishing the\n> first patch to new patches implementing an additional feature. The\n> additional feature can be discussed and worked on in its own patch\n> series building on top of this one.\n>\nOK, I understand that now. I will divide this patch series into two parts.\n> Also it feels strange that only the first patch has \"[GSOC]\".\nI forgot to add [GSOC]...\n\nThanks.\n"},{"id":"419638","messageId":"YFOAAQmVqUVOpLK1@danh.dev","threadId":"55288","inReplyTo":"949faf9ee56a3146a643ba634771779efa89bbc5.1616066156.git.gitgitgadget@gmail.com","subject":"Re: [PATCH v10 1/3] [GSOC] commit: add --trailer option","fromName":"Đoàn Trần Công Danh","fromEmail":"congdanhqx@gmail.com","sentAt":"2021-03-18T16:29:53Z","receivedAt":"2021-03-18T16:30:34Z","isPatch":true,"sender":{"key":"congdanhqx@gmail.com","avatar":"https://avatars.githubusercontent.com/u/42673067?v=4"},"body":"On 2021-03-18 11:15:54+0000, ZheNing Hu via GitGitGadget <gitgitgadget@gmail.com> wrote:\n> From: ZheNing Hu <adlternative@gmail.com>\n> \n> Historically, Git has supported the 'Signed-off-by' commit trailer\n> using the '--signoff' and the '-s' option from the command line.\n> But users may need to provide other trailer information from the\n> command line such as \"Helped-by\", \"Reported-by\", \"Mentored-by\",\n> \n> Now implement a new `--trailer <token>[(=|:)<value>]` option to pass\n> other trailers to `interpret-trailers` and insert them into commit\n> messages.\n> \n> Signed-off-by: ZheNing Hu <adlternative@gmail.com>\n> ---\n>  Documentation/git-commit.txt |  10 +-\n>  builtin/commit.c             |  23 +++\n>  t/t7502-commit-porcelain.sh  | 336 +++++++++++++++++++++++++++++++++++\n>  3 files changed, 368 insertions(+), 1 deletion(-)\n> \n> diff --git a/Documentation/git-commit.txt b/Documentation/git-commit.txt\n> index 17150fa7eabe..c5de981cd40d 100644\n> --- a/Documentation/git-commit.txt\n> +++ b/Documentation/git-commit.txt\n> @@ -14,7 +14,7 @@ SYNOPSIS\n>  \t   [--allow-empty-message] [--no-verify] [-e] [--author=<author>]\n>  \t   [--date=<date>] [--cleanup=<mode>] [--[no-]status]\n>  \t   [-i | -o] [--pathspec-from-file=<file> [--pathspec-file-nul]]\n> -\t   [-S[<keyid>]] [--] [<pathspec>...]\n> +\t   [-S[<keyid>]] [--] [<pathspec>...] [(--trailer <token>[(=|:)<value>])...]\n\nPlease move all options before non-option arguments.\nIn other words, please move --trailer before [--].\n\nThis form implies that there are no way to specify pathspec \"--trailer\"\n\n>  \n>  DESCRIPTION\n>  -----------\n> @@ -166,6 +166,14 @@ The `-m` option is mutually exclusive with `-c`, `-C`, and `-F`.\n>  \n>  include::signoff-option.txt[]\n>  \n> +--trailer <token>[(=|:)<value>]::\n> +\tSpecify a (<token>, <value>) pair that should be applied as a\n> +\ttrailer. (e.g. `git commit --trailer \"Signed-off-by:C O Mitter \\\n> +\t<committer@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 in the commit message.)\n> +\tUse `git -c trailer.* commit --trailer` to make the appropriate\n> +\tconfiguration. See linkgit:git-interpret-trailers[1] for details.\n>  -n::\n>  --no-verify::\n>  \tThis option bypasses the pre-commit and commit-msg hooks.\n> diff --git a/builtin/commit.c b/builtin/commit.c\n> index 739110c5a7f6..7a79aae48f43 100644\n> --- a/builtin/commit.c\n> +++ b/builtin/commit.c\n> @@ -113,6 +113,7 @@ static int config_commit_verbose = -1; /* unspecified */\n>  static int no_post_rewrite, allow_empty_message, pathspec_file_nul;\n>  static char *untracked_files_arg, *force_date, *ignore_submodule_arg, *ignored_arg;\n>  static char *sign_commit, *pathspec_from_file;\n> +static struct strvec trailer_args = STRVEC_INIT;\n>  \n>  /*\n>   * The default commit message cleanup mode will remove the lines\n> @@ -131,6 +132,14 @@ 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(&trailer_args, \"--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> @@ -958,6 +967,19 @@ static int prepare_to_commit(const char *index_file, const char *prefix,\n>  \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\", 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\t\tdie(_(\"unable to pass tailers to --trailers\"));\n\ns/tailers/trailers/ perhap?\nAlso we usually not put {} around single statement.\n\n> +\t\t}\n> +\t\tstrvec_clear(&trailer_args);\n> +\t}\n> +\n>  \t/*\n>  \t * Reject an attempt to record a non-merge empty commit without\n>  \t * explicit --allow-empty. In the cherry-pick case, it may be\n> @@ -1507,6 +1529,7 @@ int cmd_commit(int argc, const char **argv, const char *prefix)\n>  \t\tOPT_STRING(0, \"fixup\", &fixup_message, N_(\"commit\"), N_(\"use autosquash formatted message to fixup 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\", NULL, N_(\"trailer\"), N_(\"add custom trailer(s)\"), PARSE_OPT_NONEG, opt_pass_trailer),\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/t/t7502-commit-porcelain.sh b/t/t7502-commit-porcelain.sh\n> index 6396897cc818..6df71fa00bcb 100755\n> --- a/t/t7502-commit-porcelain.sh\n> +++ b/t/t7502-commit-porcelain.sh\n> @@ -154,6 +154,342 @@ test_expect_success 'sign off' '\n>  \n>  '\n>  \n> +test_expect_success 'commit --trailer without -c' '\n> +\techo \"fun\" >>file &&\n> +\tgit add file &&\n> +\tcat >expected <<-\\EOF &&\n> +\n> +\tSigned-off-by: C O Mitter <committer@example.com>\n> +\tSigned-off-by: C1 E1\n> +\tHelped-by: C2 E2\n> +\tReported-by: C3 E3\n> +\tMentored-by: C4 E4\n> +\tEOF\n> +\tgit commit -s --trailer \"Signed-off-by:C1 E1 \" \\\n> +\t\t--trailer \"Helped-by:C2 E2 \" \\\n> +\t\t--trailer \"Reported-by:C3 E3\" \\\n> +\t\t--trailer \"Mentored-by:C4 E4\" \\\n> +\t\t-m \"hello\" &&\n\nIt's documented that we're supporting --trailer <token>[(=|:)<value>]\nHowever, only --trailer <token>:<value> is tested.\nI think it's better to have \n\t\n\t--trailer \"Helped-by=C2 E2\" --trailer \"Reported-by\"\n\n\n-- \nDanh\n"},{"id":"419640","messageId":"YFODnm4JKfqRn37H@danh.dev","threadId":"55288","inReplyTo":"42590e95deeece6ba65e0432c3a59746e717fee3.1616066156.git.gitgitgadget@gmail.com","subject":"Re: [PATCH v10 2/3] interpret-trailers: add own-identity option","fromName":"Đoàn Trần Công Danh","fromEmail":"congdanhqx@gmail.com","sentAt":"2021-03-18T16:45:18Z","receivedAt":"2021-03-18T16:46:17Z","isPatch":true,"sender":{"key":"congdanhqx@gmail.com","avatar":"https://avatars.githubusercontent.com/u/42673067?v=4"},"body":"On 2021-03-18 11:15:55+0000, ZheNing Hu via GitGitGadget <gitgitgadget@gmail.com> wrote:\n> From: ZheNing Hu <adlternative@gmail.com>\n> \n> Beacuse `git commit --trailer=\"Signed-off-by: \\\n\ns/Beacuse/Because/\n\nAnd I think, it's easier to read if we write the command in its own\n(indented) line.\n\n> $(git config user.name) <$(git config user.email)>\"`\n> is difficult for users to add their own identities,\n> so teach interpret-trailers a new option `--own-identity`\n> which allow those trailers with no value add the user’s own\n> identity. This will help the use of `commit --trailer` as\n> easy as `--signoff`.\n\nPerhap, saying that we're optionalise <value> in --trailer, by\nsubstitute user's identity if missing instead?\n\n> @@ -131,6 +144,7 @@ OPTIONS\n>  \twhen you know your input contains just the commit message itself\n>  \t(and not an email or the output of `git format-patch`).\n>  \n> +\n\nI think it's better to not add this line change\n\n>  CONFIGURATION VARIABLES\n>  -----------------------\n>  \n> diff --git a/builtin/interpret-trailers.c b/builtin/interpret-trailers.c\n> index 84748eafc01b..be7f502a58d7 100644\n> --- a/builtin/interpret-trailers.c\n> +++ b/builtin/interpret-trailers.c\n\n-- \nDanh\n"},{"id":"419646","messageId":"xmqq1rcctgj7.fsf@gitster.g","threadId":"55288","inReplyTo":"42590e95deeece6ba65e0432c3a59746e717fee3.1616066156.git.gitgitgadget@gmail.com","subject":"Re: [PATCH v10 2/3] interpret-trailers: add own-identity option","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2021-03-18T19:20:44Z","receivedAt":"2021-03-18T19:21:54Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"\"ZheNing Hu via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n\n> From: ZheNing Hu <adlternative@gmail.com>\n>\n> Beacuse `git commit --trailer=\"Signed-off-by: \\\n> $(git config user.name) <$(git config user.email)>\"`\n> is difficult for users to add their own identities,\n> so teach interpret-trailers a new option `--own-identity`\n> which allow those trailers with no value add the user’s own\n> identity. This will help the use of `commit --trailer` as\n> easy as `--signoff`.\n\nI have a suspicion that this is too narrowly focused to be useful in\npractice, and I find that the proposed \"--own-identity\" is quite a\nmouthful.\n\n> +--own-identity::\n> +\tUsed with `--trailer`. Those trailers without value with the\n> +\t`--own-identity` option all will add the user's own identity.\n\nSo, the assumption here is that the name of the trailer tag alone,\nwithout the ':' separator, can identify which trailer the user is\ntalking about, and it can be distinguished from the tag name plus ':'\nand nothing else which is calling for a trailer entry with an empty\nstring as its value?\n\nOK.\n\nThe reason why this looks too narrowly focused on oneself alone to\nbe useful to me is because I often need to add various -by trailers\nto incoming patches, and have a script to do exactly that (which\ndoes not use interpret-trailers, as I do not think\ninterpret-trailers can accept a patch email as its input, and the\nscript predates interpret-trailers) but it will be useless if that\nscript were limited to add -by for myself.\n\nWouldn't it be a lot more useful if\n\n\tgit commit --trailer=\"Helped-by:@Ch.*Couder\"\n\nis expanded (note: I am not married to the syntax, but only for\nillustration purposes, I am using \"a value prefixed by @ triggers\nthe 'name expansion'\" convention in this example.  People can come\nup with better convention) by finding an author or a committer whose\nname matches the given pattern from \"git log\"?  Then, instead of\n\n\tgit commit --own-identity --trailer=Signed-off-by\n\nI can say\n\n\tgit commit --trailer=Signed-off-by:@gitster\n\nand I can even add more than one, e.g.\n\n\tgit commit --trailer=Helped-by:@peff --trailer=Signed-off-by:@gitster\n\n"},{"id":"419683","messageId":"CAOLTT8TQJPBRyjsuZ6sMBESfkpOT9moCbkOjdtPs7sDQJ-kd+A@mail.gmail.com","threadId":"55288","inReplyTo":"YFOAAQmVqUVOpLK1@danh.dev","subject":"Re: [PATCH v10 1/3] [GSOC] commit: add --trailer option","fromName":"ZheNing Hu","fromEmail":"adlternative@gmail.com","sentAt":"2021-03-19T07:56:31Z","receivedAt":"2021-03-19T07:57:24Z","isPatch":true,"sender":{"key":"adlternative@gmail.com","avatar":"https://avatars.githubusercontent.com/u/58138461?v=4"},"body":"Đoàn Trần Công Danh <congdanhqx@gmail.com> 于2021年3月19日周五 上午12:29写道：\n>\n> On 2021-03-18 11:15:54+0000, ZheNing Hu via GitGitGadget <gitgitgadget@gmail.com> wrote:\n> > From: ZheNing Hu <adlternative@gmail.com>\n> >\n> > Historically, Git has supported the 'Signed-off-by' commit trailer\n> > using the '--signoff' and the '-s' option from the command line.\n> > But users may need to provide other trailer information from the\n> > command line such as \"Helped-by\", \"Reported-by\", \"Mentored-by\",\n> >\n> > Now implement a new `--trailer <token>[(=|:)<value>]` option to pass\n> > other trailers to `interpret-trailers` and insert them into commit\n> > messages.\n> >\n> > Signed-off-by: ZheNing Hu <adlternative@gmail.com>\n> > ---\n> >  Documentation/git-commit.txt |  10 +-\n> >  builtin/commit.c             |  23 +++\n> >  t/t7502-commit-porcelain.sh  | 336 +++++++++++++++++++++++++++++++++++\n> >  3 files changed, 368 insertions(+), 1 deletion(-)\n> >\n> > diff --git a/Documentation/git-commit.txt b/Documentation/git-commit.txt\n> > index 17150fa7eabe..c5de981cd40d 100644\n> > --- a/Documentation/git-commit.txt\n> > +++ b/Documentation/git-commit.txt\n> > @@ -14,7 +14,7 @@ SYNOPSIS\n> >          [--allow-empty-message] [--no-verify] [-e] [--author=<author>]\n> >          [--date=<date>] [--cleanup=<mode>] [--[no-]status]\n> >          [-i | -o] [--pathspec-from-file=<file> [--pathspec-file-nul]]\n> > -        [-S[<keyid>]] [--] [<pathspec>...]\n> > +        [-S[<keyid>]] [--] [<pathspec>...] [(--trailer <token>[(=|:)<value>])...]\n>\n> Please move all options before non-option arguments.\n> In other words, please move --trailer before [--].\n>\n> This form implies that there are no way to specify pathspec \"--trailer\"\n\nThanks, I didn't pay attention to this little detail before.\n\n>\n> >\n> >  DESCRIPTION\n> >  -----------\n> > @@ -166,6 +166,14 @@ The `-m` option is mutually exclusive with `-c`, `-C`, and `-F`.\n> >\n> >  include::signoff-option.txt[]\n> >\n> > +--trailer <token>[(=|:)<value>]::\n> > +     Specify a (<token>, <value>) pair that should be applied as a\n> > +     trailer. (e.g. `git commit --trailer \"Signed-off-by:C O Mitter \\\n> > +     <committer@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 in the commit message.)\n> > +     Use `git -c trailer.* commit --trailer` to make the appropriate\n> > +     configuration. See linkgit:git-interpret-trailers[1] for details.\n> >  -n::\n> >  --no-verify::\n> >       This option bypasses the pre-commit and commit-msg hooks.\n> > diff --git a/builtin/commit.c b/builtin/commit.c\n> > index 739110c5a7f6..7a79aae48f43 100644\n> > --- a/builtin/commit.c\n> > +++ b/builtin/commit.c\n> > @@ -113,6 +113,7 @@ static int config_commit_verbose = -1; /* unspecified */\n> >  static int no_post_rewrite, allow_empty_message, pathspec_file_nul;\n> >  static char *untracked_files_arg, *force_date, *ignore_submodule_arg, *ignored_arg;\n> >  static char *sign_commit, *pathspec_from_file;\n> > +static struct strvec trailer_args = STRVEC_INIT;\n> >\n> >  /*\n> >   * The default commit message cleanup mode will remove the lines\n> > @@ -131,6 +132,14 @@ 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(&trailer_args, \"--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> > @@ -958,6 +967,19 @@ static int prepare_to_commit(const char *index_file, const char *prefix,\n> >\n> >       fclose(s->fp);\n> >\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\", git_path_commit_editmsg(), 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 tailers to --trailers\"));\n>\n> s/tailers/trailers/ perhap?\n> Also we usually not put {} around single statement.\n>\n\nOK.\n\n> > +             }\n> > +             strvec_clear(&trailer_args);\n> > +     }\n> > +\n> >       /*\n> >        * Reject an attempt to record a non-merge empty commit without\n> >        * explicit --allow-empty. In the cherry-pick case, it may be\n> > @@ -1507,6 +1529,7 @@ int cmd_commit(int argc, const char **argv, const char *prefix)\n> >               OPT_STRING(0, \"fixup\", &fixup_message, N_(\"commit\"), N_(\"use autosquash formatted message to fixup 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\", NULL, N_(\"trailer\"), N_(\"add custom trailer(s)\"), PARSE_OPT_NONEG, opt_pass_trailer),\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> > diff --git a/t/t7502-commit-porcelain.sh b/t/t7502-commit-porcelain.sh\n> > index 6396897cc818..6df71fa00bcb 100755\n> > --- a/t/t7502-commit-porcelain.sh\n> > +++ b/t/t7502-commit-porcelain.sh\n> > @@ -154,6 +154,342 @@ test_expect_success 'sign off' '\n> >\n> >  '\n> >\n> > +test_expect_success 'commit --trailer without -c' '\n> > +     echo \"fun\" >>file &&\n> > +     git add file &&\n> > +     cat >expected <<-\\EOF &&\n> > +\n> > +     Signed-off-by: C O Mitter <committer@example.com>\n> > +     Signed-off-by: C1 E1\n> > +     Helped-by: C2 E2\n> > +     Reported-by: C3 E3\n> > +     Mentored-by: C4 E4\n> > +     EOF\n> > +     git commit -s --trailer \"Signed-off-by:C1 E1 \" \\\n> > +             --trailer \"Helped-by:C2 E2 \" \\\n> > +             --trailer \"Reported-by:C3 E3\" \\\n> > +             --trailer \"Mentored-by:C4 E4\" \\\n> > +             -m \"hello\" &&\n>\n> It's documented that we're supporting --trailer <token>[(=|:)<value>]\n> However, only --trailer <token>:<value> is tested.\n> I think it's better to have\n>\n>         --trailer \"Helped-by=C2 E2\" --trailer \"Reported-by\"\n>\n\nIn fact, I want to test in `test_expect_success'commit --trailer with\n-c and \"=\" as separators'`,\nbut some changes are needed.\n\n>\n> --\n> Danh\n\n-- \nZheNing Hu\n"},{"id":"419684","messageId":"CAOLTT8R-h5T4eqZ_T1BFXX++u_A=2uhOhdfc4Yz83effhWjeqg@mail.gmail.com","threadId":"55288","inReplyTo":"YFODnm4JKfqRn37H@danh.dev","subject":"Re: [PATCH v10 2/3] interpret-trailers: add own-identity option","fromName":"ZheNing Hu","fromEmail":"adlternative@gmail.com","sentAt":"2021-03-19T08:04:02Z","receivedAt":"2021-03-19T08:04:53Z","isPatch":true,"sender":{"key":"adlternative@gmail.com","avatar":"https://avatars.githubusercontent.com/u/58138461?v=4"},"body":"Đoàn Trần Công Danh <congdanhqx@gmail.com> 于2021年3月19日周五 上午12:45写道：\n>\n> On 2021-03-18 11:15:55+0000, ZheNing Hu via GitGitGadget <gitgitgadget@gmail.com> wrote:\n> > From: ZheNing Hu <adlternative@gmail.com>\n> >\n> > Beacuse `git commit --trailer=\"Signed-off-by: \\\n>\n> s/Beacuse/Because/\n>\n> And I think, it's easier to read if we write the command in its own\n> (indented) line.\n>\n> > $(git config user.name) <$(git config user.email)>\"`\n> > is difficult for users to add their own identities,\n> > so teach interpret-trailers a new option `--own-identity`\n> > which allow those trailers with no value add the user’s own\n> > identity. This will help the use of `commit --trailer` as\n> > easy as `--signoff`.\n>\n> Perhap, saying that we're optionalise <value> in --trailer, by\n> substitute user's identity if missing instead?\n\nIndeed so.\n\n>\n> > @@ -131,6 +144,7 @@ OPTIONS\n> >       when you know your input contains just the commit message itself\n> >       (and not an email or the output of `git format-patch`).\n> >\n> > +\n>\n> I think it's better to not add this line change\n>\n> >  CONFIGURATION VARIABLES\n> >  -----------------------\n> >\n> > diff --git a/builtin/interpret-trailers.c b/builtin/interpret-trailers.c\n> > index 84748eafc01b..be7f502a58d7 100644\n> > --- a/builtin/interpret-trailers.c\n> > +++ b/builtin/interpret-trailers.c\n>\n\nThanks for these kindful advices.\n\n> --\n> Danh\n\n--\nZheNing Hu\n"},{"id":"419687","messageId":"CAOLTT8RzvQzD0baWPdJzRLK3Q+WeJR_HNA4RVHMxRmwHeym9QQ@mail.gmail.com","threadId":"55288","inReplyTo":"xmqq1rcctgj7.fsf@gitster.g","subject":"Re: [PATCH v10 2/3] interpret-trailers: add own-identity option","fromName":"ZheNing Hu","fromEmail":"adlternative@gmail.com","sentAt":"2021-03-19T09:33:23Z","receivedAt":"2021-03-19T09:34:39Z","isPatch":true,"sender":{"key":"adlternative@gmail.com","avatar":"https://avatars.githubusercontent.com/u/58138461?v=4"},"body":"Junio C Hamano <gitster@pobox.com> 于2021年3月19日周五 上午3:20写道：\n>\n> \"ZheNing Hu via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n>\n> > From: ZheNing Hu <adlternative@gmail.com>\n> >\n> > Beacuse `git commit --trailer=\"Signed-off-by: \\\n> > $(git config user.name) <$(git config user.email)>\"`\n> > is difficult for users to add their own identities,\n> > so teach interpret-trailers a new option `--own-identity`\n> > which allow those trailers with no value add the user’s own\n> > identity. This will help the use of `commit --trailer` as\n> > easy as `--signoff`.\n>\n> I have a suspicion that this is too narrowly focused to be useful in\n> practice, and I find that the proposed \"--own-identity\" is quite a\n> mouthful.\n>\n\nWell, the original meaning of this `--trailer`and`--own-identity` option is to\nimitate the behavior of `--signoff` to create other trailers, For the\ntime being,\nit can only \"imitation\", it does not yet support the ability to provide multiple\nidentities instead of \"own-identity\".\n\nIf `--own-identity` is mouthful, is there a better name?\n\n> > +--own-identity::\n> > +     Used with `--trailer`. Those trailers without value with the\n> > +     `--own-identity` option all will add the user's own identity.\n>\n> So, the assumption here is that the name of the trailer tag alone,\n> without the ':' separator, can identify which trailer the user is\n> talking about, and it can be distinguished from the tag name plus ':'\n> and nothing else which is calling for a trailer entry with an empty\n> string as its value?\n>\n\nYes. `--own-ident` is for trailers without a separator.\n\n> OK.\n>\n> The reason why this looks too narrowly focused on oneself alone to\n> be useful to me is because I often need to add various -by trailers\n> to incoming patches, and have a script to do exactly that (which\n> does not use interpret-trailers, as I do not think\n> interpret-trailers can accept a patch email as its input, and the\n> script predates interpret-trailers) but it will be useless if that\n> script were limited to add -by for myself.\n>\n\nI understand it. But I think `--own-ident` is more inclined to add\nidentities for users. If users want to add other people’s identities,\nthey need to use another method.\n\n> Wouldn't it be a lot more useful if\n>\n>         git commit --trailer=\"Helped-by:@Ch.*Couder\"\n>\n> is expanded (note: I am not married to the syntax, but only for\n> illustration purposes, I am using \"a value prefixed by @ triggers\n> the 'name expansion'\" convention in this example.  People can come\n> up with better convention) by finding an author or a committer whose\n> name matches the given pattern from \"git log\"?  Then, instead of\n>\n>         git commit --own-identity --trailer=Signed-off-by\n>\n> I can say\n>\n>         git commit --trailer=Signed-off-by:@gitster\n>\n> and I can even add more than one, e.g.\n>\n>         git commit --trailer=Helped-by:@peff --trailer=Signed-off-by:@gitster\n>\n\nI agree that this idea can be extended again from \"own-identity \"\nmode to \"multiple-identity mode\",‘@’ seems to have a lot of\n important meanings in git, but I don’t know how to design a solution\n that is more suitable than yours for the time being.\n\n Another doubt  is what happens if one person corresponds to\n multiple mailboxes or one mailbox corresponds to multiple people?\n\nI think `--own-ident` may be a somewhat \"narrow\" option that can be\nkept. Also add a new option for interpret-trailers to resolve the identity\nof other people?\n\nThanks for suggestions.\n"},{"id":"419694","messageId":"pull.901.v11.git.1616155517590.gitgitgadget@gmail.com","threadId":"55288","inReplyTo":"pull.901.v10.git.1616066156.gitgitgadget@gmail.com","subject":"[PATCH v11] [GSOC] commit: add --trailer option","fromName":"ZheNing Hu via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2021-03-19T12:05:17Z","receivedAt":"2021-03-19T12:05:54Z","isPatch":true,"sender":{"key":"adlternative@gmail.com","avatar":"https://avatars.githubusercontent.com/u/58138461?v=4"},"body":"From: ZheNing Hu <adlternative@gmail.com>\n\nHistorically, Git has supported the 'Signed-off-by' commit trailer\nusing the '--signoff' and the '-s' option from the command line.\nBut users may need to provide other trailer information from the\ncommand line such as \"Helped-by\", \"Reported-by\", \"Mentored-by\",\n\nNow implement a new `--trailer <token>[(=|:)<value>]` option to pass\nother trailers to `interpret-trailers` and insert them into commit\nmessages.\n\nSigned-off-by: ZheNing Hu <adlternative@gmail.com>\n---\n    [GSOC] commit: add --trailer option\n    \n    Now maintainers or developers can also use commit\n    --trailer=\"Signed-off-by:commiter<email>\" from the command line to\n    provide trailers to commit messages. This solution may be more\n    generalized than v1.\n\nPublished-As: https://github.com/gitgitgadget/git/releases/tag/pr-901%2Fadlternative%2Fcommit-with-multiple-signatures-v11\nFetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-901/adlternative/commit-with-multiple-signatures-v11\nPull-Request: https://github.com/gitgitgadget/git/pull/901\n\nRange-diff vs v10:\n\n 1:  949faf9ee56a ! 1:  0db1210ffd6d [GSOC] commit: add --trailer option\n     @@ Documentation/git-commit.txt: SYNOPSIS\n       \t   [--date=<date>] [--cleanup=<mode>] [--[no-]status]\n       \t   [-i | -o] [--pathspec-from-file=<file> [--pathspec-file-nul]]\n      -\t   [-S[<keyid>]] [--] [<pathspec>...]\n     -+\t   [-S[<keyid>]] [--] [<pathspec>...] [(--trailer <token>[(=|:)<value>])...]\n     ++\t   [(--trailer <token>[(=|:)<value>])...] [-S[<keyid>]]\n     ++\t   [--] [<pathspec>...]\n       \n       DESCRIPTION\n       -----------\n     @@ builtin/commit.c: static int prepare_to_commit(const char *index_file, const cha\n      +\t\t\t     \"--in-place\", 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\t\tdie(_(\"unable to pass tailers to --trailers\"));\n     -+\t\t}\n     ++\t\tif (run_command(&run_trailer))\n     ++\t\t\tdie(_(\"unable to pass trailers to --trailers\"));\n      +\t\tstrvec_clear(&trailer_args);\n      +\t}\n      +\n     @@ t/t7502-commit-porcelain.sh: test_expect_success 'sign off' '\n      +\ttest_cmp expected actual\n      +'\n      +\n     -+test_expect_success 'commit --trailer with -c and \"=\" as separators' '\n     ++test_expect_success 'commit --trailer with \"=\" ' '\n      +\techo \"fun\" >>file1 &&\n      +\tgit add file1 &&\n      +\tcat >expected <<-\\EOF &&\n      +\n     -+\t\tAcked-by= Peff\n     ++\t\tAcked-by: Peff\n      +\tEOF\n     -+\tgit -c trailer.separators=\"=\" \\\n     -+\t\t-c trailer.ack.key=\"Acked-by= \" \\\n     ++\tgit -c trailer.ack.key=\"Acked-by\" \\\n      +\t\tcommit --trailer \"ack = Peff\" -m \"hello\" &&\n      +\tgit cat-file commit HEAD >commit.msg &&\n      +\tsed -e \"1,6d\" commit.msg >actual &&\n 2:  42590e95deee < -:  ------------ interpret-trailers: add own-identity option\n 3:  2dfcc20f0e9f < -:  ------------ commit: add own-identity option\n\n\n Documentation/git-commit.txt |  11 +-\n builtin/commit.c             |  22 +++\n t/t7502-commit-porcelain.sh  | 335 +++++++++++++++++++++++++++++++++++\n 3 files changed, 367 insertions(+), 1 deletion(-)\n\ndiff --git a/Documentation/git-commit.txt b/Documentation/git-commit.txt\nindex 17150fa7eabe..2ba2fe0dd10e 100644\n--- a/Documentation/git-commit.txt\n+++ b/Documentation/git-commit.txt\n@@ -14,7 +14,8 @@ SYNOPSIS\n \t   [--allow-empty-message] [--no-verify] [-e] [--author=<author>]\n \t   [--date=<date>] [--cleanup=<mode>] [--[no-]status]\n \t   [-i | -o] [--pathspec-from-file=<file> [--pathspec-file-nul]]\n-\t   [-S[<keyid>]] [--] [<pathspec>...]\n+\t   [(--trailer <token>[(=|:)<value>])...] [-S[<keyid>]]\n+\t   [--] [<pathspec>...]\n \n DESCRIPTION\n -----------\n@@ -166,6 +167,14 @@ The `-m` option is mutually exclusive with `-c`, `-C`, and `-F`.\n \n include::signoff-option.txt[]\n \n+--trailer <token>[(=|:)<value>]::\n+\tSpecify a (<token>, <value>) pair that should be applied as a\n+\ttrailer. (e.g. `git commit --trailer \"Signed-off-by:C O Mitter \\\n+\t<committer@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 in the commit message.)\n+\tUse `git -c trailer.* commit --trailer` to make the appropriate\n+\tconfiguration. See linkgit:git-interpret-trailers[1] for details.\n -n::\n --no-verify::\n \tThis option bypasses the pre-commit and commit-msg hooks.\ndiff --git a/builtin/commit.c b/builtin/commit.c\nindex 739110c5a7f6..4b06672bd07d 100644\n--- a/builtin/commit.c\n+++ b/builtin/commit.c\n@@ -113,6 +113,7 @@ static int config_commit_verbose = -1; /* unspecified */\n static int no_post_rewrite, allow_empty_message, pathspec_file_nul;\n static char *untracked_files_arg, *force_date, *ignore_submodule_arg, *ignored_arg;\n static char *sign_commit, *pathspec_from_file;\n+static struct strvec trailer_args = STRVEC_INIT;\n \n /*\n  * The default commit message cleanup mode will remove the lines\n@@ -131,6 +132,14 @@ 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(&trailer_args, \"--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@@ -958,6 +967,18 @@ static int prepare_to_commit(const char *index_file, const char *prefix,\n \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\", 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\t\tdie(_(\"unable to pass trailers to --trailers\"));\n+\t\tstrvec_clear(&trailer_args);\n+\t}\n+\n \t/*\n \t * Reject an attempt to record a non-merge empty commit without\n \t * explicit --allow-empty. In the cherry-pick case, it may be\n@@ -1507,6 +1528,7 @@ int cmd_commit(int argc, const char **argv, const char *prefix)\n \t\tOPT_STRING(0, \"fixup\", &fixup_message, N_(\"commit\"), N_(\"use autosquash formatted message to fixup 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\", NULL, N_(\"trailer\"), N_(\"add custom trailer(s)\"), PARSE_OPT_NONEG, opt_pass_trailer),\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/t/t7502-commit-porcelain.sh b/t/t7502-commit-porcelain.sh\nindex 6396897cc818..024cf3c81b18 100755\n--- a/t/t7502-commit-porcelain.sh\n+++ b/t/t7502-commit-porcelain.sh\n@@ -154,6 +154,341 @@ test_expect_success 'sign off' '\n \n '\n \n+test_expect_success 'commit --trailer without -c' '\n+\techo \"fun\" >>file &&\n+\tgit add file &&\n+\tcat >expected <<-\\EOF &&\n+\n+\tSigned-off-by: C O Mitter <committer@example.com>\n+\tSigned-off-by: C1 E1\n+\tHelped-by: C2 E2\n+\tReported-by: C3 E3\n+\tMentored-by: C4 E4\n+\tEOF\n+\tgit commit -s --trailer \"Signed-off-by:C1 E1 \" \\\n+\t\t--trailer \"Helped-by:C2 E2 \" \\\n+\t\t--trailer \"Reported-by:C3 E3\" \\\n+\t\t--trailer \"Mentored-by:C4 E4\" \\\n+\t\t-m \"hello\" &&\n+\tgit cat-file commit HEAD >commit.msg &&\n+\tsed -e \"1,6d\" commit.msg >actual &&\n+\ttest_cmp expected actual\n+'\n+\n+test_expect_success 'commit --trailer with -c and \"replace\" as ifexists' '\n+\techo \"fun\" >>file1 &&\n+\tgit add file1 &&\n+\tcat >expected <<-\\EOF &&\n+\n+\tSigned-off-by: C O Mitter <committer@example.com>\n+\tSigned-off-by: C1 E1\n+\tReported-by: C3 E3\n+\tMentored-by: C4 E4\n+\tHelped-by: C3 E3\n+\tEOF\n+\tgit -c trailer.ifexists=\"replace\" \\\n+\t\tcommit --trailer \"Mentored-by: C4 E4\" \\\n+\t\t --trailer \"Helped-by: C3 E3\" \\\n+\t\t--amend &&\n+\tgit cat-file commit HEAD >commit.msg &&\n+\tsed -e \"1,6d\" commit.msg >actual &&\n+\ttest_cmp expected actual\n+'\n+\n+test_expect_success 'commit --trailer with -c and \"add\" as ifexists' '\n+\techo \"fun\" >>file1 &&\n+\tgit add file1 &&\n+\tcat >expected <<-\\EOF &&\n+\n+\tSigned-off-by: C O Mitter <committer@example.com>\n+\tSigned-off-by: C1 E1\n+\tReported-by: C3 E3\n+\tMentored-by: C4 E4\n+\tHelped-by: C3 E3\n+\tHelped-by: C3 E3\n+\tHelped-by: C3 E3\n+\tEOF\n+\tgit -c trailer.ifexists=\"add\" \\\n+\t\tcommit --trailer \"Helped-by: C3 E3\" \\\n+\t\t--trailer \"Helped-by: C3 E3\" \\\n+\t\t--amend &&\n+\tgit cat-file commit HEAD >commit.msg &&\n+\tsed -e \"1,6d\" commit.msg >actual &&\n+\ttest_cmp expected actual\n+'\n+\n+test_expect_success 'commit --trailer with -c and \"donothing\" as ifexists' '\n+\techo \"fun\" >>file1 &&\n+\tgit add file1 &&\n+\tcat >expected <<-\\EOF &&\n+\n+\tSigned-off-by: C O Mitter <committer@example.com>\n+\tSigned-off-by: C1 E1\n+\tReported-by: C3 E3\n+\tMentored-by: C4 E4\n+\tHelped-by: C3 E3\n+\tHelped-by: C3 E3\n+\tHelped-by: C3 E3\n+\tReviewed-by: C6 E6\n+\tEOF\n+\tgit -c trailer.ifexists=\"donothing\" \\\n+\t\tcommit --trailer \"Mentored-by: C5 E5\" \\\n+\t\t--trailer \"Reviewed-by: C6 E6\" \\\n+\t\t--amend &&\n+\tgit cat-file commit HEAD >commit.msg &&\n+\tsed -e \"1,6d\" commit.msg >actual &&\n+\ttest_cmp expected actual\n+'\n+\n+test_expect_success 'commit --trailer with -c and \"addIfDifferent\" as ifexists' '\n+\techo \"fun\" >>file1 &&\n+\tgit add file1 &&\n+\tcat >expected <<-\\EOF &&\n+\n+\tSigned-off-by: C O Mitter <committer@example.com>\n+\tSigned-off-by: C1 E1\n+\tReported-by: C3 E3\n+\tMentored-by: C4 E4\n+\tHelped-by: C3 E3\n+\tHelped-by: C3 E3\n+\tHelped-by: C3 E3\n+\tReviewed-by: C6 E6\n+\tReported-by: C5 E5\n+\tEOF\n+\tgit -c trailer.ifexists=\"addIfDifferent\" \\\n+\t\tcommit --trailer \"Reviewed-by: C6 E6\" \\\n+\t\t--trailer \"Reported-by: C5 E5\" \\\n+\t\t--amend &&\n+\tgit cat-file commit HEAD >commit.msg &&\n+\tsed -e \"1,6d\" commit.msg >actual &&\n+\ttest_cmp expected actual\n+'\n+\n+test_expect_success 'commit --trailer with -c and \"addIfDifferentNeighbor\" as ifexists' '\n+\techo \"fun\" >>file1 &&\n+\tgit add file1 &&\n+\tcat >expected <<-\\EOF &&\n+\n+\tSigned-off-by: C O Mitter <committer@example.com>\n+\tSigned-off-by: C1 E1\n+\tReported-by: C3 E3\n+\tMentored-by: C4 E4\n+\tHelped-by: C3 E3\n+\tHelped-by: C3 E3\n+\tHelped-by: C3 E3\n+\tReviewed-by: C6 E6\n+\tReported-by: C5 E5\n+\tEOF\n+\tgit -c trailer.ifexists=\"addIfDifferent\" \\\n+\t\tcommit --trailer \"Reported-by: C5 E5\" \\\n+\t\t--trailer \"Reviewed-by: C6 E6\" \\\n+\t\t--amend &&\n+\tgit cat-file commit HEAD >commit.msg &&\n+\tsed -e \"1,6d\" commit.msg >actual &&\n+\ttest_cmp expected actual\n+'\n+\n+test_expect_success 'commit --trailer with -c and \"end\" as where' '\n+\techo \"fun\" >>file1 &&\n+\tgit add file1 &&\n+\tcat >expected <<-\\EOF &&\n+\n+\tSigned-off-by: C O Mitter <committer@example.com>\n+\tSigned-off-by: C1 E1\n+\tReported-by: C3 E3\n+\tMentored-by: C4 E4\n+\tHelped-by: C3 E3\n+\tHelped-by: C3 E3\n+\tHelped-by: C3 E3\n+\tReviewed-by: C6 E6\n+\tReported-by: C5 E5\n+\tReported-by: C7 E7\n+\tEOF\n+\tgit -c trailer.where=\"end\" \\\n+\t\tcommit --trailer \"Reported-by: C5 E5\" \\\n+\t\t--trailer \"Reported-by: C7 E7\" \\\n+\t\t--amend &&\n+\tgit cat-file commit HEAD >commit.msg &&\n+\tsed -e \"1,6d\" commit.msg >actual &&\n+\ttest_cmp expected actual\n+'\n+\n+test_expect_success 'commit --trailer with -c and \"start\" as where' '\n+\techo \"fun\" >>file1 &&\n+\tgit add file1 &&\n+\tcat >expected <<-\\EOF &&\n+\n+\tSigned-off-by: C8 E8\n+\tSigned-off-by: C O Mitter <committer@example.com>\n+\tSigned-off-by: C1 E1\n+\tReported-by: C3 E3\n+\tMentored-by: C4 E4\n+\tHelped-by: C3 E3\n+\tHelped-by: C3 E3\n+\tHelped-by: C3 E3\n+\tReviewed-by: C6 E6\n+\tReported-by: C5 E5\n+\tReported-by: C7 E7\n+\tEOF\n+\tgit -c trailer.where=\"start\" \\\n+\t\tcommit --trailer \"Signed-off-by: C8 E8\" \\\n+\t\t--trailer \"Signed-off-by: C8 E8\" \\\n+\t\t--amend &&\n+\tgit cat-file commit HEAD >commit.msg &&\n+\tsed -e \"1,6d\" commit.msg >actual &&\n+\ttest_cmp expected actual\n+'\n+\n+test_expect_success 'commit --trailer with -c and \"after\" as where' '\n+\techo \"fun\" >>file1 &&\n+\tgit add file1 &&\n+\tcat >expected <<-\\EOF &&\n+\n+\tSigned-off-by: C8 E8\n+\tSigned-off-by: C O Mitter <committer@example.com>\n+\tSigned-off-by: C1 E1\n+\tReported-by: C3 E3\n+\tMentored-by: C4 E4\n+\tMentored-by: C9 E9\n+\tHelped-by: C3 E3\n+\tHelped-by: C3 E3\n+\tHelped-by: C3 E3\n+\tReviewed-by: C6 E6\n+\tReported-by: C5 E5\n+\tReported-by: C7 E7\n+\tReported-by: C10 E10\n+\tEOF\n+\tgit -c trailer.where=\"after\" \\\n+\t\tcommit --trailer \"Mentored-by: C9 E9\" \\\n+\t\t--trailer \"Reported-by: C10 E10\" \\\n+\t\t--amend &&\n+\tgit cat-file commit HEAD >commit.msg &&\n+\tsed -e \"1,6d\" commit.msg >actual &&\n+\ttest_cmp expected actual\n+'\n+\n+test_expect_success 'commit --trailer with -c and \"before\" as where' '\n+\techo \"fun\" >>file1 &&\n+\tgit add file1 &&\n+\tcat >expected <<-\\EOF &&\n+\n+\tSigned-off-by: C8 E8\n+\tSigned-off-by: C O Mitter <committer@example.com>\n+\tSigned-off-by: C1 E1\n+\tReported-by: C11 E11\n+\tReported-by: C3 E3\n+\tMentored-by: C4 E4\n+\tMentored-by: C9 E9\n+\tHelped-by: C12 E12\n+\tHelped-by: C3 E3\n+\tHelped-by: C3 E3\n+\tHelped-by: C3 E3\n+\tReviewed-by: C6 E6\n+\tReported-by: C5 E5\n+\tReported-by: C7 E7\n+\tReported-by: C10 E10\n+\tEOF\n+\tgit -c trailer.where=\"before\" \\\n+\t\tcommit --trailer \"Helped-by: C12 E12\" \\\n+\t\t--trailer \"Reported-by: C11 E11\" \\\n+\t\t--amend &&\n+\tgit cat-file commit HEAD >commit.msg &&\n+\tsed -e \"1,6d\" commit.msg >actual &&\n+\ttest_cmp expected actual\n+'\n+\n+test_expect_success 'commit --trailer with -c and \"donothing\" as ifmissing' '\n+\techo \"fun\" >>file1 &&\n+\tgit add file1 &&\n+\tcat >expected <<-\\EOF &&\n+\n+\tSigned-off-by: C8 E8\n+\tSigned-off-by: C O Mitter <committer@example.com>\n+\tSigned-off-by: C1 E1\n+\tReported-by: C11 E11\n+\tReported-by: C3 E3\n+\tMentored-by: C4 E4\n+\tMentored-by: C9 E9\n+\tHelped-by: C12 E12\n+\tHelped-by: C3 E3\n+\tHelped-by: C3 E3\n+\tHelped-by: C3 E3\n+\tReviewed-by: C6 E6\n+\tReported-by: C5 E5\n+\tReported-by: C7 E7\n+\tReported-by: C10 E10\n+\tHelped-by: C12 E12\n+\tEOF\n+\tgit -c trailer.ifmissing=\"donothing\" \\\n+\t\tcommit --trailer \"Helped-by: C12 E12\" \\\n+\t\t--trailer \"Based-by: C13 E13\" \\\n+\t\t--amend &&\n+\tgit cat-file commit HEAD >commit.msg &&\n+\tsed -e \"1,6d\" commit.msg >actual &&\n+\ttest_cmp expected actual\n+'\n+\n+test_expect_success 'commit --trailer with -c and \"add\" as ifmissing' '\n+\techo \"fun\" >>file1 &&\n+\tgit add file1 &&\n+\tcat >expected <<-\\EOF &&\n+\n+\tSigned-off-by: C8 E8\n+\tSigned-off-by: C O Mitter <committer@example.com>\n+\tSigned-off-by: C1 E1\n+\tReported-by: C11 E11\n+\tReported-by: C3 E3\n+\tMentored-by: C4 E4\n+\tMentored-by: C9 E9\n+\tHelped-by: C12 E12\n+\tHelped-by: C3 E3\n+\tHelped-by: C3 E3\n+\tHelped-by: C3 E3\n+\tReviewed-by: C6 E6\n+\tReported-by: C5 E5\n+\tReported-by: C7 E7\n+\tReported-by: C10 E10\n+\tHelped-by: C12 E12\n+\tBased-by: C13 E13\n+\tEOF\n+\tgit -c trailer.ifmissing=\"add\" \\\n+\t\tcommit --trailer \"Helped-by: C12 E12\" \\\n+\t\t--trailer \"Based-by: C13 E13\" \\\n+\t\t--amend &&\n+\tgit cat-file commit HEAD >commit.msg &&\n+\tsed -e \"1,6d\" commit.msg >actual &&\n+\ttest_cmp expected actual\n+'\n+\n+test_expect_success 'commit --trailer with \"=\" ' '\n+\techo \"fun\" >>file1 &&\n+\tgit add file1 &&\n+\tcat >expected <<-\\EOF &&\n+\n+\t\tAcked-by: Peff\n+\tEOF\n+\tgit -c trailer.ack.key=\"Acked-by\" \\\n+\t\tcommit --trailer \"ack = Peff\" -m \"hello\" &&\n+\tgit cat-file commit HEAD >commit.msg &&\n+\tsed -e \"1,6d\" commit.msg >actual &&\n+\ttest_cmp expected actual\n+'\n+\n+test_expect_success 'commit --trailer with -c and \":=#\" as separators' '\n+\techo \"fun\" >>file1 &&\n+\tgit add file1 &&\n+\tcat >expected <<-\\EOF &&\n+\n+\t\tBug #42\n+\tEOF\n+\tgit -c trailer.separators=\":=#\" \\\n+\t\t-c trailer.bug.key=\"Bug #\" \\\n+\t\tcommit --trailer \"bug = 42\" -m \"I hate bug\" &&\n+\tgit cat-file commit HEAD >commit.msg &&\n+\tsed -e \"1,6d\" commit.msg >actual &&\n+\ttest_cmp expected actual\n+'\n+\n test_expect_success 'multiple -m' '\n \n \t>negative &&\n\nbase-commit: 13d7ab6b5d7929825b626f050b62a11241ea4945\n-- \ngitgitgadget\n"},{"id":"419713","messageId":"xmqq7dm3p344.fsf@gitster.g","threadId":"55288","inReplyTo":"CAOLTT8RzvQzD0baWPdJzRLK3Q+WeJR_HNA4RVHMxRmwHeym9QQ@mail.gmail.com","subject":"Re: [PATCH v10 2/3] interpret-trailers: add own-identity option","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2021-03-19T15:36:27Z","receivedAt":"2021-03-19T15:37:14Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"ZheNing Hu <adlternative@gmail.com> writes:\n\n> If `--own-identity` is mouthful, is there a better name?\n\nI originally had \"perhaps XXXX?\" in the message you are responding\nto, but I hoped that the message, especially the examples at the\nend, would be sufficient to make you realize that the option itself\nis not such a great idea (an additional \":@gitster\" in whatever\nsyntax used would be even shorter than \"--own-identity\", and\nobviously more flexible in that it can name other people).\n\nIf you really want to have this option, perhaps call it \"--self\"?  I\nstill do not think it is a good idea, though.\n"},{"id":"419721","messageId":"xmqq4kh7nifp.fsf@gitster.g","threadId":"55288","inReplyTo":"pull.901.v11.git.1616155517590.gitgitgadget@gmail.com","subject":"Re: [PATCH v11] [GSOC] commit: add --trailer option","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2021-03-19T17:48:26Z","receivedAt":"2021-03-19T17:49:28Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"\"ZheNing Hu via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n\n> +--trailer <token>[(=|:)<value>]::\n> +\tSpecify a (<token>, <value>) pair that should be applied as a\n> +\ttrailer. (e.g. `git commit --trailer \"Signed-off-by:C O Mitter \\\n> +\t<committer@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 in the commit message.)\n\ns/in the commit message/to the commit message/ probably.\n\n> +\tUse `git -c trailer.* commit --trailer` to make the appropriate\n> +\tconfiguration. See linkgit:git-interpret-trailers[1] for details.\n\nI doubt this is a good advice for a few reasons.\n\n (1) The \"git -c var=val\" is meant to be used as a single-shot\n     oddball configuration.  If the user will be working on the\n     project long enough to be worth using the --trailer option\n     (otherwise a single-shot drive-by patch can just add these\n     trailers while editing the log message in the editor), the user\n     would not want to use \"git -c var=val\" mechanism to use\n     different configuration every time the --trailer option is\n     used.\n\n (2) The \"appropriate configuration\" is too vague and does not give\n     enough incentive to the reader to go look in the other manual\n     page.  At least there should be a cursory mention of what kind\n     of things are possible by the configuration.\n\nPrehaps\n\n    The `trailer.*` configuration variables (see linkgit:...) can be\n    used to define if a duplicated trailer is omitted, where in the\n    run of trailers each trailer would appear, and other details.\n\nor something along the line (Christian would be a better person to\nsuggest what good examples are than I am, though).\n\n> diff --git a/builtin/commit.c b/builtin/commit.c\n> index 739110c5a7f6..4b06672bd07d 100644\n> --- a/builtin/commit.c\n> +++ b/builtin/commit.c\n> @@ -113,6 +113,7 @@ static int config_commit_verbose = -1; /* unspecified */\n>  static int no_post_rewrite, allow_empty_message, pathspec_file_nul;\n>  static char *untracked_files_arg, *force_date, *ignore_submodule_arg, *ignored_arg;\n>  static char *sign_commit, *pathspec_from_file;\n> +static struct strvec trailer_args = STRVEC_INIT;\n>  \n>  /*\n>   * The default commit message cleanup mode will remove the lines\n> @@ -131,6 +132,14 @@ 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(&trailer_args, \"--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> @@ -958,6 +967,18 @@ static int prepare_to_commit(const char *index_file, const char *prefix,\n>  \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\", 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\t\tdie(_(\"unable to pass trailers to --trailers\"));\n> +\t\tstrvec_clear(&trailer_args);\n\nOK.  run_command() cleans the run_trailer.args when it returns, so\nwe only need to clear our own array here.\n\n> +\t}\n> +\n\n> diff --git a/t/t7502-commit-porcelain.sh b/t/t7502-commit-porcelain.sh\n> index 6396897cc818..024cf3c81b18 100755\n> --- a/t/t7502-commit-porcelain.sh\n> +++ b/t/t7502-commit-porcelain.sh\n> @@ -154,6 +154,341 @@ test_expect_success 'sign off' '\n>  \n>  '\n>  \n> +test_expect_success 'commit --trailer without -c' '\n> +\techo \"fun\" >>file &&\n> +\tgit add file &&\n> +\tcat >expected <<-\\EOF &&\n> +\n> +\tSigned-off-by: C O Mitter <committer@example.com>\n> +\tSigned-off-by: C1 E1\n> +\tHelped-by: C2 E2\n> +\tReported-by: C3 E3\n> +\tMentored-by: C4 E4\n> +\tEOF\n> +\tgit commit -s --trailer \"Signed-off-by:C1 E1 \" \\\n> +\t\t--trailer \"Helped-by:C2 E2 \" \\\n> +\t\t--trailer \"Reported-by:C3 E3\" \\\n> +\t\t--trailer \"Mentored-by:C4 E4\" \\\n> +\t\t-m \"hello\" &&\n> +\tgit cat-file commit HEAD >commit.msg &&\n> +\tsed -e \"1,6d\" commit.msg >actual &&\n\nThis 1,6d depends on the exact line organization of the underlying\nobject detail, which is not good.  You'd want to grab the run of the\nconsecutive non-empty lines at the end, so that commit object headers\nand the log message \"fun\" can change over time by changes to Git and\nchanges to this test, without breaking this test.\n\n> +\ttest_cmp expected actual\n> +'\n> +\n> +test_expect_success 'commit --trailer with -c and \"replace\" as ifexists' '\n> +\techo \"fun\" >>file1 &&\n> +\tgit add file1 &&\n> +\tcat >expected <<-\\EOF &&\n> +\n> +\tSigned-off-by: C O Mitter <committer@example.com>\n> +\tSigned-off-by: C1 E1\n> +\tReported-by: C3 E3\n> +\tMentored-by: C4 E4\n> +\tHelped-by: C3 E3\n> +\tEOF\n> +\tgit -c trailer.ifexists=\"replace\" \\\n> +\t\tcommit --trailer \"Mentored-by: C4 E4\" \\\n> +\t\t --trailer \"Helped-by: C3 E3\" \\\n> +\t\t--amend &&\n> +\tgit cat-file commit HEAD >commit.msg &&\n> +\tsed -e \"1,6d\" commit.msg >actual &&\n> +\ttest_cmp expected actual\n\nThe same comment applies, and also by using \"--amend\", this relies\non the outcome of the previous test, which is not great.\n\n> +'\n> +\n> +test_expect_success 'commit --trailer with -c and \"add\" as ifexists' '\n> +\techo \"fun\" >>file1 &&\n> +\tgit add file1 &&\n> +\tcat >expected <<-\\EOF &&\n> +\n> +\tSigned-off-by: C O Mitter <committer@example.com>\n> +\tSigned-off-by: C1 E1\n> +\tReported-by: C3 E3\n> +\tMentored-by: C4 E4\n> +\tHelped-by: C3 E3\n> +\tHelped-by: C3 E3\n> +\tHelped-by: C3 E3\n> +\tEOF\n> +\tgit -c trailer.ifexists=\"add\" \\\n> +\t\tcommit --trailer \"Helped-by: C3 E3\" \\\n> +\t\t--trailer \"Helped-by: C3 E3\" \\\n> +\t\t--amend &&\n\nAnd it makes things worse by keep amending.\n\nAt least, establish a baseline commit that has known set of\ntrailers, tag it, and reset the HEAD to that commit at the beginning\nof each test that tries to amend an existing commit.  That way, the\ncorrectness of each individual test would depend only on the test\nthat creates the baseline commit and tags it.\n\n"},{"id":"419761","messageId":"CAOLTT8TQdT1PkdfuOk92U89Lgk9o_YhdeMyHb6SrE7GW+6PQJQ@mail.gmail.com","threadId":"55288","inReplyTo":"xmqq7dm3p344.fsf@gitster.g","subject":"Re: [PATCH v10 2/3] interpret-trailers: add own-identity option","fromName":"ZheNing Hu","fromEmail":"adlternative@gmail.com","sentAt":"2021-03-20T02:54:38Z","receivedAt":"2021-03-20T02:55:59Z","isPatch":true,"sender":{"key":"adlternative@gmail.com","avatar":"https://avatars.githubusercontent.com/u/58138461?v=4"},"body":"Junio C Hamano <gitster@pobox.com> 于2021年3月19日周五 下午11:36写道：\n>\n> ZheNing Hu <adlternative@gmail.com> writes:\n>\n> > If `--own-identity` is mouthful, is there a better name?\n>\n> I originally had \"perhaps XXXX?\" in the message you are responding\n> to, but I hoped that the message, especially the examples at the\n> end, would be sufficient to make you realize that the option itself\n> is not such a great idea (an additional \":@gitster\" in whatever\n> syntax used would be even shorter than \"--own-identity\", and\n> obviously more flexible in that it can name other people).\n>\n> If you really want to have this option, perhaps call it \"--self\"?  I\n> still do not think it is a good idea, though.\n\nFine, If this option is really not particularly useful, I am willing\nto give it up.\n\nI am looking for how to extract each author or committer from the log.\nI just know I can use:\n\n$ git log --pretty=\"%an %ae\" | sort | uniq\n\nget all unique author <email> from a git repo.\nIs there any function in the source code of git to get them?\n\nI notice Ævar Arnfjörð Bjarmason mentioned `.mailmap`, I have seen the\n `.mailmap` under git repo. Would it be better to extract the author\nor committer\n from  the .mailmap than to extract it from git log?\nIf so, the operation of the trailer may depend on the contents of `.mailmap`.\nIf there is no `.mailmap` in the user git repo, then the identity in the trailer\ncannot be parsed.\n\nThanks.\n"},{"id":"419764","messageId":"YFWCvePMGvBY/I9v@coredump.intra.peff.net","threadId":"55288","inReplyTo":"CAOLTT8TQdT1PkdfuOk92U89Lgk9o_YhdeMyHb6SrE7GW+6PQJQ@mail.gmail.com","subject":"Re: [PATCH v10 2/3] interpret-trailers: add own-identity option","fromName":"Jeff King","fromEmail":"peff@peff.net","sentAt":"2021-03-20T05:06:05Z","receivedAt":"2021-03-20T05:25:24Z","isPatch":true,"sender":{"key":"peff@peff.net","avatar":"https://avatars.githubusercontent.com/u/45925?v=4"},"body":"On Sat, Mar 20, 2021 at 10:54:38AM +0800, ZheNing Hu wrote:\n\n> I am looking for how to extract each author or committer from the log.\n> I just know I can use:\n> \n> $ git log --pretty=\"%an %ae\" | sort | uniq\n> \n> get all unique author <email> from a git repo.\n> Is there any function in the source code of git to get them?\n\nIf I understand Junio's suggestion correctly, it is very similar to how\n\"commit --author\" works. See how it calls find_author_by_nickname(),\nwhich finds the first commit matching the name, and then pulls out the\nfull name from format_commit_message().\n\n-Peff\n"},{"id":"419765","messageId":"xmqqzgyyjoyy.fsf@gitster.g","threadId":"55288","inReplyTo":"CAOLTT8T54RHPm8P=mJ3vOKZPEwpJQgnbeU3VqczWbPSk36wZcA@mail.gmail.com","subject":"Re: [PATCH v10 2/3] interpret-trailers: add own-identity option","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2021-03-20T06:53:09Z","receivedAt":"2021-03-20T11:13:04Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"ZheNing Hu <adlternative@gmail.com> writes:\n\n> In actually ,\n>\n> @@ -1071,6 +1071,7 @@ static const char *find_author_by_nickname(const\n> char *name)\n>                 strbuf_release(&buf);\n>                 format_commit_message(commit, \"%aN <%aE>\", &buf, &ctx);\n>                 clear_mailmap(&mailmap);\n> +               reset_revision_walk();\n>                 return strbuf_detach(&buf, NULL);\n>         }\n>\n> then we can reuse this function.\n>\n> But I think I can give find_author_by_nickname another arg for choice if we want\n>  `reset_revision_walk()`.\n\nThat is half fixing and half breaking.  It would allow us to call\nthe helper number of times as long as there is no other revision\ntraversal is in progress; calling reset_revision_walk() would mean\nany and all revision traversal in progress will be broken.  So we\ncannot use the helper to tweak each and every commit we encounter\nwhile running \"git log\", for example.  Imagine adding an option to\n\"git format-patch\" to allow each commit it formats to be tweaked by\nadding \"--trailers=foo:@ZheNing\" and the like.  There is one primary\ntraversal that is used to list which commit to format in what order,\nand every time that primary traversal yields a commit, if we run\nfind_author_by_nickname(), we end up initiating another traversal,\nand then by calling reset, we clear all object flags that are used\nfor revision traversal, thereby breaking the primary traversal.\n\nThe only safe way to introduce a generally usable helper (without\nrewriting the revision traversal machinery) is to spawn a subprocess\nand do an equivalent of find_author_by_nickname() in it.\n\nA standalone \"interpret-trailers\" command, as long as it won't do\nany other revision traversal, would not have such a problem, and\ncalling reset every time find_author_by_nickname() is called may be\nsufficient.  The only thing I care about is *not* to pretend that\nfind_author_by_nickname() plus reset() is the generally reusable\nhelper function and advertise it as such, which will mislead future\ndevelopers into misusing the function in a context they shouldn't\n(i.e. while they are performing their own revision traversal).\n\nThanks.\n"},{"id":"419767","messageId":"CAOLTT8TUtYxciGr-HjRM9604HQfZDHSGrpTyVia-QUFjZvHrpA@mail.gmail.com","threadId":"55288","inReplyTo":"xmqqzgyyjoyy.fsf@gitster.g","subject":"Re: [PATCH v10 2/3] interpret-trailers: add own-identity option","fromName":"ZheNing Hu","fromEmail":"adlternative@gmail.com","sentAt":"2021-03-20T08:43:20Z","receivedAt":"2021-03-20T11:15:11Z","isPatch":true,"sender":{"key":"adlternative@gmail.com","avatar":"https://avatars.githubusercontent.com/u/58138461?v=4"},"body":"Junio C Hamano <gitster@pobox.com> 于2021年3月20日周六 下午2:53写道：\n>\n> ZheNing Hu <adlternative@gmail.com> writes:\n>\n> > In actually ,\n> >\n> > @@ -1071,6 +1071,7 @@ static const char *find_author_by_nickname(const\n> > char *name)\n> >                 strbuf_release(&buf);\n> >                 format_commit_message(commit, \"%aN <%aE>\", &buf, &ctx);\n> >                 clear_mailmap(&mailmap);\n> > +               reset_revision_walk();\n> >                 return strbuf_detach(&buf, NULL);\n> >         }\n> >\n> > then we can reuse this function.\n> >\n> > But I think I can give find_author_by_nickname another arg for choice if we want\n> >  `reset_revision_walk()`.\n>\n> That is half fixing and half breaking.  It would allow us to call\n> the helper number of times as long as there is no other revision\n> traversal is in progress; calling reset_revision_walk() would mean\n> any and all revision traversal in progress will be broken.  So we\n> cannot use the helper to tweak each and every commit we encounter\n> while running \"git log\", for example.  Imagine adding an option to\n> \"git format-patch\" to allow each commit it formats to be tweaked by\n> adding \"--trailers=foo:@ZheNing\" and the like.  There is one primary\n> traversal that is used to list which commit to format in what order,\n> and every time that primary traversal yields a commit, if we run\n> find_author_by_nickname(), we end up initiating another traversal,\n> and then by calling reset, we clear all object flags that are used\n> for revision traversal, thereby breaking the primary traversal.\n>\n\nI understand what you meaning. I may not be very thorough in considering\n the potential problems. This is as dangerous as calling `list_del` in the\n middle of `list_for_each`.\n\n> The only safe way to introduce a generally usable helper (without\n> rewriting the revision traversal machinery) is to spawn a subprocess\n> and do an equivalent of find_author_by_nickname() in it.\n>\n\nIf I didn't notice what you mentioned later, I might think about it\nfor a long time.\nExecute a child process specifically to execute `find_author_by_nickname()`,\nwhich feels a bit wasteful.\n\n> A standalone \"interpret-trailers\" command, as long as it won't do\n> any other revision traversal, would not have such a problem, and\n> calling reset every time find_author_by_nickname() is called may be\n> sufficient.  The only thing I care about is *not* to pretend that\n> find_author_by_nickname() plus reset() is the generally reusable\n> helper function and advertise it as such, which will mislead future\n> developers into misusing the function in a context they shouldn't\n> (i.e. while they are performing their own revision traversal).\n>\n> Thanks.\n\nAfter listening to your previous explanation, I agree with this view.\n`interpret-trailers` alone as a process, as long as it does not traverse\nthe revision externally, It is safe for us to call `reset_revision_walk()`\nafter `find_author_by_nickname()` inside interpret-trailers.\n\nThank you for your thoughts and answers.\n"},{"id":"419768","messageId":"xmqq4kh6l6fd.fsf@gitster.g","threadId":"55288","inReplyTo":"YFWCvePMGvBY/I9v@coredump.intra.peff.net","subject":"Re: [PATCH v10 2/3] interpret-trailers: add own-identity option","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2021-03-20T05:50:46Z","receivedAt":"2021-03-20T11:29:53Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Jeff King <peff@peff.net> writes:\n\n> If I understand Junio's suggestion correctly, it is very similar to how\n> \"commit --author\" works. See how it calls find_author_by_nickname(),\n> which finds the first commit matching the name, and then pulls out the\n> full name from format_commit_message().\n\nYup.\n\nBut I have to warn readers that it would not be a sane approach to\nsimply expose find_author_by_nickname() from builtin/commit.c as if\nit is a generally reusable helper function.  In a very limited\ncontext of \"git commit\", what the helper does is OK to run a single\nrevision traversal without cleaning the parsed commit objects and\nobject flag bits after finding a single commit.\n\nBut generally, it would not be an approach that would scale (e.g. I\ndo not know if we can expect to be able to call the helper function\ntwice and get sensible results out of it).\n"},{"id":"419769","messageId":"CAOLTT8T54RHPm8P=mJ3vOKZPEwpJQgnbeU3VqczWbPSk36wZcA@mail.gmail.com","threadId":"55288","inReplyTo":"CAOLTT8RE6LOx2AUH8PJMzFdqp4_GJkiY41K0xE1YiSQNixKCRA@mail.gmail.com","subject":"Re: [PATCH v10 2/3] interpret-trailers: add own-identity option","fromName":"ZheNing Hu","fromEmail":"adlternative@gmail.com","sentAt":"2021-03-20T06:38:11Z","receivedAt":"2021-03-20T11:30:20Z","isPatch":true,"sender":{"key":"adlternative@gmail.com","avatar":"https://avatars.githubusercontent.com/u/58138461?v=4"},"body":"In actually ,\n\n@@ -1071,6 +1071,7 @@ static const char *find_author_by_nickname(const\nchar *name)\n                strbuf_release(&buf);\n                format_commit_message(commit, \"%aN <%aE>\", &buf, &ctx);\n                clear_mailmap(&mailmap);\n+               reset_revision_walk();\n                return strbuf_detach(&buf, NULL);\n        }\n\nthen we can reuse this function.\n\nBut I think I can give find_author_by_nickname another arg for choice if we want\n `reset_revision_walk()`.\n\nThanks, Junio and Jeff.\n"},{"id":"419770","messageId":"CAOLTT8RE6LOx2AUH8PJMzFdqp4_GJkiY41K0xE1YiSQNixKCRA@mail.gmail.com","threadId":"55288","inReplyTo":"xmqq4kh6l6fd.fsf@gitster.g","subject":"Re: [PATCH v10 2/3] interpret-trailers: add own-identity option","fromName":"ZheNing Hu","fromEmail":"adlternative@gmail.com","sentAt":"2021-03-20T06:16:45Z","receivedAt":"2021-03-20T11:40:33Z","isPatch":true,"sender":{"key":"adlternative@gmail.com","avatar":"https://avatars.githubusercontent.com/u/58138461?v=4"},"body":"Junio C Hamano <gitster@pobox.com> 于2021年3月20日周六 下午1:50写道：\n>\n> Jeff King <peff@peff.net> writes:\n>\n> > If I understand Junio's suggestion correctly, it is very similar to how\n> > \"commit --author\" works. See how it calls find_author_by_nickname(),\n> > which finds the first commit matching the name, and then pulls out the\n> > full name from format_commit_message().\n>\n> Yup.\n>\n> But I have to warn readers that it would not be a sane approach to\n> simply expose find_author_by_nickname() from builtin/commit.c as if\n> it is a generally reusable helper function.  In a very limited\n> context of \"git commit\", what the helper does is OK to run a single\n> revision traversal without cleaning the parsed commit objects and\n> object flag bits after finding a single commit.\n>\n> But generally, it would not be an approach that would scale (e.g. I\n> do not know if we can expect to be able to call the helper function\n> twice and get sensible results out of it).\n\nHi, Jeff and Junio,\n\n`find_author_by_nickname` is indeed a good way to find\n the `author <email>` in the \"last\" commit that matches\nthe specified name, but after testing, `find_author_by_nickname` is\nas Junio say, it is not reusable(use it twice, git will call die), but why?\n"},{"id":"419771","messageId":"pull.901.v12.git.1616247681211.gitgitgadget@gmail.com","threadId":"55288","inReplyTo":"pull.901.v11.git.1616155517590.gitgitgadget@gmail.com","subject":"[PATCH v12] [GSOC] commit: add --trailer option","fromName":"ZheNing Hu via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2021-03-20T13:41:20Z","receivedAt":"2021-03-20T13:44:54Z","isPatch":true,"sender":{"key":"adlternative@gmail.com","avatar":"https://avatars.githubusercontent.com/u/58138461?v=4"},"body":"From: ZheNing Hu <adlternative@gmail.com>\n\nHistorically, Git has supported the 'Signed-off-by' commit trailer\nusing the '--signoff' and the '-s' option from the command line.\nBut users may need to provide other trailer information from the\ncommand line such as \"Helped-by\", \"Reported-by\", \"Mentored-by\",\n\nNow implement a new `--trailer <token>[(=|:)<value>]` option to pass\nother trailers to `interpret-trailers` and insert them into commit\nmessages.\n\nSigned-off-by: ZheNing Hu <adlternative@gmail.com>\n---\n    [GSOC] commit: add --trailer option\n    \n    Now maintainers or developers can also use commit\n    --trailer=\"Signed-off-by:commiter<email>\" from the command line to\n    provide trailers to commit messages. This solution may be more\n    generalized than v1.\n\nPublished-As: https://github.com/gitgitgadget/git/releases/tag/pr-901%2Fadlternative%2Fcommit-with-multiple-signatures-v12\nFetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-901/adlternative/commit-with-multiple-signatures-v12\nPull-Request: https://github.com/gitgitgadget/git/pull/901\n\nRange-diff vs v11:\n\n 1:  0db1210ffd6d ! 1:  2378e3b4c1ae [GSOC] commit: add --trailer option\n     @@ Documentation/git-commit.txt: The `-m` option is mutually exclusive with `-c`, `\n      +\ttrailer. (e.g. `git commit --trailer \"Signed-off-by:C O Mitter \\\n      +\t<committer@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 in the commit message.)\n     -+\tUse `git -c trailer.* commit --trailer` to make the appropriate\n     -+\tconfiguration. See linkgit:git-interpret-trailers[1] for details.\n     ++\tand the \"Helped-by\" trailer to the commit 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     ++\n       -n::\n       --no-verify::\n       \tThis option bypasses the pre-commit and commit-msg hooks.\n     @@ builtin/commit.c: int cmd_commit(int argc, const char **argv, const char *prefix\n       \t\tOPT_BOOL('e', \"edit\", &edit_flag, N_(\"force edit of commit\")),\n      \n       ## t/t7502-commit-porcelain.sh ##\n     +@@ t/t7502-commit-porcelain.sh: check_summary_oneline() {\n     + \ttest_cmp exp act\n     + }\n     + \n     ++trailer_commit_base () {\n     ++\techo \"fun\" >>file &&\n     ++\tgit add file &&\n     ++\tgit commit -s --trailer \"Signed-off-by=C1 E1 \" \\\n     ++\t\t--trailer \"Helped-by:C2 E2 \" \\\n     ++\t\t--trailer \"Reported-by=C3 E3\" \\\n     ++\t\t--trailer \"Mentored-by:C4 E4\" \\\n     ++\t\t-m \"hello\"\n     ++}\n     ++\n     + test_expect_success 'output summary format' '\n     + \n     + \techo new >file1 &&\n      @@ t/t7502-commit-porcelain.sh: test_expect_success 'sign off' '\n       \n       '\n       \n     -+test_expect_success 'commit --trailer without -c' '\n     -+\techo \"fun\" >>file &&\n     -+\tgit add file &&\n     ++test_expect_success 'commit --trailer with \"=\"' '\n     ++\ttrailer_commit_base &&\n      +\tcat >expected <<-\\EOF &&\n     ++\thello\n      +\n      +\tSigned-off-by: C O Mitter <committer@example.com>\n      +\tSigned-off-by: C1 E1\n     @@ t/t7502-commit-porcelain.sh: test_expect_success 'sign off' '\n      +\tReported-by: C3 E3\n      +\tMentored-by: C4 E4\n      +\tEOF\n     -+\tgit commit -s --trailer \"Signed-off-by:C1 E1 \" \\\n     -+\t\t--trailer \"Helped-by:C2 E2 \" \\\n     -+\t\t--trailer \"Reported-by:C3 E3\" \\\n     -+\t\t--trailer \"Mentored-by:C4 E4\" \\\n     -+\t\t-m \"hello\" &&\n      +\tgit cat-file commit HEAD >commit.msg &&\n     -+\tsed -e \"1,6d\" commit.msg >actual &&\n     ++\tsed -e \"1,/^\\$/d\" commit.msg >actual &&\n      +\ttest_cmp expected actual\n      +'\n      +\n      +test_expect_success 'commit --trailer with -c and \"replace\" as ifexists' '\n     -+\techo \"fun\" >>file1 &&\n     -+\tgit add file1 &&\n     ++\ttrailer_commit_base &&\n      +\tcat >expected <<-\\EOF &&\n     ++\thello\n      +\n      +\tSigned-off-by: C O Mitter <committer@example.com>\n      +\tSigned-off-by: C1 E1\n     @@ t/t7502-commit-porcelain.sh: test_expect_success 'sign off' '\n      +\t\t --trailer \"Helped-by: C3 E3\" \\\n      +\t\t--amend &&\n      +\tgit cat-file commit HEAD >commit.msg &&\n     -+\tsed -e \"1,6d\" commit.msg >actual &&\n     ++\tsed -e \"1,/^\\$/d\"  commit.msg >actual &&\n      +\ttest_cmp expected actual\n      +'\n      +\n      +test_expect_success 'commit --trailer with -c and \"add\" as ifexists' '\n     -+\techo \"fun\" >>file1 &&\n     -+\tgit add file1 &&\n     ++\ttrailer_commit_base &&\n      +\tcat >expected <<-\\EOF &&\n     ++\thello\n      +\n      +\tSigned-off-by: C O Mitter <committer@example.com>\n      +\tSigned-off-by: C1 E1\n     ++\tHelped-by: C2 E2\n     ++\tReported-by: C3 E3\n     ++\tMentored-by: C4 E4\n      +\tReported-by: C3 E3\n      +\tMentored-by: C4 E4\n     -+\tHelped-by: C3 E3\n     -+\tHelped-by: C3 E3\n     -+\tHelped-by: C3 E3\n      +\tEOF\n      +\tgit -c trailer.ifexists=\"add\" \\\n     -+\t\tcommit --trailer \"Helped-by: C3 E3\" \\\n     -+\t\t--trailer \"Helped-by: C3 E3\" \\\n     ++\t\tcommit --trailer \"Reported-by: C3 E3\" \\\n     ++\t\t--trailer \"Mentored-by: C4 E4\" \\\n      +\t\t--amend &&\n      +\tgit cat-file commit HEAD >commit.msg &&\n     -+\tsed -e \"1,6d\" commit.msg >actual &&\n     ++\tsed -e \"1,/^\\$/d\"  commit.msg >actual &&\n      +\ttest_cmp expected actual\n      +'\n      +\n      +test_expect_success 'commit --trailer with -c and \"donothing\" as ifexists' '\n     -+\techo \"fun\" >>file1 &&\n     -+\tgit add file1 &&\n     ++\ttrailer_commit_base &&\n      +\tcat >expected <<-\\EOF &&\n     ++\thello\n      +\n      +\tSigned-off-by: C O Mitter <committer@example.com>\n      +\tSigned-off-by: C1 E1\n     ++\tHelped-by: C2 E2\n      +\tReported-by: C3 E3\n      +\tMentored-by: C4 E4\n     -+\tHelped-by: C3 E3\n     -+\tHelped-by: C3 E3\n     -+\tHelped-by: C3 E3\n      +\tReviewed-by: C6 E6\n      +\tEOF\n      +\tgit -c trailer.ifexists=\"donothing\" \\\n     @@ t/t7502-commit-porcelain.sh: test_expect_success 'sign off' '\n      +\t\t--trailer \"Reviewed-by: C6 E6\" \\\n      +\t\t--amend &&\n      +\tgit cat-file commit HEAD >commit.msg &&\n     -+\tsed -e \"1,6d\" commit.msg >actual &&\n     ++\tsed -e \"1,/^\\$/d\"  commit.msg >actual &&\n      +\ttest_cmp expected actual\n      +'\n      +\n      +test_expect_success 'commit --trailer with -c and \"addIfDifferent\" as ifexists' '\n     -+\techo \"fun\" >>file1 &&\n     -+\tgit add file1 &&\n     ++\ttrailer_commit_base &&\n      +\tcat >expected <<-\\EOF &&\n     ++\thello\n      +\n      +\tSigned-off-by: C O Mitter <committer@example.com>\n      +\tSigned-off-by: C1 E1\n     ++\tHelped-by: C2 E2\n      +\tReported-by: C3 E3\n      +\tMentored-by: C4 E4\n     -+\tHelped-by: C3 E3\n     -+\tHelped-by: C3 E3\n     -+\tHelped-by: C3 E3\n     -+\tReviewed-by: C6 E6\n     -+\tReported-by: C5 E5\n     ++\tMentored-by: C5 E5\n      +\tEOF\n      +\tgit -c trailer.ifexists=\"addIfDifferent\" \\\n     -+\t\tcommit --trailer \"Reviewed-by: C6 E6\" \\\n     -+\t\t--trailer \"Reported-by: C5 E5\" \\\n     ++\t\tcommit --trailer \"Reported-by: C3 E3\" \\\n     ++\t\t--trailer \"Mentored-by: C5 E5\" \\\n      +\t\t--amend &&\n      +\tgit cat-file commit HEAD >commit.msg &&\n     -+\tsed -e \"1,6d\" commit.msg >actual &&\n     ++\tsed -e \"1,/^\\$/d\"  commit.msg >actual &&\n      +\ttest_cmp expected actual\n      +'\n      +\n      +test_expect_success 'commit --trailer with -c and \"addIfDifferentNeighbor\" as ifexists' '\n     -+\techo \"fun\" >>file1 &&\n     -+\tgit add file1 &&\n     ++\ttrailer_commit_base &&\n      +\tcat >expected <<-\\EOF &&\n     ++\thello\n      +\n      +\tSigned-off-by: C O Mitter <committer@example.com>\n      +\tSigned-off-by: C1 E1\n     ++\tHelped-by: C2 E2\n      +\tReported-by: C3 E3\n      +\tMentored-by: C4 E4\n     -+\tHelped-by: C3 E3\n     -+\tHelped-by: C3 E3\n     -+\tHelped-by: C3 E3\n     -+\tReviewed-by: C6 E6\n     -+\tReported-by: C5 E5\n     ++\tReported-by: C3 E3\n      +\tEOF\n     -+\tgit -c trailer.ifexists=\"addIfDifferent\" \\\n     -+\t\tcommit --trailer \"Reported-by: C5 E5\" \\\n     -+\t\t--trailer \"Reviewed-by: C6 E6\" \\\n     ++\tgit -c trailer.ifexists=\"addIfDifferentNeighbor\" \\\n     ++\t\tcommit --trailer \"Mentored-by: C4 E4\" \\\n     ++\t\t--trailer \"Reported-by: C3 E3\" \\\n      +\t\t--amend &&\n      +\tgit cat-file commit HEAD >commit.msg &&\n     -+\tsed -e \"1,6d\" commit.msg >actual &&\n     ++\tsed -e \"1,/^\\$/d\"  commit.msg >actual &&\n      +\ttest_cmp expected actual\n      +'\n      +\n      +test_expect_success 'commit --trailer with -c and \"end\" as where' '\n     -+\techo \"fun\" >>file1 &&\n     -+\tgit add file1 &&\n     ++\ttrailer_commit_base &&\n      +\tcat >expected <<-\\EOF &&\n     ++\thello\n      +\n      +\tSigned-off-by: C O Mitter <committer@example.com>\n      +\tSigned-off-by: C1 E1\n     ++\tHelped-by: C2 E2\n     ++\tReported-by: C3 E3\n     ++\tMentored-by: C4 E4\n      +\tReported-by: C3 E3\n      +\tMentored-by: C4 E4\n     -+\tHelped-by: C3 E3\n     -+\tHelped-by: C3 E3\n     -+\tHelped-by: C3 E3\n     -+\tReviewed-by: C6 E6\n     -+\tReported-by: C5 E5\n     -+\tReported-by: C7 E7\n      +\tEOF\n      +\tgit -c trailer.where=\"end\" \\\n     -+\t\tcommit --trailer \"Reported-by: C5 E5\" \\\n     -+\t\t--trailer \"Reported-by: C7 E7\" \\\n     ++\t\tcommit --trailer \"Reported-by: C3 E3\" \\\n     ++\t\t--trailer \"Mentored-by: C4 E4\" \\\n      +\t\t--amend &&\n      +\tgit cat-file commit HEAD >commit.msg &&\n     -+\tsed -e \"1,6d\" commit.msg >actual &&\n     ++\tsed -e \"1,/^\\$/d\" commit.msg >actual &&\n      +\ttest_cmp expected actual\n      +'\n      +\n      +test_expect_success 'commit --trailer with -c and \"start\" as where' '\n     -+\techo \"fun\" >>file1 &&\n     -+\tgit add file1 &&\n     ++\ttrailer_commit_base &&\n      +\tcat >expected <<-\\EOF &&\n     ++\thello\n      +\n     -+\tSigned-off-by: C8 E8\n     ++\tSigned-off-by: C1 E1\n      +\tSigned-off-by: C O Mitter <committer@example.com>\n      +\tSigned-off-by: C1 E1\n     ++\tHelped-by: C2 E2\n      +\tReported-by: C3 E3\n      +\tMentored-by: C4 E4\n     -+\tHelped-by: C3 E3\n     -+\tHelped-by: C3 E3\n     -+\tHelped-by: C3 E3\n     -+\tReviewed-by: C6 E6\n     -+\tReported-by: C5 E5\n     -+\tReported-by: C7 E7\n      +\tEOF\n      +\tgit -c trailer.where=\"start\" \\\n     -+\t\tcommit --trailer \"Signed-off-by: C8 E8\" \\\n     -+\t\t--trailer \"Signed-off-by: C8 E8\" \\\n     ++\t\tcommit --trailer \"Signed-off-by: C O Mitter <committer@example.com>\" \\\n     ++\t\t--trailer \"Signed-off-by: C1 E1\" \\\n      +\t\t--amend &&\n      +\tgit cat-file commit HEAD >commit.msg &&\n     -+\tsed -e \"1,6d\" commit.msg >actual &&\n     ++\tsed -e \"1,/^\\$/d\" commit.msg >actual &&\n      +\ttest_cmp expected actual\n      +'\n      +\n      +test_expect_success 'commit --trailer with -c and \"after\" as where' '\n     -+\techo \"fun\" >>file1 &&\n     -+\tgit add file1 &&\n     ++\ttrailer_commit_base &&\n      +\tcat >expected <<-\\EOF &&\n     ++\thello\n      +\n     -+\tSigned-off-by: C8 E8\n      +\tSigned-off-by: C O Mitter <committer@example.com>\n      +\tSigned-off-by: C1 E1\n     ++\tHelped-by: C2 E2\n      +\tReported-by: C3 E3\n      +\tMentored-by: C4 E4\n     -+\tMentored-by: C9 E9\n     -+\tHelped-by: C3 E3\n     -+\tHelped-by: C3 E3\n     -+\tHelped-by: C3 E3\n     -+\tReviewed-by: C6 E6\n     -+\tReported-by: C5 E5\n     -+\tReported-by: C7 E7\n     -+\tReported-by: C10 E10\n     ++\tMentored-by: C5 E5\n      +\tEOF\n      +\tgit -c trailer.where=\"after\" \\\n     -+\t\tcommit --trailer \"Mentored-by: C9 E9\" \\\n     -+\t\t--trailer \"Reported-by: C10 E10\" \\\n     ++\t\tcommit --trailer \"Mentored-by: C4 E4\" \\\n     ++\t\t--trailer \"Mentored-by: C5 E5\" \\\n      +\t\t--amend &&\n      +\tgit cat-file commit HEAD >commit.msg &&\n     -+\tsed -e \"1,6d\" commit.msg >actual &&\n     ++\tsed -e \"1,/^\\$/d\" commit.msg >actual &&\n      +\ttest_cmp expected actual\n      +'\n      +\n      +test_expect_success 'commit --trailer with -c and \"before\" as where' '\n     -+\techo \"fun\" >>file1 &&\n     -+\tgit add file1 &&\n     ++\ttrailer_commit_base &&\n      +\tcat >expected <<-\\EOF &&\n     ++\thello\n      +\n     -+\tSigned-off-by: C8 E8\n      +\tSigned-off-by: C O Mitter <committer@example.com>\n      +\tSigned-off-by: C1 E1\n     -+\tReported-by: C11 E11\n     ++\tHelped-by: C2 E2\n      +\tReported-by: C3 E3\n     ++\tMentored-by: C2 E2\n     ++\tMentored-by: C3 E3\n      +\tMentored-by: C4 E4\n     -+\tMentored-by: C9 E9\n     -+\tHelped-by: C12 E12\n     -+\tHelped-by: C3 E3\n     -+\tHelped-by: C3 E3\n     -+\tHelped-by: C3 E3\n     -+\tReviewed-by: C6 E6\n     -+\tReported-by: C5 E5\n     -+\tReported-by: C7 E7\n     -+\tReported-by: C10 E10\n      +\tEOF\n      +\tgit -c trailer.where=\"before\" \\\n     -+\t\tcommit --trailer \"Helped-by: C12 E12\" \\\n     -+\t\t--trailer \"Reported-by: C11 E11\" \\\n     ++\t\tcommit --trailer \"Mentored-by: C3 E3\" \\\n     ++\t\t--trailer \"Mentored-by: C2 E2\" \\\n      +\t\t--amend &&\n      +\tgit cat-file commit HEAD >commit.msg &&\n     -+\tsed -e \"1,6d\" commit.msg >actual &&\n     ++\tsed -e \"1,/^\\$/d\" commit.msg >actual &&\n      +\ttest_cmp expected actual\n      +'\n      +\n      +test_expect_success 'commit --trailer with -c and \"donothing\" as ifmissing' '\n     -+\techo \"fun\" >>file1 &&\n     -+\tgit add file1 &&\n     ++\ttrailer_commit_base &&\n      +\tcat >expected <<-\\EOF &&\n     ++\thello\n      +\n     -+\tSigned-off-by: C8 E8\n      +\tSigned-off-by: C O Mitter <committer@example.com>\n      +\tSigned-off-by: C1 E1\n     -+\tReported-by: C11 E11\n     ++\tHelped-by: C2 E2\n      +\tReported-by: C3 E3\n      +\tMentored-by: C4 E4\n     -+\tMentored-by: C9 E9\n     -+\tHelped-by: C12 E12\n     -+\tHelped-by: C3 E3\n     -+\tHelped-by: C3 E3\n     -+\tHelped-by: C3 E3\n     -+\tReviewed-by: C6 E6\n     -+\tReported-by: C5 E5\n     -+\tReported-by: C7 E7\n     -+\tReported-by: C10 E10\n     -+\tHelped-by: C12 E12\n     ++\tHelped-by: C5 E5\n      +\tEOF\n      +\tgit -c trailer.ifmissing=\"donothing\" \\\n     -+\t\tcommit --trailer \"Helped-by: C12 E12\" \\\n     -+\t\t--trailer \"Based-by: C13 E13\" \\\n     ++\t\tcommit --trailer \"Helped-by: C5 E5\" \\\n     ++\t\t--trailer \"Based-by: C6 E6\" \\\n      +\t\t--amend &&\n      +\tgit cat-file commit HEAD >commit.msg &&\n     -+\tsed -e \"1,6d\" commit.msg >actual &&\n     ++\tsed -e \"1,/^\\$/d\" commit.msg >actual &&\n      +\ttest_cmp expected actual\n      +'\n      +\n      +test_expect_success 'commit --trailer with -c and \"add\" as ifmissing' '\n     -+\techo \"fun\" >>file1 &&\n     -+\tgit add file1 &&\n     ++\ttrailer_commit_base &&\n      +\tcat >expected <<-\\EOF &&\n     ++\thello\n      +\n     -+\tSigned-off-by: C8 E8\n      +\tSigned-off-by: C O Mitter <committer@example.com>\n      +\tSigned-off-by: C1 E1\n     -+\tReported-by: C11 E11\n     ++\tHelped-by: C2 E2\n      +\tReported-by: C3 E3\n      +\tMentored-by: C4 E4\n     -+\tMentored-by: C9 E9\n     -+\tHelped-by: C12 E12\n     -+\tHelped-by: C3 E3\n     -+\tHelped-by: C3 E3\n     -+\tHelped-by: C3 E3\n     -+\tReviewed-by: C6 E6\n     -+\tReported-by: C5 E5\n     -+\tReported-by: C7 E7\n     -+\tReported-by: C10 E10\n     -+\tHelped-by: C12 E12\n     -+\tBased-by: C13 E13\n     ++\tHelped-by: C5 E5\n     ++\tBased-by: C6 E6\n      +\tEOF\n      +\tgit -c trailer.ifmissing=\"add\" \\\n     -+\t\tcommit --trailer \"Helped-by: C12 E12\" \\\n     -+\t\t--trailer \"Based-by: C13 E13\" \\\n     ++\t\tcommit --trailer \"Helped-by: C5 E5\" \\\n     ++\t\t--trailer \"Based-by: C6 E6\" \\\n      +\t\t--amend &&\n      +\tgit cat-file commit HEAD >commit.msg &&\n     -+\tsed -e \"1,6d\" commit.msg >actual &&\n     ++\tsed -e \"1,/^\\$/d\" commit.msg >actual &&\n      +\ttest_cmp expected actual\n      +'\n      +\n     -+test_expect_success 'commit --trailer with \"=\" ' '\n     ++test_expect_success 'commit --trailer with -c ack.key ' '\n      +\techo \"fun\" >>file1 &&\n      +\tgit add file1 &&\n      +\tcat >expected <<-\\EOF &&\n     ++\t\thello\n      +\n      +\t\tAcked-by: Peff\n      +\tEOF\n      +\tgit -c trailer.ack.key=\"Acked-by\" \\\n      +\t\tcommit --trailer \"ack = Peff\" -m \"hello\" &&\n      +\tgit cat-file commit HEAD >commit.msg &&\n     -+\tsed -e \"1,6d\" commit.msg >actual &&\n     ++\tsed -e \"1,/^\\$/d\" commit.msg >actual &&\n      +\ttest_cmp expected actual\n      +'\n      +\n     @@ t/t7502-commit-porcelain.sh: test_expect_success 'sign off' '\n      +\techo \"fun\" >>file1 &&\n      +\tgit add file1 &&\n      +\tcat >expected <<-\\EOF &&\n     ++\t\tI hate bug\n      +\n      +\t\tBug #42\n      +\tEOF\n     @@ t/t7502-commit-porcelain.sh: test_expect_success 'sign off' '\n      +\t\t-c trailer.bug.key=\"Bug #\" \\\n      +\t\tcommit --trailer \"bug = 42\" -m \"I hate bug\" &&\n      +\tgit cat-file commit HEAD >commit.msg &&\n     -+\tsed -e \"1,6d\" commit.msg >actual &&\n     ++\tsed -e \"1,/^\\$/d\" commit.msg >actual &&\n      +\ttest_cmp expected actual\n      +'\n      +\n\n\n Documentation/git-commit.txt |  14 +-\n builtin/commit.c             |  22 +++\n t/t7502-commit-porcelain.sh  | 291 +++++++++++++++++++++++++++++++++++\n 3 files changed, 326 insertions(+), 1 deletion(-)\n\ndiff --git a/Documentation/git-commit.txt b/Documentation/git-commit.txt\nindex 17150fa7eabe..3fe7ef33cb07 100644\n--- a/Documentation/git-commit.txt\n+++ b/Documentation/git-commit.txt\n@@ -14,7 +14,8 @@ SYNOPSIS\n \t   [--allow-empty-message] [--no-verify] [-e] [--author=<author>]\n \t   [--date=<date>] [--cleanup=<mode>] [--[no-]status]\n \t   [-i | -o] [--pathspec-from-file=<file> [--pathspec-file-nul]]\n-\t   [-S[<keyid>]] [--] [<pathspec>...]\n+\t   [(--trailer <token>[(=|:)<value>])...] [-S[<keyid>]]\n+\t   [--] [<pathspec>...]\n \n DESCRIPTION\n -----------\n@@ -166,6 +167,17 @@ The `-m` option is mutually exclusive with `-c`, `-C`, and `-F`.\n \n include::signoff-option.txt[]\n \n+--trailer <token>[(=|:)<value>]::\n+\tSpecify a (<token>, <value>) pair that should be applied as a\n+\ttrailer. (e.g. `git commit --trailer \"Signed-off-by:C O Mitter \\\n+\t<committer@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 commit 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+\n -n::\n --no-verify::\n \tThis option bypasses the pre-commit and commit-msg hooks.\ndiff --git a/builtin/commit.c b/builtin/commit.c\nindex 739110c5a7f6..4b06672bd07d 100644\n--- a/builtin/commit.c\n+++ b/builtin/commit.c\n@@ -113,6 +113,7 @@ static int config_commit_verbose = -1; /* unspecified */\n static int no_post_rewrite, allow_empty_message, pathspec_file_nul;\n static char *untracked_files_arg, *force_date, *ignore_submodule_arg, *ignored_arg;\n static char *sign_commit, *pathspec_from_file;\n+static struct strvec trailer_args = STRVEC_INIT;\n \n /*\n  * The default commit message cleanup mode will remove the lines\n@@ -131,6 +132,14 @@ 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(&trailer_args, \"--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@@ -958,6 +967,18 @@ static int prepare_to_commit(const char *index_file, const char *prefix,\n \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\", 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\t\tdie(_(\"unable to pass trailers to --trailers\"));\n+\t\tstrvec_clear(&trailer_args);\n+\t}\n+\n \t/*\n \t * Reject an attempt to record a non-merge empty commit without\n \t * explicit --allow-empty. In the cherry-pick case, it may be\n@@ -1507,6 +1528,7 @@ int cmd_commit(int argc, const char **argv, const char *prefix)\n \t\tOPT_STRING(0, \"fixup\", &fixup_message, N_(\"commit\"), N_(\"use autosquash formatted message to fixup 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\", NULL, N_(\"trailer\"), N_(\"add custom trailer(s)\"), PARSE_OPT_NONEG, opt_pass_trailer),\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/t/t7502-commit-porcelain.sh b/t/t7502-commit-porcelain.sh\nindex 6396897cc818..74b1602c0ce6 100755\n--- a/t/t7502-commit-porcelain.sh\n+++ b/t/t7502-commit-porcelain.sh\n@@ -38,6 +38,16 @@ check_summary_oneline() {\n \ttest_cmp exp act\n }\n \n+trailer_commit_base () {\n+\techo \"fun\" >>file &&\n+\tgit add file &&\n+\tgit commit -s --trailer \"Signed-off-by=C1 E1 \" \\\n+\t\t--trailer \"Helped-by:C2 E2 \" \\\n+\t\t--trailer \"Reported-by=C3 E3\" \\\n+\t\t--trailer \"Mentored-by:C4 E4\" \\\n+\t\t-m \"hello\"\n+}\n+\n test_expect_success 'output summary format' '\n \n \techo new >file1 &&\n@@ -154,6 +164,287 @@ test_expect_success 'sign off' '\n \n '\n \n+test_expect_success 'commit --trailer with \"=\"' '\n+\ttrailer_commit_base &&\n+\tcat >expected <<-\\EOF &&\n+\thello\n+\n+\tSigned-off-by: C O Mitter <committer@example.com>\n+\tSigned-off-by: C1 E1\n+\tHelped-by: C2 E2\n+\tReported-by: C3 E3\n+\tMentored-by: C4 E4\n+\tEOF\n+\tgit cat-file commit HEAD >commit.msg &&\n+\tsed -e \"1,/^\\$/d\" commit.msg >actual &&\n+\ttest_cmp expected actual\n+'\n+\n+test_expect_success 'commit --trailer with -c and \"replace\" as ifexists' '\n+\ttrailer_commit_base &&\n+\tcat >expected <<-\\EOF &&\n+\thello\n+\n+\tSigned-off-by: C O Mitter <committer@example.com>\n+\tSigned-off-by: C1 E1\n+\tReported-by: C3 E3\n+\tMentored-by: C4 E4\n+\tHelped-by: C3 E3\n+\tEOF\n+\tgit -c trailer.ifexists=\"replace\" \\\n+\t\tcommit --trailer \"Mentored-by: C4 E4\" \\\n+\t\t --trailer \"Helped-by: C3 E3\" \\\n+\t\t--amend &&\n+\tgit cat-file commit HEAD >commit.msg &&\n+\tsed -e \"1,/^\\$/d\"  commit.msg >actual &&\n+\ttest_cmp expected actual\n+'\n+\n+test_expect_success 'commit --trailer with -c and \"add\" as ifexists' '\n+\ttrailer_commit_base &&\n+\tcat >expected <<-\\EOF &&\n+\thello\n+\n+\tSigned-off-by: C O Mitter <committer@example.com>\n+\tSigned-off-by: C1 E1\n+\tHelped-by: C2 E2\n+\tReported-by: C3 E3\n+\tMentored-by: C4 E4\n+\tReported-by: C3 E3\n+\tMentored-by: C4 E4\n+\tEOF\n+\tgit -c trailer.ifexists=\"add\" \\\n+\t\tcommit --trailer \"Reported-by: C3 E3\" \\\n+\t\t--trailer \"Mentored-by: C4 E4\" \\\n+\t\t--amend &&\n+\tgit cat-file commit HEAD >commit.msg &&\n+\tsed -e \"1,/^\\$/d\"  commit.msg >actual &&\n+\ttest_cmp expected actual\n+'\n+\n+test_expect_success 'commit --trailer with -c and \"donothing\" as ifexists' '\n+\ttrailer_commit_base &&\n+\tcat >expected <<-\\EOF &&\n+\thello\n+\n+\tSigned-off-by: C O Mitter <committer@example.com>\n+\tSigned-off-by: C1 E1\n+\tHelped-by: C2 E2\n+\tReported-by: C3 E3\n+\tMentored-by: C4 E4\n+\tReviewed-by: C6 E6\n+\tEOF\n+\tgit -c trailer.ifexists=\"donothing\" \\\n+\t\tcommit --trailer \"Mentored-by: C5 E5\" \\\n+\t\t--trailer \"Reviewed-by: C6 E6\" \\\n+\t\t--amend &&\n+\tgit cat-file commit HEAD >commit.msg &&\n+\tsed -e \"1,/^\\$/d\"  commit.msg >actual &&\n+\ttest_cmp expected actual\n+'\n+\n+test_expect_success 'commit --trailer with -c and \"addIfDifferent\" as ifexists' '\n+\ttrailer_commit_base &&\n+\tcat >expected <<-\\EOF &&\n+\thello\n+\n+\tSigned-off-by: C O Mitter <committer@example.com>\n+\tSigned-off-by: C1 E1\n+\tHelped-by: C2 E2\n+\tReported-by: C3 E3\n+\tMentored-by: C4 E4\n+\tMentored-by: C5 E5\n+\tEOF\n+\tgit -c trailer.ifexists=\"addIfDifferent\" \\\n+\t\tcommit --trailer \"Reported-by: C3 E3\" \\\n+\t\t--trailer \"Mentored-by: C5 E5\" \\\n+\t\t--amend &&\n+\tgit cat-file commit HEAD >commit.msg &&\n+\tsed -e \"1,/^\\$/d\"  commit.msg >actual &&\n+\ttest_cmp expected actual\n+'\n+\n+test_expect_success 'commit --trailer with -c and \"addIfDifferentNeighbor\" as ifexists' '\n+\ttrailer_commit_base &&\n+\tcat >expected <<-\\EOF &&\n+\thello\n+\n+\tSigned-off-by: C O Mitter <committer@example.com>\n+\tSigned-off-by: C1 E1\n+\tHelped-by: C2 E2\n+\tReported-by: C3 E3\n+\tMentored-by: C4 E4\n+\tReported-by: C3 E3\n+\tEOF\n+\tgit -c trailer.ifexists=\"addIfDifferentNeighbor\" \\\n+\t\tcommit --trailer \"Mentored-by: C4 E4\" \\\n+\t\t--trailer \"Reported-by: C3 E3\" \\\n+\t\t--amend &&\n+\tgit cat-file commit HEAD >commit.msg &&\n+\tsed -e \"1,/^\\$/d\"  commit.msg >actual &&\n+\ttest_cmp expected actual\n+'\n+\n+test_expect_success 'commit --trailer with -c and \"end\" as where' '\n+\ttrailer_commit_base &&\n+\tcat >expected <<-\\EOF &&\n+\thello\n+\n+\tSigned-off-by: C O Mitter <committer@example.com>\n+\tSigned-off-by: C1 E1\n+\tHelped-by: C2 E2\n+\tReported-by: C3 E3\n+\tMentored-by: C4 E4\n+\tReported-by: C3 E3\n+\tMentored-by: C4 E4\n+\tEOF\n+\tgit -c trailer.where=\"end\" \\\n+\t\tcommit --trailer \"Reported-by: C3 E3\" \\\n+\t\t--trailer \"Mentored-by: C4 E4\" \\\n+\t\t--amend &&\n+\tgit cat-file commit HEAD >commit.msg &&\n+\tsed -e \"1,/^\\$/d\" commit.msg >actual &&\n+\ttest_cmp expected actual\n+'\n+\n+test_expect_success 'commit --trailer with -c and \"start\" as where' '\n+\ttrailer_commit_base &&\n+\tcat >expected <<-\\EOF &&\n+\thello\n+\n+\tSigned-off-by: C1 E1\n+\tSigned-off-by: C O Mitter <committer@example.com>\n+\tSigned-off-by: C1 E1\n+\tHelped-by: C2 E2\n+\tReported-by: C3 E3\n+\tMentored-by: C4 E4\n+\tEOF\n+\tgit -c trailer.where=\"start\" \\\n+\t\tcommit --trailer \"Signed-off-by: C O Mitter <committer@example.com>\" \\\n+\t\t--trailer \"Signed-off-by: C1 E1\" \\\n+\t\t--amend &&\n+\tgit cat-file commit HEAD >commit.msg &&\n+\tsed -e \"1,/^\\$/d\" commit.msg >actual &&\n+\ttest_cmp expected actual\n+'\n+\n+test_expect_success 'commit --trailer with -c and \"after\" as where' '\n+\ttrailer_commit_base &&\n+\tcat >expected <<-\\EOF &&\n+\thello\n+\n+\tSigned-off-by: C O Mitter <committer@example.com>\n+\tSigned-off-by: C1 E1\n+\tHelped-by: C2 E2\n+\tReported-by: C3 E3\n+\tMentored-by: C4 E4\n+\tMentored-by: C5 E5\n+\tEOF\n+\tgit -c trailer.where=\"after\" \\\n+\t\tcommit --trailer \"Mentored-by: C4 E4\" \\\n+\t\t--trailer \"Mentored-by: C5 E5\" \\\n+\t\t--amend &&\n+\tgit cat-file commit HEAD >commit.msg &&\n+\tsed -e \"1,/^\\$/d\" commit.msg >actual &&\n+\ttest_cmp expected actual\n+'\n+\n+test_expect_success 'commit --trailer with -c and \"before\" as where' '\n+\ttrailer_commit_base &&\n+\tcat >expected <<-\\EOF &&\n+\thello\n+\n+\tSigned-off-by: C O Mitter <committer@example.com>\n+\tSigned-off-by: C1 E1\n+\tHelped-by: C2 E2\n+\tReported-by: C3 E3\n+\tMentored-by: C2 E2\n+\tMentored-by: C3 E3\n+\tMentored-by: C4 E4\n+\tEOF\n+\tgit -c trailer.where=\"before\" \\\n+\t\tcommit --trailer \"Mentored-by: C3 E3\" \\\n+\t\t--trailer \"Mentored-by: C2 E2\" \\\n+\t\t--amend &&\n+\tgit cat-file commit HEAD >commit.msg &&\n+\tsed -e \"1,/^\\$/d\" commit.msg >actual &&\n+\ttest_cmp expected actual\n+'\n+\n+test_expect_success 'commit --trailer with -c and \"donothing\" as ifmissing' '\n+\ttrailer_commit_base &&\n+\tcat >expected <<-\\EOF &&\n+\thello\n+\n+\tSigned-off-by: C O Mitter <committer@example.com>\n+\tSigned-off-by: C1 E1\n+\tHelped-by: C2 E2\n+\tReported-by: C3 E3\n+\tMentored-by: C4 E4\n+\tHelped-by: C5 E5\n+\tEOF\n+\tgit -c trailer.ifmissing=\"donothing\" \\\n+\t\tcommit --trailer \"Helped-by: C5 E5\" \\\n+\t\t--trailer \"Based-by: C6 E6\" \\\n+\t\t--amend &&\n+\tgit cat-file commit HEAD >commit.msg &&\n+\tsed -e \"1,/^\\$/d\" commit.msg >actual &&\n+\ttest_cmp expected actual\n+'\n+\n+test_expect_success 'commit --trailer with -c and \"add\" as ifmissing' '\n+\ttrailer_commit_base &&\n+\tcat >expected <<-\\EOF &&\n+\thello\n+\n+\tSigned-off-by: C O Mitter <committer@example.com>\n+\tSigned-off-by: C1 E1\n+\tHelped-by: C2 E2\n+\tReported-by: C3 E3\n+\tMentored-by: C4 E4\n+\tHelped-by: C5 E5\n+\tBased-by: C6 E6\n+\tEOF\n+\tgit -c trailer.ifmissing=\"add\" \\\n+\t\tcommit --trailer \"Helped-by: C5 E5\" \\\n+\t\t--trailer \"Based-by: C6 E6\" \\\n+\t\t--amend &&\n+\tgit cat-file commit HEAD >commit.msg &&\n+\tsed -e \"1,/^\\$/d\" commit.msg >actual &&\n+\ttest_cmp expected actual\n+'\n+\n+test_expect_success 'commit --trailer with -c ack.key ' '\n+\techo \"fun\" >>file1 &&\n+\tgit add file1 &&\n+\tcat >expected <<-\\EOF &&\n+\t\thello\n+\n+\t\tAcked-by: Peff\n+\tEOF\n+\tgit -c trailer.ack.key=\"Acked-by\" \\\n+\t\tcommit --trailer \"ack = Peff\" -m \"hello\" &&\n+\tgit cat-file commit HEAD >commit.msg &&\n+\tsed -e \"1,/^\\$/d\" commit.msg >actual &&\n+\ttest_cmp expected actual\n+'\n+\n+test_expect_success 'commit --trailer with -c and \":=#\" as separators' '\n+\techo \"fun\" >>file1 &&\n+\tgit add file1 &&\n+\tcat >expected <<-\\EOF &&\n+\t\tI hate bug\n+\n+\t\tBug #42\n+\tEOF\n+\tgit -c trailer.separators=\":=#\" \\\n+\t\t-c trailer.bug.key=\"Bug #\" \\\n+\t\tcommit --trailer \"bug = 42\" -m \"I hate bug\" &&\n+\tgit cat-file commit HEAD >commit.msg &&\n+\tsed -e \"1,/^\\$/d\" commit.msg >actual &&\n+\ttest_cmp expected actual\n+'\n+\n test_expect_success 'multiple -m' '\n \n \t>negative &&\n\nbase-commit: 13d7ab6b5d7929825b626f050b62a11241ea4945\n-- \ngitgitgadget\n"},{"id":"419902","messageId":"pull.901.v13.git.1616387093662.gitgitgadget@gmail.com","threadId":"55288","inReplyTo":"pull.901.v12.git.1616247681211.gitgitgadget@gmail.com","subject":"[PATCH v13] [GSOC] commit: add --trailer option","fromName":"ZheNing Hu via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2021-03-22T04:24:53Z","receivedAt":"2021-03-22T04:25:55Z","isPatch":true,"sender":{"key":"adlternative@gmail.com","avatar":"https://avatars.githubusercontent.com/u/58138461?v=4"},"body":"From: ZheNing Hu <adlternative@gmail.com>\n\nHistorically, Git has supported the 'Signed-off-by' commit trailer\nusing the '--signoff' and the '-s' option from the command line.\nBut users may need to provide other trailer information from the\ncommand line such as \"Helped-by\", \"Reported-by\", \"Mentored-by\",\n\nNow implement a new `--trailer <token>[(=|:)<value>]` option to pass\nother trailers to `interpret-trailers` and insert them into commit\nmessages.\n\nSigned-off-by: ZheNing Hu <adlternative@gmail.com>\nReported-by:\n---\n    [GSOC] commit: add --trailer option\n    \n    Now maintainers or developers can also use commit\n    --trailer=\"Signed-off-by:commiter<email>\" from the command line to\n    provide trailers to commit messages. This solution may be more\n    generalized than v1.\n\nPublished-As: https://github.com/gitgitgadget/git/releases/tag/pr-901%2Fadlternative%2Fcommit-with-multiple-signatures-v13\nFetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-901/adlternative/commit-with-multiple-signatures-v13\nPull-Request: https://github.com/gitgitgadget/git/pull/901\n\nRange-diff vs v12:\n\n 1:  2378e3b4c1ae ! 1:  98b0c470e141 [GSOC] commit: add --trailer option\n     @@ Commit message\n          messages.\n      \n          Signed-off-by: ZheNing Hu <adlternative@gmail.com>\n     +    Reported-by:\n      \n       ## Documentation/git-commit.txt ##\n      @@ Documentation/git-commit.txt: SYNOPSIS\n     @@ t/t7502-commit-porcelain.sh: test_expect_success 'sign off' '\n      +\tsed -e \"1,/^\\$/d\" commit.msg >actual &&\n      +\ttest_cmp expected actual\n      +'\n     ++\n     ++test_expect_success 'commit --trailer with -c and command' '\n     ++\ttrailer_commit_base &&\n     ++\tcat >expected <<-\\EOF &&\n     ++\thello\n     ++\n     ++\tSigned-off-by: C O Mitter <committer@example.com>\n     ++\tSigned-off-by: C1 E1\n     ++\tHelped-by: C2 E2\n     ++\tMentored-by: C4 E4\n     ++\tReported-by: A U Thor <author@example.com>\n     ++\tEOF\n     ++\tgit -c trailer.report.key=\"Reported-by: \" \\\n     ++\t\t-c trailer.report.ifexists=\"replace\" \\\n     ++\t\t-c trailer.report.command=\"git log --author=\\\"\\$ARG\\\" -1 \\\n     ++\t\t--format=\\\"format:%aN <%aE>\\\"\" \\\n     ++\t\tcommit --trailer \"report = author\" --amend &&\n     ++\tgit cat-file commit HEAD >commit.msg &&\n     ++\tsed -e \"1,/^\\$/d\" commit.msg >actual &&\n     ++\ttest_cmp expected actual\n     ++'\n      +\n       test_expect_success 'multiple -m' '\n       \n\n\n Documentation/git-commit.txt |  14 +-\n builtin/commit.c             |  22 +++\n t/t7502-commit-porcelain.sh  | 312 +++++++++++++++++++++++++++++++++++\n 3 files changed, 347 insertions(+), 1 deletion(-)\n\ndiff --git a/Documentation/git-commit.txt b/Documentation/git-commit.txt\nindex 17150fa7eabe..3fe7ef33cb07 100644\n--- a/Documentation/git-commit.txt\n+++ b/Documentation/git-commit.txt\n@@ -14,7 +14,8 @@ SYNOPSIS\n \t   [--allow-empty-message] [--no-verify] [-e] [--author=<author>]\n \t   [--date=<date>] [--cleanup=<mode>] [--[no-]status]\n \t   [-i | -o] [--pathspec-from-file=<file> [--pathspec-file-nul]]\n-\t   [-S[<keyid>]] [--] [<pathspec>...]\n+\t   [(--trailer <token>[(=|:)<value>])...] [-S[<keyid>]]\n+\t   [--] [<pathspec>...]\n \n DESCRIPTION\n -----------\n@@ -166,6 +167,17 @@ The `-m` option is mutually exclusive with `-c`, `-C`, and `-F`.\n \n include::signoff-option.txt[]\n \n+--trailer <token>[(=|:)<value>]::\n+\tSpecify a (<token>, <value>) pair that should be applied as a\n+\ttrailer. (e.g. `git commit --trailer \"Signed-off-by:C O Mitter \\\n+\t<committer@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 commit 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+\n -n::\n --no-verify::\n \tThis option bypasses the pre-commit and commit-msg hooks.\ndiff --git a/builtin/commit.c b/builtin/commit.c\nindex 739110c5a7f6..4b06672bd07d 100644\n--- a/builtin/commit.c\n+++ b/builtin/commit.c\n@@ -113,6 +113,7 @@ static int config_commit_verbose = -1; /* unspecified */\n static int no_post_rewrite, allow_empty_message, pathspec_file_nul;\n static char *untracked_files_arg, *force_date, *ignore_submodule_arg, *ignored_arg;\n static char *sign_commit, *pathspec_from_file;\n+static struct strvec trailer_args = STRVEC_INIT;\n \n /*\n  * The default commit message cleanup mode will remove the lines\n@@ -131,6 +132,14 @@ 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(&trailer_args, \"--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@@ -958,6 +967,18 @@ static int prepare_to_commit(const char *index_file, const char *prefix,\n \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\", 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\t\tdie(_(\"unable to pass trailers to --trailers\"));\n+\t\tstrvec_clear(&trailer_args);\n+\t}\n+\n \t/*\n \t * Reject an attempt to record a non-merge empty commit without\n \t * explicit --allow-empty. In the cherry-pick case, it may be\n@@ -1507,6 +1528,7 @@ int cmd_commit(int argc, const char **argv, const char *prefix)\n \t\tOPT_STRING(0, \"fixup\", &fixup_message, N_(\"commit\"), N_(\"use autosquash formatted message to fixup 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\", NULL, N_(\"trailer\"), N_(\"add custom trailer(s)\"), PARSE_OPT_NONEG, opt_pass_trailer),\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/t/t7502-commit-porcelain.sh b/t/t7502-commit-porcelain.sh\nindex 6396897cc818..dd044fcea31c 100755\n--- a/t/t7502-commit-porcelain.sh\n+++ b/t/t7502-commit-porcelain.sh\n@@ -38,6 +38,16 @@ check_summary_oneline() {\n \ttest_cmp exp act\n }\n \n+trailer_commit_base () {\n+\techo \"fun\" >>file &&\n+\tgit add file &&\n+\tgit commit -s --trailer \"Signed-off-by=C1 E1 \" \\\n+\t\t--trailer \"Helped-by:C2 E2 \" \\\n+\t\t--trailer \"Reported-by=C3 E3\" \\\n+\t\t--trailer \"Mentored-by:C4 E4\" \\\n+\t\t-m \"hello\"\n+}\n+\n test_expect_success 'output summary format' '\n \n \techo new >file1 &&\n@@ -154,6 +164,308 @@ test_expect_success 'sign off' '\n \n '\n \n+test_expect_success 'commit --trailer with \"=\"' '\n+\ttrailer_commit_base &&\n+\tcat >expected <<-\\EOF &&\n+\thello\n+\n+\tSigned-off-by: C O Mitter <committer@example.com>\n+\tSigned-off-by: C1 E1\n+\tHelped-by: C2 E2\n+\tReported-by: C3 E3\n+\tMentored-by: C4 E4\n+\tEOF\n+\tgit cat-file commit HEAD >commit.msg &&\n+\tsed -e \"1,/^\\$/d\" commit.msg >actual &&\n+\ttest_cmp expected actual\n+'\n+\n+test_expect_success 'commit --trailer with -c and \"replace\" as ifexists' '\n+\ttrailer_commit_base &&\n+\tcat >expected <<-\\EOF &&\n+\thello\n+\n+\tSigned-off-by: C O Mitter <committer@example.com>\n+\tSigned-off-by: C1 E1\n+\tReported-by: C3 E3\n+\tMentored-by: C4 E4\n+\tHelped-by: C3 E3\n+\tEOF\n+\tgit -c trailer.ifexists=\"replace\" \\\n+\t\tcommit --trailer \"Mentored-by: C4 E4\" \\\n+\t\t --trailer \"Helped-by: C3 E3\" \\\n+\t\t--amend &&\n+\tgit cat-file commit HEAD >commit.msg &&\n+\tsed -e \"1,/^\\$/d\"  commit.msg >actual &&\n+\ttest_cmp expected actual\n+'\n+\n+test_expect_success 'commit --trailer with -c and \"add\" as ifexists' '\n+\ttrailer_commit_base &&\n+\tcat >expected <<-\\EOF &&\n+\thello\n+\n+\tSigned-off-by: C O Mitter <committer@example.com>\n+\tSigned-off-by: C1 E1\n+\tHelped-by: C2 E2\n+\tReported-by: C3 E3\n+\tMentored-by: C4 E4\n+\tReported-by: C3 E3\n+\tMentored-by: C4 E4\n+\tEOF\n+\tgit -c trailer.ifexists=\"add\" \\\n+\t\tcommit --trailer \"Reported-by: C3 E3\" \\\n+\t\t--trailer \"Mentored-by: C4 E4\" \\\n+\t\t--amend &&\n+\tgit cat-file commit HEAD >commit.msg &&\n+\tsed -e \"1,/^\\$/d\"  commit.msg >actual &&\n+\ttest_cmp expected actual\n+'\n+\n+test_expect_success 'commit --trailer with -c and \"donothing\" as ifexists' '\n+\ttrailer_commit_base &&\n+\tcat >expected <<-\\EOF &&\n+\thello\n+\n+\tSigned-off-by: C O Mitter <committer@example.com>\n+\tSigned-off-by: C1 E1\n+\tHelped-by: C2 E2\n+\tReported-by: C3 E3\n+\tMentored-by: C4 E4\n+\tReviewed-by: C6 E6\n+\tEOF\n+\tgit -c trailer.ifexists=\"donothing\" \\\n+\t\tcommit --trailer \"Mentored-by: C5 E5\" \\\n+\t\t--trailer \"Reviewed-by: C6 E6\" \\\n+\t\t--amend &&\n+\tgit cat-file commit HEAD >commit.msg &&\n+\tsed -e \"1,/^\\$/d\"  commit.msg >actual &&\n+\ttest_cmp expected actual\n+'\n+\n+test_expect_success 'commit --trailer with -c and \"addIfDifferent\" as ifexists' '\n+\ttrailer_commit_base &&\n+\tcat >expected <<-\\EOF &&\n+\thello\n+\n+\tSigned-off-by: C O Mitter <committer@example.com>\n+\tSigned-off-by: C1 E1\n+\tHelped-by: C2 E2\n+\tReported-by: C3 E3\n+\tMentored-by: C4 E4\n+\tMentored-by: C5 E5\n+\tEOF\n+\tgit -c trailer.ifexists=\"addIfDifferent\" \\\n+\t\tcommit --trailer \"Reported-by: C3 E3\" \\\n+\t\t--trailer \"Mentored-by: C5 E5\" \\\n+\t\t--amend &&\n+\tgit cat-file commit HEAD >commit.msg &&\n+\tsed -e \"1,/^\\$/d\"  commit.msg >actual &&\n+\ttest_cmp expected actual\n+'\n+\n+test_expect_success 'commit --trailer with -c and \"addIfDifferentNeighbor\" as ifexists' '\n+\ttrailer_commit_base &&\n+\tcat >expected <<-\\EOF &&\n+\thello\n+\n+\tSigned-off-by: C O Mitter <committer@example.com>\n+\tSigned-off-by: C1 E1\n+\tHelped-by: C2 E2\n+\tReported-by: C3 E3\n+\tMentored-by: C4 E4\n+\tReported-by: C3 E3\n+\tEOF\n+\tgit -c trailer.ifexists=\"addIfDifferentNeighbor\" \\\n+\t\tcommit --trailer \"Mentored-by: C4 E4\" \\\n+\t\t--trailer \"Reported-by: C3 E3\" \\\n+\t\t--amend &&\n+\tgit cat-file commit HEAD >commit.msg &&\n+\tsed -e \"1,/^\\$/d\"  commit.msg >actual &&\n+\ttest_cmp expected actual\n+'\n+\n+test_expect_success 'commit --trailer with -c and \"end\" as where' '\n+\ttrailer_commit_base &&\n+\tcat >expected <<-\\EOF &&\n+\thello\n+\n+\tSigned-off-by: C O Mitter <committer@example.com>\n+\tSigned-off-by: C1 E1\n+\tHelped-by: C2 E2\n+\tReported-by: C3 E3\n+\tMentored-by: C4 E4\n+\tReported-by: C3 E3\n+\tMentored-by: C4 E4\n+\tEOF\n+\tgit -c trailer.where=\"end\" \\\n+\t\tcommit --trailer \"Reported-by: C3 E3\" \\\n+\t\t--trailer \"Mentored-by: C4 E4\" \\\n+\t\t--amend &&\n+\tgit cat-file commit HEAD >commit.msg &&\n+\tsed -e \"1,/^\\$/d\" commit.msg >actual &&\n+\ttest_cmp expected actual\n+'\n+\n+test_expect_success 'commit --trailer with -c and \"start\" as where' '\n+\ttrailer_commit_base &&\n+\tcat >expected <<-\\EOF &&\n+\thello\n+\n+\tSigned-off-by: C1 E1\n+\tSigned-off-by: C O Mitter <committer@example.com>\n+\tSigned-off-by: C1 E1\n+\tHelped-by: C2 E2\n+\tReported-by: C3 E3\n+\tMentored-by: C4 E4\n+\tEOF\n+\tgit -c trailer.where=\"start\" \\\n+\t\tcommit --trailer \"Signed-off-by: C O Mitter <committer@example.com>\" \\\n+\t\t--trailer \"Signed-off-by: C1 E1\" \\\n+\t\t--amend &&\n+\tgit cat-file commit HEAD >commit.msg &&\n+\tsed -e \"1,/^\\$/d\" commit.msg >actual &&\n+\ttest_cmp expected actual\n+'\n+\n+test_expect_success 'commit --trailer with -c and \"after\" as where' '\n+\ttrailer_commit_base &&\n+\tcat >expected <<-\\EOF &&\n+\thello\n+\n+\tSigned-off-by: C O Mitter <committer@example.com>\n+\tSigned-off-by: C1 E1\n+\tHelped-by: C2 E2\n+\tReported-by: C3 E3\n+\tMentored-by: C4 E4\n+\tMentored-by: C5 E5\n+\tEOF\n+\tgit -c trailer.where=\"after\" \\\n+\t\tcommit --trailer \"Mentored-by: C4 E4\" \\\n+\t\t--trailer \"Mentored-by: C5 E5\" \\\n+\t\t--amend &&\n+\tgit cat-file commit HEAD >commit.msg &&\n+\tsed -e \"1,/^\\$/d\" commit.msg >actual &&\n+\ttest_cmp expected actual\n+'\n+\n+test_expect_success 'commit --trailer with -c and \"before\" as where' '\n+\ttrailer_commit_base &&\n+\tcat >expected <<-\\EOF &&\n+\thello\n+\n+\tSigned-off-by: C O Mitter <committer@example.com>\n+\tSigned-off-by: C1 E1\n+\tHelped-by: C2 E2\n+\tReported-by: C3 E3\n+\tMentored-by: C2 E2\n+\tMentored-by: C3 E3\n+\tMentored-by: C4 E4\n+\tEOF\n+\tgit -c trailer.where=\"before\" \\\n+\t\tcommit --trailer \"Mentored-by: C3 E3\" \\\n+\t\t--trailer \"Mentored-by: C2 E2\" \\\n+\t\t--amend &&\n+\tgit cat-file commit HEAD >commit.msg &&\n+\tsed -e \"1,/^\\$/d\" commit.msg >actual &&\n+\ttest_cmp expected actual\n+'\n+\n+test_expect_success 'commit --trailer with -c and \"donothing\" as ifmissing' '\n+\ttrailer_commit_base &&\n+\tcat >expected <<-\\EOF &&\n+\thello\n+\n+\tSigned-off-by: C O Mitter <committer@example.com>\n+\tSigned-off-by: C1 E1\n+\tHelped-by: C2 E2\n+\tReported-by: C3 E3\n+\tMentored-by: C4 E4\n+\tHelped-by: C5 E5\n+\tEOF\n+\tgit -c trailer.ifmissing=\"donothing\" \\\n+\t\tcommit --trailer \"Helped-by: C5 E5\" \\\n+\t\t--trailer \"Based-by: C6 E6\" \\\n+\t\t--amend &&\n+\tgit cat-file commit HEAD >commit.msg &&\n+\tsed -e \"1,/^\\$/d\" commit.msg >actual &&\n+\ttest_cmp expected actual\n+'\n+\n+test_expect_success 'commit --trailer with -c and \"add\" as ifmissing' '\n+\ttrailer_commit_base &&\n+\tcat >expected <<-\\EOF &&\n+\thello\n+\n+\tSigned-off-by: C O Mitter <committer@example.com>\n+\tSigned-off-by: C1 E1\n+\tHelped-by: C2 E2\n+\tReported-by: C3 E3\n+\tMentored-by: C4 E4\n+\tHelped-by: C5 E5\n+\tBased-by: C6 E6\n+\tEOF\n+\tgit -c trailer.ifmissing=\"add\" \\\n+\t\tcommit --trailer \"Helped-by: C5 E5\" \\\n+\t\t--trailer \"Based-by: C6 E6\" \\\n+\t\t--amend &&\n+\tgit cat-file commit HEAD >commit.msg &&\n+\tsed -e \"1,/^\\$/d\" commit.msg >actual &&\n+\ttest_cmp expected actual\n+'\n+\n+test_expect_success 'commit --trailer with -c ack.key ' '\n+\techo \"fun\" >>file1 &&\n+\tgit add file1 &&\n+\tcat >expected <<-\\EOF &&\n+\t\thello\n+\n+\t\tAcked-by: Peff\n+\tEOF\n+\tgit -c trailer.ack.key=\"Acked-by\" \\\n+\t\tcommit --trailer \"ack = Peff\" -m \"hello\" &&\n+\tgit cat-file commit HEAD >commit.msg &&\n+\tsed -e \"1,/^\\$/d\" commit.msg >actual &&\n+\ttest_cmp expected actual\n+'\n+\n+test_expect_success 'commit --trailer with -c and \":=#\" as separators' '\n+\techo \"fun\" >>file1 &&\n+\tgit add file1 &&\n+\tcat >expected <<-\\EOF &&\n+\t\tI hate bug\n+\n+\t\tBug #42\n+\tEOF\n+\tgit -c trailer.separators=\":=#\" \\\n+\t\t-c trailer.bug.key=\"Bug #\" \\\n+\t\tcommit --trailer \"bug = 42\" -m \"I hate bug\" &&\n+\tgit cat-file commit HEAD >commit.msg &&\n+\tsed -e \"1,/^\\$/d\" commit.msg >actual &&\n+\ttest_cmp expected actual\n+'\n+\n+test_expect_success 'commit --trailer with -c and command' '\n+\ttrailer_commit_base &&\n+\tcat >expected <<-\\EOF &&\n+\thello\n+\n+\tSigned-off-by: C O Mitter <committer@example.com>\n+\tSigned-off-by: C1 E1\n+\tHelped-by: C2 E2\n+\tMentored-by: C4 E4\n+\tReported-by: A U Thor <author@example.com>\n+\tEOF\n+\tgit -c trailer.report.key=\"Reported-by: \" \\\n+\t\t-c trailer.report.ifexists=\"replace\" \\\n+\t\t-c trailer.report.command=\"git log --author=\\\"\\$ARG\\\" -1 \\\n+\t\t--format=\\\"format:%aN <%aE>\\\"\" \\\n+\t\tcommit --trailer \"report = author\" --amend &&\n+\tgit cat-file commit HEAD >commit.msg &&\n+\tsed -e \"1,/^\\$/d\" commit.msg >actual &&\n+\ttest_cmp expected actual\n+'\n+\n test_expect_success 'multiple -m' '\n \n \t>negative &&\n\nbase-commit: 13d7ab6b5d7929825b626f050b62a11241ea4945\n-- \ngitgitgadget\n"},{"id":"419911","messageId":"CAP8UFD0rtX0m+fGcvGFtsFFKZ2LVyxHx8dptYFvM9kWnbxEwFA@mail.gmail.com","threadId":"55288","inReplyTo":"pull.901.v13.git.1616387093662.gitgitgadget@gmail.com","subject":"Re: [PATCH v13] [GSOC] commit: add --trailer option","fromName":"Christian Couder","fromEmail":"christian.couder@gmail.com","sentAt":"2021-03-22T07:43:37Z","receivedAt":"2021-03-22T07:44:37Z","isPatch":true,"sender":{"key":"christian.couder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/208954?v=4"},"body":"On Mon, Mar 22, 2021 at 5:24 AM ZheNing Hu via GitGitGadget\n<gitgitgadget@gmail.com> wrote:\n>\n> From: ZheNing Hu <adlternative@gmail.com>\n>\n> Historically, Git has supported the 'Signed-off-by' commit trailer\n> using the '--signoff' and the '-s' option from the command line.\n> But users may need to provide other trailer information from the\n> command line such as \"Helped-by\", \"Reported-by\", \"Mentored-by\",\n>\n> Now implement a new `--trailer <token>[(=|:)<value>]` option to pass\n> other trailers to `interpret-trailers` and insert them into commit\n> messages.\n>\n> Signed-off-by: ZheNing Hu <adlternative@gmail.com>\n> Reported-by:\n\nWhy is there this \"Reported-by:\" trailer with an empty value? If you\nare looking to add trailers to this commit message, you might want to\nadd them before your \"Signed-off-by\".\n\n> ---\n>     [GSOC] commit: add --trailer option\n>\n>     Now maintainers or developers can also use commit\n>     --trailer=\"Signed-off-by:commiter<email>\" from the command line to\n>     provide trailers to commit messages. This solution may be more\n>     generalized than v1.\n\nIt's not a big deal as this is not going into the commit message, but\nat this point (v13) you might want to tell explicitly what solution v1\nimplemented, instead of referring to it.\n\n>      @@ t/t7502-commit-porcelain.sh: test_expect_success 'sign off' '>       + sed -e \"1,/^\\$/d\" commit.msg >actual &&\n>       + test_cmp expected actual\n>       +'\n>      ++\n>      ++test_expect_success 'commit --trailer with -c and command' '\n>      ++ trailer_commit_base &&\n>      ++ cat >expected <<-\\EOF &&\n>      ++ hello\n>      ++\n>      ++ Signed-off-by: C O Mitter <committer@example.com>\n>      ++ Signed-off-by: C1 E1\n>      ++ Helped-by: C2 E2\n>      ++ Mentored-by: C4 E4\n>      ++ Reported-by: A U Thor <author@example.com>\n>      ++ EOF\n>      ++ git -c trailer.report.key=\"Reported-by: \" \\\n>      ++         -c trailer.report.ifexists=\"replace\" \\\n>      ++         -c trailer.report.command=\"git log --author=\\\"\\$ARG\\\" -1 \\\n>      ++         --format=\\\"format:%aN <%aE>\\\"\" \\\n>      ++         commit --trailer \"report = author\" --amend &&\n>      ++ git cat-file commit HEAD >commit.msg &&\n>      ++ sed -e \"1,/^\\$/d\" commit.msg >actual &&\n>      ++ test_cmp expected actual\n>      ++'\n\nNice that you have added such a test!\n"},{"id":"419918","messageId":"CAOLTT8Ty5kabU6ivX946=FDWJ4SEXBzPinq2aG5t7Rp9jCCEPA@mail.gmail.com","threadId":"55288","inReplyTo":"CAP8UFD0rtX0m+fGcvGFtsFFKZ2LVyxHx8dptYFvM9kWnbxEwFA@mail.gmail.com","subject":"Re: [PATCH v13] [GSOC] commit: add --trailer option","fromName":"ZheNing Hu","fromEmail":"adlternative@gmail.com","sentAt":"2021-03-22T10:23:39Z","receivedAt":"2021-03-22T10:24:32Z","isPatch":true,"sender":{"key":"adlternative@gmail.com","avatar":"https://avatars.githubusercontent.com/u/58138461?v=4"},"body":"Christian Couder <christian.couder@gmail.com> 于2021年3月22日周一 下午3:43写道：\n>\n> On Mon, Mar 22, 2021 at 5:24 AM ZheNing Hu via GitGitGadget\n> <gitgitgadget@gmail.com> wrote:\n> >\n> > From: ZheNing Hu <adlternative@gmail.com>\n> >\n> > Historically, Git has supported the 'Signed-off-by' commit trailer\n> > using the '--signoff' and the '-s' option from the command line.\n> > But users may need to provide other trailer information from the\n> > command line such as \"Helped-by\", \"Reported-by\", \"Mentored-by\",\n> >\n> > Now implement a new `--trailer <token>[(=|:)<value>]` option to pass\n> > other trailers to `interpret-trailers` and insert them into commit\n> > messages.\n> >\n> > Signed-off-by: ZheNing Hu <adlternative@gmail.com>\n> > Reported-by:\n>\n> Why is there this \"Reported-by:\" trailer with an empty value? If you\n> are looking to add trailers to this commit message, you might want to\n> add them before your \"Signed-off-by\".\n>\n\nSorry, It was purely a small accident during testing and I didn't notice it.\n\n> > ---\n> >     [GSOC] commit: add --trailer option\n> >\n> >     Now maintainers or developers can also use commit\n> >     --trailer=\"Signed-off-by:commiter<email>\" from the command line to\n> >     provide trailers to commit messages. This solution may be more\n> >     generalized than v1.\n>\n> It's not a big deal as this is not going into the commit message, but\n> at this point (v13) you might want to tell explicitly what solution v1\n> implemented, instead of referring to it.\n\nOk.\n\n>\n> >      @@ t/t7502-commit-porcelain.sh: test_expect_success 'sign off' '>       + sed -e \"1,/^\\$/d\" commit.msg >actual &&\n> >       + test_cmp expected actual\n> >       +'\n> >      ++\n> >      ++test_expect_success 'commit --trailer with -c and command' '\n> >      ++ trailer_commit_base &&\n> >      ++ cat >expected <<-\\EOF &&\n> >      ++ hello\n> >      ++\n> >      ++ Signed-off-by: C O Mitter <committer@example.com>\n> >      ++ Signed-off-by: C1 E1\n> >      ++ Helped-by: C2 E2\n> >      ++ Mentored-by: C4 E4\n> >      ++ Reported-by: A U Thor <author@example.com>\n> >      ++ EOF\n> >      ++ git -c trailer.report.key=\"Reported-by: \" \\\n> >      ++         -c trailer.report.ifexists=\"replace\" \\\n> >      ++         -c trailer.report.command=\"git log --author=\\\"\\$ARG\\\" -1 \\\n> >      ++         --format=\\\"format:%aN <%aE>\\\"\" \\\n> >      ++         commit --trailer \"report = author\" --amend &&\n> >      ++ git cat-file commit HEAD >commit.msg &&\n> >      ++ sed -e \"1,/^\\$/d\" commit.msg >actual &&\n> >      ++ test_cmp expected actual\n> >      ++'\n>\n> Nice that you have added such a test!\n\nThanks.\n\nBut at the same time I have two little doubt.\n\n1.\nIf we have your config:\n\n$ git config trailer.sign.key \"Signed-off-by: \"\n$ git config trailer.sign.ifexists replace\n$ git config trailer.sign.command \"git log --author='\\$ARG' -1\n--format='format:%aN <%aE>'\"\n\nThen I touch a test.c and use:\n\n$ git interpret-trailers --in-place test.c\n\nwithout `--trailer`, See what is happen:\n\nIt seem like your local repo last commit \"name <email>\" pair\nhave been record in `test.c`.\n\nCould this be considered a bug?\n\n2.\n`git interpret-trailers --in-place`  seem like work on git top-dir,\nIf I am in a sub-dir `b` and I want to change a file such as `d.c`,\nthen I must use `git interpret-trailers --in-place b/d.c` to add some\ntrailers.\n\nI think the original intention of `--in-place` is to modify a file similar to\n\"$COMMIT_MSG_FILE\", so make it run at top-dir, but this is not reflected\nin the git documentation. This at least confuses people who use this\noption for the first time. Is it worth modifying? Or is there something\nwrong with the design of `--in-place`?\n\n--\nZheNing Hu\n"},{"id":"419975","messageId":"CAP8UFD3fYTc8=y+kru-mN5KmTsnqc6X8mf14VtyWf1Nj9CJ1EQ@mail.gmail.com","threadId":"55288","inReplyTo":"CAOLTT8Ty5kabU6ivX946=FDWJ4SEXBzPinq2aG5t7Rp9jCCEPA@mail.gmail.com","subject":"Re: [PATCH v13] [GSOC] commit: add --trailer option","fromName":"Christian Couder","fromEmail":"christian.couder@gmail.com","sentAt":"2021-03-22T21:34:21Z","receivedAt":"2021-03-22T21:35:33Z","isPatch":true,"sender":{"key":"christian.couder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/208954?v=4"},"body":"On Mon, Mar 22, 2021 at 11:23 AM ZheNing Hu <adlternative@gmail.com> wrote:\n>\n> Christian Couder <christian.couder@gmail.com> 于2021年3月22日周一 下午3:43写道：\n\n> > Nice that you have added such a test!\n>\n> Thanks.\n>\n> But at the same time I have two little doubt.\n>\n> 1.\n> If we have your config:\n>\n> $ git config trailer.sign.key \"Signed-off-by: \"\n> $ git config trailer.sign.ifexists replace\n> $ git config trailer.sign.command \"git log --author='\\$ARG' -1\n> --format='format:%aN <%aE>'\"\n>\n> Then I touch a test.c and use:\n>\n> $ git interpret-trailers --in-place test.c\n>\n> without `--trailer`, See what is happen:\n>\n> It seem like your local repo last commit \"name <email>\" pair\n> have been record in `test.c`.\n>\n> Could this be considered a bug?\n\nFirst it seems strange to use `git interpret-trailers` on a \"test.c\"\nfile. It's supposed to be used on commit messages.\n\nThen, as the doc says, every command specified by any\n\"trailer.<token>.command\" config option is run at least once when `git\ninterpret-trailers` is run. This is because users might want to\nautomatically add some trailers all the time.\n\nIf you want nothing to happen when $ARG isn't set, you can change the\nconfig option to something like:\n\n$ git config trailer.sign.command \"NAME='\\$ARG'; test -n \\\"\\$NAME\\\" &&\ngit log --author=\\\"\\$NAME\\\" -1 --format='format:%aN <%aE>' || true\"\n\n(This is because it looks like $ARG is replaced only once with the\nactual value, which is perhaps a bug. Otherwise something like the\nfollowing might work:\n\ngit config trailer.sign.command \"test -n '\\$ARG' && git log\n--author='\\$ARG' -1 --format='format:%aN <%aE>' || true\")\n\nThen you can run `git interpret-trailers` with the --trim-empty option\nlike this:\n\n------\n$ git interpret-trailers --trim-empty --trailer sign=Linus<<EOF\nEOF\n\nSigned-off-by: Linus Torvalds <torvalds@linux-foundation.org>\n------\n\nor like:\n\n------\n$ git interpret-trailers --trim-empty<<EOF\n> EOF\n\n------\n"},{"id":"419977","messageId":"CAP8UFD275WiHC2sUJjsnLd1yonfMO-SVda=BZ6mMkgDxGs959g@mail.gmail.com","threadId":"55288","inReplyTo":"CAOLTT8Ty5kabU6ivX946=FDWJ4SEXBzPinq2aG5t7Rp9jCCEPA@mail.gmail.com","subject":"Re: [PATCH v13] [GSOC] commit: add --trailer option","fromName":"Christian Couder","fromEmail":"christian.couder@gmail.com","sentAt":"2021-03-22T21:55:20Z","receivedAt":"2021-03-22T21:56:33Z","isPatch":true,"sender":{"key":"christian.couder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/208954?v=4"},"body":"On Mon, Mar 22, 2021 at 11:23 AM ZheNing Hu <adlternative@gmail.com> wrote:\n\n> 2.\n> `git interpret-trailers --in-place`  seem like work on git top-dir,\n> If I am in a sub-dir `b` and I want to change a file such as `d.c`,\n> then I must use `git interpret-trailers --in-place b/d.c` to add some\n> trailers.\n\nWhat happens without --in-place? Are the input files read correctly?\n\n> I think the original intention of `--in-place` is to modify a file similar to\n> \"$COMMIT_MSG_FILE\", so make it run at top-dir, but this is not reflected\n> in the git documentation. This at least confuses people who use this\n> option for the first time. Is it worth modifying? Or is there something\n> wrong with the design of `--in-place`?\n\nI haven't checked but there is perhaps a bug in\ncreate_in_place_tempfile() in \"trailer.c\".\n"},{"id":"419987","messageId":"CAOLTT8Rc6Y4=a-2aj96hs24-h4zywxO0nLxBwh_B19hSSxcs_w@mail.gmail.com","threadId":"55288","inReplyTo":"CAP8UFD3fYTc8=y+kru-mN5KmTsnqc6X8mf14VtyWf1Nj9CJ1EQ@mail.gmail.com","subject":"Re: [PATCH v13] [GSOC] commit: add --trailer option","fromName":"ZheNing Hu","fromEmail":"adlternative@gmail.com","sentAt":"2021-03-23T06:11:43Z","receivedAt":"2021-03-23T06:12:42Z","isPatch":true,"sender":{"key":"adlternative@gmail.com","avatar":"https://avatars.githubusercontent.com/u/58138461?v=4"},"body":"Christian Couder <christian.couder@gmail.com> 于2021年3月23日周二 上午5:34写道：\n>\n> On Mon, Mar 22, 2021 at 11:23 AM ZheNing Hu <adlternative@gmail.com> wrote:\n> >\n> > Christian Couder <christian.couder@gmail.com> 于2021年3月22日周一 下午3:43写道：\n>\n> > > Nice that you have added such a test!\n> >\n> > Thanks.\n> >\n> > But at the same time I have two little doubt.\n> >\n> > 1.\n> > If we have your config:\n> >\n> > $ git config trailer.sign.key \"Signed-off-by: \"\n> > $ git config trailer.sign.ifexists replace\n> > $ git config trailer.sign.command \"git log --author='\\$ARG' -1\n> > --format='format:%aN <%aE>'\"\n> >\n> > Then I touch a test.c and use:\n> >\n> > $ git interpret-trailers --in-place test.c\n> >\n> > without `--trailer`, See what is happen:\n> >\n> > It seem like your local repo last commit \"name <email>\" pair\n> > have been record in `test.c`.\n> >\n> > Could this be considered a bug?\n>\n> First it seems strange to use `git interpret-trailers` on a \"test.c\"\n> file. It's supposed to be used on commit messages.\n>\n> Then, as the doc says, every command specified by any\n> \"trailer.<token>.command\" config option is run at least once when `git\n> interpret-trailers` is run. This is because users might want to\n> automatically add some trailers all the time.\n>\n\nWell, I understand it now.\n\n> If you want nothing to happen when $ARG isn't set, you can change the\n> config option to something like:\n>\n> $ git config trailer.sign.command \"NAME='\\$ARG'; test -n \\\"\\$NAME\\\" &&\n> git log --author=\\\"\\$NAME\\\" -1 --format='format:%aN <%aE>' || true\"\n>\n> (This is because it looks like $ARG is replaced only once with the\n> actual value, which is perhaps a bug. Otherwise something like the\n> following might work:\n\nthis is because `$ARG` is replaced in \"trailer.c\" by \"strbuf_replace\" which\nonly replcae the specified string for only one time,\nI think `strbuf_replace()` can be changed like:\n\n@@ -110,8 +110,9 @@ static inline int is_blank_line(const char *str)\n\n static inline void strbuf_replace(struct strbuf *sb, const char *a,\nconst char *b)\n {\n-       const char *ptr = strstr(sb->buf, a);\n-       if (ptr)\n+       char *ptr = sb->buf;\n+\n+       while ((ptr = strstr(ptr, a)))\n                strbuf_splice(sb, ptr - sb->buf, strlen(a), b, strlen(b));\n }\n\n>\n> git config trailer.sign.command \"test -n '\\$ARG' && git log\n> --author='\\$ARG' -1 --format='format:%aN <%aE>' || true\")\n>\n> Then you can run `git interpret-trailers` with the --trim-empty option\n> like this:\n>\n> ------\n> $ git interpret-trailers --trim-empty --trailer sign=Linus<<EOF\n> EOF\n>\n> Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>\n> ------\n>\n> or like:\n>\n> ------\n> $ git interpret-trailers --trim-empty<<EOF\n> > EOF\n>\n> ------\n\nThanks for effective solution.\nMaybe I should also correct this part of the test.\n"},{"id":"419988","messageId":"xmqqv99i4ck2.fsf@gitster.g","threadId":"55288","inReplyTo":"CAP8UFD3fYTc8=y+kru-mN5KmTsnqc6X8mf14VtyWf1Nj9CJ1EQ@mail.gmail.com","subject":"Re: [PATCH v13] [GSOC] commit: add --trailer option","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2021-03-23T06:19:25Z","receivedAt":"2021-03-23T06:20:26Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Christian Couder <christian.couder@gmail.com> writes:\n\n> If you want nothing to happen when $ARG isn't set, you can change the\n> config option to something like:\n>\n> $ git config trailer.sign.command \"NAME='\\$ARG'; test -n \\\"\\$NAME\\\" &&\n> git log --author=\\\"\\$NAME\\\" -1 --format='format:%aN <%aE>' || true\"\n>\n> (This is because it looks like $ARG is replaced only once with the\n> actual value, which is perhaps a bug. Otherwise something like the\n> following might work:\n\nI do not know the origin of that code in trailers.c but it feels\nquite confused and error prone to use textual replacement with\nstrbuf_replace().  Why doesn't the code, which knows it will use\nshell to execute the command line given by the end user in the\nconfiguration, to just export ARG as an environment variable and\nbe done with it?  It would also avoid quoting problem etc.\n\n"},{"id":"419990","messageId":"CAOLTT8S09oTZYjDArMNhQSLAaHtikrw=H4Z8WHF9GvWukQytaQ@mail.gmail.com","threadId":"55288","inReplyTo":"CAP8UFD275WiHC2sUJjsnLd1yonfMO-SVda=BZ6mMkgDxGs959g@mail.gmail.com","subject":"Re: [PATCH v13] [GSOC] commit: add --trailer option","fromName":"ZheNing Hu","fromEmail":"adlternative@gmail.com","sentAt":"2021-03-23T06:29:01Z","receivedAt":"2021-03-23T06:30:16Z","isPatch":true,"sender":{"key":"adlternative@gmail.com","avatar":"https://avatars.githubusercontent.com/u/58138461?v=4"},"body":"Christian Couder <christian.couder@gmail.com> 于2021年3月23日周二 上午5:55写道：\n>\n> On Mon, Mar 22, 2021 at 11:23 AM ZheNing Hu <adlternative@gmail.com> wrote:\n>\n> > 2.\n> > `git interpret-trailers --in-place`  seem like work on git top-dir,\n> > If I am in a sub-dir `b` and I want to change a file such as `d.c`,\n> > then I must use `git interpret-trailers --in-place b/d.c` to add some\n> > trailers.\n>\n> What happens without --in-place? Are the input files read correctly?\n\nIt's still wrong.\nthe git die() in `read_input_file` of \"trailer.c\".\n\n>\n> > I think the original intention of `--in-place` is to modify a file similar to\n> > \"$COMMIT_MSG_FILE\", so make it run at top-dir, but this is not reflected\n> > in the git documentation. This at least confuses people who use this\n> > option for the first time. Is it worth modifying? Or is there something\n> > wrong with the design of `--in-place`?\n>\n> I haven't checked but there is perhaps a bug in\n> create_in_place_tempfile() in \"trailer.c\".\n\nI haven't check finished. But I think it may do something like `chdir()`.\n\nThanks.\n"},{"id":"419993","messageId":"CAP8UFD0s4Gm3PgDPpsXC-8Gyrnn1JTMUBu60XGV7i8nRDCCJ2Q@mail.gmail.com","threadId":"55288","inReplyTo":"xmqqv99i4ck2.fsf@gitster.g","subject":"Re: [PATCH v13] [GSOC] commit: add --trailer option","fromName":"Christian Couder","fromEmail":"christian.couder@gmail.com","sentAt":"2021-03-23T07:57:25Z","receivedAt":"2021-03-23T07:58:48Z","isPatch":true,"sender":{"key":"christian.couder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/208954?v=4"},"body":"On Tue, Mar 23, 2021 at 7:19 AM Junio C Hamano <gitster@pobox.com> wrote:\n>\n> Christian Couder <christian.couder@gmail.com> writes:\n>\n> > If you want nothing to happen when $ARG isn't set, you can change the\n> > config option to something like:\n> >\n> > $ git config trailer.sign.command \"NAME='\\$ARG'; test -n \\\"\\$NAME\\\" &&\n> > git log --author=\\\"\\$NAME\\\" -1 --format='format:%aN <%aE>' || true\"\n> >\n> > (This is because it looks like $ARG is replaced only once with the\n> > actual value, which is perhaps a bug. Otherwise something like the\n> > following might work:\n>\n> I do not know the origin of that code in trailers.c but it feels\n> quite confused and error prone to use textual replacement with\n> strbuf_replace().  Why doesn't the code, which knows it will use\n> shell to execute the command line given by the end user in the\n> configuration, to just export ARG as an environment variable and\n> be done with it?  It would also avoid quoting problem etc.\n\nYeah, I agree that would be better.\n"},{"id":"419996","messageId":"CAOLTT8TKqDWnu3k1Xz=qBhmdZYFhM452WzVGG=OArgJL3z0yGQ@mail.gmail.com","threadId":"55288","inReplyTo":"xmqqv99i4ck2.fsf@gitster.g","subject":"Re: [PATCH v13] [GSOC] commit: add --trailer option","fromName":"ZheNing Hu","fromEmail":"adlternative@gmail.com","sentAt":"2021-03-23T10:35:04Z","receivedAt":"2021-03-23T10:36:20Z","isPatch":true,"sender":{"key":"adlternative@gmail.com","avatar":"https://avatars.githubusercontent.com/u/58138461?v=4"},"body":"Junio C Hamano <gitster@pobox.com> 于2021年3月23日周二 下午2:19写道：\n>\n> Christian Couder <christian.couder@gmail.com> writes:\n>\n> > If you want nothing to happen when $ARG isn't set, you can change the\n> > config option to something like:\n> >\n> > $ git config trailer.sign.command \"NAME='\\$ARG'; test -n \\\"\\$NAME\\\" &&\n> > git log --author=\\\"\\$NAME\\\" -1 --format='format:%aN <%aE>' || true\"\n> >\n> > (This is because it looks like $ARG is replaced only once with the\n> > actual value, which is perhaps a bug. Otherwise something like the\n> > following might work:\n>\n> I do not know the origin of that code in trailers.c but it feels\n> quite confused and error prone to use textual replacement with\n> strbuf_replace().  Why doesn't the code, which knows it will use\n> shell to execute the command line given by the end user in the\n> configuration, to just export ARG as an environment variable and\n> be done with it?  It would also avoid quoting problem etc.\n>\n\nMaybe like this?\n\n-#define TRAILER_ARG_STRING \"$ARG\"\n-\nstatic const char *git_generated_prefixes[] = {\n       \"Signed-off-by: \",\n       \"(cherry picked from commit \",\n@@ -222,13 +220,17 @@ static char *apply_command(const char *command,\nconst char *arg)\n       struct strbuf buf = STRBUF_INIT;\n       struct child_process cp = CHILD_PROCESS_INIT;\n       char *result;\n+       const char *const *var;\n\n       strbuf_addstr(&cmd, command);\n-       if (arg)\n-               strbuf_replace(&cmd, TRAILER_ARG_STRING, arg);\n+       for (var = local_repo_env; *var; var++)\n+               strvec_push(&cp.env_array, *var);\n+       if (arg) {\n+               strvec_pushf(&cp.env_array, \"ARG=%s\", arg);\n+       }\n\n       strvec_push(&cp.args, cmd.buf);\n-       cp.env = local_repo_env;\n+\n       cp.no_stdin = 1;\n       cp.use_shell = 1;\n"},{"id":"420001","messageId":"CAP8UFD2v=q45u_jttHmff_1=rBCANmam_-HbNE5Fg=SnhZ_3Hg@mail.gmail.com","threadId":"55288","inReplyTo":"CAOLTT8TKqDWnu3k1Xz=qBhmdZYFhM452WzVGG=OArgJL3z0yGQ@mail.gmail.com","subject":"Re: [PATCH v13] [GSOC] commit: add --trailer option","fromName":"Christian Couder","fromEmail":"christian.couder@gmail.com","sentAt":"2021-03-23T12:41:08Z","receivedAt":"2021-03-23T12:42:11Z","isPatch":true,"sender":{"key":"christian.couder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/208954?v=4"},"body":"On Tue, Mar 23, 2021 at 11:35 AM ZheNing Hu <adlternative@gmail.com> wrote:\n>\n> Junio C Hamano <gitster@pobox.com> 于2021年3月23日周二 下午2:19写道：\n\n> Maybe like this?\n\nYeah, it's a good idea to work on this, but please make it an\nindependent patch unrelated to adding --trailer to `git commit`.\n\nAlso the documentation might need a bit of tweeking to tell that ARG\nis now an exported environment variable.\n\n> -#define TRAILER_ARG_STRING \"$ARG\"\n\nIt might still be better to use a #define or a const char [] to avoid\nhard coding \"ARG\" below.\n\n> static const char *git_generated_prefixes[] = {\n>        \"Signed-off-by: \",\n>        \"(cherry picked from commit \",\n> @@ -222,13 +220,17 @@ static char *apply_command(const char *command,\n> const char *arg)\n>        struct strbuf buf = STRBUF_INIT;\n>        struct child_process cp = CHILD_PROCESS_INIT;\n>        char *result;\n> +       const char *const *var;\n>\n>        strbuf_addstr(&cmd, command);\n> -       if (arg)\n> -               strbuf_replace(&cmd, TRAILER_ARG_STRING, arg);\n> +       for (var = local_repo_env; *var; var++)\n> +               strvec_push(&cp.env_array, *var);\n> +       if (arg) {\n> +               strvec_pushf(&cp.env_array, \"ARG=%s\", arg);\n> +       }\n\nYou can drop the \"{\" and \"}\" above.\n\n>        strvec_push(&cp.args, cmd.buf);\n> -       cp.env = local_repo_env;\n> +\n>        cp.no_stdin = 1;\n>        cp.use_shell = 1;\n\nThanks!\n"},{"id":"420026","messageId":"pull.901.v14.git.1616507757999.gitgitgadget@gmail.com","threadId":"55288","inReplyTo":"pull.901.v13.git.1616387093662.gitgitgadget@gmail.com","subject":"[PATCH v14] [GSOC] commit: add --trailer option","fromName":"ZheNing Hu via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2021-03-23T13:55:57Z","receivedAt":"2021-03-23T13:56:42Z","isPatch":true,"sender":{"key":"adlternative@gmail.com","avatar":"https://avatars.githubusercontent.com/u/58138461?v=4"},"body":"From: ZheNing Hu <adlternative@gmail.com>\n\nHistorically, Git has supported the 'Signed-off-by' commit trailer\nusing the '--signoff' and the '-s' option from the command line.\nBut users may need to provide other trailer information from the\ncommand line such as \"Helped-by\", \"Reported-by\", \"Mentored-by\",\n\nNow implement a new `--trailer <token>[(=|:)<value>]` option to pass\nother trailers to `interpret-trailers` and insert them into commit\nmessages.\n\nSigned-off-by: ZheNing Hu <adlternative@gmail.com>\n---\n    [GSOC] commit: add --trailer option\n    \n    commit --trailer connecting to the interpret-trailers backend, which can\n    directly generate trailers similar to \"Signed-off-by\". Hope this will\n    make it easier for programmers and maintainers :)\n\nPublished-As: https://github.com/gitgitgadget/git/releases/tag/pr-901%2Fadlternative%2Fcommit-with-multiple-signatures-v14\nFetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-901/adlternative/commit-with-multiple-signatures-v14\nPull-Request: https://github.com/gitgitgadget/git/pull/901\n\nRange-diff vs v13:\n\n 1:  98b0c470e141 ! 1:  bb36af7c4827 [GSOC] commit: add --trailer option\n     @@ Commit message\n          messages.\n      \n          Signed-off-by: ZheNing Hu <adlternative@gmail.com>\n     -    Reported-by:\n      \n       ## Documentation/git-commit.txt ##\n      @@ Documentation/git-commit.txt: SYNOPSIS\n     @@ t/t7502-commit-porcelain.sh: test_expect_success 'sign off' '\n      +\tEOF\n      +\tgit -c trailer.report.key=\"Reported-by: \" \\\n      +\t\t-c trailer.report.ifexists=\"replace\" \\\n     -+\t\t-c trailer.report.command=\"git log --author=\\\"\\$ARG\\\" -1 \\\n     -+\t\t--format=\\\"format:%aN <%aE>\\\"\" \\\n     ++\t\t-c trailer.report.command=\"NAME=\\\"\\$ARG\\\"; test -n \\\"\\$NAME\\\" && \\\n     ++\t\tgit log --author=\\\"\\$NAME\\\" -1 --format=\\\"format:%aN <%aE>\\\" || true\" \\\n      +\t\tcommit --trailer \"report = author\" --amend &&\n      +\tgit cat-file commit HEAD >commit.msg &&\n      +\tsed -e \"1,/^\\$/d\" commit.msg >actual &&\n\n\n Documentation/git-commit.txt |  14 +-\n builtin/commit.c             |  22 +++\n t/t7502-commit-porcelain.sh  | 312 +++++++++++++++++++++++++++++++++++\n 3 files changed, 347 insertions(+), 1 deletion(-)\n\ndiff --git a/Documentation/git-commit.txt b/Documentation/git-commit.txt\nindex 17150fa7eabe..3fe7ef33cb07 100644\n--- a/Documentation/git-commit.txt\n+++ b/Documentation/git-commit.txt\n@@ -14,7 +14,8 @@ SYNOPSIS\n \t   [--allow-empty-message] [--no-verify] [-e] [--author=<author>]\n \t   [--date=<date>] [--cleanup=<mode>] [--[no-]status]\n \t   [-i | -o] [--pathspec-from-file=<file> [--pathspec-file-nul]]\n-\t   [-S[<keyid>]] [--] [<pathspec>...]\n+\t   [(--trailer <token>[(=|:)<value>])...] [-S[<keyid>]]\n+\t   [--] [<pathspec>...]\n \n DESCRIPTION\n -----------\n@@ -166,6 +167,17 @@ The `-m` option is mutually exclusive with `-c`, `-C`, and `-F`.\n \n include::signoff-option.txt[]\n \n+--trailer <token>[(=|:)<value>]::\n+\tSpecify a (<token>, <value>) pair that should be applied as a\n+\ttrailer. (e.g. `git commit --trailer \"Signed-off-by:C O Mitter \\\n+\t<committer@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 commit 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+\n -n::\n --no-verify::\n \tThis option bypasses the pre-commit and commit-msg hooks.\ndiff --git a/builtin/commit.c b/builtin/commit.c\nindex 739110c5a7f6..4b06672bd07d 100644\n--- a/builtin/commit.c\n+++ b/builtin/commit.c\n@@ -113,6 +113,7 @@ static int config_commit_verbose = -1; /* unspecified */\n static int no_post_rewrite, allow_empty_message, pathspec_file_nul;\n static char *untracked_files_arg, *force_date, *ignore_submodule_arg, *ignored_arg;\n static char *sign_commit, *pathspec_from_file;\n+static struct strvec trailer_args = STRVEC_INIT;\n \n /*\n  * The default commit message cleanup mode will remove the lines\n@@ -131,6 +132,14 @@ 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(&trailer_args, \"--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@@ -958,6 +967,18 @@ static int prepare_to_commit(const char *index_file, const char *prefix,\n \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\", 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\t\tdie(_(\"unable to pass trailers to --trailers\"));\n+\t\tstrvec_clear(&trailer_args);\n+\t}\n+\n \t/*\n \t * Reject an attempt to record a non-merge empty commit without\n \t * explicit --allow-empty. In the cherry-pick case, it may be\n@@ -1507,6 +1528,7 @@ int cmd_commit(int argc, const char **argv, const char *prefix)\n \t\tOPT_STRING(0, \"fixup\", &fixup_message, N_(\"commit\"), N_(\"use autosquash formatted message to fixup 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\", NULL, N_(\"trailer\"), N_(\"add custom trailer(s)\"), PARSE_OPT_NONEG, opt_pass_trailer),\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/t/t7502-commit-porcelain.sh b/t/t7502-commit-porcelain.sh\nindex 6396897cc818..38a532d81ccd 100755\n--- a/t/t7502-commit-porcelain.sh\n+++ b/t/t7502-commit-porcelain.sh\n@@ -38,6 +38,16 @@ check_summary_oneline() {\n \ttest_cmp exp act\n }\n \n+trailer_commit_base () {\n+\techo \"fun\" >>file &&\n+\tgit add file &&\n+\tgit commit -s --trailer \"Signed-off-by=C1 E1 \" \\\n+\t\t--trailer \"Helped-by:C2 E2 \" \\\n+\t\t--trailer \"Reported-by=C3 E3\" \\\n+\t\t--trailer \"Mentored-by:C4 E4\" \\\n+\t\t-m \"hello\"\n+}\n+\n test_expect_success 'output summary format' '\n \n \techo new >file1 &&\n@@ -154,6 +164,308 @@ test_expect_success 'sign off' '\n \n '\n \n+test_expect_success 'commit --trailer with \"=\"' '\n+\ttrailer_commit_base &&\n+\tcat >expected <<-\\EOF &&\n+\thello\n+\n+\tSigned-off-by: C O Mitter <committer@example.com>\n+\tSigned-off-by: C1 E1\n+\tHelped-by: C2 E2\n+\tReported-by: C3 E3\n+\tMentored-by: C4 E4\n+\tEOF\n+\tgit cat-file commit HEAD >commit.msg &&\n+\tsed -e \"1,/^\\$/d\" commit.msg >actual &&\n+\ttest_cmp expected actual\n+'\n+\n+test_expect_success 'commit --trailer with -c and \"replace\" as ifexists' '\n+\ttrailer_commit_base &&\n+\tcat >expected <<-\\EOF &&\n+\thello\n+\n+\tSigned-off-by: C O Mitter <committer@example.com>\n+\tSigned-off-by: C1 E1\n+\tReported-by: C3 E3\n+\tMentored-by: C4 E4\n+\tHelped-by: C3 E3\n+\tEOF\n+\tgit -c trailer.ifexists=\"replace\" \\\n+\t\tcommit --trailer \"Mentored-by: C4 E4\" \\\n+\t\t --trailer \"Helped-by: C3 E3\" \\\n+\t\t--amend &&\n+\tgit cat-file commit HEAD >commit.msg &&\n+\tsed -e \"1,/^\\$/d\"  commit.msg >actual &&\n+\ttest_cmp expected actual\n+'\n+\n+test_expect_success 'commit --trailer with -c and \"add\" as ifexists' '\n+\ttrailer_commit_base &&\n+\tcat >expected <<-\\EOF &&\n+\thello\n+\n+\tSigned-off-by: C O Mitter <committer@example.com>\n+\tSigned-off-by: C1 E1\n+\tHelped-by: C2 E2\n+\tReported-by: C3 E3\n+\tMentored-by: C4 E4\n+\tReported-by: C3 E3\n+\tMentored-by: C4 E4\n+\tEOF\n+\tgit -c trailer.ifexists=\"add\" \\\n+\t\tcommit --trailer \"Reported-by: C3 E3\" \\\n+\t\t--trailer \"Mentored-by: C4 E4\" \\\n+\t\t--amend &&\n+\tgit cat-file commit HEAD >commit.msg &&\n+\tsed -e \"1,/^\\$/d\"  commit.msg >actual &&\n+\ttest_cmp expected actual\n+'\n+\n+test_expect_success 'commit --trailer with -c and \"donothing\" as ifexists' '\n+\ttrailer_commit_base &&\n+\tcat >expected <<-\\EOF &&\n+\thello\n+\n+\tSigned-off-by: C O Mitter <committer@example.com>\n+\tSigned-off-by: C1 E1\n+\tHelped-by: C2 E2\n+\tReported-by: C3 E3\n+\tMentored-by: C4 E4\n+\tReviewed-by: C6 E6\n+\tEOF\n+\tgit -c trailer.ifexists=\"donothing\" \\\n+\t\tcommit --trailer \"Mentored-by: C5 E5\" \\\n+\t\t--trailer \"Reviewed-by: C6 E6\" \\\n+\t\t--amend &&\n+\tgit cat-file commit HEAD >commit.msg &&\n+\tsed -e \"1,/^\\$/d\"  commit.msg >actual &&\n+\ttest_cmp expected actual\n+'\n+\n+test_expect_success 'commit --trailer with -c and \"addIfDifferent\" as ifexists' '\n+\ttrailer_commit_base &&\n+\tcat >expected <<-\\EOF &&\n+\thello\n+\n+\tSigned-off-by: C O Mitter <committer@example.com>\n+\tSigned-off-by: C1 E1\n+\tHelped-by: C2 E2\n+\tReported-by: C3 E3\n+\tMentored-by: C4 E4\n+\tMentored-by: C5 E5\n+\tEOF\n+\tgit -c trailer.ifexists=\"addIfDifferent\" \\\n+\t\tcommit --trailer \"Reported-by: C3 E3\" \\\n+\t\t--trailer \"Mentored-by: C5 E5\" \\\n+\t\t--amend &&\n+\tgit cat-file commit HEAD >commit.msg &&\n+\tsed -e \"1,/^\\$/d\"  commit.msg >actual &&\n+\ttest_cmp expected actual\n+'\n+\n+test_expect_success 'commit --trailer with -c and \"addIfDifferentNeighbor\" as ifexists' '\n+\ttrailer_commit_base &&\n+\tcat >expected <<-\\EOF &&\n+\thello\n+\n+\tSigned-off-by: C O Mitter <committer@example.com>\n+\tSigned-off-by: C1 E1\n+\tHelped-by: C2 E2\n+\tReported-by: C3 E3\n+\tMentored-by: C4 E4\n+\tReported-by: C3 E3\n+\tEOF\n+\tgit -c trailer.ifexists=\"addIfDifferentNeighbor\" \\\n+\t\tcommit --trailer \"Mentored-by: C4 E4\" \\\n+\t\t--trailer \"Reported-by: C3 E3\" \\\n+\t\t--amend &&\n+\tgit cat-file commit HEAD >commit.msg &&\n+\tsed -e \"1,/^\\$/d\"  commit.msg >actual &&\n+\ttest_cmp expected actual\n+'\n+\n+test_expect_success 'commit --trailer with -c and \"end\" as where' '\n+\ttrailer_commit_base &&\n+\tcat >expected <<-\\EOF &&\n+\thello\n+\n+\tSigned-off-by: C O Mitter <committer@example.com>\n+\tSigned-off-by: C1 E1\n+\tHelped-by: C2 E2\n+\tReported-by: C3 E3\n+\tMentored-by: C4 E4\n+\tReported-by: C3 E3\n+\tMentored-by: C4 E4\n+\tEOF\n+\tgit -c trailer.where=\"end\" \\\n+\t\tcommit --trailer \"Reported-by: C3 E3\" \\\n+\t\t--trailer \"Mentored-by: C4 E4\" \\\n+\t\t--amend &&\n+\tgit cat-file commit HEAD >commit.msg &&\n+\tsed -e \"1,/^\\$/d\" commit.msg >actual &&\n+\ttest_cmp expected actual\n+'\n+\n+test_expect_success 'commit --trailer with -c and \"start\" as where' '\n+\ttrailer_commit_base &&\n+\tcat >expected <<-\\EOF &&\n+\thello\n+\n+\tSigned-off-by: C1 E1\n+\tSigned-off-by: C O Mitter <committer@example.com>\n+\tSigned-off-by: C1 E1\n+\tHelped-by: C2 E2\n+\tReported-by: C3 E3\n+\tMentored-by: C4 E4\n+\tEOF\n+\tgit -c trailer.where=\"start\" \\\n+\t\tcommit --trailer \"Signed-off-by: C O Mitter <committer@example.com>\" \\\n+\t\t--trailer \"Signed-off-by: C1 E1\" \\\n+\t\t--amend &&\n+\tgit cat-file commit HEAD >commit.msg &&\n+\tsed -e \"1,/^\\$/d\" commit.msg >actual &&\n+\ttest_cmp expected actual\n+'\n+\n+test_expect_success 'commit --trailer with -c and \"after\" as where' '\n+\ttrailer_commit_base &&\n+\tcat >expected <<-\\EOF &&\n+\thello\n+\n+\tSigned-off-by: C O Mitter <committer@example.com>\n+\tSigned-off-by: C1 E1\n+\tHelped-by: C2 E2\n+\tReported-by: C3 E3\n+\tMentored-by: C4 E4\n+\tMentored-by: C5 E5\n+\tEOF\n+\tgit -c trailer.where=\"after\" \\\n+\t\tcommit --trailer \"Mentored-by: C4 E4\" \\\n+\t\t--trailer \"Mentored-by: C5 E5\" \\\n+\t\t--amend &&\n+\tgit cat-file commit HEAD >commit.msg &&\n+\tsed -e \"1,/^\\$/d\" commit.msg >actual &&\n+\ttest_cmp expected actual\n+'\n+\n+test_expect_success 'commit --trailer with -c and \"before\" as where' '\n+\ttrailer_commit_base &&\n+\tcat >expected <<-\\EOF &&\n+\thello\n+\n+\tSigned-off-by: C O Mitter <committer@example.com>\n+\tSigned-off-by: C1 E1\n+\tHelped-by: C2 E2\n+\tReported-by: C3 E3\n+\tMentored-by: C2 E2\n+\tMentored-by: C3 E3\n+\tMentored-by: C4 E4\n+\tEOF\n+\tgit -c trailer.where=\"before\" \\\n+\t\tcommit --trailer \"Mentored-by: C3 E3\" \\\n+\t\t--trailer \"Mentored-by: C2 E2\" \\\n+\t\t--amend &&\n+\tgit cat-file commit HEAD >commit.msg &&\n+\tsed -e \"1,/^\\$/d\" commit.msg >actual &&\n+\ttest_cmp expected actual\n+'\n+\n+test_expect_success 'commit --trailer with -c and \"donothing\" as ifmissing' '\n+\ttrailer_commit_base &&\n+\tcat >expected <<-\\EOF &&\n+\thello\n+\n+\tSigned-off-by: C O Mitter <committer@example.com>\n+\tSigned-off-by: C1 E1\n+\tHelped-by: C2 E2\n+\tReported-by: C3 E3\n+\tMentored-by: C4 E4\n+\tHelped-by: C5 E5\n+\tEOF\n+\tgit -c trailer.ifmissing=\"donothing\" \\\n+\t\tcommit --trailer \"Helped-by: C5 E5\" \\\n+\t\t--trailer \"Based-by: C6 E6\" \\\n+\t\t--amend &&\n+\tgit cat-file commit HEAD >commit.msg &&\n+\tsed -e \"1,/^\\$/d\" commit.msg >actual &&\n+\ttest_cmp expected actual\n+'\n+\n+test_expect_success 'commit --trailer with -c and \"add\" as ifmissing' '\n+\ttrailer_commit_base &&\n+\tcat >expected <<-\\EOF &&\n+\thello\n+\n+\tSigned-off-by: C O Mitter <committer@example.com>\n+\tSigned-off-by: C1 E1\n+\tHelped-by: C2 E2\n+\tReported-by: C3 E3\n+\tMentored-by: C4 E4\n+\tHelped-by: C5 E5\n+\tBased-by: C6 E6\n+\tEOF\n+\tgit -c trailer.ifmissing=\"add\" \\\n+\t\tcommit --trailer \"Helped-by: C5 E5\" \\\n+\t\t--trailer \"Based-by: C6 E6\" \\\n+\t\t--amend &&\n+\tgit cat-file commit HEAD >commit.msg &&\n+\tsed -e \"1,/^\\$/d\" commit.msg >actual &&\n+\ttest_cmp expected actual\n+'\n+\n+test_expect_success 'commit --trailer with -c ack.key ' '\n+\techo \"fun\" >>file1 &&\n+\tgit add file1 &&\n+\tcat >expected <<-\\EOF &&\n+\t\thello\n+\n+\t\tAcked-by: Peff\n+\tEOF\n+\tgit -c trailer.ack.key=\"Acked-by\" \\\n+\t\tcommit --trailer \"ack = Peff\" -m \"hello\" &&\n+\tgit cat-file commit HEAD >commit.msg &&\n+\tsed -e \"1,/^\\$/d\" commit.msg >actual &&\n+\ttest_cmp expected actual\n+'\n+\n+test_expect_success 'commit --trailer with -c and \":=#\" as separators' '\n+\techo \"fun\" >>file1 &&\n+\tgit add file1 &&\n+\tcat >expected <<-\\EOF &&\n+\t\tI hate bug\n+\n+\t\tBug #42\n+\tEOF\n+\tgit -c trailer.separators=\":=#\" \\\n+\t\t-c trailer.bug.key=\"Bug #\" \\\n+\t\tcommit --trailer \"bug = 42\" -m \"I hate bug\" &&\n+\tgit cat-file commit HEAD >commit.msg &&\n+\tsed -e \"1,/^\\$/d\" commit.msg >actual &&\n+\ttest_cmp expected actual\n+'\n+\n+test_expect_success 'commit --trailer with -c and command' '\n+\ttrailer_commit_base &&\n+\tcat >expected <<-\\EOF &&\n+\thello\n+\n+\tSigned-off-by: C O Mitter <committer@example.com>\n+\tSigned-off-by: C1 E1\n+\tHelped-by: C2 E2\n+\tMentored-by: C4 E4\n+\tReported-by: A U Thor <author@example.com>\n+\tEOF\n+\tgit -c trailer.report.key=\"Reported-by: \" \\\n+\t\t-c trailer.report.ifexists=\"replace\" \\\n+\t\t-c trailer.report.command=\"NAME=\\\"\\$ARG\\\"; test -n \\\"\\$NAME\\\" && \\\n+\t\tgit log --author=\\\"\\$NAME\\\" -1 --format=\\\"format:%aN <%aE>\\\" || true\" \\\n+\t\tcommit --trailer \"report = author\" --amend &&\n+\tgit cat-file commit HEAD >commit.msg &&\n+\tsed -e \"1,/^\\$/d\" commit.msg >actual &&\n+\ttest_cmp expected actual\n+'\n+\n test_expect_success 'multiple -m' '\n \n \t>negative &&\n\nbase-commit: 13d7ab6b5d7929825b626f050b62a11241ea4945\n-- \ngitgitgadget\n"},{"id":"420048","messageId":"xmqqeeg54wye.fsf@gitster.g","threadId":"55288","inReplyTo":"CAP8UFD0s4Gm3PgDPpsXC-8Gyrnn1JTMUBu60XGV7i8nRDCCJ2Q@mail.gmail.com","subject":"Re: [PATCH v13] [GSOC] commit: add --trailer option","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2021-03-23T17:11:05Z","receivedAt":"2021-03-23T17:11:57Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Christian Couder <christian.couder@gmail.com> writes:\n\n> On Tue, Mar 23, 2021 at 7:19 AM Junio C Hamano <gitster@pobox.com> wrote:\n>>\n>> Christian Couder <christian.couder@gmail.com> writes:\n>>\n>> > If you want nothing to happen when $ARG isn't set, you can change the\n>> > config option to something like:\n>> >\n>> > $ git config trailer.sign.command \"NAME='\\$ARG'; test -n \\\"\\$NAME\\\" &&\n>> > git log --author=\\\"\\$NAME\\\" -1 --format='format:%aN <%aE>' || true\"\n>> >\n>> > (This is because it looks like $ARG is replaced only once with the\n>> > actual value, which is perhaps a bug. Otherwise something like the\n>> > following might work:\n>>\n>> I do not know the origin of that code in trailers.c but it feels\n>> quite confused and error prone to use textual replacement with\n>> strbuf_replace().  Why doesn't the code, which knows it will use\n>> shell to execute the command line given by the end user in the\n>> configuration, to just export ARG as an environment variable and\n>> be done with it?  It would also avoid quoting problem etc.\n>\n> Yeah, I agree that would be better.\n\nIt probably would have been better to do so before the feature got\nunleased to the public, but doing such a change retroactively would\nintroduce regression for those who were using ARG that happens to be\nsafe from shell quoting rules.\n\nFor example, if the trailer.*.command were\n\n\techo '$ARG'\n\nand the argument 'h e l l o' were to be given to it, then the\ncurrent code would have textually expanded $ARG with the argument\nand caused\n\n\techo 'h e l l o'\n\nto run, which would have been \"fine\" [*1*].\n\nBut exporting the environment ARG would \"break\" such a setting that\nhas been \"working perfectly well\" for the user.  Because of the\nsingle-quotes around $ARG, the command now will give literal four\nletter string $ARG and not 'h e l l o'.\n\nWe should think such potential ramifications of changing it (and\nalso not changing it) through before deciding what to do about it.\n\nAlthough I have a feeling that not many people would miss '$ARG'\ninside a pair of single-quotes to be replaced textually and it would\nbe OK to make a backward incompatible bugfix, the safer and better\nway is not all that difficult, so I am inclined to suggest going the\nusual \"deprecate and replace and then later remove\" dance.\n\nThe normal sequence of replacing a \"sort of works but not\nrecommended\" feature with a \"better and safer, but can break a\nsetting that has been 'working'\" feature is:\n\n - Announce deprecation of trailer.x.command and add and advertise a\n   similar traier.x.cmd that (1) exports environment variable ARG,\n   or (2) passes the argument as a positional parameter [*], as a\n   replacement.  Explain the reason for deprecation (i.e. unsafe\n   substitution that works only once).  When .cmd exists, .command\n   is ignored for the corresponding trailer.x\n\n - Wait for a few releases and then remove trailer.x.command.\n\nand that is the safest way to fix this \"bug\".\n\n\n[Footnotes]\n\n*1* If the argument were \n\n\t';rm -rf .;'\n\n    then it wouldn't have been fine, though, and that is how the\n    current code solicited \"Huh?\"  reaction out of me.\n\n\n*2* If we passed the argument as a positional parameter, the example\n    you gave in the quoted part of the message would become\n    something like this:\n\n      [trailer \"sign\"]\n        cmd = test -n \"$1\" && git log --author=\"$1\" -1 --format='%aN <%aE>'\n"},{"id":"420049","messageId":"xmqqa6qt4wvw.fsf@gitster.g","threadId":"55288","inReplyTo":"CAOLTT8TKqDWnu3k1Xz=qBhmdZYFhM452WzVGG=OArgJL3z0yGQ@mail.gmail.com","subject":"Re: [PATCH v13] [GSOC] commit: add --trailer option","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2021-03-23T17:12:35Z","receivedAt":"2021-03-23T17:13:34Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"ZheNing Hu <adlternative@gmail.com> writes:\n\n> Junio C Hamano <gitster@pobox.com> 于2021年3月23日周二 下午2:19写道：\n>>\n>> Christian Couder <christian.couder@gmail.com> writes:\n>>\n>> > If you want nothing to happen when $ARG isn't set, you can change the\n>> > config option to something like:\n>> >\n>> > $ git config trailer.sign.command \"NAME='\\$ARG'; test -n \\\"\\$NAME\\\" &&\n>> > git log --author=\\\"\\$NAME\\\" -1 --format='format:%aN <%aE>' || true\"\n>> >\n>> > (This is because it looks like $ARG is replaced only once with the\n>> > actual value, which is perhaps a bug. Otherwise something like the\n>> > following might work:\n>>\n>> I do not know the origin of that code in trailers.c but it feels\n>> quite confused and error prone to use textual replacement with\n>> strbuf_replace().  Why doesn't the code, which knows it will use\n>> shell to execute the command line given by the end user in the\n>> configuration, to just export ARG as an environment variable and\n>> be done with it?  It would also avoid quoting problem etc.\n>>\n>\n> Maybe like this?\n\nCode is not an important part.  We should think through\nramifications of making (and not making) such a change first.\n"},{"id":"420100","messageId":"CAOLTT8S5djZQ7AEA8Cs5Q_3SMdjo+efJ+U4bP23=iXQGxqfyfQ@mail.gmail.com","threadId":"55288","inReplyTo":"xmqqeeg54wye.fsf@gitster.g","subject":"Re: [PATCH v13] [GSOC] commit: add --trailer option","fromName":"ZheNing Hu","fromEmail":"adlternative@gmail.com","sentAt":"2021-03-24T05:21:54Z","receivedAt":"2021-03-24T05:23:14Z","isPatch":true,"sender":{"key":"adlternative@gmail.com","avatar":"https://avatars.githubusercontent.com/u/58138461?v=4"},"body":">\n> It probably would have been better to do so before the feature got\n> unleased to the public, but doing such a change retroactively would\n> introduce regression for those who were using ARG that happens to be\n> safe from shell quoting rules.\n>\n> For example, if the trailer.*.command were\n>\n>         echo '$ARG'\n>\n> and the argument 'h e l l o' were to be given to it, then the\n> current code would have textually expanded $ARG with the argument\n> and caused\n>\n>         echo 'h e l l o'\n>\n> to run, which would have been \"fine\" [*1*].\n>\n> But exporting the environment ARG would \"break\" such a setting that\n> has been \"working perfectly well\" for the user.  Because of the\n> single-quotes around $ARG, the command now will give literal four\n> letter string $ARG and not 'h e l l o'.\n>\n> We should think such potential ramifications of changing it (and\n> also not changing it) through before deciding what to do about it.\n>\n> Although I have a feeling that not many people would miss '$ARG'\n> inside a pair of single-quotes to be replaced textually and it would\n> be OK to make a backward incompatible bugfix, the safer and better\n> way is not all that difficult, so I am inclined to suggest going the\n> usual \"deprecate and replace and then later remove\" dance.\n>\n\nI think what you mean is that my patch breaks the principle of\n\"forward compatibility\", which may make it impossible for users who\npreviously worked with `'$ARG'`.\n\n> The normal sequence of replacing a \"sort of works but not\n> recommended\" feature with a \"better and safer, but can break a\n> setting that has been 'working'\" feature is:\n>\n>  - Announce deprecation of trailer.x.command and add and advertise a\n>    similar traier.x.cmd that (1) exports environment variable ARG,\n>    or (2) passes the argument as a positional parameter [*], as a\n>    replacement.  Explain the reason for deprecation (i.e. unsafe\n>    substitution that works only once).  When .cmd exists, .command\n>    is ignored for the corresponding trailer.x\n>\n\nAs your example below:\n\ncmd = test -n \"$1\" && git log --author=\"$1\" -1 --format='%aN <%aE>'\n\nI would like to use this way.\n\nIt may be a bit cumbersome to do \"deprecate and replace and\nthen later remove\", Should documents tell users that the old method\n\"trailer.command\" has been replaced by \"trialer.cmd\"? Or tell users that\n\"trailer.cmd\" is a new feature?\n\nKeep these doubts, I will try go to code it.\n\n>  - Wait for a few releases and then remove trailer.x.command.\n>\n> and that is the safest way to fix this \"bug\".\n>\n>\n> [Footnotes]\n>\n> *1* If the argument were\n>\n>         ';rm -rf .;'\n>\n>     then it wouldn't have been fine, though, and that is how the\n>     current code solicited \"Huh?\"  reaction out of me.\n>\n\nA little scary. :-)\n\n>\n> *2* If we passed the argument as a positional parameter, the example\n>     you gave in the quoted part of the message would become\n>     something like this:\n>\n>       [trailer \"sign\"]\n>         cmd = test -n \"$1\" && git log --author=\"$1\" -1 --format='%aN <%aE>'\n"},{"id":"420101","messageId":"CAOLTT8Sff+Kn91QjvPZdX1JxEQp8eDBrWfqprtd=6mKQjCWGew@mail.gmail.com","threadId":"55288","inReplyTo":"xmqqa6qt4wvw.fsf@gitster.g","subject":"Re: [PATCH v13] [GSOC] commit: add --trailer option","fromName":"ZheNing Hu","fromEmail":"adlternative@gmail.com","sentAt":"2021-03-24T05:25:08Z","receivedAt":"2021-03-24T05:26:16Z","isPatch":true,"sender":{"key":"adlternative@gmail.com","avatar":"https://avatars.githubusercontent.com/u/58138461?v=4"},"body":"> > Maybe like this?\n>\n> Code is not an important part.  We should think through\n> ramifications of making (and not making) such a change first.\n\nSorry for without careful consideration.\nThank for this valuable suggestion.\n"}]}