Skip to content

Commit 806aa30

Browse files
committed
apply: check and fix incomplete lines
The final line of a file that lacks the terminating newline at its end is called an incomplete line. In general they are frowned upon for many reasons (imagine concatenating two files with "cat A B" and what happens when A ends in an incomplete line, for example), and text-oriented tools often mishandle such a line. Implement checks in "git apply" for incomplete lines, which is off by default for backward compatibility's sake, so that "git apply --whitespace={fix,warn,error}" can notice, warn against, and fix them. As one of the new test shows, if you modify contents on an incomplete line in the original and leave the resulting line incomplete, it is still considered a whitespace error, the reasoning being that "you'd better fix it while at it if you are making a change on an incomplete line anyway", which may controversial. Signed-off-by: Junio C Hamano <gitster@pobox.com>
1 parent bdc2dbb commit 806aa30

File tree

3 files changed

+213
-1
lines changed

3 files changed

+213
-1
lines changed

apply.c

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1640,6 +1640,14 @@ static void record_ws_error(struct apply_state *state,
16401640
state->squelch_whitespace_errors < state->whitespace_error)
16411641
return;
16421642

1643+
/*
1644+
* line[len] for an incomplete line points at the "\n" at the end
1645+
* of patch input line, so "%.*s" would drop the last letter on line;
1646+
* compensate for it.
1647+
*/
1648+
if (result & WS_INCOMPLETE_LINE)
1649+
len++;
1650+
16431651
err = whitespace_error_string(result);
16441652
if (state->apply_verbosity > verbosity_silent)
16451653
fprintf(stderr, "%s:%d: %s.\n%.*s\n",
@@ -1794,7 +1802,10 @@ static int parse_fragment(struct apply_state *state,
17941802
}
17951803

17961804
/* eat the "\\ No newline..." as well, if exists */
1797-
len += skip_len;
1805+
if (skip_len) {
1806+
len += skip_len;
1807+
state->linenr++;
1808+
}
17981809
}
17991810
if (oldlines || newlines)
18001811
return -1;

t/t4124-apply-ws-rule.sh

Lines changed: 187 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -556,4 +556,191 @@ test_expect_success 'whitespace check skipped for excluded paths' '
556556
git apply --include=used --stat --whitespace=error <patch
557557
'
558558

