pretty: support "mboxrd" output format
This output format prevents format-patch output from breaking readers if somebody copy+pasted an mbox into a commit message. Unlike the traditional "mboxo" format, "mboxrd" is designed to be fully-reversible. "mboxrd" also gracefully degrades to showing extra ">" in existing "mboxo" readers. This degradation is preferable to breaking message splitting completely, a problem I've seen in "mboxcl" due to having multiple, non-existent, or inaccurate Content-Length headers. "mboxcl2" is a non-starter since it's inherits the problems of "mboxcl" while being completely incompatible with existing tooling based around mailsplit. ref: http://homepage.ntlworld.com/jonathan.deboynepollard/FGA/mail-mbox-formats.html Signed-off-by: Eric Wong <e@80x24.org> Signed-off-by: Junio C Hamano <gitster@pobox.com>maint
							parent
							
								
									6326f19925
								
							
						
					
					
						commit
						9f23e04061
					
				|  | @ -953,7 +953,7 @@ static void make_cover_letter(struct rev_info *rev, int use_stdout, | ||||||
| 	struct pretty_print_context pp = {0}; | 	struct pretty_print_context pp = {0}; | ||||||
| 	struct commit *head = list[0]; | 	struct commit *head = list[0]; | ||||||
|  |  | ||||||
| 	if (rev->commit_format != CMIT_FMT_EMAIL) | 	if (!cmit_fmt_is_mail(rev->commit_format)) | ||||||
| 		die(_("Cover letter needs email format")); | 		die(_("Cover letter needs email format")); | ||||||
|  |  | ||||||
| 	committer = git_committer_info(0); | 	committer = git_committer_info(0); | ||||||
|  |  | ||||||
							
								
								
									
										6
									
								
								commit.h
								
								
								
								
							
							
						
						
									
										6
									
								
								commit.h
								
								
								
								
							|  | @ -131,11 +131,17 @@ enum cmit_fmt { | ||||||
| 	CMIT_FMT_FULLER, | 	CMIT_FMT_FULLER, | ||||||
| 	CMIT_FMT_ONELINE, | 	CMIT_FMT_ONELINE, | ||||||
| 	CMIT_FMT_EMAIL, | 	CMIT_FMT_EMAIL, | ||||||
|  | 	CMIT_FMT_MBOXRD, | ||||||
| 	CMIT_FMT_USERFORMAT, | 	CMIT_FMT_USERFORMAT, | ||||||
|  |  | ||||||
| 	CMIT_FMT_UNSPECIFIED | 	CMIT_FMT_UNSPECIFIED | ||||||
| }; | }; | ||||||
|  |  | ||||||
|  | static inline int cmit_fmt_is_mail(enum cmit_fmt fmt) | ||||||
|  | { | ||||||
|  | 	return (fmt == CMIT_FMT_EMAIL || fmt == CMIT_FMT_MBOXRD); | ||||||
|  | } | ||||||
|  |  | ||||||
| struct pretty_print_context { | struct pretty_print_context { | ||||||
| 	/* | 	/* | ||||||
| 	 * Callers should tweak these to change the behavior of pp_* functions. | 	 * Callers should tweak these to change the behavior of pp_* functions. | ||||||
|  |  | ||||||
|  | @ -603,7 +603,7 @@ void show_log(struct rev_info *opt) | ||||||
| 	 * Print header line of header.. | 	 * Print header line of header.. | ||||||
| 	 */ | 	 */ | ||||||
|  |  | ||||||
| 	if (opt->commit_format == CMIT_FMT_EMAIL) { | 	if (cmit_fmt_is_mail(opt->commit_format)) { | ||||||
| 		log_write_email_headers(opt, commit, &ctx.subject, &extra_headers, | 		log_write_email_headers(opt, commit, &ctx.subject, &extra_headers, | ||||||
| 					&ctx.need_8bit_cte); | 					&ctx.need_8bit_cte); | ||||||
| 	} else if (opt->commit_format != CMIT_FMT_USERFORMAT) { | 	} else if (opt->commit_format != CMIT_FMT_USERFORMAT) { | ||||||
|  | @ -694,7 +694,7 @@ void show_log(struct rev_info *opt) | ||||||
|  |  | ||||||
| 	if ((ctx.fmt != CMIT_FMT_USERFORMAT) && | 	if ((ctx.fmt != CMIT_FMT_USERFORMAT) && | ||||||
| 	    ctx.notes_message && *ctx.notes_message) { | 	    ctx.notes_message && *ctx.notes_message) { | ||||||
| 		if (ctx.fmt == CMIT_FMT_EMAIL) { | 		if (cmit_fmt_is_mail(ctx.fmt)) { | ||||||
| 			strbuf_addstr(&msgbuf, "---\n"); | 			strbuf_addstr(&msgbuf, "---\n"); | ||||||
| 			opt->shown_dashes = 1; | 			opt->shown_dashes = 1; | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
							
								
								
									
										33
									
								
								pretty.c
								
								
								
								
							
							
						
						
									
										33
									
								
								pretty.c
								
								
								
								
							|  | @ -92,6 +92,7 @@ static void setup_commit_formats(void) | ||||||
| 		{ "medium",	CMIT_FMT_MEDIUM,	0,	8 }, | 		{ "medium",	CMIT_FMT_MEDIUM,	0,	8 }, | ||||||
| 		{ "short",	CMIT_FMT_SHORT,		0,	0 }, | 		{ "short",	CMIT_FMT_SHORT,		0,	0 }, | ||||||
| 		{ "email",	CMIT_FMT_EMAIL,		0,	0 }, | 		{ "email",	CMIT_FMT_EMAIL,		0,	0 }, | ||||||
|  | 		{ "mboxrd",	CMIT_FMT_MBOXRD,	0,	0 }, | ||||||
| 		{ "fuller",	CMIT_FMT_FULLER,	0,	8 }, | 		{ "fuller",	CMIT_FMT_FULLER,	0,	8 }, | ||||||
| 		{ "full",	CMIT_FMT_FULL,		0,	8 }, | 		{ "full",	CMIT_FMT_FULL,		0,	8 }, | ||||||
| 		{ "oneline",	CMIT_FMT_ONELINE,	1,	0 } | 		{ "oneline",	CMIT_FMT_ONELINE,	1,	0 } | ||||||
|  | @ -444,7 +445,7 @@ void pp_user_info(struct pretty_print_context *pp, | ||||||
| 	if (pp->mailmap) | 	if (pp->mailmap) | ||||||
| 		map_user(pp->mailmap, &mailbuf, &maillen, &namebuf, &namelen); | 		map_user(pp->mailmap, &mailbuf, &maillen, &namebuf, &namelen); | ||||||
|  |  | ||||||
| 	if (pp->fmt == CMIT_FMT_EMAIL) { | 	if (cmit_fmt_is_mail(pp->fmt)) { | ||||||
| 		if (pp->from_ident && ident_cmp(pp->from_ident, &ident)) { | 		if (pp->from_ident && ident_cmp(pp->from_ident, &ident)) { | ||||||
| 			struct strbuf buf = STRBUF_INIT; | 			struct strbuf buf = STRBUF_INIT; | ||||||
|  |  | ||||||
|  | @ -494,6 +495,7 @@ void pp_user_info(struct pretty_print_context *pp, | ||||||
| 			    show_ident_date(&ident, &pp->date_mode)); | 			    show_ident_date(&ident, &pp->date_mode)); | ||||||
| 		break; | 		break; | ||||||
| 	case CMIT_FMT_EMAIL: | 	case CMIT_FMT_EMAIL: | ||||||
|  | 	case CMIT_FMT_MBOXRD: | ||||||
| 		strbuf_addf(sb, "Date: %s\n", | 		strbuf_addf(sb, "Date: %s\n", | ||||||
| 			    show_ident_date(&ident, DATE_MODE(RFC2822))); | 			    show_ident_date(&ident, DATE_MODE(RFC2822))); | ||||||
| 		break; | 		break; | ||||||
|  | @ -535,7 +537,7 @@ static void add_merge_info(const struct pretty_print_context *pp, | ||||||
| { | { | ||||||
| 	struct commit_list *parent = commit->parents; | 	struct commit_list *parent = commit->parents; | ||||||
|  |  | ||||||
| 	if ((pp->fmt == CMIT_FMT_ONELINE) || (pp->fmt == CMIT_FMT_EMAIL) || | 	if ((pp->fmt == CMIT_FMT_ONELINE) || (cmit_fmt_is_mail(pp->fmt)) || | ||||||
| 	    !parent || !parent->next) | 	    !parent || !parent->next) | ||||||
| 		return; | 		return; | ||||||
|  |  | ||||||
|  | @ -1614,7 +1616,7 @@ void pp_title_line(struct pretty_print_context *pp, | ||||||
| 	if (pp->after_subject) { | 	if (pp->after_subject) { | ||||||
| 		strbuf_addstr(sb, pp->after_subject); | 		strbuf_addstr(sb, pp->after_subject); | ||||||
| 	} | 	} | ||||||
| 	if (pp->fmt == CMIT_FMT_EMAIL) { | 	if (cmit_fmt_is_mail(pp->fmt)) { | ||||||
| 		strbuf_addch(sb, '\n'); | 		strbuf_addch(sb, '\n'); | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
|  | @ -1697,6 +1699,16 @@ static void pp_handle_indent(struct pretty_print_context *pp, | ||||||
| 		strbuf_add(sb, line, linelen); | 		strbuf_add(sb, line, linelen); | ||||||
| } | } | ||||||
|  |  | ||||||
|  | static int is_mboxrd_from(const char *line, int len) | ||||||
|  | { | ||||||
|  | 	/* | ||||||
|  | 	 * a line matching /^From $/ here would only have len == 4 | ||||||
|  | 	 * at this point because is_empty_line would've trimmed all | ||||||
|  | 	 * trailing space | ||||||
|  | 	 */ | ||||||
|  | 	return len > 4 && starts_with(line + strspn(line, ">"), "From "); | ||||||
|  | } | ||||||
|  |  | ||||||
| void pp_remainder(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, | ||||||
|  | @ -1725,8 +1737,13 @@ void pp_remainder(struct pretty_print_context *pp, | ||||||
| 		else if (pp->expand_tabs_in_log) | 		else if (pp->expand_tabs_in_log) | ||||||
| 			strbuf_add_tabexpand(sb, pp->expand_tabs_in_log, | 			strbuf_add_tabexpand(sb, pp->expand_tabs_in_log, | ||||||
| 					     line, linelen); | 					     line, linelen); | ||||||
| 		else | 		else { | ||||||
|  | 			if (pp->fmt == CMIT_FMT_MBOXRD && | ||||||
|  | 					is_mboxrd_from(line, linelen)) | ||||||
|  | 				strbuf_addch(sb, '>'); | ||||||
|  |  | ||||||
| 			strbuf_add(sb, line, linelen); | 			strbuf_add(sb, line, linelen); | ||||||
|  | 		} | ||||||
| 		strbuf_addch(sb, '\n'); | 		strbuf_addch(sb, '\n'); | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  | @ -1750,14 +1767,14 @@ void pretty_print_commit(struct pretty_print_context *pp, | ||||||
| 	encoding = get_log_output_encoding(); | 	encoding = get_log_output_encoding(); | ||||||
| 	msg = reencoded = logmsg_reencode(commit, NULL, encoding); | 	msg = reencoded = logmsg_reencode(commit, NULL, encoding); | ||||||
|  |  | ||||||
| 	if (pp->fmt == CMIT_FMT_ONELINE || pp->fmt == CMIT_FMT_EMAIL) | 	if (pp->fmt == CMIT_FMT_ONELINE || cmit_fmt_is_mail(pp->fmt)) | ||||||
| 		indent = 0; | 		indent = 0; | ||||||
|  |  | ||||||
| 	/* | 	/* | ||||||
| 	 * We need to check and emit Content-type: to mark it | 	 * We need to check and emit Content-type: to mark it | ||||||
| 	 * as 8-bit if we haven't done so. | 	 * as 8-bit if we haven't done so. | ||||||
| 	 */ | 	 */ | ||||||
| 	if (pp->fmt == CMIT_FMT_EMAIL && need_8bit_cte == 0) { | 	if (cmit_fmt_is_mail(pp->fmt) && need_8bit_cte == 0) { | ||||||
| 		int i, ch, in_body; | 		int i, ch, in_body; | ||||||
|  |  | ||||||
| 		for (in_body = i = 0; (ch = msg[i]); i++) { | 		for (in_body = i = 0; (ch = msg[i]); i++) { | ||||||
|  | @ -1785,7 +1802,7 @@ void pretty_print_commit(struct pretty_print_context *pp, | ||||||
| 	msg = skip_empty_lines(msg); | 	msg = skip_empty_lines(msg); | ||||||
|  |  | ||||||
| 	/* These formats treat the title line specially. */ | 	/* These formats treat the title line specially. */ | ||||||
| 	if (pp->fmt == CMIT_FMT_ONELINE || pp->fmt == CMIT_FMT_EMAIL) | 	if (pp->fmt == CMIT_FMT_ONELINE || cmit_fmt_is_mail(pp->fmt)) | ||||||
| 		pp_title_line(pp, &msg, sb, encoding, need_8bit_cte); | 		pp_title_line(pp, &msg, sb, encoding, need_8bit_cte); | ||||||
|  |  | ||||||
| 	beginning_of_body = sb->len; | 	beginning_of_body = sb->len; | ||||||
|  | @ -1802,7 +1819,7 @@ void pretty_print_commit(struct pretty_print_context *pp, | ||||||
| 	 * format.  Make sure we did not strip the blank line | 	 * format.  Make sure we did not strip the blank line | ||||||
| 	 * between the header and the body. | 	 * between the header and the body. | ||||||
| 	 */ | 	 */ | ||||||
| 	if (pp->fmt == CMIT_FMT_EMAIL && sb->len <= beginning_of_body) | 	if (cmit_fmt_is_mail(pp->fmt) && sb->len <= beginning_of_body) | ||||||
| 		strbuf_addch(sb, '\n'); | 		strbuf_addch(sb, '\n'); | ||||||
|  |  | ||||||
| 	unuse_commit_buffer(commit, reencoded); | 	unuse_commit_buffer(commit, reencoded); | ||||||
|  |  | ||||||
|  | @ -1565,4 +1565,45 @@ test_expect_success 'format-patch --base overrides format.useAutoBase' ' | ||||||
| 	test_cmp expected actual | 	test_cmp expected actual | ||||||
| ' | ' | ||||||
|  |  | ||||||
|  | test_expect_success 'format-patch --pretty=mboxrd' ' | ||||||
|  | 	sp=" " && | ||||||
|  | 	cat >msg <<-INPUT_END && | ||||||
|  | 	mboxrd should escape the body | ||||||
|  |  | ||||||
|  | 	From could trip up a loose mbox parser | ||||||
|  | 	>From extra escape for reversibility | ||||||
|  | 	>>From extra escape for reversibility 2 | ||||||
|  | 	from lower case not escaped | ||||||
|  | 	Fromm bad speling not escaped | ||||||
|  | 	 From with leading space not escaped | ||||||
|  |  | ||||||
|  | 	F | ||||||
|  | 	From | ||||||
|  | 	From$sp | ||||||
|  | 	From    $sp | ||||||
|  | 	From	$sp | ||||||
|  | 	INPUT_END | ||||||
|  |  | ||||||
|  | 	cat >expect <<-INPUT_END && | ||||||
|  | 	>From could trip up a loose mbox parser | ||||||
|  | 	>>From extra escape for reversibility | ||||||
|  | 	>>>From extra escape for reversibility 2 | ||||||
|  | 	from lower case not escaped | ||||||
|  | 	Fromm bad speling not escaped | ||||||
|  | 	 From with leading space not escaped | ||||||
|  |  | ||||||
|  | 	F | ||||||
|  | 	From | ||||||
|  | 	From | ||||||
|  | 	From | ||||||
|  | 	From | ||||||
|  | 	INPUT_END | ||||||
|  |  | ||||||
|  | 	C=$(git commit-tree HEAD^^{tree} -p HEAD <msg) && | ||||||
|  | 	git format-patch --pretty=mboxrd --stdout -1 $C~1..$C >patch && | ||||||
|  | 	git grep -h --no-index -A11 \ | ||||||
|  | 		"^>From could trip up a loose mbox parser" patch >actual && | ||||||
|  | 	test_cmp expect actual | ||||||
|  | ' | ||||||
|  |  | ||||||
| test_done | test_done | ||||||
|  |  | ||||||
		Loading…
	
		Reference in New Issue
	
	 Eric Wong
						Eric Wong