Merge branch 'jk/format-patch-from'

"git format-patch" learned "--from[=whom]" option, which sets the
"From: " header to the specified person (or the person who runs the
command, if "=whom" part is missing) and move the original author
information to an in-body From: header as necessary.

* jk/format-patch-from:
  teach format-patch to place other authors into in-body "From"
  pretty.c: drop const-ness from pretty_print_context
This commit is contained in:
Junio C Hamano 2013-07-15 10:28:39 -07:00
commit 22fcbc420e
7 changed files with 145 additions and 9 deletions

View File

@ -187,6 +187,21 @@ will want to ensure that threading is disabled for `git send-email`.
The negated form `--no-cc` discards all `Cc:` headers added so The negated form `--no-cc` discards all `Cc:` headers added so
far (from config or command line). far (from config or command line).
--from::
--from=<ident>::
Use `ident` in the `From:` header of each commit email. If the
author ident of the commit is not textually identical to the
provided `ident`, place a `From:` header in the body of the
message with the original author. If no `ident` is given, use
the committer ident.
+
Note that this option is only useful if you are actually sending the
emails and want to identify yourself as the sender, but retain the
original author (and `git am` will correctly pick up the in-body
header). Note also that `git send-email` already handles this
transformation for you, and this option should not be used if you are
feeding the result to `git send-email`.
--add-header=<header>:: --add-header=<header>::
Add an arbitrary header to the email headers. This is in addition Add an arbitrary header to the email headers. This is in addition
to any configured headers, and may be used multiple times. to any configured headers, and may be used multiple times.

View File

