Currently, interpret-trailers requires that a trailer be only on 1 line. For example: a: first line second line would be interpreted as one trailer line followed by one non-trailer line. Make interpret-trailers support RFC 822-style folding, treating those lines as one logical trailer. Signed-off-by: Jonathan Tan <jonathantanmy@xxxxxxxxxx> --- Documentation/git-interpret-trailers.txt | 7 +- t/t7513-interpret-trailers.sh | 169 +++++++++++++++++++++++++++++++ trailer.c | 45 ++++++-- 3 files changed, 211 insertions(+), 10 deletions(-) diff --git a/Documentation/git-interpret-trailers.txt b/Documentation/git-interpret-trailers.txt index 4966b5b..e99bda6 100644 --- a/Documentation/git-interpret-trailers.txt +++ b/Documentation/git-interpret-trailers.txt @@ -57,11 +57,12 @@ minus signs start the patch part of the message. When reading trailers, there can be whitespaces after the token, the separator and the value. There can also be whitespaces -inside the token and the value. +inside the token and the value. The value may be split over multiple lines with +each subsequent line starting with whitespace, like the "folding" in RFC 822. Note that 'trailers' do not follow and are not intended to follow many -rules for RFC 822 headers. For example they do not follow the line -folding rules, the encoding rules and probably many other rules. +rules for RFC 822 headers. For example they do not follow +the encoding rules and probably many other rules. OPTIONS ------- diff --git a/t/t7513-interpret-trailers.sh b/t/t7513-interpret-trailers.sh index 3d94b3a..4dd1d7c 100755 --- a/t/t7513-interpret-trailers.sh +++ b/t/t7513-interpret-trailers.sh @@ -256,6 +256,175 @@ test_expect_success 'line with leading whitespace is not trailer' ' test_cmp expected actual ' +test_expect_success 'multiline field treated as one trailer for 25% check' ' + q_to_tab >patch <<-\EOF && + + Signed-off-by: a <a@xxxxxxxxxxx> + name: value on + Qmultiple lines + this is not a trailer + this is not a trailer + this is not a trailer + this is not a trailer + this is not a trailer + this is not a trailer + EOF + q_to_tab >expected <<-\EOF && + + Signed-off-by: a <a@xxxxxxxxxxx> + name: value on + Qmultiple lines + this is not a trailer + this is not a trailer + this is not a trailer + this is not a trailer + this is not a trailer + this is not a trailer + name: value + EOF + git interpret-trailers --trailer "name: value" patch >actual && + test_cmp expected actual +' + +test_expect_success 'multiline field treated as atomic for placement' ' + q_to_tab >patch <<-\EOF && + + another: trailer + name: value on + Qmultiple lines + another: trailer + EOF + q_to_tab >expected <<-\EOF && + + another: trailer + name: value on + Qmultiple lines + name: value + another: trailer + EOF + test_config trailer.name.where after && + git interpret-trailers --trailer "name: value" patch >actual && + test_cmp expected actual +' + +test_expect_success 'multiline field treated as atomic for replacement' ' + q_to_tab >patch <<-\EOF && + + another: trailer + name: value on + Qmultiple lines + another: trailer + EOF + q_to_tab >expected <<-\EOF && + + another: trailer + another: trailer + name: value + EOF + test_config trailer.name.ifexists replace && + git interpret-trailers --trailer "name: value" patch >actual && + test_cmp expected actual +' + +test_expect_success 'multiline field treated as atomic for difference check' ' + q_to_tab >patch <<-\EOF && + + another: trailer + name: first line + Qsecond line + another: trailer + EOF + test_config trailer.name.ifexists addIfDifferent && + + q_to_tab >trailer <<-\EOF && + name: first line + Qsecond line + EOF + q_to_tab >expected <<-\EOF && + + another: trailer + name: first line + Qsecond line + another: trailer + EOF + git interpret-trailers --trailer "$(cat trailer)" patch >actual && + test_cmp expected actual && + + q_to_tab >trailer <<-\EOF && + name: first line + QQQQQsecond line + EOF + q_to_tab >expected <<-\EOF && + + another: trailer + name: first line + Qsecond line + another: trailer + name: first line + QQQQQsecond line + EOF + git interpret-trailers --trailer "$(cat trailer)" patch >actual && + test_cmp expected actual && + + q_to_tab >trailer <<-\EOF && + name: first line *DIFFERENT* + Qsecond line + EOF + q_to_tab >expected <<-\EOF && + + another: trailer + name: first line + Qsecond line + another: trailer + name: first line *DIFFERENT* + Qsecond line + EOF + git interpret-trailers --trailer "$(cat trailer)" patch >actual && + test_cmp expected actual +' + +test_expect_success 'multiline field treated as atomic for neighbor check' ' + q_to_tab >patch <<-\EOF && + + another: trailer + name: first line + Qsecond line + another: trailer + EOF + test_config trailer.name.where after && + test_config trailer.name.ifexists addIfDifferentNeighbor && + + q_to_tab >trailer <<-\EOF && + name: first line + Qsecond line + EOF + q_to_tab >expected <<-\EOF && + + another: trailer + name: first line + Qsecond line + another: trailer + EOF + git interpret-trailers --trailer "$(cat trailer)" patch >actual && + test_cmp expected actual && + + q_to_tab >trailer <<-\EOF && + name: first line + QQQQQsecond line + EOF + q_to_tab >expected <<-\EOF && + + another: trailer + name: first line + Qsecond line + name: first line + QQQQQsecond line + another: trailer + EOF + git interpret-trailers --trailer "$(cat trailer)" patch >actual && + test_cmp expected actual +' + test_expect_success 'with config setup' ' git config trailer.ack.key "Acked-by: " && cat >expected <<-\EOF && diff --git a/trailer.c b/trailer.c index d978437..65866d0 100644 --- a/trailer.c +++ b/trailer.c @@ -619,12 +619,14 @@ static void parse_trailer(struct strbuf *tok, struct strbuf *val, } } -static void add_trailer_item(struct list_head *head, char *tok, char *val) +static struct trailer_item *add_trailer_item(struct list_head *head, char *tok, + char *val) { struct trailer_item *new = xcalloc(sizeof(*new), 1); new->token = tok; new->value = val; list_add_tail(&new->list, head); + return new; } static void add_arg_item(struct list_head *arg_head, char *tok, char *val, @@ -732,6 +734,14 @@ static int find_trailer_start(struct strbuf **lines, int count) { int start, end_of_title, only_spaces = 1; int recognized_prefix = 0, trailer_lines = 0, non_trailer_lines = 0; + /* + * Number of possible continuation lines encountered. This will be + * reset to 0 if we encounter a trailer (since those lines are to be + * considered continuations of that trailer), and added to + * non_trailer_lines if we encounter a non-trailer (since those lines + * are to be considered non-trailers). + */ + int possible_continuation_lines = 0; /* The first paragraph is the title and cannot be trailers */ for (start = 0; start < count; start++) { @@ -752,11 +762,15 @@ static int find_trailer_start(struct strbuf **lines, int count) const char **p; int separator_pos; - if (lines[start]->buf[0] == comment_line_char) + if (lines[start]->buf[0] == comment_line_char) { + non_trailer_lines += possible_continuation_lines; + possible_continuation_lines = 0; continue; + } if (contains_only_spaces(lines[start]->buf)) { if (only_spaces) continue; + non_trailer_lines += possible_continuation_lines; if (recognized_prefix && trailer_lines * 3 >= non_trailer_lines) return start + 1; @@ -769,16 +783,18 @@ static int find_trailer_start(struct strbuf **lines, int count) for (p = git_generated_prefixes; *p; p++) { if (starts_with(lines[start]->buf, *p)) { trailer_lines++; + possible_continuation_lines = 0; recognized_prefix = 1; goto continue_outer_loop; } } - separator_pos = find_separator(lines[start]->buf); + separator_pos = find_separator(lines[start]->buf, separators); if (separator_pos >= 1 && !isspace(lines[start]->buf[0])) { struct list_head *pos; trailer_lines++; + possible_continuation_lines = 0; if (recognized_prefix) continue; list_for_each(pos, &conf_head) { @@ -790,8 +806,13 @@ static int find_trailer_start(struct strbuf **lines, int count) break; } } - } else + } else if (isspace(lines[start]->buf[0])) + possible_continuation_lines++; + else { non_trailer_lines++; + non_trailer_lines += possible_continuation_lines; + possible_continuation_lines = 0; + } continue_outer_loop: ; } @@ -840,6 +861,7 @@ static int process_input_file(FILE *outfile, int patch_start, trailer_start, trailer_end, i; struct strbuf tok = STRBUF_INIT; struct strbuf val = STRBUF_INIT; + struct trailer_item *last = NULL; /* Get the line count */ while (lines[count]) @@ -860,19 +882,28 @@ static int process_input_file(FILE *outfile, int separator_pos; if (lines[i]->buf[0] == comment_line_char) continue; + if (last && isspace(lines[i]->buf[0])) { + struct strbuf sb = STRBUF_INIT; + strbuf_addf(&sb, "%s\n%s", last->value, lines[i]->buf); + strbuf_strip_suffix(&sb, "\n"); + free(last->value); + last->value = strbuf_detach(&sb, NULL); + continue; + } separator_pos = find_separator(lines[i]->buf, separators); if (separator_pos >= 1) { parse_trailer(&tok, &val, NULL, lines[i]->buf, separator_pos); - add_trailer_item(head, - strbuf_detach(&tok, NULL), - strbuf_detach(&val, NULL)); + last = add_trailer_item(head, + strbuf_detach(&tok, NULL), + strbuf_detach(&val, NULL)); } else { strbuf_addbuf(&val, lines[i]); strbuf_strip_suffix(&val, "\n"); add_trailer_item(head, NULL, strbuf_detach(&val, NULL)); + last = NULL; } } -- 2.8.0.rc3.226.g39d4020