Skip to content

Commit a908047

Browse files
peffgitster
authored andcommitted
teach format-patch to place other authors into in-body "From"
Format-patch generates emails with the "From" address set to the author of each patch. If you are going to send the emails, however, you would want to replace the author identity with yours (if they are not the same), and bump the author identity to an in-body header. Normally this is handled by git-send-email, which does the transformation before sending out the emails. However, some workflows may not use send-email (e.g., imap-send, or a custom script which feeds the mbox to a non-git MUA). They could each implement this feature themselves, but getting it right is non-trivial (one must canonicalize the identities by reversing any RFC2047 encoding or RFC822 quoting of the headers, which has caused many bugs in send-email over the years). This patch takes a different approach: it teaches format-patch a "--from" option which handles the ident check and in-body header while it is writing out the email. It's much simpler to do at this level (because we haven't done any quoting yet), and any workflow based on format-patch can easily turn it on. Signed-off-by: Jeff King <[email protected]> Signed-off-by: Junio C Hamano <[email protected]>
1 parent 10f2fbf commit a908047

File tree

7 files changed

+128
-0
lines changed

7 files changed

+128
-0
lines changed

Documentation/git-format-patch.txt

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -187,6 +187,21 @@ will want to ensure that threading is disabled for `git send-email`.
187187
The negated form `--no-cc` discards all `Cc:` headers added so
188188
far (from config or command line).
189189

190+
--from::
191+
--from=<ident>::
192+
Use `ident` in the `From:` header of each commit email. If the
193+
author ident of the commit is not textually identical to the
194+
provided `ident`, place a `From:` header in the body of the
195+
message with the original author. If no `ident` is given, use
196+
the committer ident.
197+
+
198+
Note that this option is only useful if you are actually sending the
199+
emails and want to identify yourself as the sender, but retain the
200+
original author (and `git am` will correctly pick up the in-body
201+
header). Note also that `git send-email` already handles this
202+
transformation for you, and this option should not be used if you are
203+
feeding the result to `git send-email`.
204+
190205
--add-header=<header>::
191206
Add an arbitrary header to the email headers. This is in addition
192207
to any configured headers, and may be used multiple times.

builtin/log.c

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1112,6 +1112,21 @@ static int cc_callback(const struct option *opt, const char *arg, int unset)
11121112
return 0;
11131113
}
11141114

1115+
static int from_callback(const struct option *opt, const char *arg, int unset)
1116+
{
1117+
char **from = opt->value;
1118+
1119+
free(*from);
1120+
1121+
if (unset)
1122+
*from = NULL;
1123+
else if (arg)
1124+
*from = xstrdup(arg);
1125+
else
1126+
*from = xstrdup(git_committer_info(IDENT_NO_DATE));
1127+
return 0;
1128+
}
1129+
11151130
int cmd_format_patch(int argc, const char **argv, const char *prefix)
11161131
{
11171132
struct commit *commit;
@@ -1134,6 +1149,7 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix)
11341149
int quiet = 0;
11351150
int reroll_count = -1;
11361151
char *branch_name = NULL;
1152+
char *from = NULL;
11371153
const struct option builtin_format_patch_options[] = {
11381154
{ OPTION_CALLBACK, 'n', "numbered", &numbered, NULL,
11391155
N_("use [PATCH n/m] even with a single patch"),
@@ -1177,6 +1193,9 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix)
11771193
0, to_callback },
11781194
{ OPTION_CALLBACK, 0, "cc", NULL, N_("email"), N_("add Cc: header"),
11791195
0, cc_callback },
1196+
{ OPTION_CALLBACK, 0, "from", &from, N_("ident"),
1197+
N_("set From address to <ident> (or committer ident if absent)"),
1198+
PARSE_OPT_OPTARG, from_callback },
11801199
OPT_STRING(0, "in-reply-to", &in_reply_to, N_("message-id"),
11811200
N_("make first mail a reply to <message-id>")),
11821201
{ OPTION_CALLBACK, 0, "attach", &rev, N_("boundary"),
@@ -1264,6 +1283,11 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix)
12641283

12651284
rev.extra_headers = strbuf_detach(&buf, NULL);
12661285

1286+
if (from) {
1287+
if (split_ident_line(&rev.from_ident, from, strlen(from)))
1288+
die(_("invalid ident line: %s"), from);
1289+
}
1290+
12671291
if (start_number < 0)
12681292
start_number = 1;
12691293

commit.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
#include "strbuf.h"
77
#include "decorate.h"
88
#include "gpg-interface.h"
9+
#include "string-list.h"
910

