From 334192b411312c1ffab01af346d2d64d23597d84 Mon Sep 17 00:00:00 2001 From: Jonathan Tan Date: Mon, 19 Sep 2016 14:08:50 -0700 Subject: mailinfo: separate in-body header processing The check_header function contains logic specific to in-body headers, although it is invoked during both the processing of actual headers and in-body headers. Separate out the in-body header part into its own function. Signed-off-by: Jonathan Tan Signed-off-by: Junio C Hamano --- mailinfo.c | 33 +++++++++++++++++---------------- 1 file changed, 17 insertions(+), 16 deletions(-) diff --git a/mailinfo.c b/mailinfo.c index e19abe3cb9..0c4738a70e 100644 --- a/mailinfo.c +++ b/mailinfo.c @@ -495,21 +495,6 @@ static int check_header(struct mailinfo *mi, goto check_header_out; } - /* for inbody stuff */ - if (starts_with(line->buf, ">From") && isspace(line->buf[5])) { - ret = is_format_patch_separator(line->buf + 1, line->len - 1); - goto check_header_out; - } - if (starts_with(line->buf, "[PATCH]") && isspace(line->buf[7])) { - for (i = 0; header[i]; i++) { - if (!strcmp("Subject", header[i])) { - handle_header(&hdr_data[i], line); - ret = 1; - goto check_header_out; - } - } - } - check_header_out: strbuf_release(&sb); return ret; @@ -623,6 +608,22 @@ static int is_scissors_line(const struct strbuf *line) gap * 2 < perforation); } +static int check_inbody_header(struct mailinfo *mi, const struct strbuf *line) +{ + if (starts_with(line->buf, ">From") && isspace(line->buf[5])) + return is_format_patch_separator(line->buf + 1, line->len - 1); + if (starts_with(line->buf, "[PATCH]") && isspace(line->buf[7])) { + int i; + for (i = 0; header[i]; i++) + if (!strcmp("Subject", header[i])) { + handle_header(&mi->s_hdr_data[i], line); + return 1; + } + return 0; + } + return check_header(mi, line, mi->s_hdr_data, 0); +} + static int handle_commit_msg(struct mailinfo *mi, struct strbuf *line) { assert(!mi->filter_stage); @@ -633,7 +634,7 @@ static int handle_commit_msg(struct mailinfo *mi, struct strbuf *line) } if (mi->use_inbody_headers && mi->header_stage) { - mi->header_stage = check_header(mi, line, mi->s_hdr_data, 0); + mi->header_stage = check_inbody_header(mi, line); if (mi->header_stage) return 0; } else -- cgit v1.2.3 From 9c5681da88825c4320d3f7142ed320e6984c2377 Mon Sep 17 00:00:00 2001 From: Jonathan Tan Date: Mon, 19 Sep 2016 14:08:52 -0700 Subject: mailinfo: make is_scissors_line take plain char * The is_scissors_line takes a struct strbuf * when a char * would suffice. Make it take char *. Signed-off-by: Jonathan Tan Signed-off-by: Junio C Hamano --- mailinfo.c | 35 ++++++++++++++++++----------------- 1 file changed, 18 insertions(+), 17 deletions(-) diff --git a/mailinfo.c b/mailinfo.c index 0c4738a70e..69391aaca1 100644 --- a/mailinfo.c +++ b/mailinfo.c @@ -557,37 +557,35 @@ static inline int patchbreak(const struct strbuf *line) return 0; } -static int is_scissors_line(const struct strbuf *line) +static int is_scissors_line(const char *line) { - size_t i, len = line->len; + const char *c; int scissors = 0, gap = 0; - int first_nonblank = -1; - int last_nonblank = 0, visible, perforation = 0, in_perforation = 0; - const char *buf = line->buf; + const char *first_nonblank = NULL, *last_nonblank = NULL; + int visible, perforation = 0, in_perforation = 0; - for (i = 0; i < len; i++) { - if (isspace(buf[i])) { + for (c = line; *c; c++) { + if (isspace(*c)) { if (in_perforation) { perforation++; gap++; } continue; } - last_nonblank = i; - if (first_nonblank < 0) - first_nonblank = i; - if (buf[i] == '-') { + last_nonblank = c; + if (first_nonblank == NULL) + first_nonblank = c; + if (*c == '-') { in_perforation = 1; perforation++; continue; } - if (i + 1 < len && - (!memcmp(buf + i, ">8", 2) || !memcmp(buf + i, "8<", 2) || - !memcmp(buf + i, ">%", 2) || !memcmp(buf + i, "%<", 2))) { + if ((!memcmp(c, ">8", 2) || !memcmp(c, "8<", 2) || + !memcmp(c, ">%", 2) || !memcmp(c, "%<", 2))) { in_perforation = 1; perforation += 2; scissors += 2; - i++; + c++; continue; } in_perforation = 0; @@ -602,7 +600,10 @@ static int is_scissors_line(const struct strbuf *line) * than half of the perforation. */ - visible = last_nonblank - first_nonblank + 1; + if (first_nonblank && last_nonblank) + visible = last_nonblank - first_nonblank + 1; + else + visible = 0; return (scissors && 8 <= visible && visible < perforation * 3 && gap * 2 < perforation); @@ -647,7 +648,7 @@ static int handle_commit_msg(struct mailinfo *mi, struct strbuf *line) if (convert_to_utf8(mi, line, mi->charset.buf)) return 0; /* mi->input_error already set */ - if (mi->use_scissors && is_scissors_line(line)) { + if (mi->use_scissors && is_scissors_line(line->buf)) { int i; strbuf_setlen(&mi->log_message, 0); -- cgit v1.2.3 From 6b4b013f1884a3b5e67877d65a9f1da598ab4a6f Mon Sep 17 00:00:00 2001 From: Jonathan Tan Date: Tue, 20 Sep 2016 10:17:53 -0700 Subject: mailinfo: handle in-body header continuations Mailinfo currently handles multi-line headers, but it does not handle multi-line in-body headers. Teach it to handle such headers, for example, for this input: From: author Date: Fri, 9 Jun 2006 00:44:16 -0700 Subject: a very long broken line Subject: another very long broken line interpret the in-body subject to be "another very long broken line" instead of "another very long". An existing test (t/t5100/msg0015) has an indented line immediately after an in-body header - it has been modified to reflect the new functionality. Signed-off-by: Jonathan Tan Signed-off-by: Junio C Hamano --- mailinfo.c | 50 +++++++++++++++++++++++++++++++++++- mailinfo.h | 1 + t/t4150-am.sh | 23 +++++++++++++++++ t/t5100-mailinfo.sh | 2 +- t/t5100/info0018 | 5 ++++ t/t5100/info0018--no-inbody-headers | 5 ++++ t/t5100/msg0015 | 2 -- t/t5100/msg0018 | 2 ++ t/t5100/msg0018--no-inbody-headers | 8 ++++++ t/t5100/patch0018 | 6 +++++ t/t5100/patch0018--no-inbody-headers | 6 +++++ t/t5100/sample.mbox | 19 ++++++++++++++ 12 files changed, 125 insertions(+), 4 deletions(-) create mode 100644 t/t5100/info0018 create mode 100644 t/t5100/info0018--no-inbody-headers create mode 100644 t/t5100/msg0018 create mode 100644 t/t5100/msg0018--no-inbody-headers create mode 100644 t/t5100/patch0018 create mode 100644 t/t5100/patch0018--no-inbody-headers diff --git a/mailinfo.c b/mailinfo.c index 69391aaca1..2275b285f0 100644 --- a/mailinfo.c +++ b/mailinfo.c @@ -500,6 +500,21 @@ check_header_out: return ret; } +/* + * Returns 1 if the given line or any line beginning with the given line is an + * in-body header (that is, check_header will succeed when passed + * mi->s_hdr_data). + */ +static int is_inbody_header(const struct mailinfo *mi, + const struct strbuf *line) +{ + int i; + for (i = 0; header[i]; i++) + if (!mi->s_hdr_data[i] && cmp_header(line, header[i])) + return 1; + return 0; +} + static void decode_transfer_encoding(struct mailinfo *mi, struct strbuf *line) { struct strbuf *ret; @@ -609,8 +624,33 @@ static int is_scissors_line(const char *line) gap * 2 < perforation); } +static void flush_inbody_header_accum(struct mailinfo *mi) +{ + if (!mi->inbody_header_accum.len) + return; + assert(check_header(mi, &mi->inbody_header_accum, mi->s_hdr_data, 0)); + strbuf_reset(&mi->inbody_header_accum); +} + static int check_inbody_header(struct mailinfo *mi, const struct strbuf *line) { + if (mi->inbody_header_accum.len && + (line->buf[0] == ' ' || line->buf[0] == '\t')) { + if (mi->use_scissors && is_scissors_line(line->buf)) { + /* + * This is a scissors line; do not consider this line + * as a header continuation line. + */ + flush_inbody_header_accum(mi); + return 0; + } + strbuf_strip_suffix(&mi->inbody_header_accum, "\n"); + strbuf_addbuf(&mi->inbody_header_accum, line); + return 1; + } + + flush_inbody_header_accum(mi); + if (starts_with(line->buf, ">From") && isspace(line->buf[5])) return is_format_patch_separator(line->buf + 1, line->len - 1); if (starts_with(line->buf, "[PATCH]") && isspace(line->buf[7])) { @@ -622,7 +662,11 @@ static int check_inbody_header(struct mailinfo *mi, const struct strbuf *line) } return 0; } - return check_header(mi, line, mi->s_hdr_data, 0); + if (is_inbody_header(mi, line)) { + strbuf_addbuf(&mi->inbody_header_accum, line); + return 1; + } + return 0; } static int handle_commit_msg(struct mailinfo *mi, struct strbuf *line) @@ -888,6 +932,8 @@ static void handle_body(struct mailinfo *mi, struct strbuf *line) break; } while (!strbuf_getwholeline(line, mi->input, '\n')); + flush_inbody_header_accum(mi); + handle_body_out: strbuf_release(&prev); } @@ -1003,6 +1049,7 @@ void setup_mailinfo(struct mailinfo *mi) strbuf_init(&mi->email, 0); strbuf_init(&mi->charset, 0); strbuf_init(&mi->log_message, 0); + strbuf_init(&mi->inbody_header_accum, 0); mi->header_stage = 1; mi->use_inbody_headers = 1; mi->content_top = mi->content; @@ -1016,6 +1063,7 @@ void clear_mailinfo(struct mailinfo *mi) strbuf_release(&mi->name); strbuf_release(&mi->email); strbuf_release(&mi->charset); + strbuf_release(&mi->inbody_header_accum); free(mi->message_id); for (i = 0; mi->p_hdr_data[i]; i++) diff --git a/mailinfo.h b/mailinfo.h index 93776a7e05..04a25351d6 100644 --- a/mailinfo.h +++ b/mailinfo.h @@ -27,6 +27,7 @@ struct mailinfo { int patch_lines; int filter_stage; /* still reading log or are we copying patch? */ int header_stage; /* still checking in-body headers? */ + struct strbuf inbody_header_accum; struct strbuf **p_hdr_data; struct strbuf **s_hdr_data; diff --git a/t/t4150-am.sh b/t/t4150-am.sh index 9ce9424d15..89a5bacac5 100755 --- a/t/t4150-am.sh +++ b/t/t4150-am.sh @@ -977,4 +977,27 @@ test_expect_success 'am --patch-format=mboxrd handles mboxrd' ' test_cmp msg out ' +test_expect_success 'am works with multi-line in-body headers' ' + FORTY="String that has a length of more than forty characters" && + LONG="$FORTY $FORTY" && + rm -fr .git/rebase-apply && + git checkout -f first && + echo one >> file && + git commit -am "$LONG" --author="$LONG " && + git format-patch --stdout -1 >patch && + # bump from, date, and subject down to in-body header + perl -lpe " + if (/^From:/) { + print \"From: x \"; + print \"Date: Sat, 1 Jan 2000 00:00:00 +0000\"; + print \"Subject: x\n\"; + } + " patch >msg && + git checkout HEAD^ && + git am msg && + # Ensure that the author and full message are present + git cat-file commit HEAD | grep "^author.*long@example.com" && + git cat-file commit HEAD | grep "^$LONG" +' + test_done diff --git a/t/t5100-mailinfo.sh b/t/t5100-mailinfo.sh index 1a5a546230..e173c33f4b 100755 --- a/t/t5100-mailinfo.sh +++ b/t/t5100-mailinfo.sh @@ -11,7 +11,7 @@ test_expect_success 'split sample box' \ 'git mailsplit -o. "$TEST_DIRECTORY"/t5100/sample.mbox >last && last=$(cat last) && echo total is $last && - test $(cat last) = 17' + test $(cat last) = 18' check_mailinfo () { mail=$1 opt=$2 diff --git a/t/t5100/info0018 b/t/t5100/info0018 new file mode 100644 index 0000000000..d53e7491c7 --- /dev/null +++ b/t/t5100/info0018 @@ -0,0 +1,5 @@ +Author: Another Thor +Email: a.thor@example.com +Subject: This one contains a tab and a space +Date: Fri, 9 Jun 2006 00:44:16 -0700 + diff --git a/t/t5100/info0018--no-inbody-headers b/t/t5100/info0018--no-inbody-headers new file mode 100644 index 0000000000..30b17bd913 --- /dev/null +++ b/t/t5100/info0018--no-inbody-headers @@ -0,0 +1,5 @@ +Author: A U Thor +Email: a.u.thor@example.com +Subject: check multiline inbody headers +Date: Fri, 9 Jun 2006 00:44:16 -0700 + diff --git a/t/t5100/msg0015 b/t/t5100/msg0015 index 4abb3d5c6c..e69de29bb2 100644 --- a/t/t5100/msg0015 +++ b/t/t5100/msg0015 @@ -1,2 +0,0 @@ - - a list - - of stuff diff --git a/t/t5100/msg0018 b/t/t5100/msg0018 new file mode 100644 index 0000000000..56de83d7fc --- /dev/null +++ b/t/t5100/msg0018 @@ -0,0 +1,2 @@ +a commit message + diff --git a/t/t5100/msg0018--no-inbody-headers b/t/t5100/msg0018--no-inbody-headers new file mode 100644 index 0000000000..b1e05d3862 --- /dev/null +++ b/t/t5100/msg0018--no-inbody-headers @@ -0,0 +1,8 @@ +From: Another Thor + +Subject: This one contains + a tab + and a space + +a commit message + diff --git a/t/t5100/patch0018 b/t/t5100/patch0018 new file mode 100644 index 0000000000..789df6d030 --- /dev/null +++ b/t/t5100/patch0018 @@ -0,0 +1,6 @@ +diff --git a/foo b/foo +index e69de29..d95f3ad 100644 +--- a/foo ++++ b/foo +@@ -0,0 +1 @@ ++content diff --git a/t/t5100/patch0018--no-inbody-headers b/t/t5100/patch0018--no-inbody-headers new file mode 100644 index 0000000000..789df6d030 --- /dev/null +++ b/t/t5100/patch0018--no-inbody-headers @@ -0,0 +1,6 @@ +diff --git a/foo b/foo +index e69de29..d95f3ad 100644 +--- a/foo ++++ b/foo +@@ -0,0 +1 @@ ++content diff --git a/t/t5100/sample.mbox b/t/t5100/sample.mbox index 8b2ae064c3..6d4d0e4474 100644 --- a/t/t5100/sample.mbox +++ b/t/t5100/sample.mbox @@ -699,3 +699,22 @@ index e69de29..d95f3ad 100644 +++ b/foo @@ -0,0 +1 @@ +New content +From nobody Mon Sep 17 00:00:00 2001 +From: A U Thor +Subject: check multiline inbody headers +Date: Fri, 9 Jun 2006 00:44:16 -0700 + +From: Another Thor + +Subject: This one contains + a tab + and a space + +a commit message + +diff --git a/foo b/foo +index e69de29..d95f3ad 100644 +--- a/foo ++++ b/foo +@@ -0,0 +1 @@ ++content -- cgit v1.2.3