{"thread":{"id":"59125","subject":"[PATCH 0/5] format-patch: Add --{to,cc}-cmd support","startedAt":"2023-01-19T23:00:09Z","lastAt":"2023-02-01T23:55:53Z","messageCount":14,"participants":["Zev Weiss","Junio C Hamano","Calvin Wan"],"isPatch":true,"patchVersion":1,"patchTotal":5},"messages":[{"id":"470743","messageId":"20230119223858.29262-1-zev@bewilderbeest.net","threadId":"59125","inReplyTo":null,"subject":"[PATCH 0/5] format-patch: Add --{to,cc}-cmd support","fromName":"Zev Weiss","fromEmail":"zev@bewilderbeest.net","sentAt":"2023-01-19T22:38:53Z","receivedAt":"2023-01-19T23:00:09Z","isPatch":true,"sender":{"key":"zev@bewilderbeest.net","avatar":null},"body":"Hello,\n\nOn more than one occasion I've found myself wishing that git's\nformat-patch command supported the --to-cmd/--cc-cmd flags that\nsend-email does; this patch series is my attempt at implementing it.\n\nThis is my first foray into the git codebase, so odds are good I've\nbungled something or missed the existence of some useful API, but it\npasses the existing test suite (and the tests added for the new\nfeature), and I'm giving it its inaugural dogfooding on this very\nseries with `contrib/contacts/git-contacts`.\n\nThe first patch adds a test I found myself wanting when I realized I\nhad accidentally broken something that wasn't covered by the existing\ntests.  The next two do a bit of refactoring and rearrangement, the\nfourth adds a bit of library infrastructure to pretty.c, and the final\npatch implements the feature itself.\n\nThere are a few points where I wasn't sure if the approach I chose was\nreally the best:\n\n - The 'name_and_address_only' pretty_print_context flag added in the\n   fourth patch is kind of clunky, but I didn't see any other obvious\n   great way to reuse the bits of pp_user_info() that I wanted.\n\n - Fork/exec-ing another internal format-patch command to generate the\n   temporary patch file to run the commands on -- my attempts to\n   recurse back into show_log() to do it more directly didn't go well.\n\n - As currently written any commands specified are only run on the\n   patches themselves, not on the cover letter.  The slight\n   inconsistency with git-send-email is a bit unfortunate, but I\n   figure it's probably not often all that useful in that context\n   anyway (and the implementation looked like it would be just\n   non-trivial enough that laziness may have played a role as well).\n\nSuggestions on better alternatives there (or elsewhere) are of course\nwelcome.\n\n\nThanks,\nZev Weiss\n\nZev Weiss (5):\n  t4014: Add test checking cover-letter To header\n  log: Refactor duplicated code to headerize recipient lists\n  log: Push to/cc handling down into show_log()\n  pretty: Add name_and_address_only parameter\n  format-patch: Add support for --{to,cc}-cmd flags\n\n builtin/log.c           |  31 +++---\n log-tree.c              | 208 +++++++++++++++++++++++++++++++++++++++-\n log-tree.h              |   1 +\n pretty.c                |  15 ++-\n pretty.h                |   1 +\n revision.h              |   4 +\n t/t4014-format-patch.sh |  26 +++++\n 7 files changed, 262 insertions(+), 24 deletions(-)\n\n-- \n2.39.1.236.ga8a28b9eace8\n\n"},{"id":"470744","messageId":"20230119223858.29262-4-zev@bewilderbeest.net","threadId":"59125","inReplyTo":"20230119223858.29262-1-zev@bewilderbeest.net","subject":"[PATCH 3/5] log: Push to/cc handling down into show_log()","fromName":"Zev Weiss","fromEmail":"zev@bewilderbeest.net","sentAt":"2023-01-19T22:38:56Z","receivedAt":"2023-01-19T23:00:16Z","isPatch":true,"sender":{"key":"zev@bewilderbeest.net","avatar":null},"body":"This rearrangement is a measure to facilitate adding --to-cmd/--cc-cmd\nsupport to format-patch.  The command will need to be run separately\nfor each commit, so the lists of individual recipients specified via\n--to and --cc (and potentially --add-header) need to be available at\nthe per-commit level instead of just the combined headers in a single\nstring as has been the case until now.\n\nSigned-off-by: Zev Weiss <zev@bewilderbeest.net>\n---\n builtin/log.c |  6 +++---\n log-tree.c    | 22 +++++++++++++++++++---\n log-tree.h    |  3 +--\n revision.h    |  2 ++\n 4 files changed, 25 insertions(+), 8 deletions(-)\n\ndiff --git a/builtin/log.c b/builtin/log.c\nindex ad9d7ebc6d73..c0c7b8544d73 100644\n--- a/builtin/log.c\n+++ b/builtin/log.c\n@@ -1326,6 +1326,7 @@ static void make_cover_letter(struct rev_info *rev, int use_separate_file,\n \tpp.rev = rev;\n \tpp.print_email_subject = 1;\n \tpp_user_info(&pp, NULL, &sb, committer, encoding);\n+\tformat_recipients(rev, &sb);\n \tprepare_cover_text(&pp, branch_name, &sb, encoding, need_8bit_cte);\n \tfprintf(rev->diffopt.file, \"%s\\n\", sb.buf);\n \n@@ -2028,9 +2029,8 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix)\n \t\tstrbuf_addch(&buf, '\\n');\n \t}\n \n-\trecipients_to_header_buf(\"To\", &buf, &extra_to);\n-\trecipients_to_header_buf(\"Cc\", &buf, &extra_cc);\n-\n+\trev.to_recipients = &extra_to;\n+\trev.cc_recipients = &extra_cc;\n \trev.extra_headers = to_free = strbuf_detach(&buf, NULL);\n \n \tif (from) {\ndiff --git a/log-tree.c b/log-tree.c\nindex 0e8863fe545a..7aa2841dd803 100644\n--- a/log-tree.c\n+++ b/log-tree.c\n@@ -426,8 +426,8 @@ void fmt_output_email_subject(struct strbuf *sb, struct rev_info *opt)\n \t}\n }\n \n-void recipients_to_header_buf(const char *hdr, struct strbuf *buf,\n-\t\t\t      const struct string_list *recipients)\n+static void recipients_to_header_buf(const char *hdr, struct strbuf *buf,\n+\t\t\t\t     const struct string_list *recipients)\n {\n \tfor (int i = 0; i < recipients->nr; i++) {\n \t\tif (!i)\n@@ -441,6 +441,14 @@ void recipients_to_header_buf(const char *hdr, struct strbuf *buf,\n \t}\n }\n \n+void format_recipients(struct rev_info *rev, struct strbuf *sb)\n+{\n+\tif (rev->to_recipients)\n+\t\trecipients_to_header_buf(\"To\", sb, rev->to_recipients);\n+\tif (rev->cc_recipients)\n+\t\trecipients_to_header_buf(\"Cc\", sb, rev->cc_recipients);\n+}\n+\n void log_write_email_headers(struct rev_info *opt, struct commit *commit,\n \t\t\t     const char **extra_headers_p,\n \t\t\t     int *need_8bit_cte_p,\n@@ -647,10 +655,12 @@ static void next_commentary_block(struct rev_info *opt, struct strbuf *sb)\n void show_log(struct rev_info *opt)\n {\n \tstruct strbuf msgbuf = STRBUF_INIT;\n+\tstruct strbuf hdrbuf = STRBUF_INIT;\n \tstruct log_info *log = opt->loginfo;\n \tstruct commit *commit = log->commit, *parent = log->parent;\n \tint abbrev_commit = opt->abbrev_commit ? opt->abbrev : the_hash_algo->hexsz;\n \tconst char *extra_headers = opt->extra_headers;\n+\tchar *to_free;\n \tstruct pretty_print_context ctx = {0};\n \n \topt->loginfo = NULL;\n@@ -770,6 +780,11 @@ void show_log(struct rev_info *opt)\n \t\tctx.notes_message = strbuf_detach(&notebuf, NULL);\n \t}\n \n+\tformat_recipients(opt, &hdrbuf);\n+\n+\tif (extra_headers)\n+\t\tstrbuf_addstr(&hdrbuf, extra_headers);\n+\n \t/*\n \t * And then the pretty-printed message itself\n \t */\n@@ -779,7 +794,7 @@ void show_log(struct rev_info *opt)\n \tctx.date_mode = opt->date_mode;\n \tctx.date_mode_explicit = opt->date_mode_explicit;\n \tctx.abbrev = opt->diffopt.abbrev;\n-\tctx.after_subject = extra_headers;\n+\tctx.after_subject = to_free = strbuf_detach(&hdrbuf, NULL);\n \tctx.preserve_subject = opt->preserve_subject;\n \tctx.encode_email_headers = opt->encode_email_headers;\n \tctx.reflog_info = opt->reflog_info;\n@@ -828,6 +843,7 @@ void show_log(struct rev_info *opt)\n \n \tstrbuf_release(&msgbuf);\n \tfree(ctx.notes_message);\n+\tfree(to_free);\n \n \tif (cmit_fmt_is_mail(ctx.fmt) && opt->idiff_oid1) {\n \t\tstruct diff_queue_struct dq;\ndiff --git a/log-tree.h b/log-tree.h\nindex 227edc564121..ace50dad6c83 100644\n--- a/log-tree.h\n+++ b/log-tree.h\n@@ -25,8 +25,7 @@ void format_decorations_extended(struct strbuf *sb, const struct commit *commit,\n #define format_decorations(strbuf, commit, color) \\\n \t\t\t     format_decorations_extended((strbuf), (commit), (color), \" (\", \", \", \")\")\n void show_decorations(struct rev_info *opt, struct commit *commit);\n-void recipients_to_header_buf(const char *hdr, struct strbuf *buf,\n-\t\t\t      const struct string_list *recipients);\n+void format_recipients(struct rev_info *rev, struct strbuf *sb);\n void log_write_email_headers(struct rev_info *opt, struct commit *commit,\n \t\t\t     const char **extra_headers_p,\n \t\t\t     int *need_8bit_cte_p,\ndiff --git a/revision.h b/revision.h\nindex 30febad09a1e..330d351b2e4c 100644\n--- a/revision.h\n+++ b/revision.h\n@@ -283,6 +283,8 @@ struct rev_info {\n \tstruct ident_split from_ident;\n \tstruct string_list *ref_message_ids;\n \tint\t\tadd_signoff;\n+\tstruct string_list *to_recipients;\n+\tstruct string_list *cc_recipients;\n \tconst char\t*extra_headers;\n \tconst char\t*log_reencode;\n \tconst char\t*subject_prefix;\n-- \n2.39.1.236.ga8a28b9eace8\n\n"},{"id":"470745","messageId":"20230119223858.29262-3-zev@bewilderbeest.net","threadId":"59125","inReplyTo":"20230119223858.29262-1-zev@bewilderbeest.net","subject":"[PATCH 2/5] log: Refactor duplicated code to headerize recipient lists","fromName":"Zev Weiss","fromEmail":"zev@bewilderbeest.net","sentAt":"2023-01-19T22:38:55Z","receivedAt":"2023-01-19T23:00:22Z","isPatch":true,"sender":{"key":"zev@bewilderbeest.net","avatar":null},"body":"The To and Cc headers are handled identically (the only difference is\nthe header name tag), so we might as well reuse the same code for both\ninstead of duplicating it.\n\nSigned-off-by: Zev Weiss <zev@bewilderbeest.net>\n---\n builtin/log.c | 23 ++---------------------\n log-tree.c    | 15 +++++++++++++++\n log-tree.h    |  2 ++\n 3 files changed, 19 insertions(+), 21 deletions(-)\n\ndiff --git a/builtin/log.c b/builtin/log.c\nindex 057e299c245c..ad9d7ebc6d73 100644\n--- a/builtin/log.c\n+++ b/builtin/log.c\n@@ -2028,27 +2028,8 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix)\n \t\tstrbuf_addch(&buf, '\\n');\n \t}\n \n-\tif (extra_to.nr)\n-\t\tstrbuf_addstr(&buf, \"To: \");\n-\tfor (i = 0; i < extra_to.nr; i++) {\n-\t\tif (i)\n-\t\t\tstrbuf_addstr(&buf, \"    \");\n-\t\tstrbuf_addstr(&buf, extra_to.items[i].string);\n-\t\tif (i + 1 < extra_to.nr)\n-\t\t\tstrbuf_addch(&buf, ',');\n-\t\tstrbuf_addch(&buf, '\\n');\n-\t}\n-\n-\tif (extra_cc.nr)\n-\t\tstrbuf_addstr(&buf, \"Cc: \");\n-\tfor (i = 0; i < extra_cc.nr; i++) {\n-\t\tif (i)\n-\t\t\tstrbuf_addstr(&buf, \"    \");\n-\t\tstrbuf_addstr(&buf, extra_cc.items[i].string);\n-\t\tif (i + 1 < extra_cc.nr)\n-\t\t\tstrbuf_addch(&buf, ',');\n-\t\tstrbuf_addch(&buf, '\\n');\n-\t}\n+\trecipients_to_header_buf(\"To\", &buf, &extra_to);\n+\trecipients_to_header_buf(\"Cc\", &buf, &extra_cc);\n \n \trev.extra_headers = to_free = strbuf_detach(&buf, NULL);\n \ndiff --git a/log-tree.c b/log-tree.c\nindex 1dd5fcbf7be4..0e8863fe545a 100644\n--- a/log-tree.c\n+++ b/log-tree.c\n@@ -426,6 +426,21 @@ void fmt_output_email_subject(struct strbuf *sb, struct rev_info *opt)\n \t}\n }\n \n+void recipients_to_header_buf(const char *hdr, struct strbuf *buf,\n+\t\t\t      const struct string_list *recipients)\n+{\n+\tfor (int i = 0; i < recipients->nr; i++) {\n+\t\tif (!i)\n+\t\t\tstrbuf_addf(buf, \"%s: \", hdr);\n+\t\telse\n+\t\t\tstrbuf_addstr(buf, \"    \");\n+\t\tstrbuf_addstr(buf, recipients->items[i].string);\n+\t\tif (i + 1 < recipients->nr)\n+\t\t\tstrbuf_addch(buf, ',');\n+\t\tstrbuf_addch(buf, '\\n');\n+\t}\n+}\n+\n void log_write_email_headers(struct rev_info *opt, struct commit *commit,\n \t\t\t     const char **extra_headers_p,\n \t\t\t     int *need_8bit_cte_p,\ndiff --git a/log-tree.h b/log-tree.h\nindex e7e4641cf83c..227edc564121 100644\n--- a/log-tree.h\n+++ b/log-tree.h\n@@ -25,6 +25,8 @@ void format_decorations_extended(struct strbuf *sb, const struct commit *commit,\n #define format_decorations(strbuf, commit, color) \\\n \t\t\t     format_decorations_extended((strbuf), (commit), (color), \" (\", \", \", \")\")\n void show_decorations(struct rev_info *opt, struct commit *commit);\n+void recipients_to_header_buf(const char *hdr, struct strbuf *buf,\n+\t\t\t      const struct string_list *recipients);\n void log_write_email_headers(struct rev_info *opt, struct commit *commit,\n \t\t\t     const char **extra_headers_p,\n \t\t\t     int *need_8bit_cte_p,\n-- \n2.39.1.236.ga8a28b9eace8\n\n"},{"id":"470746","messageId":"20230119223858.29262-5-zev@bewilderbeest.net","threadId":"59125","inReplyTo":"20230119223858.29262-1-zev@bewilderbeest.net","subject":"[PATCH 4/5] pretty: Add name_and_address_only parameter","fromName":"Zev Weiss","fromEmail":"zev@bewilderbeest.net","sentAt":"2023-01-19T22:38:57Z","receivedAt":"2023-01-19T23:00:29Z","isPatch":true,"sender":{"key":"zev@bewilderbeest.net","avatar":null},"body":"This is meant to be used with pp_user_info() when using it to format\nemail recipients generated by --to-cmd/--cc-cmd.  When set it omits\nthe leading 'From: ', trailing linefeed, and the date suffix, and\nadditionally will return the input string unmodified if\nsplit_ident_line() can't parse it (e.g. for a bare email address).\n\nSigned-off-by: Zev Weiss <zev@bewilderbeest.net>\n---\n pretty.c | 15 ++++++++++++---\n pretty.h |  1 +\n 2 files changed, 13 insertions(+), 3 deletions(-)\n\ndiff --git a/pretty.c b/pretty.c\nindex 1e1e21878c83..e6798fadc107 100644\n--- a/pretty.c\n+++ b/pretty.c\n@@ -509,8 +509,11 @@ void pp_user_info(struct pretty_print_context *pp,\n \t\treturn;\n \n \tline_end = strchrnul(line, '\\n');\n-\tif (split_ident_line(&ident, line, line_end - line))\n+\tif (split_ident_line(&ident, line, line_end - line)) {\n+\t\tif (pp->name_and_address_only)\n+\t\t\tstrbuf_addstr(sb, line);\n \t\treturn;\n+\t}\n \n \tmailbuf = ident.mail_begin;\n \tmaillen = ident.mail_end - ident.mail_begin;\n@@ -538,7 +541,8 @@ void pp_user_info(struct pretty_print_context *pp,\n \t\t\tnamelen = pp->from_ident->name_end - namebuf;\n \t\t}\n \n-\t\tstrbuf_addstr(sb, \"From: \");\n+\t\tif (!pp->name_and_address_only)\n+\t\t\tstrbuf_addstr(sb, \"From: \");\n \t\tif (pp->encode_email_headers &&\n \t\t    needs_rfc2047_encoding(namebuf, namelen)) {\n \t\t\tadd_rfc2047(sb, namebuf, namelen,\n@@ -558,7 +562,9 @@ void pp_user_info(struct pretty_print_context *pp,\n \t\tif (max_length <\n \t\t    last_line_length(sb) + strlen(\" <\") + maillen + strlen(\">\"))\n \t\t\tstrbuf_addch(sb, '\\n');\n-\t\tstrbuf_addf(sb, \" <%.*s>\\n\", (int)maillen, mailbuf);\n+\t\tstrbuf_addf(sb, \" <%.*s>\", (int)maillen, mailbuf);\n+\t\tif (!pp->name_and_address_only)\n+\t\t\tstrbuf_addch(sb, '\\n');\n \t} else {\n \t\tstruct strbuf id = STRBUF_INIT;\n \t\tenum grep_header_field field = GREP_HEADER_FIELD_MAX;\n@@ -582,6 +588,9 @@ void pp_user_info(struct pretty_print_context *pp,\n \t\tstrbuf_release(&id);\n \t}\n \n+\tif (pp->name_and_address_only)\n+\t\treturn;\n+\n \tswitch (pp->fmt) {\n \tcase CMIT_FMT_MEDIUM:\n \t\tstrbuf_addf(sb, \"Date:   %s\\n\",\ndiff --git a/pretty.h b/pretty.h\nindex f34e24c53a49..306666e99294 100644\n--- a/pretty.h\n+++ b/pretty.h\n@@ -39,6 +39,7 @@ struct pretty_print_context {\n \tint preserve_subject;\n \tstruct date_mode date_mode;\n \tunsigned date_mode_explicit:1;\n+\tunsigned name_and_address_only:1;\n \tint print_email_subject;\n \tint expand_tabs_in_log;\n \tint need_8bit_cte;\n-- \n2.39.1.236.ga8a28b9eace8\n\n"},{"id":"470747","messageId":"20230119223858.29262-2-zev@bewilderbeest.net","threadId":"59125","inReplyTo":"20230119223858.29262-1-zev@bewilderbeest.net","subject":"[PATCH 1/5] t4014: Add test checking cover-letter To header","fromName":"Zev Weiss","fromEmail":"zev@bewilderbeest.net","sentAt":"2023-01-19T22:38:54Z","receivedAt":"2023-01-19T23:00:37Z","isPatch":true,"sender":{"key":"zev@bewilderbeest.net","avatar":null},"body":"I at first inadvertently broke this in the process of adding\n--{to,cc}-cmd support to format-patch and didn't immediately notice\nbecause there wasn't a test for it, so let's add one now.\n\nSigned-off-by: Zev Weiss <zev@bewilderbeest.net>\n---\n t/t4014-format-patch.sh | 7 +++++++\n 1 file changed, 7 insertions(+)\n\ndiff --git a/t/t4014-format-patch.sh b/t/t4014-format-patch.sh\nindex 012f155e10ae..ba5fd0efe2ae 100755\n--- a/t/t4014-format-patch.sh\n+++ b/t/t4014-format-patch.sh\n@@ -2368,4 +2368,11 @@ test_expect_success 'interdiff: solo-patch' '\n \ttest_cmp expect actual\n '\n \n+test_expect_success 'cover-letter gets To: header' '\n+\trm -fr patches &&\n+\tgit config --unset-all format.to &&\n+\tgit format-patch -o patches --cover-letter --to \"R E Cipient <rcipient@example.com>\" main..side >list &&\n+\tgrep \"^To: R E Cipient <rcipient@example.com>\\$\" patches/0000-cover-letter.patch\n+'\n+\n test_done\n-- \n2.39.1.236.ga8a28b9eace8\n\n"},{"id":"470748","messageId":"20230119223858.29262-6-zev@bewilderbeest.net","threadId":"59125","inReplyTo":"20230119223858.29262-1-zev@bewilderbeest.net","subject":"[PATCH 5/5] format-patch: Add support for --{to,cc}-cmd flags","fromName":"Zev Weiss","fromEmail":"zev@bewilderbeest.net","sentAt":"2023-01-19T22:38:58Z","receivedAt":"2023-01-19T23:00:41Z","isPatch":true,"sender":{"key":"zev@bewilderbeest.net","avatar":null},"body":"Having these flags available for format-patch instead of only on\nsend-email makes it much easier to use an automated command to do the\nbulk of the recipient-selection work but still manually adjust it if\ndesired.\n\nSigned-off-by: Zev Weiss <zev@bewilderbeest.net>\n---\n builtin/log.c           |  10 +++\n log-tree.c              | 179 +++++++++++++++++++++++++++++++++++++++-\n revision.h              |   2 +\n t/t4014-format-patch.sh |  19 +++++\n 4 files changed, 208 insertions(+), 2 deletions(-)\n\ndiff --git a/builtin/log.c b/builtin/log.c\nindex c0c7b8544d73..da3edb2a8299 100644\n--- a/builtin/log.c\n+++ b/builtin/log.c\n@@ -1877,6 +1877,8 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix)\n \tstruct strbuf rdiff2 = STRBUF_INIT;\n \tstruct strbuf rdiff_title = STRBUF_INIT;\n \tint creation_factor = -1;\n+\tchar *to_cmd_arg = NULL;\n+\tchar *cc_cmd_arg = NULL;\n \n \tconst struct option builtin_format_patch_options[] = {\n \t\tOPT_CALLBACK_F('n', \"numbered\", &numbered, NULL,\n@@ -1929,6 +1931,10 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix)\n \t\t\t    N_(\"add email header\"), header_callback),\n \t\tOPT_CALLBACK(0, \"to\", NULL, N_(\"email\"), N_(\"add To: header\"), to_callback),\n \t\tOPT_CALLBACK(0, \"cc\", NULL, N_(\"email\"), N_(\"add Cc: header\"), cc_callback),\n+\t\tOPT_STRING(0, \"to-cmd\", &to_cmd_arg, N_(\"command\"),\n+\t\t           N_(\"command to generate To: addresses for a patch\")),\n+\t\tOPT_STRING(0, \"cc-cmd\", &cc_cmd_arg, N_(\"command\"),\n+\t\t           N_(\"command to generate Cc: addresses for a patch\")),\n \t\tOPT_CALLBACK_F(0, \"from\", &from, N_(\"ident\"),\n \t\t\t    N_(\"set From address to <ident> (or committer ident if absent)\"),\n \t\t\t    PARSE_OPT_OPTARG, from_callback),\n@@ -2031,6 +2037,10 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix)\n \n \trev.to_recipients = &extra_to;\n \trev.cc_recipients = &extra_cc;\n+\n+\trev.to_cmd = to_cmd_arg;\n+\trev.cc_cmd = cc_cmd_arg;\n+\n \trev.extra_headers = to_free = strbuf_detach(&buf, NULL);\n \n \tif (from) {\ndiff --git a/log-tree.c b/log-tree.c\nindex 7aa2841dd803..6fdc3a3ffb7f 100644\n--- a/log-tree.c\n+++ b/log-tree.c\n@@ -20,6 +20,7 @@\n #include \"help.h\"\n #include \"range-diff.h\"\n #include \"strmap.h\"\n+#include \"run-command.h\"\n \n static struct decoration name_decoration = { \"object names\" };\n static int decoration_loaded;\n@@ -652,11 +653,175 @@ static void next_commentary_block(struct rev_info *opt, struct strbuf *sb)\n \topt->shown_dashes = 1;\n }\n \n+/*\n+ * Aims to mirror git-send-email.perl's function of the same name, returning a\n+ * malloc()ed string that the caller must free().\n+ */\n+static char *sanitize_address(const struct rev_info *opt, const char *entry)\n+{\n+\tint escaped = 0;\n+\tconst char *inp;\n+\tchar *tmp, *outp;\n+\tstruct strbuf addr = STRBUF_INIT;\n+\tstruct pretty_print_context ctx = {0};\n+\n+\t/* Skip over any leading whitespace */\n+\twhile (isspace(*entry))\n+\t\tentry++;\n+\n+\toutp = tmp = xmalloc(strlen(entry) + 1);\n+\n+\t/* Remove non-escaped quotes */\n+\tfor (inp = entry; *inp; inp++) {\n+\t\tif (escaped) {\n+\t\t\tescaped = 0;\n+\t\t} else {\n+\t\t\tswitch (*inp) {\n+\t\t\tcase '\\\\':\n+\t\t\t\tescaped = 1;\n+\t\t\t\t/* fallthrough */\n+\t\t\tcase '\"':\n+\t\t\t\tcontinue;\n+\t\t\t}\n+\t\t}\n+\t\t*outp++ = *inp;\n+\t}\n+\t*outp = '\\0';\n+\n+\tctx.fmt = opt->commit_format;\n+\tctx.mailmap = opt->mailmap;\n+\tctx.encode_email_headers = opt->encode_email_headers;\n+\tctx.output_encoding = get_log_output_encoding();\n+\tctx.name_and_address_only = 1;\n+\n+\tpp_user_info(&ctx, NULL, &addr, tmp, get_log_output_encoding());\n+\n+\tfree(tmp);\n+\n+\treturn strbuf_detach(&addr, NULL);\n+}\n+\n+/*\n+ * Given 'text' as the output of a --to-cmd or --cc-cmd command, add each\n+ * entry to 'list'.\n+ */\n+static void ingest_recipients_to_list(const char *text, const struct rev_info *opt,\n+\t\t\t\t      struct string_list *list)\n+{\n+\tstruct string_list_item *item;\n+\tstruct string_list lines = STRING_LIST_INIT_DUP;\n+\n+\tstring_list_split(&lines, text, '\\n', -1);\n+\n+\tfor_each_string_list_item(item, &lines) {\n+\t\tchar *addr = sanitize_address(opt, item->string);\n+\t\tif (*addr)\n+\t\t\tstring_list_append_nodup(list, addr);\n+\t\telse\n+\t\t\tfree(addr);\n+\t}\n+\n+\tstring_list_clear(&lines, 0);\n+}\n+\n+/*\n+ * Generate a temporary patch file for the given commit, returning its path as\n+ * a malloc()ed string the caller must free (and should unlink when finished\n+ * with it).\n+ */\n+static char *generate_temp_patch(struct commit *commit)\n+{\n+\tchar path[PATH_MAX];\n+\tchar *diff_output_arg;\n+\tstruct strbuf diff_output_arg_buf = STRBUF_INIT;\n+\tstruct child_process diffproc = CHILD_PROCESS_INIT;\n+\n+\txsnprintf(path, sizeof(path), \".git-temp-diff.XXXXXX\");\n+\tclose(xmkstemp(path));\n+\n+\tstrbuf_addf(&diff_output_arg_buf, \"--output=%s\", path);\n+\tdiff_output_arg = strbuf_detach(&diff_output_arg_buf, NULL);\n+\n+\tdiffproc.git_cmd = 1;\n+\tstrvec_push(&diffproc.args, \"format-patch\");\n+\tstrvec_push(&diffproc.args, \"-1\");\n+\tstrvec_push(&diffproc.args, diff_output_arg);\n+\tstrvec_push(&diffproc.args, oid_to_hex(&commit->object.oid));\n+\n+\tif (run_command(&diffproc))\n+\t\tdie(_(\"Error generating temporary diff\"));\n+\n+\tfree(diff_output_arg);\n+\n+\treturn xstrdup(path);\n+}\n+\n+/*\n+ * Execute a --to-cmd or --cc-cmd command on a temporary patch file, adding\n+ * each recipient produced to 'list'.\n+ */\n+static void run_recipients_command(const char *cmd, const char *tmpdiff,\n+\t\t\t\t   const struct rev_info *opt,\n+\t\t\t\t   struct string_list *list)\n+{\n+\tchar *full_cmd;\n+\tchar *cmd_output;\n+\tstruct strbuf cmd_output_buf = STRBUF_INIT;\n+\tstruct strbuf cmd_buf = STRBUF_INIT;\n+\tstruct child_process cmdproc = CHILD_PROCESS_INIT;\n+\n+\tstrbuf_addf(&cmd_buf, \"%s %s\", cmd, tmpdiff);\n+\tfull_cmd = strbuf_detach(&cmd_buf, NULL);\n+\n+\tcmdproc.use_shell = 1;\n+\tstrvec_push(&cmdproc.args, full_cmd);\n+\tif (capture_command(&cmdproc, &cmd_output_buf, 1024))\n+\t\tdie(_(\"Error generating recipients list: command failed\"));\n+\n+\tcmd_output = strbuf_detach(&cmd_output_buf, NULL);\n+\tingest_recipients_to_list(cmd_output, opt, list);\n+\n+\tfree(cmd_output);\n+\tfree(full_cmd);\n+}\n+\n+/*\n+ * Generate a To or Cc header into 'buf', where 'header' is \"To\" or \"Cc\",\n+ * 'fixed' is the list of fixed recipients (e.g. those specified by --to or\n+ * --cc, optional), 'cmd' is the (optional) --to-cmd or --cc-cmd argument to\n+ * generate recipients, and 'tmpdiff' is the path of a temp file containing a\n+ * patch file to run 'cmd' on (mandatory if 'cmd' is non-NULL).\n+ */\n+static void headerize_recipients(const char *header, const char *tmpdiff,\n+\t\t\t\t const struct rev_info *opt,\n+\t\t\t\t const struct string_list *fixed,\n+\t\t\t\t const char *cmd, struct strbuf *buf)\n+{\n+\tstruct string_list_item *item;\n+\tstruct string_list recipients = STRING_LIST_INIT_DUP;\n+\n+\tif (fixed) {\n+\t\tfor_each_string_list_item(item, fixed)\n+\t\t\tstring_list_append(&recipients, item->string);\n+\t}\n+\n+\tif (cmd)\n+\t\trun_recipients_command(cmd, tmpdiff, opt, &recipients);\n+\n+\tstring_list_sort(&recipients);\n+\tstring_list_remove_duplicates(&recipients, 0);\n+\n+\trecipients_to_header_buf(header, buf, &recipients);\n+\n+\tstring_list_clear(&recipients, 0);\n+}\n+\n void show_log(struct rev_info *opt)\n {\n \tstruct strbuf msgbuf = STRBUF_INIT;\n \tstruct strbuf hdrbuf = STRBUF_INIT;\n \tstruct log_info *log = opt->loginfo;\n+\tchar *tmpdiff = NULL;\n \tstruct commit *commit = log->commit, *parent = log->parent;\n \tint abbrev_commit = opt->abbrev_commit ? opt->abbrev : the_hash_algo->hexsz;\n \tconst char *extra_headers = opt->extra_headers;\n@@ -715,6 +880,18 @@ void show_log(struct rev_info *opt)\n \t */\n \tgraph_show_commit(opt->graph);\n \n+\t/* Generate dynamic To/Cc lists as needed */\n+\tif (opt->to_cmd || opt->cc_cmd)\n+\t\ttmpdiff = generate_temp_patch(commit);\n+\n+\theaderize_recipients(\"To\", tmpdiff, opt, opt->to_recipients, opt->to_cmd, &hdrbuf);\n+\theaderize_recipients(\"Cc\", tmpdiff, opt, opt->cc_recipients, opt->cc_cmd, &hdrbuf);\n+\n+\tif (tmpdiff) {\n+\t\tunlink_or_warn(tmpdiff);\n+\t\tfree(tmpdiff);\n+\t}\n+\n \t/*\n \t * Print header line of header..\n \t */\n@@ -780,8 +957,6 @@ void show_log(struct rev_info *opt)\n \t\tctx.notes_message = strbuf_detach(&notebuf, NULL);\n \t}\n \n-\tformat_recipients(opt, &hdrbuf);\n-\n \tif (extra_headers)\n \t\tstrbuf_addstr(&hdrbuf, extra_headers);\n \ndiff --git a/revision.h b/revision.h\nindex 330d351b2e4c..9611309ae496 100644\n--- a/revision.h\n+++ b/revision.h\n@@ -285,6 +285,8 @@ struct rev_info {\n \tint\t\tadd_signoff;\n \tstruct string_list *to_recipients;\n \tstruct string_list *cc_recipients;\n+\tconst char\t*to_cmd;\n+\tconst char\t*cc_cmd;\n \tconst char\t*extra_headers;\n \tconst char\t*log_reencode;\n \tconst char\t*subject_prefix;\ndiff --git a/t/t4014-format-patch.sh b/t/t4014-format-patch.sh\nindex ba5fd0efe2ae..2387bda4f272 100755\n--- a/t/t4014-format-patch.sh\n+++ b/t/t4014-format-patch.sh\n@@ -228,6 +228,25 @@ test_expect_failure 'configuration To: header (rfc2047)' '\n \tgrep \"^To: =?UTF-8?q?R=20=C3=84=20Cipient?= <rcipient@example.com>\\$\" hdrs9\n '\n \n+test_expect_success 'dynamic To: header (ascii)' '\n+\tgit config --unset-all format.to &&\n+\tgit format-patch --to-cmd=\"echo \\\"R E Cipient <rcipient@example.com>\\\" #\" --stdout main..side >patch10 &&\n+\tsed -e \"/^\\$/q\" patch10 >hdrs10 &&\n+\tgrep \"^To: R E Cipient <rcipient@example.com>\\$\" hdrs10\n+'\n+\n+test_expect_success 'dynamic To: header (rfc822)' '\n+\tgit format-patch --to-cmd=\"echo \\\"R. E. Cipient <rcipient@example.com>\\\" #\" --stdout main..side >patch10 &&\n+\tsed -e \"/^\\$/q\" patch10 >hdrs10 &&\n+\tgrep \"^To: \\\"R. E. Cipient\\\" <rcipient@example.com>\\$\" hdrs10\n+'\n+\n+test_expect_success 'dynamic To: header (rfc2047)' '\n+\tgit format-patch --to-cmd=\"echo \\\"R Ä Cipient <rcipient@example.com>\\\" #\" --stdout main..side >patch10 &&\n+\tsed -e \"/^\\$/q\" patch10 >hdrs10 &&\n+\tgrep \"^To: =?UTF-8?q?R=20=C3=84=20Cipient?= <rcipient@example.com>\\$\" hdrs10\n+'\n+\n # check_patch <patch>: Verify that <patch> looks like a half-sane\n # patch email to avoid a false positive with !grep\n check_patch () {\n-- \n2.39.1.236.ga8a28b9eace8\n\n"},{"id":"470756","messageId":"xmqqy1pyyrhd.fsf@gitster.g","threadId":"59125","inReplyTo":"20230119223858.29262-4-zev@bewilderbeest.net","subject":"Re: [PATCH 3/5] log: Push to/cc handling down into show_log()","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2023-01-20T00:33:34Z","receivedAt":"2023-01-20T00:33:48Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Zev Weiss <zev@bewilderbeest.net> writes:\n\n> Subject: Re: [PATCH 3/5] log: Push to/cc handling down into show_log()\n\ns/Push/push/\ncf. Documentation/SubmittingPatches[[summary-section]]\n\nThis is common to all these patches.\n\n> @@ -1326,6 +1326,7 @@ static void make_cover_letter(struct rev_info *rev, int use_separate_file,\n>  \tpp.rev = rev;\n>  \tpp.print_email_subject = 1;\n>  \tpp_user_info(&pp, NULL, &sb, committer, encoding);\n> +\tformat_recipients(rev, &sb);\n\nThis is where the two new members in the rev structure is used.\n\n> @@ -2028,9 +2029,8 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix)\n>  \t\tstrbuf_addch(&buf, '\\n');\n>  \t}\n>  \n> -\trecipients_to_header_buf(\"To\", &buf, &extra_to);\n> -\trecipients_to_header_buf(\"Cc\", &buf, &extra_cc);\n> -\n> +\trev.to_recipients = &extra_to;\n> +\trev.cc_recipients = &extra_cc;\n>  \trev.extra_headers = to_free = strbuf_detach(&buf, NULL);\n\nAnd these two members point at borrowed memory.  extra_to and\nextra_cc is freed after everything is done, near the end of the\ncmd_format_patch() function.  We don't leak any extra memory by this\nchange, which is good.\n"},{"id":"471004","messageId":"xmqqo7qnfjnf.fsf@gitster.g","threadId":"59125","inReplyTo":"20230119223858.29262-3-zev@bewilderbeest.net","subject":"Re: [PATCH 2/5] log: Refactor duplicated code to headerize recipient lists","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2023-01-25T02:11:16Z","receivedAt":"2023-01-25T02:11:22Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Zev Weiss <zev@bewilderbeest.net> writes:\n\n> Subject: Re: [PATCH 2/5] log: Refactor duplicated code to headerize recipient lists\n\nStyle: \"log: Refactor\" -> \"log: refactor\"\n\ncf. Documentation/SubmittingPatches[[summary-section]]\n\n> The To and Cc headers are handled identically (the only difference is\n> the header name tag), so we might as well reuse the same code for both\n> instead of duplicating it.\n\nMakes tons of sense.  But seeing this one ...\n\n> +\trecipients_to_header_buf(\"To\", &buf, &extra_to);\n> +\trecipients_to_header_buf(\"Cc\", &buf, &extra_cc);\n\n... the parameters to the function probably should be ...\n\n> +void recipients_to_header_buf(const char *hdr, struct strbuf *buf,\n> +\t\t\t      const struct string_list *recipients);\n\n... in this order, instead:\n\n    format_recipients(&buf, \"To\", &extra_to);\n\nThat is, \"To\" and &extra_to are much closely related to each other\nthan they are to &buf in the sense that they are both input to the\nhelper function to work on, while &buf is an output buffer, and we\ntend to place closer things together, next to each other.\n\nOther than that, removal of repetition is very good.\n\nThanks.\n"},{"id":"471005","messageId":"xmqqedrjfj2x.fsf@gitster.g","threadId":"59125","inReplyTo":"20230119223858.29262-5-zev@bewilderbeest.net","subject":"Re: [PATCH 4/5] pretty: Add name_and_address_only parameter","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2023-01-25T02:23:34Z","receivedAt":"2023-01-25T02:24:15Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Zev Weiss <zev@bewilderbeest.net> writes:\n\n> This is meant to be used with pp_user_info() when using it to format\n> email recipients generated by --to-cmd/--cc-cmd.  When set it omits\n> the leading 'From: ', trailing linefeed, and the date suffix, and\n> additionally will return the input string unmodified if\n> split_ident_line() can't parse it (e.g. for a bare email address).\n\nIt is somewhat disturbing to see that this only takes effect when\ncmit_fmt_is_mail(pp->fmt) and completely ignored otherwise.  Seeing\npp->fmt and pp->name_and_address_only sitting next to each other, it\nlooks like a layering error.\n\nI wonder if you instead want a new value for pp->fmt that\ncmit_fmt_is_mail() considers an e-mail format but is different from\nwhat we used for usual e-mail format?\n\n> diff --git a/pretty.c b/pretty.c\n> index 1e1e21878c83..e6798fadc107 100644\n> --- a/pretty.c\n> +++ b/pretty.c\n> @@ -509,8 +509,11 @@ void pp_user_info(struct pretty_print_context *pp,\n>  \t\treturn;\n>  \n>  \tline_end = strchrnul(line, '\\n');\n> -\tif (split_ident_line(&ident, line, line_end - line))\n> +\tif (split_ident_line(&ident, line, line_end - line)) {\n> +\t\tif (pp->name_and_address_only)\n> +\t\t\tstrbuf_addstr(sb, line);\n>  \t\treturn;\n> +\t}\n\nThis error handling is also disturbing.  What makes it correct to\nparrot the original input that does not parse correctly as an ident\nline to the output, only when name_and_address_only bit is on?  It\ndoes not make any sense to do so when cmit_fmt_is_mail(pp->fmt) is\nfalse, especially.\n"},{"id":"471285","messageId":"20230201235253.1891546-1-calvinwan@google.com","threadId":"59125","inReplyTo":"20230119223858.29262-1-zev@bewilderbeest.net","subject":"Re: [PATCH 0/5] format-patch: Add --{to,cc}-cmd support","fromName":"Calvin Wan","fromEmail":"calvinwan@google.com","sentAt":"2023-02-01T23:52:49Z","receivedAt":"2023-02-01T23:54:05Z","isPatch":true,"sender":{"key":"calvinwan@google.com","avatar":"https://avatars.githubusercontent.com/u/92547554?v=4"},"body":"Hi Zev! Thanks for the patch series. I agree that these flags are a very\ngood idea to add. I appreciate the well written cover letter -- it does\nwell describing the need for this series and calling out design\nquestions. I'll leave the rest of my comments in the particular patches,\nlooking forward to the reroll!\n\n>  - As currently written any commands specified are only run on the\n>    patches themselves, not on the cover letter.  The slight\n>    inconsistency with git-send-email is a bit unfortunate, but I\n>    figure it's probably not often all that useful in that context\n>    anyway (and the implementation looked like it would be just\n>    non-trivial enough that laziness may have played a role as well).\n\nIt is also inconsistent with git-format-patch --to and --cc. I strongly\nbelieve that your flags should also work with the cover letter, since\nusers should expect equivalency across similar flags. git-send-email has\nconfig options, sendemail.tocmd and sendemail.cccmd, that I also believe\nshould be implemented.\n"},{"id":"471286","messageId":"20230201235253.1891546-2-calvinwan@google.com","threadId":"59125","inReplyTo":"20230119223858.29262-2-zev@bewilderbeest.net","subject":"Re: [PATCH 1/5] t4014: Add test checking cover-letter To header","fromName":"Calvin Wan","fromEmail":"calvinwan@google.com","sentAt":"2023-02-01T23:52:50Z","receivedAt":"2023-02-01T23:55:27Z","isPatch":true,"sender":{"key":"calvinwan@google.com","avatar":"https://avatars.githubusercontent.com/u/92547554?v=4"},"body":"> +test_expect_success 'cover-letter gets To: header' '\n> +\trm -fr patches &&\n> +\tgit config --unset-all format.to &&\n> +\tgit format-patch -o patches --cover-letter --to \"R E Cipient <rcipient@example.com>\" main..side >list &&\n> +\tgrep \"^To: R E Cipient <rcipient@example.com>\\$\" patches/0000-cover-letter.patch\n> +'\n> +\n>  test_done\n\n'>list' is unnecessary. The test can also be moved closer to other\ncover-letter or header tests.\n"},{"id":"471287","messageId":"20230201235253.1891546-3-calvinwan@google.com","threadId":"59125","inReplyTo":"20230119223858.29262-4-zev@bewilderbeest.net","subject":"Re: [PATCH 3/5] log: Push to/cc handling down into show_log()","fromName":"Calvin Wan","fromEmail":"calvinwan@google.com","sentAt":"2023-02-01T23:52:51Z","receivedAt":"2023-02-01T23:55:47Z","isPatch":true,"sender":{"key":"calvinwan@google.com","avatar":"https://avatars.githubusercontent.com/u/92547554?v=4"},"body":">  void log_write_email_headers(struct rev_info *opt, struct commit *commit,\n>  \t\t\t     const char **extra_headers_p,\n>  \t\t\t     int *need_8bit_cte_p,\n> @@ -647,10 +655,12 @@ static void next_commentary_block(struct rev_info *opt, struct strbuf *sb)\n>  void show_log(struct rev_info *opt)\n>  {\n>  \tstruct strbuf msgbuf = STRBUF_INIT;\n> +\tstruct strbuf hdrbuf = STRBUF_INIT;\n>  \tstruct log_info *log = opt->loginfo;\n>  \tstruct commit *commit = log->commit, *parent = log->parent;\n>  \tint abbrev_commit = opt->abbrev_commit ? opt->abbrev : the_hash_algo->hexsz;\n>  \tconst char *extra_headers = opt->extra_headers;\n> +\tchar *to_free;\n>  \tstruct pretty_print_context ctx = {0};\n>  \n>  \topt->loginfo = NULL;\n> @@ -770,6 +780,11 @@ void show_log(struct rev_info *opt)\n>  \t\tctx.notes_message = strbuf_detach(&notebuf, NULL);\n>  \t}\n>  \n> +\tformat_recipients(opt, &hdrbuf);\n> +\n> +\tif (extra_headers)\n> +\t\tstrbuf_addstr(&hdrbuf, extra_headers);\n> +\n>  \t/*\n>  \t * And then the pretty-printed message itself\n>  \t */\n> @@ -779,7 +794,7 @@ void show_log(struct rev_info *opt)\n>  \tctx.date_mode = opt->date_mode;\n>  \tctx.date_mode_explicit = opt->date_mode_explicit;\n>  \tctx.abbrev = opt->diffopt.abbrev;\n> -\tctx.after_subject = extra_headers;\n> +\tctx.after_subject = to_free = strbuf_detach(&hdrbuf, NULL);\n\nCan you explain in the commit message why hdrbuf, which contains the\noutput from format_recipients and extra_headers, is still the same\nfunctionality as before?\n\n> -void recipients_to_header_buf(const char *hdr, struct strbuf *buf,\n> -\t\t\t      const struct string_list *recipients);\n> +void format_recipients(struct rev_info *rev, struct strbuf *sb);\n\nWhat do you think about renaming this function to strbuf_add_recipients\nand swapping the parameters? \n"},{"id":"471288","messageId":"20230201235253.1891546-4-calvinwan@google.com","threadId":"59125","inReplyTo":"20230119223858.29262-5-zev@bewilderbeest.net","subject":"Re: [PATCH 4/5] pretty: Add name_and_address_only parameter","fromName":"Calvin Wan","fromEmail":"calvinwan@google.com","sentAt":"2023-02-01T23:52:52Z","receivedAt":"2023-02-01T23:55:51Z","isPatch":true,"sender":{"key":"calvinwan@google.com","avatar":"https://avatars.githubusercontent.com/u/92547554?v=4"},"body":"Zev Weiss <zev@bewilderbeest.net> writes:\n> This is meant to be used with pp_user_info() when using it to format\n> email recipients generated by --to-cmd/--cc-cmd.  When set it omits\n> the leading 'From: ', trailing linefeed, and the date suffix, and\n> additionally will return the input string unmodified if\n> split_ident_line() can't parse it (e.g. for a bare email address).\n\nI think the ideal solution, instead of choosing this hacky approach, is\nto refactor out the common functionality you need from pp_user_info()\nand to call that instead in your next patch.\n"},{"id":"471289","messageId":"20230201235253.1891546-5-calvinwan@google.com","threadId":"59125","inReplyTo":"20230119223858.29262-6-zev@bewilderbeest.net","subject":"Re: [PATCH 5/5] format-patch: Add support for --{to,cc}-cmd flags","fromName":"Calvin Wan","fromEmail":"calvinwan@google.com","sentAt":"2023-02-01T23:52:53Z","receivedAt":"2023-02-01T23:55:53Z","isPatch":true,"sender":{"key":"calvinwan@google.com","avatar":"https://avatars.githubusercontent.com/u/92547554?v=4"},"body":"Zev Weiss <zev@bewilderbeest.net> writes:\n> Having these flags available for format-patch instead of only on\n> send-email makes it much easier to use an automated command to do the\n> bulk of the recipient-selection work but still manually adjust it if\n> desired.\n\nCommit message could use some more explanation to make blame diving\neasier later on. In particular, why is creating temp files necessary? \n\nMissing update to Documentation/git-format-patch.txt\n\nThis patch also needs many more tests\n - tests for --cc-cmd\n - tests for commands that run on the patches\n - similar tests in t9001-send-email.sh\n\nWill wait for reroll to dive deeper into the implementation here, but\noverall a well-intentioned v1!\n"}]}