1011
struct commit_list {
1112
struct commit *item;
@@ -95,11 +96,15 @@ struct pretty_print_context {
9596
const char *output_encoding;
9697
struct string_list *mailmap;
9798
int color;
99+
struct ident_split *from_ident;
98100

99101
/*
100102
* Fields below here are manipulated internally by pp_* functions and
101103
* should not be counted on by callers.
102104
*/
105+
106+
/* Manipulated by the pp_* functions internally. */
107+
struct string_list in_body_headers;
103108
};
104109

105110
struct userformat_want {

log-tree.c

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -617,6 +617,8 @@ void show_log(struct rev_info *opt)
617617
ctx.fmt = opt->commit_format;
618618
ctx.mailmap = opt->mailmap;
619619
ctx.color = opt->diffopt.use_color;
620+
if (opt->from_ident.mail_begin && opt->from_ident.name_begin)
621+
ctx.from_ident = &opt->from_ident;
620622
pretty_print_commit(&ctx, commit, &msgbuf);
621623

622624
if (opt->add_signoff)

pretty.c

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -432,6 +432,23 @@ void pp_user_info(struct pretty_print_context *pp,
432432
map_user(pp->mailmap, &mailbuf, &maillen, &namebuf, &namelen);
433433

434434
if (pp->fmt == CMIT_FMT_EMAIL) {
435+
if (pp->from_ident) {
436+
struct strbuf buf = STRBUF_INIT;
437+
438+
strbuf_addstr(&buf, "From: ");
439+
strbuf_add(&buf, namebuf, namelen);
440+
strbuf_addstr(&buf, " <");
441+
strbuf_add(&buf, mailbuf, maillen);
442+
strbuf_addstr(&buf, ">\n");
443+
string_list_append(&pp->in_body_headers,
444+
strbuf_detach(&buf, NULL));
445+
446+
mailbuf = pp->from_ident->mail_begin;
447+
maillen = pp->from_ident->mail_end - mailbuf;
448+
namebuf = pp->from_ident->name_begin;
449+
namelen = pp->from_ident->name_end - namebuf;
450+
}
451+
435452
strbuf_addstr(sb, "From: ");
436453
if (needs_rfc2047_encoding(namebuf, namelen, RFC2047_ADDRESS)) {
437454
add_rfc2047(sb, namebuf, namelen,
@@ -1602,6 +1619,16 @@ void pp_title_line(struct pretty_print_context *pp,
16021619
}
16031620
strbuf_addch(sb, '\n');
16041621

1622+
if (need_8bit_cte == 0) {
1623+
int i;
1624+
for (i = 0; i < pp->in_body_headers.nr; i++) {
1625+
if (has_non_ascii(pp->in_body_headers.items[i].string)) {
1626+
need_8bit_cte = 1;
1627+
break;
1628+
}
1629+
}
1630+
}
1631+
16051632
if (need_8bit_cte > 0) {
16061633
const char *header_fmt =
16071634
"MIME-Version: 1.0\n"
@@ -1615,6 +1642,17 @@ void pp_title_line(struct pretty_print_context *pp,
16151642
if (pp->fmt == CMIT_FMT_EMAIL) {
16161643
strbuf_addch(sb, '\n');
16171644
}
1645+
1646+
if (pp->in_body_headers.nr) {
1647+
int i;
1648+
for (i = 0; i < pp->in_body_headers.nr; i++) {
1649+
strbuf_addstr(sb, pp->in_body_headers.items[i].string);
1650+
free(pp->in_body_headers.items[i].string);
1651+
}
1652+
string_list_clear(&pp->in_body_headers, 0);
1653+
strbuf_addch(sb, '\n');
1654+
}
1655+
16181656
strbuf_release(&title);
16191657
}
16201658

revision.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -144,6 +144,7 @@ struct rev_info {
144144
int numbered_files;
145145
int reroll_count;
146146
char *message_id;
147+
struct ident_split from_ident;
147148
struct string_list *ref_message_ids;
148149
int add_signoff;
149150
const char *extra_headers;

t/t4014-format-patch.sh

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -972,6 +972,49 @@ test_expect_success 'empty subject prefix does not have extra space' '
972972
test_cmp expect actual
973973
'
974974

975+
test_expect_success '--from=ident notices bogus ident' '
976+
test_must_fail git format-patch -1 --stdout --from=foo >patch
977+
'
978+
979+
test_expect_success '--from=ident replaces author' '
980+
git format-patch -1 --stdout --from="Me <[email protected]>" >patch &&
981+
cat >expect <<-\EOF &&
982+
From: Me <[email protected]>
983+
984+
From: A U Thor <[email protected]>
985+
986+
EOF
987+
sed -ne "/^From:/p; /^$/p; /^---$/q" <patch >patch.head &&
988+
test_cmp expect patch.head
989+
'
990+
991+
test_expect_success '--from uses committer ident' '
992+
git format-patch -1 --stdout --from >patch &&
993+
cat >expect <<-\EOF &&
994+
From: C O Mitter <[email protected]>
995+
996+
From: A U Thor <[email protected]>
997+
998+
EOF
999+
sed -ne "/^From:/p; /^$/p; /^---$/q" <patch >patch.head &&
1000+
test_cmp expect patch.head
1001+
'
1002+
1003+
test_expect_success 'in-body headers trigger content encoding' '
1004+
GIT_AUTHOR_NAME="éxötìc" test_commit exotic &&
1005+
test_when_finished "git reset --hard HEAD^" &&
1006+
git format-patch -1 --stdout --from >patch &&
1007+
cat >expect <<-\EOF &&
1008+
From: C O Mitter <[email protected]>
1009+
Content-Type: text/plain; charset=UTF-8
1010+
1011+
From: éxötìc <[email protected]>
1012+
1013+
EOF
1014+
sed -ne "/^From:/p; /^$/p; /^Content-Type/p; /^---$/q" <patch >patch.head &&
1015+
test_cmp expect patch.head
1016+
'
1017+
9751018
append_signoff()
9761019
{
9771020
C=$(git commit-tree HEAD^^{tree} -p HEAD) &&

0 commit comments

Comments
 (0)