559+
test_expect_success 'check incomplete lines (setup)' '
560+
rm -f .gitattributes &&
561+
git config core.whitespace incomplete-line
562+
'
563+
564+
test_expect_success 'incomplete context line (not an error)' '
565+
(test_write_lines 1 2 3 4 5 && printf 6) >sample-i &&
566+
(test_write_lines 1 2 3 0 5 && printf 6) >sample2-i &&
567+
cat sample-i >target &&
568+
git add target &&
569+
cat sample2-i >target &&
570+
git diff-files -p target >patch &&
571+
572+
cat sample-i >target &&
573+
git apply --whitespace=error <patch &&
574+
test_cmp sample2-i target &&
575+
576+
cat sample-i >target &&
577+
git apply --whitespace=error --check <patch 2>error &&
578+
test_cmp sample-i target &&
579+
test_must_be_empty error &&
580+
581+
cat sample2-i >target &&
582+
git apply --whitespace=error -R <patch &&
583+
test_cmp sample-i target &&
584+
585+
cat sample2-i >target &&
586+
git apply -R --whitespace=error --check <patch 2>error &&
587+
test_cmp sample2-i target &&
588+
test_must_be_empty error
589+
'
590+
591+
test_expect_success 'last line made incomplete (error)' '
592+
test_write_lines 1 2 3 4 5 6 >sample &&
593+
(test_write_lines 1 2 3 4 5 && printf 6) >sample-i &&
594+
cat sample >target &&
595+
git add target &&
596+
cat sample-i >target &&
597+
git diff-files -p target >patch &&
598+
599+
cat sample >target &&
600+
test_must_fail git apply --whitespace=error <patch 2>error &&
601+
test_grep "no newline" error &&
602+
603+
cat sample >target &&
604+
test_must_fail git apply --whitespace=error --check <patch 2>actual &&
605+
test_cmp sample target &&
606+
cat >expect <<-\EOF &&
607+
<stdin>:10: no newline at the end of file.
608+
6
609+
error: 1 line adds whitespace errors.
610+
EOF
611+
test_cmp expect actual &&
612+
613+
cat sample-i >target &&
614+
git apply --whitespace=error -R <patch &&
615+
test_cmp sample target &&
616+
617+
cat sample-i >target &&
618+
git apply --whitespace=error --check -R <patch 2>error &&
619+
test_cmp sample-i target &&
620+
test_must_be_empty error &&
621+
622+
cat sample >target &&
623+
git apply --whitespace=fix <patch &&
624+
test_cmp sample target
625+
'
626+
627+
test_expect_success 'incomplete line removed at the end (not an error)' '
628+
(test_write_lines 1 2 3 4 5 && printf 6) >sample-i &&
629+
test_write_lines 1 2 3 4 5 6 >sample &&
630+
cat sample-i >target &&
631+
git add target &&
632+
cat sample >target &&
633+
git diff-files -p target >patch &&
634+
635+
cat sample-i >target &&
636+
git apply --whitespace=error <patch &&
637+
test_cmp sample target &&
638+
639+
cat sample-i >target &&
640+
git apply --whitespace=error --check <patch 2>error &&
641+
test_cmp sample-i target &&
642+
test_must_be_empty error &&
643+
644+
cat sample >target &&
645+
test_must_fail git apply --whitespace=error -R <patch 2>error &&
646+
test_grep "no newline" error &&
647+
648+
cat sample >target &&
649+
test_must_fail git apply --whitespace=error --check -R <patch 2>actual &&
650+
test_cmp sample target &&
651+
cat >expect <<-\EOF &&
652+
<stdin>:9: no newline at the end of file.
653+
6
654+
error: 1 line adds whitespace errors.
655+
EOF
656+
test_cmp expect actual &&
657+
658+
cat sample >target &&
659+
git apply --whitespace=fix -R <patch &&
660+
test_cmp sample target
661+
'
662+
663+
test_expect_success 'incomplete line corrected at the end (not an error)' '
664+
(test_write_lines 1 2 3 4 5 && printf 6) >sample-i &&
665+
test_write_lines 1 2 3 4 5 7 >sample3 &&
666+
cat sample-i >target &&
667+
git add target &&
668+
cat sample3 >target &&
669+
git diff-files -p target >patch &&
670+
671+
cat sample-i >target &&
672+
git apply --whitespace=error <patch &&
673+
test_cmp sample3 target &&
674+
675+
cat sample-i >target &&
676+
git apply --whitespace=error --check <patch 2>error &&
677+
test_cmp sample-i target &&
678+
test_must_be_empty error &&
679+
680+
cat sample3 >target &&
681+
test_must_fail git apply --whitespace=error -R <patch 2>error &&
682+
test_grep "no newline" error &&
683+
684+
cat sample3 >target &&
685+
test_must_fail git apply --whitespace=error -R --check <patch 2>actual &&
686+
test_cmp sample3 target &&
687+
cat >expect <<-\EOF &&
688+
<stdin>:9: no newline at the end of file.
689+
6
690+
error: 1 line adds whitespace errors.
691+
EOF
692+
test_cmp expect actual &&
693+
694+
cat sample3 >target &&
695+
git apply --whitespace=fix -R <patch &&
696+
test_cmp sample target
697+
'
698+
699+
test_expect_success 'incomplete line modified at the end (error)' '
700+
(test_write_lines 1 2 3 4 5 && printf 6) >sample-i &&
701+
(test_write_lines 1 2 3 4 5 && printf 7) >sample3-i &&
702+
test_write_lines 1 2 3 4 5 6 >sample &&
703+
test_write_lines 1 2 3 4 5 7 >sample3 &&
704+
cat sample-i >target &&
705+
git add target &&
706+
cat sample3-i >target &&
707+
git diff-files -p target >patch &&
708+
709+
cat sample-i >target &&
710+
test_must_fail git apply --whitespace=error <patch 2>error &&
711+
test_grep "no newline" error &&
712+
713+
cat sample-i >target &&
714+
test_must_fail git apply --whitespace=error --check <patch 2>actual &&
715+
test_cmp sample-i target &&
716+
cat >expect <<-\EOF &&
717+
<stdin>:11: no newline at the end of file.
718+
7
719+
error: 1 line adds whitespace errors.
720+
EOF
721+
test_cmp expect actual &&
722+
723+
cat sample3-i >target &&
724+
test_must_fail git apply --whitespace=error -R <patch 2>error &&
725+
test_grep "no newline" error &&
726+
727+
cat sample3-i >target &&
728+
test_must_fail git apply --whitespace=error --check -R <patch 2>actual &&
729+
test_cmp sample3-i target &&
730+
cat >expect <<-\EOF &&
731+
<stdin>:9: no newline at the end of file.
732+
6
733+
error: 1 line adds whitespace errors.
734+
EOF
735+
test_cmp expect actual &&
736+
737+
cat sample-i >target &&
738+
git apply --whitespace=fix <patch &&
739+
test_cmp sample3 target &&
740+
741+
cat sample3-i >target &&
742+
git apply --whitespace=fix -R <patch &&
743+
test_cmp sample target
744+
'
745+
559746
test_done

ws.c

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -186,6 +186,9 @@ static unsigned ws_check_emit_1(const char *line, int len, unsigned ws_rule,
186186
if (trailing_whitespace == -1)
187187
trailing_whitespace = len;
188188

189+
if (!trailing_newline && (ws_rule & WS_INCOMPLETE_LINE))
190+
result |= WS_INCOMPLETE_LINE;
191+
189192
/* Check indentation */
190193
for (i = 0; i < trailing_whitespace; i++) {
191194
if (line[i] == ' ')
@@ -297,6 +300,17 @@ void ws_fix_copy(struct strbuf *dst, const char *src, int len, unsigned ws_rule,
297300
int last_space_in_indent = -1;
298301
int need_fix_leading_space = 0;
299302

303+
/*
304+
* Remembering that we need to add '\n' at the end
305+
* is sufficient to fix an incomplete line.
306+
*/
307+
if (ws_rule & WS_INCOMPLETE_LINE) {
308+
if (0 < len && src[len - 1] != '\n') {
309+
fixed = 1;
310+
add_nl_to_tail = 1;
311+
}
312+
}
313+
300314
/*
301315
* Strip trailing whitespace
302316
*/

0 commit comments

Comments
 (0)