Files
git/t/t4124-apply-ws-rule.sh
Junio C Hamano afdb4c665f apply: fix new-style empty context line triggering incomplete-line check
A new-style unified context diff represents an empty context line
with an empty line (instead of a line with a single SP on it).  The
code to check whitespace errors in an incoming patch is designed to
omit the first byte of a line (typically SP, "-", or "+") and pass the
remainder of the line to the whitespace checker.

Usually we do not pass a context line to the whitespace error checker,
but when we are correcting errors, we do.  This "remove the first
byte and send the remainder" strategy of checking a line ended up
sending a zero-length string to the whitespace checker when seeing a
new-style empty context line, which caused the whitespace checker to
say "ah, you do not even have a newline at the end!", leading to an
"incomplete line" in the middle of the patch!

Fix this by pretending that we got a traditional empty context line
when we drive the whitespace checker.

Signed-off-by: Junio C Hamano <gitster@pobox.com>
2026-03-17 11:46:47 -07:00

849 lines
21 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 'no incomplete context line (not an error)' '
test_when_finished "rm -f sample*-i patch patch-new target" &&
test_write_lines 1 2 3 "" 4 5 >sample-i &&
test_write_lines 1 2 3 "" 0 5 >sample2-i &&
cat sample-i >target &&
git add target &&
cat sample2-i >target &&
git diff-files -p target >patch &&
sed -e "s/^ $//" <patch >patch-new &&
cat sample-i >target &&
git apply --whitespace=fix <patch-new 2>error &&
test_cmp sample2-i target &&
test_must_be_empty error
'
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_expect_success "incomplete-line error is disabled for symlinks" '
test_when_finished "git reset" &&
test_when_finished "rm -f patch.txt" &&
oneblob=$(printf "one" | git hash-object --stdin -w -t blob) &&
twoblob=$(printf "two" | git hash-object --stdin -w -t blob) &&
oneshort=$(git rev-parse --short $oneblob) &&
twoshort=$(git rev-parse --short $twoblob) &&
cat >patch0.txt <<-EOF &&
diff --git a/mylink b/mylink
index $oneshort..$twoshort 120000
--- a/mylink
+++ b/mylink
@@ -1 +1 @@
-one
\ No newline at end of file
+two
\ No newline at end of file
EOF
# the index has the preimage symlink
git update-index --add --cacheinfo "120000,$oneblob,mylink" &&
# check the patch going forward and reverse
git -c core.whitespace=incomplete apply --cached --check \
--whitespace=error patch0.txt &&
git update-index --add --cacheinfo "120000,$twoblob,mylink" &&
git -c core.whitespace=incomplete apply --cached --check \
--whitespace=error -R patch0.txt &&
# the patch turns it into the postimage symlink
git update-index --add --cacheinfo "120000,$oneblob,mylink" &&
git -c core.whitespace=incomplete apply --cached --whitespace=error \
patch0.txt &&
# and then back.
git -c core.whitespace=incomplete apply --cached -R --whitespace=error \
patch0.txt &&
# a text file turns into a symlink
cat >patch1.txt <<-EOF &&
diff --git a/mylink b/mylink
deleted file mode 100644
index $oneshort..0000000
--- a/mylink
+++ /dev/null
@@ -1 +0,0 @@
-one
\ No newline at end of file
diff --git a/mylink b/mylink
new file mode 120000
index 0000000..$twoshort
--- /dev/null
+++ b/mylink
@@ -0,0 +1 @@
+two
\ No newline at end of file
EOF
# the index has the preimage text
git update-index --cacheinfo "100644,$oneblob,mylink" &&
# check
git -c core.whitespace=incomplete apply --cached \
--check --whitespace=error patch1.txt &&
# reverse, leaving an incomplete text file, should error
git update-index --cacheinfo "120000,$twoblob,mylink" &&
test_must_fail git -c core.whitespace=incomplete \
apply --cached --check --whitespace=error -R patch1.txt &&
# apply to create a symbolic link
git update-index --cacheinfo "100644,$oneblob,mylink" &&
git -c core.whitespace=incomplete apply --cached --whitespace=error \
patch1.txt &&
# turning it back into an incomplete text file is an error
test_must_fail git -c core.whitespace=incomplete \
apply --cached --whitespace=error -R patch1.txt
'
test_done