Files
git-mirror/t/t4124-apply-ws-rule.sh
Junio C Hamano 9fb15a8e14 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>
2025-11-12 14:04:04 -08:00

747 lines
18 KiB
Bash
Executable File

#!/bin/sh
test_description='core.whitespace rules and git apply'
. ./test-lib.sh
prepare_test_file () {
# A line that has character X is touched iff RULE is in effect:
# X RULE
# ! trailing-space
# @ space-before-tab
# # indent-with-non-tab (default tab width 8)
# = indent-with-non-tab,tabwidth=16
# % tab-in-indent
sed -e "s/_/ /g" -e "s/>/ /" <<-\EOF
An_SP in an ordinary line>and a HT.
>A HT (%).
_>A SP and a HT (@%).
_>_A SP, a HT and a SP (@%).
_______Seven SP.
________Eight SP (#).
_______>Seven SP and a HT (@%).
________>Eight SP and a HT (@#%).
_______>_Seven SP, a HT and a SP (@%).
________>_Eight SP, a HT and a SP (@#%).
_______________Fifteen SP (#).
_______________>Fifteen SP and a HT (@#%).
________________Sixteen SP (#=).
________________>Sixteen SP and a HT (@#%=).
_____a__Five SP, a non WS, two SP.
A line with a (!) trailing SP_
A line with a (!) trailing HT>
EOF
}
apply_patch () {
cmd_prefix= &&
if test "x$1" = 'x!'
then
cmd_prefix=test_must_fail &&
shift
fi &&
>target &&
sed -e "s|\([ab]\)/file|\1/target|" <patch |
$cmd_prefix git apply "$@"
}
test_fix () {
# fix should not barf
apply_patch --whitespace=fix || return 1
# find touched lines
$DIFF file target | sed -n -e "s/^> //p" >fixed
# busybox's diff(1) doesn't output normal format
if ! test -s fixed
then
$DIFF -u file target |
grep -v '^+++ target' |
sed -ne "/^+/s/+//p" >fixed
fi
# the changed lines are all expected to change
fixed_cnt=$(wc -l <fixed)
case "$1" in
'') expect_cnt=$fixed_cnt ;;
?*) expect_cnt=$(grep "[$1]" <fixed | wc -l) ;;
esac
test $fixed_cnt -eq $expect_cnt || return 1
# and we are not missing anything
case "$1" in
'') expect_cnt=0 ;;
?*) expect_cnt=$(grep "[$1]" <file | wc -l) ;;
esac
test $fixed_cnt -eq $expect_cnt || return 1
# Get the patch actually applied
git diff-files -p target >fixed-patch
test -s fixed-patch && return 0
# Make sure it is complaint-free
>target
git apply --whitespace=error-all <fixed-patch
}
test_expect_success setup '
>file &&
git add file &&
prepare_test_file >file &&
git diff-files -p >patch &&
>target &&
git add target
'
test_expect_success 'whitespace=nowarn, default rule' '
apply_patch --whitespace=nowarn &&
test_cmp file target
'
test_expect_success 'whitespace=warn, default rule' '
apply_patch --whitespace=warn &&
test_cmp file target
'
test_expect_success 'whitespace=error-all, default rule' '
apply_patch ! --whitespace=error-all &&
test_must_be_empty target
'
test_expect_success 'whitespace=error-all, no rule' '
git config core.whitespace -trailing,-space-before,-indent &&
apply_patch --whitespace=error-all &&
test_cmp file target
'
test_expect_success 'whitespace=error-all, no rule (attribute)' '
git config --unset core.whitespace &&
echo "target -whitespace" >.gitattributes &&
apply_patch --whitespace=error-all &&
test_cmp file target
'
test_expect_success 'spaces inserted by tab-in-indent' '
git config core.whitespace -trailing,-space,-indent,tab &&
rm -f .gitattributes &&
test_fix % &&
sed -e "s/_/ /g" -e "s/>/ /" <<-\EOF >expect &&
An_SP in an ordinary line>and a HT.
________A HT (%).
________A SP and a HT (@%).
_________A SP, a HT and a SP (@%).
_______Seven SP.
________Eight SP (#).
________Seven SP and a HT (@%).
________________Eight SP and a HT (@#%).
_________Seven SP, a HT and a SP (@%).
_________________Eight SP, a HT and a SP (@#%).
_______________Fifteen SP (#).
________________Fifteen SP and a HT (@#%).
________________Sixteen SP (#=).
________________________Sixteen SP and a HT (@#%=).
_____a__Five SP, a non WS, two SP.
A line with a (!) trailing SP_
A line with a (!) trailing HT>
EOF
test_cmp expect target
'
for t in - ''
do
case "$t" in '') tt='!' ;; *) tt= ;; esac
for s in - ''
do
case "$s" in '') ts='@' ;; *) ts= ;; esac
for i in - ''
do
case "$i" in '') ti='#' ti16='=';; *) ti= ti16= ;; esac
for h in - ''
do
[ -z "$h$i" ] && continue
case "$h" in '') th='%' ;; *) th= ;; esac
rule=${t}trailing,${s}space,${i}indent,${h}tab
rm -f .gitattributes
test_expect_success "rule=$rule" '
git config core.whitespace "$rule" &&
test_fix "$tt$ts$ti$th"
'
test_expect_success "rule=$rule,tabwidth=16" '
git config core.whitespace "$rule,tabwidth=16" &&
test_fix "$tt$ts$ti16$th"
'
test_expect_success "rule=$rule (attributes)" '
git config --unset core.whitespace &&
echo "target whitespace=$rule" >.gitattributes &&
test_fix "$tt$ts$ti$th"
'
test_expect_success "rule=$rule,tabwidth=16 (attributes)" '
echo "target whitespace=$rule,tabwidth=16" >.gitattributes &&
test_fix "$tt$ts$ti16$th"
'
done
done
done
done
create_patch () {
sed -e "s/_/ /" <<-\EOF
diff --git a/target b/target
index e69de29..8bd6648 100644
--- a/target
+++ b/target
@@ -0,0 +1,3 @@
+An empty line follows
+
+A line with trailing whitespace and no newline_
\ No newline at end of file
EOF
}
test_expect_success 'trailing whitespace & no newline at the end of file' '
>target &&
create_patch >patch-file &&
git apply --whitespace=fix patch-file &&
grep "newline$" target &&
grep "^$" target
'
test_expect_success 'blank at EOF with --whitespace=fix (1)' '
test_might_fail git config --unset core.whitespace &&
rm -f .gitattributes &&
test_write_lines a b c >one &&
git add one &&
test_write_lines a b c >expect &&
{ cat expect && echo; } >one &&
git diff -- one >patch &&
git checkout one &&
git apply --whitespace=fix patch &&
test_cmp expect one
'
test_expect_success 'blank at EOF with --whitespace=fix (2)' '
test_write_lines a b c >one &&
git add one &&
test_write_lines a b >expect &&
{ cat expect && test_write_lines "" ""; } >one &&
git diff -- one >patch &&
git checkout one &&
git apply --whitespace=fix patch &&
test_cmp expect one
'
test_expect_success 'blank at EOF with --whitespace=fix (3)' '
test_write_lines a b "" >one &&
git add one &&
test_write_lines a c "" >expect &&
{ cat expect && test_write_lines "" ""; } >one &&
git diff -- one >patch &&
git checkout one &&
git apply --whitespace=fix patch &&
test_cmp expect one
'
test_expect_success 'blank at end of hunk, not at EOF with --whitespace=fix' '
test_write_lines a b "" "" "" "" "" d >one &&
git add one &&
test_write_lines a b "" "" "" "" "" "" d >expect &&
cp expect one &&
git diff -- one >patch &&
git checkout one &&
git apply --whitespace=fix patch &&
test_cmp expect one
'
test_expect_success 'blank at EOF with --whitespace=warn' '
test_write_lines a b c >one &&
git add one &&
echo >>one &&
cat one >expect &&
git diff -- one >patch &&
git checkout one &&
git apply --whitespace=warn patch 2>error &&
test_cmp expect one &&
grep "new blank line at EOF" error
'
test_expect_success 'blank at EOF with --whitespace=error' '
test_write_lines a b c >one &&
git add one &&
cat one >expect &&
echo >>one &&
git diff -- one >patch &&
git checkout one &&
test_must_fail git apply --whitespace=error patch 2>error &&
test_cmp expect one &&
grep "new blank line at EOF" error
'
test_expect_success 'blank but not empty at EOF' '
test_write_lines a b c >one &&
git add one &&
echo " " >>one &&
cat one >expect &&
git diff -- one >patch &&
git checkout one &&
git apply --whitespace=warn patch 2>error &&
test_cmp expect one &&
grep "new blank line at EOF" error
'
test_expect_success 'applying beyond EOF requires one non-blank context line' '
test_write_lines "" "" "" "" >one &&
git add one &&
echo b >>one &&
git diff -- one >patch &&
git checkout one &&
test_write_lines a "" >one &&
cp one expect &&
test_must_fail git apply --whitespace=fix patch &&
test_cmp expect one &&
test_must_fail git apply --ignore-space-change --whitespace=fix patch &&
test_cmp expect one
'
test_expect_success 'tons of blanks at EOF should not apply' '
for i in 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16; do
test_write_lines "" "" "" "" || return 1
done >one &&
git add one &&
echo a >>one &&
git diff -- one >patch &&
>one &&
test_must_fail git apply --whitespace=fix patch &&
test_must_fail git apply --ignore-space-change --whitespace=fix patch
'
test_expect_success 'missing blank line at end with --whitespace=fix' '
echo a >one &&
echo >>one &&
git add one &&
echo b >>one &&
cp one expect &&
git diff -- one >patch &&
echo a >one &&
cp one saved-one &&
test_must_fail git apply patch &&
git apply --whitespace=fix patch &&
test_cmp expect one &&
mv saved-one one &&
git apply --ignore-space-change --whitespace=fix patch &&
test_cmp expect one
'
test_expect_success 'two missing blank lines at end with --whitespace=fix' '
test_write_lines a "" b c >one &&
cp one no-blank-lines &&
test_write_lines "" "" >>one &&
git add one &&
echo d >>one &&
cp one expect &&
echo >>one &&
git diff -- one >patch &&
cp no-blank-lines one &&
test_must_fail git apply patch &&
git apply --whitespace=fix patch &&
test_cmp expect one &&
mv no-blank-lines one &&
test_must_fail git apply patch &&
git apply --ignore-space-change --whitespace=fix patch &&
test_cmp expect one
'
test_expect_success 'missing blank line at end, insert before end, --whitespace=fix' '
test_write_lines a "" >one &&
git add one &&
test_write_lines b a "" >one &&
cp one expect &&
git diff -- one >patch &&
echo a >one &&
test_must_fail git apply patch &&
git apply --whitespace=fix patch &&
test_cmp expect one
'
test_expect_success 'shrink file with tons of missing blanks at end of file' '
test_write_lines a b c >one &&
cp one no-blank-lines &&
for i in 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16; do
test_write_lines "" "" "" "" || return 1
done >>one &&
git add one &&
echo a >one &&
cp one expect &&
git diff -- one >patch &&
cp no-blank-lines one &&
test_must_fail git apply patch &&
git apply --whitespace=fix patch &&
test_cmp expect one &&
mv no-blank-lines one &&
git apply --ignore-space-change --whitespace=fix patch &&
test_cmp expect one
'
test_expect_success 'missing blanks at EOF must only match blank lines' '
test_write_lines a b >one &&
git add one &&
test_write_lines c d >>one &&
git diff -- one >patch &&
echo a >one &&
test_must_fail git apply patch &&
test_must_fail git apply --whitespace=fix patch &&
test_must_fail git apply --ignore-space-change --whitespace=fix patch
'
sed -e's/Z//' >one <<EOF
a
b
c
Z
EOF
test_expect_success 'missing blank line should match context line with spaces' '
git add one &&
echo d >>one &&
git diff -- one >patch &&
test_write_lines a b c >one &&
cp one expect &&
test_write_lines "" d >>expect &&
git add one &&
git apply --whitespace=fix patch &&
test_cmp expect one
'
sed -e's/Z//' >one <<EOF
a
b
c
Z
EOF
test_expect_success 'same, but with the --ignore-space-option' '
git add one &&
echo d >>one &&
cp one expect &&
git diff -- one >patch &&
test_write_lines a b c >one &&
git add one &&
git checkout-index -f one &&
git apply --ignore-space-change --whitespace=fix patch &&
test_cmp expect one
'
test_expect_success 'same, but with CR-LF line endings && cr-at-eol set' '
git config core.whitespace cr-at-eol &&
printf "a\r\n" >one &&
printf "b\r\n" >>one &&
printf "c\r\n" >>one &&
cp one save-one &&
printf " \r\n" >>one &&
git add one &&
printf "d\r\n" >>one &&
cp one expect &&
git diff -- one >patch &&
mv save-one one &&
git apply --ignore-space-change --whitespace=fix patch &&
test_cmp expect one
'
test_expect_success 'CR-LF line endings && add line && text=auto' '
git config --unset core.whitespace &&
printf "a\r\n" >one &&
cp one save-one &&
git add one &&
printf "b\r\n" >>one &&
cp one expect &&
git diff -- one >patch &&
mv save-one one &&
echo "one text=auto" >.gitattributes &&
git apply patch &&
test_cmp expect one
'
test_expect_success 'CR-LF line endings && change line && text=auto' '
printf "a\r\n" >one &&
cp one save-one &&
git add one &&
printf "b\r\n" >one &&
cp one expect &&
git diff -- one >patch &&
mv save-one one &&
echo "one text=auto" >.gitattributes &&
git apply patch &&
test_cmp expect one
'
test_expect_success 'LF in repo, CRLF in worktree && change line && text=auto' '
printf "a\n" >one &&
git add one &&
printf "b\r\n" >one &&
git diff -- one >patch &&
printf "a\r\n" >one &&
echo "one text=auto" >.gitattributes &&
git -c core.eol=CRLF apply patch &&
printf "b\r\n" >expect &&
test_cmp expect one
'
test_expect_success 'whitespace=fix to expand' '
qz_to_tab_space >preimage <<-\EOF &&
QQa
QQb
QQc
ZZZZZZZZZZZZZZZZd
QQe
QQf
QQg
EOF
qz_to_tab_space >patch <<-\EOF &&
diff --git a/preimage b/preimage
--- a/preimage
+++ b/preimage
@@ -1,7 +1,6 @@
QQa
QQb
QQc
-QQd
QQe
QQf
QQg
EOF
git -c core.whitespace=tab-in-indent apply --whitespace=fix patch
'
test_expect_success 'whitespace check skipped for excluded paths' '
git config core.whitespace blank-at-eol &&
>used &&
>unused &&
git add used unused &&
echo "used" >used &&
echo "unused " >unused &&
git diff-files -p used unused >patch &&
git apply --include=used --stat --whitespace=error <patch
'
test_expect_success 'check incomplete lines (setup)' '
rm -f .gitattributes &&
git config core.whitespace incomplete-line
'
test_expect_success 'incomplete context line (not an error)' '
(test_write_lines 1 2 3 4 5 && printf 6) >sample-i &&
(test_write_lines 1 2 3 0 5 && printf 6) >sample2-i &&
cat sample-i >target &&
git add target &&
cat sample2-i >target &&
git diff-files -p target >patch &&
cat sample-i >target &&
git apply --whitespace=error <patch &&
test_cmp sample2-i target &&
cat sample-i >target &&
git apply --whitespace=error --check <patch 2>error &&
test_cmp sample-i target &&
test_must_be_empty error &&
cat sample2-i >target &&
git apply --whitespace=error -R <patch &&
test_cmp sample-i target &&
cat sample2-i >target &&
git apply -R --whitespace=error --check <patch 2>error &&
test_cmp sample2-i target &&
test_must_be_empty error
'
test_expect_success 'last line made incomplete (error)' '
test_write_lines 1 2 3 4 5 6 >sample &&
(test_write_lines 1 2 3 4 5 && printf 6) >sample-i &&
cat sample >target &&
git add target &&
cat sample-i >target &&
git diff-files -p target >patch &&
cat sample >target &&
test_must_fail git apply --whitespace=error <patch 2>error &&
test_grep "no newline" error &&
cat sample >target &&
test_must_fail git apply --whitespace=error --check <patch 2>actual &&
test_cmp sample target &&
cat >expect <<-\EOF &&
<stdin>:10: no newline at the end of file.
6
error: 1 line adds whitespace errors.
EOF
test_cmp expect actual &&
cat sample-i >target &&
git apply --whitespace=error -R <patch &&
test_cmp sample target &&
cat sample-i >target &&
git apply --whitespace=error --check -R <patch 2>error &&
test_cmp sample-i target &&
test_must_be_empty error &&
cat sample >target &&
git apply --whitespace=fix <patch &&
test_cmp sample target
'
test_expect_success 'incomplete line removed at the end (not an error)' '
(test_write_lines 1 2 3 4 5 && printf 6) >sample-i &&
test_write_lines 1 2 3 4 5 6 >sample &&
cat sample-i >target &&
git add target &&
cat sample >target &&
git diff-files -p target >patch &&
cat sample-i >target &&
git apply --whitespace=error <patch &&
test_cmp sample target &&
cat sample-i >target &&
git apply --whitespace=error --check <patch 2>error &&
test_cmp sample-i target &&
test_must_be_empty error &&
cat sample >target &&
test_must_fail git apply --whitespace=error -R <patch 2>error &&
test_grep "no newline" error &&
cat sample >target &&
test_must_fail git apply --whitespace=error --check -R <patch 2>actual &&
test_cmp sample target &&
cat >expect <<-\EOF &&
<stdin>:9: no newline at the end of file.
6
error: 1 line adds whitespace errors.
EOF
test_cmp expect actual &&
cat sample >target &&
git apply --whitespace=fix -R <patch &&
test_cmp sample target
'
test_expect_success 'incomplete line corrected at the end (not an error)' '
(test_write_lines 1 2 3 4 5 && printf 6) >sample-i &&
test_write_lines 1 2 3 4 5 7 >sample3 &&
cat sample-i >target &&
git add target &&
cat sample3 >target &&
git diff-files -p target >patch &&
cat sample-i >target &&
git apply --whitespace=error <patch &&
test_cmp sample3 target &&
cat sample-i >target &&
git apply --whitespace=error --check <patch 2>error &&
test_cmp sample-i target &&
test_must_be_empty error &&
cat sample3 >target &&
test_must_fail git apply --whitespace=error -R <patch 2>error &&
test_grep "no newline" error &&
cat sample3 >target &&
test_must_fail git apply --whitespace=error -R --check <patch 2>actual &&
test_cmp sample3 target &&
cat >expect <<-\EOF &&
<stdin>:9: no newline at the end of file.
6
error: 1 line adds whitespace errors.
EOF
test_cmp expect actual &&
cat sample3 >target &&
git apply --whitespace=fix -R <patch &&
test_cmp sample target
'
test_expect_success 'incomplete line modified at the end (error)' '
(test_write_lines 1 2 3 4 5 && printf 6) >sample-i &&
(test_write_lines 1 2 3 4 5 && printf 7) >sample3-i &&
test_write_lines 1 2 3 4 5 6 >sample &&
test_write_lines 1 2 3 4 5 7 >sample3 &&
cat sample-i >target &&
git add target &&
cat sample3-i >target &&
git diff-files -p target >patch &&
cat sample-i >target &&
test_must_fail git apply --whitespace=error <patch 2>error &&
test_grep "no newline" error &&
cat sample-i >target &&
test_must_fail git apply --whitespace=error --check <patch 2>actual &&
test_cmp sample-i target &&
cat >expect <<-\EOF &&
<stdin>:11: no newline at the end of file.
7
error: 1 line adds whitespace errors.
EOF
test_cmp expect actual &&
cat sample3-i >target &&
test_must_fail git apply --whitespace=error -R <patch 2>error &&
test_grep "no newline" error &&
cat sample3-i >target &&
test_must_fail git apply --whitespace=error --check -R <patch 2>actual &&
test_cmp sample3-i target &&
cat >expect <<-\EOF &&
<stdin>:9: no newline at the end of file.
6
error: 1 line adds whitespace errors.
EOF
test_cmp expect actual &&
cat sample-i >target &&
git apply --whitespace=fix <patch &&
test_cmp sample3 target &&
cat sample3-i >target &&
git apply --whitespace=fix -R <patch &&
test_cmp sample target
'
test_done