[PATCH] [GSOC] commit: provides multiple common signatures

Subsystems: documentation, the rest

STALE2022d

55 messages, 7 authors, 2021-03-23 · open the first message on its own page

[PATCH] [GSOC] commit: provides multiple common signatures

From: ZheNing Hu via GitGitGadget <hidden>
Date: 2021-03-11 07:16:54

From: ZheNing Hu <redacted>

Similar to "Helped-by", "Reported-by", "Reviewed-by", "Mentored-by"
these signatures are often seen in git commit messages. After
referring to the simple implementation of `commit --signoff`
and `send-email -cc=" commiter <email>"`, I am considering
whether to provide multiple signature parameters from the
command line. I think this might help maintainers and
developers directly uses the shell to provide these signatures
instead of multiple times repetitive writing those trailers
each time.

To achieve this goal, i refactored the `append_signoff` design and
provided `append_message` and `append_message_string_list` interfaces,
providing new ways to generate those various signatures.

Users now can use `commit -H "helper <eamil>"` to generate "Helped-by" trailer,
`commit -R "reviewer <eamil>"` to generate "Reviewed-by" trailer,
`commit -r "reporter <eamil> "`to generate "Reported-by" trailer,
`commit -M "mentor <eamil>"` to generate "Mentored-by" trailer.

Signed-off-by: ZheNing Hu <redacted>
---
    [GSOC] commit: provides multiple signatures from command line
    
    I don’t know if my idea will satisfy everyone, I'm also thinking about
    whether we can provide a more generalized version (I think this idea can
    be extended: because users and developers have other signature methods
    that they want, such as "Based-on-patch-by") I hope someone can give me
    pointers (on the correctness of ideas or codes)

Published-As: https://github.com/gitgitgadget/git/releases/tag/pr-901%2Fadlternative%2Fcommit-with-multiple-signatures-v1
Fetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-901/adlternative/commit-with-multiple-signatures-v1
Pull-Request: https://github.com/gitgitgadget/git/pull/901

 Documentation/git-commit.txt |  24 +++++++-
 builtin/commit.c             |  63 +++++++++++++++++++++
 sequencer.c                  |  40 +++++++++----
 sequencer.h                  |   4 ++
 t/t7502-commit-porcelain.sh  | 106 +++++++++++++++++++++++++++++++++++
 5 files changed, 226 insertions(+), 11 deletions(-)
diff --git a/Documentation/git-commit.txt b/Documentation/git-commit.txt
index 17150fa7eabe..e1b528d70c1a 100644
--- a/Documentation/git-commit.txt
+++ b/Documentation/git-commit.txt
@@ -14,7 +14,9 @@ SYNOPSIS
 	   [--allow-empty-message] [--no-verify] [-e] [--author=<author>]
 	   [--date=<date>] [--cleanup=<mode>] [--[no-]status]
 	   [-i | -o] [--pathspec-from-file=<file> [--pathspec-file-nul]]
-	   [-S[<keyid>]] [--] [<pathspec>...]
+	   [-S[<keyid>]] [--] [<pathspec>...] [(-H|--helped-by)=<address>...]
+	   [(-R|--reviewed-by)=<address>...] [(-r|--reported-by)=<address>...]
+	   [(-M|--mentored)=<address>...]
 
 DESCRIPTION
 -----------
@@ -166,6 +168,26 @@ The `-m` option is mutually exclusive with `-c`, `-C`, and `-F`.
 
 include::signoff-option.txt[]
 
+-H=<address>...::
+--helped-by=<address>...::
+	Add one or more `Helped-by` trailer by the committer at the end of the commit
+	log message.
+
+-R=<address>...::
+--reviewed-by=<address>...::
+	Add one or more `Reviewed-by` trailer by the committer at the end of the commit
+	log message.
+
+-r=<address>...::
+--reported-by=<address>...::
+	Add one or more `Reported-by` trailer by the committer at the end of the commit
+	log message.
+
+-M=<address>...::
+--mentored-by=<address>...::
+	Add one or more `Mentored-by` trailer by the committer at the end of the commit
+	log message.
+
 -n::
 --no-verify::
 	This option bypasses the pre-commit and commit-msg hooks.
diff --git a/builtin/commit.c b/builtin/commit.c
index 739110c5a7f6..4b312af03181 100644
--- a/builtin/commit.c
+++ b/builtin/commit.c
@@ -113,6 +113,10 @@ static int config_commit_verbose = -1; /* unspecified */
 static int no_post_rewrite, allow_empty_message, pathspec_file_nul;
 static char *untracked_files_arg, *force_date, *ignore_submodule_arg, *ignored_arg;
 static char *sign_commit, *pathspec_from_file;
+static struct string_list helped_by = STRING_LIST_INIT_NODUP;
+static struct string_list mentored_by = STRING_LIST_INIT_NODUP;
+static struct string_list reviewed_by = STRING_LIST_INIT_NODUP;
+static struct string_list reported_by = STRING_LIST_INIT_NODUP;
 
 /*
  * The default commit message cleanup mode will remove the lines
@@ -829,6 +833,20 @@ static int prepare_to_commit(const char *index_file, const char *prefix,
 	if (signoff)
 		append_signoff(&sb, ignore_non_trailer(sb.buf, sb.len), 0);
 
+	if(helped_by.items)
+		append_message_string_list(&sb, "Helped-by: ", &helped_by, ignore_non_trailer(sb.buf, sb.len), 0);
+	if(reviewed_by.items)
+		append_message_string_list(&sb, "Reviewed-by: ", &reviewed_by, ignore_non_trailer(sb.buf, sb.len), 0);
+	if(reported_by.items)
+		append_message_string_list(&sb, "Reported-by: ", &reported_by, ignore_non_trailer(sb.buf, sb.len), 0);
+	if(mentored_by.items)
+		append_message_string_list(&sb, "Mentored-by: ", &mentored_by, ignore_non_trailer(sb.buf, sb.len), 0);
+
+	string_list_clear(&helped_by, 0);
+	string_list_clear(&reviewed_by, 0);
+	string_list_clear(&reported_by, 0);
+	string_list_clear(&mentored_by, 0);
+
 	if (fwrite(sb.buf, 1, sb.len, s->fp) < sb.len)
 		die_errno(_("could not write commit template"));
 
@@ -1490,6 +1508,42 @@ static int git_commit_config(const char *k, const char *v, void *cb)
 	return git_status_config(k, v, s);
 }
 
+static int help_callback(const struct option *opt, const char *arg, int unset)
+{
+	if (unset)
+		string_list_clear(&helped_by, 0);
+	else
+		string_list_append(&helped_by, arg);
+	return 0;
+}
+
+static int review_callback(const struct option *opt, const char *arg, int unset)
+{
+	if (unset)
+		string_list_clear(&reviewed_by, 0);
+	else
+		string_list_append(&reviewed_by, arg);
+	return 0;
+}
+
+static int report_callback(const struct option *opt, const char *arg, int unset)
+{
+	if (unset)
+		string_list_clear(&reported_by, 0);
+	else
+		string_list_append(&reported_by, arg);
+	return 0;
+}
+
+static int mentor_callback(const struct option *opt, const char *arg, int unset)
+{
+	if (unset)
+		string_list_clear(&mentored_by, 0);
+	else
+		string_list_append(&mentored_by, arg);
+	return 0;
+}
+
 int cmd_commit(int argc, const char **argv, const char *prefix)
 {
 	static struct wt_status s;
@@ -1507,6 +1561,10 @@ int cmd_commit(int argc, const char **argv, const char *prefix)
 		OPT_STRING(0, "fixup", &fixup_message, N_("commit"), N_("use autosquash formatted message to fixup specified commit")),
 		OPT_STRING(0, "squash", &squash_message, N_("commit"), N_("use autosquash formatted message to squash specified commit")),
 		OPT_BOOL(0, "reset-author", &renew_authorship, N_("the commit is authored by me now (used with -C/-c/--amend)")),
+		OPT_CALLBACK('H', "helped-by", NULL, N_("email"), N_("add a Helped-by trailer"), help_callback),
+		OPT_CALLBACK('r', "reported-by", NULL, N_("email"), N_("add a Reported-by trailer"), report_callback),
+		OPT_CALLBACK('R', "reviewed-by", NULL, N_("email"), N_("add a Reviewed-by trailer"), review_callback),
+		OPT_CALLBACK('M', "mentored-by", NULL, N_("email"), N_("add a Mentored-by trailer"), mentor_callback),
 		OPT_BOOL('s', "signoff", &signoff, N_("add a Signed-off-by trailer")),
 		OPT_FILENAME('t', "template", &template_file, N_("use specified template file")),
 		OPT_BOOL('e', "edit", &edit_flag, N_("force edit of commit")),
@@ -1561,6 +1619,11 @@ int cmd_commit(int argc, const char **argv, const char *prefix)
 	struct commit_extra_header *extra = NULL;
 	struct strbuf err = STRBUF_INIT;
 
+	helped_by.strdup_strings = 1;
+	reviewed_by.strdup_strings = 1;
+	reported_by.strdup_strings = 1;
+	mentored_by.strdup_strings = 1;
+
 	if (argc == 2 && !strcmp(argv[1], "-h"))
 		usage_with_options(builtin_commit_usage, builtin_commit_options);
 
diff --git a/sequencer.c b/sequencer.c
index d2332d3e1787..528daf9df252 100644
--- a/sequencer.c
+++ b/sequencer.c
@@ -4668,16 +4668,12 @@ int sequencer_pick_revisions(struct repository *r,
 	return res;
 }
 
-void append_signoff(struct strbuf *msgbuf, size_t ignore_footer, unsigned flag)
+void append_message(struct strbuf *msgbuf, struct strbuf *sob,
+			size_t ignore_footer, unsigned flag)
 {
 	unsigned no_dup_sob = flag & APPEND_SIGNOFF_DEDUP;
-	struct strbuf sob = STRBUF_INIT;
 	int has_footer;
 
-	strbuf_addstr(&sob, sign_off_header);
-	strbuf_addstr(&sob, fmt_name(WANT_COMMITTER_IDENT));
-	strbuf_addch(&sob, '\n');
-
 	if (!ignore_footer)
 		strbuf_complete_line(msgbuf);
 
@@ -4685,11 +4681,11 @@ void append_signoff(struct strbuf *msgbuf, size_t ignore_footer, unsigned flag)
 	 * If the whole message buffer is equal to the sob, pretend that we
 	 * found a conforming footer with a matching sob
 	 */
-	if (msgbuf->len - ignore_footer == sob.len &&
-	    !strncmp(msgbuf->buf, sob.buf, sob.len))
+	if (msgbuf->len - ignore_footer == sob->len &&
+	    !strncmp(msgbuf->buf, sob->buf, sob->len))
 		has_footer = 3;
 	else
-		has_footer = has_conforming_footer(msgbuf, &sob, ignore_footer);
+		has_footer = has_conforming_footer(msgbuf, sob, ignore_footer);
 
 	if (!has_footer) {
 		const char *append_newlines = NULL;
@@ -4723,8 +4719,32 @@ void append_signoff(struct strbuf *msgbuf, size_t ignore_footer, unsigned flag)
 
 	if (has_footer != 3 && (!no_dup_sob || has_footer != 2))
 		strbuf_splice(msgbuf, msgbuf->len - ignore_footer, 0,
-				sob.buf, sob.len);
+				sob->buf, sob->len);
+}
+
+void append_signoff(struct strbuf *msgbuf, size_t ignore_footer, unsigned flag)
+{
+	struct strbuf sob = STRBUF_INIT;
+	strbuf_addstr(&sob, sign_off_header);
+	strbuf_addstr(&sob, fmt_name(WANT_COMMITTER_IDENT));
+	strbuf_addch(&sob, '\n');
+	append_message(msgbuf, &sob, ignore_footer, flag);
+	strbuf_release(&sob);
+}
 
+void append_message_string_list(struct strbuf *msgbuf, const char *header,
+		struct string_list *sobs, size_t ignore_footer, unsigned flag) {
+	int i;
+	struct strbuf sob = STRBUF_INIT;
+
+	for ( i = 0; i < sobs->nr; i++)
+	{
+		strbuf_addstr(&sob, header);
+		strbuf_addstr(&sob, sobs->items[i].string);
+		strbuf_addch(&sob, '\n');
+		append_message(msgbuf, &sob, ignore_footer, flag);
+		strbuf_reset(&sob);
+	}
 	strbuf_release(&sob);
 }
 
diff --git a/sequencer.h b/sequencer.h
index f8b2e4ab8527..b24e274f4c62 100644
--- a/sequencer.h
+++ b/sequencer.h
@@ -174,6 +174,10 @@ int todo_list_rearrange_squash(struct todo_list *todo_list);
  * and the new signoff will be spliced into the buffer before those bytes.
  */
 void append_signoff(struct strbuf *msgbuf, size_t ignore_footer, unsigned flag);
+void append_message(struct strbuf *msgbuf, struct strbuf *sob,
+		size_t ignore_footer, unsigned flag);
+void append_message_string_list(struct strbuf *msgbuf, const char*header,
+		struct string_list *sobs, size_t ignore_footer, unsigned flag);
 
 void append_conflicts_hint(struct index_state *istate,
 		struct strbuf *msgbuf, enum commit_msg_cleanup_mode cleanup_mode);
diff --git a/t/t7502-commit-porcelain.sh b/t/t7502-commit-porcelain.sh
index 6396897cc818..40823152a51c 100755
--- a/t/t7502-commit-porcelain.sh
+++ b/t/t7502-commit-porcelain.sh
@@ -154,6 +154,112 @@ test_expect_success 'sign off' '
 
 '
 
+test_expect_success 'helped-by' '
+
+	>file1 &&
+	git add file1 &&
+	git commit --helped-by="foo <bar@frotz>" \
+	--helped-by="foo2 <bar2@frotz>" -m "thank you" &&
+	git cat-file commit HEAD >commit.msg &&
+	sed -ne "s/Helped-by: //p" commit.msg >actual &&
+	cat >expected <<-\EOF &&
+	foo <bar@frotz>
+	foo2 <bar2@frotz>
+	EOF
+	test_cmp expected actual
+
+'
+
+test_expect_success 'reported-by' '
+
+	>file2 &&
+	git add file2 &&
+	git commit --reported-by="foo <bar@frotz>" \
+	--reported-by="foo2 <bar2@frotz>" -m "thank you" &&
+	git cat-file commit HEAD >commit.msg &&
+	sed -ne "s/Reported-by: //p" commit.msg >actual &&
+	cat >expected <<-\EOF &&
+	foo <bar@frotz>
+	foo2 <bar2@frotz>
+	EOF
+	test_cmp expected actual
+
+'
+
+test_expect_success 'reviewed-by' '
+
+	>file3 &&
+	git add file3 &&
+	git commit --reviewed-by="foo <bar@frotz>" \
+	--reviewed-by="foo2 <bar2@frotz>" -m "thank you" &&
+	git cat-file commit HEAD >commit.msg &&
+	sed -ne "s/Reviewed-by: //p" commit.msg >actual &&
+	cat >expected <<-\EOF &&
+	foo <bar@frotz>
+	foo2 <bar2@frotz>
+	EOF
+	test_cmp expected actual
+
+'
+
+test_expect_success 'mentored-by' '
+
+	>file4 &&
+	git add file4 &&
+	git commit --mentored-by="foo <bar@frotz>" \
+	--mentored-by="foo2 <bar2@frotz>" -m "thank you" &&
+	git cat-file commit HEAD >commit.msg &&
+	sed -ne "s/Mentored-by: //p" commit.msg >actual &&
+	cat >expected <<-\EOF &&
+	foo <bar@frotz>
+	foo2 <bar2@frotz>
+	EOF
+	test_cmp expected actual
+
+'
+
+test_expect_success 'multiple signatures' '
+
+	>file5 &&
+	git add file5 &&
+	git commit --helped-by="foo <bar@frotz>" \
+	--reviewed-by="foo2 <bar2@frotz>" \
+	--mentored-by="foo3 <bar3@frotz>" \
+	--reported-by="foo4 <bar4@frotz>" -s -m "thank you" &&
+	git cat-file commit HEAD >commit.msg &&
+	sed -e "1,7d" commit.msg >actual &&
+	cat >expected <<-\EOF &&
+	Signed-off-by: C O Mitter <committer@example.com>
+	Helped-by: foo <bar@frotz>
+	Reviewed-by: foo2 <bar2@frotz>
+	Reported-by: foo4 <bar4@frotz>
+	Mentored-by: foo3 <bar3@frotz>
+	EOF
+	test_cmp expected actual
+
+'
+
+test_expect_success 'multiple signatures (use abbreviations)' '
+
+	>file6 &&
+	git add file6 &&
+	git commit -H "foo <bar@frotz>" \
+	-R "foo2 <bar2@frotz>" \
+	-M "foo3 <bar3@frotz>" \
+	-r "foo4 <bar4@frotz>" -s -m "thank you" &&
+	git cat-file commit HEAD >commit.msg &&
+	sed -e "1,7d" commit.msg >actual &&
+	cat >expected <<-\EOF &&
+	Signed-off-by: C O Mitter <committer@example.com>
+	Helped-by: foo <bar@frotz>
+	Reviewed-by: foo2 <bar2@frotz>
+	Reported-by: foo4 <bar4@frotz>
+	Mentored-by: foo3 <bar3@frotz>
+	EOF
+	test_cmp expected actual
+
+'
+
 test_expect_success 'multiple -m' '
 
 	>negative &&
base-commit: 13d7ab6b5d7929825b626f050b62a11241ea4945
-- 
gitgitgadget

Re: [PATCH] [GSOC] commit: provides multiple common signatures

From: Shourya Shukla <hidden>
Date: 2021-03-11 15:04:02

Hey!

The idea seems very useful to me though I am not sure what others feel
about this and whether Git is ready for such a thing or not. Keeping
this concern aside, I will still review the patch due to my own interest
in it as well.

On 11/03 07:16, ZheNing Hu via GitGitGadget wrote:
From: ZheNing Hu <redacted>

Similar to "Helped-by", "Reported-by", "Reviewed-by", "Mentored-by"
these signatures are often seen in git commit messages. After
I think it will be better to rephrase the line so that it is easier to
understand. It took me a couple of reads to figure out what you meant.

Something like:

Similar to "Signed-off-by", trailers such as "Reported-by", "Helped-by"
and "Mentored-by" are also seen in Git commits.
referring to the simple implementation of `commit --signoff`
and `send-email -cc=" commiter <email>"`, I am considering
whether to provide multiple signature parameters from the
command line. I think this might help maintainers and
developers directly uses the shell to provide these signatures
instead of multiple times repetitive writing those trailers
each time.
The above para is more appropriate for a cover letter than a commit
message. Your thought process for the patch you sent is equally valuable
but this does not belong in the commit message. Commit messages are more
about what you did and any nuances that follow. You get me?

Use 'git format-patch --cover-letter <...>' to create a cover letter for
your patch.
To achieve this goal, i refactored the `append_signoff` design and
provided `append_message` and `append_message_string_list` interfaces,
providing new ways to generate those various signatures.
s/i/I

Also, commit messages are generally written in the imperative tense when
desciribing what you have done in the commit.
Users now can use `commit -H "helper <eamil>"` to generate "Helped-by" trailer,
`commit -R "reviewer <eamil>"` to generate "Reviewed-by" trailer,
`commit -r "reporter <eamil> "`to generate "Reported-by" trailer,
`commit -M "mentor <eamil>"` to generate "Mentored-by" trailer.
Multiple typos.
Signed-off-by: ZheNing Hu <redacted>
So, an improved commit message could be:

-----8<-----
commit: support multiple trailers from the command line

Historically, Git has supported the 'Signed-off-by' commit trailer
using the '--signoff' and the '-s' option from the command line. Extend
this list to include other commonly used trailers viz., "Helped-by",
"Reviewed-by", "Reported-by" and "Mentored-by". Introduce options '-H',
'-R', '-r' and '-M' corresponding to the aforementioned trailers.

While at it, add tests in the script 't7502-commit-porcelain.sh' and add
information regarding these options in the Documentation of 'git
commit'.
----->8-----
    [GSOC] commit: provides multiple signatures from command line
    
    I don’t know if my idea will satisfy everyone, I'm also thinking about
    whether we can provide a more generalized version (I think this idea can
    be extended: because users and developers have other signature methods
    that they want, such as "Based-on-patch-by") I hope someone can give me
    pointers (on the correctness of ideas or codes)
It will be great if we let the user add customised options for the
respective trailers but I think that trying to cater to such a large
audience will only complicate the whole process and decrease the support
avialable for the options/command. Apart from this, I think that the
trailer "Reviewed-by" may not be that widely used and it would be better
if we remove it from this patch.
quoted hunk
 Documentation/git-commit.txt |  24 +++++++-
 builtin/commit.c             |  63 +++++++++++++++++++++
 sequencer.c                  |  40 +++++++++----
 sequencer.h                  |   4 ++
 t/t7502-commit-porcelain.sh  | 106 +++++++++++++++++++++++++++++++++++
 5 files changed, 226 insertions(+), 11 deletions(-)
diff --git a/Documentation/git-commit.txt b/Documentation/git-commit.txt
index 17150fa7eabe..e1b528d70c1a 100644
--- a/Documentation/git-commit.txt
+++ b/Documentation/git-commit.txt
@@ -14,7 +14,9 @@ SYNOPSIS
 	   [--allow-empty-message] [--no-verify] [-e] [--author=<author>]
 	   [--date=<date>] [--cleanup=<mode>] [--[no-]status]
 	   [-i | -o] [--pathspec-from-file=<file> [--pathspec-file-nul]]
-	   [-S[<keyid>]] [--] [<pathspec>...]
+	   [-S[<keyid>]] [--] [<pathspec>...] [(-H|--helped-by)=<address>...]
+	   [(-R|--reviewed-by)=<address>...] [(-r|--reported-by)=<address>...]
+	   [(-M|--mentored)=<address>...]
 
 DESCRIPTION
 -----------
@@ -166,6 +168,26 @@ The `-m` option is mutually exclusive with `-c`, `-C`, and `-F`.
 
 include::signoff-option.txt[]
 
+-H=<address>...::
+--helped-by=<address>...::
+	Add one or more `Helped-by` trailer by the committer at the end of the commit
+	log message.
+
+-R=<address>...::
+--reviewed-by=<address>...::
+	Add one or more `Reviewed-by` trailer by the committer at the end of the commit
+	log message.
+
+-r=<address>...::
+--reported-by=<address>...::
+	Add one or more `Reported-by` trailer by the committer at the end of the commit
+	log message.
+
+-M=<address>...::
+--mentored-by=<address>...::
+	Add one or more `Mentored-by` trailer by the committer at the end of the commit
+	log message.
Oh! I did not think you had added long forms of these options and was
about to comment on that. Good that you added. Do talk about them as
well in the commit message.
quoted hunk
 -n::
 --no-verify::
 	This option bypasses the pre-commit and commit-msg hooks.
diff --git a/builtin/commit.c b/builtin/commit.c
index 739110c5a7f6..4b312af03181 100644
--- a/builtin/commit.c
+++ b/builtin/commit.c
@@ -113,6 +113,10 @@ static int config_commit_verbose = -1; /* unspecified */
 static int no_post_rewrite, allow_empty_message, pathspec_file_nul;
 static char *untracked_files_arg, *force_date, *ignore_submodule_arg, *ignored_arg;
 static char *sign_commit, *pathspec_from_file;
+static struct string_list helped_by = STRING_LIST_INIT_NODUP;
+static struct string_list mentored_by = STRING_LIST_INIT_NODUP;
+static struct string_list reviewed_by = STRING_LIST_INIT_NODUP;
+static struct string_list reported_by = STRING_LIST_INIT_NODUP;
Good that you kept the variables as 'string_list's instead of a usual
'char*'.
quoted hunk
 /*
  * The default commit message cleanup mode will remove the lines
@@ -829,6 +833,20 @@ static int prepare_to_commit(const char *index_file, const char *prefix,
 	if (signoff)
 		append_signoff(&sb, ignore_non_trailer(sb.buf, sb.len), 0);
 
+	if(helped_by.items)
+		append_message_string_list(&sb, "Helped-by: ", &helped_by, ignore_non_trailer(sb.buf, sb.len), 0);
+	if(reviewed_by.items)
+		append_message_string_list(&sb, "Reviewed-by: ", &reviewed_by, ignore_non_trailer(sb.buf, sb.len), 0);
+	if(reported_by.items)
+		append_message_string_list(&sb, "Reported-by: ", &reported_by, ignore_non_trailer(sb.buf, sb.len), 0);
+	if(mentored_by.items)
+		append_message_string_list(&sb, "Mentored-by: ", &mentored_by, ignore_non_trailer(sb.buf, sb.len), 0);
I think this part will look prettier if you wrap around the text. For
code segments, the wrap around limit is 80 chars. For commit messages
its 72 chars. Wrap around means that as soon as you hit N number of
characters, you proceed to the next(new) line.
quoted hunk
+	string_list_clear(&helped_by, 0);
+	string_list_clear(&reviewed_by, 0);
+	string_list_clear(&reported_by, 0);
+	string_list_clear(&mentored_by, 0);
+
 	if (fwrite(sb.buf, 1, sb.len, s->fp) < sb.len)
 		die_errno(_("could not write commit template"));
 
@@ -1490,6 +1508,42 @@ static int git_commit_config(const char *k, const char *v, void *cb)
 	return git_status_config(k, v, s);
 }
 
+static int help_callback(const struct option *opt, const char *arg, int unset)
+{
+	if (unset)
+		string_list_clear(&helped_by, 0);
+	else
+		string_list_append(&helped_by, arg);
+	return 0;
+}
+
+static int review_callback(const struct option *opt, const char *arg, int unset)
+{
+	if (unset)
+		string_list_clear(&reviewed_by, 0);
+	else
+		string_list_append(&reviewed_by, arg);
+	return 0;
+}
+
+static int report_callback(const struct option *opt, const char *arg, int unset)
+{
+	if (unset)
+		string_list_clear(&reported_by, 0);
+	else
+		string_list_append(&reported_by, arg);
+	return 0;
+}
+
+static int mentor_callback(const struct option *opt, const char *arg, int unset)
+{
+	if (unset)
+		string_list_clear(&mentored_by, 0);
+	else
+		string_list_append(&mentored_by, arg);
+	return 0;
+}
 int cmd_commit(int argc, const char **argv, const char *prefix)
 {
 	static struct wt_status s;
@@ -1507,6 +1561,10 @@ int cmd_commit(int argc, const char **argv, const char *prefix)
 		OPT_STRING(0, "fixup", &fixup_message, N_("commit"), N_("use autosquash formatted message to fixup specified commit")),
 		OPT_STRING(0, "squash", &squash_message, N_("commit"), N_("use autosquash formatted message to squash specified commit")),
 		OPT_BOOL(0, "reset-author", &renew_authorship, N_("the commit is authored by me now (used with -C/-c/--amend)")),
+		OPT_CALLBACK('H', "helped-by", NULL, N_("email"), N_("add a Helped-by trailer"), help_callback),
+		OPT_CALLBACK('r', "reported-by", NULL, N_("email"), N_("add a Reported-by trailer"), report_callback),
+		OPT_CALLBACK('R', "reviewed-by", NULL, N_("email"), N_("add a Reviewed-by trailer"), review_callback),
+		OPT_CALLBACK('M', "mentored-by", NULL, N_("email"), N_("add a Mentored-by trailer"), mentor_callback),
It would be lovely if you could line wrap these above additions too!
quoted hunk
 		OPT_BOOL('s', "signoff", &signoff, N_("add a Signed-off-by trailer")),
 		OPT_FILENAME('t', "template", &template_file, N_("use specified template file")),
 		OPT_BOOL('e', "edit", &edit_flag, N_("force edit of commit")),
@@ -1561,6 +1619,11 @@ int cmd_commit(int argc, const char **argv, const char *prefix)
 	struct commit_extra_header *extra = NULL;
 	struct strbuf err = STRBUF_INIT;
 
+	helped_by.strdup_strings = 1;
+	reviewed_by.strdup_strings = 1;
+	reported_by.strdup_strings = 1;
+	mentored_by.strdup_strings = 1;
+
 	if (argc == 2 && !strcmp(argv[1], "-h"))
 		usage_with_options(builtin_commit_usage, builtin_commit_options);
 
diff --git a/sequencer.c b/sequencer.c
index d2332d3e1787..528daf9df252 100644
--- a/sequencer.c
+++ b/sequencer.c
@@ -4668,16 +4668,12 @@ int sequencer_pick_revisions(struct repository *r,
 	return res;
 }
 
-void append_signoff(struct strbuf *msgbuf, size_t ignore_footer, unsigned flag)
+void append_message(struct strbuf *msgbuf, struct strbuf *sob,
+			size_t ignore_footer, unsigned flag)
Its nice to see that you generalised the pre-exisiting function instead
of creating a new one(s) for the new trailers. Good.

It will be better to name 'struct strbuf *sob' to something more
generic, maybe 'struct strbuf *trailer' since 'sob' stands for
'Signed-off-by' and since the function is becoming more generic, its
contents should too.
quoted hunk
 {
 	unsigned no_dup_sob = flag & APPEND_SIGNOFF_DEDUP;
-	struct strbuf sob = STRBUF_INIT;
 	int has_footer;
 
-	strbuf_addstr(&sob, sign_off_header);
-	strbuf_addstr(&sob, fmt_name(WANT_COMMITTER_IDENT));
-	strbuf_addch(&sob, '\n');
-
 	if (!ignore_footer)
 		strbuf_complete_line(msgbuf);
 
@@ -4685,11 +4681,11 @@ void append_signoff(struct strbuf *msgbuf, size_t ignore_footer, unsigned flag)
 	 * If the whole message buffer is equal to the sob, pretend that we
 	 * found a conforming footer with a matching sob
 	 */
-	if (msgbuf->len - ignore_footer == sob.len &&
-	    !strncmp(msgbuf->buf, sob.buf, sob.len))
+	if (msgbuf->len - ignore_footer == sob->len &&
+	    !strncmp(msgbuf->buf, sob->buf, sob->len))
 		has_footer = 3;
 	else
-		has_footer = has_conforming_footer(msgbuf, &sob, ignore_footer);
+		has_footer = has_conforming_footer(msgbuf, sob, ignore_footer);
Again, rename the variables.
quoted hunk
 	if (!has_footer) {
 		const char *append_newlines = NULL;
@@ -4723,8 +4719,32 @@ void append_signoff(struct strbuf *msgbuf, size_t ignore_footer, unsigned flag)
 
 	if (has_footer != 3 && (!no_dup_sob || has_footer != 2))
 		strbuf_splice(msgbuf, msgbuf->len - ignore_footer, 0,
-				sob.buf, sob.len);
+				sob->buf, sob->len);
+}
+void append_signoff(struct strbuf *msgbuf, size_t ignore_footer, unsigned flag)
+{
+	struct strbuf sob = STRBUF_INIT;
+	strbuf_addstr(&sob, sign_off_header);
+	strbuf_addstr(&sob, fmt_name(WANT_COMMITTER_IDENT));
+	strbuf_addch(&sob, '\n');
+	append_message(msgbuf, &sob, ignore_footer, flag);
+	strbuf_release(&sob);
+}
I think it will be nice if you could use a flag to denote whether the
entity to be appended is a 'sign-off' or not. This way when say the
variable 'int signoff' is 1, then use the above part in the
'append_message()' function otherwise go with the flow that exists.
quoted hunk
+void append_message_string_list(struct strbuf *msgbuf, const char *header,
+		struct string_list *sobs, size_t ignore_footer, unsigned flag) {
+	int i;
+	struct strbuf sob = STRBUF_INIT;
+
+	for ( i = 0; i < sobs->nr; i++)
+	{
+		strbuf_addstr(&sob, header);
+		strbuf_addstr(&sob, sobs->items[i].string);
+		strbuf_addch(&sob, '\n');
+		append_message(msgbuf, &sob, ignore_footer, flag);
+		strbuf_reset(&sob);
+	}
 	strbuf_release(&sob);
 }
Similarly, here, if 'signoff = 0' then the above part goes on. Or
another alternative can be to create a 'append_message_helper()' and
shift the current contents of 'append_message()' into that and use the
above function to work as per the value of signoff.

So a possible flow can be:

void append_message_string_list(...params...) {

	if (signoff) {
		..excecute the necessary segments and call the
		'append_message_helper()' function...
	} else {
		..similarly here..
	}
}

This way we save ourselves some extra functions.
quoted hunk
diff --git a/sequencer.h b/sequencer.h
index f8b2e4ab8527..b24e274f4c62 100644
--- a/sequencer.h
+++ b/sequencer.h
@@ -174,6 +174,10 @@ int todo_list_rearrange_squash(struct todo_list *todo_list);
  * and the new signoff will be spliced into the buffer before those bytes.
  */
 void append_signoff(struct strbuf *msgbuf, size_t ignore_footer, unsigned flag);
+void append_message(struct strbuf *msgbuf, struct strbuf *sob,
+		size_t ignore_footer, unsigned flag);
+void append_message_string_list(struct strbuf *msgbuf, const char*header,
+		struct string_list *sobs, size_t ignore_footer, unsigned flag);
 
 void append_conflicts_hint(struct index_state *istate,
 		struct strbuf *msgbuf, enum commit_msg_cleanup_mode cleanup_mode);
Then, these would have to be corrected too.
quoted hunk
diff --git a/t/t7502-commit-porcelain.sh b/t/t7502-commit-porcelain.sh
index 6396897cc818..40823152a51c 100755
--- a/t/t7502-commit-porcelain.sh
+++ b/t/t7502-commit-porcelain.sh
@@ -154,6 +154,112 @@ test_expect_success 'sign off' '
 
 '
I feel that the tests deserve a commit of their own. A follow-up commit
in this patch.
quoted hunk
+test_expect_success 'helped-by' '
+
+	>file1 &&
+	git add file1 &&
+	git commit --helped-by="foo <bar@frotz>" \
+	--helped-by="foo2 <bar2@frotz>" -m "thank you" &&
+	git cat-file commit HEAD >commit.msg &&
+	sed -ne "s/Helped-by: //p" commit.msg >actual &&
+	cat >expected <<-\EOF &&
+	foo <bar@frotz>
+	foo2 <bar2@frotz>
+	EOF
+	test_cmp expected actual
+
Was this extra line feed intentional? It looks odd here and other test
(scripts) don't have this. Though, this test script seems to have many
tests which have an extra line feed. My suggestion would be to eliminate
it. Maybe this script is old and hasn't been reviewed for a cleanup in
a long time.
quoted hunk
+'
+
+test_expect_success 'reported-by' '
+
+	>file2 &&
+	git add file2 &&
+	git commit --reported-by="foo <bar@frotz>" \
+	--reported-by="foo2 <bar2@frotz>" -m "thank you" &&
+	git cat-file commit HEAD >commit.msg &&
+	sed -ne "s/Reported-by: //p" commit.msg >actual &&
+	cat >expected <<-\EOF &&
+	foo <bar@frotz>
+	foo2 <bar2@frotz>
+	EOF
+	test_cmp expected actual
+
Similarly here and in the parts that follow.

<...>

This seems like a good patch to me but more experienced members will be
able to comment better I think. Good job anyways.

Regards,
Shourya Shukla

Re: [PATCH] [GSOC] commit: provides multiple common signatures

From: ZheNing Hu <hidden>
Date: 2021-03-12 11:41:59

Shourya Shukla [off-list ref] 于2021年3月11日周四 下午11:05写道:
Hey!
Hi!
The idea seems very useful to me though I am not sure what others feel
about this and whether Git is ready for such a thing or not. Keeping
this concern aside, I will still review the patch due to my own interest
in it as well.
haha, thank you! I'm glad that my whims can be recognized by you and help
git itself.
On 11/03 07:16, ZheNing Hu via GitGitGadget wrote:
quoted
From: ZheNing Hu <redacted>

Similar to "Helped-by", "Reported-by", "Reviewed-by", "Mentored-by"
these signatures are often seen in git commit messages. After
I think it will be better to rephrase the line so that it is easier to
understand. It took me a couple of reads to figure out what you meant.

Something like:

Similar to "Signed-off-by", trailers such as "Reported-by", "Helped-by"
and "Mentored-by" are also seen in Git commits.
It's exactly what you said.
My lack of English sometimes limits my expression.
quoted
referring to the simple implementation of `commit --signoff`
and `send-email -cc=" commiter <email>"`, I am considering
whether to provide multiple signature parameters from the
command line. I think this might help maintainers and
developers directly uses the shell to provide these signatures
instead of multiple times repetitive writing those trailers
each time.
The above para is more appropriate for a cover letter than a commit
message. Your thought process for the patch you sent is equally valuable
but this does not belong in the commit message. Commit messages are more
about what you did and any nuances that follow. You get me?

Use 'git format-patch --cover-letter <...>' to create a cover letter for
your patch.
I understand it now, because I use GGG, I will put this content into the
GGG conversation.
quoted
To achieve this goal, i refactored the `append_signoff` design and
provided `append_message` and `append_message_string_list` interfaces,
providing new ways to generate those various signatures.
s/i/I

Also, commit messages are generally written in the imperative tense when
desciribing what you have done in the commit.
You're right.
quoted
Users now can use `commit -H "helper <eamil>"` to generate "Helped-by" trailer,
`commit -R "reviewer <eamil>"` to generate "Reviewed-by" trailer,
`commit -r "reporter <eamil> "`to generate "Reported-by" trailer,
`commit -M "mentor <eamil>"` to generate "Mentored-by" trailer.
Multiple typos.
quoted
Signed-off-by: ZheNing Hu <redacted>
So, an improved commit message could be:

-----8<-----
commit: support multiple trailers from the command line

Historically, Git has supported the 'Signed-off-by' commit trailer
using the '--signoff' and the '-s' option from the command line. Extend
this list to include other commonly used trailers viz., "Helped-by",
"Reviewed-by", "Reported-by" and "Mentored-by". Introduce options '-H',
'-R', '-r' and '-M' corresponding to the aforementioned trailers.

While at it, add tests in the script 't7502-commit-porcelain.sh' and add
information regarding these options in the Documentation of 'git
commit'.
I think this paragraph may not be needed. Junio said in reply to one
of my previous patches that we may not need to add instructions about test
files and documents, because `git log -p --stat` can clearly see that what we
have done in testing and documentation.
----->8-----
quoted
    [GSOC] commit: provides multiple signatures from command line

    I don’t know if my idea will satisfy everyone, I'm also thinking about
    whether we can provide a more generalized version (I think this idea can
    be extended: because users and developers have other signature methods
    that they want, such as "Based-on-patch-by") I hope someone can give me
    pointers (on the correctness of ideas or codes)
It will be great if we let the user add customised options for the
respective trailers but I think that trying to cater to such a large
audience will only complicate the whole process and decrease the support
avialable for the options/command. Apart from this, I think that the
trailer "Reviewed-by" may not be that widely used and it would be better
if we remove it from this patch.
Okay, I will delete it.
quoted
 Documentation/git-commit.txt |  24 +++++++-
 builtin/commit.c             |  63 +++++++++++++++++++++
 sequencer.c                  |  40 +++++++++----
 sequencer.h                  |   4 ++
 t/t7502-commit-porcelain.sh  | 106 +++++++++++++++++++++++++++++++++++
 5 files changed, 226 insertions(+), 11 deletions(-)
diff --git a/Documentation/git-commit.txt b/Documentation/git-commit.txt
index 17150fa7eabe..e1b528d70c1a 100644
--- a/Documentation/git-commit.txt
+++ b/Documentation/git-commit.txt
@@ -14,7 +14,9 @@ SYNOPSIS
         [--allow-empty-message] [--no-verify] [-e] [--author=<author>]
         [--date=<date>] [--cleanup=<mode>] [--[no-]status]
         [-i | -o] [--pathspec-from-file=<file> [--pathspec-file-nul]]
-        [-S[<keyid>]] [--] [<pathspec>...]
+        [-S[<keyid>]] [--] [<pathspec>...] [(-H|--helped-by)=<address>...]
+        [(-R|--reviewed-by)=<address>...] [(-r|--reported-by)=<address>...]
+        [(-M|--mentored)=<address>...]

 DESCRIPTION
 -----------
@@ -166,6 +168,26 @@ The `-m` option is mutually exclusive with `-c`, `-C`, and `-F`.

 include::signoff-option.txt[]

+-H=<address>...::
+--helped-by=<address>...::
+     Add one or more `Helped-by` trailer by the committer at the end of the commit
+     log message.
+
+-R=<address>...::
+--reviewed-by=<address>...::
+     Add one or more `Reviewed-by` trailer by the committer at the end of the commit
+     log message.
+
+-r=<address>...::
+--reported-by=<address>...::
+     Add one or more `Reported-by` trailer by the committer at the end of the commit
+     log message.
+
+-M=<address>...::
+--mentored-by=<address>...::
+     Add one or more `Mentored-by` trailer by the committer at the end of the commit
+     log message.
Oh! I did not think you had added long forms of these options and was
about to comment on that. Good that you added. Do talk about them as
well in the commit message.
Yes, I forgot to mention these long formats in the commit message.
quoted
 -n::
 --no-verify::
      This option bypasses the pre-commit and commit-msg hooks.
diff --git a/builtin/commit.c b/builtin/commit.c
index 739110c5a7f6..4b312af03181 100644
--- a/builtin/commit.c
+++ b/builtin/commit.c
@@ -113,6 +113,10 @@ static int config_commit_verbose = -1; /* unspecified */
 static int no_post_rewrite, allow_empty_message, pathspec_file_nul;
 static char *untracked_files_arg, *force_date, *ignore_submodule_arg, *ignored_arg;
 static char *sign_commit, *pathspec_from_file;
+static struct string_list helped_by = STRING_LIST_INIT_NODUP;
+static struct string_list mentored_by = STRING_LIST_INIT_NODUP;
+static struct string_list reviewed_by = STRING_LIST_INIT_NODUP;
+static struct string_list reported_by = STRING_LIST_INIT_NODUP;
Good that you kept the variables as 'string_list's instead of a usual
'char*'.
quoted
 /*
  * The default commit message cleanup mode will remove the lines
@@ -829,6 +833,20 @@ static int prepare_to_commit(const char *index_file, const char *prefix,
      if (signoff)
              append_signoff(&sb, ignore_non_trailer(sb.buf, sb.len), 0);

+     if(helped_by.items)
+             append_message_string_list(&sb, "Helped-by: ", &helped_by, ignore_non_trailer(sb.buf, sb.len), 0);
+     if(reviewed_by.items)
+             append_message_string_list(&sb, "Reviewed-by: ", &reviewed_by, ignore_non_trailer(sb.buf, sb.len), 0);
+     if(reported_by.items)
+             append_message_string_list(&sb, "Reported-by: ", &reported_by, ignore_non_trailer(sb.buf, sb.len), 0);
+     if(mentored_by.items)
+             append_message_string_list(&sb, "Mentored-by: ", &mentored_by, ignore_non_trailer(sb.buf, sb.len), 0);
I think this part will look prettier if you wrap around the text. For
code segments, the wrap around limit is 80 chars. For commit messages
its 72 chars. Wrap around means that as soon as you hit N number of
characters, you proceed to the next(new) line.
Fine.
quoted
+     string_list_clear(&helped_by, 0);
+     string_list_clear(&reviewed_by, 0);
+     string_list_clear(&reported_by, 0);
+     string_list_clear(&mentored_by, 0);
+
      if (fwrite(sb.buf, 1, sb.len, s->fp) < sb.len)
              die_errno(_("could not write commit template"));
@@ -1490,6 +1508,42 @@ static int git_commit_config(const char *k, const char *v, void *cb)
      return git_status_config(k, v, s);
 }

+static int help_callback(const struct option *opt, const char *arg, int unset)
+{
+     if (unset)
+             string_list_clear(&helped_by, 0);
+     else
+             string_list_append(&helped_by, arg);
+     return 0;
+}
+
+static int review_callback(const struct option *opt, const char *arg, int unset)
+{
+     if (unset)
+             string_list_clear(&reviewed_by, 0);
+     else
+             string_list_append(&reviewed_by, arg);
+     return 0;
+}
+
+static int report_callback(const struct option *opt, const char *arg, int unset)
+{
+     if (unset)
+             string_list_clear(&reported_by, 0);
+     else
+             string_list_append(&reported_by, arg);
+     return 0;
+}
+
+static int mentor_callback(const struct option *opt, const char *arg, int unset)
+{
+     if (unset)
+             string_list_clear(&mentored_by, 0);
+     else
+             string_list_append(&mentored_by, arg);
+     return 0;
+}
 int cmd_commit(int argc, const char **argv, const char *prefix)
 {
      static struct wt_status s;
@@ -1507,6 +1561,10 @@ int cmd_commit(int argc, const char **argv, const char *prefix)
              OPT_STRING(0, "fixup", &fixup_message, N_("commit"), N_("use autosquash formatted message to fixup specified commit")),
              OPT_STRING(0, "squash", &squash_message, N_("commit"), N_("use autosquash formatted message to squash specified commit")),
              OPT_BOOL(0, "reset-author", &renew_authorship, N_("the commit is authored by me now (used with -C/-c/--amend)")),
+             OPT_CALLBACK('H', "helped-by", NULL, N_("email"), N_("add a Helped-by trailer"), help_callback),
+             OPT_CALLBACK('r', "reported-by", NULL, N_("email"), N_("add a Reported-by trailer"), report_callback),
+             OPT_CALLBACK('R', "reviewed-by", NULL, N_("email"), N_("add a Reviewed-by trailer"), review_callback),
+             OPT_CALLBACK('M', "mentored-by", NULL, N_("email"), N_("add a Mentored-by trailer"), mentor_callback),
It would be lovely if you could line wrap these above additions too!
quoted
              OPT_BOOL('s', "signoff", &signoff, N_("add a Signed-off-by trailer")),
              OPT_FILENAME('t', "template", &template_file, N_("use specified template file")),
              OPT_BOOL('e', "edit", &edit_flag, N_("force edit of commit")),
@@ -1561,6 +1619,11 @@ int cmd_commit(int argc, const char **argv, const char *prefix)
      struct commit_extra_header *extra = NULL;
      struct strbuf err = STRBUF_INIT;

+     helped_by.strdup_strings = 1;
+     reviewed_by.strdup_strings = 1;
+     reported_by.strdup_strings = 1;
+     mentored_by.strdup_strings = 1;
+
      if (argc == 2 && !strcmp(argv[1], "-h"))
              usage_with_options(builtin_commit_usage, builtin_commit_options);
diff --git a/sequencer.c b/sequencer.c
index d2332d3e1787..528daf9df252 100644
--- a/sequencer.c
+++ b/sequencer.c
@@ -4668,16 +4668,12 @@ int sequencer_pick_revisions(struct repository *r,
      return res;
 }

-void append_signoff(struct strbuf *msgbuf, size_t ignore_footer, unsigned flag)
+void append_message(struct strbuf *msgbuf, struct strbuf *sob,
+                     size_t ignore_footer, unsigned flag)
Its nice to see that you generalised the pre-exisiting function instead
of creating a new one(s) for the new trailers. Good.

It will be better to name 'struct strbuf *sob' to something more
generic, maybe 'struct strbuf *trailer' since 'sob' stands for
'Signed-off-by' and since the function is becoming more generic, its
contents should too.
When I wrote it, I didn’t realize that sob refers to the abbreviation of
'Signed-off-by', I will change it.
quoted
 {
      unsigned no_dup_sob = flag & APPEND_SIGNOFF_DEDUP;
-     struct strbuf sob = STRBUF_INIT;
      int has_footer;

-     strbuf_addstr(&sob, sign_off_header);
-     strbuf_addstr(&sob, fmt_name(WANT_COMMITTER_IDENT));
-     strbuf_addch(&sob, '\n');
-
      if (!ignore_footer)
              strbuf_complete_line(msgbuf);
@@ -4685,11 +4681,11 @@ void append_signoff(struct strbuf *msgbuf, size_t ignore_footer, unsigned flag)
       * If the whole message buffer is equal to the sob, pretend that we
       * found a conforming footer with a matching sob
       */
-     if (msgbuf->len - ignore_footer == sob.len &&
-         !strncmp(msgbuf->buf, sob.buf, sob.len))
+     if (msgbuf->len - ignore_footer == sob->len &&
+         !strncmp(msgbuf->buf, sob->buf, sob->len))
              has_footer = 3;
      else
-             has_footer = has_conforming_footer(msgbuf, &sob, ignore_footer);
+             has_footer = has_conforming_footer(msgbuf, sob, ignore_footer);
Again, rename the variables.
quoted
      if (!has_footer) {
              const char *append_newlines = NULL;
@@ -4723,8 +4719,32 @@ void append_signoff(struct strbuf *msgbuf, size_t ignore_footer, unsigned flag)

      if (has_footer != 3 && (!no_dup_sob || has_footer != 2))
              strbuf_splice(msgbuf, msgbuf->len - ignore_footer, 0,
-                             sob.buf, sob.len);
+                             sob->buf, sob->len);
+}
+void append_signoff(struct strbuf *msgbuf, size_t ignore_footer, unsigned flag)
+{
+     struct strbuf sob = STRBUF_INIT;
+     strbuf_addstr(&sob, sign_off_header);
+     strbuf_addstr(&sob, fmt_name(WANT_COMMITTER_IDENT));
+     strbuf_addch(&sob, '\n');
+     append_message(msgbuf, &sob, ignore_footer, flag);
+     strbuf_release(&sob);
+}
I think it will be nice if you could use a flag to denote whether the
entity to be appended is a 'sign-off' or not. This way when say the
variable 'int signoff' is 1, then use the above part in the
'append_message()' function otherwise go with the flow that exists.
quoted
+void append_message_string_list(struct strbuf *msgbuf, const char *header,
+             struct string_list *sobs, size_t ignore_footer, unsigned flag) {
+     int i;
+     struct strbuf sob = STRBUF_INIT;
+
+     for ( i = 0; i < sobs->nr; i++)
+     {
+             strbuf_addstr(&sob, header);
+             strbuf_addstr(&sob, sobs->items[i].string);
+             strbuf_addch(&sob, '\n');
+             append_message(msgbuf, &sob, ignore_footer, flag);
+             strbuf_reset(&sob);
+     }
      strbuf_release(&sob);
 }
Similarly, here, if 'signoff = 0' then the above part goes on. Or
another alternative can be to create a 'append_message_helper()' and
shift the current contents of 'append_message()' into that and use the
above function to work as per the value of signoff.

So a possible flow can be:

void append_message_string_list(...params...) {

        if (signoff) {
                ..excecute the necessary segments and call the
                'append_message_helper()' function...
        } else {
                ..similarly here..
        }
}

This way we save ourselves some extra functions.
May some thing like this:

if (signoff)
        append_message_string_list(&sb, "Signed-off-by: ", NULL,
                   ignore_non_trailer(sb.buf, sb.len), 0);

And then we could judge in `append_message_string_list`
if the arguement `string_list *trailers` set to NULL. If so,
we do something similar to `append_signoff`, otherwise,
we carry out other situations.

So that we can delete the `append_signoff` and user can only
need to call  `append_message_string_list` in any where, I don't
 know if it is better.
quoted
diff --git a/sequencer.h b/sequencer.h
index f8b2e4ab8527..b24e274f4c62 100644
--- a/sequencer.h
+++ b/sequencer.h
@@ -174,6 +174,10 @@ int todo_list_rearrange_squash(struct todo_list *todo_list);
  * and the new signoff will be spliced into the buffer before those bytes.
  */
 void append_signoff(struct strbuf *msgbuf, size_t ignore_footer, unsigned flag);
+void append_message(struct strbuf *msgbuf, struct strbuf *sob,
+             size_t ignore_footer, unsigned flag);
+void append_message_string_list(struct strbuf *msgbuf, const char*header,
+             struct string_list *sobs, size_t ignore_footer, unsigned flag);

 void append_conflicts_hint(struct index_state *istate,
              struct strbuf *msgbuf, enum commit_msg_cleanup_mode cleanup_mode);
Then, these would have to be corrected too.
quoted
diff --git a/t/t7502-commit-porcelain.sh b/t/t7502-commit-porcelain.sh
index 6396897cc818..40823152a51c 100755
--- a/t/t7502-commit-porcelain.sh
+++ b/t/t7502-commit-porcelain.sh
@@ -154,6 +154,112 @@ test_expect_success 'sign off' '

 '
I feel that the tests deserve a commit of their own. A follow-up commit
in this patch.
quoted
+test_expect_success 'helped-by' '
+
+     >file1 &&
+     git add file1 &&
+     git commit --helped-by="foo <bar@frotz>" \
+     --helped-by="foo2 <bar2@frotz>" -m "thank you" &&
+     git cat-file commit HEAD >commit.msg &&
+     sed -ne "s/Helped-by: //p" commit.msg >actual &&
+     cat >expected <<-\EOF &&
+     foo <bar@frotz>
+     foo2 <bar2@frotz>
+     EOF
+     test_cmp expected actual
+
Was this extra line feed intentional? It looks odd here and other test
(scripts) don't have this. Though, this test script seems to have many
tests which have an extra line feed. My suggestion would be to eliminate
it. Maybe this script is old and hasn't been reviewed for a cleanup in
a long time.
I might want to keep the same format with the last "sign off" test. Now it
seems that this is not necessary.
quoted
+'
+
+test_expect_success 'reported-by' '
+
+     >file2 &&
+     git add file2 &&
+     git commit --reported-by="foo <bar@frotz>" \
+     --reported-by="foo2 <bar2@frotz>" -m "thank you" &&
+     git cat-file commit HEAD >commit.msg &&
+     sed -ne "s/Reported-by: //p" commit.msg >actual &&
+     cat >expected <<-\EOF &&
+     foo <bar@frotz>
+     foo2 <bar2@frotz>
+     EOF
+     test_cmp expected actual
+
Similarly here and in the parts that follow.

<...>

This seems like a good patch to me but more experienced members will be
able to comment better I think. Good job anyways.
Your comments and encouragement are of great help to me :)
Regards,
Shourya Shukla
Regards,
ZheNing Hu

[PATCH v2] [GSOC] commit: add trailer command

From: ZheNing Hu via GitGitGadget <hidden>
Date: 2021-03-12 15:55:14

From: ZheNing Hu <redacted>

Historically, Git has supported the 'Signed-off-by' commit trailer
using the '--signoff' and the '-s' option from the command line.
But users may need to provide richer trailer information from the
command line such as "Helped-by", "Reported-by", "Mentored-by",

Now use `--trailer <token>[(=|:)<value>]` pass the trailers to
`interpret-trailers` and generate trailers in commit messages.

Signed-off-by: ZheNing Hu <redacted>
---
    [GSOC] commit: provides multiple signatures from command line
    
    Now maintainers or developers can also use commit
    --trailer="Signed-off-by:commiter<email>" from the command line to
    provide trailers to commit messages. This solution may be more
    generalized than v1.

Published-As: https://github.com/gitgitgadget/git/releases/tag/pr-901%2Fadlternative%2Fcommit-with-multiple-signatures-v2
Fetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-901/adlternative/commit-with-multiple-signatures-v2
Pull-Request: https://github.com/gitgitgadget/git/pull/901

Range-diff vs v1:

 1:  e9389e72ac65 ! 1:  4c507d17db4f [GSOC] commit: provides multiple common signatures
     @@ Metadata
      Author: ZheNing Hu [off-list ref]
      
       ## Commit message ##
     -    [GSOC] commit: provides multiple common signatures
     +    [GSOC] commit: add trailer command
      
     -    Similar to "Helped-by", "Reported-by", "Reviewed-by", "Mentored-by"
     -    these signatures are often seen in git commit messages. After
     -    referring to the simple implementation of `commit --signoff`
     -    and `send-email -cc=" commiter <email>"`, I am considering
     -    whether to provide multiple signature parameters from the
     -    command line. I think this might help maintainers and
     -    developers directly uses the shell to provide these signatures
     -    instead of multiple times repetitive writing those trailers
     -    each time.
     +    Historically, Git has supported the 'Signed-off-by' commit trailer
     +    using the '--signoff' and the '-s' option from the command line.
     +    But users may need to provide richer trailer information from the
     +    command line such as "Helped-by", "Reported-by", "Mentored-by",
      
     -    To achieve this goal, i refactored the `append_signoff` design and
     -    provided `append_message` and `append_message_string_list` interfaces,
     -    providing new ways to generate those various signatures.
     -
     -    Users now can use `commit -H "helper <eamil>"` to generate "Helped-by" trailer,
     -    `commit -R "reviewer <eamil>"` to generate "Reviewed-by" trailer,
     -    `commit -r "reporter <eamil> "`to generate "Reported-by" trailer,
     -    `commit -M "mentor <eamil>"` to generate "Mentored-by" trailer.
     +    Now use `--trailer <token>[(=|:)<value>]` pass the trailers to
     +    `interpret-trailers` and generate trailers in commit messages.
      
          Signed-off-by: ZheNing Hu [off-list ref]
      
     @@ Documentation/git-commit.txt: SYNOPSIS
       	   [--date=<date>] [--cleanup=<mode>] [--[no-]status]
       	   [-i | -o] [--pathspec-from-file=<file> [--pathspec-file-nul]]
      -	   [-S[<keyid>]] [--] [<pathspec>...]
     -+	   [-S[<keyid>]] [--] [<pathspec>...] [(-H|--helped-by)=<address>...]
     -+	   [(-R|--reviewed-by)=<address>...] [(-r|--reported-by)=<address>...]
     -+	   [(-M|--mentored)=<address>...]
     ++	   [-S[<keyid>]] [--] [<pathspec>...] [(--trailer <token>[(=|:)<value>])...]
       
       DESCRIPTION
       -----------
     @@ Documentation/git-commit.txt: The `-m` option is mutually exclusive with `-c`, `
       
       include::signoff-option.txt[]
       
     -+-H=<address>...::
     -+--helped-by=<address>...::
     -+	Add one or more `Helped-by` trailer by the committer at the end of the commit
     -+	log message.
     -+
     -+-R=<address>...::
     -+--reviewed-by=<address>...::
     -+	Add one or more `Reviewed-by` trailer by the committer at the end of the commit
     -+	log message.
     -+
     -+-r=<address>...::
     -+--reported-by=<address>...::
     -+	Add one or more `Reported-by` trailer by the committer at the end of the commit
     -+	log message.
     -+
     -+-M=<address>...::
     -+--mentored-by=<address>...::
     -+	Add one or more `Mentored-by` trailer by the committer at the end of the commit
     -+	log message.
     ++--trailer <token>[(=|:)<value>]::
     ++	Specify a (<token>, <value>) pair that should be applied as a
     ++	trailer. (e.g.  `git commit --trailer "Signed-off-by:C O Mitter [off-list ref]" \
     ++	--trailer "Helped-by:C O Mitter [off-list ref]"`will add the "Signed-off" trailer
     ++	and the "Helped-by" trailer in the commit message.)
      +
       -n::
       --no-verify::
     @@ builtin/commit.c: static int config_commit_verbose = -1; /* unspecified */
       static int no_post_rewrite, allow_empty_message, pathspec_file_nul;
       static char *untracked_files_arg, *force_date, *ignore_submodule_arg, *ignored_arg;
       static char *sign_commit, *pathspec_from_file;
     -+static struct string_list helped_by = STRING_LIST_INIT_NODUP;
     -+static struct string_list mentored_by = STRING_LIST_INIT_NODUP;
     -+static struct string_list reviewed_by = STRING_LIST_INIT_NODUP;
     -+static struct string_list reported_by = STRING_LIST_INIT_NODUP;
     ++struct child_process run_trailer = CHILD_PROCESS_INIT;
     ++static const char *trailer;
       
       /*
        * The default commit message cleanup mode will remove the lines
     -@@ builtin/commit.c: static int prepare_to_commit(const char *index_file, const char *prefix,
     - 	if (signoff)
     - 		append_signoff(&sb, ignore_non_trailer(sb.buf, sb.len), 0);
     - 
     -+	if(helped_by.items)
     -+		append_message_string_list(&sb, "Helped-by: ", &helped_by, ignore_non_trailer(sb.buf, sb.len), 0);
     -+	if(reviewed_by.items)
     -+		append_message_string_list(&sb, "Reviewed-by: ", &reviewed_by, ignore_non_trailer(sb.buf, sb.len), 0);
     -+	if(reported_by.items)
     -+		append_message_string_list(&sb, "Reported-by: ", &reported_by, ignore_non_trailer(sb.buf, sb.len), 0);
     -+	if(mentored_by.items)
     -+		append_message_string_list(&sb, "Mentored-by: ", &mentored_by, ignore_non_trailer(sb.buf, sb.len), 0);
     -+
     -+	string_list_clear(&helped_by, 0);
     -+	string_list_clear(&reviewed_by, 0);
     -+	string_list_clear(&reported_by, 0);
     -+	string_list_clear(&mentored_by, 0);
     -+
     - 	if (fwrite(sb.buf, 1, sb.len, s->fp) < sb.len)
     - 		die_errno(_("could not write commit template"));
     +@@ builtin/commit.c: static struct strbuf message = STRBUF_INIT;
       
     -@@ builtin/commit.c: static int git_commit_config(const char *k, const char *v, void *cb)
     - 	return git_status_config(k, v, s);
     - }
     + static enum wt_status_format status_format = STATUS_FORMAT_UNSPECIFIED;
       
     -+static int help_callback(const struct option *opt, const char *arg, int unset)
     -+{
     -+	if (unset)
     -+		string_list_clear(&helped_by, 0);
     -+	else
     -+		string_list_append(&helped_by, arg);
     -+	return 0;
     -+}
     -+
     -+static int review_callback(const struct option *opt, const char *arg, int unset)
     ++static int opt_pass_trailer(const struct option *opt, const char *arg, int unset)
      +{
     -+	if (unset)
     -+		string_list_clear(&reviewed_by, 0);
     -+	else
     -+		string_list_append(&reviewed_by, arg);
     -+	return 0;
     -+}
     -+
     -+static int report_callback(const struct option *opt, const char *arg, int unset)
     -+{
     -+	if (unset)
     -+		string_list_clear(&reported_by, 0);
     -+	else
     -+		string_list_append(&reported_by, arg);
     -+	return 0;
     -+}
     -+
     -+static int mentor_callback(const struct option *opt, const char *arg, int unset)
     -+{
     -+	if (unset)
     -+		string_list_clear(&mentored_by, 0);
     -+	else
     -+		string_list_append(&mentored_by, arg);
     ++	if (unset) {
     ++		strvec_clear(&run_trailer.args);
     ++		return -1;
     ++	}
     ++	run_trailer.git_cmd = 1;
     ++	strvec_pushl(&run_trailer.args, "--trailer", arg, NULL);
      +	return 0;
      +}
      +
     - int cmd_commit(int argc, const char **argv, const char *prefix)
     + static int opt_parse_porcelain(const struct option *opt, const char *arg, int unset)
       {
     - 	static struct wt_status s;
     + 	enum wt_status_format *value = (enum wt_status_format *)opt->value;
     +@@ builtin/commit.c: static int prepare_to_commit(const char *index_file, const char *prefix,
     + 
     + 	fclose(s->fp);
     + 
     ++	run_command(&run_trailer);
     ++
     + 	/*
     + 	 * Reject an attempt to record a non-merge empty commit without
     + 	 * explicit --allow-empty. In the cherry-pick case, it may be
      @@ builtin/commit.c: int cmd_commit(int argc, const char **argv, const char *prefix)
       		OPT_STRING(0, "fixup", &fixup_message, N_("commit"), N_("use autosquash formatted message to fixup specified commit")),
       		OPT_STRING(0, "squash", &squash_message, N_("commit"), N_("use autosquash formatted message to squash specified commit")),
       		OPT_BOOL(0, "reset-author", &renew_authorship, N_("the commit is authored by me now (used with -C/-c/--amend)")),
     -+		OPT_CALLBACK('H', "helped-by", NULL, N_("email"), N_("add a Helped-by trailer"), help_callback),
     -+		OPT_CALLBACK('r', "reported-by", NULL, N_("email"), N_("add a Reported-by trailer"), report_callback),
     -+		OPT_CALLBACK('R', "reviewed-by", NULL, N_("email"), N_("add a Reviewed-by trailer"), review_callback),
     -+		OPT_CALLBACK('M', "mentored-by", NULL, N_("email"), N_("add a Mentored-by trailer"), mentor_callback),
     ++		OPT_CALLBACK(0, "trailer", &trailer, N_("trailer"), N_("trailer(s) to add"), opt_pass_trailer),
       		OPT_BOOL('s', "signoff", &signoff, N_("add a Signed-off-by trailer")),
       		OPT_FILENAME('t', "template", &template_file, N_("use specified template file")),
       		OPT_BOOL('e', "edit", &edit_flag, N_("force edit of commit")),
      @@ builtin/commit.c: int cmd_commit(int argc, const char **argv, const char *prefix)
     - 	struct commit_extra_header *extra = NULL;
     - 	struct strbuf err = STRBUF_INIT;
     - 
     -+	helped_by.strdup_strings = 1;
     -+	reviewed_by.strdup_strings = 1;
     -+	reported_by.strdup_strings = 1;
     -+	mentored_by.strdup_strings = 1;
     -+
     - 	if (argc == 2 && !strcmp(argv[1], "-h"))
     - 		usage_with_options(builtin_commit_usage, builtin_commit_options);
     - 
     -
     - ## sequencer.c ##
     -@@ sequencer.c: int sequencer_pick_revisions(struct repository *r,
     - 	return res;
     - }
     - 
     --void append_signoff(struct strbuf *msgbuf, size_t ignore_footer, unsigned flag)
     -+void append_message(struct strbuf *msgbuf, struct strbuf *sob,
     -+			size_t ignore_footer, unsigned flag)
     - {
     - 	unsigned no_dup_sob = flag & APPEND_SIGNOFF_DEDUP;
     --	struct strbuf sob = STRBUF_INIT;
     - 	int has_footer;
     - 
     --	strbuf_addstr(&sob, sign_off_header);
     --	strbuf_addstr(&sob, fmt_name(WANT_COMMITTER_IDENT));
     --	strbuf_addch(&sob, '\n');
     --
     - 	if (!ignore_footer)
     - 		strbuf_complete_line(msgbuf);
     - 
     -@@ sequencer.c: void append_signoff(struct strbuf *msgbuf, size_t ignore_footer, unsigned flag)
     - 	 * If the whole message buffer is equal to the sob, pretend that we
     - 	 * found a conforming footer with a matching sob
     - 	 */
     --	if (msgbuf->len - ignore_footer == sob.len &&
     --	    !strncmp(msgbuf->buf, sob.buf, sob.len))
     -+	if (msgbuf->len - ignore_footer == sob->len &&
     -+	    !strncmp(msgbuf->buf, sob->buf, sob->len))
     - 		has_footer = 3;
     - 	else
     --		has_footer = has_conforming_footer(msgbuf, &sob, ignore_footer);
     -+		has_footer = has_conforming_footer(msgbuf, sob, ignore_footer);
     - 
     - 	if (!has_footer) {
     - 		const char *append_newlines = NULL;
     -@@ sequencer.c: void append_signoff(struct strbuf *msgbuf, size_t ignore_footer, unsigned flag)
     - 
     - 	if (has_footer != 3 && (!no_dup_sob || has_footer != 2))
     - 		strbuf_splice(msgbuf, msgbuf->len - ignore_footer, 0,
     --				sob.buf, sob.len);
     -+				sob->buf, sob->len);
     -+}
     -+
     -+void append_signoff(struct strbuf *msgbuf, size_t ignore_footer, unsigned flag)
     -+{
     -+	struct strbuf sob = STRBUF_INIT;
     -+	strbuf_addstr(&sob, sign_off_header);
     -+	strbuf_addstr(&sob, fmt_name(WANT_COMMITTER_IDENT));
     -+	strbuf_addch(&sob, '\n');
     -+	append_message(msgbuf, &sob, ignore_footer, flag);
     -+	strbuf_release(&sob);
     -+}
     - 
     -+void append_message_string_list(struct strbuf *msgbuf, const char *header,
     -+		struct string_list *sobs, size_t ignore_footer, unsigned flag) {
     -+	int i;
     -+	struct strbuf sob = STRBUF_INIT;
     -+
     -+	for ( i = 0; i < sobs->nr; i++)
     -+	{
     -+		strbuf_addstr(&sob, header);
     -+		strbuf_addstr(&sob, sobs->items[i].string);
     -+		strbuf_addch(&sob, '\n');
     -+		append_message(msgbuf, &sob, ignore_footer, flag);
     -+		strbuf_reset(&sob);
     -+	}
     - 	strbuf_release(&sob);
     - }
     - 
     -
     - ## sequencer.h ##
     -@@ sequencer.h: int todo_list_rearrange_squash(struct todo_list *todo_list);
     -  * and the new signoff will be spliced into the buffer before those bytes.
     -  */
     - void append_signoff(struct strbuf *msgbuf, size_t ignore_footer, unsigned flag);
     -+void append_message(struct strbuf *msgbuf, struct strbuf *sob,
     -+		size_t ignore_footer, unsigned flag);
     -+void append_message_string_list(struct strbuf *msgbuf, const char*header,
     -+		struct string_list *sobs, size_t ignore_footer, unsigned flag);
     - 
     - void append_conflicts_hint(struct index_state *istate,
     - 		struct strbuf *msgbuf, enum commit_msg_cleanup_mode cleanup_mode);
     + 			die(_("could not parse HEAD commit"));
     + 	}
     + 	verbose = -1; /* unspecified */
     ++	strvec_pushl(&run_trailer.args, "interpret-trailers",
     ++		"--in-place", "--where=end", git_path_commit_editmsg(), NULL);
     + 	argc = parse_and_validate_options(argc, argv, builtin_commit_options,
     + 					  builtin_commit_usage,
     + 					  prefix, current_head, &s);
      
       ## t/t7502-commit-porcelain.sh ##
      @@ t/t7502-commit-porcelain.sh: test_expect_success 'sign off' '
       
       '
       
     -+test_expect_success 'helped-by' '
     -+
     ++test_expect_success 'trailer' '
      +	>file1 &&
      +	git add file1 &&
     -+	git commit --helped-by="foo <bar@frotz>" \
     -+	--helped-by="foo2 <bar2@frotz>" -m "thank you" &&
     -+	git cat-file commit HEAD >commit.msg &&
     -+	sed -ne "s/Helped-by: //p" commit.msg >actual &&
     -+	cat >expected <<-\EOF &&
     -+	foo <bar@frotz>
     -+	foo2 <bar2@frotz>
     -+	EOF
     -+	test_cmp expected actual
     -+
     -+'
     -+
     -+test_expect_success 'reported-by' '
     -+
     -+	>file2 &&
     -+	git add file2 &&
     -+	git commit --reported-by="foo <bar@frotz>" \
     -+	--reported-by="foo2 <bar2@frotz>" -m "thank you" &&
     -+	git cat-file commit HEAD >commit.msg &&
     -+	sed -ne "s/Reported-by: //p" commit.msg >actual &&
     -+	cat >expected <<-\EOF &&
     -+	foo <bar@frotz>
     -+	foo2 <bar2@frotz>
     -+	EOF
     -+	test_cmp expected actual
     -+
     -+'
     -+
     -+test_expect_success 'reviewed-by' '
     -+
     -+	>file3 &&
     -+	git add file3 &&
     -+	git commit --reviewed-by="foo <bar@frotz>" \
     -+	--reviewed-by="foo2 <bar2@frotz>" -m "thank you" &&
     -+	git cat-file commit HEAD >commit.msg &&
     -+	sed -ne "s/Reviewed-by: //p" commit.msg >actual &&
     -+	cat >expected <<-\EOF &&
     -+	foo <bar@frotz>
     -+	foo2 <bar2@frotz>
     -+	EOF
     -+	test_cmp expected actual
     -+
     -+'
     -+
     -+test_expect_success 'mentored-by' '
     -+
     -+	>file4 &&
     -+	git add file4 &&
     -+	git commit --mentored-by="foo <bar@frotz>" \
     -+	--mentored-by="foo2 <bar2@frotz>" -m "thank you" &&
     -+	git cat-file commit HEAD >commit.msg &&
     -+	sed -ne "s/Mentored-by: //p" commit.msg >actual &&
     -+	cat >expected <<-\EOF &&
     -+	foo <bar@frotz>
     -+	foo2 <bar2@frotz>
     -+	EOF
     -+	test_cmp expected actual
     -+
     -+'
     -+
     -+test_expect_success 'multiple signatures' '
     -+
     -+	>file5 &&
     -+	git add file5 &&
     -+	git commit --helped-by="foo <bar@frotz>" \
     -+	--reviewed-by="foo2 <bar2@frotz>" \
     -+	--mentored-by="foo3 <bar3@frotz>" \
     -+	--reported-by="foo4 <bar4@frotz>" -s -m "thank you" &&
     ++	git commit -s --trailer "Signed-off-by:C O Mitter1 [off-list ref]" \
     ++	--trailer "Helped-by:C O Mitter2 [off-list ref]"  \
     ++	--trailer "Reported-by:C O Mitter3 [off-list ref]" \
     ++	--trailer "Mentored-by:C O Mitter4 [off-list ref]" \
     ++	-m "hello" &&
      +	git cat-file commit HEAD >commit.msg &&
      +	sed -e "1,7d" commit.msg >actual &&
      +	cat >expected <<-\EOF &&
      +	Signed-off-by: C O Mitter [off-list ref]
     -+	Helped-by: foo <bar@frotz>
     -+	Reviewed-by: foo2 <bar2@frotz>
     -+	Reported-by: foo4 <bar4@frotz>
     -+	Mentored-by: foo3 <bar3@frotz>
     ++	Signed-off-by: C O Mitter1 [off-list ref]
     ++	Helped-by: C O Mitter2 [off-list ref]
     ++	Reported-by: C O Mitter3 [off-list ref]
     ++	Mentored-by: C O Mitter4 [off-list ref]
      +	EOF
      +	test_cmp expected actual
     -+
     -+'
     -+
     -+test_expect_success 'multiple signatures (use abbreviations)' '
     -+
     -+	>file6 &&
     -+	git add file6 &&
     -+	git commit -H "foo <bar@frotz>" \
     -+	-R "foo2 <bar2@frotz>" \
     -+	-M "foo3 <bar3@frotz>" \
     -+	-r "foo4 <bar4@frotz>" -s -m "thank you" &&
     -+	git cat-file commit HEAD >commit.msg &&
     -+	sed -e "1,7d" commit.msg >actual &&
     -+	cat >expected <<-\EOF &&
     -+	Signed-off-by: C O Mitter [off-list ref]
     -+	Helped-by: foo <bar@frotz>
     -+	Reviewed-by: foo2 <bar2@frotz>
     -+	Reported-by: foo4 <bar4@frotz>
     -+	Mentored-by: foo3 <bar3@frotz>
     -+	EOF
     -+	test_cmp expected actual
     -+
      +'
      +
       test_expect_success 'multiple -m' '


 Documentation/git-commit.txt |  8 +++++++-
 builtin/commit.c             | 18 ++++++++++++++++++
 t/t7502-commit-porcelain.sh  | 20 ++++++++++++++++++++
 3 files changed, 45 insertions(+), 1 deletion(-)
diff --git a/Documentation/git-commit.txt b/Documentation/git-commit.txt
index 17150fa7eabe..764513a3f287 100644
--- a/Documentation/git-commit.txt
+++ b/Documentation/git-commit.txt
@@ -14,7 +14,7 @@ SYNOPSIS
 	   [--allow-empty-message] [--no-verify] [-e] [--author=<author>]
 	   [--date=<date>] [--cleanup=<mode>] [--[no-]status]
 	   [-i | -o] [--pathspec-from-file=<file> [--pathspec-file-nul]]
-	   [-S[<keyid>]] [--] [<pathspec>...]
+	   [-S[<keyid>]] [--] [<pathspec>...] [(--trailer <token>[(=|:)<value>])...]
 
 DESCRIPTION
 -----------
@@ -166,6 +166,12 @@ The `-m` option is mutually exclusive with `-c`, `-C`, and `-F`.
 
 include::signoff-option.txt[]
 
+--trailer <token>[(=|:)<value>]::
+	Specify a (<token>, <value>) pair that should be applied as a
+	trailer. (e.g.  `git commit --trailer "Signed-off-by:C O Mitter <committer@example.com>" \
+	--trailer "Helped-by:C O Mitter <committer@example.com>"`will add the "Signed-off" trailer
+	and the "Helped-by" trailer in the commit message.)
+
 -n::
 --no-verify::
 	This option bypasses the pre-commit and commit-msg hooks.
diff --git a/builtin/commit.c b/builtin/commit.c
index 739110c5a7f6..abbd136b27f0 100644
--- a/builtin/commit.c
+++ b/builtin/commit.c
@@ -113,6 +113,8 @@ static int config_commit_verbose = -1; /* unspecified */
 static int no_post_rewrite, allow_empty_message, pathspec_file_nul;
 static char *untracked_files_arg, *force_date, *ignore_submodule_arg, *ignored_arg;
 static char *sign_commit, *pathspec_from_file;
+struct child_process run_trailer = CHILD_PROCESS_INIT;
+static const char *trailer;
 
 /*
  * The default commit message cleanup mode will remove the lines
@@ -131,6 +133,17 @@ static struct strbuf message = STRBUF_INIT;
 
 static enum wt_status_format status_format = STATUS_FORMAT_UNSPECIFIED;
 
+static int opt_pass_trailer(const struct option *opt, const char *arg, int unset)
+{
+	if (unset) {
+		strvec_clear(&run_trailer.args);
+		return -1;
+	}
+	run_trailer.git_cmd = 1;
+	strvec_pushl(&run_trailer.args, "--trailer", arg, NULL);
+	return 0;
+}
+
 static int opt_parse_porcelain(const struct option *opt, const char *arg, int unset)
 {
 	enum wt_status_format *value = (enum wt_status_format *)opt->value;
@@ -958,6 +971,8 @@ static int prepare_to_commit(const char *index_file, const char *prefix,
 
 	fclose(s->fp);
 
+	run_command(&run_trailer);
+
 	/*
 	 * Reject an attempt to record a non-merge empty commit without
 	 * explicit --allow-empty. In the cherry-pick case, it may be
@@ -1507,6 +1522,7 @@ int cmd_commit(int argc, const char **argv, const char *prefix)
 		OPT_STRING(0, "fixup", &fixup_message, N_("commit"), N_("use autosquash formatted message to fixup specified commit")),
 		OPT_STRING(0, "squash", &squash_message, N_("commit"), N_("use autosquash formatted message to squash specified commit")),
 		OPT_BOOL(0, "reset-author", &renew_authorship, N_("the commit is authored by me now (used with -C/-c/--amend)")),
+		OPT_CALLBACK(0, "trailer", &trailer, N_("trailer"), N_("trailer(s) to add"), opt_pass_trailer),
 		OPT_BOOL('s', "signoff", &signoff, N_("add a Signed-off-by trailer")),
 		OPT_FILENAME('t', "template", &template_file, N_("use specified template file")),
 		OPT_BOOL('e', "edit", &edit_flag, N_("force edit of commit")),
@@ -1577,6 +1593,8 @@ int cmd_commit(int argc, const char **argv, const char *prefix)
 			die(_("could not parse HEAD commit"));
 	}
 	verbose = -1; /* unspecified */
+	strvec_pushl(&run_trailer.args, "interpret-trailers",
+		"--in-place", "--where=end", git_path_commit_editmsg(), NULL);
 	argc = parse_and_validate_options(argc, argv, builtin_commit_options,
 					  builtin_commit_usage,
 					  prefix, current_head, &s);
diff --git a/t/t7502-commit-porcelain.sh b/t/t7502-commit-porcelain.sh
index 6396897cc818..4b9ac4587d17 100755
--- a/t/t7502-commit-porcelain.sh
+++ b/t/t7502-commit-porcelain.sh
@@ -154,6 +154,26 @@ test_expect_success 'sign off' '
 
 '
 
+test_expect_success 'trailer' '
+	>file1 &&
+	git add file1 &&
+	git commit -s --trailer "Signed-off-by:C O Mitter1 <committer1@example.com>" \
+	--trailer "Helped-by:C O Mitter2 <committer2@example.com>"  \
+	--trailer "Reported-by:C O Mitter3 <committer3@example.com>" \
+	--trailer "Mentored-by:C O Mitter4 <committer4@example.com>" \
+	-m "hello" &&
+	git cat-file commit HEAD >commit.msg &&
+	sed -e "1,7d" commit.msg >actual &&
+	cat >expected <<-\EOF &&
+	Signed-off-by: C O Mitter <committer@example.com>
+	Signed-off-by: C O Mitter1 <committer1@example.com>
+	Helped-by: C O Mitter2 <committer2@example.com>
+	Reported-by: C O Mitter3 <committer3@example.com>
+	Mentored-by: C O Mitter4 <committer4@example.com>
+	EOF
+	test_cmp expected actual
+'
+
 test_expect_success 'multiple -m' '
 
 	>negative &&
base-commit: 13d7ab6b5d7929825b626f050b62a11241ea4945
-- 
gitgitgadget

Re: [PATCH v2] [GSOC] commit: add trailer command

From: Christian Couder <hidden>
Date: 2021-03-14 04:20:13

On Fri, Mar 12, 2021 at 4:59 PM ZheNing Hu via GitGitGadget
[off-list ref] wrote:
From: ZheNing Hu <redacted>

Historically, Git has supported the 'Signed-off-by' commit trailer
using the '--signoff' and the '-s' option from the command line.
But users may need to provide richer trailer information from the
command line such as "Helped-by", "Reported-by", "Mentored-by",
Nit: not sure that "richer" is the proper word here. I would just use
"other" instead.
Now use `--trailer <token>[(=|:)<value>]` pass the trailers to
`interpret-trailers` and generate trailers in commit messages.
The subject says "add trailer command" while here you say "use". So
which one is it? Does "--trailer" already exist, and we are just going
to use it? Or will this patch series actually "add" it?

Looking at the existing options and the code of this patch series, the
patch series actually adds the "--trailer" (not "trailer") option, so
"add" or "implement" would be clearer than "use".

So maybe something like the following might be better:

"Now implement a new `--trailer <token>[(=|:)<value>]` option to pass
other trailers to `interpret-trailers` and insert them into commit
messages."

Also something like "--trailer" is usually called an option (or
sometimes a flag), not a command (especially not when the word is not
a verb, and when the new feature isn't a new exclusive mode of
operation). So something like "commit: add --trailer option" might be
a better subject.
Signed-off-by: ZheNing Hu <redacted>
---
    [GSOC] commit: provides multiple signatures from command line
It looks like this is using the subject of a patch that previously
attempted to add features with a similar purpose. I don't think you
need to put it there, or if you want to refer to it, I think it might
be better to be a bit more explicit, for example like:

"This patch replaces my previous attempt to provide similar features
in a patch called: [GSOC] commit: provides multiple signatures from
command line."
    Now maintainers or developers can also use commit
    --trailer="Signed-off-by:commiter<email>" from the command line to
    provide trailers to commit messages. This solution may be more
    generalized than v1.
Ok, I agree that it's a good idea to have a good generic solution
first, before having specialized options for specific trailers.
Published-As: https://github.com/gitgitgadget/git/releases/tag/pr-901%2Fadlternative%2Fcommit-with-multiple-signatures-v2
Fetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-901/adlternative/commit-with-multiple-signatures-v2
Pull-Request: https://github.com/gitgitgadget/git/pull/901

Range-diff vs v1:
If this patch series has very few code and commit messages in common
with a previous attempt at implementing similar features, it might be
better to make it a new patch series rather than a v2. This could
avoid sending range-diffs that are mostly useless.

Re: [PATCH v2] [GSOC] commit: add trailer command

From: ZheNing Hu <hidden>
Date: 2021-03-14 07:10:37

Christian Couder [off-list ref] 于2021年3月14日周日 下午12:19写道:
On Fri, Mar 12, 2021 at 4:59 PM ZheNing Hu via GitGitGadget
[off-list ref] wrote:
quoted
From: ZheNing Hu <redacted>

Historically, Git has supported the 'Signed-off-by' commit trailer
using the '--signoff' and the '-s' option from the command line.
But users may need to provide richer trailer information from the
command line such as "Helped-by", "Reported-by", "Mentored-by",
Nit: not sure that "richer" is the proper word here. I would just use
"other" instead.
OK.
quoted
Now use `--trailer <token>[(=|:)<value>]` pass the trailers to
`interpret-trailers` and generate trailers in commit messages.
The subject says "add trailer command" while here you say "use". So
which one is it? Does "--trailer" already exist, and we are just going
to use it? Or will this patch series actually "add" it?

Looking at the existing options and the code of this patch series, the
patch series actually adds the "--trailer" (not "trailer") option, so
"add" or "implement" would be clearer than "use".
You're right. "add" will be more accurate in this situation.
So maybe something like the following might be better:

"Now implement a new `--trailer <token>[(=|:)<value>]` option to pass
other trailers to `interpret-trailers` and insert them into commit
messages."

Also something like "--trailer" is usually called an option (or
sometimes a flag), not a command (especially not when the word is not
a verb, and when the new feature isn't a new exclusive mode of
operation). So something like "commit: add --trailer option" might be
a better subject.
quoted
Signed-off-by: ZheNing Hu <redacted>
---
    [GSOC] commit: provides multiple signatures from command line
It looks like this is using the subject of a patch that previously
attempted to add features with a similar purpose. I don't think you
need to put it there, or if you want to refer to it, I think it might
be better to be a bit more explicit, for example like:

"This patch replaces my previous attempt to provide similar features
in a patch called: [GSOC] commit: provides multiple signatures from
command line."
I may have thought that the effect of the two patch was closer so did not
change it.
quoted
    Now maintainers or developers can also use commit
    --trailer="Signed-off-by:commiter<email>" from the command line to
    provide trailers to commit messages. This solution may be more
    generalized than v1.
Ok, I agree that it's a good idea to have a good generic solution
first, before having specialized options for specific trailers.
Thanks. :)
quoted
Published-As: https://github.com/gitgitgadget/git/releases/tag/pr-901%2Fadlternative%2Fcommit-with-multiple-signatures-v2
Fetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-901/adlternative/commit-with-multiple-signatures-v2
Pull-Request: https://github.com/gitgitgadget/git/pull/901

Range-diff vs v1:
If this patch series has very few code and commit messages in common
with a previous attempt at implementing similar features, it might be
better to make it a new patch series rather than a v2. This could
avoid sending range-diffs that are mostly useless.
Thank you for these pertinent suggestions. I will pay more attention.

[PATCH v3] [GSOC] commit: add --trailer option

From: ZheNing Hu via GitGitGadget <hidden>
Date: 2021-03-14 13:03:58

From: ZheNing Hu <redacted>

Historically, Git has supported the 'Signed-off-by' commit trailer
using the '--signoff' and the '-s' option from the command line.
But users may need to provide other trailer information from the
command line such as "Helped-by", "Reported-by", "Mentored-by",

Now implement a new `--trailer <token>[(=|:)<value>]` option to pass
other trailers to `interpret-trailers` and insert them into commit
messages.

Signed-off-by: ZheNing Hu <redacted>
---
    [GSOC] commit: add --trailer option
    
    Now maintainers or developers can also use commit
    --trailer="Signed-off-by:commiter<email>" from the command line to
    provide trailers to commit messages. This solution may be more
    generalized than v1.

Published-As: https://github.com/gitgitgadget/git/releases/tag/pr-901%2Fadlternative%2Fcommit-with-multiple-signatures-v3
Fetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-901/adlternative/commit-with-multiple-signatures-v3
Pull-Request: https://github.com/gitgitgadget/git/pull/901

Range-diff vs v2:

 1:  4c507d17db4f ! 1:  b4e161a98f8b [GSOC] commit: add trailer command
     @@ Metadata
      Author: ZheNing Hu [off-list ref]
      
       ## Commit message ##
     -    [GSOC] commit: add trailer command
     +    [GSOC] commit: add --trailer option
      
          Historically, Git has supported the 'Signed-off-by' commit trailer
          using the '--signoff' and the '-s' option from the command line.
     -    But users may need to provide richer trailer information from the
     +    But users may need to provide other trailer information from the
          command line such as "Helped-by", "Reported-by", "Mentored-by",
      
     -    Now use `--trailer <token>[(=|:)<value>]` pass the trailers to
     -    `interpret-trailers` and generate trailers in commit messages.
     +    Now implement a new `--trailer <token>[(=|:)<value>]` option to pass
     +    other trailers to `interpret-trailers` and insert them into commit
     +    messages.
      
          Signed-off-by: ZheNing Hu [off-list ref]
      


 Documentation/git-commit.txt |  8 +++++++-
 builtin/commit.c             | 18 ++++++++++++++++++
 t/t7502-commit-porcelain.sh  | 20 ++++++++++++++++++++
 3 files changed, 45 insertions(+), 1 deletion(-)
diff --git a/Documentation/git-commit.txt b/Documentation/git-commit.txt
index 17150fa7eabe..764513a3f287 100644
--- a/Documentation/git-commit.txt
+++ b/Documentation/git-commit.txt
@@ -14,7 +14,7 @@ SYNOPSIS
 	   [--allow-empty-message] [--no-verify] [-e] [--author=<author>]
 	   [--date=<date>] [--cleanup=<mode>] [--[no-]status]
 	   [-i | -o] [--pathspec-from-file=<file> [--pathspec-file-nul]]
-	   [-S[<keyid>]] [--] [<pathspec>...]
+	   [-S[<keyid>]] [--] [<pathspec>...] [(--trailer <token>[(=|:)<value>])...]
 
 DESCRIPTION
 -----------
@@ -166,6 +166,12 @@ The `-m` option is mutually exclusive with `-c`, `-C`, and `-F`.
 
 include::signoff-option.txt[]
 
+--trailer <token>[(=|:)<value>]::
+	Specify a (<token>, <value>) pair that should be applied as a
+	trailer. (e.g.  `git commit --trailer "Signed-off-by:C O Mitter <committer@example.com>" \
+	--trailer "Helped-by:C O Mitter <committer@example.com>"`will add the "Signed-off" trailer
+	and the "Helped-by" trailer in the commit message.)
+
 -n::
 --no-verify::
 	This option bypasses the pre-commit and commit-msg hooks.
diff --git a/builtin/commit.c b/builtin/commit.c
index 739110c5a7f6..abbd136b27f0 100644
--- a/builtin/commit.c
+++ b/builtin/commit.c
@@ -113,6 +113,8 @@ static int config_commit_verbose = -1; /* unspecified */
 static int no_post_rewrite, allow_empty_message, pathspec_file_nul;
 static char *untracked_files_arg, *force_date, *ignore_submodule_arg, *ignored_arg;
 static char *sign_commit, *pathspec_from_file;
+struct child_process run_trailer = CHILD_PROCESS_INIT;
+static const char *trailer;
 
 /*
  * The default commit message cleanup mode will remove the lines
@@ -131,6 +133,17 @@ static struct strbuf message = STRBUF_INIT;
 
 static enum wt_status_format status_format = STATUS_FORMAT_UNSPECIFIED;
 
+static int opt_pass_trailer(const struct option *opt, const char *arg, int unset)
+{
+	if (unset) {
+		strvec_clear(&run_trailer.args);
+		return -1;
+	}
+	run_trailer.git_cmd = 1;
+	strvec_pushl(&run_trailer.args, "--trailer", arg, NULL);
+	return 0;
+}
+
 static int opt_parse_porcelain(const struct option *opt, const char *arg, int unset)
 {
 	enum wt_status_format *value = (enum wt_status_format *)opt->value;
@@ -958,6 +971,8 @@ static int prepare_to_commit(const char *index_file, const char *prefix,
 
 	fclose(s->fp);
 
+	run_command(&run_trailer);
+
 	/*
 	 * Reject an attempt to record a non-merge empty commit without
 	 * explicit --allow-empty. In the cherry-pick case, it may be
@@ -1507,6 +1522,7 @@ int cmd_commit(int argc, const char **argv, const char *prefix)
 		OPT_STRING(0, "fixup", &fixup_message, N_("commit"), N_("use autosquash formatted message to fixup specified commit")),
 		OPT_STRING(0, "squash", &squash_message, N_("commit"), N_("use autosquash formatted message to squash specified commit")),
 		OPT_BOOL(0, "reset-author", &renew_authorship, N_("the commit is authored by me now (used with -C/-c/--amend)")),
+		OPT_CALLBACK(0, "trailer", &trailer, N_("trailer"), N_("trailer(s) to add"), opt_pass_trailer),
 		OPT_BOOL('s', "signoff", &signoff, N_("add a Signed-off-by trailer")),
 		OPT_FILENAME('t', "template", &template_file, N_("use specified template file")),
 		OPT_BOOL('e', "edit", &edit_flag, N_("force edit of commit")),
@@ -1577,6 +1593,8 @@ int cmd_commit(int argc, const char **argv, const char *prefix)
 			die(_("could not parse HEAD commit"));
 	}
 	verbose = -1; /* unspecified */
+	strvec_pushl(&run_trailer.args, "interpret-trailers",
+		"--in-place", "--where=end", git_path_commit_editmsg(), NULL);
 	argc = parse_and_validate_options(argc, argv, builtin_commit_options,
 					  builtin_commit_usage,
 					  prefix, current_head, &s);
diff --git a/t/t7502-commit-porcelain.sh b/t/t7502-commit-porcelain.sh
index 6396897cc818..4b9ac4587d17 100755
--- a/t/t7502-commit-porcelain.sh
+++ b/t/t7502-commit-porcelain.sh
@@ -154,6 +154,26 @@ test_expect_success 'sign off' '
 
 '
 
+test_expect_success 'trailer' '
+	>file1 &&
+	git add file1 &&
+	git commit -s --trailer "Signed-off-by:C O Mitter1 <committer1@example.com>" \
+	--trailer "Helped-by:C O Mitter2 <committer2@example.com>"  \
+	--trailer "Reported-by:C O Mitter3 <committer3@example.com>" \
+	--trailer "Mentored-by:C O Mitter4 <committer4@example.com>" \
+	-m "hello" &&
+	git cat-file commit HEAD >commit.msg &&
+	sed -e "1,7d" commit.msg >actual &&
+	cat >expected <<-\EOF &&
+	Signed-off-by: C O Mitter <committer@example.com>
+	Signed-off-by: C O Mitter1 <committer1@example.com>
+	Helped-by: C O Mitter2 <committer2@example.com>
+	Reported-by: C O Mitter3 <committer3@example.com>
+	Mentored-by: C O Mitter4 <committer4@example.com>
+	EOF
+	test_cmp expected actual
+'
+
 test_expect_success 'multiple -m' '
 
 	>negative &&
base-commit: 13d7ab6b5d7929825b626f050b62a11241ea4945
-- 
gitgitgadget

Re: [PATCH v3] [GSOC] commit: add --trailer option

From: Rafael Silva <hidden>
Date: 2021-03-14 13:47:19

"ZheNing Hu via GitGitGadget" [off-list ref] writes:
quoted hunk
diff --git a/builtin/commit.c b/builtin/commit.c
index 739110c5a7f6..abbd136b27f0 100644
--- a/builtin/commit.c
+++ b/builtin/commit.c
@@ -113,6 +113,8 @@ static int config_commit_verbose = -1; /* unspecified */
 static int no_post_rewrite, allow_empty_message, pathspec_file_nul;
 static char *untracked_files_arg, *force_date, *ignore_submodule_arg, *ignored_arg;
 static char *sign_commit, *pathspec_from_file;
+struct child_process run_trailer = CHILD_PROCESS_INIT;
+static const char *trailer;
 
 /*
  * The default commit message cleanup mode will remove the lines
@@ -131,6 +133,17 @@ static struct strbuf message = STRBUF_INIT;
 
 static enum wt_status_format status_format = STATUS_FORMAT_UNSPECIFIED;
 
+static int opt_pass_trailer(const struct option *opt, const char *arg, int unset)
+{
+	if (unset) {
+		strvec_clear(&run_trailer.args);
+		return -1;
+	}
+	run_trailer.git_cmd = 1;
+	strvec_pushl(&run_trailer.args, "--trailer", arg, NULL);
+	return 0;
+}
+
 static int opt_parse_porcelain(const struct option *opt, const char *arg, int unset)
 {
 	enum wt_status_format *value = (enum wt_status_format *)opt->value;
@@ -958,6 +971,8 @@ static int prepare_to_commit(const char *index_file, const char *prefix,
 
 	fclose(s->fp);
 
+	run_command(&run_trailer);
+
There is slight problem with running the command unconditionally.
If no --trailer is passed, then the opt_pass_trailer() backend
is never called, which consequently will not set the trailer
command ".git_cmd" option to 1.

This will lead the run_command() API to not interpret the command as git
internal, and attempt to launch as an usual command "interpret-trailers"
that will likely not exist or launch an unwanted command that is not
part of the GIT suite.

This can be seen by running `git commit` without any options:

     $ ./bin-wrappers/git -C /tmp/test commit
     error: cannot run interpret-trailers: No such file or directory
     ...

The `.git_cmd` should be set to true before running the command.

(the above output is from a built version with v2.31.0-rc2 + this patch
 for confirmation).


Furthermore, in my opinion, we shouldn't even bother to run the command
if no --trailer is passed, otherwise, we always be paying the cost of
launching an OS process regardless if the user doesn't want to add
trailers in theirs projects.

With that said and based on this current implementation, maybe an
improved version will look like:

        if (run_trailer.args.nr) {
                run_trailer.git_cmd = 1;
                run_command(&run_trailer);
        }

Naturally the `git_cmd = 1` will be removed from opt_pass_trailer()
function as it won't be necessary. As minor bonus, we don't end up
setting the value for every new --trailer :).
quoted hunk
 	/*
 	 * Reject an attempt to record a non-merge empty commit without
 	 * explicit --allow-empty. In the cherry-pick case, it may be
@@ -1507,6 +1522,7 @@ int cmd_commit(int argc, const char **argv, const char *prefix)
 		OPT_STRING(0, "fixup", &fixup_message, N_("commit"), N_("use autosquash formatted message to fixup specified commit")),
 		OPT_STRING(0, "squash", &squash_message, N_("commit"), N_("use autosquash formatted message to squash specified commit")),
 		OPT_BOOL(0, "reset-author", &renew_authorship, N_("the commit is authored by me now (used with -C/-c/--amend)")),
+		OPT_CALLBACK(0, "trailer", &trailer, N_("trailer"), N_("trailer(s) to add"), opt_pass_trailer),
 		OPT_BOOL('s', "signoff", &signoff, N_("add a Signed-off-by trailer")),
 		OPT_FILENAME('t', "template", &template_file, N_("use specified template file")),
 		OPT_BOOL('e', "edit", &edit_flag, N_("force edit of commit")),
@@ -1577,6 +1593,8 @@ int cmd_commit(int argc, const char **argv, const char *prefix)
 			die(_("could not parse HEAD commit"));
 	}
 	verbose = -1; /* unspecified */
+	strvec_pushl(&run_trailer.args, "interpret-trailers",
+		"--in-place", "--where=end", git_path_commit_editmsg(), NULL);
Style: The "--in-place" part should be aligned with the parentheses
much the like the following line with the "argc = parse_and_validate_options....".

For example:

	strvec_pushl(&run_trailer.args, "interpret-trailers",
		     "--in-place", "--where=end", .....
quoted hunk
 	argc = parse_and_validate_options(argc, argv, builtin_commit_options,
 					  builtin_commit_usage,
 					  prefix, current_head, &s);
diff --git a/t/t7502-commit-porcelain.sh b/t/t7502-commit-porcelain.sh
index 6396897cc818..4b9ac4587d17 100755
--- a/t/t7502-commit-porcelain.sh
+++ b/t/t7502-commit-porcelain.sh
@@ -154,6 +154,26 @@ test_expect_success 'sign off' '
 
 '
 
+test_expect_success 'trailer' '
+	>file1 &&
+	git add file1 &&
+	git commit -s --trailer "Signed-off-by:C O Mitter1 <committer1@example.com>" \
+	--trailer "Helped-by:C O Mitter2 <committer2@example.com>"  \
+	--trailer "Reported-by:C O Mitter3 <committer3@example.com>" \
+	--trailer "Mentored-by:C O Mitter4 <committer4@example.com>" \
+	-m "hello" &&
Perhaps here, the --trailer lines and "-m hello" option should be
indent in order to make it clear that these option are part of the
"git commit" from the above line, something like this:

	git commit -s --trailer "Signed-off-by:C O Mitter1 [off-list ref]" \
	        --trailer "Helped-by:C O Mitter2 [off-list ref]"  \
	        --trailer "Reported-by:C O Mitter3 [off-list ref]" \
	        --trailer "Mentored-by:C O Mitter4 [off-list ref]" \
	        -m "hello" &&
quoted hunk
+	git cat-file commit HEAD >commit.msg &&
+	sed -e "1,7d" commit.msg >actual &&
+	cat >expected <<-\EOF &&
+	Signed-off-by: C O Mitter <committer@example.com>
+	Signed-off-by: C O Mitter1 <committer1@example.com>
+	Helped-by: C O Mitter2 <committer2@example.com>
+	Reported-by: C O Mitter3 <committer3@example.com>
+	Mentored-by: C O Mitter4 <committer4@example.com>
+	EOF
+	test_cmp expected actual
+'
+
 test_expect_success 'multiple -m' '

Hope these comments are useful.
-- 
Thanks
Rafael

Re: [PATCH v3] [GSOC] commit: add --trailer option

From: ZheNing Hu <hidden>
Date: 2021-03-14 14:17:22

Rafael Silva [off-list ref] 于2021年3月14日周日 下午9:46写道:

"ZheNing Hu via GitGitGadget" [off-list ref] writes:
quoted
diff --git a/builtin/commit.c b/builtin/commit.c
index 739110c5a7f6..abbd136b27f0 100644
--- a/builtin/commit.c
+++ b/builtin/commit.c
@@ -113,6 +113,8 @@ static int config_commit_verbose = -1; /* unspecified */
 static int no_post_rewrite, allow_empty_message, pathspec_file_nul;
 static char *untracked_files_arg, *force_date, *ignore_submodule_arg, *ignored_arg;
 static char *sign_commit, *pathspec_from_file;
+struct child_process run_trailer = CHILD_PROCESS_INIT;
+static const char *trailer;

 /*
  * The default commit message cleanup mode will remove the lines
@@ -131,6 +133,17 @@ static struct strbuf message = STRBUF_INIT;

 static enum wt_status_format status_format = STATUS_FORMAT_UNSPECIFIED;

+static int opt_pass_trailer(const struct option *opt, const char *arg, int unset)
+{
+     if (unset) {
+             strvec_clear(&run_trailer.args);
+             return -1;
+     }
+     run_trailer.git_cmd = 1;
+     strvec_pushl(&run_trailer.args, "--trailer", arg, NULL);
+     return 0;
+}
+
 static int opt_parse_porcelain(const struct option *opt, const char *arg, int unset)
 {
      enum wt_status_format *value = (enum wt_status_format *)opt->value;
@@ -958,6 +971,8 @@ static int prepare_to_commit(const char *index_file, const char *prefix,

      fclose(s->fp);

+     run_command(&run_trailer);
+
There is slight problem with running the command unconditionally.
If no --trailer is passed, then the opt_pass_trailer() backend
is never called, which consequently will not set the trailer
command ".git_cmd" option to 1.

This will lead the run_command() API to not interpret the command as git
internal, and attempt to launch as an usual command "interpret-trailers"
that will likely not exist or launch an unwanted command that is not
part of the GIT suite.

This can be seen by running `git commit` without any options:

     $ ./bin-wrappers/git -C /tmp/test commit
     error: cannot run interpret-trailers: No such file or directory
     ...

The `.git_cmd` should be set to true before running the command.

(the above output is from a built version with v2.31.0-rc2 + this patch
 for confirmation).


Furthermore, in my opinion, we shouldn't even bother to run the command
if no --trailer is passed, otherwise, we always be paying the cost of
launching an OS process regardless if the user doesn't want to add
trailers in theirs projects.

With that said and based on this current implementation, maybe an
improved version will look like:

        if (run_trailer.args.nr) {
                run_trailer.git_cmd = 1;
                run_command(&run_trailer);
        }

Naturally the `git_cmd = 1` will be removed from opt_pass_trailer()
function as it won't be necessary. As minor bonus, we don't end up
setting the value for every new --trailer :).
Thank you, I didn't notice the problem before :).
quoted
      /*
       * Reject an attempt to record a non-merge empty commit without
       * explicit --allow-empty. In the cherry-pick case, it may be
@@ -1507,6 +1522,7 @@ int cmd_commit(int argc, const char **argv, const char *prefix)
              OPT_STRING(0, "fixup", &fixup_message, N_("commit"), N_("use autosquash formatted message to fixup specified commit")),
              OPT_STRING(0, "squash", &squash_message, N_("commit"), N_("use autosquash formatted message to squash specified commit")),
              OPT_BOOL(0, "reset-author", &renew_authorship, N_("the commit is authored by me now (used with -C/-c/--amend)")),
+             OPT_CALLBACK(0, "trailer", &trailer, N_("trailer"), N_("trailer(s) to add"), opt_pass_trailer),
              OPT_BOOL('s', "signoff", &signoff, N_("add a Signed-off-by trailer")),
              OPT_FILENAME('t', "template", &template_file, N_("use specified template file")),
              OPT_BOOL('e', "edit", &edit_flag, N_("force edit of commit")),
@@ -1577,6 +1593,8 @@ int cmd_commit(int argc, const char **argv, const char *prefix)
                      die(_("could not parse HEAD commit"));
      }
      verbose = -1; /* unspecified */
+     strvec_pushl(&run_trailer.args, "interpret-trailers",
+             "--in-place", "--where=end", git_path_commit_editmsg(), NULL);
Style: The "--in-place" part should be aligned with the parentheses
much the like the following line with the "argc = parse_and_validate_options....".
Well, the CodingGuidelines say it's both vaild. But you are right,
try to keep the same format as nearby code will be better.
For example:

        strvec_pushl(&run_trailer.args, "interpret-trailers",
                     "--in-place", "--where=end", .....
quoted
      argc = parse_and_validate_options(argc, argv, builtin_commit_options,
                                        builtin_commit_usage,
                                        prefix, current_head, &s);
diff --git a/t/t7502-commit-porcelain.sh b/t/t7502-commit-porcelain.sh
index 6396897cc818..4b9ac4587d17 100755
--- a/t/t7502-commit-porcelain.sh
+++ b/t/t7502-commit-porcelain.sh
@@ -154,6 +154,26 @@ test_expect_success 'sign off' '

 '

+test_expect_success 'trailer' '
+     >file1 &&
+     git add file1 &&
+     git commit -s --trailer "Signed-off-by:C O Mitter1 <committer1@example.com>" \
+     --trailer "Helped-by:C O Mitter2 <committer2@example.com>"  \
+     --trailer "Reported-by:C O Mitter3 <committer3@example.com>" \
+     --trailer "Mentored-by:C O Mitter4 <committer4@example.com>" \
+     -m "hello" &&
Perhaps here, the --trailer lines and "-m hello" option should be
indent in order to make it clear that these option are part of the
"git commit" from the above line, something like this:

        git commit -s --trailer "Signed-off-by:C O Mitter1 [off-list ref]" \
                --trailer "Helped-by:C O Mitter2 [off-list ref]"  \
                --trailer "Reported-by:C O Mitter3 [off-list ref]" \
                --trailer "Mentored-by:C O Mitter4 [off-list ref]" \
                -m "hello" &&
quoted
+     git cat-file commit HEAD >commit.msg &&
+     sed -e "1,7d" commit.msg >actual &&
+     cat >expected <<-\EOF &&
+     Signed-off-by: C O Mitter <committer@example.com>
+     Signed-off-by: C O Mitter1 <committer1@example.com>
+     Helped-by: C O Mitter2 <committer2@example.com>
+     Reported-by: C O Mitter3 <committer3@example.com>
+     Mentored-by: C O Mitter4 <committer4@example.com>
+     EOF
+     test_cmp expected actual
+'
+
 test_expect_success 'multiple -m' '

Hope these comments are useful.
--
Thanks
Rafael
 I am gratitude to you for helping me.
--
Thanks
ZheNing Hu

[PATCH v4] [GSOC] commit: add --trailer option

From: ZheNing Hu via GitGitGadget <hidden>
Date: 2021-03-14 15:59:32

From: ZheNing Hu <redacted>

Historically, Git has supported the 'Signed-off-by' commit trailer
using the '--signoff' and the '-s' option from the command line.
But users may need to provide other trailer information from the
command line such as "Helped-by", "Reported-by", "Mentored-by",

Now implement a new `--trailer <token>[(=|:)<value>]` option to pass
other trailers to `interpret-trailers` and insert them into commit
messages.

Signed-off-by: ZheNing Hu <redacted>
---
    [GSOC] commit: add --trailer option
    
    Now maintainers or developers can also use commit
    --trailer="Signed-off-by:commiter<email>" from the command line to
    provide trailers to commit messages. This solution may be more
    generalized than v1.

Published-As: https://github.com/gitgitgadget/git/releases/tag/pr-901%2Fadlternative%2Fcommit-with-multiple-signatures-v4
Fetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-901/adlternative/commit-with-multiple-signatures-v4
Pull-Request: https://github.com/gitgitgadget/git/pull/901

Range-diff vs v3:

 1:  b4e161a98f8b ! 1:  dc507553ef4f [GSOC] commit: add --trailer option
     @@ Documentation/git-commit.txt: The `-m` option is mutually exclusive with `-c`, `
       
      +--trailer <token>[(=|:)<value>]::
      +	Specify a (<token>, <value>) pair that should be applied as a
     -+	trailer. (e.g.  `git commit --trailer "Signed-off-by:C O Mitter [off-list ref]" \
     -+	--trailer "Helped-by:C O Mitter [off-list ref]"`will add the "Signed-off" trailer
     ++	trailer. (e.g. `git commit --trailer "Signed-off-by:C O Mitter \
     ++	[off-list ref]" --trailer "Helped-by:C O Mitter \
     ++	[off-list ref]"` will add the "Signed-off" trailer
      +	and the "Helped-by" trailer in the commit message.)
      +
       -n::
     @@ builtin/commit.c: static struct strbuf message = STRBUF_INIT;
      +		strvec_clear(&run_trailer.args);
      +		return -1;
      +	}
     -+	run_trailer.git_cmd = 1;
      +	strvec_pushl(&run_trailer.args, "--trailer", arg, NULL);
      +	return 0;
      +}
     @@ builtin/commit.c: static int prepare_to_commit(const char *index_file, const cha
       
       	fclose(s->fp);
       
     -+	run_command(&run_trailer);
     ++	if (run_trailer.args.nr != 4) {
     ++		run_trailer.git_cmd = 1;
     ++		run_command(&run_trailer);
     ++	} else
     ++		strvec_clear(&run_trailer.args);
      +
       	/*
       	 * Reject an attempt to record a non-merge empty commit without
     @@ builtin/commit.c: int cmd_commit(int argc, const char **argv, const char *prefix
       	}
       	verbose = -1; /* unspecified */
      +	strvec_pushl(&run_trailer.args, "interpret-trailers",
     -+		"--in-place", "--where=end", git_path_commit_editmsg(), NULL);
     ++		    "--in-place", "--where=end", git_path_commit_editmsg(), NULL);
       	argc = parse_and_validate_options(argc, argv, builtin_commit_options,
       					  builtin_commit_usage,
       					  prefix, current_head, &s);
     @@ t/t7502-commit-porcelain.sh: test_expect_success 'sign off' '
      +	>file1 &&
      +	git add file1 &&
      +	git commit -s --trailer "Signed-off-by:C O Mitter1 [off-list ref]" \
     -+	--trailer "Helped-by:C O Mitter2 [off-list ref]"  \
     -+	--trailer "Reported-by:C O Mitter3 [off-list ref]" \
     -+	--trailer "Mentored-by:C O Mitter4 [off-list ref]" \
     -+	-m "hello" &&
     ++		--trailer "Helped-by:C O Mitter2 [off-list ref]"  \
     ++		--trailer "Reported-by:C O Mitter3 [off-list ref]" \
     ++		--trailer "Mentored-by:C O Mitter4 [off-list ref]" \
     ++		-m "hello" &&
      +	git cat-file commit HEAD >commit.msg &&
      +	sed -e "1,7d" commit.msg >actual &&
      +	cat >expected <<-\EOF &&


 Documentation/git-commit.txt |  9 ++++++++-
 builtin/commit.c             | 21 +++++++++++++++++++++
 t/t7502-commit-porcelain.sh  | 20 ++++++++++++++++++++
 3 files changed, 49 insertions(+), 1 deletion(-)
diff --git a/Documentation/git-commit.txt b/Documentation/git-commit.txt
index 17150fa7eabe..73a7507db47f 100644
--- a/Documentation/git-commit.txt
+++ b/Documentation/git-commit.txt
@@ -14,7 +14,7 @@ SYNOPSIS
 	   [--allow-empty-message] [--no-verify] [-e] [--author=<author>]
 	   [--date=<date>] [--cleanup=<mode>] [--[no-]status]
 	   [-i | -o] [--pathspec-from-file=<file> [--pathspec-file-nul]]
-	   [-S[<keyid>]] [--] [<pathspec>...]
+	   [-S[<keyid>]] [--] [<pathspec>...] [(--trailer <token>[(=|:)<value>])...]
 
 DESCRIPTION
 -----------
@@ -166,6 +166,13 @@ The `-m` option is mutually exclusive with `-c`, `-C`, and `-F`.
 
 include::signoff-option.txt[]
 
+--trailer <token>[(=|:)<value>]::
+	Specify a (<token>, <value>) pair that should be applied as a
+	trailer. (e.g. `git commit --trailer "Signed-off-by:C O Mitter \
+	<committer@example.com>" --trailer "Helped-by:C O Mitter \
+	<committer@example.com>"` will add the "Signed-off" trailer
+	and the "Helped-by" trailer in the commit message.)
+
 -n::
 --no-verify::
 	This option bypasses the pre-commit and commit-msg hooks.
diff --git a/builtin/commit.c b/builtin/commit.c
index 739110c5a7f6..b35ae5c9790d 100644
--- a/builtin/commit.c
+++ b/builtin/commit.c
@@ -113,6 +113,8 @@ static int config_commit_verbose = -1; /* unspecified */
 static int no_post_rewrite, allow_empty_message, pathspec_file_nul;
 static char *untracked_files_arg, *force_date, *ignore_submodule_arg, *ignored_arg;
 static char *sign_commit, *pathspec_from_file;
+struct child_process run_trailer = CHILD_PROCESS_INIT;
+static const char *trailer;
 
 /*
  * The default commit message cleanup mode will remove the lines
@@ -131,6 +133,16 @@ static struct strbuf message = STRBUF_INIT;
 
 static enum wt_status_format status_format = STATUS_FORMAT_UNSPECIFIED;
 
+static int opt_pass_trailer(const struct option *opt, const char *arg, int unset)
+{
+	if (unset) {
+		strvec_clear(&run_trailer.args);
+		return -1;
+	}
+	strvec_pushl(&run_trailer.args, "--trailer", arg, NULL);
+	return 0;
+}
+
 static int opt_parse_porcelain(const struct option *opt, const char *arg, int unset)
 {
 	enum wt_status_format *value = (enum wt_status_format *)opt->value;
@@ -958,6 +970,12 @@ static int prepare_to_commit(const char *index_file, const char *prefix,
 
 	fclose(s->fp);
 
+	if (run_trailer.args.nr != 4) {
+		run_trailer.git_cmd = 1;
+		run_command(&run_trailer);
+	} else
+		strvec_clear(&run_trailer.args);
+
 	/*
 	 * Reject an attempt to record a non-merge empty commit without
 	 * explicit --allow-empty. In the cherry-pick case, it may be
@@ -1507,6 +1525,7 @@ int cmd_commit(int argc, const char **argv, const char *prefix)
 		OPT_STRING(0, "fixup", &fixup_message, N_("commit"), N_("use autosquash formatted message to fixup specified commit")),
 		OPT_STRING(0, "squash", &squash_message, N_("commit"), N_("use autosquash formatted message to squash specified commit")),
 		OPT_BOOL(0, "reset-author", &renew_authorship, N_("the commit is authored by me now (used with -C/-c/--amend)")),
+		OPT_CALLBACK(0, "trailer", &trailer, N_("trailer"), N_("trailer(s) to add"), opt_pass_trailer),
 		OPT_BOOL('s', "signoff", &signoff, N_("add a Signed-off-by trailer")),
 		OPT_FILENAME('t', "template", &template_file, N_("use specified template file")),
 		OPT_BOOL('e', "edit", &edit_flag, N_("force edit of commit")),
@@ -1577,6 +1596,8 @@ int cmd_commit(int argc, const char **argv, const char *prefix)
 			die(_("could not parse HEAD commit"));
 	}
 	verbose = -1; /* unspecified */
+	strvec_pushl(&run_trailer.args, "interpret-trailers",
+		    "--in-place", "--where=end", git_path_commit_editmsg(), NULL);
 	argc = parse_and_validate_options(argc, argv, builtin_commit_options,
 					  builtin_commit_usage,
 					  prefix, current_head, &s);
diff --git a/t/t7502-commit-porcelain.sh b/t/t7502-commit-porcelain.sh
index 6396897cc818..0acf23799931 100755
--- a/t/t7502-commit-porcelain.sh
+++ b/t/t7502-commit-porcelain.sh
@@ -154,6 +154,26 @@ test_expect_success 'sign off' '
 
 '
 
+test_expect_success 'trailer' '
+	>file1 &&
+	git add file1 &&
+	git commit -s --trailer "Signed-off-by:C O Mitter1 <committer1@example.com>" \
+		--trailer "Helped-by:C O Mitter2 <committer2@example.com>"  \
+		--trailer "Reported-by:C O Mitter3 <committer3@example.com>" \
+		--trailer "Mentored-by:C O Mitter4 <committer4@example.com>" \
+		-m "hello" &&
+	git cat-file commit HEAD >commit.msg &&
+	sed -e "1,7d" commit.msg >actual &&
+	cat >expected <<-\EOF &&
+	Signed-off-by: C O Mitter <committer@example.com>
+	Signed-off-by: C O Mitter1 <committer1@example.com>
+	Helped-by: C O Mitter2 <committer2@example.com>
+	Reported-by: C O Mitter3 <committer3@example.com>
+	Mentored-by: C O Mitter4 <committer4@example.com>
+	EOF
+	test_cmp expected actual
+'
+
 test_expect_success 'multiple -m' '
 
 	>negative &&
base-commit: 13d7ab6b5d7929825b626f050b62a11241ea4945
-- 
gitgitgadget

[PATCH v5] [GSOC] commit: add --trailer option

From: ZheNing Hu via GitGitGadget <hidden>
Date: 2021-03-15 03:25:48

From: ZheNing Hu <redacted>

Historically, Git has supported the 'Signed-off-by' commit trailer
using the '--signoff' and the '-s' option from the command line.
But users may need to provide other trailer information from the
command line such as "Helped-by", "Reported-by", "Mentored-by",

Now implement a new `--trailer <token>[(=|:)<value>]` option to pass
other trailers to `interpret-trailers` and insert them into commit
messages.

Signed-off-by: ZheNing Hu <redacted>
---
    [GSOC] commit: add --trailer option
    
    Now maintainers or developers can also use commit
    --trailer="Signed-off-by:commiter<email>" from the command line to
    provide trailers to commit messages. This solution may be more
    generalized than v1.

Published-As: https://github.com/gitgitgadget/git/releases/tag/pr-901%2Fadlternative%2Fcommit-with-multiple-signatures-v5
Fetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-901/adlternative/commit-with-multiple-signatures-v5
Pull-Request: https://github.com/gitgitgadget/git/pull/901

Range-diff vs v4:

 1:  dc507553ef4f ! 1:  ca91accb2852 [GSOC] commit: add --trailer option
     @@ builtin/commit.c: static int config_commit_verbose = -1; /* unspecified */
       static char *untracked_files_arg, *force_date, *ignore_submodule_arg, *ignored_arg;
       static char *sign_commit, *pathspec_from_file;
      +struct child_process run_trailer = CHILD_PROCESS_INIT;
     ++struct strvec trailer_args = STRVEC_INIT;
      +static const char *trailer;
       
       /*
     @@ builtin/commit.c: static struct strbuf message = STRBUF_INIT;
      +static int opt_pass_trailer(const struct option *opt, const char *arg, int unset)
      +{
      +	if (unset) {
     -+		strvec_clear(&run_trailer.args);
     ++		strvec_clear(&trailer_args);
      +		return -1;
      +	}
     -+	strvec_pushl(&run_trailer.args, "--trailer", arg, NULL);
     ++	strvec_pushl(&trailer_args, "--trailer", arg, NULL);
      +	return 0;
      +}
      +
     @@ builtin/commit.c: static int prepare_to_commit(const char *index_file, const cha
       
       	fclose(s->fp);
       
     -+	if (run_trailer.args.nr != 4) {
     ++	if (trailer_args.nr) {
     ++		strvec_pushl(&run_trailer.args, "interpret-trailers",
     ++			     "--in-place", "--where=end", git_path_commit_editmsg(), NULL);
     ++		strvec_pushv(&run_trailer.args, trailer_args.v);
      +		run_trailer.git_cmd = 1;
     -+		run_command(&run_trailer);
     -+	} else
     -+		strvec_clear(&run_trailer.args);
     ++		if (run_command(&run_trailer))
     ++			strvec_clear(&run_trailer.args);
     ++		strvec_clear(&trailer_args);
     ++	}
      +
       	/*
       	 * Reject an attempt to record a non-merge empty commit without
     @@ builtin/commit.c: int cmd_commit(int argc, const char **argv, const char *prefix
       		OPT_BOOL('s', "signoff", &signoff, N_("add a Signed-off-by trailer")),
       		OPT_FILENAME('t', "template", &template_file, N_("use specified template file")),
       		OPT_BOOL('e', "edit", &edit_flag, N_("force edit of commit")),
     -@@ builtin/commit.c: int cmd_commit(int argc, const char **argv, const char *prefix)
     - 			die(_("could not parse HEAD commit"));
     - 	}
     - 	verbose = -1; /* unspecified */
     -+	strvec_pushl(&run_trailer.args, "interpret-trailers",
     -+		    "--in-place", "--where=end", git_path_commit_editmsg(), NULL);
     - 	argc = parse_and_validate_options(argc, argv, builtin_commit_options,
     - 					  builtin_commit_usage,
     - 					  prefix, current_head, &s);
      
       ## t/t7502-commit-porcelain.sh ##
      @@ t/t7502-commit-porcelain.sh: test_expect_success 'sign off' '


 Documentation/git-commit.txt |  9 ++++++++-
 builtin/commit.c             | 24 ++++++++++++++++++++++++
 t/t7502-commit-porcelain.sh  | 20 ++++++++++++++++++++
 3 files changed, 52 insertions(+), 1 deletion(-)
diff --git a/Documentation/git-commit.txt b/Documentation/git-commit.txt
index 17150fa7eabe..73a7507db47f 100644
--- a/Documentation/git-commit.txt
+++ b/Documentation/git-commit.txt
@@ -14,7 +14,7 @@ SYNOPSIS
 	   [--allow-empty-message] [--no-verify] [-e] [--author=<author>]
 	   [--date=<date>] [--cleanup=<mode>] [--[no-]status]
 	   [-i | -o] [--pathspec-from-file=<file> [--pathspec-file-nul]]
-	   [-S[<keyid>]] [--] [<pathspec>...]
+	   [-S[<keyid>]] [--] [<pathspec>...] [(--trailer <token>[(=|:)<value>])...]
 
 DESCRIPTION
 -----------
@@ -166,6 +166,13 @@ The `-m` option is mutually exclusive with `-c`, `-C`, and `-F`.
 
 include::signoff-option.txt[]
 
+--trailer <token>[(=|:)<value>]::
+	Specify a (<token>, <value>) pair that should be applied as a
+	trailer. (e.g. `git commit --trailer "Signed-off-by:C O Mitter \
+	<committer@example.com>" --trailer "Helped-by:C O Mitter \
+	<committer@example.com>"` will add the "Signed-off" trailer
+	and the "Helped-by" trailer in the commit message.)
+
 -n::
 --no-verify::
 	This option bypasses the pre-commit and commit-msg hooks.
diff --git a/builtin/commit.c b/builtin/commit.c
index 739110c5a7f6..24fccb561398 100644
--- a/builtin/commit.c
+++ b/builtin/commit.c
@@ -113,6 +113,9 @@ static int config_commit_verbose = -1; /* unspecified */
 static int no_post_rewrite, allow_empty_message, pathspec_file_nul;
 static char *untracked_files_arg, *force_date, *ignore_submodule_arg, *ignored_arg;
 static char *sign_commit, *pathspec_from_file;
+struct child_process run_trailer = CHILD_PROCESS_INIT;
+struct strvec trailer_args = STRVEC_INIT;
+static const char *trailer;
 
 /*
  * The default commit message cleanup mode will remove the lines
@@ -131,6 +134,16 @@ static struct strbuf message = STRBUF_INIT;
 
 static enum wt_status_format status_format = STATUS_FORMAT_UNSPECIFIED;
 
+static int opt_pass_trailer(const struct option *opt, const char *arg, int unset)
+{
+	if (unset) {
+		strvec_clear(&trailer_args);
+		return -1;
+	}
+	strvec_pushl(&trailer_args, "--trailer", arg, NULL);
+	return 0;
+}
+
 static int opt_parse_porcelain(const struct option *opt, const char *arg, int unset)
 {
 	enum wt_status_format *value = (enum wt_status_format *)opt->value;
@@ -958,6 +971,16 @@ static int prepare_to_commit(const char *index_file, const char *prefix,
 
 	fclose(s->fp);
 
+	if (trailer_args.nr) {
+		strvec_pushl(&run_trailer.args, "interpret-trailers",
+			     "--in-place", "--where=end", git_path_commit_editmsg(), NULL);
+		strvec_pushv(&run_trailer.args, trailer_args.v);
+		run_trailer.git_cmd = 1;
+		if (run_command(&run_trailer))
+			strvec_clear(&run_trailer.args);
+		strvec_clear(&trailer_args);
+	}
+
 	/*
 	 * Reject an attempt to record a non-merge empty commit without
 	 * explicit --allow-empty. In the cherry-pick case, it may be
@@ -1507,6 +1530,7 @@ int cmd_commit(int argc, const char **argv, const char *prefix)
 		OPT_STRING(0, "fixup", &fixup_message, N_("commit"), N_("use autosquash formatted message to fixup specified commit")),
 		OPT_STRING(0, "squash", &squash_message, N_("commit"), N_("use autosquash formatted message to squash specified commit")),
 		OPT_BOOL(0, "reset-author", &renew_authorship, N_("the commit is authored by me now (used with -C/-c/--amend)")),
+		OPT_CALLBACK(0, "trailer", &trailer, N_("trailer"), N_("trailer(s) to add"), opt_pass_trailer),
 		OPT_BOOL('s', "signoff", &signoff, N_("add a Signed-off-by trailer")),
 		OPT_FILENAME('t', "template", &template_file, N_("use specified template file")),
 		OPT_BOOL('e', "edit", &edit_flag, N_("force edit of commit")),
diff --git a/t/t7502-commit-porcelain.sh b/t/t7502-commit-porcelain.sh
index 6396897cc818..0acf23799931 100755
--- a/t/t7502-commit-porcelain.sh
+++ b/t/t7502-commit-porcelain.sh
@@ -154,6 +154,26 @@ test_expect_success 'sign off' '
 
 '
 
+test_expect_success 'trailer' '
+	>file1 &&
+	git add file1 &&
+	git commit -s --trailer "Signed-off-by:C O Mitter1 <committer1@example.com>" \
+		--trailer "Helped-by:C O Mitter2 <committer2@example.com>"  \
+		--trailer "Reported-by:C O Mitter3 <committer3@example.com>" \
+		--trailer "Mentored-by:C O Mitter4 <committer4@example.com>" \
+		-m "hello" &&
+	git cat-file commit HEAD >commit.msg &&
+	sed -e "1,7d" commit.msg >actual &&
+	cat >expected <<-\EOF &&
+	Signed-off-by: C O Mitter <committer@example.com>
+	Signed-off-by: C O Mitter1 <committer1@example.com>
+	Helped-by: C O Mitter2 <committer2@example.com>
+	Reported-by: C O Mitter3 <committer3@example.com>
+	Mentored-by: C O Mitter4 <committer4@example.com>
+	EOF
+	test_cmp expected actual
+'
+
 test_expect_success 'multiple -m' '
 
 	>negative &&
base-commit: 13d7ab6b5d7929825b626f050b62a11241ea4945
-- 
gitgitgadget

Re: [PATCH v5] [GSOC] commit: add --trailer option

From: Christian Couder <hidden>
Date: 2021-03-15 05:34:27

On Mon, Mar 15, 2021 at 4:24 AM ZheNing Hu via GitGitGadget
[off-list ref] wrote:
quoted hunk
diff --git a/builtin/commit.c b/builtin/commit.c
index 739110c5a7f6..24fccb561398 100644
--- a/builtin/commit.c
+++ b/builtin/commit.c
@@ -113,6 +113,9 @@ static int config_commit_verbose = -1; /* unspecified */
 static int no_post_rewrite, allow_empty_message, pathspec_file_nul;
 static char *untracked_files_arg, *force_date, *ignore_submodule_arg, *ignored_arg;
 static char *sign_commit, *pathspec_from_file;
+struct child_process run_trailer = CHILD_PROCESS_INIT;
It seems to me that `run_trailer` is used only in the `if
(trailer_args.nr) {...}` block, so it could be declared there instead
of as a global variable.
+struct strvec trailer_args = STRVEC_INIT;
+static const char *trailer;
I am not sure that this `trailer`variable is really needed. It seems
to be used only as the third argument to OPT_CALLBACK(), but there are
other places in the code base where we pass NULL as the third
argument.

Re: [PATCH v5] [GSOC] commit: add --trailer option

From: Christian Couder <hidden>
Date: 2021-03-15 05:42:58

On Mon, Mar 15, 2021 at 6:33 AM Christian Couder
[off-list ref] wrote:
On Mon, Mar 15, 2021 at 4:24 AM ZheNing Hu via GitGitGadget
[off-list ref] wrote:
quoted
diff --git a/builtin/commit.c b/builtin/commit.c
index 739110c5a7f6..24fccb561398 100644
--- a/builtin/commit.c
+++ b/builtin/commit.c
@@ -113,6 +113,9 @@ static int config_commit_verbose = -1; /* unspecified */
 static int no_post_rewrite, allow_empty_message, pathspec_file_nul;
 static char *untracked_files_arg, *force_date, *ignore_submodule_arg, *ignored_arg;
 static char *sign_commit, *pathspec_from_file;
+struct child_process run_trailer = CHILD_PROCESS_INIT;
It seems to me that `run_trailer` is used only in the `if
(trailer_args.nr) {...}` block, so it could be declared there instead
of as a global variable.
quoted
+struct strvec trailer_args = STRVEC_INIT;
Also you might want to add "static" in front of "struct strvec" in the
above line.

Re: [PATCH v5] [GSOC] commit: add --trailer option

From: ZheNing Hu <hidden>
Date: 2021-03-15 05:47:19

Christian Couder [off-list ref] 于2021年3月15日周一 下午1:33写道:
On Mon, Mar 15, 2021 at 4:24 AM ZheNing Hu via GitGitGadget
[off-list ref] wrote:
quoted
diff --git a/builtin/commit.c b/builtin/commit.c
index 739110c5a7f6..24fccb561398 100644
--- a/builtin/commit.c
+++ b/builtin/commit.c
@@ -113,6 +113,9 @@ static int config_commit_verbose = -1; /* unspecified */
 static int no_post_rewrite, allow_empty_message, pathspec_file_nul;
 static char *untracked_files_arg, *force_date, *ignore_submodule_arg, *ignored_arg;
 static char *sign_commit, *pathspec_from_file;
+struct child_process run_trailer = CHILD_PROCESS_INIT;
It seems to me that `run_trailer` is used only in the `if
(trailer_args.nr) {...}` block, so it could be declared there instead
of as a global variable.
quoted
+struct strvec trailer_args = STRVEC_INIT;
+static const char *trailer;
I am not sure that this `trailer`variable is really needed. It seems
to be used only as the third argument to OPT_CALLBACK(), but there are
other places in the code base where we pass NULL as the third
argument.
Good advice.

[PATCH v6] [GSOC] commit: add --trailer option

From: ZheNing Hu via GitGitGadget <hidden>
Date: 2021-03-15 06:37:00

From: ZheNing Hu <redacted>

Historically, Git has supported the 'Signed-off-by' commit trailer
using the '--signoff' and the '-s' option from the command line.
But users may need to provide other trailer information from the
command line such as "Helped-by", "Reported-by", "Mentored-by",

Now implement a new `--trailer <token>[(=|:)<value>]` option to pass
other trailers to `interpret-trailers` and insert them into commit
messages.

Signed-off-by: ZheNing Hu <redacted>
---
    [GSOC] commit: add --trailer option
    
    Now maintainers or developers can also use commit
    --trailer="Signed-off-by:commiter<email>" from the command line to
    provide trailers to commit messages. This solution may be more
    generalized than v1.

Published-As: https://github.com/gitgitgadget/git/releases/tag/pr-901%2Fadlternative%2Fcommit-with-multiple-signatures-v6
Fetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-901/adlternative/commit-with-multiple-signatures-v6
Pull-Request: https://github.com/gitgitgadget/git/pull/901

Range-diff vs v5:

 1:  ca91accb2852 ! 1:  c99ce75da792 [GSOC] commit: add --trailer option
     @@ builtin/commit.c: static int config_commit_verbose = -1; /* unspecified */
       static int no_post_rewrite, allow_empty_message, pathspec_file_nul;
       static char *untracked_files_arg, *force_date, *ignore_submodule_arg, *ignored_arg;
       static char *sign_commit, *pathspec_from_file;
     -+struct child_process run_trailer = CHILD_PROCESS_INIT;
      +struct strvec trailer_args = STRVEC_INIT;
     -+static const char *trailer;
       
       /*
        * The default commit message cleanup mode will remove the lines
     @@ builtin/commit.c: static struct strbuf message = STRBUF_INIT;
       
      +static int opt_pass_trailer(const struct option *opt, const char *arg, int unset)
      +{
     -+	if (unset) {
     -+		strvec_clear(&trailer_args);
     -+		return -1;
     -+	}
     ++	BUG_ON_OPT_NEG(unset);
     ++
      +	strvec_pushl(&trailer_args, "--trailer", arg, NULL);
      +	return 0;
      +}
     @@ builtin/commit.c: static int prepare_to_commit(const char *index_file, const cha
       	fclose(s->fp);
       
      +	if (trailer_args.nr) {
     ++		static struct child_process run_trailer = CHILD_PROCESS_INIT;
     ++
      +		strvec_pushl(&run_trailer.args, "interpret-trailers",
      +			     "--in-place", "--where=end", git_path_commit_editmsg(), NULL);
      +		strvec_pushv(&run_trailer.args, trailer_args.v);
     @@ builtin/commit.c: int cmd_commit(int argc, const char **argv, const char *prefix
       		OPT_STRING(0, "fixup", &fixup_message, N_("commit"), N_("use autosquash formatted message to fixup specified commit")),
       		OPT_STRING(0, "squash", &squash_message, N_("commit"), N_("use autosquash formatted message to squash specified commit")),
       		OPT_BOOL(0, "reset-author", &renew_authorship, N_("the commit is authored by me now (used with -C/-c/--amend)")),
     -+		OPT_CALLBACK(0, "trailer", &trailer, N_("trailer"), N_("trailer(s) to add"), opt_pass_trailer),
     ++		OPT_CALLBACK_F(0, "trailer", NULL, N_("trailer"), N_("trailer(s) to add"), PARSE_OPT_NONEG, opt_pass_trailer),
       		OPT_BOOL('s', "signoff", &signoff, N_("add a Signed-off-by trailer")),
       		OPT_FILENAME('t', "template", &template_file, N_("use specified template file")),
       		OPT_BOOL('e', "edit", &edit_flag, N_("force edit of commit")),


 Documentation/git-commit.txt |  9 ++++++++-
 builtin/commit.c             | 22 ++++++++++++++++++++++
 t/t7502-commit-porcelain.sh  | 20 ++++++++++++++++++++
 3 files changed, 50 insertions(+), 1 deletion(-)
diff --git a/Documentation/git-commit.txt b/Documentation/git-commit.txt
index 17150fa7eabe..73a7507db47f 100644
--- a/Documentation/git-commit.txt
+++ b/Documentation/git-commit.txt
@@ -14,7 +14,7 @@ SYNOPSIS
 	   [--allow-empty-message] [--no-verify] [-e] [--author=<author>]
 	   [--date=<date>] [--cleanup=<mode>] [--[no-]status]
 	   [-i | -o] [--pathspec-from-file=<file> [--pathspec-file-nul]]
-	   [-S[<keyid>]] [--] [<pathspec>...]
+	   [-S[<keyid>]] [--] [<pathspec>...] [(--trailer <token>[(=|:)<value>])...]
 
 DESCRIPTION
 -----------
@@ -166,6 +166,13 @@ The `-m` option is mutually exclusive with `-c`, `-C`, and `-F`.
 
 include::signoff-option.txt[]
 
+--trailer <token>[(=|:)<value>]::
+	Specify a (<token>, <value>) pair that should be applied as a
+	trailer. (e.g. `git commit --trailer "Signed-off-by:C O Mitter \
+	<committer@example.com>" --trailer "Helped-by:C O Mitter \
+	<committer@example.com>"` will add the "Signed-off" trailer
+	and the "Helped-by" trailer in the commit message.)
+
 -n::
 --no-verify::
 	This option bypasses the pre-commit and commit-msg hooks.
diff --git a/builtin/commit.c b/builtin/commit.c
index 739110c5a7f6..726d2f4eaf5d 100644
--- a/builtin/commit.c
+++ b/builtin/commit.c
@@ -113,6 +113,7 @@ static int config_commit_verbose = -1; /* unspecified */
 static int no_post_rewrite, allow_empty_message, pathspec_file_nul;
 static char *untracked_files_arg, *force_date, *ignore_submodule_arg, *ignored_arg;
 static char *sign_commit, *pathspec_from_file;
+struct strvec trailer_args = STRVEC_INIT;
 
 /*
  * The default commit message cleanup mode will remove the lines
@@ -131,6 +132,14 @@ static struct strbuf message = STRBUF_INIT;
 
 static enum wt_status_format status_format = STATUS_FORMAT_UNSPECIFIED;
 
+static int opt_pass_trailer(const struct option *opt, const char *arg, int unset)
+{
+	BUG_ON_OPT_NEG(unset);
+
+	strvec_pushl(&trailer_args, "--trailer", arg, NULL);
+	return 0;
+}
+
 static int opt_parse_porcelain(const struct option *opt, const char *arg, int unset)
 {
 	enum wt_status_format *value = (enum wt_status_format *)opt->value;
@@ -958,6 +967,18 @@ static int prepare_to_commit(const char *index_file, const char *prefix,
 
 	fclose(s->fp);
 
+	if (trailer_args.nr) {
+		static struct child_process run_trailer = CHILD_PROCESS_INIT;
+
+		strvec_pushl(&run_trailer.args, "interpret-trailers",
+			     "--in-place", "--where=end", git_path_commit_editmsg(), NULL);
+		strvec_pushv(&run_trailer.args, trailer_args.v);
+		run_trailer.git_cmd = 1;
+		if (run_command(&run_trailer))
+			strvec_clear(&run_trailer.args);
+		strvec_clear(&trailer_args);
+	}
+
 	/*
 	 * Reject an attempt to record a non-merge empty commit without
 	 * explicit --allow-empty. In the cherry-pick case, it may be
@@ -1507,6 +1528,7 @@ int cmd_commit(int argc, const char **argv, const char *prefix)
 		OPT_STRING(0, "fixup", &fixup_message, N_("commit"), N_("use autosquash formatted message to fixup specified commit")),
 		OPT_STRING(0, "squash", &squash_message, N_("commit"), N_("use autosquash formatted message to squash specified commit")),
 		OPT_BOOL(0, "reset-author", &renew_authorship, N_("the commit is authored by me now (used with -C/-c/--amend)")),
+		OPT_CALLBACK_F(0, "trailer", NULL, N_("trailer"), N_("trailer(s) to add"), PARSE_OPT_NONEG, opt_pass_trailer),
 		OPT_BOOL('s', "signoff", &signoff, N_("add a Signed-off-by trailer")),
 		OPT_FILENAME('t', "template", &template_file, N_("use specified template file")),
 		OPT_BOOL('e', "edit", &edit_flag, N_("force edit of commit")),
diff --git a/t/t7502-commit-porcelain.sh b/t/t7502-commit-porcelain.sh
index 6396897cc818..0acf23799931 100755
--- a/t/t7502-commit-porcelain.sh
+++ b/t/t7502-commit-porcelain.sh
@@ -154,6 +154,26 @@ test_expect_success 'sign off' '
 
 '
 
+test_expect_success 'trailer' '
+	>file1 &&
+	git add file1 &&
+	git commit -s --trailer "Signed-off-by:C O Mitter1 <committer1@example.com>" \
+		--trailer "Helped-by:C O Mitter2 <committer2@example.com>"  \
+		--trailer "Reported-by:C O Mitter3 <committer3@example.com>" \
+		--trailer "Mentored-by:C O Mitter4 <committer4@example.com>" \
+		-m "hello" &&
+	git cat-file commit HEAD >commit.msg &&
+	sed -e "1,7d" commit.msg >actual &&
+	cat >expected <<-\EOF &&
+	Signed-off-by: C O Mitter <committer@example.com>
+	Signed-off-by: C O Mitter1 <committer1@example.com>
+	Helped-by: C O Mitter2 <committer2@example.com>
+	Reported-by: C O Mitter3 <committer3@example.com>
+	Mentored-by: C O Mitter4 <committer4@example.com>
+	EOF
+	test_cmp expected actual
+'
+
 test_expect_success 'multiple -m' '
 
 	>negative &&
base-commit: 13d7ab6b5d7929825b626f050b62a11241ea4945
-- 
gitgitgadget

Re: [PATCH v6] [GSOC] commit: add --trailer option

From: Christian Couder <hidden>
Date: 2021-03-15 08:03:37

On Mon, Mar 15, 2021 at 7:35 AM ZheNing Hu via GitGitGadget
[off-list ref] wrote:
From: ZheNing Hu <redacted>

Historically, Git has supported the 'Signed-off-by' commit trailer
using the '--signoff' and the '-s' option from the command line.
But users may need to provide other trailer information from the
command line such as "Helped-by", "Reported-by", "Mentored-by",

Now implement a new `--trailer <token>[(=|:)<value>]` option to pass
other trailers to `interpret-trailers` and insert them into commit
messages.

Signed-off-by: ZheNing Hu <redacted>
---
    [GSOC] commit: add --trailer option

    Now maintainers or developers can also use commit
    --trailer="Signed-off-by:commiter<email>" from the command line to
    provide trailers to commit messages. This solution may be more
    generalized than v1.

Published-As: https://github.com/gitgitgadget/git/releases/tag/pr-901%2Fadlternative%2Fcommit-with-multiple-signatures-v6
Fetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-901/adlternative/commit-with-multiple-signatures-v6
Pull-Request: https://github.com/gitgitgadget/git/pull/901

Range-diff vs v5:

 1:  ca91accb2852 ! 1:  c99ce75da792 [GSOC] commit: add --trailer option
     @@ builtin/commit.c: static int config_commit_verbose = -1; /* unspecified */
       static int no_post_rewrite, allow_empty_message, pathspec_file_nul;
       static char *untracked_files_arg, *force_date, *ignore_submodule_arg, *ignored_arg;
       static char *sign_commit, *pathspec_from_file;
     -+struct child_process run_trailer = CHILD_PROCESS_INIT;
      +struct strvec trailer_args = STRVEC_INIT;
I suggested added "static" in front of the above line, like this:

static struct strvec trailer_args = STRVEC_INIT;

You can check with `git grep '^static struct strvec' '*.c'` and `git
grep '^struct strvec' '*.c'` that we use "static" when declaring a
'struct strvec' globally.

[...]
      + if (trailer_args.nr) {
     ++         static struct child_process run_trailer = CHILD_PROCESS_INIT;
I didn't suggest using "static" here.
     ++
      +         strvec_pushl(&run_trailer.args, "interpret-trailers",
Thanks for working on this!

Re: [PATCH v6] [GSOC] commit: add --trailer option

From: ZheNing Hu <hidden>
Date: 2021-03-15 08:22:16

Christian Couder [off-list ref] 于2021年3月15日周一 下午4:03写道:
On Mon, Mar 15, 2021 at 7:35 AM ZheNing Hu via GitGitGadget
[off-list ref] wrote:
quoted
From: ZheNing Hu <redacted>

Historically, Git has supported the 'Signed-off-by' commit trailer
using the '--signoff' and the '-s' option from the command line.
But users may need to provide other trailer information from the
command line such as "Helped-by", "Reported-by", "Mentored-by",

Now implement a new `--trailer <token>[(=|:)<value>]` option to pass
other trailers to `interpret-trailers` and insert them into commit
messages.

Signed-off-by: ZheNing Hu <redacted>
---
    [GSOC] commit: add --trailer option

    Now maintainers or developers can also use commit
    --trailer="Signed-off-by:commiter<email>" from the command line to
    provide trailers to commit messages. This solution may be more
    generalized than v1.

Published-As: https://github.com/gitgitgadget/git/releases/tag/pr-901%2Fadlternative%2Fcommit-with-multiple-signatures-v6
Fetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-901/adlternative/commit-with-multiple-signatures-v6
Pull-Request: https://github.com/gitgitgadget/git/pull/901

Range-diff vs v5:

 1:  ca91accb2852 ! 1:  c99ce75da792 [GSOC] commit: add --trailer option
     @@ builtin/commit.c: static int config_commit_verbose = -1; /* unspecified */
       static int no_post_rewrite, allow_empty_message, pathspec_file_nul;
       static char *untracked_files_arg, *force_date, *ignore_submodule_arg, *ignored_arg;
       static char *sign_commit, *pathspec_from_file;
     -+struct child_process run_trailer = CHILD_PROCESS_INIT;
      +struct strvec trailer_args = STRVEC_INIT;
I suggested added "static" in front of the above line, like this:

static struct strvec trailer_args = STRVEC_INIT;

You can check with `git grep '^static struct strvec' '*.c'` and `git
grep '^struct strvec' '*.c'` that we use "static" when declaring a
'struct strvec' globally.

[...]
quoted
      + if (trailer_args.nr) {
     ++         static struct child_process run_trailer = CHILD_PROCESS_INIT;
I didn't suggest using "static" here.
quoted
     ++
      +         strvec_pushl(&run_trailer.args, "interpret-trailers",
Thanks for working on this!
Well, I was too eager to submit it.
There are many rules for git projects to explore.
Thanks.

[PATCH v7] [GSOC] commit: add --trailer option

From: ZheNing Hu via GitGitGadget <hidden>
Date: 2021-03-15 09:09:00

From: ZheNing Hu <redacted>

Historically, Git has supported the 'Signed-off-by' commit trailer
using the '--signoff' and the '-s' option from the command line.
But users may need to provide other trailer information from the
command line such as "Helped-by", "Reported-by", "Mentored-by",

Now implement a new `--trailer <token>[(=|:)<value>]` option to pass
other trailers to `interpret-trailers` and insert them into commit
messages.

Signed-off-by: ZheNing Hu <redacted>
---
    [GSOC] commit: add --trailer option
    
    Now maintainers or developers can also use commit
    --trailer="Signed-off-by:commiter<email>" from the command line to
    provide trailers to commit messages. This solution may be more
    generalized than v1.

Published-As: https://github.com/gitgitgadget/git/releases/tag/pr-901%2Fadlternative%2Fcommit-with-multiple-signatures-v7
Fetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-901/adlternative/commit-with-multiple-signatures-v7
Pull-Request: https://github.com/gitgitgadget/git/pull/901

Range-diff vs v6:

 1:  c99ce75da792 ! 1:  3ce8e8cac24a [GSOC] commit: add --trailer option
     @@ builtin/commit.c: static int config_commit_verbose = -1; /* unspecified */
       static int no_post_rewrite, allow_empty_message, pathspec_file_nul;
       static char *untracked_files_arg, *force_date, *ignore_submodule_arg, *ignored_arg;
       static char *sign_commit, *pathspec_from_file;
     -+struct strvec trailer_args = STRVEC_INIT;
     ++static struct strvec trailer_args = STRVEC_INIT;
       
       /*
        * The default commit message cleanup mode will remove the lines
     @@ builtin/commit.c: static int prepare_to_commit(const char *index_file, const cha
       	fclose(s->fp);
       
      +	if (trailer_args.nr) {
     -+		static struct child_process run_trailer = CHILD_PROCESS_INIT;
     ++		struct child_process run_trailer = CHILD_PROCESS_INIT;
      +
      +		strvec_pushl(&run_trailer.args, "interpret-trailers",
      +			     "--in-place", "--where=end", git_path_commit_editmsg(), NULL);


 Documentation/git-commit.txt |  9 ++++++++-
 builtin/commit.c             | 22 ++++++++++++++++++++++
 t/t7502-commit-porcelain.sh  | 20 ++++++++++++++++++++
 3 files changed, 50 insertions(+), 1 deletion(-)
diff --git a/Documentation/git-commit.txt b/Documentation/git-commit.txt
index 17150fa7eabe..73a7507db47f 100644
--- a/Documentation/git-commit.txt
+++ b/Documentation/git-commit.txt
@@ -14,7 +14,7 @@ SYNOPSIS
 	   [--allow-empty-message] [--no-verify] [-e] [--author=<author>]
 	   [--date=<date>] [--cleanup=<mode>] [--[no-]status]
 	   [-i | -o] [--pathspec-from-file=<file> [--pathspec-file-nul]]
-	   [-S[<keyid>]] [--] [<pathspec>...]
+	   [-S[<keyid>]] [--] [<pathspec>...] [(--trailer <token>[(=|:)<value>])...]
 
 DESCRIPTION
 -----------
@@ -166,6 +166,13 @@ The `-m` option is mutually exclusive with `-c`, `-C`, and `-F`.
 
 include::signoff-option.txt[]
 
+--trailer <token>[(=|:)<value>]::
+	Specify a (<token>, <value>) pair that should be applied as a
+	trailer. (e.g. `git commit --trailer "Signed-off-by:C O Mitter \
+	<committer@example.com>" --trailer "Helped-by:C O Mitter \
+	<committer@example.com>"` will add the "Signed-off" trailer
+	and the "Helped-by" trailer in the commit message.)
+
 -n::
 --no-verify::
 	This option bypasses the pre-commit and commit-msg hooks.
diff --git a/builtin/commit.c b/builtin/commit.c
index 739110c5a7f6..0ddd1891f71c 100644
--- a/builtin/commit.c
+++ b/builtin/commit.c
@@ -113,6 +113,7 @@ static int config_commit_verbose = -1; /* unspecified */
 static int no_post_rewrite, allow_empty_message, pathspec_file_nul;
 static char *untracked_files_arg, *force_date, *ignore_submodule_arg, *ignored_arg;
 static char *sign_commit, *pathspec_from_file;
+static struct strvec trailer_args = STRVEC_INIT;
 
 /*
  * The default commit message cleanup mode will remove the lines
@@ -131,6 +132,14 @@ static struct strbuf message = STRBUF_INIT;
 
 static enum wt_status_format status_format = STATUS_FORMAT_UNSPECIFIED;
 
+static int opt_pass_trailer(const struct option *opt, const char *arg, int unset)
+{
+	BUG_ON_OPT_NEG(unset);
+
+	strvec_pushl(&trailer_args, "--trailer", arg, NULL);
+	return 0;
+}
+
 static int opt_parse_porcelain(const struct option *opt, const char *arg, int unset)
 {
 	enum wt_status_format *value = (enum wt_status_format *)opt->value;
@@ -958,6 +967,18 @@ static int prepare_to_commit(const char *index_file, const char *prefix,
 
 	fclose(s->fp);
 
+	if (trailer_args.nr) {
+		struct child_process run_trailer = CHILD_PROCESS_INIT;
+
+		strvec_pushl(&run_trailer.args, "interpret-trailers",
+			     "--in-place", "--where=end", git_path_commit_editmsg(), NULL);
+		strvec_pushv(&run_trailer.args, trailer_args.v);
+		run_trailer.git_cmd = 1;
+		if (run_command(&run_trailer))
+			strvec_clear(&run_trailer.args);
+		strvec_clear(&trailer_args);
+	}
+
 	/*
 	 * Reject an attempt to record a non-merge empty commit without
 	 * explicit --allow-empty. In the cherry-pick case, it may be
@@ -1507,6 +1528,7 @@ int cmd_commit(int argc, const char **argv, const char *prefix)
 		OPT_STRING(0, "fixup", &fixup_message, N_("commit"), N_("use autosquash formatted message to fixup specified commit")),
 		OPT_STRING(0, "squash", &squash_message, N_("commit"), N_("use autosquash formatted message to squash specified commit")),
 		OPT_BOOL(0, "reset-author", &renew_authorship, N_("the commit is authored by me now (used with -C/-c/--amend)")),
+		OPT_CALLBACK_F(0, "trailer", NULL, N_("trailer"), N_("trailer(s) to add"), PARSE_OPT_NONEG, opt_pass_trailer),
 		OPT_BOOL('s', "signoff", &signoff, N_("add a Signed-off-by trailer")),
 		OPT_FILENAME('t', "template", &template_file, N_("use specified template file")),
 		OPT_BOOL('e', "edit", &edit_flag, N_("force edit of commit")),
diff --git a/t/t7502-commit-porcelain.sh b/t/t7502-commit-porcelain.sh
index 6396897cc818..0acf23799931 100755
--- a/t/t7502-commit-porcelain.sh
+++ b/t/t7502-commit-porcelain.sh
@@ -154,6 +154,26 @@ test_expect_success 'sign off' '
 
 '
 
+test_expect_success 'trailer' '
+	>file1 &&
+	git add file1 &&
+	git commit -s --trailer "Signed-off-by:C O Mitter1 <committer1@example.com>" \
+		--trailer "Helped-by:C O Mitter2 <committer2@example.com>"  \
+		--trailer "Reported-by:C O Mitter3 <committer3@example.com>" \
+		--trailer "Mentored-by:C O Mitter4 <committer4@example.com>" \
+		-m "hello" &&
+	git cat-file commit HEAD >commit.msg &&
+	sed -e "1,7d" commit.msg >actual &&
+	cat >expected <<-\EOF &&
+	Signed-off-by: C O Mitter <committer@example.com>
+	Signed-off-by: C O Mitter1 <committer1@example.com>
+	Helped-by: C O Mitter2 <committer2@example.com>
+	Reported-by: C O Mitter3 <committer3@example.com>
+	Mentored-by: C O Mitter4 <committer4@example.com>
+	EOF
+	test_cmp expected actual
+'
+
 test_expect_success 'multiple -m' '
 
 	>negative &&
base-commit: 13d7ab6b5d7929825b626f050b62a11241ea4945
-- 
gitgitgadget

Re: [PATCH v7] [GSOC] commit: add --trailer option

From: Christian Couder <hidden>
Date: 2021-03-15 10:01:41

On Mon, Mar 15, 2021 at 10:08 AM ZheNing Hu via GitGitGadget
[off-list ref] wrote:
quoted hunk
@@ -166,6 +166,13 @@ The `-m` option is mutually exclusive with `-c`, `-C`, and `-F`.

 include::signoff-option.txt[]

+--trailer <token>[(=|:)<value>]::
+       Specify a (<token>, <value>) pair that should be applied as a
+       trailer. (e.g. `git commit --trailer "Signed-off-by:C O Mitter \
+       <committer@example.com>" --trailer "Helped-by:C O Mitter \
+       <committer@example.com>"` will add the "Signed-off" trailer
s/"Signed-off"/"Signed-off-by"/
+       and the "Helped-by" trailer in the commit message.)
It might be useful to point users to git interpret-trailers'
documentation, for example by adding: "See
linkgit:git-interpret-trailers[1]."

Otherwise, this looks good to me!

Re: [PATCH v7] [GSOC] commit: add --trailer option

From: Christian Couder <hidden>
Date: 2021-03-15 10:15:09

On Mon, Mar 15, 2021 at 10:08 AM ZheNing Hu via GitGitGadget
[off-list ref] wrote:
      + if (trailer_args.nr) {
     -+         static struct child_process run_trailer = CHILD_PROCESS_INIT;
     ++         struct child_process run_trailer = CHILD_PROCESS_INIT;
      +
      +         strvec_pushl(&run_trailer.args, "interpret-trailers",
      +                      "--in-place", "--where=end", git_path_commit_editmsg(), NULL);
Actually I don't think "--where=end" should be used here. "end" is the
default for the "trailer.where" config variable, so by default if
nothing has been configured, it will work as if "--where=end" was
passed above.

If a user has configured "trailer.where" or trailer.<token>.where,
then this should be respected. And users should be able to override
such config variable using for example:

git -c trailer.where=start commit --trailer "Signed-off-by:C O Mitter
[off-list ref]"

Re: [PATCH v7] [GSOC] commit: add --trailer option

From: ZheNing Hu <hidden>
Date: 2021-03-15 11:33:36

Christian Couder [off-list ref] 于2021年3月15日周一 下午6:14写道:
On Mon, Mar 15, 2021 at 10:08 AM ZheNing Hu via GitGitGadget
[off-list ref] wrote:
quoted
      + if (trailer_args.nr) {
     -+         static struct child_process run_trailer = CHILD_PROCESS_INIT;
     ++         struct child_process run_trailer = CHILD_PROCESS_INIT;
      +
      +         strvec_pushl(&run_trailer.args, "interpret-trailers",
      +                      "--in-place", "--where=end", git_path_commit_editmsg(), NULL);
Actually I don't think "--where=end" should be used here. "end" is the
default for the "trailer.where" config variable, so by default if
nothing has been configured, it will work as if "--where=end" was
passed above.

If a user has configured "trailer.where" or trailer.<token>.where,
then this should be respected. And users should be able to override
such config variable using for example:

git -c trailer.where=start commit --trailer "Signed-off-by:C O Mitter
[off-list ref]"
Thanks for reminding, generally speaking, we will put the trailer at the
end of the commit messages.Take trailers in start, this should be
something I haven't considered.

I notice another question:
if we commit this again with same trailer (even same email or same commiter)
`--trailer` will not work again, because in `interpret_trailers`,
"if-exists" default
set to "addIfDifferentNeighbor", I addvice enforce use "if-exists="add".

Thanks.

[PATCH v8 0/2] [GSOC] commit: add --trailer option

From: ZheNing Hu via GitGitGadget <hidden>
Date: 2021-03-15 13:08:24

Now maintainers or developers can also use commit
--trailer="Signed-off-by:commiter<email>" from the command line to provide
trailers to commit messages. This solution may be more generalized than v1.

ZheNing Hu (2):
  [GSOC] commit: add --trailer option
  interpret_trailers: for three options parse add warning

 Documentation/git-commit.txt |  9 ++++++++-
 builtin/commit.c             | 23 +++++++++++++++++++++++
 builtin/interpret-trailers.c | 25 ++++++++++++++++++++++---
 t/t7502-commit-porcelain.sh  | 20 ++++++++++++++++++++
 4 files changed, 73 insertions(+), 4 deletions(-)


base-commit: 13d7ab6b5d7929825b626f050b62a11241ea4945
Published-As: https://github.com/gitgitgadget/git/releases/tag/pr-901%2Fadlternative%2Fcommit-with-multiple-signatures-v8
Fetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-901/adlternative/commit-with-multiple-signatures-v8
Pull-Request: https://github.com/gitgitgadget/git/pull/901

Range-diff vs v7:

 1:  3ce8e8cac24a ! 1:  f81b6e66a6ba [GSOC] commit: add --trailer option
     @@ Documentation/git-commit.txt: The `-m` option is mutually exclusive with `-c`, `
      +	Specify a (<token>, <value>) pair that should be applied as a
      +	trailer. (e.g. `git commit --trailer "Signed-off-by:C O Mitter \
      +	[off-list ref]" --trailer "Helped-by:C O Mitter \
     -+	[off-list ref]"` will add the "Signed-off" trailer
     ++	[off-list ref]"` will add the "Signed-off-by" trailer
      +	and the "Helped-by" trailer in the commit message.)
     -+
     ++	See linkgit:git-interpret-trailers[1] for details.
       -n::
       --no-verify::
       	This option bypasses the pre-commit and commit-msg hooks.
     @@ builtin/commit.c: static int prepare_to_commit(const char *index_file, const cha
      +		struct child_process run_trailer = CHILD_PROCESS_INIT;
      +
      +		strvec_pushl(&run_trailer.args, "interpret-trailers",
     -+			     "--in-place", "--where=end", git_path_commit_editmsg(), NULL);
     ++			     "--in-place", "--if-exists=add",
     ++			     git_path_commit_editmsg(), NULL);
      +		strvec_pushv(&run_trailer.args, trailer_args.v);
      +		run_trailer.git_cmd = 1;
      +		if (run_command(&run_trailer))
 -:  ------------ > 2:  68e0bd9e2d6f interpret_trailers: for three options parse add warning

-- 
gitgitgadget

[PATCH v8 1/2] [GSOC] commit: add --trailer option

From: ZheNing Hu via GitGitGadget <hidden>
Date: 2021-03-15 13:08:24

From: ZheNing Hu <redacted>

Historically, Git has supported the 'Signed-off-by' commit trailer
using the '--signoff' and the '-s' option from the command line.
But users may need to provide other trailer information from the
command line such as "Helped-by", "Reported-by", "Mentored-by",

Now implement a new `--trailer <token>[(=|:)<value>]` option to pass
other trailers to `interpret-trailers` and insert them into commit
messages.

Signed-off-by: ZheNing Hu <redacted>
---
 Documentation/git-commit.txt |  9 ++++++++-
 builtin/commit.c             | 23 +++++++++++++++++++++++
 t/t7502-commit-porcelain.sh  | 20 ++++++++++++++++++++
 3 files changed, 51 insertions(+), 1 deletion(-)
diff --git a/Documentation/git-commit.txt b/Documentation/git-commit.txt
index 17150fa7eabe..f74d320f0c96 100644
--- a/Documentation/git-commit.txt
+++ b/Documentation/git-commit.txt
@@ -14,7 +14,7 @@ SYNOPSIS
 	   [--allow-empty-message] [--no-verify] [-e] [--author=<author>]
 	   [--date=<date>] [--cleanup=<mode>] [--[no-]status]
 	   [-i | -o] [--pathspec-from-file=<file> [--pathspec-file-nul]]
-	   [-S[<keyid>]] [--] [<pathspec>...]
+	   [-S[<keyid>]] [--] [<pathspec>...] [(--trailer <token>[(=|:)<value>])...]
 
 DESCRIPTION
 -----------
@@ -166,6 +166,13 @@ The `-m` option is mutually exclusive with `-c`, `-C`, and `-F`.
 
 include::signoff-option.txt[]
 
+--trailer <token>[(=|:)<value>]::
+	Specify a (<token>, <value>) pair that should be applied as a
+	trailer. (e.g. `git commit --trailer "Signed-off-by:C O Mitter \
+	<committer@example.com>" --trailer "Helped-by:C O Mitter \
+	<committer@example.com>"` will add the "Signed-off-by" trailer
+	and the "Helped-by" trailer in the commit message.)
+	See linkgit:git-interpret-trailers[1] for details.
 -n::
 --no-verify::
 	This option bypasses the pre-commit and commit-msg hooks.
diff --git a/builtin/commit.c b/builtin/commit.c
index 739110c5a7f6..250e93d977df 100644
--- a/builtin/commit.c
+++ b/builtin/commit.c
@@ -113,6 +113,7 @@ static int config_commit_verbose = -1; /* unspecified */
 static int no_post_rewrite, allow_empty_message, pathspec_file_nul;
 static char *untracked_files_arg, *force_date, *ignore_submodule_arg, *ignored_arg;
 static char *sign_commit, *pathspec_from_file;
+static struct strvec trailer_args = STRVEC_INIT;
 
 /*
  * The default commit message cleanup mode will remove the lines
@@ -131,6 +132,14 @@ static struct strbuf message = STRBUF_INIT;
 
 static enum wt_status_format status_format = STATUS_FORMAT_UNSPECIFIED;
 
+static int opt_pass_trailer(const struct option *opt, const char *arg, int unset)
+{
+	BUG_ON_OPT_NEG(unset);
+
+	strvec_pushl(&trailer_args, "--trailer", arg, NULL);
+	return 0;
+}
+
 static int opt_parse_porcelain(const struct option *opt, const char *arg, int unset)
 {
 	enum wt_status_format *value = (enum wt_status_format *)opt->value;
@@ -958,6 +967,19 @@ static int prepare_to_commit(const char *index_file, const char *prefix,
 
 	fclose(s->fp);
 
+	if (trailer_args.nr) {
+		struct child_process run_trailer = CHILD_PROCESS_INIT;
+
+		strvec_pushl(&run_trailer.args, "interpret-trailers",
+			     "--in-place", "--if-exists=add",
+			     git_path_commit_editmsg(), NULL);
+		strvec_pushv(&run_trailer.args, trailer_args.v);
+		run_trailer.git_cmd = 1;
+		if (run_command(&run_trailer))
+			strvec_clear(&run_trailer.args);
+		strvec_clear(&trailer_args);
+	}
+
 	/*
 	 * Reject an attempt to record a non-merge empty commit without
 	 * explicit --allow-empty. In the cherry-pick case, it may be
@@ -1507,6 +1529,7 @@ int cmd_commit(int argc, const char **argv, const char *prefix)
 		OPT_STRING(0, "fixup", &fixup_message, N_("commit"), N_("use autosquash formatted message to fixup specified commit")),
 		OPT_STRING(0, "squash", &squash_message, N_("commit"), N_("use autosquash formatted message to squash specified commit")),
 		OPT_BOOL(0, "reset-author", &renew_authorship, N_("the commit is authored by me now (used with -C/-c/--amend)")),
+		OPT_CALLBACK_F(0, "trailer", NULL, N_("trailer"), N_("trailer(s) to add"), PARSE_OPT_NONEG, opt_pass_trailer),
 		OPT_BOOL('s', "signoff", &signoff, N_("add a Signed-off-by trailer")),
 		OPT_FILENAME('t', "template", &template_file, N_("use specified template file")),
 		OPT_BOOL('e', "edit", &edit_flag, N_("force edit of commit")),
diff --git a/t/t7502-commit-porcelain.sh b/t/t7502-commit-porcelain.sh
index 6396897cc818..0acf23799931 100755
--- a/t/t7502-commit-porcelain.sh
+++ b/t/t7502-commit-porcelain.sh
@@ -154,6 +154,26 @@ test_expect_success 'sign off' '
 
 '
 
+test_expect_success 'trailer' '
+	>file1 &&
+	git add file1 &&
+	git commit -s --trailer "Signed-off-by:C O Mitter1 <committer1@example.com>" \
+		--trailer "Helped-by:C O Mitter2 <committer2@example.com>"  \
+		--trailer "Reported-by:C O Mitter3 <committer3@example.com>" \
+		--trailer "Mentored-by:C O Mitter4 <committer4@example.com>" \
+		-m "hello" &&
+	git cat-file commit HEAD >commit.msg &&
+	sed -e "1,7d" commit.msg >actual &&
+	cat >expected <<-\EOF &&
+	Signed-off-by: C O Mitter <committer@example.com>
+	Signed-off-by: C O Mitter1 <committer1@example.com>
+	Helped-by: C O Mitter2 <committer2@example.com>
+	Reported-by: C O Mitter3 <committer3@example.com>
+	Mentored-by: C O Mitter4 <committer4@example.com>
+	EOF
+	test_cmp expected actual
+'
+
 test_expect_success 'multiple -m' '
 
 	>negative &&
-- 
gitgitgadget

[PATCH v8 2/2] interpret_trailers: for three options parse add warning

From: ZheNing Hu via GitGitGadget <hidden>
Date: 2021-03-15 13:08:25

From: ZheNing Hu <redacted>

When using `interpret-trailers`, if user accidentally fill in the
wrong option values (e.g. `--if-exists=addd` or `--where=begin` or
`--if-missing=do-nothing`), git will exit quietly, and the user may
not even know what happened.

So lets provides warnings when git parsing this three options:
"unknown value '%s' for key ('where'|'--if-exist'|'--if-missing')".
This will remind the user :"Oh, it was because of my spelling error."
(or using a synonym for error).

Signed-off-by: ZheNing Hu <redacted>
---
 builtin/interpret-trailers.c | 25 ++++++++++++++++++++++---
 1 file changed, 22 insertions(+), 3 deletions(-)
diff --git a/builtin/interpret-trailers.c b/builtin/interpret-trailers.c
index 84748eafc01b..6d8e86a44d43 100644
--- a/builtin/interpret-trailers.c
+++ b/builtin/interpret-trailers.c
@@ -24,19 +24,38 @@ static enum trailer_if_missing if_missing;
 static int option_parse_where(const struct option *opt,
 			      const char *arg, int unset)
 {
-	return trailer_set_where(&where, arg);
+	int ret;
+
+	ret = trailer_set_where(&where, arg);
+	if (ret)
+		warning(_("unknown value '%s' for key 'where'"),
+			arg);
+	return ret;
+
 }
 
 static int option_parse_if_exists(const struct option *opt,
 				  const char *arg, int unset)
 {
-	return trailer_set_if_exists(&if_exists, arg);
+	int ret;
+
+	ret = trailer_set_if_exists(&if_exists, arg);
+	if (ret)
+		warning(_("unknown value '%s' for key 'if_exists'"),
+			arg);
+	return ret;
 }
 
 static int option_parse_if_missing(const struct option *opt,
 				   const char *arg, int unset)
 {
-	return trailer_set_if_missing(&if_missing, arg);
+	int ret;
+
+	ret = trailer_set_if_missing(&if_missing, arg);
+	if (ret)
+		warning(_("unknown value '%s' for key 'if_missing'"),
+			arg);
+	return ret;
 }
 
 static void new_trailers_clear(struct list_head *trailers)
-- 
gitgitgadget

Re: [PATCH v7] [GSOC] commit: add --trailer option

From: Christian Couder <hidden>
Date: 2021-03-16 05:38:47

On Mon, Mar 15, 2021 at 12:32 PM ZheNing Hu [off-list ref] wrote:
Christian Couder [off-list ref] 于2021年3月15日周一 下午6:14写道:
quoted
On Mon, Mar 15, 2021 at 10:08 AM ZheNing Hu via GitGitGadget
[off-list ref] wrote:
quoted
      + if (trailer_args.nr) {
     -+         static struct child_process run_trailer = CHILD_PROCESS_INIT;
     ++         struct child_process run_trailer = CHILD_PROCESS_INIT;
      +
      +         strvec_pushl(&run_trailer.args, "interpret-trailers",
      +                      "--in-place", "--where=end", git_path_commit_editmsg(), NULL);
Actually I don't think "--where=end" should be used here. "end" is the
default for the "trailer.where" config variable, so by default if
nothing has been configured, it will work as if "--where=end" was
passed above.

If a user has configured "trailer.where" or trailer.<token>.where,
then this should be respected. And users should be able to override
such config variable using for example:

git -c trailer.where=start commit --trailer "Signed-off-by:C O Mitter
[off-list ref]"
Thanks for reminding, generally speaking, we will put the trailer at the
end of the commit messages.Take trailers in start, this should be
something I haven't considered.
In general what I want to say is that `git interpret-trailers` should
be considered to have sensible defaults, that can possibly be
overridden using a number of config variables (or the git -c ...
mechanism) which is a good thing. If something in it doesn't work
well, it's possible to improve it of course. Otherwise it's better to
just fully take advantage of it.
I notice another question:
if we commit this again with same trailer (even same email or same commiter)
`--trailer` will not work again, because in `interpret_trailers`,
"if-exists" default
set to "addIfDifferentNeighbor", I addvice enforce use "if-exists="add".
I don't agree with using "--if-exists=add". I think the default to not
add a trailer line if it would be just above or below the same line is
better, as doing that wouldn't add much information. It's better to
encourage people to use trailers in a meaningful way.

And again if we use "--if-exists=add", then people who would want to
take advantage of `git interpret-trailers` to customize what happens
when the trailer already exists would not be able to do it.

For example if we don't use "--if-exists=add", then:

- people who want to customize what happens when the trailer already
exists can do it with for example:

git -c trailer.ifexists=addIfDifferent commit --trailer
"Signed-off-by:C O Mitter [off-list ref]"

- which means that people who want the "--if-exists=add" behavior can
still have it with:

git -c trailer.ifexists=add commit --trailer "Signed-off-by:C O Mitter
[off-list ref]"

While if we use "--if-exists=add", then using `git -c
trailer.ifexists=... commit ...` will not customize anything as the
"--if-exists=add" command line option will override any config
customization.

Re: [PATCH v8 2/2] interpret_trailers: for three options parse add warning

From: Christian Couder <hidden>
Date: 2021-03-16 05:54:16

On Mon, Mar 15, 2021 at 2:07 PM ZheNing Hu via GitGitGadget
[off-list ref] wrote:
From: ZheNing Hu <redacted>

When using `interpret-trailers`, if user accidentally fill in the
wrong option values (e.g. `--if-exists=addd` or `--where=begin` or
`--if-missing=do-nothing`), git will exit quietly, and the user may
not even know what happened.

So lets provides warnings when git parsing this three options:
"unknown value '%s' for key ('where'|'--if-exist'|'--if-missing')".
This will remind the user :"Oh, it was because of my spelling error."
(or using a synonym for error).
I am not against this, but I just want to say that when I previously
worked on `interpret-trailers` I think I implemented or suggested such
warnings, but they were rejected.

I think the reason they were rejected was to improve compatibility
with future versions of Git where more options would be implemented.

For example if in a few years someone implements `--where=middle` and
some people use it in a script like this:

git interpret-trailers --where=middle --trailer foo=bar

Then when such a script would be used with a recent version of Git it
would work well, while if it would be used with an old version of Git
it would emit warnings. And these warnings might actually be more
annoying than the fact that the trailer is not put in the middle.

I might be wrong and there might have been other reasons though. Also
things might have changed since that time, as not many options if any
have been added since then.

Re: [PATCH v7] [GSOC] commit: add --trailer option

From: ZheNing Hu <hidden>
Date: 2021-03-16 08:36:31

Christian Couder [off-list ref] 于2021年3月16日周二 下午1:37写道:
quoted
Thanks for reminding, generally speaking, we will put the trailer at the
end of the commit messages.Take trailers in start, this should be
something I haven't considered.
In general what I want to say is that `git interpret-trailers` should
be considered to have sensible defaults, that can possibly be
overridden using a number of config variables (or the git -c ...
mechanism) which is a good thing. If something in it doesn't work
well, it's possible to improve it of course. Otherwise it's better to
just fully take advantage of it.
quoted
I notice another question:
if we commit this again with same trailer (even same email or same commiter)
`--trailer` will not work again, because in `interpret_trailers`,
"if-exists" default
set to "addIfDifferentNeighbor", I addvice enforce use "if-exists="add".
I don't agree with using "--if-exists=add". I think the default to not
add a trailer line if it would be just above or below the same line is
better, as doing that wouldn't add much information. It's better to
encourage people to use trailers in a meaningful way.

And again if we use "--if-exists=add", then people who would want to
take advantage of `git interpret-trailers` to customize what happens
when the trailer already exists would not be able to do it.

For example if we don't use "--if-exists=add", then:

- people who want to customize what happens when the trailer already
exists can do it with for example:

git -c trailer.ifexists=addIfDifferent commit --trailer
"Signed-off-by:C O Mitter [off-list ref]"

- which means that people who want the "--if-exists=add" behavior can
still have it with:

git -c trailer.ifexists=add commit --trailer "Signed-off-by:C O Mitter
[off-list ref]"

While if we use "--if-exists=add", then using `git -c
trailer.ifexists=... commit ...` will not customize anything as the
"--if-exists=add" command line option will override any config
customization.
Well, I see what you mean, this will keep git better flexibility,
give users more personalized configuration options.
And this should be more in line with git design philosophy,
I will follow your suggestion.

Re: [PATCH v8 2/2] interpret_trailers: for three options parse add warning

From: ZheNing Hu <hidden>
Date: 2021-03-16 09:13:51

Christian Couder [off-list ref] 于2021年3月16日周二 下午1:53写道:
I am not against this, but I just want to say that when I previously
worked on `interpret-trailers` I think I implemented or suggested such
warnings, but they were rejected.

I think the reason they were rejected was to improve compatibility
with future versions of Git where more options would be implemented.

For example if in a few years someone implements `--where=middle` and
some people use it in a script like this:

git interpret-trailers --where=middle --trailer foo=bar

Then when such a script would be used with a recent version of Git it
would work well, while if it would be used with an old version of Git
it would emit warnings. And these warnings might actually be more
annoying than the fact that the trailer is not put in the middle.
Well, this is indeed a situation I did not foresee. As a user, I don't see an
error | warning reminder which may make me a little confused.

At the same time, some sub-command like `git cat-file`, If user give a wrong
format:

$  git cat-file  --batch-check="%(deltabases)"

git will tell us, "fatal: unknown format element: deltabases".
It's easy for user to check.
I might be wrong and there might have been other reasons though. Also
things might have changed since that time, as not many options if any
have been added since then.
Thanks for your patiently explanation.
If these warnings are really unnecessary, I will drop this patch.

[PATCH v9] [GSOC] commit: add --trailer option

From: ZheNing Hu via GitGitGadget <hidden>
Date: 2021-03-16 10:40:37

From: ZheNing Hu <redacted>

Historically, Git has supported the 'Signed-off-by' commit trailer
using the '--signoff' and the '-s' option from the command line.
But users may need to provide other trailer information from the
command line such as "Helped-by", "Reported-by", "Mentored-by",

Now implement a new `--trailer <token>[(=|:)<value>]` option to pass
other trailers to `interpret-trailers` and insert them into commit
messages.

Signed-off-by: ZheNing Hu <redacted>
---
    [GSOC] commit: add --trailer option
    
    Now maintainers or developers can also use commit
    --trailer="Signed-off-by:commiter<email>" from the command line to
    provide trailers to commit messages. This solution may be more
    generalized than v1.

Published-As: https://github.com/gitgitgadget/git/releases/tag/pr-901%2Fadlternative%2Fcommit-with-multiple-signatures-v9
Fetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-901/adlternative/commit-with-multiple-signatures-v9
Pull-Request: https://github.com/gitgitgadget/git/pull/901

Range-diff vs v8:

 1:  f81b6e66a6ba ! 1:  e524c4ba5dc1 [GSOC] commit: add --trailer option
     @@ builtin/commit.c: static int prepare_to_commit(const char *index_file, const cha
      +		struct child_process run_trailer = CHILD_PROCESS_INIT;
      +
      +		strvec_pushl(&run_trailer.args, "interpret-trailers",
     -+			     "--in-place", "--if-exists=add",
     -+			     git_path_commit_editmsg(), NULL);
     ++			     "--in-place", git_path_commit_editmsg(), NULL);
      +		strvec_pushv(&run_trailer.args, trailer_args.v);
      +		run_trailer.git_cmd = 1;
      +		if (run_command(&run_trailer))
 2:  68e0bd9e2d6f < -:  ------------ interpret_trailers: for three options parse add warning


 Documentation/git-commit.txt |  9 ++++++++-
 builtin/commit.c             | 22 ++++++++++++++++++++++
 t/t7502-commit-porcelain.sh  | 20 ++++++++++++++++++++
 3 files changed, 50 insertions(+), 1 deletion(-)
diff --git a/Documentation/git-commit.txt b/Documentation/git-commit.txt
index 17150fa7eabe..f74d320f0c96 100644
--- a/Documentation/git-commit.txt
+++ b/Documentation/git-commit.txt
@@ -14,7 +14,7 @@ SYNOPSIS
 	   [--allow-empty-message] [--no-verify] [-e] [--author=<author>]
 	   [--date=<date>] [--cleanup=<mode>] [--[no-]status]
 	   [-i | -o] [--pathspec-from-file=<file> [--pathspec-file-nul]]
-	   [-S[<keyid>]] [--] [<pathspec>...]
+	   [-S[<keyid>]] [--] [<pathspec>...] [(--trailer <token>[(=|:)<value>])...]
 
 DESCRIPTION
 -----------
@@ -166,6 +166,13 @@ The `-m` option is mutually exclusive with `-c`, `-C`, and `-F`.
 
 include::signoff-option.txt[]
 
+--trailer <token>[(=|:)<value>]::
+	Specify a (<token>, <value>) pair that should be applied as a
+	trailer. (e.g. `git commit --trailer "Signed-off-by:C O Mitter \
+	<committer@example.com>" --trailer "Helped-by:C O Mitter \
+	<committer@example.com>"` will add the "Signed-off-by" trailer
+	and the "Helped-by" trailer in the commit message.)
+	See linkgit:git-interpret-trailers[1] for details.
 -n::
 --no-verify::
 	This option bypasses the pre-commit and commit-msg hooks.
diff --git a/builtin/commit.c b/builtin/commit.c
index 739110c5a7f6..a002330d9d56 100644
--- a/builtin/commit.c
+++ b/builtin/commit.c
@@ -113,6 +113,7 @@ static int config_commit_verbose = -1; /* unspecified */
 static int no_post_rewrite, allow_empty_message, pathspec_file_nul;
 static char *untracked_files_arg, *force_date, *ignore_submodule_arg, *ignored_arg;
 static char *sign_commit, *pathspec_from_file;
+static struct strvec trailer_args = STRVEC_INIT;
 
 /*
  * The default commit message cleanup mode will remove the lines
@@ -131,6 +132,14 @@ static struct strbuf message = STRBUF_INIT;
 
 static enum wt_status_format status_format = STATUS_FORMAT_UNSPECIFIED;
 
+static int opt_pass_trailer(const struct option *opt, const char *arg, int unset)
+{
+	BUG_ON_OPT_NEG(unset);
+
+	strvec_pushl(&trailer_args, "--trailer", arg, NULL);
+	return 0;
+}
+
 static int opt_parse_porcelain(const struct option *opt, const char *arg, int unset)
 {
 	enum wt_status_format *value = (enum wt_status_format *)opt->value;
@@ -958,6 +967,18 @@ static int prepare_to_commit(const char *index_file, const char *prefix,
 
 	fclose(s->fp);
 
+	if (trailer_args.nr) {
+		struct child_process run_trailer = CHILD_PROCESS_INIT;
+
+		strvec_pushl(&run_trailer.args, "interpret-trailers",
+			     "--in-place", git_path_commit_editmsg(), NULL);
+		strvec_pushv(&run_trailer.args, trailer_args.v);
+		run_trailer.git_cmd = 1;
+		if (run_command(&run_trailer))
+			strvec_clear(&run_trailer.args);
+		strvec_clear(&trailer_args);
+	}
+
 	/*
 	 * Reject an attempt to record a non-merge empty commit without
 	 * explicit --allow-empty. In the cherry-pick case, it may be
@@ -1507,6 +1528,7 @@ int cmd_commit(int argc, const char **argv, const char *prefix)
 		OPT_STRING(0, "fixup", &fixup_message, N_("commit"), N_("use autosquash formatted message to fixup specified commit")),
 		OPT_STRING(0, "squash", &squash_message, N_("commit"), N_("use autosquash formatted message to squash specified commit")),
 		OPT_BOOL(0, "reset-author", &renew_authorship, N_("the commit is authored by me now (used with -C/-c/--amend)")),
+		OPT_CALLBACK_F(0, "trailer", NULL, N_("trailer"), N_("trailer(s) to add"), PARSE_OPT_NONEG, opt_pass_trailer),
 		OPT_BOOL('s', "signoff", &signoff, N_("add a Signed-off-by trailer")),
 		OPT_FILENAME('t', "template", &template_file, N_("use specified template file")),
 		OPT_BOOL('e', "edit", &edit_flag, N_("force edit of commit")),
diff --git a/t/t7502-commit-porcelain.sh b/t/t7502-commit-porcelain.sh
index 6396897cc818..0acf23799931 100755
--- a/t/t7502-commit-porcelain.sh
+++ b/t/t7502-commit-porcelain.sh
@@ -154,6 +154,26 @@ test_expect_success 'sign off' '
 
 '
 
+test_expect_success 'trailer' '
+	>file1 &&
+	git add file1 &&
+	git commit -s --trailer "Signed-off-by:C O Mitter1 <committer1@example.com>" \
+		--trailer "Helped-by:C O Mitter2 <committer2@example.com>"  \
+		--trailer "Reported-by:C O Mitter3 <committer3@example.com>" \
+		--trailer "Mentored-by:C O Mitter4 <committer4@example.com>" \
+		-m "hello" &&
+	git cat-file commit HEAD >commit.msg &&
+	sed -e "1,7d" commit.msg >actual &&
+	cat >expected <<-\EOF &&
+	Signed-off-by: C O Mitter <committer@example.com>
+	Signed-off-by: C O Mitter1 <committer1@example.com>
+	Helped-by: C O Mitter2 <committer2@example.com>
+	Reported-by: C O Mitter3 <committer3@example.com>
+	Mentored-by: C O Mitter4 <committer4@example.com>
+	EOF
+	test_cmp expected actual
+'
+
 test_expect_success 'multiple -m' '
 
 	>negative &&
base-commit: 13d7ab6b5d7929825b626f050b62a11241ea4945
-- 
gitgitgadget

Re: [PATCH v8 1/2] [GSOC] commit: add --trailer option

From: Ævar Arnfjörð Bjarmason <hidden>
Date: 2021-03-16 12:53:40

On Mon, Mar 15 2021, ZheNing Hu via GitGitGadget wrote:
From: ZheNing Hu <redacted>

Historically, Git has supported the 'Signed-off-by' commit trailer
using the '--signoff' and the '-s' option from the command line.
But users may need to provide other trailer information from the
command line such as "Helped-by", "Reported-by", "Mentored-by",

Now implement a new `--trailer <token>[(=|:)<value>]` option to pass
other trailers to `interpret-trailers` and insert them into commit
messages.
I paged through v1-v8, looking mostly good so far. Some comments:
quoted hunk
Signed-off-by: ZheNing Hu <redacted>
---
 Documentation/git-commit.txt |  9 ++++++++-
 builtin/commit.c             | 23 +++++++++++++++++++++++
 t/t7502-commit-porcelain.sh  | 20 ++++++++++++++++++++
 3 files changed, 51 insertions(+), 1 deletion(-)
diff --git a/Documentation/git-commit.txt b/Documentation/git-commit.txt
index 17150fa7eabe..f74d320f0c96 100644
--- a/Documentation/git-commit.txt
+++ b/Documentation/git-commit.txt
@@ -14,7 +14,7 @@ SYNOPSIS
 	   [--allow-empty-message] [--no-verify] [-e] [--author=<author>]
 	   [--date=<date>] [--cleanup=<mode>] [--[no-]status]
 	   [-i | -o] [--pathspec-from-file=<file> [--pathspec-file-nul]]
-	   [-S[<keyid>]] [--] [<pathspec>...]
+	   [-S[<keyid>]] [--] [<pathspec>...] [(--trailer <token>[(=|:)<value>])...]
 
 DESCRIPTION
 -----------
@@ -166,6 +166,13 @@ The `-m` option is mutually exclusive with `-c`, `-C`, and `-F`.
 
 include::signoff-option.txt[]
 
+--trailer <token>[(=|:)<value>]::
+	Specify a (<token>, <value>) pair that should be applied as a
+	trailer. (e.g. `git commit --trailer "Signed-off-by:C O Mitter \
+	<committer@example.com>" --trailer "Helped-by:C O Mitter \
+	<committer@example.com>"` will add the "Signed-off-by" trailer
+	and the "Helped-by" trailer in the commit message.)
+	See linkgit:git-interpret-trailers[1] for details.
 -n::
 --no-verify::
 	This option bypasses the pre-commit and commit-msg hooks.
diff --git a/builtin/commit.c b/builtin/commit.c
index 739110c5a7f6..250e93d977df 100644
--- a/builtin/commit.c
+++ b/builtin/commit.c
@@ -113,6 +113,7 @@ static int config_commit_verbose = -1; /* unspecified */
 static int no_post_rewrite, allow_empty_message, pathspec_file_nul;
 static char *untracked_files_arg, *force_date, *ignore_submodule_arg, *ignored_arg;
 static char *sign_commit, *pathspec_from_file;
+static struct strvec trailer_args = STRVEC_INIT;
 
 /*
  * The default commit message cleanup mode will remove the lines
@@ -131,6 +132,14 @@ static struct strbuf message = STRBUF_INIT;
 
 static enum wt_status_format status_format = STATUS_FORMAT_UNSPECIFIED;
 
+static int opt_pass_trailer(const struct option *opt, const char *arg, int unset)
+{
+	BUG_ON_OPT_NEG(unset);
+
+	strvec_pushl(&trailer_args, "--trailer", arg, NULL);
+	return 0;
+}
+
 static int opt_parse_porcelain(const struct option *opt, const char *arg, int unset)
 {
 	enum wt_status_format *value = (enum wt_status_format *)opt->value;
@@ -958,6 +967,19 @@ static int prepare_to_commit(const char *index_file, const char *prefix,
 
 	fclose(s->fp);
 
+	if (trailer_args.nr) {
+		struct child_process run_trailer = CHILD_PROCESS_INIT;
+
+		strvec_pushl(&run_trailer.args, "interpret-trailers",
+			     "--in-place", "--if-exists=add",
+			     git_path_commit_editmsg(), NULL);
+		strvec_pushv(&run_trailer.args, trailer_args.v);
+		run_trailer.git_cmd = 1;
+		if (run_command(&run_trailer))
+			strvec_clear(&run_trailer.args);
This is git-commit, shouldn't we die() here instead of ignoring errors
in sub-processes?
quoted hunk
+		strvec_clear(&trailer_args);
+	}
+
 	/*
 	 * Reject an attempt to record a non-merge empty commit without
 	 * explicit --allow-empty. In the cherry-pick case, it may be
@@ -1507,6 +1529,7 @@ int cmd_commit(int argc, const char **argv, const char *prefix)
 		OPT_STRING(0, "fixup", &fixup_message, N_("commit"), N_("use autosquash formatted message to fixup specified commit")),
 		OPT_STRING(0, "squash", &squash_message, N_("commit"), N_("use autosquash formatted message to squash specified commit")),
 		OPT_BOOL(0, "reset-author", &renew_authorship, N_("the commit is authored by me now (used with -C/-c/--amend)")),
+		OPT_CALLBACK_F(0, "trailer", NULL, N_("trailer"), N_("trailer(s) to add"), PARSE_OPT_NONEG, opt_pass_trailer),
 		OPT_BOOL('s', "signoff", &signoff, N_("add a Signed-off-by trailer")),
Not required for this change, but perhaps a change here to N_() (if we
can get it to fit) + doc update saying that we prefer
--trailer="Signed-Off-By: to --signoff"? More on that later.
quoted hunk
 		OPT_FILENAME('t', "template", &template_file, N_("use specified template file")),
 		OPT_BOOL('e', "edit", &edit_flag, N_("force edit of commit")),
diff --git a/t/t7502-commit-porcelain.sh b/t/t7502-commit-porcelain.sh
index 6396897cc818..0acf23799931 100755
--- a/t/t7502-commit-porcelain.sh
+++ b/t/t7502-commit-porcelain.sh
@@ -154,6 +154,26 @@ test_expect_success 'sign off' '
 
 '
 
+test_expect_success 'trailer' '
+	>file1 &&
+	git add file1 &&
+	git commit -s --trailer "Signed-off-by:C O Mitter1 <committer1@example.com>" \
+		--trailer "Helped-by:C O Mitter2 <committer2@example.com>"  \
+		--trailer "Reported-by:C O Mitter3 <committer3@example.com>" \
+		--trailer "Mentored-by:C O Mitter4 <committer4@example.com>" \
+		-m "hello" &&
+	git cat-file commit HEAD >commit.msg &&
+	sed -e "1,7d" commit.msg >actual &&
+	cat >expected <<-\EOF &&
+	Signed-off-by: C O Mitter <committer@example.com>
+	Signed-off-by: C O Mitter1 <committer1@example.com>
+	Helped-by: C O Mitter2 <committer2@example.com>
+	Reported-by: C O Mitter3 <committer3@example.com>
+	Mentored-by: C O Mitter4 <committer4@example.com>
+	EOF
+	test_cmp expected actual
+'
+
How does this interact with cases where the user has configured
"trailer.separators" to have a value that doesn't contain ":"?  I
haven't tested, but my reading of git-interpret-trailers(1) is that if
you supplied "=" instead that case would just work:

    By default only : is recognized as a trailer separator, except that
    = is always accepted on the command line for compatibility with
    other git commands.

I don't know if that does the right thing in the presence of
--if-exists=add.

So it would be good to update these tests so you test:

 * For the --if-exists=add case at all, there's no tests for it
   now. I.e. add some trailers manually to the commit (via -F or
   whatever) and then see if they get added to, replacet etc.

 * Ditto but for the user having configured trailer.separators (see the
   test_config helper for how to set config in a test). I.e. if it's "="
   does adding trailers work, how about if it's "=" on the CLI but the
   config/commit message has ";" instead of ":" or something?

 * Hrm, actually I think tweaking "-c trailer.ifexists" won't work at
   all, since the CLI switch would override it. I honestly don't know,
   but why not not supply it and keep the addIfDifferentNeighbor
   default?

   If it's essential that seems like a good test / documentation
   addition...

 * For the above -c ... case I can't think of a good way to deal with it
   that doesn't involve pulling in git_trailer_config() into
   git_commit_config(), but perhaps the least nasty way is to just set a
   flag in git_commit_config() if we see a "trailer.ifexists" flag, and
   if so don't provide "--if-exists=add", if there's no config (this
   will include "git -c ... commit" we set provide "--if-exists=add" )
   or as noted above, maybe we can skip the whole thing and use the
   addIfDifferentNeighbor default.

And, not needed for this patch but worth thinking about:

 * We pass through --trailer to git-interpret-trailers, what should we
   do about the other options? Should git-commit eventually support
   --trailer-where and pass it along as --where to
   git-interpret-trailers, or is "git -c trailer.where=... commit" good
   enough?

 * It would be good to test for and document if that "-c trailer.*"
   trick works (no reason it shouldn't). I.e. to add something like this
   after what you have (along with tests, and check if it's even true):

       Only the `--trailer` argument to
       linkgit:git-interpret-trailers[1] is supported. Other
       pass-through switches may be added in the future, but currently
       you'll need to pass arguments to
       linkgit:git-interpret-trailers[1] along as config, e.g. `git -c
       trailer.where=start commit [...] --trailer=[...]`.
      
 * We have a longer-term goal of having the .mailmap apply to trailers,
   it would be nice if git-interpret-trailers had some fuzzy-matching to
   check if the RHS of a trailer is a name/E-Mail pair, and if so did
   stricter validation on it with the ident functions we use for fsck
   etc. (that's copied & subtly different in several different places in
   the codebase, unfortunately[1]).

More thoughts:

 * Having written all the above I checked how --signoff is implemented.

   It seems to me to be a good idea to (at least for testing) convert
   the --signoff trailer to your implementation. We have plenty of tests
   for it, does migrating it over pass or fail those?

 * I also agree with Junio that we shouldn't have a --fixed-by or
   whatever and wouldn't add --signoff today, but it seems very useful
   to me to have a shortcut like:

       --trailer "Signed-off-by"

   I.e. omitting the value, or:

      --trailer "Signed-off-by="

   Or some other thing we deem sufficiently useful/sane
   syntax/unambiguous.n

   Then the value would be provided by fmt_name(WANT_COMMITTER_IDENT)
   just as we do in append_signoff() now. I think a *very common* case
   for this would be something like:

       git commit --amend -v --trailer "Reviewed-by"

   And it would be useful to help that along and not have to do:

       git commit --amend -v --trailer "Reviewed-by=$(git config user.name) <$(git config user.email)>"

   Or worse yet, manually typo your name/e-mail address, as I'm sure I
   and many others will inevitably do when using this option...

1. https://lore.kernel.org/git/87bld8ov9q.fsf@evledraar.gmail.com/

Re: [PATCH v8 1/2] [GSOC] commit: add --trailer option

From: ZheNing Hu <hidden>
Date: 2021-03-17 02:02:49

Ævar Arnfjörð Bjarmason [off-list ref] 于2021年3月16日周二 下午8:52写道:
quoted
+             if (run_command(&run_trailer))
+                     strvec_clear(&run_trailer.args);
This is git-commit, shouldn't we die() here instead of ignoring errors
in sub-processes?
After thinking about it carefully, your opinion is more
reasonable, because if the user uses the wrong `--trailer`
and does not get the information he needs, I think he will
have to use `--amend` to modify, and `die()` can exit
this commit directly.
quoted
+             strvec_clear(&trailer_args);
+     }
+
      /*
       * Reject an attempt to record a non-merge empty commit without
       * explicit --allow-empty. In the cherry-pick case, it may be
@@ -1507,6 +1529,7 @@ int cmd_commit(int argc, const char **argv, const char *prefix)
              OPT_STRING(0, "fixup", &fixup_message, N_("commit"), N_("use autosquash formatted message to fixup specified commit")),
              OPT_STRING(0, "squash", &squash_message, N_("commit"), N_("use autosquash formatted message to squash specified commit")),
              OPT_BOOL(0, "reset-author", &renew_authorship, N_("the commit is authored by me now (used with -C/-c/--amend)")),
+             OPT_CALLBACK_F(0, "trailer", NULL, N_("trailer"), N_("trailer(s) to add"), PARSE_OPT_NONEG, opt_pass_trailer),
              OPT_BOOL('s', "signoff", &signoff, N_("add a Signed-off-by trailer")),
Not required for this change, but perhaps a change here to N_() (if we
can get it to fit) + doc update saying that we prefer
--trailer="Signed-Off-By: to --signoff"? More on that later.
quoted
              OPT_FILENAME('t', "template", &template_file, N_("use specified template file")),
              OPT_BOOL('e', "edit", &edit_flag, N_("force edit of commit")),
diff --git a/t/t7502-commit-porcelain.sh b/t/t7502-commit-porcelain.sh
index 6396897cc818..0acf23799931 100755
--- a/t/t7502-commit-porcelain.sh
+++ b/t/t7502-commit-porcelain.sh
@@ -154,6 +154,26 @@ test_expect_success 'sign off' '

 '

+test_expect_success 'trailer' '
+     >file1 &&
+     git add file1 &&
+     git commit -s --trailer "Signed-off-by:C O Mitter1 <committer1@example.com>" \
+             --trailer "Helped-by:C O Mitter2 <committer2@example.com>"  \
+             --trailer "Reported-by:C O Mitter3 <committer3@example.com>" \
+             --trailer "Mentored-by:C O Mitter4 <committer4@example.com>" \
+             -m "hello" &&
+     git cat-file commit HEAD >commit.msg &&
+     sed -e "1,7d" commit.msg >actual &&
+     cat >expected <<-\EOF &&
+     Signed-off-by: C O Mitter <committer@example.com>
+     Signed-off-by: C O Mitter1 <committer1@example.com>
+     Helped-by: C O Mitter2 <committer2@example.com>
+     Reported-by: C O Mitter3 <committer3@example.com>
+     Mentored-by: C O Mitter4 <committer4@example.com>
+     EOF
+     test_cmp expected actual
+'
+
How does this interact with cases where the user has configured
"trailer.separators" to have a value that doesn't contain ":"?  I
haven't tested, but my reading of git-interpret-trailers(1) is that if
you supplied "=" instead that case would just work:

    By default only : is recognized as a trailer separator, except that
    = is always accepted on the command line for compatibility with
    other git commands.
But interpret_trailers interface allow us use "=" instead of other separators.

I did a simple test and modified the configuration "trailer.separators"
and it still works. Now things are good here:

$ git -c trailer.separators="@" commit --trailer="Signed-off-by=C O <email>"

or

$ git -c trailer.separators="@" commit --trailer="Signed-off-by@C O <email>"

Both can work normally,

--trailer="Signed-off-by@ C O <email>"

will output in the commit message.
I don't know if that does the right thing in the presence of
--if-exists=add.
Yesterday, Christian Couder and I had already discussed this issue:
Your idea is correct, I should not add "--if-exists = add",  this will destroy
the user's rights to configure by using `git -c trailer.if-exist`.
So it would be good to update these tests so you test:

 * For the --if-exists=add case at all, there's no tests for it
   now. I.e. add some trailers manually to the commit (via -F or
   whatever) and then see if they get added to, replacet etc.

 * Ditto but for the user having configured trailer.separators (see the
   test_config helper for how to set config in a test). I.e. if it's "="
   does adding trailers work, how about if it's "=" on the CLI but the
   config/commit message has ";" instead of ":" or something?
As mentioned above, it works normally.
 * Hrm, actually I think tweaking "-c trailer.ifexists" won't work at
   all, since the CLI switch would override it. I honestly don't know,
   but why not not supply it and keep the addIfDifferentNeighbor
   default?

   If it's essential that seems like a good test / documentation
   addition...

 * For the above -c ... case I can't think of a good way to deal with it
   that doesn't involve pulling in git_trailer_config() into
   git_commit_config(), but perhaps the least nasty way is to just set a
   flag in git_commit_config() if we see a "trailer.ifexists" flag, and
   if so don't provide "--if-exists=add", if there's no config (this
   will include "git -c ... commit" we set provide "--if-exists=add" )
   or as noted above, maybe we can skip the whole thing and use the
   addIfDifferentNeighbor default.
Has been restored to the default settings.
And, not needed for this patch but worth thinking about:

 * We pass through --trailer to git-interpret-trailers, what should we
   do about the other options? Should git-commit eventually support
   --trailer-where and pass it along as --where to
   git-interpret-trailers, or is "git -c trailer.where=... commit" good
   enough?
Logically speaking, `interpret_trailers` should be dedicated to `commit`
or other sub-commands that require trailers.

But I think that in the later stage, the parse_options of the `cmd_commit`
can keep the unrecognized options, and then these choices can be directly
passed to the `interpret_trailers` backend.
 * It would be good to test for and document if that "-c trailer.*"
   trick works (no reason it shouldn't). I.e. to add something like this
   after what you have (along with tests, and check if it's even true):
I haven't tested them for the time being, but I will do it.
       Only the `--trailer` argument to
       linkgit:git-interpret-trailers[1] is supported. Other
       pass-through switches may be added in the future, but currently
       you'll need to pass arguments to
       linkgit:git-interpret-trailers[1] along as config, e.g. `git -c
       trailer.where=start commit [...] --trailer=[...]`.
I think this is worth writing in the documentation.
 * We have a longer-term goal of having the .mailmap apply to trailers,
   it would be nice if git-interpret-trailers had some fuzzy-matching to
   check if the RHS of a trailer is a name/E-Mail pair, and if so did
   stricter validation on it with the ident functions we use for fsck
   etc. (that's copied & subtly different in several different places in
   the codebase, unfortunately[1]).
I may not know much about fuzzy-matching, which may be worth studying later.
More thoughts:

 * Having written all the above I checked how --signoff is implemented.

   It seems to me to be a good idea to (at least for testing) convert
   the --signoff trailer to your implementation. We have plenty of tests
   for it, does migrating it over pass or fail those?
I don’t know how to migrating yet, it may take a long time.
Even I think I can leave it as #leftoverbit later.
 * I also agree with Junio that we shouldn't have a --fixed-by or
   whatever and wouldn't add --signoff today, but it seems very useful
   to me to have a shortcut like:

       --trailer "Signed-off-by"

   I.e. omitting the value, or:

      --trailer "Signed-off-by="

   Or some other thing we deem sufficiently useful/sane
   syntax/unambiguous.n

   Then the value would be provided by fmt_name(WANT_COMMITTER_IDENT)
   just as we do in append_signoff() now. I think a *very common* case
   for this would be something like:

       git commit --amend -v --trailer "Reviewed-by"

   And it would be useful to help that along and not have to do:

       git commit --amend -v --trailer "Reviewed-by=$(git config user.name) <$(git config user.email)>"

   Or worse yet, manually typo your name/e-mail address, as I'm sure I
   and many others will inevitably do when using this option...
I think this idea is very good and easy to implement.
We only need to do a simple string match when we get the "trailer" string,
If it can be completed, it can indeed bring great convenience to users.
1. https://lore.kernel.org/git/87bld8ov9q.fsf@evledraar.gmail.com/
Thanks, Ævar Arnfjörð Bjarmason!

--
ZheNing Hu

Re: [PATCH v9] [GSOC] commit: add --trailer option

From: Shourya Shukla <hidden>
Date: 2021-03-17 05:27:34

On 16/03 10:39, ZheNing Hu via GitGitGadget wrote:

Hey ZheNing!
From: ZheNing Hu <redacted>

Historically, Git has supported the 'Signed-off-by' commit trailer
using the '--signoff' and the '-s' option from the command line.
But users may need to provide other trailer information from the
command line such as "Helped-by", "Reported-by", "Mentored-by",

Now implement a new `--trailer <token>[(=|:)<value>]` option to pass
other trailers to `interpret-trailers` and insert them into commit
messages.

Signed-off-by: ZheNing Hu <redacted>
I have been away for a while and directly seeing a V9 of this patch
feels great! Its good that you have worked upon the patch. The above
approach seems good to me!
quoted hunk
 	/*
 	 * Reject an attempt to record a non-merge empty commit without
 	 * explicit --allow-empty. In the cherry-pick case, it may be
@@ -1507,6 +1528,7 @@ int cmd_commit(int argc, const char **argv, const char *prefix)
 		OPT_STRING(0, "fixup", &fixup_message, N_("commit"), N_("use autosquash formatted message to fixup specified commit")),
 		OPT_STRING(0, "squash", &squash_message, N_("commit"), N_("use autosquash formatted message to squash specified commit")),
 		OPT_BOOL(0, "reset-author", &renew_authorship, N_("the commit is authored by me now (used with -C/-c/--amend)")),
+		OPT_CALLBACK_F(0, "trailer", NULL, N_("trailer"), N_("trailer(s) to add"), PARSE_OPT_NONEG, opt_pass_trailer),
I feel that a better option description could be offered? Maybe
something like: 'add custom trailer(s)'.

 		OPT_BOOL('s', "signoff", &signoff, N_("add a Signed-off-by trailer")),
 		OPT_FILENAME('t', "template", &template_file, N_("use specified template file")),
 		OPT_BOOL('e', "edit", &edit_flag, N_("force edit of commit")),
I have not yet gone through the the V2-V8s but I have a comment not
associated with the contents of the patch. I feel that you should wait a
little before posting a new version of the patch. I see that V4-V8 are
put up in almost 3 hour gaps. This isn't technically wrong or prohibited
by the communication rules of the List but I feel that posting a patch
in such short intervals makes it hard to review and unnecessarily
increases the versions of the patch. The reviewer lags behind the patch
series in fact.

What you could do instead is post one patch per day instead of 3-4 in
one single day so that your patches get thorough reviews. This way, you
won't create 3-4 new versions of the patch containing not-so-many
significant changes. You get me?

Also, in your reply on the V1 here:
https://lore.kernel.org/git/CAOLTT8SpAOj51jqYUYqYwXaVKSn1fANvetauaG0z4etiBMzVEw@mail.gmail.com/

I read:
It's exactly what you said.
My lack of English sometimes limits my expression.
It is okay please do not worry. Neither do we have English as our first
language nor have we ever communicated this much with an English
speaking audience online. I struggled initially too especially with many
American terms used here. You will get the hang of it soon.

Keep contributing!

Regards,
Shourya Shukla

Re: [PATCH v9] [GSOC] commit: add --trailer option

From: ZheNing Hu <hidden>
Date: 2021-03-17 06:07:05

Shourya Shukla [off-list ref] 于2021年3月17日周三 下午1:26写道:
On 16/03 10:39, ZheNing Hu via GitGitGadget wrote:

Hey ZheNing!


I have been away for a while and directly seeing a V9 of this patch
feels great! Its good that you have worked upon the patch. The above
approach seems good to me!
Hi! :-)
quoted
      /*
       * Reject an attempt to record a non-merge empty commit without
       * explicit --allow-empty. In the cherry-pick case, it may be
@@ -1507,6 +1528,7 @@ int cmd_commit(int argc, const char **argv, const char *prefix)
              OPT_STRING(0, "fixup", &fixup_message, N_("commit"), N_("use autosquash formatted message to fixup specified commit")),
              OPT_STRING(0, "squash", &squash_message, N_("commit"), N_("use autosquash formatted message to squash specified commit")),
              OPT_BOOL(0, "reset-author", &renew_authorship, N_("the commit is authored by me now (used with -C/-c/--amend)")),
+             OPT_CALLBACK_F(0, "trailer", NULL, N_("trailer"), N_("trailer(s) to add"), PARSE_OPT_NONEG, opt_pass_trailer),
I feel that a better option description could be offered? Maybe
something like: 'add custom trailer(s)'.
Feel ok.
quoted
              OPT_BOOL('s', "signoff", &signoff, N_("add a Signed-off-by trailer")),
              OPT_FILENAME('t', "template", &template_file, N_("use specified template file")),
              OPT_BOOL('e', "edit", &edit_flag, N_("force edit of commit")),
I have not yet gone through the the V2-V8s but I have a comment not
associated with the contents of the patch. I feel that you should wait a
little before posting a new version of the patch. I see that V4-V8 are
put up in almost 3 hour gaps. This isn't technically wrong or prohibited
by the communication rules of the List but I feel that posting a patch
in such short intervals makes it hard to review and unnecessarily
increases the versions of the patch. The reviewer lags behind the patch
series in fact.

What you could do instead is post one patch per day instead of 3-4 in
one single day so that your patches get thorough reviews. This way, you
won't create 3-4 new versions of the patch containing not-so-many
significant changes. You get me?
Get it. Especially seeing Ævar Arnfjörð Bjarmason give me comments
behind the new iterations I posted, I knew that I might be sending new
versions too frequently.
Also, in your reply on the V1 here:
https://lore.kernel.org/git/CAOLTT8SpAOj51jqYUYqYwXaVKSn1fANvetauaG0z4etiBMzVEw@mail.gmail.com/

I read:
quoted
It's exactly what you said.
My lack of English sometimes limits my expression.
It is okay please do not worry. Neither do we have English as our first
language nor have we ever communicated this much with an English
speaking audience online. I struggled initially too especially with many
American terms used here. You will get the hang of it soon.

Keep contributing!
Thank you for your encouragement. Many people come to discuss and
share some ideas in English with me every day. It is a cool thing in itself.
Regards,
Shourya Shukla
Regards,
ZheNing Hu

Re: [PATCH v8 1/2] [GSOC] commit: add --trailer option

From: Ævar Arnfjörð Bjarmason <hidden>
Date: 2021-03-17 08:09:03

On Wed, Mar 17 2021, ZheNing Hu wrote:
Ævar Arnfjörð Bjarmason [off-list ref] 于2021年3月16日周二 下午8:52写道:
quoted
quoted
+             if (run_command(&run_trailer))
+                     strvec_clear(&run_trailer.args);
This is git-commit, shouldn't we die() here instead of ignoring errors
in sub-processes?
After thinking about it carefully, your opinion is more
reasonable, because if the user uses the wrong `--trailer`
and does not get the information he needs, I think he will
have to use `--amend` to modify, and `die()` can exit
this commit directly.
Yeah, we don't want to silently lose data.
quoted
quoted
+             strvec_clear(&trailer_args);
+     }
+
      /*
       * Reject an attempt to record a non-merge empty commit without
       * explicit --allow-empty. In the cherry-pick case, it may be
@@ -1507,6 +1529,7 @@ int cmd_commit(int argc, const char **argv, const char *prefix)
              OPT_STRING(0, "fixup", &fixup_message, N_("commit"), N_("use autosquash formatted message to fixup specified commit")),
              OPT_STRING(0, "squash", &squash_message, N_("commit"), N_("use autosquash formatted message to squash specified commit")),
              OPT_BOOL(0, "reset-author", &renew_authorship, N_("the commit is authored by me now (used with -C/-c/--amend)")),
+             OPT_CALLBACK_F(0, "trailer", NULL, N_("trailer"), N_("trailer(s) to add"), PARSE_OPT_NONEG, opt_pass_trailer),
              OPT_BOOL('s', "signoff", &signoff, N_("add a Signed-off-by trailer")),
Not required for this change, but perhaps a change here to N_() (if we
can get it to fit) + doc update saying that we prefer
--trailer="Signed-Off-By: to --signoff"? More on that later.
quoted
              OPT_FILENAME('t', "template", &template_file, N_("use specified template file")),
              OPT_BOOL('e', "edit", &edit_flag, N_("force edit of commit")),
diff --git a/t/t7502-commit-porcelain.sh b/t/t7502-commit-porcelain.sh
index 6396897cc818..0acf23799931 100755
--- a/t/t7502-commit-porcelain.sh
+++ b/t/t7502-commit-porcelain.sh
@@ -154,6 +154,26 @@ test_expect_success 'sign off' '

 '

+test_expect_success 'trailer' '
+     >file1 &&
+     git add file1 &&
+     git commit -s --trailer "Signed-off-by:C O Mitter1 <committer1@example.com>" \
+             --trailer "Helped-by:C O Mitter2 <committer2@example.com>"  \
+             --trailer "Reported-by:C O Mitter3 <committer3@example.com>" \
+             --trailer "Mentored-by:C O Mitter4 <committer4@example.com>" \
+             -m "hello" &&
+     git cat-file commit HEAD >commit.msg &&
+     sed -e "1,7d" commit.msg >actual &&
+     cat >expected <<-\EOF &&
+     Signed-off-by: C O Mitter <committer@example.com>
+     Signed-off-by: C O Mitter1 <committer1@example.com>
+     Helped-by: C O Mitter2 <committer2@example.com>
+     Reported-by: C O Mitter3 <committer3@example.com>
+     Mentored-by: C O Mitter4 <committer4@example.com>
+     EOF
+     test_cmp expected actual
+'
+
How does this interact with cases where the user has configured
"trailer.separators" to have a value that doesn't contain ":"?  I
haven't tested, but my reading of git-interpret-trailers(1) is that if
you supplied "=" instead that case would just work:

    By default only : is recognized as a trailer separator, except that
    = is always accepted on the command line for compatibility with
    other git commands.
But interpret_trailers interface allow us use "=" instead of other separators.

I did a simple test and modified the configuration "trailer.separators"
and it still works. Now things are good here:

$ git -c trailer.separators="@" commit --trailer="Signed-off-by=C O <email>"

or

$ git -c trailer.separators="@" commit --trailer="Signed-off-by@C O <email>"

Both can work normally,

--trailer="Signed-off-by@ C O <email>"

will output in the commit message.
quoted
I don't know if that does the right thing in the presence of
--if-exists=add.
Yesterday, Christian Couder and I had already discussed this issue:
Your idea is correct, I should not add "--if-exists = add",  this will destroy
the user's rights to configure by using `git -c trailer.if-exist`.
quoted
So it would be good to update these tests so you test:

 * For the --if-exists=add case at all, there's no tests for it
   now. I.e. add some trailers manually to the commit (via -F or
   whatever) and then see if they get added to, replacet etc.

 * Ditto but for the user having configured trailer.separators (see the
   test_config helper for how to set config in a test). I.e. if it's "="
   does adding trailers work, how about if it's "=" on the CLI but the
   config/commit message has ";" instead of ":" or something?
As mentioned above, it works normally.
quoted
 * Hrm, actually I think tweaking "-c trailer.ifexists" won't work at
   all, since the CLI switch would override it. I honestly don't know,
   but why not not supply it and keep the addIfDifferentNeighbor
   default?

   If it's essential that seems like a good test / documentation
   addition...

 * For the above -c ... case I can't think of a good way to deal with it
   that doesn't involve pulling in git_trailer_config() into
   git_commit_config(), but perhaps the least nasty way is to just set a
   flag in git_commit_config() if we see a "trailer.ifexists" flag, and
   if so don't provide "--if-exists=add", if there's no config (this
   will include "git -c ... commit" we set provide "--if-exists=add" )
   or as noted above, maybe we can skip the whole thing and use the
   addIfDifferentNeighbor default.
Has been restored to the default settings.
To clarify: What I really mean is for all these things you've tested:
let's add those to the tests as part of the patch.
quoted
And, not needed for this patch but worth thinking about:

 * We pass through --trailer to git-interpret-trailers, what should we
   do about the other options? Should git-commit eventually support
   --trailer-where and pass it along as --where to
   git-interpret-trailers, or is "git -c trailer.where=... commit" good
   enough?
Logically speaking, `interpret_trailers` should be dedicated to `commit`
or other sub-commands that require trailers.

But I think that in the later stage, the parse_options of the `cmd_commit`
can keep the unrecognized options, and then these choices can be directly
passed to the `interpret_trailers` backend.
We have this interaction with e.g. range-diff and "log", it's often
surprising. You add an option to one command and it appears in the
other.
quoted
 * It would be good to test for and document if that "-c trailer.*"
   trick works (no reason it shouldn't). I.e. to add something like this
   after what you have (along with tests, and check if it's even true):
I haven't tested them for the time being, but I will do it.
quoted
       Only the `--trailer` argument to
       linkgit:git-interpret-trailers[1] is supported. Other
       pass-through switches may be added in the future, but currently
       you'll need to pass arguments to
       linkgit:git-interpret-trailers[1] along as config, e.g. `git -c
       trailer.where=start commit [...] --trailer=[...]`.
I think this is worth writing in the documentation.
quoted
 * We have a longer-term goal of having the .mailmap apply to trailers,
   it would be nice if git-interpret-trailers had some fuzzy-matching to
   check if the RHS of a trailer is a name/E-Mail pair, and if so did
   stricter validation on it with the ident functions we use for fsck
   etc. (that's copied & subtly different in several different places in
   the codebase, unfortunately[1]).
I may not know much about fuzzy-matching, which may be worth studying later.
quoted
More thoughts:

 * Having written all the above I checked how --signoff is implemented.

   It seems to me to be a good idea to (at least for testing) convert
   the --signoff trailer to your implementation. We have plenty of tests
   for it, does migrating it over pass or fail those?
I don’t know how to migrating yet, it may take a long time.
Even I think I can leave it as #leftoverbit later.
Sure, I mean (having looked at it) that at least for your own local
testing it would make sense to change it (even if just search-replacing
the --signoff in the test suite) to see if it behaves as you
expect. I.e. does the --trailer behavior mirror --signoff?
quoted
 * I also agree with Junio that we shouldn't have a --fixed-by or
   whatever and wouldn't add --signoff today, but it seems very useful
   to me to have a shortcut like:

       --trailer "Signed-off-by"

   I.e. omitting the value, or:

      --trailer "Signed-off-by="

   Or some other thing we deem sufficiently useful/sane
   syntax/unambiguous.n

   Then the value would be provided by fmt_name(WANT_COMMITTER_IDENT)
   just as we do in append_signoff() now. I think a *very common* case
   for this would be something like:

       git commit --amend -v --trailer "Reviewed-by"

   And it would be useful to help that along and not have to do:

       git commit --amend -v --trailer "Reviewed-by=$(git config user.name) <$(git config user.email)>"

   Or worse yet, manually typo your name/e-mail address, as I'm sure I
   and many others will inevitably do when using this option...
I think this idea is very good and easy to implement.
We only need to do a simple string match when we get the "trailer" string,
If it can be completed, it can indeed bring great convenience to users.
quoted
1. https://lore.kernel.org/git/87bld8ov9q.fsf@evledraar.gmail.com/
Thanks, Ævar Arnfjörð Bjarmason!
And thanks for working on this.

Re: [PATCH v8 1/2] [GSOC] commit: add --trailer option

From: ZheNing Hu <hidden>
Date: 2021-03-17 13:55:58

Ævar Arnfjörð Bjarmason [off-list ref] 于2021年3月17日周三 下午4:08写道:
quoted
Logically speaking, `interpret_trailers` should be dedicated to `commit`
or other sub-commands that require trailers.

But I think that in the later stage, the parse_options of the `cmd_commit`
can keep the unrecognized options, and then these choices can be directly
passed to the `interpret_trailers` backend.
We have this interaction with e.g. range-diff and "log", it's often
surprising. You add an option to one command and it appears in the
other.
All right, I'm wrong, I may have reference to an wrong experience
of `difftool`-->`diff`.
quoted
quoted
   It seems to me to be a good idea to (at least for testing) convert
   the --signoff trailer to your implementation. We have plenty of tests
   for it, does migrating it over pass or fail those?
I don’t know how to migrating yet, it may take a long time.
Even I think I can leave it as #leftoverbit later.
Sure, I mean (having looked at it) that at least for your own local
testing it would make sense to change it (even if just search-replacing
the --signoff in the test suite) to see if it behaves as you
expect. I.e. does the --trailer behavior mirror --signoff?
quoted
quoted
 * I also agree with Junio that we shouldn't have a --fixed-by or
   whatever and wouldn't add --signoff today, but it seems very useful
   to me to have a shortcut like:

       --trailer "Signed-off-by"

   I.e. omitting the value, or:

      --trailer "Signed-off-by="

   Or some other thing we deem sufficiently useful/sane
   syntax/unambiguous.n

   Then the value would be provided by fmt_name(WANT_COMMITTER_IDENT)
   just as we do in append_signoff() now. I think a *very common* case
   for this would be something like:

       git commit --amend -v --trailer "Reviewed-by"

   And it would be useful to help that along and not have to do:

       git commit --amend -v --trailer "Reviewed-by=$(git config user.name) <$(git config user.email)>"

   Or worse yet, manually typo your name/e-mail address, as I'm sure I
   and many others will inevitably do when using this option...
Well, that's what I think here:

Now we can go through:

$ git -c trailer.signoff.key = "Signed-off-by" commit --trailer
"signoff = commiter <email>"

to get a trailer: "Signed-off-by: commiter <email>", this means we
can't just do simple string
matching in `cmd_commit` to replace `--trailer="Signed-off-by"` or
`--trailer="Reviewed-by"` to
user's own identity, to replace the trailers which have omitting value
 we passed in, but I think
we can provide a new option to `commit` which can mandatory that
trailers with no value can be
 replaced with the identity of the user.

e.g.

$ git -c trailer.signoff.key = "Signed-off-by" commit --trailer
"signoff" --trailer "Helped-by" \
 --trailer "Helped-by = C <E>" --own_ident

will output like this:

Signed-off-by: $(git config user.name) <$(git config user.email)>
Signed-off-by: $(git config user.name) <$(git config user.email)>
Helped-by: $(git config user.name) <$(git config user.email)>
Helped-by: C <E>

I don't know if this idea is good, I will try to do it first.

Thanks.

[PATCH v10 1/3] [GSOC] commit: add --trailer option

From: ZheNing Hu via GitGitGadget <hidden>
Date: 2021-03-18 11:16:42

From: ZheNing Hu <redacted>

Historically, Git has supported the 'Signed-off-by' commit trailer
using the '--signoff' and the '-s' option from the command line.
But users may need to provide other trailer information from the
command line such as "Helped-by", "Reported-by", "Mentored-by",

Now implement a new `--trailer <token>[(=|:)<value>]` option to pass
other trailers to `interpret-trailers` and insert them into commit
messages.

Signed-off-by: ZheNing Hu <redacted>
---
 Documentation/git-commit.txt |  10 +-
 builtin/commit.c             |  23 +++
 t/t7502-commit-porcelain.sh  | 336 +++++++++++++++++++++++++++++++++++
 3 files changed, 368 insertions(+), 1 deletion(-)
diff --git a/Documentation/git-commit.txt b/Documentation/git-commit.txt
index 17150fa7eabe..c5de981cd40d 100644
--- a/Documentation/git-commit.txt
+++ b/Documentation/git-commit.txt
@@ -14,7 +14,7 @@ SYNOPSIS
 	   [--allow-empty-message] [--no-verify] [-e] [--author=<author>]
 	   [--date=<date>] [--cleanup=<mode>] [--[no-]status]
 	   [-i | -o] [--pathspec-from-file=<file> [--pathspec-file-nul]]
-	   [-S[<keyid>]] [--] [<pathspec>...]
+	   [-S[<keyid>]] [--] [<pathspec>...] [(--trailer <token>[(=|:)<value>])...]
 
 DESCRIPTION
 -----------
@@ -166,6 +166,14 @@ The `-m` option is mutually exclusive with `-c`, `-C`, and `-F`.
 
 include::signoff-option.txt[]
 
+--trailer <token>[(=|:)<value>]::
+	Specify a (<token>, <value>) pair that should be applied as a
+	trailer. (e.g. `git commit --trailer "Signed-off-by:C O Mitter \
+	<committer@example.com>" --trailer "Helped-by:C O Mitter \
+	<committer@example.com>"` will add the "Signed-off-by" trailer
+	and the "Helped-by" trailer in the commit message.)
+	Use `git -c trailer.* commit --trailer` to make the appropriate
+	configuration. See linkgit:git-interpret-trailers[1] for details.
 -n::
 --no-verify::
 	This option bypasses the pre-commit and commit-msg hooks.
diff --git a/builtin/commit.c b/builtin/commit.c
index 739110c5a7f6..7a79aae48f43 100644
--- a/builtin/commit.c
+++ b/builtin/commit.c
@@ -113,6 +113,7 @@ static int config_commit_verbose = -1; /* unspecified */
 static int no_post_rewrite, allow_empty_message, pathspec_file_nul;
 static char *untracked_files_arg, *force_date, *ignore_submodule_arg, *ignored_arg;
 static char *sign_commit, *pathspec_from_file;
+static struct strvec trailer_args = STRVEC_INIT;
 
 /*
  * The default commit message cleanup mode will remove the lines
@@ -131,6 +132,14 @@ static struct strbuf message = STRBUF_INIT;
 
 static enum wt_status_format status_format = STATUS_FORMAT_UNSPECIFIED;
 
+static int opt_pass_trailer(const struct option *opt, const char *arg, int unset)
+{
+	BUG_ON_OPT_NEG(unset);
+
+	strvec_pushl(&trailer_args, "--trailer", arg, NULL);
+	return 0;
+}
+
 static int opt_parse_porcelain(const struct option *opt, const char *arg, int unset)
 {
 	enum wt_status_format *value = (enum wt_status_format *)opt->value;
@@ -958,6 +967,19 @@ static int prepare_to_commit(const char *index_file, const char *prefix,
 
 	fclose(s->fp);
 
+	if (trailer_args.nr) {
+		struct child_process run_trailer = CHILD_PROCESS_INIT;
+
+		strvec_pushl(&run_trailer.args, "interpret-trailers",
+			     "--in-place", git_path_commit_editmsg(), NULL);
+		strvec_pushv(&run_trailer.args, trailer_args.v);
+		run_trailer.git_cmd = 1;
+		if (run_command(&run_trailer)) {
+			die(_("unable to pass tailers to --trailers"));
+		}
+		strvec_clear(&trailer_args);
+	}
+
 	/*
 	 * Reject an attempt to record a non-merge empty commit without
 	 * explicit --allow-empty. In the cherry-pick case, it may be
@@ -1507,6 +1529,7 @@ int cmd_commit(int argc, const char **argv, const char *prefix)
 		OPT_STRING(0, "fixup", &fixup_message, N_("commit"), N_("use autosquash formatted message to fixup specified commit")),
 		OPT_STRING(0, "squash", &squash_message, N_("commit"), N_("use autosquash formatted message to squash specified commit")),
 		OPT_BOOL(0, "reset-author", &renew_authorship, N_("the commit is authored by me now (used with -C/-c/--amend)")),
+		OPT_CALLBACK_F(0, "trailer", NULL, N_("trailer"), N_("add custom trailer(s)"), PARSE_OPT_NONEG, opt_pass_trailer),
 		OPT_BOOL('s', "signoff", &signoff, N_("add a Signed-off-by trailer")),
 		OPT_FILENAME('t', "template", &template_file, N_("use specified template file")),
 		OPT_BOOL('e', "edit", &edit_flag, N_("force edit of commit")),
diff --git a/t/t7502-commit-porcelain.sh b/t/t7502-commit-porcelain.sh
index 6396897cc818..6df71fa00bcb 100755
--- a/t/t7502-commit-porcelain.sh
+++ b/t/t7502-commit-porcelain.sh
@@ -154,6 +154,342 @@ test_expect_success 'sign off' '
 
 '
 
+test_expect_success 'commit --trailer without -c' '
+	echo "fun" >>file &&
+	git add file &&
+	cat >expected <<-\EOF &&
+
+	Signed-off-by: C O Mitter <committer@example.com>
+	Signed-off-by: C1 E1
+	Helped-by: C2 E2
+	Reported-by: C3 E3
+	Mentored-by: C4 E4
+	EOF
+	git commit -s --trailer "Signed-off-by:C1 E1 " \
+		--trailer "Helped-by:C2 E2 " \
+		--trailer "Reported-by:C3 E3" \
+		--trailer "Mentored-by:C4 E4" \
+		-m "hello" &&
+	git cat-file commit HEAD >commit.msg &&
+	sed -e "1,6d" commit.msg >actual &&
+	test_cmp expected actual
+'
+
+test_expect_success 'commit --trailer with -c and "replace" as ifexists' '
+	echo "fun" >>file1 &&
+	git add file1 &&
+	cat >expected <<-\EOF &&
+
+	Signed-off-by: C O Mitter <committer@example.com>
+	Signed-off-by: C1 E1
+	Reported-by: C3 E3
+	Mentored-by: C4 E4
+	Helped-by: C3 E3
+	EOF
+	git -c trailer.ifexists="replace" \
+		commit --trailer "Mentored-by: C4 E4" \
+		 --trailer "Helped-by: C3 E3" \
+		--amend &&
+	git cat-file commit HEAD >commit.msg &&
+	sed -e "1,6d" commit.msg >actual &&
+	test_cmp expected actual
+'
+
+test_expect_success 'commit --trailer with -c and "add" as ifexists' '
+	echo "fun" >>file1 &&
+	git add file1 &&
+	cat >expected <<-\EOF &&
+
+	Signed-off-by: C O Mitter <committer@example.com>
+	Signed-off-by: C1 E1
+	Reported-by: C3 E3
+	Mentored-by: C4 E4
+	Helped-by: C3 E3
+	Helped-by: C3 E3
+	Helped-by: C3 E3
+	EOF
+	git -c trailer.ifexists="add" \
+		commit --trailer "Helped-by: C3 E3" \
+		--trailer "Helped-by: C3 E3" \
+		--amend &&
+	git cat-file commit HEAD >commit.msg &&
+	sed -e "1,6d" commit.msg >actual &&
+	test_cmp expected actual
+'
+
+test_expect_success 'commit --trailer with -c and "donothing" as ifexists' '
+	echo "fun" >>file1 &&
+	git add file1 &&
+	cat >expected <<-\EOF &&
+
+	Signed-off-by: C O Mitter <committer@example.com>
+	Signed-off-by: C1 E1
+	Reported-by: C3 E3
+	Mentored-by: C4 E4
+	Helped-by: C3 E3
+	Helped-by: C3 E3
+	Helped-by: C3 E3
+	Reviewed-by: C6 E6
+	EOF
+	git -c trailer.ifexists="donothing" \
+		commit --trailer "Mentored-by: C5 E5" \
+		--trailer "Reviewed-by: C6 E6" \
+		--amend &&
+	git cat-file commit HEAD >commit.msg &&
+	sed -e "1,6d" commit.msg >actual &&
+	test_cmp expected actual
+'
+
+test_expect_success 'commit --trailer with -c and "addIfDifferent" as ifexists' '
+	echo "fun" >>file1 &&
+	git add file1 &&
+	cat >expected <<-\EOF &&
+
+	Signed-off-by: C O Mitter <committer@example.com>
+	Signed-off-by: C1 E1
+	Reported-by: C3 E3
+	Mentored-by: C4 E4
+	Helped-by: C3 E3
+	Helped-by: C3 E3
+	Helped-by: C3 E3
+	Reviewed-by: C6 E6
+	Reported-by: C5 E5
+	EOF
+	git -c trailer.ifexists="addIfDifferent" \
+		commit --trailer "Reviewed-by: C6 E6" \
+		--trailer "Reported-by: C5 E5" \
+		--amend &&
+	git cat-file commit HEAD >commit.msg &&
+	sed -e "1,6d" commit.msg >actual &&
+	test_cmp expected actual
+'
+
+test_expect_success 'commit --trailer with -c and "addIfDifferentNeighbor" as ifexists' '
+	echo "fun" >>file1 &&
+	git add file1 &&
+	cat >expected <<-\EOF &&
+
+	Signed-off-by: C O Mitter <committer@example.com>
+	Signed-off-by: C1 E1
+	Reported-by: C3 E3
+	Mentored-by: C4 E4
+	Helped-by: C3 E3
+	Helped-by: C3 E3
+	Helped-by: C3 E3
+	Reviewed-by: C6 E6
+	Reported-by: C5 E5
+	EOF
+	git -c trailer.ifexists="addIfDifferent" \
+		commit --trailer "Reported-by: C5 E5" \
+		--trailer "Reviewed-by: C6 E6" \
+		--amend &&
+	git cat-file commit HEAD >commit.msg &&
+	sed -e "1,6d" commit.msg >actual &&
+	test_cmp expected actual
+'
+
+test_expect_success 'commit --trailer with -c and "end" as where' '
+	echo "fun" >>file1 &&
+	git add file1 &&
+	cat >expected <<-\EOF &&
+
+	Signed-off-by: C O Mitter <committer@example.com>
+	Signed-off-by: C1 E1
+	Reported-by: C3 E3
+	Mentored-by: C4 E4
+	Helped-by: C3 E3
+	Helped-by: C3 E3
+	Helped-by: C3 E3
+	Reviewed-by: C6 E6
+	Reported-by: C5 E5
+	Reported-by: C7 E7
+	EOF
+	git -c trailer.where="end" \
+		commit --trailer "Reported-by: C5 E5" \
+		--trailer "Reported-by: C7 E7" \
+		--amend &&
+	git cat-file commit HEAD >commit.msg &&
+	sed -e "1,6d" commit.msg >actual &&
+	test_cmp expected actual
+'
+
+test_expect_success 'commit --trailer with -c and "start" as where' '
+	echo "fun" >>file1 &&
+	git add file1 &&
+	cat >expected <<-\EOF &&
+
+	Signed-off-by: C8 E8
+	Signed-off-by: C O Mitter <committer@example.com>
+	Signed-off-by: C1 E1
+	Reported-by: C3 E3
+	Mentored-by: C4 E4
+	Helped-by: C3 E3
+	Helped-by: C3 E3
+	Helped-by: C3 E3
+	Reviewed-by: C6 E6
+	Reported-by: C5 E5
+	Reported-by: C7 E7
+	EOF
+	git -c trailer.where="start" \
+		commit --trailer "Signed-off-by: C8 E8" \
+		--trailer "Signed-off-by: C8 E8" \
+		--amend &&
+	git cat-file commit HEAD >commit.msg &&
+	sed -e "1,6d" commit.msg >actual &&
+	test_cmp expected actual
+'
+
+test_expect_success 'commit --trailer with -c and "after" as where' '
+	echo "fun" >>file1 &&
+	git add file1 &&
+	cat >expected <<-\EOF &&
+
+	Signed-off-by: C8 E8
+	Signed-off-by: C O Mitter <committer@example.com>
+	Signed-off-by: C1 E1
+	Reported-by: C3 E3
+	Mentored-by: C4 E4
+	Mentored-by: C9 E9
+	Helped-by: C3 E3
+	Helped-by: C3 E3
+	Helped-by: C3 E3
+	Reviewed-by: C6 E6
+	Reported-by: C5 E5
+	Reported-by: C7 E7
+	Reported-by: C10 E10
+	EOF
+	git -c trailer.where="after" \
+		commit --trailer "Mentored-by: C9 E9" \
+		--trailer "Reported-by: C10 E10" \
+		--amend &&
+	git cat-file commit HEAD >commit.msg &&
+	sed -e "1,6d" commit.msg >actual &&
+	test_cmp expected actual
+'
+
+test_expect_success 'commit --trailer with -c and "before" as where' '
+	echo "fun" >>file1 &&
+	git add file1 &&
+	cat >expected <<-\EOF &&
+
+	Signed-off-by: C8 E8
+	Signed-off-by: C O Mitter <committer@example.com>
+	Signed-off-by: C1 E1
+	Reported-by: C11 E11
+	Reported-by: C3 E3
+	Mentored-by: C4 E4
+	Mentored-by: C9 E9
+	Helped-by: C12 E12
+	Helped-by: C3 E3
+	Helped-by: C3 E3
+	Helped-by: C3 E3
+	Reviewed-by: C6 E6
+	Reported-by: C5 E5
+	Reported-by: C7 E7
+	Reported-by: C10 E10
+	EOF
+	git -c trailer.where="before" \
+		commit --trailer "Helped-by: C12 E12" \
+		--trailer "Reported-by: C11 E11" \
+		--amend &&
+	git cat-file commit HEAD >commit.msg &&
+	sed -e "1,6d" commit.msg >actual &&
+	test_cmp expected actual
+'
+
+test_expect_success 'commit --trailer with -c and "donothing" as ifmissing' '
+	echo "fun" >>file1 &&
+	git add file1 &&
+	cat >expected <<-\EOF &&
+
+	Signed-off-by: C8 E8
+	Signed-off-by: C O Mitter <committer@example.com>
+	Signed-off-by: C1 E1
+	Reported-by: C11 E11
+	Reported-by: C3 E3
+	Mentored-by: C4 E4
+	Mentored-by: C9 E9
+	Helped-by: C12 E12
+	Helped-by: C3 E3
+	Helped-by: C3 E3
+	Helped-by: C3 E3
+	Reviewed-by: C6 E6
+	Reported-by: C5 E5
+	Reported-by: C7 E7
+	Reported-by: C10 E10
+	Helped-by: C12 E12
+	EOF
+	git -c trailer.ifmissing="donothing" \
+		commit --trailer "Helped-by: C12 E12" \
+		--trailer "Based-by: C13 E13" \
+		--amend &&
+	git cat-file commit HEAD >commit.msg &&
+	sed -e "1,6d" commit.msg >actual &&
+	test_cmp expected actual
+'
+
+test_expect_success 'commit --trailer with -c and "add" as ifmissing' '
+	echo "fun" >>file1 &&
+	git add file1 &&
+	cat >expected <<-\EOF &&
+
+	Signed-off-by: C8 E8
+	Signed-off-by: C O Mitter <committer@example.com>
+	Signed-off-by: C1 E1
+	Reported-by: C11 E11
+	Reported-by: C3 E3
+	Mentored-by: C4 E4
+	Mentored-by: C9 E9
+	Helped-by: C12 E12
+	Helped-by: C3 E3
+	Helped-by: C3 E3
+	Helped-by: C3 E3
+	Reviewed-by: C6 E6
+	Reported-by: C5 E5
+	Reported-by: C7 E7
+	Reported-by: C10 E10
+	Helped-by: C12 E12
+	Based-by: C13 E13
+	EOF
+	git -c trailer.ifmissing="add" \
+		commit --trailer "Helped-by: C12 E12" \
+		--trailer "Based-by: C13 E13" \
+		--amend &&
+	git cat-file commit HEAD >commit.msg &&
+	sed -e "1,6d" commit.msg >actual &&
+	test_cmp expected actual
+'
+
+test_expect_success 'commit --trailer with -c and "=" as separators' '
+	echo "fun" >>file1 &&
+	git add file1 &&
+	cat >expected <<-\EOF &&
+
+		Acked-by= Peff
+	EOF
+	git -c trailer.separators="=" \
+		-c trailer.ack.key="Acked-by= " \
+		commit --trailer "ack = Peff" -m "hello" &&
+	git cat-file commit HEAD >commit.msg &&
+	sed -e "1,6d" commit.msg >actual &&
+	test_cmp expected actual
+'
+
+test_expect_success 'commit --trailer with -c and ":=#" as separators' '
+	echo "fun" >>file1 &&
+	git add file1 &&
+	cat >expected <<-\EOF &&
+
+		Bug #42
+	EOF
+	git -c trailer.separators=":=#" \
+		-c trailer.bug.key="Bug #" \
+		commit --trailer "bug = 42" -m "I hate bug" &&
+	git cat-file commit HEAD >commit.msg &&
+	sed -e "1,6d" commit.msg >actual &&
+	test_cmp expected actual
+'
+
 test_expect_success 'multiple -m' '
 
 	>negative &&
-- 
gitgitgadget

[PATCH v10 2/3] interpret-trailers: add own-identity option

From: ZheNing Hu via GitGitGadget <hidden>
Date: 2021-03-18 11:16:43

From: ZheNing Hu <redacted>

Beacuse `git commit --trailer="Signed-off-by: \
$(git config user.name) <$(git config user.email)>"`
is difficult for users to add their own identities,
so teach interpret-trailers a new option `--own-identity`
which allow those trailers with no value add the user’s own
identity. This will help the use of `commit --trailer` as
easy as `--signoff`.

Signed-off-by: ZheNing Hu <redacted>
---
 Documentation/git-interpret-trailers.txt | 14 ++++++++++++++
 builtin/interpret-trailers.c             |  6 ++++--
 t/t7513-interpret-trailers.sh            | 12 ++++++++++++
 trailer.c                                | 18 ++++++++++++++++--
 trailer.h                                |  3 ++-
 5 files changed, 48 insertions(+), 5 deletions(-)
diff --git a/Documentation/git-interpret-trailers.txt b/Documentation/git-interpret-trailers.txt
index 96ec6499f001..ace44446c3e3 100644
--- a/Documentation/git-interpret-trailers.txt
+++ b/Documentation/git-interpret-trailers.txt
@@ -84,6 +84,19 @@ OPTIONS
 	trailer to the input messages. See the description of this
 	command.
 
+--own-identity::
+	Used with `--trailer`. Those trailers without value with the
+	`--own-identity` option all will add the user's own identity.
+	For example,` git interpret-trailers --trailer "A:B" --trailer \
+	"Signed-off-by" --trailer "Helped-by" --own-identity --inplace a.txt`
+	will output:
+	"
+	A:B
+	Signed-off-by: C O Mitter <committer@example.com>
+	Helped-by: C O Mitter <committer@example.com>
+	"
+	in `a.txt`.
+
 --where <placement>::
 --no-where::
 	Specify where all new trailers will be added.  A setting
@@ -131,6 +144,7 @@ OPTIONS
 	when you know your input contains just the commit message itself
 	(and not an email or the output of `git format-patch`).
 
+
 CONFIGURATION VARIABLES
 -----------------------
 
diff --git a/builtin/interpret-trailers.c b/builtin/interpret-trailers.c
index 84748eafc01b..be7f502a58d7 100644
--- a/builtin/interpret-trailers.c
+++ b/builtin/interpret-trailers.c
@@ -47,6 +47,7 @@ static void new_trailers_clear(struct list_head *trailers)
 	list_for_each_safe(pos, tmp, trailers) {
 		item = list_entry(pos, struct new_trailer_item, list);
 		list_del(pos);
+		free(item->text);
 		free(item);
 	}
 }
@@ -66,7 +67,7 @@ static int option_parse_trailer(const struct option *opt,
 		return -1;
 
 	item = xmalloc(sizeof(*item));
-	item->text = arg;
+	item->text = xstrdup(arg);
 	item->where = where;
 	item->if_exists = if_exists;
 	item->if_missing = if_missing;
@@ -94,7 +95,8 @@ int cmd_interpret_trailers(int argc, const char **argv, const char *prefix)
 	struct option options[] = {
 		OPT_BOOL(0, "in-place", &opts.in_place, N_("edit files in place")),
 		OPT_BOOL(0, "trim-empty", &opts.trim_empty, N_("trim empty trailers")),
-
+		OPT_BOOL(0, "own-identity", &opts.own_identity,
+			     N_("specify the user's own identity for omitted trailers value")),
 		OPT_CALLBACK(0, "where", NULL, N_("action"),
 			     N_("where to place the new trailer"), option_parse_where),
 		OPT_CALLBACK(0, "if-exists", NULL, N_("action"),
diff --git a/t/t7513-interpret-trailers.sh b/t/t7513-interpret-trailers.sh
index 6602790b5f4c..f82cee93bfb2 100755
--- a/t/t7513-interpret-trailers.sh
+++ b/t/t7513-interpret-trailers.sh
@@ -63,6 +63,18 @@ test_expect_success 'without config' '
 	test_cmp expected actual
 '
 
+test_expect_success 'without config with --own-identity' '
+	cat >expected <<-\EOF &&
+
+	Acked-by: A B <C>
+	Helped-by: C O Mitter <committer@example.com>
+	Signed-off-by: C O Mitter <committer@example.com>
+	EOF
+	git interpret-trailers --trailer "Acked-by: A B <C>" --trailer "Helped-by" \
+	--trailer "Signed-off-by" --own-identity empty >actual &&
+	test_cmp expected actual
+'
+
 test_expect_success 'without config in another order' '
 	sed -e "s/ Z\$/ /" >expected <<-\EOF &&
 
diff --git a/trailer.c b/trailer.c
index 249ed618ed8e..cd4f85e71c9a 100644
--- a/trailer.c
+++ b/trailer.c
@@ -690,8 +690,18 @@ static void add_arg_item(struct list_head *arg_head, char *tok, char *val,
 	list_add_tail(&new_item->list, arg_head);
 }
 
+static void add_user_own_identity(struct new_trailer_item *item)
+{
+	struct strbuf buf = STRBUF_INIT;
+	strbuf_addstr(&buf, item->text);
+	strbuf_add(&buf, "=", 1);
+	strbuf_addstr(&buf, fmt_name(WANT_COMMITTER_IDENT));
+	free(item->text);
+	item->text = buf.buf;
+}
+
 static void process_command_line_args(struct list_head *arg_head,
-				      struct list_head *new_trailer_head)
+				      struct list_head *new_trailer_head, int own_identity)
 {
 	struct arg_item *item;
 	struct strbuf tok = STRBUF_INIT;
@@ -728,6 +738,10 @@ static void process_command_line_args(struct list_head *arg_head,
 			error(_("empty trailer token in trailer '%.*s'"),
 			      (int) sb.len, sb.buf);
 			strbuf_release(&sb);
+		} else if (separator_pos == -1 && own_identity) {
+				add_user_own_identity(tr);
+				pos = pos->prev;
+				continue;
 		} else {
 			parse_trailer(&tok, &val, &conf, tr->text,
 				      separator_pos);
@@ -1048,7 +1062,7 @@ void process_trailers(const char *file,
 
 	if (!opts->only_input) {
 		LIST_HEAD(arg_head);
-		process_command_line_args(&arg_head, new_trailer_head);
+		process_command_line_args(&arg_head, new_trailer_head ,opts->own_identity);
 		process_trailers_lists(&head, &arg_head);
 	}
 
diff --git a/trailer.h b/trailer.h
index 795d2fccfd95..235dfdfa1978 100644
--- a/trailer.h
+++ b/trailer.h
@@ -57,7 +57,7 @@ struct trailer_info {
 struct new_trailer_item {
 	struct list_head list;
 
-	const char *text;
+	char *text;
 
 	enum trailer_where where;
 	enum trailer_if_exists if_exists;
@@ -73,6 +73,7 @@ struct process_trailer_options {
 	int no_divider;
 	int key_only;
 	int value_only;
+	int own_identity;
 	const struct strbuf *separator;
 	const struct strbuf *key_value_separator;
 	int (*filter)(const struct strbuf *, void *);
-- 
gitgitgadget

[PATCH v10 0/3] [GSOC] commit: add --trailer option

From: ZheNing Hu via GitGitGadget <hidden>
Date: 2021-03-18 11:16:43

Now maintainers or developers can also use commit
--trailer="Signed-off-by:commiter<email>" from the command line to provide
trailers to commit messages. This solution may be more generalized than v1.

ZheNing Hu (3):
  [GSOC] commit: add --trailer option
  interpret-trailers: add own-identity option
  commit: add own-identity option

 Documentation/git-commit.txt             |  25 +-
 Documentation/git-interpret-trailers.txt |  14 +
 builtin/commit.c                         |  31 ++
 builtin/interpret-trailers.c             |   6 +-
 t/t7501-commit-basic-functionality.sh    |  91 ++++++
 t/t7502-commit-porcelain.sh              | 356 +++++++++++++++++++++++
 t/t7513-interpret-trailers.sh            |  12 +
 trailer.c                                |  18 +-
 trailer.h                                |   3 +-
 9 files changed, 550 insertions(+), 6 deletions(-)


base-commit: 13d7ab6b5d7929825b626f050b62a11241ea4945
Published-As: https://github.com/gitgitgadget/git/releases/tag/pr-901%2Fadlternative%2Fcommit-with-multiple-signatures-v10
Fetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-901/adlternative/commit-with-multiple-signatures-v10
Pull-Request: https://github.com/gitgitgadget/git/pull/901

Range-diff vs v9:

 1:  e524c4ba5dc1 ! 1:  949faf9ee56a [GSOC] commit: add --trailer option
     @@ Documentation/git-commit.txt: The `-m` option is mutually exclusive with `-c`, `
      +	[off-list ref]" --trailer "Helped-by:C O Mitter \
      +	[off-list ref]"` will add the "Signed-off-by" trailer
      +	and the "Helped-by" trailer in the commit message.)
     -+	See linkgit:git-interpret-trailers[1] for details.
     ++	Use `git -c trailer.* commit --trailer` to make the appropriate
     ++	configuration. See linkgit:git-interpret-trailers[1] for details.
       -n::
       --no-verify::
       	This option bypasses the pre-commit and commit-msg hooks.
     @@ builtin/commit.c: static int prepare_to_commit(const char *index_file, const cha
      +			     "--in-place", git_path_commit_editmsg(), NULL);
      +		strvec_pushv(&run_trailer.args, trailer_args.v);
      +		run_trailer.git_cmd = 1;
     -+		if (run_command(&run_trailer))
     -+			strvec_clear(&run_trailer.args);
     ++		if (run_command(&run_trailer)) {
     ++			die(_("unable to pass tailers to --trailers"));
     ++		}
      +		strvec_clear(&trailer_args);
      +	}
      +
     @@ builtin/commit.c: int cmd_commit(int argc, const char **argv, const char *prefix
       		OPT_STRING(0, "fixup", &fixup_message, N_("commit"), N_("use autosquash formatted message to fixup specified commit")),
       		OPT_STRING(0, "squash", &squash_message, N_("commit"), N_("use autosquash formatted message to squash specified commit")),
       		OPT_BOOL(0, "reset-author", &renew_authorship, N_("the commit is authored by me now (used with -C/-c/--amend)")),
     -+		OPT_CALLBACK_F(0, "trailer", NULL, N_("trailer"), N_("trailer(s) to add"), PARSE_OPT_NONEG, opt_pass_trailer),
     ++		OPT_CALLBACK_F(0, "trailer", NULL, N_("trailer"), N_("add custom trailer(s)"), PARSE_OPT_NONEG, opt_pass_trailer),
       		OPT_BOOL('s', "signoff", &signoff, N_("add a Signed-off-by trailer")),
       		OPT_FILENAME('t', "template", &template_file, N_("use specified template file")),
       		OPT_BOOL('e', "edit", &edit_flag, N_("force edit of commit")),
     @@ t/t7502-commit-porcelain.sh: test_expect_success 'sign off' '
       
       '
       
     -+test_expect_success 'trailer' '
     -+	>file1 &&
     -+	git add file1 &&
     -+	git commit -s --trailer "Signed-off-by:C O Mitter1 [off-list ref]" \
     -+		--trailer "Helped-by:C O Mitter2 [off-list ref]"  \
     -+		--trailer "Reported-by:C O Mitter3 [off-list ref]" \
     -+		--trailer "Mentored-by:C O Mitter4 [off-list ref]" \
     ++test_expect_success 'commit --trailer without -c' '
     ++	echo "fun" >>file &&
     ++	git add file &&
     ++	cat >expected <<-\EOF &&
     ++
     ++	Signed-off-by: C O Mitter [off-list ref]
     ++	Signed-off-by: C1 E1
     ++	Helped-by: C2 E2
     ++	Reported-by: C3 E3
     ++	Mentored-by: C4 E4
     ++	EOF
     ++	git commit -s --trailer "Signed-off-by:C1 E1 " \
     ++		--trailer "Helped-by:C2 E2 " \
     ++		--trailer "Reported-by:C3 E3" \
     ++		--trailer "Mentored-by:C4 E4" \
      +		-m "hello" &&
      +	git cat-file commit HEAD >commit.msg &&
     -+	sed -e "1,7d" commit.msg >actual &&
     ++	sed -e "1,6d" commit.msg >actual &&
     ++	test_cmp expected actual
     ++'
     ++
     ++test_expect_success 'commit --trailer with -c and "replace" as ifexists' '
     ++	echo "fun" >>file1 &&
     ++	git add file1 &&
     ++	cat >expected <<-\EOF &&
     ++
     ++	Signed-off-by: C O Mitter [off-list ref]
     ++	Signed-off-by: C1 E1
     ++	Reported-by: C3 E3
     ++	Mentored-by: C4 E4
     ++	Helped-by: C3 E3
     ++	EOF
     ++	git -c trailer.ifexists="replace" \
     ++		commit --trailer "Mentored-by: C4 E4" \
     ++		 --trailer "Helped-by: C3 E3" \
     ++		--amend &&
     ++	git cat-file commit HEAD >commit.msg &&
     ++	sed -e "1,6d" commit.msg >actual &&
     ++	test_cmp expected actual
     ++'
     ++
     ++test_expect_success 'commit --trailer with -c and "add" as ifexists' '
     ++	echo "fun" >>file1 &&
     ++	git add file1 &&
     ++	cat >expected <<-\EOF &&
     ++
     ++	Signed-off-by: C O Mitter [off-list ref]
     ++	Signed-off-by: C1 E1
     ++	Reported-by: C3 E3
     ++	Mentored-by: C4 E4
     ++	Helped-by: C3 E3
     ++	Helped-by: C3 E3
     ++	Helped-by: C3 E3
     ++	EOF
     ++	git -c trailer.ifexists="add" \
     ++		commit --trailer "Helped-by: C3 E3" \
     ++		--trailer "Helped-by: C3 E3" \
     ++		--amend &&
     ++	git cat-file commit HEAD >commit.msg &&
     ++	sed -e "1,6d" commit.msg >actual &&
     ++	test_cmp expected actual
     ++'
     ++
     ++test_expect_success 'commit --trailer with -c and "donothing" as ifexists' '
     ++	echo "fun" >>file1 &&
     ++	git add file1 &&
      +	cat >expected <<-\EOF &&
     ++
      +	Signed-off-by: C O Mitter [off-list ref]
     -+	Signed-off-by: C O Mitter1 [off-list ref]
     -+	Helped-by: C O Mitter2 [off-list ref]
     -+	Reported-by: C O Mitter3 [off-list ref]
     -+	Mentored-by: C O Mitter4 [off-list ref]
     ++	Signed-off-by: C1 E1
     ++	Reported-by: C3 E3
     ++	Mentored-by: C4 E4
     ++	Helped-by: C3 E3
     ++	Helped-by: C3 E3
     ++	Helped-by: C3 E3
     ++	Reviewed-by: C6 E6
      +	EOF
     ++	git -c trailer.ifexists="donothing" \
     ++		commit --trailer "Mentored-by: C5 E5" \
     ++		--trailer "Reviewed-by: C6 E6" \
     ++		--amend &&
     ++	git cat-file commit HEAD >commit.msg &&
     ++	sed -e "1,6d" commit.msg >actual &&
     ++	test_cmp expected actual
     ++'
     ++
     ++test_expect_success 'commit --trailer with -c and "addIfDifferent" as ifexists' '
     ++	echo "fun" >>file1 &&
     ++	git add file1 &&
     ++	cat >expected <<-\EOF &&
     ++
     ++	Signed-off-by: C O Mitter [off-list ref]
     ++	Signed-off-by: C1 E1
     ++	Reported-by: C3 E3
     ++	Mentored-by: C4 E4
     ++	Helped-by: C3 E3
     ++	Helped-by: C3 E3
     ++	Helped-by: C3 E3
     ++	Reviewed-by: C6 E6
     ++	Reported-by: C5 E5
     ++	EOF
     ++	git -c trailer.ifexists="addIfDifferent" \
     ++		commit --trailer "Reviewed-by: C6 E6" \
     ++		--trailer "Reported-by: C5 E5" \
     ++		--amend &&
     ++	git cat-file commit HEAD >commit.msg &&
     ++	sed -e "1,6d" commit.msg >actual &&
     ++	test_cmp expected actual
     ++'
     ++
     ++test_expect_success 'commit --trailer with -c and "addIfDifferentNeighbor" as ifexists' '
     ++	echo "fun" >>file1 &&
     ++	git add file1 &&
     ++	cat >expected <<-\EOF &&
     ++
     ++	Signed-off-by: C O Mitter [off-list ref]
     ++	Signed-off-by: C1 E1
     ++	Reported-by: C3 E3
     ++	Mentored-by: C4 E4
     ++	Helped-by: C3 E3
     ++	Helped-by: C3 E3
     ++	Helped-by: C3 E3
     ++	Reviewed-by: C6 E6
     ++	Reported-by: C5 E5
     ++	EOF
     ++	git -c trailer.ifexists="addIfDifferent" \
     ++		commit --trailer "Reported-by: C5 E5" \
     ++		--trailer "Reviewed-by: C6 E6" \
     ++		--amend &&
     ++	git cat-file commit HEAD >commit.msg &&
     ++	sed -e "1,6d" commit.msg >actual &&
     ++	test_cmp expected actual
     ++'
     ++
     ++test_expect_success 'commit --trailer with -c and "end" as where' '
     ++	echo "fun" >>file1 &&
     ++	git add file1 &&
     ++	cat >expected <<-\EOF &&
     ++
     ++	Signed-off-by: C O Mitter [off-list ref]
     ++	Signed-off-by: C1 E1
     ++	Reported-by: C3 E3
     ++	Mentored-by: C4 E4
     ++	Helped-by: C3 E3
     ++	Helped-by: C3 E3
     ++	Helped-by: C3 E3
     ++	Reviewed-by: C6 E6
     ++	Reported-by: C5 E5
     ++	Reported-by: C7 E7
     ++	EOF
     ++	git -c trailer.where="end" \
     ++		commit --trailer "Reported-by: C5 E5" \
     ++		--trailer "Reported-by: C7 E7" \
     ++		--amend &&
     ++	git cat-file commit HEAD >commit.msg &&
     ++	sed -e "1,6d" commit.msg >actual &&
     ++	test_cmp expected actual
     ++'
     ++
     ++test_expect_success 'commit --trailer with -c and "start" as where' '
     ++	echo "fun" >>file1 &&
     ++	git add file1 &&
     ++	cat >expected <<-\EOF &&
     ++
     ++	Signed-off-by: C8 E8
     ++	Signed-off-by: C O Mitter [off-list ref]
     ++	Signed-off-by: C1 E1
     ++	Reported-by: C3 E3
     ++	Mentored-by: C4 E4
     ++	Helped-by: C3 E3
     ++	Helped-by: C3 E3
     ++	Helped-by: C3 E3
     ++	Reviewed-by: C6 E6
     ++	Reported-by: C5 E5
     ++	Reported-by: C7 E7
     ++	EOF
     ++	git -c trailer.where="start" \
     ++		commit --trailer "Signed-off-by: C8 E8" \
     ++		--trailer "Signed-off-by: C8 E8" \
     ++		--amend &&
     ++	git cat-file commit HEAD >commit.msg &&
     ++	sed -e "1,6d" commit.msg >actual &&
     ++	test_cmp expected actual
     ++'
     ++
     ++test_expect_success 'commit --trailer with -c and "after" as where' '
     ++	echo "fun" >>file1 &&
     ++	git add file1 &&
     ++	cat >expected <<-\EOF &&
     ++
     ++	Signed-off-by: C8 E8
     ++	Signed-off-by: C O Mitter [off-list ref]
     ++	Signed-off-by: C1 E1
     ++	Reported-by: C3 E3
     ++	Mentored-by: C4 E4
     ++	Mentored-by: C9 E9
     ++	Helped-by: C3 E3
     ++	Helped-by: C3 E3
     ++	Helped-by: C3 E3
     ++	Reviewed-by: C6 E6
     ++	Reported-by: C5 E5
     ++	Reported-by: C7 E7
     ++	Reported-by: C10 E10
     ++	EOF
     ++	git -c trailer.where="after" \
     ++		commit --trailer "Mentored-by: C9 E9" \
     ++		--trailer "Reported-by: C10 E10" \
     ++		--amend &&
     ++	git cat-file commit HEAD >commit.msg &&
     ++	sed -e "1,6d" commit.msg >actual &&
     ++	test_cmp expected actual
     ++'
     ++
     ++test_expect_success 'commit --trailer with -c and "before" as where' '
     ++	echo "fun" >>file1 &&
     ++	git add file1 &&
     ++	cat >expected <<-\EOF &&
     ++
     ++	Signed-off-by: C8 E8
     ++	Signed-off-by: C O Mitter [off-list ref]
     ++	Signed-off-by: C1 E1
     ++	Reported-by: C11 E11
     ++	Reported-by: C3 E3
     ++	Mentored-by: C4 E4
     ++	Mentored-by: C9 E9
     ++	Helped-by: C12 E12
     ++	Helped-by: C3 E3
     ++	Helped-by: C3 E3
     ++	Helped-by: C3 E3
     ++	Reviewed-by: C6 E6
     ++	Reported-by: C5 E5
     ++	Reported-by: C7 E7
     ++	Reported-by: C10 E10
     ++	EOF
     ++	git -c trailer.where="before" \
     ++		commit --trailer "Helped-by: C12 E12" \
     ++		--trailer "Reported-by: C11 E11" \
     ++		--amend &&
     ++	git cat-file commit HEAD >commit.msg &&
     ++	sed -e "1,6d" commit.msg >actual &&
     ++	test_cmp expected actual
     ++'
     ++
     ++test_expect_success 'commit --trailer with -c and "donothing" as ifmissing' '
     ++	echo "fun" >>file1 &&
     ++	git add file1 &&
     ++	cat >expected <<-\EOF &&
     ++
     ++	Signed-off-by: C8 E8
     ++	Signed-off-by: C O Mitter [off-list ref]
     ++	Signed-off-by: C1 E1
     ++	Reported-by: C11 E11
     ++	Reported-by: C3 E3
     ++	Mentored-by: C4 E4
     ++	Mentored-by: C9 E9
     ++	Helped-by: C12 E12
     ++	Helped-by: C3 E3
     ++	Helped-by: C3 E3
     ++	Helped-by: C3 E3
     ++	Reviewed-by: C6 E6
     ++	Reported-by: C5 E5
     ++	Reported-by: C7 E7
     ++	Reported-by: C10 E10
     ++	Helped-by: C12 E12
     ++	EOF
     ++	git -c trailer.ifmissing="donothing" \
     ++		commit --trailer "Helped-by: C12 E12" \
     ++		--trailer "Based-by: C13 E13" \
     ++		--amend &&
     ++	git cat-file commit HEAD >commit.msg &&
     ++	sed -e "1,6d" commit.msg >actual &&
     ++	test_cmp expected actual
     ++'
     ++
     ++test_expect_success 'commit --trailer with -c and "add" as ifmissing' '
     ++	echo "fun" >>file1 &&
     ++	git add file1 &&
     ++	cat >expected <<-\EOF &&
     ++
     ++	Signed-off-by: C8 E8
     ++	Signed-off-by: C O Mitter [off-list ref]
     ++	Signed-off-by: C1 E1
     ++	Reported-by: C11 E11
     ++	Reported-by: C3 E3
     ++	Mentored-by: C4 E4
     ++	Mentored-by: C9 E9
     ++	Helped-by: C12 E12
     ++	Helped-by: C3 E3
     ++	Helped-by: C3 E3
     ++	Helped-by: C3 E3
     ++	Reviewed-by: C6 E6
     ++	Reported-by: C5 E5
     ++	Reported-by: C7 E7
     ++	Reported-by: C10 E10
     ++	Helped-by: C12 E12
     ++	Based-by: C13 E13
     ++	EOF
     ++	git -c trailer.ifmissing="add" \
     ++		commit --trailer "Helped-by: C12 E12" \
     ++		--trailer "Based-by: C13 E13" \
     ++		--amend &&
     ++	git cat-file commit HEAD >commit.msg &&
     ++	sed -e "1,6d" commit.msg >actual &&
     ++	test_cmp expected actual
     ++'
     ++
     ++test_expect_success 'commit --trailer with -c and "=" as separators' '
     ++	echo "fun" >>file1 &&
     ++	git add file1 &&
     ++	cat >expected <<-\EOF &&
     ++
     ++		Acked-by= Peff
     ++	EOF
     ++	git -c trailer.separators="=" \
     ++		-c trailer.ack.key="Acked-by= " \
     ++		commit --trailer "ack = Peff" -m "hello" &&
     ++	git cat-file commit HEAD >commit.msg &&
     ++	sed -e "1,6d" commit.msg >actual &&
     ++	test_cmp expected actual
     ++'
     ++
     ++test_expect_success 'commit --trailer with -c and ":=#" as separators' '
     ++	echo "fun" >>file1 &&
     ++	git add file1 &&
     ++	cat >expected <<-\EOF &&
     ++
     ++		Bug #42
     ++	EOF
     ++	git -c trailer.separators=":=#" \
     ++		-c trailer.bug.key="Bug #" \
     ++		commit --trailer "bug = 42" -m "I hate bug" &&
     ++	git cat-file commit HEAD >commit.msg &&
     ++	sed -e "1,6d" commit.msg >actual &&
      +	test_cmp expected actual
      +'
      +
 -:  ------------ > 2:  42590e95deee interpret-trailers: add own-identity option
 -:  ------------ > 3:  2dfcc20f0e9f commit: add own-identity option

-- 
gitgitgadget

[PATCH v10 3/3] commit: add own-identity option

From: ZheNing Hu via GitGitGadget <hidden>
Date: 2021-03-18 11:16:44

From: ZheNing Hu <redacted>

Use the newly added option `--own-identity` in `interpret-trailers`,
implement new commit option `--own-identity` to allow those trailers
with no value add the user’s own identity. Using the `--own-identity`
option, users can directly use `--trailer="Signed-off-by"` to generate
a signoff trailer with their own identities in commit messages,

The effect is basically the same as the `--signoff` option. However, users
can add more useful options at the same time. e.g. `--trailer="Helped-by"
--own-identity` can general `Helped-by: C O Mitter [off-list ref]`;
Or through appropriate configuration:
`git -c trailer.signoff.key="Signed-off-by" commit --trailer="signoff" \
--own-identity` can also general their needs trailers with their
favorite keys and their own identities.

Signed-off-by: ZheNing Hu <redacted>
---
 Documentation/git-commit.txt          | 15 +++++
 builtin/commit.c                      |  8 +++
 t/t7501-commit-basic-functionality.sh | 91 +++++++++++++++++++++++++++
 t/t7502-commit-porcelain.sh           | 20 ++++++
 4 files changed, 134 insertions(+)
diff --git a/Documentation/git-commit.txt b/Documentation/git-commit.txt
index c5de981cd40d..393c424e8184 100644
--- a/Documentation/git-commit.txt
+++ b/Documentation/git-commit.txt
@@ -174,6 +174,21 @@ include::signoff-option.txt[]
 	and the "Helped-by" trailer in the commit message.)
 	Use `git -c trailer.* commit --trailer` to make the appropriate
 	configuration. See linkgit:git-interpret-trailers[1] for details.
+
+--own-identity::
+	Used with `--trailer`. Those trailers without value with the
+	`--own-identity` option all will add the user's own identity.
+	For example, `git commit --trailer \
+	"A:B" --trailer	"Signed-off-by" --trailer "Helped-by" --own-identity`,
+	will output:
+	"
+	A:B
+	Signed-off-by: C O Mitter <committer@example.com>
+	Helped-by: C O Mitter <committer@example.com>
+	"
+	in commit messages.
+	See linkgit:git-interpret-trailers[1]for details.
+
 -n::
 --no-verify::
 	This option bypasses the pre-commit and commit-msg hooks.
diff --git a/builtin/commit.c b/builtin/commit.c
index 7a79aae48f43..0310c187ec07 100644
--- a/builtin/commit.c
+++ b/builtin/commit.c
@@ -114,6 +114,7 @@ static int no_post_rewrite, allow_empty_message, pathspec_file_nul;
 static char *untracked_files_arg, *force_date, *ignore_submodule_arg, *ignored_arg;
 static char *sign_commit, *pathspec_from_file;
 static struct strvec trailer_args = STRVEC_INIT;
+static int own_identity;
 
 /*
  * The default commit message cleanup mode will remove the lines
@@ -972,6 +973,8 @@ static int prepare_to_commit(const char *index_file, const char *prefix,
 
 		strvec_pushl(&run_trailer.args, "interpret-trailers",
 			     "--in-place", git_path_commit_editmsg(), NULL);
+		if (own_identity)
+			strvec_push(&run_trailer.args, "--own-identity");
 		strvec_pushv(&run_trailer.args, trailer_args.v);
 		run_trailer.git_cmd = 1;
 		if (run_command(&run_trailer)) {
@@ -1530,6 +1533,8 @@ int cmd_commit(int argc, const char **argv, const char *prefix)
 		OPT_STRING(0, "squash", &squash_message, N_("commit"), N_("use autosquash formatted message to squash specified commit")),
 		OPT_BOOL(0, "reset-author", &renew_authorship, N_("the commit is authored by me now (used with -C/-c/--amend)")),
 		OPT_CALLBACK_F(0, "trailer", NULL, N_("trailer"), N_("add custom trailer(s)"), PARSE_OPT_NONEG, opt_pass_trailer),
+		OPT_BOOL(0, "own-identity", &own_identity,
+			     N_("specify the user's own identity for omitted trailers value")),
 		OPT_BOOL('s', "signoff", &signoff, N_("add a Signed-off-by trailer")),
 		OPT_FILENAME('t', "template", &template_file, N_("use specified template file")),
 		OPT_BOOL('e', "edit", &edit_flag, N_("force edit of commit")),
@@ -1606,6 +1611,9 @@ int cmd_commit(int argc, const char **argv, const char *prefix)
 	if (verbose == -1)
 		verbose = (config_commit_verbose < 0) ? 0 : config_commit_verbose;
 
+	if (own_identity && !trailer_args.nr)
+		die(_("--own_identity requires --trailer"));
+
 	if (dry_run)
 		return dry_run_commit(argv, prefix, current_head, &s);
 	index_file = prepare_index(argv, prefix, current_head, 0);
diff --git a/t/t7501-commit-basic-functionality.sh b/t/t7501-commit-basic-functionality.sh
index 512ae2781fe2..7ff74cf81e75 100755
--- a/t/t7501-commit-basic-functionality.sh
+++ b/t/t7501-commit-basic-functionality.sh
@@ -423,6 +423,25 @@ test_expect_success 'sign off (1)' '
 
 '
 
+test_expect_success '--trailer="signoff" (1)' '
+
+	echo 1 >>positive &&
+	git add positive &&
+	git -c trailer.signoff.key="Signed-off-by" \
+		commit --trailer="signoff" --own-identity \
+		-m "thank you" &&
+	git cat-file commit HEAD >commit &&
+	sed -e "1,/^\$/d" commit >actual &&
+	(
+		echo thank you &&
+		echo &&
+		git var GIT_COMMITTER_IDENT >ident &&
+		sed -e "s/>.*/>/" -e "s/^/Signed-off-by: /" ident
+	) >expected &&
+	test_cmp expected actual
+
+'
+
 test_expect_success 'sign off (2)' '
 
 	echo 2 >positive &&
@@ -444,6 +463,30 @@ $existing" &&
 
 '
 
+test_expect_success '--trailer="signoff" (2)' '
+
+	echo 2 >>positive &&
+	git add positive &&
+	existing="Signed-off-by: Watch This <watchthis@example.com>" &&
+	git -c trailer.signoff.key="Signed-off-by" \
+		commit --trailer="signoff" --own-identity \
+		-m "thank you
+
+$existing" &&
+	git cat-file commit HEAD >commit &&
+	sed -e "1,/^\$/d" commit >actual &&
+	(
+		echo thank you &&
+		echo &&
+		echo $existing &&
+		git var GIT_COMMITTER_IDENT >ident &&
+		sed -e "s/>.*/>/" -e "s/^/Signed-off-by: /" ident
+	) >expected &&
+	test_cmp expected actual
+
+'
+
+
 test_expect_success 'signoff gap' '
 
 	echo 3 >positive &&
@@ -464,6 +507,29 @@ $alt" &&
 	test_cmp expected actual
 '
 
+test_expect_success '--trailer="signoff" gap' '
+
+	echo 3 >>positive &&
+	git add positive &&
+	alt="Alt-RFC-822-Header: Value" &&
+	git -c trailer.signoff.key="Signed-off-by" \
+		commit --trailer="signoff" --own-identity \
+		-m "welcome
+
+$alt" &&
+	git cat-file commit HEAD >commit &&
+	sed -e "1,/^\$/d" commit >actual &&
+	(
+		echo welcome &&
+		echo &&
+		echo $alt &&
+		git var GIT_COMMITTER_IDENT >ident &&
+		sed -e "s/>.*/>/" -e "s/^/Signed-off-by: /" ident
+	) >expected &&
+	test_cmp expected actual
+'
+
+
 test_expect_success 'signoff gap 2' '
 
 	echo 4 >positive &&
@@ -487,6 +553,31 @@ $alt" &&
 	test_cmp expected actual
 '
 
+test_expect_success '--trailer="signoff" gap 2' '
+
+	echo 4 >>positive &&
+	git add positive &&
+	alt="fixed: 34" &&
+	git -c trailer.signoff.key="Signed-off-by" \
+		commit --trailer="signoff" --own-identity \
+		-m "welcome
+
+We have now
+$alt" &&
+	git cat-file commit HEAD >commit &&
+	sed -e "1,/^\$/d" commit >actual &&
+	(
+		echo welcome &&
+		echo &&
+		echo We have now &&
+		echo $alt &&
+		echo &&
+		git var GIT_COMMITTER_IDENT >ident &&
+		sed -e "s/>.*/>/" -e "s/^/Signed-off-by: /" ident
+	) >expected &&
+	test_cmp expected actual
+'
+
 test_expect_success 'signoff respects trailer config' '
 
 	echo 5 >positive &&
diff --git a/t/t7502-commit-porcelain.sh b/t/t7502-commit-porcelain.sh
index 6df71fa00bcb..1628a1c1e7d6 100755
--- a/t/t7502-commit-porcelain.sh
+++ b/t/t7502-commit-porcelain.sh
@@ -490,6 +490,26 @@ test_expect_success 'commit --trailer with -c and ":=#" as separators' '
 	test_cmp expected actual
 '
 
+test_expect_success 'commit --trailer with -c and --own-identity' '
+	echo "fun" >>file1 &&
+	git add file1 &&
+	cat >expected <<-\EOF &&
+
+	Signed-off-by: C O Mitter <committer@example.com>
+	EOF
+	git -c trailer.signoff.key="Signed-off-by: " \
+		commit --trailer "signoff" --own-identity -m "abc" &&
+	git cat-file commit HEAD >commit.msg &&
+	sed -e "1,6d" commit.msg >actual &&
+	test_cmp expected actual
+'
+
+test_expect_success 'commit --own-identity without --trailer' '
+	echo "fun" >>file1 &&
+	git add file1 &&
+	test_must_fail git -c commit --own-identity -m "abc"
+'
+
 test_expect_success 'multiple -m' '
 
 	>negative &&
-- 
gitgitgadget

Re: [PATCH v10 0/3] [GSOC] commit: add --trailer option

From: Christian Couder <hidden>
Date: 2021-03-18 13:48:50

On Thu, Mar 18, 2021 at 12:15 PM ZheNing Hu via GitGitGadget
[off-list ref] wrote:
Now maintainers or developers can also use commit
--trailer="Signed-off-by:commiter<email>" from the command line to provide
trailers to commit messages. This solution may be more generalized than v1.

ZheNing Hu (3):
  [GSOC] commit: add --trailer option
  interpret-trailers: add own-identity option
  commit: add own-identity option
I see that you discussed an own-identity option in this thread
recently, but this is version 10 of a patch series, so I don't think
it's a good idea to introduce new features at this point. I think it's
better to not tie the work that has already been done on polishing the
first patch to new patches implementing an additional feature. The
additional feature can be discussed and worked on in its own patch
series building on top of this one.

Also it feels strange that only the first patch has "[GSOC]".

Re: [PATCH v10 0/3] [GSOC] commit: add --trailer option

From: ZheNing Hu <hidden>
Date: 2021-03-18 15:28:50

Christian Couder [off-list ref] 于2021年3月18日周四 下午9:48写道:
On Thu, Mar 18, 2021 at 12:15 PM ZheNing Hu via GitGitGadget
[off-list ref] wrote:
quoted
Now maintainers or developers can also use commit
--trailer="Signed-off-by:commiter<email>" from the command line to provide
trailers to commit messages. This solution may be more generalized than v1.

ZheNing Hu (3):
  [GSOC] commit: add --trailer option
  interpret-trailers: add own-identity option
  commit: add own-identity option
I see that you discussed an own-identity option in this thread
recently, but this is version 10 of a patch series, so I don't think
it's a good idea to introduce new features at this point. I think it's
better to not tie the work that has already been done on polishing the
first patch to new patches implementing an additional feature. The
additional feature can be discussed and worked on in its own patch
series building on top of this one.
OK, I understand that now. I will divide this patch series into two parts.
Also it feels strange that only the first patch has "[GSOC]".
I forgot to add [GSOC]...

Thanks.

Re: [PATCH v10 1/3] [GSOC] commit: add --trailer option

From: Đoàn Trần Công Danh <hidden>
Date: 2021-03-18 16:30:34

On 2021-03-18 11:15:54+0000, ZheNing Hu via GitGitGadget [off-list ref] wrote:
quoted hunk
From: ZheNing Hu <redacted>

Historically, Git has supported the 'Signed-off-by' commit trailer
using the '--signoff' and the '-s' option from the command line.
But users may need to provide other trailer information from the
command line such as "Helped-by", "Reported-by", "Mentored-by",

Now implement a new `--trailer <token>[(=|:)<value>]` option to pass
other trailers to `interpret-trailers` and insert them into commit
messages.

Signed-off-by: ZheNing Hu <redacted>
---
 Documentation/git-commit.txt |  10 +-
 builtin/commit.c             |  23 +++
 t/t7502-commit-porcelain.sh  | 336 +++++++++++++++++++++++++++++++++++
 3 files changed, 368 insertions(+), 1 deletion(-)
diff --git a/Documentation/git-commit.txt b/Documentation/git-commit.txt
index 17150fa7eabe..c5de981cd40d 100644
--- a/Documentation/git-commit.txt
+++ b/Documentation/git-commit.txt
@@ -14,7 +14,7 @@ SYNOPSIS
 	   [--allow-empty-message] [--no-verify] [-e] [--author=<author>]
 	   [--date=<date>] [--cleanup=<mode>] [--[no-]status]
 	   [-i | -o] [--pathspec-from-file=<file> [--pathspec-file-nul]]
-	   [-S[<keyid>]] [--] [<pathspec>...]
+	   [-S[<keyid>]] [--] [<pathspec>...] [(--trailer <token>[(=|:)<value>])...]
Please move all options before non-option arguments.
In other words, please move --trailer before [--].

This form implies that there are no way to specify pathspec "--trailer"
quoted hunk
 
 DESCRIPTION
 -----------
@@ -166,6 +166,14 @@ The `-m` option is mutually exclusive with `-c`, `-C`, and `-F`.
 
 include::signoff-option.txt[]
 
+--trailer <token>[(=|:)<value>]::
+	Specify a (<token>, <value>) pair that should be applied as a
+	trailer. (e.g. `git commit --trailer "Signed-off-by:C O Mitter \
+	<committer@example.com>" --trailer "Helped-by:C O Mitter \
+	<committer@example.com>"` will add the "Signed-off-by" trailer
+	and the "Helped-by" trailer in the commit message.)
+	Use `git -c trailer.* commit --trailer` to make the appropriate
+	configuration. See linkgit:git-interpret-trailers[1] for details.
 -n::
 --no-verify::
 	This option bypasses the pre-commit and commit-msg hooks.
diff --git a/builtin/commit.c b/builtin/commit.c
index 739110c5a7f6..7a79aae48f43 100644
--- a/builtin/commit.c
+++ b/builtin/commit.c
@@ -113,6 +113,7 @@ static int config_commit_verbose = -1; /* unspecified */
 static int no_post_rewrite, allow_empty_message, pathspec_file_nul;
 static char *untracked_files_arg, *force_date, *ignore_submodule_arg, *ignored_arg;
 static char *sign_commit, *pathspec_from_file;
+static struct strvec trailer_args = STRVEC_INIT;
 
 /*
  * The default commit message cleanup mode will remove the lines
@@ -131,6 +132,14 @@ static struct strbuf message = STRBUF_INIT;
 
 static enum wt_status_format status_format = STATUS_FORMAT_UNSPECIFIED;
 
+static int opt_pass_trailer(const struct option *opt, const char *arg, int unset)
+{
+	BUG_ON_OPT_NEG(unset);
+
+	strvec_pushl(&trailer_args, "--trailer", arg, NULL);
+	return 0;
+}
+
 static int opt_parse_porcelain(const struct option *opt, const char *arg, int unset)
 {
 	enum wt_status_format *value = (enum wt_status_format *)opt->value;
@@ -958,6 +967,19 @@ static int prepare_to_commit(const char *index_file, const char *prefix,
 
 	fclose(s->fp);
 
+	if (trailer_args.nr) {
+		struct child_process run_trailer = CHILD_PROCESS_INIT;
+
+		strvec_pushl(&run_trailer.args, "interpret-trailers",
+			     "--in-place", git_path_commit_editmsg(), NULL);
+		strvec_pushv(&run_trailer.args, trailer_args.v);
+		run_trailer.git_cmd = 1;
+		if (run_command(&run_trailer)) {
+			die(_("unable to pass tailers to --trailers"));
s/tailers/trailers/ perhap?
Also we usually not put {} around single statement.
quoted hunk
+		}
+		strvec_clear(&trailer_args);
+	}
+
 	/*
 	 * Reject an attempt to record a non-merge empty commit without
 	 * explicit --allow-empty. In the cherry-pick case, it may be
@@ -1507,6 +1529,7 @@ int cmd_commit(int argc, const char **argv, const char *prefix)
 		OPT_STRING(0, "fixup", &fixup_message, N_("commit"), N_("use autosquash formatted message to fixup specified commit")),
 		OPT_STRING(0, "squash", &squash_message, N_("commit"), N_("use autosquash formatted message to squash specified commit")),
 		OPT_BOOL(0, "reset-author", &renew_authorship, N_("the commit is authored by me now (used with -C/-c/--amend)")),
+		OPT_CALLBACK_F(0, "trailer", NULL, N_("trailer"), N_("add custom trailer(s)"), PARSE_OPT_NONEG, opt_pass_trailer),
 		OPT_BOOL('s', "signoff", &signoff, N_("add a Signed-off-by trailer")),
 		OPT_FILENAME('t', "template", &template_file, N_("use specified template file")),
 		OPT_BOOL('e', "edit", &edit_flag, N_("force edit of commit")),
diff --git a/t/t7502-commit-porcelain.sh b/t/t7502-commit-porcelain.sh
index 6396897cc818..6df71fa00bcb 100755
--- a/t/t7502-commit-porcelain.sh
+++ b/t/t7502-commit-porcelain.sh
@@ -154,6 +154,342 @@ test_expect_success 'sign off' '
 
 '
 
+test_expect_success 'commit --trailer without -c' '
+	echo "fun" >>file &&
+	git add file &&
+	cat >expected <<-\EOF &&
+
+	Signed-off-by: C O Mitter <committer@example.com>
+	Signed-off-by: C1 E1
+	Helped-by: C2 E2
+	Reported-by: C3 E3
+	Mentored-by: C4 E4
+	EOF
+	git commit -s --trailer "Signed-off-by:C1 E1 " \
+		--trailer "Helped-by:C2 E2 " \
+		--trailer "Reported-by:C3 E3" \
+		--trailer "Mentored-by:C4 E4" \
+		-m "hello" &&
It's documented that we're supporting --trailer <token>[(=|:)<value>]
However, only --trailer <token>:<value> is tested.
I think it's better to have 
	
	--trailer "Helped-by=C2 E2" --trailer "Reported-by"


-- 
Danh

Re: [PATCH v10 2/3] interpret-trailers: add own-identity option

From: Đoàn Trần Công Danh <hidden>
Date: 2021-03-18 16:46:17

On 2021-03-18 11:15:55+0000, ZheNing Hu via GitGitGadget [off-list ref] wrote:
From: ZheNing Hu <redacted>

Beacuse `git commit --trailer="Signed-off-by: \
s/Beacuse/Because/

And I think, it's easier to read if we write the command in its own
(indented) line.
$(git config user.name) <$(git config user.email)>"`
is difficult for users to add their own identities,
so teach interpret-trailers a new option `--own-identity`
which allow those trailers with no value add the user’s own
identity. This will help the use of `commit --trailer` as
easy as `--signoff`.
Perhap, saying that we're optionalise <value> in --trailer, by
substitute user's identity if missing instead?
quoted hunk
@@ -131,6 +144,7 @@ OPTIONS
 	when you know your input contains just the commit message itself
 	(and not an email or the output of `git format-patch`).
 
+
I think it's better to not add this line change
quoted hunk
 CONFIGURATION VARIABLES
 -----------------------
 
diff --git a/builtin/interpret-trailers.c b/builtin/interpret-trailers.c
index 84748eafc01b..be7f502a58d7 100644
--- a/builtin/interpret-trailers.c
+++ b/builtin/interpret-trailers.c
-- 
Danh

Re: [PATCH v10 1/3] [GSOC] commit: add --trailer option

From: ZheNing Hu <hidden>
Date: 2021-03-19 07:57:24

Đoàn Trần Công Danh [off-list ref] 于2021年3月19日周五 上午12:29写道:
On 2021-03-18 11:15:54+0000, ZheNing Hu via GitGitGadget [off-list ref] wrote:
quoted
From: ZheNing Hu <redacted>

Historically, Git has supported the 'Signed-off-by' commit trailer
using the '--signoff' and the '-s' option from the command line.
But users may need to provide other trailer information from the
command line such as "Helped-by", "Reported-by", "Mentored-by",

Now implement a new `--trailer <token>[(=|:)<value>]` option to pass
other trailers to `interpret-trailers` and insert them into commit
messages.

Signed-off-by: ZheNing Hu <redacted>
---
 Documentation/git-commit.txt |  10 +-
 builtin/commit.c             |  23 +++
 t/t7502-commit-porcelain.sh  | 336 +++++++++++++++++++++++++++++++++++
 3 files changed, 368 insertions(+), 1 deletion(-)
diff --git a/Documentation/git-commit.txt b/Documentation/git-commit.txt
index 17150fa7eabe..c5de981cd40d 100644
--- a/Documentation/git-commit.txt
+++ b/Documentation/git-commit.txt
@@ -14,7 +14,7 @@ SYNOPSIS
         [--allow-empty-message] [--no-verify] [-e] [--author=<author>]
         [--date=<date>] [--cleanup=<mode>] [--[no-]status]
         [-i | -o] [--pathspec-from-file=<file> [--pathspec-file-nul]]
-        [-S[<keyid>]] [--] [<pathspec>...]
+        [-S[<keyid>]] [--] [<pathspec>...] [(--trailer <token>[(=|:)<value>])...]
Please move all options before non-option arguments.
In other words, please move --trailer before [--].

This form implies that there are no way to specify pathspec "--trailer"
Thanks, I didn't pay attention to this little detail before.
quoted
 DESCRIPTION
 -----------
@@ -166,6 +166,14 @@ The `-m` option is mutually exclusive with `-c`, `-C`, and `-F`.

 include::signoff-option.txt[]

+--trailer <token>[(=|:)<value>]::
+     Specify a (<token>, <value>) pair that should be applied as a
+     trailer. (e.g. `git commit --trailer "Signed-off-by:C O Mitter \
+     <committer@example.com>" --trailer "Helped-by:C O Mitter \
+     <committer@example.com>"` will add the "Signed-off-by" trailer
+     and the "Helped-by" trailer in the commit message.)
+     Use `git -c trailer.* commit --trailer` to make the appropriate
+     configuration. See linkgit:git-interpret-trailers[1] for details.
 -n::
 --no-verify::
      This option bypasses the pre-commit and commit-msg hooks.
diff --git a/builtin/commit.c b/builtin/commit.c
index 739110c5a7f6..7a79aae48f43 100644
--- a/builtin/commit.c
+++ b/builtin/commit.c
@@ -113,6 +113,7 @@ static int config_commit_verbose = -1; /* unspecified */
 static int no_post_rewrite, allow_empty_message, pathspec_file_nul;
 static char *untracked_files_arg, *force_date, *ignore_submodule_arg, *ignored_arg;
 static char *sign_commit, *pathspec_from_file;
+static struct strvec trailer_args = STRVEC_INIT;

 /*
  * The default commit message cleanup mode will remove the lines
@@ -131,6 +132,14 @@ static struct strbuf message = STRBUF_INIT;

 static enum wt_status_format status_format = STATUS_FORMAT_UNSPECIFIED;

+static int opt_pass_trailer(const struct option *opt, const char *arg, int unset)
+{
+     BUG_ON_OPT_NEG(unset);
+
+     strvec_pushl(&trailer_args, "--trailer", arg, NULL);
+     return 0;
+}
+
 static int opt_parse_porcelain(const struct option *opt, const char *arg, int unset)
 {
      enum wt_status_format *value = (enum wt_status_format *)opt->value;
@@ -958,6 +967,19 @@ static int prepare_to_commit(const char *index_file, const char *prefix,

      fclose(s->fp);

+     if (trailer_args.nr) {
+             struct child_process run_trailer = CHILD_PROCESS_INIT;
+
+             strvec_pushl(&run_trailer.args, "interpret-trailers",
+                          "--in-place", git_path_commit_editmsg(), NULL);
+             strvec_pushv(&run_trailer.args, trailer_args.v);
+             run_trailer.git_cmd = 1;
+             if (run_command(&run_trailer)) {
+                     die(_("unable to pass tailers to --trailers"));
s/tailers/trailers/ perhap?
Also we usually not put {} around single statement.
OK.
quoted
+             }
+             strvec_clear(&trailer_args);
+     }
+
      /*
       * Reject an attempt to record a non-merge empty commit without
       * explicit --allow-empty. In the cherry-pick case, it may be
@@ -1507,6 +1529,7 @@ int cmd_commit(int argc, const char **argv, const char *prefix)
              OPT_STRING(0, "fixup", &fixup_message, N_("commit"), N_("use autosquash formatted message to fixup specified commit")),
              OPT_STRING(0, "squash", &squash_message, N_("commit"), N_("use autosquash formatted message to squash specified commit")),
              OPT_BOOL(0, "reset-author", &renew_authorship, N_("the commit is authored by me now (used with -C/-c/--amend)")),
+             OPT_CALLBACK_F(0, "trailer", NULL, N_("trailer"), N_("add custom trailer(s)"), PARSE_OPT_NONEG, opt_pass_trailer),
              OPT_BOOL('s', "signoff", &signoff, N_("add a Signed-off-by trailer")),
              OPT_FILENAME('t', "template", &template_file, N_("use specified template file")),
              OPT_BOOL('e', "edit", &edit_flag, N_("force edit of commit")),
diff --git a/t/t7502-commit-porcelain.sh b/t/t7502-commit-porcelain.sh
index 6396897cc818..6df71fa00bcb 100755
--- a/t/t7502-commit-porcelain.sh
+++ b/t/t7502-commit-porcelain.sh
@@ -154,6 +154,342 @@ test_expect_success 'sign off' '

 '

+test_expect_success 'commit --trailer without -c' '
+     echo "fun" >>file &&
+     git add file &&
+     cat >expected <<-\EOF &&
+
+     Signed-off-by: C O Mitter <committer@example.com>
+     Signed-off-by: C1 E1
+     Helped-by: C2 E2
+     Reported-by: C3 E3
+     Mentored-by: C4 E4
+     EOF
+     git commit -s --trailer "Signed-off-by:C1 E1 " \
+             --trailer "Helped-by:C2 E2 " \
+             --trailer "Reported-by:C3 E3" \
+             --trailer "Mentored-by:C4 E4" \
+             -m "hello" &&
It's documented that we're supporting --trailer <token>[(=|:)<value>]
However, only --trailer <token>:<value> is tested.
I think it's better to have

        --trailer "Helped-by=C2 E2" --trailer "Reported-by"
In fact, I want to test in `test_expect_success'commit --trailer with
-c and "=" as separators'`,
but some changes are needed.
--
Danh
-- 
ZheNing Hu

Re: [PATCH v10 2/3] interpret-trailers: add own-identity option

From: ZheNing Hu <hidden>
Date: 2021-03-19 08:04:53

Đoàn Trần Công Danh [off-list ref] 于2021年3月19日周五 上午12:45写道:
On 2021-03-18 11:15:55+0000, ZheNing Hu via GitGitGadget [off-list ref] wrote:
quoted
From: ZheNing Hu <redacted>

Beacuse `git commit --trailer="Signed-off-by: \
s/Beacuse/Because/

And I think, it's easier to read if we write the command in its own
(indented) line.
quoted
$(git config user.name) <$(git config user.email)>"`
is difficult for users to add their own identities,
so teach interpret-trailers a new option `--own-identity`
which allow those trailers with no value add the user’s own
identity. This will help the use of `commit --trailer` as
easy as `--signoff`.
Perhap, saying that we're optionalise <value> in --trailer, by
substitute user's identity if missing instead?
Indeed so.
quoted
@@ -131,6 +144,7 @@ OPTIONS
      when you know your input contains just the commit message itself
      (and not an email or the output of `git format-patch`).

+
I think it's better to not add this line change
quoted
 CONFIGURATION VARIABLES
 -----------------------
diff --git a/builtin/interpret-trailers.c b/builtin/interpret-trailers.c
index 84748eafc01b..be7f502a58d7 100644
--- a/builtin/interpret-trailers.c
+++ b/builtin/interpret-trailers.c
Thanks for these kindful advices.
--
Danh
--
ZheNing Hu

[PATCH v11] [GSOC] commit: add --trailer option

From: ZheNing Hu via GitGitGadget <hidden>
Date: 2021-03-19 12:05:54

From: ZheNing Hu <redacted>

Historically, Git has supported the 'Signed-off-by' commit trailer
using the '--signoff' and the '-s' option from the command line.
But users may need to provide other trailer information from the
command line such as "Helped-by", "Reported-by", "Mentored-by",

Now implement a new `--trailer <token>[(=|:)<value>]` option to pass
other trailers to `interpret-trailers` and insert them into commit
messages.

Signed-off-by: ZheNing Hu <redacted>
---
    [GSOC] commit: add --trailer option
    
    Now maintainers or developers can also use commit
    --trailer="Signed-off-by:commiter<email>" from the command line to
    provide trailers to commit messages. This solution may be more
    generalized than v1.

Published-As: https://github.com/gitgitgadget/git/releases/tag/pr-901%2Fadlternative%2Fcommit-with-multiple-signatures-v11
Fetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-901/adlternative/commit-with-multiple-signatures-v11
Pull-Request: https://github.com/gitgitgadget/git/pull/901

Range-diff vs v10:

 1:  949faf9ee56a ! 1:  0db1210ffd6d [GSOC] commit: add --trailer option
     @@ Documentation/git-commit.txt: SYNOPSIS
       	   [--date=<date>] [--cleanup=<mode>] [--[no-]status]
       	   [-i | -o] [--pathspec-from-file=<file> [--pathspec-file-nul]]
      -	   [-S[<keyid>]] [--] [<pathspec>...]
     -+	   [-S[<keyid>]] [--] [<pathspec>...] [(--trailer <token>[(=|:)<value>])...]
     ++	   [(--trailer <token>[(=|:)<value>])...] [-S[<keyid>]]
     ++	   [--] [<pathspec>...]
       
       DESCRIPTION
       -----------
     @@ builtin/commit.c: static int prepare_to_commit(const char *index_file, const cha
      +			     "--in-place", git_path_commit_editmsg(), NULL);
      +		strvec_pushv(&run_trailer.args, trailer_args.v);
      +		run_trailer.git_cmd = 1;
     -+		if (run_command(&run_trailer)) {
     -+			die(_("unable to pass tailers to --trailers"));
     -+		}
     ++		if (run_command(&run_trailer))
     ++			die(_("unable to pass trailers to --trailers"));
      +		strvec_clear(&trailer_args);
      +	}
      +
     @@ t/t7502-commit-porcelain.sh: test_expect_success 'sign off' '
      +	test_cmp expected actual
      +'
      +
     -+test_expect_success 'commit --trailer with -c and "=" as separators' '
     ++test_expect_success 'commit --trailer with "=" ' '
      +	echo "fun" >>file1 &&
      +	git add file1 &&
      +	cat >expected <<-\EOF &&
      +
     -+		Acked-by= Peff
     ++		Acked-by: Peff
      +	EOF
     -+	git -c trailer.separators="=" \
     -+		-c trailer.ack.key="Acked-by= " \
     ++	git -c trailer.ack.key="Acked-by" \
      +		commit --trailer "ack = Peff" -m "hello" &&
      +	git cat-file commit HEAD >commit.msg &&
      +	sed -e "1,6d" commit.msg >actual &&
 2:  42590e95deee < -:  ------------ interpret-trailers: add own-identity option
 3:  2dfcc20f0e9f < -:  ------------ commit: add own-identity option


 Documentation/git-commit.txt |  11 +-
 builtin/commit.c             |  22 +++
 t/t7502-commit-porcelain.sh  | 335 +++++++++++++++++++++++++++++++++++
 3 files changed, 367 insertions(+), 1 deletion(-)
diff --git a/Documentation/git-commit.txt b/Documentation/git-commit.txt
index 17150fa7eabe..2ba2fe0dd10e 100644
--- a/Documentation/git-commit.txt
+++ b/Documentation/git-commit.txt
@@ -14,7 +14,8 @@ SYNOPSIS
 	   [--allow-empty-message] [--no-verify] [-e] [--author=<author>]
 	   [--date=<date>] [--cleanup=<mode>] [--[no-]status]
 	   [-i | -o] [--pathspec-from-file=<file> [--pathspec-file-nul]]
-	   [-S[<keyid>]] [--] [<pathspec>...]
+	   [(--trailer <token>[(=|:)<value>])...] [-S[<keyid>]]
+	   [--] [<pathspec>...]
 
 DESCRIPTION
 -----------
@@ -166,6 +167,14 @@ The `-m` option is mutually exclusive with `-c`, `-C`, and `-F`.
 
 include::signoff-option.txt[]
 
+--trailer <token>[(=|:)<value>]::
+	Specify a (<token>, <value>) pair that should be applied as a
+	trailer. (e.g. `git commit --trailer "Signed-off-by:C O Mitter \
+	<committer@example.com>" --trailer "Helped-by:C O Mitter \
+	<committer@example.com>"` will add the "Signed-off-by" trailer
+	and the "Helped-by" trailer in the commit message.)
+	Use `git -c trailer.* commit --trailer` to make the appropriate
+	configuration. See linkgit:git-interpret-trailers[1] for details.
 -n::
 --no-verify::
 	This option bypasses the pre-commit and commit-msg hooks.
diff --git a/builtin/commit.c b/builtin/commit.c
index 739110c5a7f6..4b06672bd07d 100644
--- a/builtin/commit.c
+++ b/builtin/commit.c
@@ -113,6 +113,7 @@ static int config_commit_verbose = -1; /* unspecified */
 static int no_post_rewrite, allow_empty_message, pathspec_file_nul;
 static char *untracked_files_arg, *force_date, *ignore_submodule_arg, *ignored_arg;
 static char *sign_commit, *pathspec_from_file;
+static struct strvec trailer_args = STRVEC_INIT;
 
 /*
  * The default commit message cleanup mode will remove the lines
@@ -131,6 +132,14 @@ static struct strbuf message = STRBUF_INIT;
 
 static enum wt_status_format status_format = STATUS_FORMAT_UNSPECIFIED;
 
+static int opt_pass_trailer(const struct option *opt, const char *arg, int unset)
+{
+	BUG_ON_OPT_NEG(unset);
+
+	strvec_pushl(&trailer_args, "--trailer", arg, NULL);
+	return 0;
+}
+
 static int opt_parse_porcelain(const struct option *opt, const char *arg, int unset)
 {
 	enum wt_status_format *value = (enum wt_status_format *)opt->value;
@@ -958,6 +967,18 @@ static int prepare_to_commit(const char *index_file, const char *prefix,
 
 	fclose(s->fp);
 
+	if (trailer_args.nr) {
+		struct child_process run_trailer = CHILD_PROCESS_INIT;
+
+		strvec_pushl(&run_trailer.args, "interpret-trailers",
+			     "--in-place", git_path_commit_editmsg(), NULL);
+		strvec_pushv(&run_trailer.args, trailer_args.v);
+		run_trailer.git_cmd = 1;
+		if (run_command(&run_trailer))
+			die(_("unable to pass trailers to --trailers"));
+		strvec_clear(&trailer_args);
+	}
+
 	/*
 	 * Reject an attempt to record a non-merge empty commit without
 	 * explicit --allow-empty. In the cherry-pick case, it may be
@@ -1507,6 +1528,7 @@ int cmd_commit(int argc, const char **argv, const char *prefix)
 		OPT_STRING(0, "fixup", &fixup_message, N_("commit"), N_("use autosquash formatted message to fixup specified commit")),
 		OPT_STRING(0, "squash", &squash_message, N_("commit"), N_("use autosquash formatted message to squash specified commit")),
 		OPT_BOOL(0, "reset-author", &renew_authorship, N_("the commit is authored by me now (used with -C/-c/--amend)")),
+		OPT_CALLBACK_F(0, "trailer", NULL, N_("trailer"), N_("add custom trailer(s)"), PARSE_OPT_NONEG, opt_pass_trailer),
 		OPT_BOOL('s', "signoff", &signoff, N_("add a Signed-off-by trailer")),
 		OPT_FILENAME('t', "template", &template_file, N_("use specified template file")),
 		OPT_BOOL('e', "edit", &edit_flag, N_("force edit of commit")),
diff --git a/t/t7502-commit-porcelain.sh b/t/t7502-commit-porcelain.sh
index 6396897cc818..024cf3c81b18 100755
--- a/t/t7502-commit-porcelain.sh
+++ b/t/t7502-commit-porcelain.sh
@@ -154,6 +154,341 @@ test_expect_success 'sign off' '
 
 '
 
+test_expect_success 'commit --trailer without -c' '
+	echo "fun" >>file &&
+	git add file &&
+	cat >expected <<-\EOF &&
+
+	Signed-off-by: C O Mitter <committer@example.com>
+	Signed-off-by: C1 E1
+	Helped-by: C2 E2
+	Reported-by: C3 E3
+	Mentored-by: C4 E4
+	EOF
+	git commit -s --trailer "Signed-off-by:C1 E1 " \
+		--trailer "Helped-by:C2 E2 " \
+		--trailer "Reported-by:C3 E3" \
+		--trailer "Mentored-by:C4 E4" \
+		-m "hello" &&
+	git cat-file commit HEAD >commit.msg &&
+	sed -e "1,6d" commit.msg >actual &&
+	test_cmp expected actual
+'
+
+test_expect_success 'commit --trailer with -c and "replace" as ifexists' '
+	echo "fun" >>file1 &&
+	git add file1 &&
+	cat >expected <<-\EOF &&
+
+	Signed-off-by: C O Mitter <committer@example.com>
+	Signed-off-by: C1 E1
+	Reported-by: C3 E3
+	Mentored-by: C4 E4
+	Helped-by: C3 E3
+	EOF
+	git -c trailer.ifexists="replace" \
+		commit --trailer "Mentored-by: C4 E4" \
+		 --trailer "Helped-by: C3 E3" \
+		--amend &&
+	git cat-file commit HEAD >commit.msg &&
+	sed -e "1,6d" commit.msg >actual &&
+	test_cmp expected actual
+'
+
+test_expect_success 'commit --trailer with -c and "add" as ifexists' '
+	echo "fun" >>file1 &&
+	git add file1 &&
+	cat >expected <<-\EOF &&
+
+	Signed-off-by: C O Mitter <committer@example.com>
+	Signed-off-by: C1 E1
+	Reported-by: C3 E3
+	Mentored-by: C4 E4
+	Helped-by: C3 E3
+	Helped-by: C3 E3
+	Helped-by: C3 E3
+	EOF
+	git -c trailer.ifexists="add" \
+		commit --trailer "Helped-by: C3 E3" \
+		--trailer "Helped-by: C3 E3" \
+		--amend &&
+	git cat-file commit HEAD >commit.msg &&
+	sed -e "1,6d" commit.msg >actual &&
+	test_cmp expected actual
+'
+
+test_expect_success 'commit --trailer with -c and "donothing" as ifexists' '
+	echo "fun" >>file1 &&
+	git add file1 &&
+	cat >expected <<-\EOF &&
+
+	Signed-off-by: C O Mitter <committer@example.com>
+	Signed-off-by: C1 E1
+	Reported-by: C3 E3
+	Mentored-by: C4 E4
+	Helped-by: C3 E3
+	Helped-by: C3 E3
+	Helped-by: C3 E3
+	Reviewed-by: C6 E6
+	EOF
+	git -c trailer.ifexists="donothing" \
+		commit --trailer "Mentored-by: C5 E5" \
+		--trailer "Reviewed-by: C6 E6" \
+		--amend &&
+	git cat-file commit HEAD >commit.msg &&
+	sed -e "1,6d" commit.msg >actual &&
+	test_cmp expected actual
+'
+
+test_expect_success 'commit --trailer with -c and "addIfDifferent" as ifexists' '
+	echo "fun" >>file1 &&
+	git add file1 &&
+	cat >expected <<-\EOF &&
+
+	Signed-off-by: C O Mitter <committer@example.com>
+	Signed-off-by: C1 E1
+	Reported-by: C3 E3
+	Mentored-by: C4 E4
+	Helped-by: C3 E3
+	Helped-by: C3 E3
+	Helped-by: C3 E3
+	Reviewed-by: C6 E6
+	Reported-by: C5 E5
+	EOF
+	git -c trailer.ifexists="addIfDifferent" \
+		commit --trailer "Reviewed-by: C6 E6" \
+		--trailer "Reported-by: C5 E5" \
+		--amend &&
+	git cat-file commit HEAD >commit.msg &&
+	sed -e "1,6d" commit.msg >actual &&
+	test_cmp expected actual
+'
+
+test_expect_success 'commit --trailer with -c and "addIfDifferentNeighbor" as ifexists' '
+	echo "fun" >>file1 &&
+	git add file1 &&
+	cat >expected <<-\EOF &&
+
+	Signed-off-by: C O Mitter <committer@example.com>
+	Signed-off-by: C1 E1
+	Reported-by: C3 E3
+	Mentored-by: C4 E4
+	Helped-by: C3 E3
+	Helped-by: C3 E3
+	Helped-by: C3 E3
+	Reviewed-by: C6 E6
+	Reported-by: C5 E5
+	EOF
+	git -c trailer.ifexists="addIfDifferent" \
+		commit --trailer "Reported-by: C5 E5" \
+		--trailer "Reviewed-by: C6 E6" \
+		--amend &&
+	git cat-file commit HEAD >commit.msg &&
+	sed -e "1,6d" commit.msg >actual &&
+	test_cmp expected actual
+'
+
+test_expect_success 'commit --trailer with -c and "end" as where' '
+	echo "fun" >>file1 &&
+	git add file1 &&
+	cat >expected <<-\EOF &&
+
+	Signed-off-by: C O Mitter <committer@example.com>
+	Signed-off-by: C1 E1
+	Reported-by: C3 E3
+	Mentored-by: C4 E4
+	Helped-by: C3 E3
+	Helped-by: C3 E3
+	Helped-by: C3 E3
+	Reviewed-by: C6 E6
+	Reported-by: C5 E5
+	Reported-by: C7 E7
+	EOF
+	git -c trailer.where="end" \
+		commit --trailer "Reported-by: C5 E5" \
+		--trailer "Reported-by: C7 E7" \
+		--amend &&
+	git cat-file commit HEAD >commit.msg &&
+	sed -e "1,6d" commit.msg >actual &&
+	test_cmp expected actual
+'
+
+test_expect_success 'commit --trailer with -c and "start" as where' '
+	echo "fun" >>file1 &&
+	git add file1 &&
+	cat >expected <<-\EOF &&
+
+	Signed-off-by: C8 E8
+	Signed-off-by: C O Mitter <committer@example.com>
+	Signed-off-by: C1 E1
+	Reported-by: C3 E3
+	Mentored-by: C4 E4
+	Helped-by: C3 E3
+	Helped-by: C3 E3
+	Helped-by: C3 E3
+	Reviewed-by: C6 E6
+	Reported-by: C5 E5
+	Reported-by: C7 E7
+	EOF
+	git -c trailer.where="start" \
+		commit --trailer "Signed-off-by: C8 E8" \
+		--trailer "Signed-off-by: C8 E8" \
+		--amend &&
+	git cat-file commit HEAD >commit.msg &&
+	sed -e "1,6d" commit.msg >actual &&
+	test_cmp expected actual
+'
+
+test_expect_success 'commit --trailer with -c and "after" as where' '
+	echo "fun" >>file1 &&
+	git add file1 &&
+	cat >expected <<-\EOF &&
+
+	Signed-off-by: C8 E8
+	Signed-off-by: C O Mitter <committer@example.com>
+	Signed-off-by: C1 E1
+	Reported-by: C3 E3
+	Mentored-by: C4 E4
+	Mentored-by: C9 E9
+	Helped-by: C3 E3
+	Helped-by: C3 E3
+	Helped-by: C3 E3
+	Reviewed-by: C6 E6
+	Reported-by: C5 E5
+	Reported-by: C7 E7
+	Reported-by: C10 E10
+	EOF
+	git -c trailer.where="after" \
+		commit --trailer "Mentored-by: C9 E9" \
+		--trailer "Reported-by: C10 E10" \
+		--amend &&
+	git cat-file commit HEAD >commit.msg &&
+	sed -e "1,6d" commit.msg >actual &&
+	test_cmp expected actual
+'
+
+test_expect_success 'commit --trailer with -c and "before" as where' '
+	echo "fun" >>file1 &&
+	git add file1 &&
+	cat >expected <<-\EOF &&
+
+	Signed-off-by: C8 E8
+	Signed-off-by: C O Mitter <committer@example.com>
+	Signed-off-by: C1 E1
+	Reported-by: C11 E11
+	Reported-by: C3 E3
+	Mentored-by: C4 E4
+	Mentored-by: C9 E9
+	Helped-by: C12 E12
+	Helped-by: C3 E3
+	Helped-by: C3 E3
+	Helped-by: C3 E3
+	Reviewed-by: C6 E6
+	Reported-by: C5 E5
+	Reported-by: C7 E7
+	Reported-by: C10 E10
+	EOF
+	git -c trailer.where="before" \
+		commit --trailer "Helped-by: C12 E12" \
+		--trailer "Reported-by: C11 E11" \
+		--amend &&
+	git cat-file commit HEAD >commit.msg &&
+	sed -e "1,6d" commit.msg >actual &&
+	test_cmp expected actual
+'
+
+test_expect_success 'commit --trailer with -c and "donothing" as ifmissing' '
+	echo "fun" >>file1 &&
+	git add file1 &&
+	cat >expected <<-\EOF &&
+
+	Signed-off-by: C8 E8
+	Signed-off-by: C O Mitter <committer@example.com>
+	Signed-off-by: C1 E1
+	Reported-by: C11 E11
+	Reported-by: C3 E3
+	Mentored-by: C4 E4
+	Mentored-by: C9 E9
+	Helped-by: C12 E12
+	Helped-by: C3 E3
+	Helped-by: C3 E3
+	Helped-by: C3 E3
+	Reviewed-by: C6 E6
+	Reported-by: C5 E5
+	Reported-by: C7 E7
+	Reported-by: C10 E10
+	Helped-by: C12 E12
+	EOF
+	git -c trailer.ifmissing="donothing" \
+		commit --trailer "Helped-by: C12 E12" \
+		--trailer "Based-by: C13 E13" \
+		--amend &&
+	git cat-file commit HEAD >commit.msg &&
+	sed -e "1,6d" commit.msg >actual &&
+	test_cmp expected actual
+'
+
+test_expect_success 'commit --trailer with -c and "add" as ifmissing' '
+	echo "fun" >>file1 &&
+	git add file1 &&
+	cat >expected <<-\EOF &&
+
+	Signed-off-by: C8 E8
+	Signed-off-by: C O Mitter <committer@example.com>
+	Signed-off-by: C1 E1
+	Reported-by: C11 E11
+	Reported-by: C3 E3
+	Mentored-by: C4 E4
+	Mentored-by: C9 E9
+	Helped-by: C12 E12
+	Helped-by: C3 E3
+	Helped-by: C3 E3
+	Helped-by: C3 E3
+	Reviewed-by: C6 E6
+	Reported-by: C5 E5
+	Reported-by: C7 E7
+	Reported-by: C10 E10
+	Helped-by: C12 E12
+	Based-by: C13 E13
+	EOF
+	git -c trailer.ifmissing="add" \
+		commit --trailer "Helped-by: C12 E12" \
+		--trailer "Based-by: C13 E13" \
+		--amend &&
+	git cat-file commit HEAD >commit.msg &&
+	sed -e "1,6d" commit.msg >actual &&
+	test_cmp expected actual
+'
+
+test_expect_success 'commit --trailer with "=" ' '
+	echo "fun" >>file1 &&
+	git add file1 &&
+	cat >expected <<-\EOF &&
+
+		Acked-by: Peff
+	EOF
+	git -c trailer.ack.key="Acked-by" \
+		commit --trailer "ack = Peff" -m "hello" &&
+	git cat-file commit HEAD >commit.msg &&
+	sed -e "1,6d" commit.msg >actual &&
+	test_cmp expected actual
+'
+
+test_expect_success 'commit --trailer with -c and ":=#" as separators' '
+	echo "fun" >>file1 &&
+	git add file1 &&
+	cat >expected <<-\EOF &&
+
+		Bug #42
+	EOF
+	git -c trailer.separators=":=#" \
+		-c trailer.bug.key="Bug #" \
+		commit --trailer "bug = 42" -m "I hate bug" &&
+	git cat-file commit HEAD >commit.msg &&
+	sed -e "1,6d" commit.msg >actual &&
+	test_cmp expected actual
+'
+
 test_expect_success 'multiple -m' '
 
 	>negative &&
base-commit: 13d7ab6b5d7929825b626f050b62a11241ea4945
-- 
gitgitgadget

[PATCH v12] [GSOC] commit: add --trailer option

From: ZheNing Hu via GitGitGadget <hidden>
Date: 2021-03-20 13:44:54

From: ZheNing Hu <redacted>

Historically, Git has supported the 'Signed-off-by' commit trailer
using the '--signoff' and the '-s' option from the command line.
But users may need to provide other trailer information from the
command line such as "Helped-by", "Reported-by", "Mentored-by",

Now implement a new `--trailer <token>[(=|:)<value>]` option to pass
other trailers to `interpret-trailers` and insert them into commit
messages.

Signed-off-by: ZheNing Hu <redacted>
---
    [GSOC] commit: add --trailer option
    
    Now maintainers or developers can also use commit
    --trailer="Signed-off-by:commiter<email>" from the command line to
    provide trailers to commit messages. This solution may be more
    generalized than v1.

Published-As: https://github.com/gitgitgadget/git/releases/tag/pr-901%2Fadlternative%2Fcommit-with-multiple-signatures-v12
Fetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-901/adlternative/commit-with-multiple-signatures-v12
Pull-Request: https://github.com/gitgitgadget/git/pull/901

Range-diff vs v11:

 1:  0db1210ffd6d ! 1:  2378e3b4c1ae [GSOC] commit: add --trailer option
     @@ Documentation/git-commit.txt: The `-m` option is mutually exclusive with `-c`, `
      +	trailer. (e.g. `git commit --trailer "Signed-off-by:C O Mitter \
      +	[off-list ref]" --trailer "Helped-by:C O Mitter \
      +	[off-list ref]"` will add the "Signed-off-by" trailer
     -+	and the "Helped-by" trailer in the commit message.)
     -+	Use `git -c trailer.* commit --trailer` to make the appropriate
     -+	configuration. See linkgit:git-interpret-trailers[1] for details.
     ++	and the "Helped-by" trailer to the commit message.)
     ++	The `trailer.*` configuration variables
     ++	(linkgit:git-interpret-trailers[1]) can be used to define if
     ++	a duplicated trailer is omitted, where in the run of trailers
     ++	each trailer would appear, and other details.
     ++
       -n::
       --no-verify::
       	This option bypasses the pre-commit and commit-msg hooks.
     @@ builtin/commit.c: int cmd_commit(int argc, const char **argv, const char *prefix
       		OPT_BOOL('e', "edit", &edit_flag, N_("force edit of commit")),
      
       ## t/t7502-commit-porcelain.sh ##
     +@@ t/t7502-commit-porcelain.sh: check_summary_oneline() {
     + 	test_cmp exp act
     + }
     + 
     ++trailer_commit_base () {
     ++	echo "fun" >>file &&
     ++	git add file &&
     ++	git commit -s --trailer "Signed-off-by=C1 E1 " \
     ++		--trailer "Helped-by:C2 E2 " \
     ++		--trailer "Reported-by=C3 E3" \
     ++		--trailer "Mentored-by:C4 E4" \
     ++		-m "hello"
     ++}
     ++
     + test_expect_success 'output summary format' '
     + 
     + 	echo new >file1 &&
      @@ t/t7502-commit-porcelain.sh: test_expect_success 'sign off' '
       
       '
       
     -+test_expect_success 'commit --trailer without -c' '
     -+	echo "fun" >>file &&
     -+	git add file &&
     ++test_expect_success 'commit --trailer with "="' '
     ++	trailer_commit_base &&
      +	cat >expected <<-\EOF &&
     ++	hello
      +
      +	Signed-off-by: C O Mitter [off-list ref]
      +	Signed-off-by: C1 E1
     @@ t/t7502-commit-porcelain.sh: test_expect_success 'sign off' '
      +	Reported-by: C3 E3
      +	Mentored-by: C4 E4
      +	EOF
     -+	git commit -s --trailer "Signed-off-by:C1 E1 " \
     -+		--trailer "Helped-by:C2 E2 " \
     -+		--trailer "Reported-by:C3 E3" \
     -+		--trailer "Mentored-by:C4 E4" \
     -+		-m "hello" &&
      +	git cat-file commit HEAD >commit.msg &&
     -+	sed -e "1,6d" commit.msg >actual &&
     ++	sed -e "1,/^\$/d" commit.msg >actual &&
      +	test_cmp expected actual
      +'
      +
      +test_expect_success 'commit --trailer with -c and "replace" as ifexists' '
     -+	echo "fun" >>file1 &&
     -+	git add file1 &&
     ++	trailer_commit_base &&
      +	cat >expected <<-\EOF &&
     ++	hello
      +
      +	Signed-off-by: C O Mitter [off-list ref]
      +	Signed-off-by: C1 E1
     @@ t/t7502-commit-porcelain.sh: test_expect_success 'sign off' '
      +		 --trailer "Helped-by: C3 E3" \
      +		--amend &&
      +	git cat-file commit HEAD >commit.msg &&
     -+	sed -e "1,6d" commit.msg >actual &&
     ++	sed -e "1,/^\$/d"  commit.msg >actual &&
      +	test_cmp expected actual
      +'
      +
      +test_expect_success 'commit --trailer with -c and "add" as ifexists' '
     -+	echo "fun" >>file1 &&
     -+	git add file1 &&
     ++	trailer_commit_base &&
      +	cat >expected <<-\EOF &&
     ++	hello
      +
      +	Signed-off-by: C O Mitter [off-list ref]
      +	Signed-off-by: C1 E1
     ++	Helped-by: C2 E2
     ++	Reported-by: C3 E3
     ++	Mentored-by: C4 E4
      +	Reported-by: C3 E3
      +	Mentored-by: C4 E4
     -+	Helped-by: C3 E3
     -+	Helped-by: C3 E3
     -+	Helped-by: C3 E3
      +	EOF
      +	git -c trailer.ifexists="add" \
     -+		commit --trailer "Helped-by: C3 E3" \
     -+		--trailer "Helped-by: C3 E3" \
     ++		commit --trailer "Reported-by: C3 E3" \
     ++		--trailer "Mentored-by: C4 E4" \
      +		--amend &&
      +	git cat-file commit HEAD >commit.msg &&
     -+	sed -e "1,6d" commit.msg >actual &&
     ++	sed -e "1,/^\$/d"  commit.msg >actual &&
      +	test_cmp expected actual
      +'
      +
      +test_expect_success 'commit --trailer with -c and "donothing" as ifexists' '
     -+	echo "fun" >>file1 &&
     -+	git add file1 &&
     ++	trailer_commit_base &&
      +	cat >expected <<-\EOF &&
     ++	hello
      +
      +	Signed-off-by: C O Mitter [off-list ref]
      +	Signed-off-by: C1 E1
     ++	Helped-by: C2 E2
      +	Reported-by: C3 E3
      +	Mentored-by: C4 E4
     -+	Helped-by: C3 E3
     -+	Helped-by: C3 E3
     -+	Helped-by: C3 E3
      +	Reviewed-by: C6 E6
      +	EOF
      +	git -c trailer.ifexists="donothing" \
     @@ t/t7502-commit-porcelain.sh: test_expect_success 'sign off' '
      +		--trailer "Reviewed-by: C6 E6" \
      +		--amend &&
      +	git cat-file commit HEAD >commit.msg &&
     -+	sed -e "1,6d" commit.msg >actual &&
     ++	sed -e "1,/^\$/d"  commit.msg >actual &&
      +	test_cmp expected actual
      +'
      +
      +test_expect_success 'commit --trailer with -c and "addIfDifferent" as ifexists' '
     -+	echo "fun" >>file1 &&
     -+	git add file1 &&
     ++	trailer_commit_base &&
      +	cat >expected <<-\EOF &&
     ++	hello
      +
      +	Signed-off-by: C O Mitter [off-list ref]
      +	Signed-off-by: C1 E1
     ++	Helped-by: C2 E2
      +	Reported-by: C3 E3
      +	Mentored-by: C4 E4
     -+	Helped-by: C3 E3
     -+	Helped-by: C3 E3
     -+	Helped-by: C3 E3
     -+	Reviewed-by: C6 E6
     -+	Reported-by: C5 E5
     ++	Mentored-by: C5 E5
      +	EOF
      +	git -c trailer.ifexists="addIfDifferent" \
     -+		commit --trailer "Reviewed-by: C6 E6" \
     -+		--trailer "Reported-by: C5 E5" \
     ++		commit --trailer "Reported-by: C3 E3" \
     ++		--trailer "Mentored-by: C5 E5" \
      +		--amend &&
      +	git cat-file commit HEAD >commit.msg &&
     -+	sed -e "1,6d" commit.msg >actual &&
     ++	sed -e "1,/^\$/d"  commit.msg >actual &&
      +	test_cmp expected actual
      +'
      +
      +test_expect_success 'commit --trailer with -c and "addIfDifferentNeighbor" as ifexists' '
     -+	echo "fun" >>file1 &&
     -+	git add file1 &&
     ++	trailer_commit_base &&
      +	cat >expected <<-\EOF &&
     ++	hello
      +
      +	Signed-off-by: C O Mitter [off-list ref]
      +	Signed-off-by: C1 E1
     ++	Helped-by: C2 E2
      +	Reported-by: C3 E3
      +	Mentored-by: C4 E4
     -+	Helped-by: C3 E3
     -+	Helped-by: C3 E3
     -+	Helped-by: C3 E3
     -+	Reviewed-by: C6 E6
     -+	Reported-by: C5 E5
     ++	Reported-by: C3 E3
      +	EOF
     -+	git -c trailer.ifexists="addIfDifferent" \
     -+		commit --trailer "Reported-by: C5 E5" \
     -+		--trailer "Reviewed-by: C6 E6" \
     ++	git -c trailer.ifexists="addIfDifferentNeighbor" \
     ++		commit --trailer "Mentored-by: C4 E4" \
     ++		--trailer "Reported-by: C3 E3" \
      +		--amend &&
      +	git cat-file commit HEAD >commit.msg &&
     -+	sed -e "1,6d" commit.msg >actual &&
     ++	sed -e "1,/^\$/d"  commit.msg >actual &&
      +	test_cmp expected actual
      +'
      +
      +test_expect_success 'commit --trailer with -c and "end" as where' '
     -+	echo "fun" >>file1 &&
     -+	git add file1 &&
     ++	trailer_commit_base &&
      +	cat >expected <<-\EOF &&
     ++	hello
      +
      +	Signed-off-by: C O Mitter [off-list ref]
      +	Signed-off-by: C1 E1
     ++	Helped-by: C2 E2
     ++	Reported-by: C3 E3
     ++	Mentored-by: C4 E4
      +	Reported-by: C3 E3
      +	Mentored-by: C4 E4
     -+	Helped-by: C3 E3
     -+	Helped-by: C3 E3
     -+	Helped-by: C3 E3
     -+	Reviewed-by: C6 E6
     -+	Reported-by: C5 E5
     -+	Reported-by: C7 E7
      +	EOF
      +	git -c trailer.where="end" \
     -+		commit --trailer "Reported-by: C5 E5" \
     -+		--trailer "Reported-by: C7 E7" \
     ++		commit --trailer "Reported-by: C3 E3" \
     ++		--trailer "Mentored-by: C4 E4" \
      +		--amend &&
      +	git cat-file commit HEAD >commit.msg &&
     -+	sed -e "1,6d" commit.msg >actual &&
     ++	sed -e "1,/^\$/d" commit.msg >actual &&
      +	test_cmp expected actual
      +'
      +
      +test_expect_success 'commit --trailer with -c and "start" as where' '
     -+	echo "fun" >>file1 &&
     -+	git add file1 &&
     ++	trailer_commit_base &&
      +	cat >expected <<-\EOF &&
     ++	hello
      +
     -+	Signed-off-by: C8 E8
     ++	Signed-off-by: C1 E1
      +	Signed-off-by: C O Mitter [off-list ref]
      +	Signed-off-by: C1 E1
     ++	Helped-by: C2 E2
      +	Reported-by: C3 E3
      +	Mentored-by: C4 E4
     -+	Helped-by: C3 E3
     -+	Helped-by: C3 E3
     -+	Helped-by: C3 E3
     -+	Reviewed-by: C6 E6
     -+	Reported-by: C5 E5
     -+	Reported-by: C7 E7
      +	EOF
      +	git -c trailer.where="start" \
     -+		commit --trailer "Signed-off-by: C8 E8" \
     -+		--trailer "Signed-off-by: C8 E8" \
     ++		commit --trailer "Signed-off-by: C O Mitter [off-list ref]" \
     ++		--trailer "Signed-off-by: C1 E1" \
      +		--amend &&
      +	git cat-file commit HEAD >commit.msg &&
     -+	sed -e "1,6d" commit.msg >actual &&
     ++	sed -e "1,/^\$/d" commit.msg >actual &&
      +	test_cmp expected actual
      +'
      +
      +test_expect_success 'commit --trailer with -c and "after" as where' '
     -+	echo "fun" >>file1 &&
     -+	git add file1 &&
     ++	trailer_commit_base &&
      +	cat >expected <<-\EOF &&
     ++	hello
      +
     -+	Signed-off-by: C8 E8
      +	Signed-off-by: C O Mitter [off-list ref]
      +	Signed-off-by: C1 E1
     ++	Helped-by: C2 E2
      +	Reported-by: C3 E3
      +	Mentored-by: C4 E4
     -+	Mentored-by: C9 E9
     -+	Helped-by: C3 E3
     -+	Helped-by: C3 E3
     -+	Helped-by: C3 E3
     -+	Reviewed-by: C6 E6
     -+	Reported-by: C5 E5
     -+	Reported-by: C7 E7
     -+	Reported-by: C10 E10
     ++	Mentored-by: C5 E5
      +	EOF
      +	git -c trailer.where="after" \
     -+		commit --trailer "Mentored-by: C9 E9" \
     -+		--trailer "Reported-by: C10 E10" \
     ++		commit --trailer "Mentored-by: C4 E4" \
     ++		--trailer "Mentored-by: C5 E5" \
      +		--amend &&
      +	git cat-file commit HEAD >commit.msg &&
     -+	sed -e "1,6d" commit.msg >actual &&
     ++	sed -e "1,/^\$/d" commit.msg >actual &&
      +	test_cmp expected actual
      +'
      +
      +test_expect_success 'commit --trailer with -c and "before" as where' '
     -+	echo "fun" >>file1 &&
     -+	git add file1 &&
     ++	trailer_commit_base &&
      +	cat >expected <<-\EOF &&
     ++	hello
      +
     -+	Signed-off-by: C8 E8
      +	Signed-off-by: C O Mitter [off-list ref]
      +	Signed-off-by: C1 E1
     -+	Reported-by: C11 E11
     ++	Helped-by: C2 E2
      +	Reported-by: C3 E3
     ++	Mentored-by: C2 E2
     ++	Mentored-by: C3 E3
      +	Mentored-by: C4 E4
     -+	Mentored-by: C9 E9
     -+	Helped-by: C12 E12
     -+	Helped-by: C3 E3
     -+	Helped-by: C3 E3
     -+	Helped-by: C3 E3
     -+	Reviewed-by: C6 E6
     -+	Reported-by: C5 E5
     -+	Reported-by: C7 E7
     -+	Reported-by: C10 E10
      +	EOF
      +	git -c trailer.where="before" \
     -+		commit --trailer "Helped-by: C12 E12" \
     -+		--trailer "Reported-by: C11 E11" \
     ++		commit --trailer "Mentored-by: C3 E3" \
     ++		--trailer "Mentored-by: C2 E2" \
      +		--amend &&
      +	git cat-file commit HEAD >commit.msg &&
     -+	sed -e "1,6d" commit.msg >actual &&
     ++	sed -e "1,/^\$/d" commit.msg >actual &&
      +	test_cmp expected actual
      +'
      +
      +test_expect_success 'commit --trailer with -c and "donothing" as ifmissing' '
     -+	echo "fun" >>file1 &&
     -+	git add file1 &&
     ++	trailer_commit_base &&
      +	cat >expected <<-\EOF &&
     ++	hello
      +
     -+	Signed-off-by: C8 E8
      +	Signed-off-by: C O Mitter [off-list ref]
      +	Signed-off-by: C1 E1
     -+	Reported-by: C11 E11
     ++	Helped-by: C2 E2
      +	Reported-by: C3 E3
      +	Mentored-by: C4 E4
     -+	Mentored-by: C9 E9
     -+	Helped-by: C12 E12
     -+	Helped-by: C3 E3
     -+	Helped-by: C3 E3
     -+	Helped-by: C3 E3
     -+	Reviewed-by: C6 E6
     -+	Reported-by: C5 E5
     -+	Reported-by: C7 E7
     -+	Reported-by: C10 E10
     -+	Helped-by: C12 E12
     ++	Helped-by: C5 E5
      +	EOF
      +	git -c trailer.ifmissing="donothing" \
     -+		commit --trailer "Helped-by: C12 E12" \
     -+		--trailer "Based-by: C13 E13" \
     ++		commit --trailer "Helped-by: C5 E5" \
     ++		--trailer "Based-by: C6 E6" \
      +		--amend &&
      +	git cat-file commit HEAD >commit.msg &&
     -+	sed -e "1,6d" commit.msg >actual &&
     ++	sed -e "1,/^\$/d" commit.msg >actual &&
      +	test_cmp expected actual
      +'
      +
      +test_expect_success 'commit --trailer with -c and "add" as ifmissing' '
     -+	echo "fun" >>file1 &&
     -+	git add file1 &&
     ++	trailer_commit_base &&
      +	cat >expected <<-\EOF &&
     ++	hello
      +
     -+	Signed-off-by: C8 E8
      +	Signed-off-by: C O Mitter [off-list ref]
      +	Signed-off-by: C1 E1
     -+	Reported-by: C11 E11
     ++	Helped-by: C2 E2
      +	Reported-by: C3 E3
      +	Mentored-by: C4 E4
     -+	Mentored-by: C9 E9
     -+	Helped-by: C12 E12
     -+	Helped-by: C3 E3
     -+	Helped-by: C3 E3
     -+	Helped-by: C3 E3
     -+	Reviewed-by: C6 E6
     -+	Reported-by: C5 E5
     -+	Reported-by: C7 E7
     -+	Reported-by: C10 E10
     -+	Helped-by: C12 E12
     -+	Based-by: C13 E13
     ++	Helped-by: C5 E5
     ++	Based-by: C6 E6
      +	EOF
      +	git -c trailer.ifmissing="add" \
     -+		commit --trailer "Helped-by: C12 E12" \
     -+		--trailer "Based-by: C13 E13" \
     ++		commit --trailer "Helped-by: C5 E5" \
     ++		--trailer "Based-by: C6 E6" \
      +		--amend &&
      +	git cat-file commit HEAD >commit.msg &&
     -+	sed -e "1,6d" commit.msg >actual &&
     ++	sed -e "1,/^\$/d" commit.msg >actual &&
      +	test_cmp expected actual
      +'
      +
     -+test_expect_success 'commit --trailer with "=" ' '
     ++test_expect_success 'commit --trailer with -c ack.key ' '
      +	echo "fun" >>file1 &&
      +	git add file1 &&
      +	cat >expected <<-\EOF &&
     ++		hello
      +
      +		Acked-by: Peff
      +	EOF
      +	git -c trailer.ack.key="Acked-by" \
      +		commit --trailer "ack = Peff" -m "hello" &&
      +	git cat-file commit HEAD >commit.msg &&
     -+	sed -e "1,6d" commit.msg >actual &&
     ++	sed -e "1,/^\$/d" commit.msg >actual &&
      +	test_cmp expected actual
      +'
      +
     @@ t/t7502-commit-porcelain.sh: test_expect_success 'sign off' '
      +	echo "fun" >>file1 &&
      +	git add file1 &&
      +	cat >expected <<-\EOF &&
     ++		I hate bug
      +
      +		Bug #42
      +	EOF
     @@ t/t7502-commit-porcelain.sh: test_expect_success 'sign off' '
      +		-c trailer.bug.key="Bug #" \
      +		commit --trailer "bug = 42" -m "I hate bug" &&
      +	git cat-file commit HEAD >commit.msg &&
     -+	sed -e "1,6d" commit.msg >actual &&
     ++	sed -e "1,/^\$/d" commit.msg >actual &&
      +	test_cmp expected actual
      +'
      +


 Documentation/git-commit.txt |  14 +-
 builtin/commit.c             |  22 +++
 t/t7502-commit-porcelain.sh  | 291 +++++++++++++++++++++++++++++++++++
 3 files changed, 326 insertions(+), 1 deletion(-)
diff --git a/Documentation/git-commit.txt b/Documentation/git-commit.txt
index 17150fa7eabe..3fe7ef33cb07 100644
--- a/Documentation/git-commit.txt
+++ b/Documentation/git-commit.txt
@@ -14,7 +14,8 @@ SYNOPSIS
 	   [--allow-empty-message] [--no-verify] [-e] [--author=<author>]
 	   [--date=<date>] [--cleanup=<mode>] [--[no-]status]
 	   [-i | -o] [--pathspec-from-file=<file> [--pathspec-file-nul]]
-	   [-S[<keyid>]] [--] [<pathspec>...]
+	   [(--trailer <token>[(=|:)<value>])...] [-S[<keyid>]]
+	   [--] [<pathspec>...]
 
 DESCRIPTION
 -----------
@@ -166,6 +167,17 @@ The `-m` option is mutually exclusive with `-c`, `-C`, and `-F`.
 
 include::signoff-option.txt[]
 
+--trailer <token>[(=|:)<value>]::
+	Specify a (<token>, <value>) pair that should be applied as a
+	trailer. (e.g. `git commit --trailer "Signed-off-by:C O Mitter \
+	<committer@example.com>" --trailer "Helped-by:C O Mitter \
+	<committer@example.com>"` will add the "Signed-off-by" trailer
+	and the "Helped-by" trailer to the commit message.)
+	The `trailer.*` configuration variables
+	(linkgit:git-interpret-trailers[1]) can be used to define if
+	a duplicated trailer is omitted, where in the run of trailers
+	each trailer would appear, and other details.
+
 -n::
 --no-verify::
 	This option bypasses the pre-commit and commit-msg hooks.
diff --git a/builtin/commit.c b/builtin/commit.c
index 739110c5a7f6..4b06672bd07d 100644
--- a/builtin/commit.c
+++ b/builtin/commit.c
@@ -113,6 +113,7 @@ static int config_commit_verbose = -1; /* unspecified */
 static int no_post_rewrite, allow_empty_message, pathspec_file_nul;
 static char *untracked_files_arg, *force_date, *ignore_submodule_arg, *ignored_arg;
 static char *sign_commit, *pathspec_from_file;
+static struct strvec trailer_args = STRVEC_INIT;
 
 /*
  * The default commit message cleanup mode will remove the lines
@@ -131,6 +132,14 @@ static struct strbuf message = STRBUF_INIT;
 
 static enum wt_status_format status_format = STATUS_FORMAT_UNSPECIFIED;
 
+static int opt_pass_trailer(const struct option *opt, const char *arg, int unset)
+{
+	BUG_ON_OPT_NEG(unset);
+
+	strvec_pushl(&trailer_args, "--trailer", arg, NULL);
+	return 0;
+}
+
 static int opt_parse_porcelain(const struct option *opt, const char *arg, int unset)
 {
 	enum wt_status_format *value = (enum wt_status_format *)opt->value;
@@ -958,6 +967,18 @@ static int prepare_to_commit(const char *index_file, const char *prefix,
 
 	fclose(s->fp);
 
+	if (trailer_args.nr) {
+		struct child_process run_trailer = CHILD_PROCESS_INIT;
+
+		strvec_pushl(&run_trailer.args, "interpret-trailers",
+			     "--in-place", git_path_commit_editmsg(), NULL);
+		strvec_pushv(&run_trailer.args, trailer_args.v);
+		run_trailer.git_cmd = 1;
+		if (run_command(&run_trailer))
+			die(_("unable to pass trailers to --trailers"));
+		strvec_clear(&trailer_args);
+	}
+
 	/*
 	 * Reject an attempt to record a non-merge empty commit without
 	 * explicit --allow-empty. In the cherry-pick case, it may be
@@ -1507,6 +1528,7 @@ int cmd_commit(int argc, const char **argv, const char *prefix)
 		OPT_STRING(0, "fixup", &fixup_message, N_("commit"), N_("use autosquash formatted message to fixup specified commit")),
 		OPT_STRING(0, "squash", &squash_message, N_("commit"), N_("use autosquash formatted message to squash specified commit")),
 		OPT_BOOL(0, "reset-author", &renew_authorship, N_("the commit is authored by me now (used with -C/-c/--amend)")),
+		OPT_CALLBACK_F(0, "trailer", NULL, N_("trailer"), N_("add custom trailer(s)"), PARSE_OPT_NONEG, opt_pass_trailer),
 		OPT_BOOL('s', "signoff", &signoff, N_("add a Signed-off-by trailer")),
 		OPT_FILENAME('t', "template", &template_file, N_("use specified template file")),
 		OPT_BOOL('e', "edit", &edit_flag, N_("force edit of commit")),
diff --git a/t/t7502-commit-porcelain.sh b/t/t7502-commit-porcelain.sh
index 6396897cc818..74b1602c0ce6 100755
--- a/t/t7502-commit-porcelain.sh
+++ b/t/t7502-commit-porcelain.sh
@@ -38,6 +38,16 @@ check_summary_oneline() {
 	test_cmp exp act
 }
 
+trailer_commit_base () {
+	echo "fun" >>file &&
+	git add file &&
+	git commit -s --trailer "Signed-off-by=C1 E1 " \
+		--trailer "Helped-by:C2 E2 " \
+		--trailer "Reported-by=C3 E3" \
+		--trailer "Mentored-by:C4 E4" \
+		-m "hello"
+}
+
 test_expect_success 'output summary format' '
 
 	echo new >file1 &&
@@ -154,6 +164,287 @@ test_expect_success 'sign off' '
 
 '
 
+test_expect_success 'commit --trailer with "="' '
+	trailer_commit_base &&
+	cat >expected <<-\EOF &&
+	hello
+
+	Signed-off-by: C O Mitter <committer@example.com>
+	Signed-off-by: C1 E1
+	Helped-by: C2 E2
+	Reported-by: C3 E3
+	Mentored-by: C4 E4
+	EOF
+	git cat-file commit HEAD >commit.msg &&
+	sed -e "1,/^\$/d" commit.msg >actual &&
+	test_cmp expected actual
+'
+
+test_expect_success 'commit --trailer with -c and "replace" as ifexists' '
+	trailer_commit_base &&
+	cat >expected <<-\EOF &&
+	hello
+
+	Signed-off-by: C O Mitter <committer@example.com>
+	Signed-off-by: C1 E1
+	Reported-by: C3 E3
+	Mentored-by: C4 E4
+	Helped-by: C3 E3
+	EOF
+	git -c trailer.ifexists="replace" \
+		commit --trailer "Mentored-by: C4 E4" \
+		 --trailer "Helped-by: C3 E3" \
+		--amend &&
+	git cat-file commit HEAD >commit.msg &&
+	sed -e "1,/^\$/d"  commit.msg >actual &&
+	test_cmp expected actual
+'
+
+test_expect_success 'commit --trailer with -c and "add" as ifexists' '
+	trailer_commit_base &&
+	cat >expected <<-\EOF &&
+	hello
+
+	Signed-off-by: C O Mitter <committer@example.com>
+	Signed-off-by: C1 E1
+	Helped-by: C2 E2
+	Reported-by: C3 E3
+	Mentored-by: C4 E4
+	Reported-by: C3 E3
+	Mentored-by: C4 E4
+	EOF
+	git -c trailer.ifexists="add" \
+		commit --trailer "Reported-by: C3 E3" \
+		--trailer "Mentored-by: C4 E4" \
+		--amend &&
+	git cat-file commit HEAD >commit.msg &&
+	sed -e "1,/^\$/d"  commit.msg >actual &&
+	test_cmp expected actual
+'
+
+test_expect_success 'commit --trailer with -c and "donothing" as ifexists' '
+	trailer_commit_base &&
+	cat >expected <<-\EOF &&
+	hello
+
+	Signed-off-by: C O Mitter <committer@example.com>
+	Signed-off-by: C1 E1
+	Helped-by: C2 E2
+	Reported-by: C3 E3
+	Mentored-by: C4 E4
+	Reviewed-by: C6 E6
+	EOF
+	git -c trailer.ifexists="donothing" \
+		commit --trailer "Mentored-by: C5 E5" \
+		--trailer "Reviewed-by: C6 E6" \
+		--amend &&
+	git cat-file commit HEAD >commit.msg &&
+	sed -e "1,/^\$/d"  commit.msg >actual &&
+	test_cmp expected actual
+'
+
+test_expect_success 'commit --trailer with -c and "addIfDifferent" as ifexists' '
+	trailer_commit_base &&
+	cat >expected <<-\EOF &&
+	hello
+
+	Signed-off-by: C O Mitter <committer@example.com>
+	Signed-off-by: C1 E1
+	Helped-by: C2 E2
+	Reported-by: C3 E3
+	Mentored-by: C4 E4
+	Mentored-by: C5 E5
+	EOF
+	git -c trailer.ifexists="addIfDifferent" \
+		commit --trailer "Reported-by: C3 E3" \
+		--trailer "Mentored-by: C5 E5" \
+		--amend &&
+	git cat-file commit HEAD >commit.msg &&
+	sed -e "1,/^\$/d"  commit.msg >actual &&
+	test_cmp expected actual
+'
+
+test_expect_success 'commit --trailer with -c and "addIfDifferentNeighbor" as ifexists' '
+	trailer_commit_base &&
+	cat >expected <<-\EOF &&
+	hello
+
+	Signed-off-by: C O Mitter <committer@example.com>
+	Signed-off-by: C1 E1
+	Helped-by: C2 E2
+	Reported-by: C3 E3
+	Mentored-by: C4 E4
+	Reported-by: C3 E3
+	EOF
+	git -c trailer.ifexists="addIfDifferentNeighbor" \
+		commit --trailer "Mentored-by: C4 E4" \
+		--trailer "Reported-by: C3 E3" \
+		--amend &&
+	git cat-file commit HEAD >commit.msg &&
+	sed -e "1,/^\$/d"  commit.msg >actual &&
+	test_cmp expected actual
+'
+
+test_expect_success 'commit --trailer with -c and "end" as where' '
+	trailer_commit_base &&
+	cat >expected <<-\EOF &&
+	hello
+
+	Signed-off-by: C O Mitter <committer@example.com>
+	Signed-off-by: C1 E1
+	Helped-by: C2 E2
+	Reported-by: C3 E3
+	Mentored-by: C4 E4
+	Reported-by: C3 E3
+	Mentored-by: C4 E4
+	EOF
+	git -c trailer.where="end" \
+		commit --trailer "Reported-by: C3 E3" \
+		--trailer "Mentored-by: C4 E4" \
+		--amend &&
+	git cat-file commit HEAD >commit.msg &&
+	sed -e "1,/^\$/d" commit.msg >actual &&
+	test_cmp expected actual
+'
+
+test_expect_success 'commit --trailer with -c and "start" as where' '
+	trailer_commit_base &&
+	cat >expected <<-\EOF &&
+	hello
+
+	Signed-off-by: C1 E1
+	Signed-off-by: C O Mitter <committer@example.com>
+	Signed-off-by: C1 E1
+	Helped-by: C2 E2
+	Reported-by: C3 E3
+	Mentored-by: C4 E4
+	EOF
+	git -c trailer.where="start" \
+		commit --trailer "Signed-off-by: C O Mitter <committer@example.com>" \
+		--trailer "Signed-off-by: C1 E1" \
+		--amend &&
+	git cat-file commit HEAD >commit.msg &&
+	sed -e "1,/^\$/d" commit.msg >actual &&
+	test_cmp expected actual
+'
+
+test_expect_success 'commit --trailer with -c and "after" as where' '
+	trailer_commit_base &&
+	cat >expected <<-\EOF &&
+	hello
+
+	Signed-off-by: C O Mitter <committer@example.com>
+	Signed-off-by: C1 E1
+	Helped-by: C2 E2
+	Reported-by: C3 E3
+	Mentored-by: C4 E4
+	Mentored-by: C5 E5
+	EOF
+	git -c trailer.where="after" \
+		commit --trailer "Mentored-by: C4 E4" \
+		--trailer "Mentored-by: C5 E5" \
+		--amend &&
+	git cat-file commit HEAD >commit.msg &&
+	sed -e "1,/^\$/d" commit.msg >actual &&
+	test_cmp expected actual
+'
+
+test_expect_success 'commit --trailer with -c and "before" as where' '
+	trailer_commit_base &&
+	cat >expected <<-\EOF &&
+	hello
+
+	Signed-off-by: C O Mitter <committer@example.com>
+	Signed-off-by: C1 E1
+	Helped-by: C2 E2
+	Reported-by: C3 E3
+	Mentored-by: C2 E2
+	Mentored-by: C3 E3
+	Mentored-by: C4 E4
+	EOF
+	git -c trailer.where="before" \
+		commit --trailer "Mentored-by: C3 E3" \
+		--trailer "Mentored-by: C2 E2" \
+		--amend &&
+	git cat-file commit HEAD >commit.msg &&
+	sed -e "1,/^\$/d" commit.msg >actual &&
+	test_cmp expected actual
+'
+
+test_expect_success 'commit --trailer with -c and "donothing" as ifmissing' '
+	trailer_commit_base &&
+	cat >expected <<-\EOF &&
+	hello
+
+	Signed-off-by: C O Mitter <committer@example.com>
+	Signed-off-by: C1 E1
+	Helped-by: C2 E2
+	Reported-by: C3 E3
+	Mentored-by: C4 E4
+	Helped-by: C5 E5
+	EOF
+	git -c trailer.ifmissing="donothing" \
+		commit --trailer "Helped-by: C5 E5" \
+		--trailer "Based-by: C6 E6" \
+		--amend &&
+	git cat-file commit HEAD >commit.msg &&
+	sed -e "1,/^\$/d" commit.msg >actual &&
+	test_cmp expected actual
+'
+
+test_expect_success 'commit --trailer with -c and "add" as ifmissing' '
+	trailer_commit_base &&
+	cat >expected <<-\EOF &&
+	hello
+
+	Signed-off-by: C O Mitter <committer@example.com>
+	Signed-off-by: C1 E1
+	Helped-by: C2 E2
+	Reported-by: C3 E3
+	Mentored-by: C4 E4
+	Helped-by: C5 E5
+	Based-by: C6 E6
+	EOF
+	git -c trailer.ifmissing="add" \
+		commit --trailer "Helped-by: C5 E5" \
+		--trailer "Based-by: C6 E6" \
+		--amend &&
+	git cat-file commit HEAD >commit.msg &&
+	sed -e "1,/^\$/d" commit.msg >actual &&
+	test_cmp expected actual
+'
+
+test_expect_success 'commit --trailer with -c ack.key ' '
+	echo "fun" >>file1 &&
+	git add file1 &&
+	cat >expected <<-\EOF &&
+		hello
+
+		Acked-by: Peff
+	EOF
+	git -c trailer.ack.key="Acked-by" \
+		commit --trailer "ack = Peff" -m "hello" &&
+	git cat-file commit HEAD >commit.msg &&
+	sed -e "1,/^\$/d" commit.msg >actual &&
+	test_cmp expected actual
+'
+
+test_expect_success 'commit --trailer with -c and ":=#" as separators' '
+	echo "fun" >>file1 &&
+	git add file1 &&
+	cat >expected <<-\EOF &&
+		I hate bug
+
+		Bug #42
+	EOF
+	git -c trailer.separators=":=#" \
+		-c trailer.bug.key="Bug #" \
+		commit --trailer "bug = 42" -m "I hate bug" &&
+	git cat-file commit HEAD >commit.msg &&
+	sed -e "1,/^\$/d" commit.msg >actual &&
+	test_cmp expected actual
+'
+
 test_expect_success 'multiple -m' '
 
 	>negative &&
base-commit: 13d7ab6b5d7929825b626f050b62a11241ea4945
-- 
gitgitgadget

[PATCH v13] [GSOC] commit: add --trailer option

From: ZheNing Hu via GitGitGadget <hidden>
Date: 2021-03-22 04:25:55

From: ZheNing Hu <redacted>

Historically, Git has supported the 'Signed-off-by' commit trailer
using the '--signoff' and the '-s' option from the command line.
But users may need to provide other trailer information from the
command line such as "Helped-by", "Reported-by", "Mentored-by",

Now implement a new `--trailer <token>[(=|:)<value>]` option to pass
other trailers to `interpret-trailers` and insert them into commit
messages.

Signed-off-by: ZheNing Hu <redacted>
Reported-by:
---
    [GSOC] commit: add --trailer option
    
    Now maintainers or developers can also use commit
    --trailer="Signed-off-by:commiter<email>" from the command line to
    provide trailers to commit messages. This solution may be more
    generalized than v1.

Published-As: https://github.com/gitgitgadget/git/releases/tag/pr-901%2Fadlternative%2Fcommit-with-multiple-signatures-v13
Fetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-901/adlternative/commit-with-multiple-signatures-v13
Pull-Request: https://github.com/gitgitgadget/git/pull/901

Range-diff vs v12:

 1:  2378e3b4c1ae ! 1:  98b0c470e141 [GSOC] commit: add --trailer option
     @@ Commit message
          messages.
      
          Signed-off-by: ZheNing Hu [off-list ref]
     +    Reported-by:
      
       ## Documentation/git-commit.txt ##
      @@ Documentation/git-commit.txt: SYNOPSIS
     @@ t/t7502-commit-porcelain.sh: test_expect_success 'sign off' '
      +	sed -e "1,/^\$/d" commit.msg >actual &&
      +	test_cmp expected actual
      +'
     ++
     ++test_expect_success 'commit --trailer with -c and command' '
     ++	trailer_commit_base &&
     ++	cat >expected <<-\EOF &&
     ++	hello
     ++
     ++	Signed-off-by: C O Mitter [off-list ref]
     ++	Signed-off-by: C1 E1
     ++	Helped-by: C2 E2
     ++	Mentored-by: C4 E4
     ++	Reported-by: A U Thor [off-list ref]
     ++	EOF
     ++	git -c trailer.report.key="Reported-by: " \
     ++		-c trailer.report.ifexists="replace" \
     ++		-c trailer.report.command="git log --author=\"\$ARG\" -1 \
     ++		--format=\"format:%aN <%aE>\"" \
     ++		commit --trailer "report = author" --amend &&
     ++	git cat-file commit HEAD >commit.msg &&
     ++	sed -e "1,/^\$/d" commit.msg >actual &&
     ++	test_cmp expected actual
     ++'
      +
       test_expect_success 'multiple -m' '
       


 Documentation/git-commit.txt |  14 +-
 builtin/commit.c             |  22 +++
 t/t7502-commit-porcelain.sh  | 312 +++++++++++++++++++++++++++++++++++
 3 files changed, 347 insertions(+), 1 deletion(-)
diff --git a/Documentation/git-commit.txt b/Documentation/git-commit.txt
index 17150fa7eabe..3fe7ef33cb07 100644
--- a/Documentation/git-commit.txt
+++ b/Documentation/git-commit.txt
@@ -14,7 +14,8 @@ SYNOPSIS
 	   [--allow-empty-message] [--no-verify] [-e] [--author=<author>]
 	   [--date=<date>] [--cleanup=<mode>] [--[no-]status]
 	   [-i | -o] [--pathspec-from-file=<file> [--pathspec-file-nul]]
-	   [-S[<keyid>]] [--] [<pathspec>...]
+	   [(--trailer <token>[(=|:)<value>])...] [-S[<keyid>]]
+	   [--] [<pathspec>...]
 
 DESCRIPTION
 -----------
@@ -166,6 +167,17 @@ The `-m` option is mutually exclusive with `-c`, `-C`, and `-F`.
 
 include::signoff-option.txt[]
 
+--trailer <token>[(=|:)<value>]::
+	Specify a (<token>, <value>) pair that should be applied as a
+	trailer. (e.g. `git commit --trailer "Signed-off-by:C O Mitter \
+	<committer@example.com>" --trailer "Helped-by:C O Mitter \
+	<committer@example.com>"` will add the "Signed-off-by" trailer
+	and the "Helped-by" trailer to the commit message.)
+	The `trailer.*` configuration variables
+	(linkgit:git-interpret-trailers[1]) can be used to define if
+	a duplicated trailer is omitted, where in the run of trailers
+	each trailer would appear, and other details.
+
 -n::
 --no-verify::
 	This option bypasses the pre-commit and commit-msg hooks.
diff --git a/builtin/commit.c b/builtin/commit.c
index 739110c5a7f6..4b06672bd07d 100644
--- a/builtin/commit.c
+++ b/builtin/commit.c
@@ -113,6 +113,7 @@ static int config_commit_verbose = -1; /* unspecified */
 static int no_post_rewrite, allow_empty_message, pathspec_file_nul;
 static char *untracked_files_arg, *force_date, *ignore_submodule_arg, *ignored_arg;
 static char *sign_commit, *pathspec_from_file;
+static struct strvec trailer_args = STRVEC_INIT;
 
 /*
  * The default commit message cleanup mode will remove the lines
@@ -131,6 +132,14 @@ static struct strbuf message = STRBUF_INIT;
 
 static enum wt_status_format status_format = STATUS_FORMAT_UNSPECIFIED;
 
+static int opt_pass_trailer(const struct option *opt, const char *arg, int unset)
+{
+	BUG_ON_OPT_NEG(unset);
+
+	strvec_pushl(&trailer_args, "--trailer", arg, NULL);
+	return 0;
+}
+
 static int opt_parse_porcelain(const struct option *opt, const char *arg, int unset)
 {
 	enum wt_status_format *value = (enum wt_status_format *)opt->value;
@@ -958,6 +967,18 @@ static int prepare_to_commit(const char *index_file, const char *prefix,
 
 	fclose(s->fp);
 
+	if (trailer_args.nr) {
+		struct child_process run_trailer = CHILD_PROCESS_INIT;
+
+		strvec_pushl(&run_trailer.args, "interpret-trailers",
+			     "--in-place", git_path_commit_editmsg(), NULL);
+		strvec_pushv(&run_trailer.args, trailer_args.v);
+		run_trailer.git_cmd = 1;
+		if (run_command(&run_trailer))
+			die(_("unable to pass trailers to --trailers"));
+		strvec_clear(&trailer_args);
+	}
+
 	/*
 	 * Reject an attempt to record a non-merge empty commit without
 	 * explicit --allow-empty. In the cherry-pick case, it may be
@@ -1507,6 +1528,7 @@ int cmd_commit(int argc, const char **argv, const char *prefix)
 		OPT_STRING(0, "fixup", &fixup_message, N_("commit"), N_("use autosquash formatted message to fixup specified commit")),
 		OPT_STRING(0, "squash", &squash_message, N_("commit"), N_("use autosquash formatted message to squash specified commit")),
 		OPT_BOOL(0, "reset-author", &renew_authorship, N_("the commit is authored by me now (used with -C/-c/--amend)")),
+		OPT_CALLBACK_F(0, "trailer", NULL, N_("trailer"), N_("add custom trailer(s)"), PARSE_OPT_NONEG, opt_pass_trailer),
 		OPT_BOOL('s', "signoff", &signoff, N_("add a Signed-off-by trailer")),
 		OPT_FILENAME('t', "template", &template_file, N_("use specified template file")),
 		OPT_BOOL('e', "edit", &edit_flag, N_("force edit of commit")),
diff --git a/t/t7502-commit-porcelain.sh b/t/t7502-commit-porcelain.sh
index 6396897cc818..dd044fcea31c 100755
--- a/t/t7502-commit-porcelain.sh
+++ b/t/t7502-commit-porcelain.sh
@@ -38,6 +38,16 @@ check_summary_oneline() {
 	test_cmp exp act
 }
 
+trailer_commit_base () {
+	echo "fun" >>file &&
+	git add file &&
+	git commit -s --trailer "Signed-off-by=C1 E1 " \
+		--trailer "Helped-by:C2 E2 " \
+		--trailer "Reported-by=C3 E3" \
+		--trailer "Mentored-by:C4 E4" \
+		-m "hello"
+}
+
 test_expect_success 'output summary format' '
 
 	echo new >file1 &&
@@ -154,6 +164,308 @@ test_expect_success 'sign off' '
 
 '
 
+test_expect_success 'commit --trailer with "="' '
+	trailer_commit_base &&
+	cat >expected <<-\EOF &&
+	hello
+
+	Signed-off-by: C O Mitter <committer@example.com>
+	Signed-off-by: C1 E1
+	Helped-by: C2 E2
+	Reported-by: C3 E3
+	Mentored-by: C4 E4
+	EOF
+	git cat-file commit HEAD >commit.msg &&
+	sed -e "1,/^\$/d" commit.msg >actual &&
+	test_cmp expected actual
+'
+
+test_expect_success 'commit --trailer with -c and "replace" as ifexists' '
+	trailer_commit_base &&
+	cat >expected <<-\EOF &&
+	hello
+
+	Signed-off-by: C O Mitter <committer@example.com>
+	Signed-off-by: C1 E1
+	Reported-by: C3 E3
+	Mentored-by: C4 E4
+	Helped-by: C3 E3
+	EOF
+	git -c trailer.ifexists="replace" \
+		commit --trailer "Mentored-by: C4 E4" \
+		 --trailer "Helped-by: C3 E3" \
+		--amend &&
+	git cat-file commit HEAD >commit.msg &&
+	sed -e "1,/^\$/d"  commit.msg >actual &&
+	test_cmp expected actual
+'
+
+test_expect_success 'commit --trailer with -c and "add" as ifexists' '
+	trailer_commit_base &&
+	cat >expected <<-\EOF &&
+	hello
+
+	Signed-off-by: C O Mitter <committer@example.com>
+	Signed-off-by: C1 E1
+	Helped-by: C2 E2
+	Reported-by: C3 E3
+	Mentored-by: C4 E4
+	Reported-by: C3 E3
+	Mentored-by: C4 E4
+	EOF
+	git -c trailer.ifexists="add" \
+		commit --trailer "Reported-by: C3 E3" \
+		--trailer "Mentored-by: C4 E4" \
+		--amend &&
+	git cat-file commit HEAD >commit.msg &&
+	sed -e "1,/^\$/d"  commit.msg >actual &&
+	test_cmp expected actual
+'
+
+test_expect_success 'commit --trailer with -c and "donothing" as ifexists' '
+	trailer_commit_base &&
+	cat >expected <<-\EOF &&
+	hello
+
+	Signed-off-by: C O Mitter <committer@example.com>
+	Signed-off-by: C1 E1
+	Helped-by: C2 E2
+	Reported-by: C3 E3
+	Mentored-by: C4 E4
+	Reviewed-by: C6 E6
+	EOF
+	git -c trailer.ifexists="donothing" \
+		commit --trailer "Mentored-by: C5 E5" \
+		--trailer "Reviewed-by: C6 E6" \
+		--amend &&
+	git cat-file commit HEAD >commit.msg &&
+	sed -e "1,/^\$/d"  commit.msg >actual &&
+	test_cmp expected actual
+'
+
+test_expect_success 'commit --trailer with -c and "addIfDifferent" as ifexists' '
+	trailer_commit_base &&
+	cat >expected <<-\EOF &&
+	hello
+
+	Signed-off-by: C O Mitter <committer@example.com>
+	Signed-off-by: C1 E1
+	Helped-by: C2 E2
+	Reported-by: C3 E3
+	Mentored-by: C4 E4
+	Mentored-by: C5 E5
+	EOF
+	git -c trailer.ifexists="addIfDifferent" \
+		commit --trailer "Reported-by: C3 E3" \
+		--trailer "Mentored-by: C5 E5" \
+		--amend &&
+	git cat-file commit HEAD >commit.msg &&
+	sed -e "1,/^\$/d"  commit.msg >actual &&
+	test_cmp expected actual
+'
+
+test_expect_success 'commit --trailer with -c and "addIfDifferentNeighbor" as ifexists' '
+	trailer_commit_base &&
+	cat >expected <<-\EOF &&
+	hello
+
+	Signed-off-by: C O Mitter <committer@example.com>
+	Signed-off-by: C1 E1
+	Helped-by: C2 E2
+	Reported-by: C3 E3
+	Mentored-by: C4 E4
+	Reported-by: C3 E3
+	EOF
+	git -c trailer.ifexists="addIfDifferentNeighbor" \
+		commit --trailer "Mentored-by: C4 E4" \
+		--trailer "Reported-by: C3 E3" \
+		--amend &&
+	git cat-file commit HEAD >commit.msg &&
+	sed -e "1,/^\$/d"  commit.msg >actual &&
+	test_cmp expected actual
+'
+
+test_expect_success 'commit --trailer with -c and "end" as where' '
+	trailer_commit_base &&
+	cat >expected <<-\EOF &&
+	hello
+
+	Signed-off-by: C O Mitter <committer@example.com>
+	Signed-off-by: C1 E1
+	Helped-by: C2 E2
+	Reported-by: C3 E3
+	Mentored-by: C4 E4
+	Reported-by: C3 E3
+	Mentored-by: C4 E4
+	EOF
+	git -c trailer.where="end" \
+		commit --trailer "Reported-by: C3 E3" \
+		--trailer "Mentored-by: C4 E4" \
+		--amend &&
+	git cat-file commit HEAD >commit.msg &&
+	sed -e "1,/^\$/d" commit.msg >actual &&
+	test_cmp expected actual
+'
+
+test_expect_success 'commit --trailer with -c and "start" as where' '
+	trailer_commit_base &&
+	cat >expected <<-\EOF &&
+	hello
+
+	Signed-off-by: C1 E1
+	Signed-off-by: C O Mitter <committer@example.com>
+	Signed-off-by: C1 E1
+	Helped-by: C2 E2
+	Reported-by: C3 E3
+	Mentored-by: C4 E4
+	EOF
+	git -c trailer.where="start" \
+		commit --trailer "Signed-off-by: C O Mitter <committer@example.com>" \
+		--trailer "Signed-off-by: C1 E1" \
+		--amend &&
+	git cat-file commit HEAD >commit.msg &&
+	sed -e "1,/^\$/d" commit.msg >actual &&
+	test_cmp expected actual
+'
+
+test_expect_success 'commit --trailer with -c and "after" as where' '
+	trailer_commit_base &&
+	cat >expected <<-\EOF &&
+	hello
+
+	Signed-off-by: C O Mitter <committer@example.com>
+	Signed-off-by: C1 E1
+	Helped-by: C2 E2
+	Reported-by: C3 E3
+	Mentored-by: C4 E4
+	Mentored-by: C5 E5
+	EOF
+	git -c trailer.where="after" \
+		commit --trailer "Mentored-by: C4 E4" \
+		--trailer "Mentored-by: C5 E5" \
+		--amend &&
+	git cat-file commit HEAD >commit.msg &&
+	sed -e "1,/^\$/d" commit.msg >actual &&
+	test_cmp expected actual
+'
+
+test_expect_success 'commit --trailer with -c and "before" as where' '
+	trailer_commit_base &&
+	cat >expected <<-\EOF &&
+	hello
+
+	Signed-off-by: C O Mitter <committer@example.com>
+	Signed-off-by: C1 E1
+	Helped-by: C2 E2
+	Reported-by: C3 E3
+	Mentored-by: C2 E2
+	Mentored-by: C3 E3
+	Mentored-by: C4 E4
+	EOF
+	git -c trailer.where="before" \
+		commit --trailer "Mentored-by: C3 E3" \
+		--trailer "Mentored-by: C2 E2" \
+		--amend &&
+	git cat-file commit HEAD >commit.msg &&
+	sed -e "1,/^\$/d" commit.msg >actual &&
+	test_cmp expected actual
+'
+
+test_expect_success 'commit --trailer with -c and "donothing" as ifmissing' '
+	trailer_commit_base &&
+	cat >expected <<-\EOF &&
+	hello
+
+	Signed-off-by: C O Mitter <committer@example.com>
+	Signed-off-by: C1 E1
+	Helped-by: C2 E2
+	Reported-by: C3 E3
+	Mentored-by: C4 E4
+	Helped-by: C5 E5
+	EOF
+	git -c trailer.ifmissing="donothing" \
+		commit --trailer "Helped-by: C5 E5" \
+		--trailer "Based-by: C6 E6" \
+		--amend &&
+	git cat-file commit HEAD >commit.msg &&
+	sed -e "1,/^\$/d" commit.msg >actual &&
+	test_cmp expected actual
+'
+
+test_expect_success 'commit --trailer with -c and "add" as ifmissing' '
+	trailer_commit_base &&
+	cat >expected <<-\EOF &&
+	hello
+
+	Signed-off-by: C O Mitter <committer@example.com>
+	Signed-off-by: C1 E1
+	Helped-by: C2 E2
+	Reported-by: C3 E3
+	Mentored-by: C4 E4
+	Helped-by: C5 E5
+	Based-by: C6 E6
+	EOF
+	git -c trailer.ifmissing="add" \
+		commit --trailer "Helped-by: C5 E5" \
+		--trailer "Based-by: C6 E6" \
+		--amend &&
+	git cat-file commit HEAD >commit.msg &&
+	sed -e "1,/^\$/d" commit.msg >actual &&
+	test_cmp expected actual
+'
+
+test_expect_success 'commit --trailer with -c ack.key ' '
+	echo "fun" >>file1 &&
+	git add file1 &&
+	cat >expected <<-\EOF &&
+		hello
+
+		Acked-by: Peff
+	EOF
+	git -c trailer.ack.key="Acked-by" \
+		commit --trailer "ack = Peff" -m "hello" &&
+	git cat-file commit HEAD >commit.msg &&
+	sed -e "1,/^\$/d" commit.msg >actual &&
+	test_cmp expected actual
+'
+
+test_expect_success 'commit --trailer with -c and ":=#" as separators' '
+	echo "fun" >>file1 &&
+	git add file1 &&
+	cat >expected <<-\EOF &&
+		I hate bug
+
+		Bug #42
+	EOF
+	git -c trailer.separators=":=#" \
+		-c trailer.bug.key="Bug #" \
+		commit --trailer "bug = 42" -m "I hate bug" &&
+	git cat-file commit HEAD >commit.msg &&
+	sed -e "1,/^\$/d" commit.msg >actual &&
+	test_cmp expected actual
+'
+
+test_expect_success 'commit --trailer with -c and command' '
+	trailer_commit_base &&
+	cat >expected <<-\EOF &&
+	hello
+
+	Signed-off-by: C O Mitter <committer@example.com>
+	Signed-off-by: C1 E1
+	Helped-by: C2 E2
+	Mentored-by: C4 E4
+	Reported-by: A U Thor <author@example.com>
+	EOF
+	git -c trailer.report.key="Reported-by: " \
+		-c trailer.report.ifexists="replace" \
+		-c trailer.report.command="git log --author=\"\$ARG\" -1 \
+		--format=\"format:%aN <%aE>\"" \
+		commit --trailer "report = author" --amend &&
+	git cat-file commit HEAD >commit.msg &&
+	sed -e "1,/^\$/d" commit.msg >actual &&
+	test_cmp expected actual
+'
+
 test_expect_success 'multiple -m' '
 
 	>negative &&
base-commit: 13d7ab6b5d7929825b626f050b62a11241ea4945
-- 
gitgitgadget

Re: [PATCH v13] [GSOC] commit: add --trailer option

From: Christian Couder <hidden>
Date: 2021-03-22 07:44:37

On Mon, Mar 22, 2021 at 5:24 AM ZheNing Hu via GitGitGadget
[off-list ref] wrote:
From: ZheNing Hu <redacted>

Historically, Git has supported the 'Signed-off-by' commit trailer
using the '--signoff' and the '-s' option from the command line.
But users may need to provide other trailer information from the
command line such as "Helped-by", "Reported-by", "Mentored-by",

Now implement a new `--trailer <token>[(=|:)<value>]` option to pass
other trailers to `interpret-trailers` and insert them into commit
messages.

Signed-off-by: ZheNing Hu <redacted>
Reported-by:
Why is there this "Reported-by:" trailer with an empty value? If you
are looking to add trailers to this commit message, you might want to
add them before your "Signed-off-by".
quoted hunk
---
    [GSOC] commit: add --trailer option

    Now maintainers or developers can also use commit
    --trailer="Signed-off-by:commiter<email>" from the command line to
    provide trailers to commit messages. This solution may be more
    generalized than v1.
It's not a big deal as this is not going into the commit message, but
at this point (v13) you might want to tell explicitly what solution v1
implemented, instead of referring to it.
     @@ t/t7502-commit-porcelain.sh: test_expect_success 'sign off' '>       + sed -e "1,/^\$/d" commit.msg >actual &&
      + test_cmp expected actual
      +'
     ++
     ++test_expect_success 'commit --trailer with -c and command' '
     ++ trailer_commit_base &&
     ++ cat >expected <<-\EOF &&
     ++ hello
     ++
     ++ Signed-off-by: C O Mitter [off-list ref]
     ++ Signed-off-by: C1 E1
     ++ Helped-by: C2 E2
     ++ Mentored-by: C4 E4
     ++ Reported-by: A U Thor [off-list ref]
     ++ EOF
     ++ git -c trailer.report.key="Reported-by: " \
     ++         -c trailer.report.ifexists="replace" \
     ++         -c trailer.report.command="git log --author=\"\$ARG\" -1 \
     ++         --format=\"format:%aN <%aE>\"" \
     ++         commit --trailer "report = author" --amend &&
     ++ git cat-file commit HEAD >commit.msg &&
     ++ sed -e "1,/^\$/d" commit.msg >actual &&
     ++ test_cmp expected actual
     ++'
Nice that you have added such a test!

Re: [PATCH v13] [GSOC] commit: add --trailer option

From: ZheNing Hu <hidden>
Date: 2021-03-22 10:24:32

Christian Couder [off-list ref] 于2021年3月22日周一 下午3:43写道:
On Mon, Mar 22, 2021 at 5:24 AM ZheNing Hu via GitGitGadget
[off-list ref] wrote:
quoted
From: ZheNing Hu <redacted>

Historically, Git has supported the 'Signed-off-by' commit trailer
using the '--signoff' and the '-s' option from the command line.
But users may need to provide other trailer information from the
command line such as "Helped-by", "Reported-by", "Mentored-by",

Now implement a new `--trailer <token>[(=|:)<value>]` option to pass
other trailers to `interpret-trailers` and insert them into commit
messages.

Signed-off-by: ZheNing Hu <redacted>
Reported-by:
Why is there this "Reported-by:" trailer with an empty value? If you
are looking to add trailers to this commit message, you might want to
add them before your "Signed-off-by".
Sorry, It was purely a small accident during testing and I didn't notice it.
quoted
---
    [GSOC] commit: add --trailer option

    Now maintainers or developers can also use commit
    --trailer="Signed-off-by:commiter<email>" from the command line to
    provide trailers to commit messages. This solution may be more
    generalized than v1.
It's not a big deal as this is not going into the commit message, but
at this point (v13) you might want to tell explicitly what solution v1
implemented, instead of referring to it.
Ok.
quoted
     @@ t/t7502-commit-porcelain.sh: test_expect_success 'sign off' '>       + sed -e "1,/^\$/d" commit.msg >actual &&
      + test_cmp expected actual
      +'
     ++
     ++test_expect_success 'commit --trailer with -c and command' '
     ++ trailer_commit_base &&
     ++ cat >expected <<-\EOF &&
     ++ hello
     ++
     ++ Signed-off-by: C O Mitter [off-list ref]
     ++ Signed-off-by: C1 E1
     ++ Helped-by: C2 E2
     ++ Mentored-by: C4 E4
     ++ Reported-by: A U Thor [off-list ref]
     ++ EOF
     ++ git -c trailer.report.key="Reported-by: " \
     ++         -c trailer.report.ifexists="replace" \
     ++         -c trailer.report.command="git log --author=\"\$ARG\" -1 \
     ++         --format=\"format:%aN <%aE>\"" \
     ++         commit --trailer "report = author" --amend &&
     ++ git cat-file commit HEAD >commit.msg &&
     ++ sed -e "1,/^\$/d" commit.msg >actual &&
     ++ test_cmp expected actual
     ++'
Nice that you have added such a test!
Thanks.

But at the same time I have two little doubt.

1.
If we have your config:

$ git config trailer.sign.key "Signed-off-by: "
$ git config trailer.sign.ifexists replace
$ git config trailer.sign.command "git log --author='\$ARG' -1
--format='format:%aN <%aE>'"

Then I touch a test.c and use:

$ git interpret-trailers --in-place test.c

without `--trailer`, See what is happen:

It seem like your local repo last commit "name <email>" pair
have been record in `test.c`.

Could this be considered a bug?

2.
`git interpret-trailers --in-place`  seem like work on git top-dir,
If I am in a sub-dir `b` and I want to change a file such as `d.c`,
then I must use `git interpret-trailers --in-place b/d.c` to add some
trailers.

I think the original intention of `--in-place` is to modify a file similar to
"$COMMIT_MSG_FILE", so make it run at top-dir, but this is not reflected
in the git documentation. This at least confuses people who use this
option for the first time. Is it worth modifying? Or is there something
wrong with the design of `--in-place`?

--
ZheNing Hu

Re: [PATCH v13] [GSOC] commit: add --trailer option

From: Christian Couder <hidden>
Date: 2021-03-22 21:35:33

On Mon, Mar 22, 2021 at 11:23 AM ZheNing Hu [off-list ref] wrote:
Christian Couder [off-list ref] 于2021年3月22日周一 下午3:43写道:
quoted
Nice that you have added such a test!
Thanks.

But at the same time I have two little doubt.

1.
If we have your config:

$ git config trailer.sign.key "Signed-off-by: "
$ git config trailer.sign.ifexists replace
$ git config trailer.sign.command "git log --author='\$ARG' -1
--format='format:%aN <%aE>'"

Then I touch a test.c and use:

$ git interpret-trailers --in-place test.c

without `--trailer`, See what is happen:

It seem like your local repo last commit "name <email>" pair
have been record in `test.c`.

Could this be considered a bug?
First it seems strange to use `git interpret-trailers` on a "test.c"
file. It's supposed to be used on commit messages.

Then, as the doc says, every command specified by any
"trailer.<token>.command" config option is run at least once when `git
interpret-trailers` is run. This is because users might want to
automatically add some trailers all the time.

If you want nothing to happen when $ARG isn't set, you can change the
config option to something like:

$ git config trailer.sign.command "NAME='\$ARG'; test -n \"\$NAME\" &&
git log --author=\"\$NAME\" -1 --format='format:%aN <%aE>' || true"

(This is because it looks like $ARG is replaced only once with the
actual value, which is perhaps a bug. Otherwise something like the
following might work:

git config trailer.sign.command "test -n '\$ARG' && git log
--author='\$ARG' -1 --format='format:%aN <%aE>' || true")

Then you can run `git interpret-trailers` with the --trim-empty option
like this:

------
$ git interpret-trailers --trim-empty --trailer sign=Linus<<EOF
EOF

Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
------

or like:

------
$ git interpret-trailers --trim-empty<<EOF
EOF
------

Re: [PATCH v13] [GSOC] commit: add --trailer option

From: Christian Couder <hidden>
Date: 2021-03-22 21:56:33

On Mon, Mar 22, 2021 at 11:23 AM ZheNing Hu [off-list ref] wrote:
2.
`git interpret-trailers --in-place`  seem like work on git top-dir,
If I am in a sub-dir `b` and I want to change a file such as `d.c`,
then I must use `git interpret-trailers --in-place b/d.c` to add some
trailers.
What happens without --in-place? Are the input files read correctly?
I think the original intention of `--in-place` is to modify a file similar to
"$COMMIT_MSG_FILE", so make it run at top-dir, but this is not reflected
in the git documentation. This at least confuses people who use this
option for the first time. Is it worth modifying? Or is there something
wrong with the design of `--in-place`?
I haven't checked but there is perhaps a bug in
create_in_place_tempfile() in "trailer.c".

Re: [PATCH v13] [GSOC] commit: add --trailer option

From: ZheNing Hu <hidden>
Date: 2021-03-23 06:12:42

Christian Couder [off-list ref] 于2021年3月23日周二 上午5:34写道:
On Mon, Mar 22, 2021 at 11:23 AM ZheNing Hu [off-list ref] wrote:
quoted
Christian Couder [off-list ref] 于2021年3月22日周一 下午3:43写道:
quoted
quoted
Nice that you have added such a test!
Thanks.

But at the same time I have two little doubt.

1.
If we have your config:

$ git config trailer.sign.key "Signed-off-by: "
$ git config trailer.sign.ifexists replace
$ git config trailer.sign.command "git log --author='\$ARG' -1
--format='format:%aN <%aE>'"

Then I touch a test.c and use:

$ git interpret-trailers --in-place test.c

without `--trailer`, See what is happen:

It seem like your local repo last commit "name <email>" pair
have been record in `test.c`.

Could this be considered a bug?
First it seems strange to use `git interpret-trailers` on a "test.c"
file. It's supposed to be used on commit messages.

Then, as the doc says, every command specified by any
"trailer.<token>.command" config option is run at least once when `git
interpret-trailers` is run. This is because users might want to
automatically add some trailers all the time.
Well, I understand it now.
If you want nothing to happen when $ARG isn't set, you can change the
config option to something like:

$ git config trailer.sign.command "NAME='\$ARG'; test -n \"\$NAME\" &&
git log --author=\"\$NAME\" -1 --format='format:%aN <%aE>' || true"

(This is because it looks like $ARG is replaced only once with the
actual value, which is perhaps a bug. Otherwise something like the
following might work:
this is because `$ARG` is replaced in "trailer.c" by "strbuf_replace" which
only replcae the specified string for only one time,
I think `strbuf_replace()` can be changed like:
@@ -110,8 +110,9 @@ static inline int is_blank_line(const char *str)

 static inline void strbuf_replace(struct strbuf *sb, const char *a,
const char *b)
 {
-       const char *ptr = strstr(sb->buf, a);
-       if (ptr)
+       char *ptr = sb->buf;
+
+       while ((ptr = strstr(ptr, a)))
                strbuf_splice(sb, ptr - sb->buf, strlen(a), b, strlen(b));
 }
git config trailer.sign.command "test -n '\$ARG' && git log
--author='\$ARG' -1 --format='format:%aN <%aE>' || true")

Then you can run `git interpret-trailers` with the --trim-empty option
like this:

------
$ git interpret-trailers --trim-empty --trailer sign=Linus<<EOF
EOF

Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
------

or like:

------
$ git interpret-trailers --trim-empty<<EOF
quoted
EOF
------
Thanks for effective solution.
Maybe I should also correct this part of the test.

Re: [PATCH v13] [GSOC] commit: add --trailer option

From: ZheNing Hu <hidden>
Date: 2021-03-23 06:30:16

Christian Couder [off-list ref] 于2021年3月23日周二 上午5:55写道:
On Mon, Mar 22, 2021 at 11:23 AM ZheNing Hu [off-list ref] wrote:
quoted
2.
`git interpret-trailers --in-place`  seem like work on git top-dir,
If I am in a sub-dir `b` and I want to change a file such as `d.c`,
then I must use `git interpret-trailers --in-place b/d.c` to add some
trailers.
What happens without --in-place? Are the input files read correctly?
It's still wrong.
the git die() in `read_input_file` of "trailer.c".
quoted
I think the original intention of `--in-place` is to modify a file similar to
"$COMMIT_MSG_FILE", so make it run at top-dir, but this is not reflected
in the git documentation. This at least confuses people who use this
option for the first time. Is it worth modifying? Or is there something
wrong with the design of `--in-place`?
I haven't checked but there is perhaps a bug in
create_in_place_tempfile() in "trailer.c".
I haven't check finished. But I think it may do something like `chdir()`.

Thanks.

[PATCH v14] [GSOC] commit: add --trailer option

From: ZheNing Hu via GitGitGadget <hidden>
Date: 2021-03-23 13:56:42

From: ZheNing Hu <redacted>

Historically, Git has supported the 'Signed-off-by' commit trailer
using the '--signoff' and the '-s' option from the command line.
But users may need to provide other trailer information from the
command line such as "Helped-by", "Reported-by", "Mentored-by",

Now implement a new `--trailer <token>[(=|:)<value>]` option to pass
other trailers to `interpret-trailers` and insert them into commit
messages.

Signed-off-by: ZheNing Hu <redacted>
---
    [GSOC] commit: add --trailer option
    
    commit --trailer connecting to the interpret-trailers backend, which can
    directly generate trailers similar to "Signed-off-by". Hope this will
    make it easier for programmers and maintainers :)

Published-As: https://github.com/gitgitgadget/git/releases/tag/pr-901%2Fadlternative%2Fcommit-with-multiple-signatures-v14
Fetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-901/adlternative/commit-with-multiple-signatures-v14
Pull-Request: https://github.com/gitgitgadget/git/pull/901

Range-diff vs v13:

 1:  98b0c470e141 ! 1:  bb36af7c4827 [GSOC] commit: add --trailer option
     @@ Commit message
          messages.
      
          Signed-off-by: ZheNing Hu [off-list ref]
     -    Reported-by:
      
       ## Documentation/git-commit.txt ##
      @@ Documentation/git-commit.txt: SYNOPSIS
     @@ t/t7502-commit-porcelain.sh: test_expect_success 'sign off' '
      +	EOF
      +	git -c trailer.report.key="Reported-by: " \
      +		-c trailer.report.ifexists="replace" \
     -+		-c trailer.report.command="git log --author=\"\$ARG\" -1 \
     -+		--format=\"format:%aN <%aE>\"" \
     ++		-c trailer.report.command="NAME=\"\$ARG\"; test -n \"\$NAME\" && \
     ++		git log --author=\"\$NAME\" -1 --format=\"format:%aN <%aE>\" || true" \
      +		commit --trailer "report = author" --amend &&
      +	git cat-file commit HEAD >commit.msg &&
      +	sed -e "1,/^\$/d" commit.msg >actual &&


 Documentation/git-commit.txt |  14 +-
 builtin/commit.c             |  22 +++
 t/t7502-commit-porcelain.sh  | 312 +++++++++++++++++++++++++++++++++++
 3 files changed, 347 insertions(+), 1 deletion(-)
diff --git a/Documentation/git-commit.txt b/Documentation/git-commit.txt
index 17150fa7eabe..3fe7ef33cb07 100644
--- a/Documentation/git-commit.txt
+++ b/Documentation/git-commit.txt
@@ -14,7 +14,8 @@ SYNOPSIS
 	   [--allow-empty-message] [--no-verify] [-e] [--author=<author>]
 	   [--date=<date>] [--cleanup=<mode>] [--[no-]status]
 	   [-i | -o] [--pathspec-from-file=<file> [--pathspec-file-nul]]
-	   [-S[<keyid>]] [--] [<pathspec>...]
+	   [(--trailer <token>[(=|:)<value>])...] [-S[<keyid>]]
+	   [--] [<pathspec>...]
 
 DESCRIPTION
 -----------
@@ -166,6 +167,17 @@ The `-m` option is mutually exclusive with `-c`, `-C`, and `-F`.
 
 include::signoff-option.txt[]
 
+--trailer <token>[(=|:)<value>]::
+	Specify a (<token>, <value>) pair that should be applied as a
+	trailer. (e.g. `git commit --trailer "Signed-off-by:C O Mitter \
+	<committer@example.com>" --trailer "Helped-by:C O Mitter \
+	<committer@example.com>"` will add the "Signed-off-by" trailer
+	and the "Helped-by" trailer to the commit message.)
+	The `trailer.*` configuration variables
+	(linkgit:git-interpret-trailers[1]) can be used to define if
+	a duplicated trailer is omitted, where in the run of trailers
+	each trailer would appear, and other details.
+
 -n::
 --no-verify::
 	This option bypasses the pre-commit and commit-msg hooks.
diff --git a/builtin/commit.c b/builtin/commit.c
index 739110c5a7f6..4b06672bd07d 100644
--- a/builtin/commit.c
+++ b/builtin/commit.c
@@ -113,6 +113,7 @@ static int config_commit_verbose = -1; /* unspecified */
 static int no_post_rewrite, allow_empty_message, pathspec_file_nul;
 static char *untracked_files_arg, *force_date, *ignore_submodule_arg, *ignored_arg;
 static char *sign_commit, *pathspec_from_file;
+static struct strvec trailer_args = STRVEC_INIT;
 
 /*
  * The default commit message cleanup mode will remove the lines
@@ -131,6 +132,14 @@ static struct strbuf message = STRBUF_INIT;
 
 static enum wt_status_format status_format = STATUS_FORMAT_UNSPECIFIED;
 
+static int opt_pass_trailer(const struct option *opt, const char *arg, int unset)
+{
+	BUG_ON_OPT_NEG(unset);
+
+	strvec_pushl(&trailer_args, "--trailer", arg, NULL);
+	return 0;
+}
+
 static int opt_parse_porcelain(const struct option *opt, const char *arg, int unset)
 {
 	enum wt_status_format *value = (enum wt_status_format *)opt->value;
@@ -958,6 +967,18 @@ static int prepare_to_commit(const char *index_file, const char *prefix,
 
 	fclose(s->fp);
 
+	if (trailer_args.nr) {
+		struct child_process run_trailer = CHILD_PROCESS_INIT;
+
+		strvec_pushl(&run_trailer.args, "interpret-trailers",
+			     "--in-place", git_path_commit_editmsg(), NULL);
+		strvec_pushv(&run_trailer.args, trailer_args.v);
+		run_trailer.git_cmd = 1;
+		if (run_command(&run_trailer))
+			die(_("unable to pass trailers to --trailers"));
+		strvec_clear(&trailer_args);
+	}
+
 	/*
 	 * Reject an attempt to record a non-merge empty commit without
 	 * explicit --allow-empty. In the cherry-pick case, it may be
@@ -1507,6 +1528,7 @@ int cmd_commit(int argc, const char **argv, const char *prefix)
 		OPT_STRING(0, "fixup", &fixup_message, N_("commit"), N_("use autosquash formatted message to fixup specified commit")),
 		OPT_STRING(0, "squash", &squash_message, N_("commit"), N_("use autosquash formatted message to squash specified commit")),
 		OPT_BOOL(0, "reset-author", &renew_authorship, N_("the commit is authored by me now (used with -C/-c/--amend)")),
+		OPT_CALLBACK_F(0, "trailer", NULL, N_("trailer"), N_("add custom trailer(s)"), PARSE_OPT_NONEG, opt_pass_trailer),
 		OPT_BOOL('s', "signoff", &signoff, N_("add a Signed-off-by trailer")),
 		OPT_FILENAME('t', "template", &template_file, N_("use specified template file")),
 		OPT_BOOL('e', "edit", &edit_flag, N_("force edit of commit")),
diff --git a/t/t7502-commit-porcelain.sh b/t/t7502-commit-porcelain.sh
index 6396897cc818..38a532d81ccd 100755
--- a/t/t7502-commit-porcelain.sh
+++ b/t/t7502-commit-porcelain.sh
@@ -38,6 +38,16 @@ check_summary_oneline() {
 	test_cmp exp act
 }
 
+trailer_commit_base () {
+	echo "fun" >>file &&
+	git add file &&
+	git commit -s --trailer "Signed-off-by=C1 E1 " \
+		--trailer "Helped-by:C2 E2 " \
+		--trailer "Reported-by=C3 E3" \
+		--trailer "Mentored-by:C4 E4" \
+		-m "hello"
+}
+
 test_expect_success 'output summary format' '
 
 	echo new >file1 &&
@@ -154,6 +164,308 @@ test_expect_success 'sign off' '
 
 '
 
+test_expect_success 'commit --trailer with "="' '
+	trailer_commit_base &&
+	cat >expected <<-\EOF &&
+	hello
+
+	Signed-off-by: C O Mitter <committer@example.com>
+	Signed-off-by: C1 E1
+	Helped-by: C2 E2
+	Reported-by: C3 E3
+	Mentored-by: C4 E4
+	EOF
+	git cat-file commit HEAD >commit.msg &&
+	sed -e "1,/^\$/d" commit.msg >actual &&
+	test_cmp expected actual
+'
+
+test_expect_success 'commit --trailer with -c and "replace" as ifexists' '
+	trailer_commit_base &&
+	cat >expected <<-\EOF &&
+	hello
+
+	Signed-off-by: C O Mitter <committer@example.com>
+	Signed-off-by: C1 E1
+	Reported-by: C3 E3
+	Mentored-by: C4 E4
+	Helped-by: C3 E3
+	EOF
+	git -c trailer.ifexists="replace" \
+		commit --trailer "Mentored-by: C4 E4" \
+		 --trailer "Helped-by: C3 E3" \
+		--amend &&
+	git cat-file commit HEAD >commit.msg &&
+	sed -e "1,/^\$/d"  commit.msg >actual &&
+	test_cmp expected actual
+'
+
+test_expect_success 'commit --trailer with -c and "add" as ifexists' '
+	trailer_commit_base &&
+	cat >expected <<-\EOF &&
+	hello
+
+	Signed-off-by: C O Mitter <committer@example.com>
+	Signed-off-by: C1 E1
+	Helped-by: C2 E2
+	Reported-by: C3 E3
+	Mentored-by: C4 E4
+	Reported-by: C3 E3
+	Mentored-by: C4 E4
+	EOF
+	git -c trailer.ifexists="add" \
+		commit --trailer "Reported-by: C3 E3" \
+		--trailer "Mentored-by: C4 E4" \
+		--amend &&
+	git cat-file commit HEAD >commit.msg &&
+	sed -e "1,/^\$/d"  commit.msg >actual &&
+	test_cmp expected actual
+'
+
+test_expect_success 'commit --trailer with -c and "donothing" as ifexists' '
+	trailer_commit_base &&
+	cat >expected <<-\EOF &&
+	hello
+
+	Signed-off-by: C O Mitter <committer@example.com>
+	Signed-off-by: C1 E1
+	Helped-by: C2 E2
+	Reported-by: C3 E3
+	Mentored-by: C4 E4
+	Reviewed-by: C6 E6
+	EOF
+	git -c trailer.ifexists="donothing" \
+		commit --trailer "Mentored-by: C5 E5" \
+		--trailer "Reviewed-by: C6 E6" \
+		--amend &&
+	git cat-file commit HEAD >commit.msg &&
+	sed -e "1,/^\$/d"  commit.msg >actual &&
+	test_cmp expected actual
+'
+
+test_expect_success 'commit --trailer with -c and "addIfDifferent" as ifexists' '
+	trailer_commit_base &&
+	cat >expected <<-\EOF &&
+	hello
+
+	Signed-off-by: C O Mitter <committer@example.com>
+	Signed-off-by: C1 E1
+	Helped-by: C2 E2
+	Reported-by: C3 E3
+	Mentored-by: C4 E4
+	Mentored-by: C5 E5
+	EOF
+	git -c trailer.ifexists="addIfDifferent" \
+		commit --trailer "Reported-by: C3 E3" \
+		--trailer "Mentored-by: C5 E5" \
+		--amend &&
+	git cat-file commit HEAD >commit.msg &&
+	sed -e "1,/^\$/d"  commit.msg >actual &&
+	test_cmp expected actual
+'
+
+test_expect_success 'commit --trailer with -c and "addIfDifferentNeighbor" as ifexists' '
+	trailer_commit_base &&
+	cat >expected <<-\EOF &&
+	hello
+
+	Signed-off-by: C O Mitter <committer@example.com>
+	Signed-off-by: C1 E1
+	Helped-by: C2 E2
+	Reported-by: C3 E3
+	Mentored-by: C4 E4
+	Reported-by: C3 E3
+	EOF
+	git -c trailer.ifexists="addIfDifferentNeighbor" \
+		commit --trailer "Mentored-by: C4 E4" \
+		--trailer "Reported-by: C3 E3" \
+		--amend &&
+	git cat-file commit HEAD >commit.msg &&
+	sed -e "1,/^\$/d"  commit.msg >actual &&
+	test_cmp expected actual
+'
+
+test_expect_success 'commit --trailer with -c and "end" as where' '
+	trailer_commit_base &&
+	cat >expected <<-\EOF &&
+	hello
+
+	Signed-off-by: C O Mitter <committer@example.com>
+	Signed-off-by: C1 E1
+	Helped-by: C2 E2
+	Reported-by: C3 E3
+	Mentored-by: C4 E4
+	Reported-by: C3 E3
+	Mentored-by: C4 E4
+	EOF
+	git -c trailer.where="end" \
+		commit --trailer "Reported-by: C3 E3" \
+		--trailer "Mentored-by: C4 E4" \
+		--amend &&
+	git cat-file commit HEAD >commit.msg &&
+	sed -e "1,/^\$/d" commit.msg >actual &&
+	test_cmp expected actual
+'
+
+test_expect_success 'commit --trailer with -c and "start" as where' '
+	trailer_commit_base &&
+	cat >expected <<-\EOF &&
+	hello
+
+	Signed-off-by: C1 E1
+	Signed-off-by: C O Mitter <committer@example.com>
+	Signed-off-by: C1 E1
+	Helped-by: C2 E2
+	Reported-by: C3 E3
+	Mentored-by: C4 E4
+	EOF
+	git -c trailer.where="start" \
+		commit --trailer "Signed-off-by: C O Mitter <committer@example.com>" \
+		--trailer "Signed-off-by: C1 E1" \
+		--amend &&
+	git cat-file commit HEAD >commit.msg &&
+	sed -e "1,/^\$/d" commit.msg >actual &&
+	test_cmp expected actual
+'
+
+test_expect_success 'commit --trailer with -c and "after" as where' '
+	trailer_commit_base &&
+	cat >expected <<-\EOF &&
+	hello
+
+	Signed-off-by: C O Mitter <committer@example.com>
+	Signed-off-by: C1 E1
+	Helped-by: C2 E2
+	Reported-by: C3 E3
+	Mentored-by: C4 E4
+	Mentored-by: C5 E5
+	EOF
+	git -c trailer.where="after" \
+		commit --trailer "Mentored-by: C4 E4" \
+		--trailer "Mentored-by: C5 E5" \
+		--amend &&
+	git cat-file commit HEAD >commit.msg &&
+	sed -e "1,/^\$/d" commit.msg >actual &&
+	test_cmp expected actual
+'
+
+test_expect_success 'commit --trailer with -c and "before" as where' '
+	trailer_commit_base &&
+	cat >expected <<-\EOF &&
+	hello
+
+	Signed-off-by: C O Mitter <committer@example.com>
+	Signed-off-by: C1 E1
+	Helped-by: C2 E2
+	Reported-by: C3 E3
+	Mentored-by: C2 E2
+	Mentored-by: C3 E3
+	Mentored-by: C4 E4
+	EOF
+	git -c trailer.where="before" \
+		commit --trailer "Mentored-by: C3 E3" \
+		--trailer "Mentored-by: C2 E2" \
+		--amend &&
+	git cat-file commit HEAD >commit.msg &&
+	sed -e "1,/^\$/d" commit.msg >actual &&
+	test_cmp expected actual
+'
+
+test_expect_success 'commit --trailer with -c and "donothing" as ifmissing' '
+	trailer_commit_base &&
+	cat >expected <<-\EOF &&
+	hello
+
+	Signed-off-by: C O Mitter <committer@example.com>
+	Signed-off-by: C1 E1
+	Helped-by: C2 E2
+	Reported-by: C3 E3
+	Mentored-by: C4 E4
+	Helped-by: C5 E5
+	EOF
+	git -c trailer.ifmissing="donothing" \
+		commit --trailer "Helped-by: C5 E5" \
+		--trailer "Based-by: C6 E6" \
+		--amend &&
+	git cat-file commit HEAD >commit.msg &&
+	sed -e "1,/^\$/d" commit.msg >actual &&
+	test_cmp expected actual
+'
+
+test_expect_success 'commit --trailer with -c and "add" as ifmissing' '
+	trailer_commit_base &&
+	cat >expected <<-\EOF &&
+	hello
+
+	Signed-off-by: C O Mitter <committer@example.com>
+	Signed-off-by: C1 E1
+	Helped-by: C2 E2
+	Reported-by: C3 E3
+	Mentored-by: C4 E4
+	Helped-by: C5 E5
+	Based-by: C6 E6
+	EOF
+	git -c trailer.ifmissing="add" \
+		commit --trailer "Helped-by: C5 E5" \
+		--trailer "Based-by: C6 E6" \
+		--amend &&
+	git cat-file commit HEAD >commit.msg &&
+	sed -e "1,/^\$/d" commit.msg >actual &&
+	test_cmp expected actual
+'
+
+test_expect_success 'commit --trailer with -c ack.key ' '
+	echo "fun" >>file1 &&
+	git add file1 &&
+	cat >expected <<-\EOF &&
+		hello
+
+		Acked-by: Peff
+	EOF
+	git -c trailer.ack.key="Acked-by" \
+		commit --trailer "ack = Peff" -m "hello" &&
+	git cat-file commit HEAD >commit.msg &&
+	sed -e "1,/^\$/d" commit.msg >actual &&
+	test_cmp expected actual
+'
+
+test_expect_success 'commit --trailer with -c and ":=#" as separators' '
+	echo "fun" >>file1 &&
+	git add file1 &&
+	cat >expected <<-\EOF &&
+		I hate bug
+
+		Bug #42
+	EOF
+	git -c trailer.separators=":=#" \
+		-c trailer.bug.key="Bug #" \
+		commit --trailer "bug = 42" -m "I hate bug" &&
+	git cat-file commit HEAD >commit.msg &&
+	sed -e "1,/^\$/d" commit.msg >actual &&
+	test_cmp expected actual
+'
+
+test_expect_success 'commit --trailer with -c and command' '
+	trailer_commit_base &&
+	cat >expected <<-\EOF &&
+	hello
+
+	Signed-off-by: C O Mitter <committer@example.com>
+	Signed-off-by: C1 E1
+	Helped-by: C2 E2
+	Mentored-by: C4 E4
+	Reported-by: A U Thor <author@example.com>
+	EOF
+	git -c trailer.report.key="Reported-by: " \
+		-c trailer.report.ifexists="replace" \
+		-c trailer.report.command="NAME=\"\$ARG\"; test -n \"\$NAME\" && \
+		git log --author=\"\$NAME\" -1 --format=\"format:%aN <%aE>\" || true" \
+		commit --trailer "report = author" --amend &&
+	git cat-file commit HEAD >commit.msg &&
+	sed -e "1,/^\$/d" commit.msg >actual &&
+	test_cmp expected actual
+'
+
 test_expect_success 'multiple -m' '
 
 	>negative &&
base-commit: 13d7ab6b5d7929825b626f050b62a11241ea4945
-- 
gitgitgadget
Keyboard shortcuts
hback out one level
jnext message in thread
kprevious message in thread
ldrill in
Escclose help / fold thread tree
?toggle this help