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.
---
 Documentation/git-interpret-trailers.txt |   7 +-
 t/t7513-interpret-trailers.sh            | 139 +++++++++++++++++++++++++++++++
 trailer.c                                |  40 ++++++---
 3 files changed, 170 insertions(+), 16 deletions(-)

diff --git a/Documentation/git-interpret-trailers.txt 
b/Documentation/git-interpret-trailers.txt
index c480da6..cfec636 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 before and 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 7f5cd2a..195cdd3 100755
--- a/t/t7513-interpret-trailers.sh
+++ b/t/t7513-interpret-trailers.sh
@@ -157,6 +157,145 @@ test_expect_success 'with non-trailer lines only' '
        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
+               Qsecond line *DIFFERENT*
+       EOF
+       q_to_tab >expected <<-\EOF &&
+
+               another: trailer
+               name: first line
+               Qsecond line
+               another: trailer
+               name: first line
+               Qsecond line *DIFFERENT*
+       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
+               Qsecond line *DIFFERENT*
+       EOF
+       q_to_tab >expected <<-\EOF &&
+
+               another: trailer
+               name: first line
+               Qsecond line
+               name: first line
+               Qsecond line *DIFFERENT*
+               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 97e96a9..907baa0 100644
--- a/trailer.c
+++ b/trailer.c
@@ -31,7 +31,7 @@ struct trailer_item {
         * (excluding the terminating newline) and token is NULL.
         */
        char *token;
-       char *value;
+       struct strbuf value;
 };
 
 struct arg_item {
@@ -81,7 +81,7 @@ static int same_token(const struct trailer_item *a, const 
struct arg_item *b)
 
 static int same_value(const struct trailer_item *a, const struct arg_item *b)
 {
-       return !strcasecmp(a->value, b->value);
+       return !strcasecmp(a->value.buf, b->value);
 }
 
 static int same_trailer(const struct trailer_item *a, const struct arg_item *b)
@@ -107,7 +107,7 @@ static inline void strbuf_replace(struct strbuf *sb, const 
char *a, const char *
 static void free_trailer_item(struct trailer_item *item)
 {
        free(item->token);
-       free(item->value);
+       strbuf_release(&item->value);
        free(item);
 }
 
@@ -152,8 +152,8 @@ static void print_all(FILE *outfile, struct trailer_item 
*first, int trim_empty)
 {
        struct trailer_item *item;
        for (item = first; item; item = item->next) {
-               if (!trim_empty || strlen(item->value) > 0)
-                       print_tok_val(outfile, item->token, item->value);
+               if (!trim_empty || item->value.len > 0)
+                       print_tok_val(outfile, item->token, item->value.buf);
        }
 }
 
@@ -195,8 +195,8 @@ static void apply_item_command(struct trailer_item *in_tok, 
struct arg_item *arg
                if (arg_tok->value && arg_tok->value[0]) {
                        arg = arg_tok->value;
                } else {
-                       if (in_tok && in_tok->value)
-                               arg = xstrdup(in_tok->value);
+                       if (in_tok)
+                               arg = xstrdup(in_tok->value.buf);
                        else
                                arg = xstrdup("");
                }
@@ -209,7 +209,9 @@ static struct trailer_item *trailer_from_arg(struct 
arg_item *arg_tok)
 {
        struct trailer_item *new = xcalloc(sizeof(*new), 1);
        new->token = arg_tok->token;
-       new->value = arg_tok->value;
+       strbuf_init(&new->value, 0);
+       strbuf_attach(&new->value, arg_tok->value, strlen(arg_tok->value),
+                     strlen(arg_tok->value));
        arg_tok->token = arg_tok->value = NULL;
        free_arg_item(arg_tok);
        return new;
@@ -587,14 +589,17 @@ static int parse_trailer(struct strbuf *tok, struct 
strbuf *val,
        return 0;
 }
 
-static void add_trailer_item(struct trailer_item ***tail, char *tok, char *val)
+static struct trailer_item *add_trailer_item(struct trailer_item ***tail, char 
*tok,
+                                            char *val)
 {
        struct trailer_item *new = xcalloc(sizeof(*new), 1);
        new->token = tok;
-       new->value = val;
+       strbuf_init(&new->value, 0);
+       strbuf_attach(&new->value, val, strlen(val), strlen(val));
 
        **tail = new;
        *tail = &new->next;
+       return new;
 }
 
 static void add_arg_item(struct arg_item ***tail, char *tok, char *val,
@@ -750,6 +755,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])
@@ -767,16 +773,24 @@ static int process_input_file(FILE *outfile,
 
        /* Parse trailer lines */
        for (i = trailer_start; i < trailer_end; i++) {
+               if (last && isspace(lines[i]->buf[0])) {
+                       /* continuation line of the last trailer item */
+                       strbuf_addch(&last->value, '\n');
+                       strbuf_addbuf(&last->value, lines[i]);
+                       strbuf_strip_suffix(&last->value, "\n");
+                       continue;
+               }
                if (!parse_trailer(&tok, &val, NULL, lines[i]->buf, 0))
-                       add_trailer_item(in_tok_tail,
-                                        strbuf_detach(&tok, NULL),
-                                        strbuf_detach(&val, NULL));
+                       last = add_trailer_item(in_tok_tail,
+                                               strbuf_detach(&tok, NULL),
+                                               strbuf_detach(&val, NULL));
                else {
                        strbuf_addbuf(&val, lines[i]);
                        strbuf_strip_suffix(&val, "\n");
                        add_trailer_item(in_tok_tail,
                                         NULL,
                                         strbuf_detach(&val, NULL));
+                       last = NULL;
                }
        }
 
-- 
2.8.0.rc3.226.g39d4020

Reply via email to