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 | ||||
| 	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 an arbitrary header to the email headers.  This is in addition | ||||
| 	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; | ||||
| } | ||||
|  | ||||
| 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) | ||||
| { | ||||
| 	struct commit *commit; | ||||
| @ -1134,6 +1149,7 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix) | ||||
| 	int quiet = 0; | ||||
| 	int reroll_count = -1; | ||||
| 	char *branch_name = NULL; | ||||
| 	char *from = NULL; | ||||
| 	const struct option builtin_format_patch_options[] = { | ||||
| 		{ OPTION_CALLBACK, 'n', "numbered", &numbered, NULL, | ||||
| 			    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 }, | ||||
| 		{ OPTION_CALLBACK, 0, "cc", NULL, N_("email"), N_("add Cc: header"), | ||||
| 			    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"), | ||||
| 			    N_("make first mail a reply to <message-id>")), | ||||
| 		{ 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); | ||||
|  | ||||
| 	if (from) { | ||||
| 		if (split_ident_line(&rev.from_ident, from, strlen(from))) | ||||
| 			die(_("invalid ident line: %s"), from); | ||||
| 	} | ||||
|  | ||||
| 	if (start_number < 0) | ||||
| 		start_number = 1; | ||||
|  | ||||
|  | ||||
							
								
								
									
										21
									
								
								commit.h
									
									
									
									
									
								
							
							
						
						
									
										21
									
								
								commit.h
									
									
									
									
									
								
							| @ -6,6 +6,7 @@ | ||||
| #include "strbuf.h" | ||||
| #include "decorate.h" | ||||
| #include "gpg-interface.h" | ||||
| #include "string-list.h" | ||||
|  | ||||
| struct commit_list { | ||||
| 	struct commit *item; | ||||
| @ -79,6 +80,9 @@ enum cmit_fmt { | ||||
| }; | ||||
|  | ||||
| struct pretty_print_context { | ||||
| 	/* | ||||
| 	 * Callers should tweak these to change the behavior of pp_* functions. | ||||
| 	 */ | ||||
| 	enum cmit_fmt fmt; | ||||
| 	int abbrev; | ||||
| 	const char *subject; | ||||
| @ -92,6 +96,15 @@ struct pretty_print_context { | ||||
| 	const char *output_encoding; | ||||
| 	struct string_list *mailmap; | ||||
| 	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 { | ||||
| @ -111,20 +124,20 @@ extern void userformat_find_requirements(const char *fmt, struct userformat_want | ||||
| extern void format_commit_message(const struct commit *commit, | ||||
| 				  const char *format, struct strbuf *sb, | ||||
| 				  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, | ||||
| 				struct strbuf *sb); | ||||
| extern void pp_commit_easy(enum cmit_fmt fmt, const struct commit *commit, | ||||
| 			   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 *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, | ||||
| 		   struct strbuf *sb, | ||||
| 		   const char *encoding, | ||||
| 		   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, | ||||
| 		  struct strbuf *sb, | ||||
| 		  int indent); | ||||
|  | ||||
| @ -618,6 +618,8 @@ void show_log(struct rev_info *opt) | ||||
| 	ctx.mailmap = opt->mailmap; | ||||
| 	ctx.color = opt->diffopt.use_color; | ||||
| 	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); | ||||
|  | ||||
| 	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); | ||||
| } | ||||
|  | ||||
| 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 *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); | ||||
|  | ||||
| 	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: "); | ||||
| 		if (needs_rfc2047_encoding(namebuf, namelen, RFC2047_ADDRESS)) { | ||||
| 			add_rfc2047(sb, namebuf, namelen, | ||||
| @ -1514,7 +1531,7 @@ void format_commit_message(const struct commit *commit, | ||||
| 	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 struct commit *commit, | ||||
| 		      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, | ||||
| 		   struct strbuf *sb, | ||||
| 		   const char *encoding, | ||||
| @ -1602,6 +1619,16 @@ void pp_title_line(const struct pretty_print_context *pp, | ||||
| 	} | ||||
| 	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) { | ||||
| 		const char *header_fmt = | ||||
| 			"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) { | ||||
| 		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); | ||||
| } | ||||
|  | ||||
| void pp_remainder(const struct pretty_print_context *pp, | ||||
| void pp_remainder(struct pretty_print_context *pp, | ||||
| 		  const char **msg_p, | ||||
| 		  struct strbuf *sb, | ||||
| 		  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, | ||||
| 			 struct strbuf *sb) | ||||
| { | ||||
|  | ||||
| @ -144,6 +144,7 @@ struct rev_info { | ||||
| 	int		numbered_files; | ||||
| 	int		reroll_count; | ||||
| 	char		*message_id; | ||||
| 	struct ident_split from_ident; | ||||
| 	struct string_list *ref_message_ids; | ||||
| 	int		add_signoff; | ||||
| 	const char	*extra_headers; | ||||
|  | ||||
| @ -972,6 +972,49 @@ test_expect_success 'empty subject prefix does not have extra space' ' | ||||
| 	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() | ||||
| { | ||||
| 	C=$(git commit-tree HEAD^^{tree} -p HEAD) && | ||||
|  | ||||
		Reference in New Issue
	
	Block a user
	 Junio C Hamano
					Junio C Hamano