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:
		| @ -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. | ||||||
|  | |||||||
| @ -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; | ||||||
|  |  | ||||||
|  | |||||||
							
								
								
									
										21
									
								
								commit.h
									
									
									
									
									
								
							
							
						
						
									
										21
									
								
								commit.h
									
									
									
									
									
								
							| @ -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); | ||||||
|  | |||||||
| @ -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) | ||||||
|  | |||||||
							
								
								
									
										48
									
								
								pretty.c
									
									
									
									
									
								
							
							
						
						
									
										48
									
								
								pretty.c
									
									
									
									
									
								
							| @ -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) | ||||||
| { | { | ||||||
|  | |||||||
| @ -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; | ||||||
|  | |||||||
| @ -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) && | ||||||
|  | |||||||
		Reference in New Issue
	
	Block a user
	 Junio C Hamano
					Junio C Hamano