trailer: support values folded to multiple lines
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@google.com> Signed-off-by: Junio C Hamano <gitster@pobox.com>
Jonathan Tan committed
Oct 21, 2016 at 10:55 UTC
60ef86a16268bfb1efb28bcb3f1b646bfad1931c
3 files changed
+211
-10
Documentation/git-interpret-trailers.txt
+4
-3
@@ -57,11 +57,12 @@ minus signs start the patch part of the message.
57
58
When reading trailers, there can be whitespaces after the
59
token, the separator and the value. There can also be whitespaces
60
-inside the token and the value.
60
+inside the token and the value. The value may be split over multiple lines with
61
+each subsequent line starting with whitespace, like the "folding" in RFC 822.
62
63
Note that 'trailers' do not follow and are not intended to follow many
63
-rules for RFC 822 headers. For example they do not follow the line
64
-folding rules, the encoding rules and probably many other rules.
64
+rules for RFC 822 headers. For example they do not follow
65
+the encoding rules and probably many other rules.
66
67
OPTIONS
68
-------
t/t7513-interpret-trailers.sh
+169
@@ -256,6 +256,175 @@ test_expect_success 'line with leading whitespace is not trailer' '
256
test_cmp expected actual
257
'
258
259
+test_expect_success 'multiline field treated as one trailer for 25% check' '
260
+ q_to_tab >patch <<-\EOF &&
261
+
262
+ Signed-off-by: a <a@example.com>
263
+ name: value on
264
+ Qmultiple lines
265
+ this is not a trailer
266
+ this is not a trailer
267
+ this is not a trailer
268
+ this is not a trailer
269
+ this is not a trailer
270
+ this is not a trailer
271
+ EOF
272
+ q_to_tab >expected <<-\EOF &&
273
+
274
+ Signed-off-by: a <a@example.com>
275
+ name: value on
276
+ Qmultiple lines
277
+ this is not a trailer
278
+ this is not a trailer
279
+ this is not a trailer
280
+ this is not a trailer
281
+ this is not a trailer
282
+ this is not a trailer
283
+ name: value
284
+ EOF
285
+ git interpret-trailers --trailer "name: value" patch >actual &&
286
+ test_cmp expected actual
287
+'
288
+
289
+test_expect_success 'multiline field treated as atomic for placement' '
290
+ q_to_tab >patch <<-\EOF &&
291
+
292
+ another: trailer
293
+ name: value on
294
+ Qmultiple lines
295
+ another: trailer
296
+ EOF
297
+ q_to_tab >expected <<-\EOF &&
298
+
299
+ another: trailer
300
+ name: value on
301
+ Qmultiple lines
302
+ name: value
303
+ another: trailer
304
+ EOF
305
+ test_config trailer.name.where after &&
306
+ git interpret-trailers --trailer "name: value" patch >actual &&
307
+ test_cmp expected actual
308
+'
309
+
310
+test_expect_success 'multiline field treated as atomic for replacement' '
311
+ q_to_tab >patch <<-\EOF &&
312
+
313
+ another: trailer
314
+ name: value on
315
+ Qmultiple lines
316
+ another: trailer
317
+ EOF
318
+ q_to_tab >expected <<-\EOF &&
319
+
320
+ another: trailer
321
+ another: trailer
322
+ name: value
323
+ EOF
324
+ test_config trailer.name.ifexists replace &&
325
+ git interpret-trailers --trailer "name: value" patch >actual &&
326
+ test_cmp expected actual
327
+'
328
+
329
+test_expect_success 'multiline field treated as atomic for difference check' '
330
+ q_to_tab >patch <<-\EOF &&
331
+
332
+ another: trailer
333
+ name: first line
334
+ Qsecond line
335
+ another: trailer
336
+ EOF
337
+ test_config trailer.name.ifexists addIfDifferent &&
338
+
339
+ q_to_tab >trailer <<-\EOF &&
340
+ name: first line
341
+ Qsecond line
342
+ EOF
343
+ q_to_tab >expected <<-\EOF &&
344
+
345
+ another: trailer
346
+ name: first line
347
+ Qsecond line
348
+ another: trailer
349
+ EOF
350
+ git interpret-trailers --trailer "$(cat trailer)" patch >actual &&
351
+ test_cmp expected actual &&
352
+
353
+ q_to_tab >trailer <<-\EOF &&
354
+ name: first line
355
+ QQQQQsecond line
356
+ EOF
357
+ q_to_tab >expected <<-\EOF &&
358
+
359
+ another: trailer
360
+ name: first line
361
+ Qsecond line
362
+ another: trailer
363
+ name: first line
364
+ QQQQQsecond line
365
+ EOF
366
+ git interpret-trailers --trailer "$(cat trailer)" patch >actual &&
367
+ test_cmp expected actual &&
368
+
369
+ q_to_tab >trailer <<-\EOF &&
370
+ name: first line *DIFFERENT*
371
+ Qsecond line
372
+ EOF
373
+ q_to_tab >expected <<-\EOF &&
374
+
375
+ another: trailer
376
+ name: first line
377
+ Qsecond line
378
+ another: trailer
379
+ name: first line *DIFFERENT*
380
+ Qsecond line
381
+ EOF
382
+ git interpret-trailers --trailer "$(cat trailer)" patch >actual &&
383
+ test_cmp expected actual
384
+'
385
+
386
+test_expect_success 'multiline field treated as atomic for neighbor check' '
387
+ q_to_tab >patch <<-\EOF &&
388
+
389
+ another: trailer
390
+ name: first line
391
+ Qsecond line
392
+ another: trailer
393
+ EOF
394
+ test_config trailer.name.where after &&
395
+ test_config trailer.name.ifexists addIfDifferentNeighbor &&
396
+
397
+ q_to_tab >trailer <<-\EOF &&
398
+ name: first line
399
+ Qsecond line
400
+ EOF
401
+ q_to_tab >expected <<-\EOF &&
402
+
403
+ another: trailer
404
+ name: first line
405
+ Qsecond line
406
+ another: trailer
407
+ EOF
408
+ git interpret-trailers --trailer "$(cat trailer)" patch >actual &&
409
+ test_cmp expected actual &&
410
+
411
+ q_to_tab >trailer <<-\EOF &&
412
+ name: first line
413
+ QQQQQsecond line
414
+ EOF
415
+ q_to_tab >expected <<-\EOF &&
416
+
417
+ another: trailer
418
+ name: first line
419
+ Qsecond line
420
+ name: first line
421
+ QQQQQsecond line
422
+ another: trailer
423
+ EOF
424
+ git interpret-trailers --trailer "$(cat trailer)" patch >actual &&
425
+ test_cmp expected actual
426
+'
427
+
428
test_expect_success 'with config setup' '
429
git config trailer.ack.key "Acked-by: " &&
430
cat >expected <<-\EOF &&
trailer.c
+38
-7
@@ -619,12 +619,14 @@ static void parse_trailer(struct strbuf *tok, struct strbuf *val,
619
}
620
}
621
622
-static void add_trailer_item(struct list_head *head, char *tok, char *val)
622
+static struct trailer_item *add_trailer_item(struct list_head *head, char *tok,
623
+ char *val)
624
{
625
struct trailer_item *new = xcalloc(sizeof(*new), 1);
626
new->token = tok;
627
new->value = val;
628
list_add_tail(&new->list, head);
629
+ return new;
630
}
631
632
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)
734
{
735
int start, end_of_title, only_spaces = 1;
736
int recognized_prefix = 0, trailer_lines = 0, non_trailer_lines = 0;
737
+ /*
738
+ * Number of possible continuation lines encountered. This will be
739
+ * reset to 0 if we encounter a trailer (since those lines are to be
740
+ * considered continuations of that trailer), and added to
741
+ * non_trailer_lines if we encounter a non-trailer (since those lines
742
+ * are to be considered non-trailers).
743
+ */
744
+ int possible_continuation_lines = 0;
745
746
/* The first paragraph is the title and cannot be trailers */
747
for (start = 0; start < count; start++) {
@@ -752,11 +762,15 @@ static int find_trailer_start(struct strbuf **lines, int count)
762
const char **p;
763
int separator_pos;
764
755
- if (lines[start]->buf[0] == comment_line_char)
765
+ if (lines[start]->buf[0] == comment_line_char) {
766
+ non_trailer_lines += possible_continuation_lines;
767
+ possible_continuation_lines = 0;
768
continue;
769
+ }
770
if (contains_only_spaces(lines[start]->buf)) {
771
if (only_spaces)
772
continue;
773
+ non_trailer_lines += possible_continuation_lines;
774
if (recognized_prefix &&
775
trailer_lines * 3 >= non_trailer_lines)
776
return start + 1;
@@ -769,16 +783,18 @@ static int find_trailer_start(struct strbuf **lines, int count)
783
for (p = git_generated_prefixes; *p; p++) {
784
if (starts_with(lines[start]->buf, *p)) {
785
trailer_lines++;
786
+ possible_continuation_lines = 0;
787
recognized_prefix = 1;
788
goto continue_outer_loop;
789
}
790
}
791
777
- separator_pos = find_separator(lines[start]->buf);
792
+ separator_pos = find_separator(lines[start]->buf, separators);
793
if (separator_pos >= 1 && !isspace(lines[start]->buf[0])) {
794
struct list_head *pos;
795
796
trailer_lines++;
797
+ possible_continuation_lines = 0;
798
if (recognized_prefix)
799
continue;
800
list_for_each(pos, &conf_head) {
@@ -790,8 +806,13 @@ static int find_trailer_start(struct strbuf **lines, int count)
806
break;
807
}
808
}
793
- } else
809
+ } else if (isspace(lines[start]->buf[0]))
810
+ possible_continuation_lines++;
811
+ else {
812
non_trailer_lines++;
813
+ non_trailer_lines += possible_continuation_lines;
814
+ possible_continuation_lines = 0;
815
+ }
816
continue_outer_loop:
817
;
818
}
@@ -840,6 +861,7 @@ static int process_input_file(FILE *outfile,
861
int patch_start, trailer_start, trailer_end, i;
862
struct strbuf tok = STRBUF_INIT;
863
struct strbuf val = STRBUF_INIT;
864
+ struct trailer_item *last = NULL;
865
866
/* Get the line count */
867
while (lines[count])
@@ -860,19 +882,28 @@ static int process_input_file(FILE *outfile,
882
int separator_pos;
883
if (lines[i]->buf[0] == comment_line_char)
884
continue;
885
+ if (last && isspace(lines[i]->buf[0])) {
886
+ struct strbuf sb = STRBUF_INIT;
887
+ strbuf_addf(&sb, "%s\n%s", last->value, lines[i]->buf);
888
+ strbuf_strip_suffix(&sb, "\n");
889
+ free(last->value);
890
+ last->value = strbuf_detach(&sb, NULL);
891
+ continue;
892
+ }
893
separator_pos = find_separator(lines[i]->buf, separators);
894
if (separator_pos >= 1) {
895
parse_trailer(&tok, &val, NULL, lines[i]->buf,
896
separator_pos);
867
- add_trailer_item(head,
868
- strbuf_detach(&tok, NULL),
869
- strbuf_detach(&val, NULL));
897
+ last = add_trailer_item(head,
898
+ strbuf_detach(&tok, NULL),
899
+ strbuf_detach(&val, NULL));
900
} else {
901
strbuf_addbuf(&val, lines[i]);
902
strbuf_strip_suffix(&val, "\n");
903
add_trailer_item(head,
904
NULL,
905
strbuf_detach(&val, NULL));
906
+ last = NULL;
907
}
908
}
909