@ -1112,6 +1112,21 @@ static int cc_callback(const struct option *opt, const char *arg, int unset)
return 0; return 0;
} }
static int from_callback(const struct option *opt, const char *arg, int unset)
{
char **from = opt->value;
free(*from);
if (unset)
*from = NULL;
else if (arg)
*from = xstrdup(arg);
else
*from = xstrdup(git_committer_info(IDENT_NO_DATE));
return 0;
}
int cmd_format_patch(int argc, const char **argv, const char *prefix) int cmd_format_patch(int argc, const char **argv, const char *prefix)
{ {
struct commit *commit; struct commit *commit;
@ -1134,6 +1149,7 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix)
int quiet = 0; int quiet = 0;
int reroll_count = -1; int reroll_count = -1;
char *branch_name = NULL; char *branch_name = NULL;
char *from = NULL;
const struct option builtin_format_patch_options[] = { const struct option builtin_format_patch_options[] = {
{ OPTION_CALLBACK, 'n', "numbered", &numbered, NULL, { OPTION_CALLBACK, 'n', "numbered", &numbered, NULL,
N_("use [PATCH n/m] even with a single patch"), 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)
0, to_callback }, 0, to_callback },
{ OPTION_CALLBACK, 0, "cc", NULL, N_("email"), N_("add Cc: header"), { OPTION_CALLBACK, 0, "cc", NULL, N_("email"), N_("add Cc: header"),
0, cc_callback }, 0, cc_callback },
{ OPTION_CALLBACK, 0, "from", &from, N_("ident"),
N_("set From address to <ident> (or committer ident if absent)"),
PARSE_OPT_OPTARG, from_callback },
OPT_STRING(0, "in-reply-to", &in_reply_to, N_("message-id"), OPT_STRING(0, "in-reply-to", &in_reply_to, N_("message-id"),
N_("make first mail a reply to <message-id>")), N_("make first mail a reply to <message-id>")),
{ OPTION_CALLBACK, 0, "attach", &rev, N_("boundary"), { OPTION_CALLBACK, 0, "attach", &rev, N_("boundary"),
@ -1264,6 +1283,11 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix)
rev.extra_headers = strbuf_detach(&buf, NULL); rev.extra_headers = strbuf_detach(&buf, NULL);
if (from) {
if (split_ident_line(&rev.from_ident, from, strlen(from)))
die(_("invalid ident line: %s"), from);
}
if (start_number < 0) if (start_number < 0)
start_number = 1; start_number = 1;

View File

@ -6,6 +6,7 @@
#include "strbuf.h" #include "strbuf.h"
#include "decorate.h" #include "decorate.h"
#include "gpg-interface.h" #include "gpg-interface.h"
#include "string-list.h"
struct commit_list { struct commit_list {
struct commit *item; struct commit *item;
@ -79,6 +80,9 @@ enum cmit_fmt {
}; };
struct pretty_print_context { struct pretty_print_context {
/*
* Callers should tweak these to change the behavior of pp_* functions.
*/
enum cmit_fmt fmt; enum cmit_fmt fmt;
int abbrev; int abbrev;
const char *subject; const char *subject;
@ -92,6 +96,15 @@ struct pretty_print_context {
const char *output_encoding; const char *output_encoding;
struct string_list *mailmap; struct string_list *mailmap;
int color; int color;
struct ident_split *from_ident;
/*
* Fields below here are manipulated internally by pp_* functions and
* should not be counted on by callers.
*/
/* Manipulated by the pp_* functions internally. */
struct string_list in_body_headers;
}; };
struct userformat_want { struct userformat_want {
@ -111,20 +124,20 @@ extern void userformat_find_requirements(const char *fmt, struct userformat_want
extern void format_commit_message(const struct commit *commit, extern void format_commit_message(const struct commit *commit,
const char *format, struct strbuf *sb, const char *format, struct strbuf *sb,
const struct pretty_print_context *context); const struct pretty_print_context *context);
extern void pretty_print_commit(const struct pretty_print_context *pp, extern void pretty_print_commit(struct pretty_print_context *pp,
const struct commit *commit, const struct commit *commit,
struct strbuf *sb); struct strbuf *sb);
extern void pp_commit_easy(enum cmit_fmt fmt, const struct commit *commit, extern void pp_commit_easy(enum cmit_fmt fmt, const struct commit *commit,
struct strbuf *sb); struct strbuf *sb);
void pp_user_info(const struct pretty_print_context *pp, void pp_user_info(struct pretty_print_context *pp,
const char *what, struct strbuf *sb, const char *what, struct strbuf *sb,
const char *line, const char *encoding); const char *line, const char *encoding);
void pp_title_line(const struct pretty_print_context *pp, void pp_title_line(struct pretty_print_context *pp,
const char **msg_p, const char **msg_p,
struct strbuf *sb, struct strbuf *sb,
const char *encoding, const char *encoding,
int need_8bit_cte); int need_8bit_cte);
void pp_remainder(const struct pretty_print_context *pp, void pp_remainder(struct pretty_print_context *pp,
const char **msg_p, const char **msg_p,
struct strbuf *sb, struct strbuf *sb,
int indent); int indent);

View File

@ -618,6 +618,8 @@ void show_log(struct rev_info *opt)
ctx.mailmap = opt->mailmap; ctx.mailmap = opt->mailmap;
ctx.color = opt->diffopt.use_color; ctx.color = opt->diffopt.use_color;
ctx.output_encoding = get_log_output_encoding(); ctx.output_encoding = get_log_output_encoding();
if (opt->from_ident.mail_begin && opt->from_ident.name_begin)
ctx.from_ident = &opt->from_ident;
pretty_print_commit(&ctx, commit, &msgbuf); pretty_print_commit(&ctx, commit, &msgbuf);
if (opt->add_signoff) if (opt->add_signoff)

View File

@ -406,7 +406,7 @@ static const char *show_ident_date(const struct ident_split *ident,
return show_date(date, tz, mode); return show_date(date, tz, mode);
} }
void pp_user_info(const struct pretty_print_context *pp, void pp_user_info(struct pretty_print_context *pp,
const char *what, struct strbuf *sb, const char *what, struct strbuf *sb,
const char *line, const char *encoding) const char *line, const char *encoding)
{ {
@ -432,6 +432,23 @@ void pp_user_info(const struct pretty_print_context *pp,
map_user(pp->mailmap, &mailbuf, &maillen, &namebuf, &namelen); map_user(pp->mailmap, &mailbuf, &maillen, &namebuf, &namelen);
if (pp->fmt == CMIT_FMT_EMAIL) { if (pp->fmt == CMIT_FMT_EMAIL) {
if (pp->from_ident) {
struct strbuf buf = STRBUF_INIT;
strbuf_addstr(&buf, "From: ");
strbuf_add(&buf, namebuf, namelen);
strbuf_addstr(&buf, " <");
strbuf_add(&buf, mailbuf, maillen);
strbuf_addstr(&buf, ">\n");
string_list_append(&pp->in_body_headers,
strbuf_detach(&buf, NULL));
mailbuf = pp->from_ident->mail_begin;
maillen = pp->from_ident->mail_end - mailbuf;
namebuf = pp->from_ident->name_begin;
namelen = pp->from_ident->name_end - namebuf;
}
strbuf_addstr(sb, "From: "); strbuf_addstr(sb, "From: ");
if (needs_rfc2047_encoding(namebuf, namelen, RFC2047_ADDRESS)) { if (needs_rfc2047_encoding(namebuf, namelen, RFC2047_ADDRESS)) {
add_rfc2047(sb, namebuf, namelen, add_rfc2047(sb, namebuf, namelen,
@ -1514,7 +1531,7 @@ void format_commit_message(const struct commit *commit,
free(context.signature_check.signer); free(context.signature_check.signer);
} }
static void pp_header(const struct pretty_print_context *pp, static void pp_header(struct pretty_print_context *pp,
const char *encoding, const char *encoding,
const struct commit *commit, const struct commit *commit,
const char **msg_p, const char **msg_p,
@ -1575,7 +1592,7 @@ static void pp_header(const struct pretty_print_context *pp,
} }
} }
void pp_title_line(const struct pretty_print_context *pp, void pp_title_line(struct pretty_print_context *pp,
const char **msg_p, const char **msg_p,
struct strbuf *sb, struct strbuf *sb,
const char *encoding, const char *encoding,
@ -1602,6 +1619,16 @@ void pp_title_line(const struct pretty_print_context *pp,
} }
strbuf_addch(sb, '\n'); strbuf_addch(sb, '\n');
if (need_8bit_cte == 0) {
int i;
for (i = 0; i < pp->in_body_headers.nr; i++) {
if (has_non_ascii(pp->in_body_headers.items[i].string)) {
need_8bit_cte = 1;
break;
}
}
}
if (need_8bit_cte > 0) { if (need_8bit_cte > 0) {
const char *header_fmt = const char *header_fmt =
"MIME-Version: 1.0\n" "MIME-Version: 1.0\n"
@ -1615,10 +1642,21 @@ void pp_title_line(const struct pretty_print_context *pp,
if (pp->fmt == CMIT_FMT_EMAIL) { if (pp->fmt == CMIT_FMT_EMAIL) {
strbuf_addch(sb, '\n'); strbuf_addch(sb, '\n');
} }
if (pp->in_body_headers.nr) {
int i;
for (i = 0; i < pp->in_body_headers.nr; i++) {
strbuf_addstr(sb, pp->in_body_headers.items[i].string);
free(pp->in_body_headers.items[i].string);
}
string_list_clear(&pp->in_body_headers, 0);
strbuf_addch(sb, '\n');
}
strbuf_release(&title); strbuf_release(&title);
} }
void pp_remainder(const struct pretty_print_context *pp, void pp_remainder(struct pretty_print_context *pp,
const char **msg_p, const char **msg_p,
struct strbuf *sb, struct strbuf *sb,
int indent) int indent)
@ -1650,7 +1688,7 @@ void pp_remainder(const struct pretty_print_context *pp,
} }
} }
void pretty_print_commit(const struct pretty_print_context *pp, void pretty_print_commit(struct pretty_print_context *pp,
const struct commit *commit, const struct commit *commit,
struct strbuf *sb) struct strbuf *sb)
{ {

View File

@ -144,6 +144,7 @@ struct rev_info {
int numbered_files; int numbered_files;
int reroll_count; int reroll_count;
char *message_id; char *message_id;
struct ident_split from_ident;
struct string_list *ref_message_ids; struct string_list *ref_message_ids;
int add_signoff; int add_signoff;
const char *extra_headers; const char *extra_headers;

View File

@ -972,6 +972,49 @@ test_expect_success 'empty subject prefix does not have extra space' '
test_cmp expect actual test_cmp expect actual
' '
test_expect_success '--from=ident notices bogus ident' '
test_must_fail git format-patch -1 --stdout --from=foo >patch
'
test_expect_success '--from=ident replaces author' '
git format-patch -1 --stdout --from="Me <me@example.com>" >patch &&
cat >expect <<-\EOF &&
From: Me <me@example.com>
From: A U Thor <author@example.com>
EOF
sed -ne "/^From:/p; /^$/p; /^---$/q" <patch >patch.head &&
test_cmp expect patch.head
'
test_expect_success '--from uses committer ident' '
git format-patch -1 --stdout --from >patch &&
cat >expect <<-\EOF &&
From: C O Mitter <committer@example.com>
From: A U Thor <author@example.com>
EOF
sed -ne "/^From:/p; /^$/p; /^---$/q" <patch >patch.head &&
test_cmp expect patch.head
'
test_expect_success 'in-body headers trigger content encoding' '
GIT_AUTHOR_NAME="éxötìc" test_commit exotic &&
test_when_finished "git reset --hard HEAD^" &&
git format-patch -1 --stdout --from >patch &&
cat >expect <<-\EOF &&
From: C O Mitter <committer@example.com>
Content-Type: text/plain; charset=UTF-8
From: éxötìc <author@example.com>
EOF
sed -ne "/^From:/p; /^$/p; /^Content-Type/p; /^---$/q" <patch >patch.head &&
test_cmp expect patch.head
'
append_signoff() append_signoff()
{ {
C=$(git commit-tree HEAD^^{tree} -p HEAD) && C=$(git commit-tree HEAD^^{tree} -p HEAD) &&