Merge branch 'jc/calloc-fix'
[git] / t / t0021-conversion.sh
1 #!/bin/sh
2
3 test_description='blob conversion via gitattributes'
4
5 GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main
6 export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME
7
8 . ./test-lib.sh
9
10 TEST_ROOT="$PWD"
11 PATH=$TEST_ROOT:$PATH
12
13 write_script <<\EOF "$TEST_ROOT/rot13.sh"
14 tr \
15   'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ' \
16   'nopqrstuvwxyzabcdefghijklmNOPQRSTUVWXYZABCDEFGHIJKLM'
17 EOF
18
19 write_script rot13-filter.pl "$PERL_PATH" \
20         <"$TEST_DIRECTORY"/t0021/rot13-filter.pl
21
22 generate_random_characters () {
23         LEN=$1
24         NAME=$2
25         test-tool genrandom some-seed $LEN |
26                 perl -pe "s/./chr((ord($&) % 26) + ord('a'))/sge" >"$TEST_ROOT/$NAME"
27 }
28
29 filter_git () {
30         rm -f *.log &&
31         git "$@"
32 }
33
34 # Compare two files and ensure that `clean` and `smudge` respectively are
35 # called at least once if specified in the `expect` file. The actual
36 # invocation count is not relevant because their number can vary.
37 # c.f. https://lore.kernel.org/git/xmqqshv18i8i.fsf@gitster.mtv.corp.google.com/
38 test_cmp_count () {
39         expect=$1
40         actual=$2
41         for FILE in "$expect" "$actual"
42         do
43                 sort "$FILE" | uniq -c |
44                 sed -e "s/^ *[0-9][0-9]*[       ]*IN: /x IN: /" >"$FILE.tmp"
45         done &&
46         test_cmp "$expect.tmp" "$actual.tmp" &&
47         rm "$expect.tmp" "$actual.tmp"
48 }
49
50 # Compare two files but exclude all `clean` invocations because Git can
51 # call `clean` zero or more times.
52 # c.f. https://lore.kernel.org/git/xmqqshv18i8i.fsf@gitster.mtv.corp.google.com/
53 test_cmp_exclude_clean () {
54         expect=$1
55         actual=$2
56         for FILE in "$expect" "$actual"
57         do
58                 grep -v "IN: clean" "$FILE" >"$FILE.tmp"
59         done &&
60         test_cmp "$expect.tmp" "$actual.tmp" &&
61         rm "$expect.tmp" "$actual.tmp"
62 }
63
64 # Check that the contents of two files are equal and that their rot13 version
65 # is equal to the committed content.
66 test_cmp_committed_rot13 () {
67         test_cmp "$1" "$2" &&
68         rot13.sh <"$1" >expected &&
69         git cat-file blob :"$2" >actual &&
70         test_cmp expected actual
71 }
72
73 test_expect_success setup '
74         git config filter.rot13.smudge ./rot13.sh &&
75         git config filter.rot13.clean ./rot13.sh &&
76
77         {
78             echo "*.t filter=rot13"
79             echo "*.i ident"
80         } >.gitattributes &&
81
82         {
83             echo a b c d e f g h i j k l m
84             echo n o p q r s t u v w x y z
85             echo '\''$Id$'\''
86         } >test &&
87         cat test >test.t &&
88         cat test >test.o &&
89         cat test >test.i &&
90         git add test test.t test.i &&
91         rm -f test test.t test.i &&
92         git checkout -- test test.t test.i &&
93
94         echo "content-test2" >test2.o &&
95         echo "content-test3 - filename with special characters" >"test3 '\''sq'\'',\$x=.o"
96 '
97
98 script='s/^\$Id: \([0-9a-f]*\) \$/\1/p'
99
100 test_expect_success check '
101
102         test_cmp test.o test &&
103         test_cmp test.o test.t &&
104
105         # ident should be stripped in the repository
106         git diff --raw --exit-code :test :test.i &&
107         id=$(git rev-parse --verify :test) &&
108         embedded=$(sed -ne "$script" test.i) &&
109         test "z$id" = "z$embedded" &&
110
111         git cat-file blob :test.t >test.r &&
112
113         ./rot13.sh <test.o >test.t &&
114         test_cmp test.r test.t
115 '
116
117 # If an expanded ident ever gets into the repository, we want to make sure that
118 # it is collapsed before being expanded again on checkout
119 test_expect_success expanded_in_repo '
120         {
121                 echo "File with expanded keywords"
122                 echo "\$Id\$"
123                 echo "\$Id:\$"
124                 echo "\$Id: 0000000000000000000000000000000000000000 \$"
125                 echo "\$Id: NoSpaceAtEnd\$"
126                 echo "\$Id:NoSpaceAtFront \$"
127                 echo "\$Id:NoSpaceAtEitherEnd\$"
128                 echo "\$Id: NoTerminatingSymbol"
129                 echo "\$Id: Foreign Commit With Spaces \$"
130         } >expanded-keywords.0 &&
131
132         {
133                 cat expanded-keywords.0 &&
134                 printf "\$Id: NoTerminatingSymbolAtEOF"
135         } >expanded-keywords &&
136         cat expanded-keywords >expanded-keywords-crlf &&
137         git add expanded-keywords expanded-keywords-crlf &&
138         git commit -m "File with keywords expanded" &&
139         id=$(git rev-parse --verify :expanded-keywords) &&
140
141         {
142                 echo "File with expanded keywords"
143                 echo "\$Id: $id \$"
144                 echo "\$Id: $id \$"
145                 echo "\$Id: $id \$"
146                 echo "\$Id: $id \$"
147                 echo "\$Id: $id \$"
148                 echo "\$Id: $id \$"
149                 echo "\$Id: NoTerminatingSymbol"
150                 echo "\$Id: Foreign Commit With Spaces \$"
151         } >expected-output.0 &&
152         {
153                 cat expected-output.0 &&
154                 printf "\$Id: NoTerminatingSymbolAtEOF"
155         } >expected-output &&
156         {
157                 append_cr <expected-output.0 &&
158                 printf "\$Id: NoTerminatingSymbolAtEOF"
159         } >expected-output-crlf &&
160         {
161                 echo "expanded-keywords ident"
162                 echo "expanded-keywords-crlf ident text eol=crlf"
163         } >>.gitattributes &&
164
165         rm -f expanded-keywords expanded-keywords-crlf &&
166
167         git checkout -- expanded-keywords &&
168         test_cmp expected-output expanded-keywords &&
169
170         git checkout -- expanded-keywords-crlf &&
171         test_cmp expected-output-crlf expanded-keywords-crlf
172 '
173
174 # The use of %f in a filter definition is expanded to the path to
175 # the filename being smudged or cleaned.  It must be shell escaped.
176 # First, set up some interesting file names and pet them in
177 # .gitattributes.
178 test_expect_success 'filter shell-escaped filenames' '
179         cat >argc.sh <<-EOF &&
180         #!$SHELL_PATH
181         cat >/dev/null
182         echo argc: \$# "\$@"
183         EOF
184         normal=name-no-magic &&
185         special="name  with '\''sq'\'' and \$x" &&
186         echo some test text >"$normal" &&
187         echo some test text >"$special" &&
188         git add "$normal" "$special" &&
189         git commit -q -m "add files" &&
190         echo "name* filter=argc" >.gitattributes &&
191
192         # delete the files and check them out again, using a smudge filter
193         # that will count the args and echo the command-line back to us
194         test_config filter.argc.smudge "sh ./argc.sh %f" &&
195         rm "$normal" "$special" &&
196         git checkout -- "$normal" "$special" &&
197
198         # make sure argc.sh counted the right number of args
199         echo "argc: 1 $normal" >expect &&
200         test_cmp expect "$normal" &&
201         echo "argc: 1 $special" >expect &&
202         test_cmp expect "$special" &&
203
204         # do the same thing, but with more args in the filter expression
205         test_config filter.argc.smudge "sh ./argc.sh %f --my-extra-arg" &&
206         rm "$normal" "$special" &&
207         git checkout -- "$normal" "$special" &&
208
209         # make sure argc.sh counted the right number of args
210         echo "argc: 2 $normal --my-extra-arg" >expect &&
211         test_cmp expect "$normal" &&
212         echo "argc: 2 $special --my-extra-arg" >expect &&
213         test_cmp expect "$special" &&
214         :
215 '
216
217 test_expect_success 'required filter should filter data' '
218         test_config filter.required.smudge ./rot13.sh &&
219         test_config filter.required.clean ./rot13.sh &&
220         test_config filter.required.required true &&
221
222         echo "*.r filter=required" >.gitattributes &&
223
224         cat test.o >test.r &&
225         git add test.r &&
226
227         rm -f test.r &&
228         git checkout -- test.r &&
229         test_cmp test.o test.r &&
230
231         ./rot13.sh <test.o >expected &&
232         git cat-file blob :test.r >actual &&
233         test_cmp expected actual
234 '
235
236 test_expect_success 'required filter smudge failure' '
237         test_config filter.failsmudge.smudge false &&
238         test_config filter.failsmudge.clean cat &&
239         test_config filter.failsmudge.required true &&
240
241         echo "*.fs filter=failsmudge" >.gitattributes &&
242
243         echo test >test.fs &&
244         git add test.fs &&
245         rm -f test.fs &&
246         test_must_fail git checkout -- test.fs
247 '
248
249 test_expect_success 'required filter clean failure' '
250         test_config filter.failclean.smudge cat &&
251         test_config filter.failclean.clean false &&
252         test_config filter.failclean.required true &&
253
254         echo "*.fc filter=failclean" >.gitattributes &&
255
256         echo test >test.fc &&
257         test_must_fail git add test.fc
258 '
259
260 test_expect_success 'filtering large input to small output should use little memory' '
261         test_config filter.devnull.clean "cat >/dev/null" &&
262         test_config filter.devnull.required true &&
263         for i in $(test_seq 1 30); do printf "%1048576d" 1; done >30MB &&
264         echo "30MB filter=devnull" >.gitattributes &&
265         GIT_MMAP_LIMIT=1m GIT_ALLOC_LIMIT=1m git add 30MB
266 '
267
268 test_expect_success 'filter that does not read is fine' '
269         test-tool genrandom foo $((128 * 1024 + 1)) >big &&
270         echo "big filter=epipe" >.gitattributes &&
271         test_config filter.epipe.clean "echo xyzzy" &&
272         git add big &&
273         git cat-file blob :big >actual &&
274         echo xyzzy >expect &&
275         test_cmp expect actual
276 '
277
278 test_expect_success EXPENSIVE 'filter large file' '
279         test_config filter.largefile.smudge cat &&
280         test_config filter.largefile.clean cat &&
281         for i in $(test_seq 1 2048); do printf "%1048576d" 1; done >2GB &&
282         echo "2GB filter=largefile" >.gitattributes &&
283         git add 2GB 2>err &&
284         test_must_be_empty err &&
285         rm -f 2GB &&
286         git checkout -- 2GB 2>err &&
287         test_must_be_empty err
288 '
289
290 test_expect_success "filter: clean empty file" '
291         test_config filter.in-repo-header.clean  "echo cleaned && cat" &&
292         test_config filter.in-repo-header.smudge "sed 1d" &&
293
294         echo "empty-in-worktree    filter=in-repo-header" >>.gitattributes &&
295         >empty-in-worktree &&
296
297         echo cleaned >expected &&
298         git add empty-in-worktree &&
299         git show :empty-in-worktree >actual &&
300         test_cmp expected actual
301 '
302
303 test_expect_success "filter: smudge empty file" '
304         test_config filter.empty-in-repo.clean "cat >/dev/null" &&
305         test_config filter.empty-in-repo.smudge "echo smudged && cat" &&
306
307         echo "empty-in-repo filter=empty-in-repo" >>.gitattributes &&
308         echo dead data walking >empty-in-repo &&
309         git add empty-in-repo &&
310
311         echo smudged >expected &&
312         git checkout-index --prefix=filtered- empty-in-repo &&
313         test_cmp expected filtered-empty-in-repo
314 '
315
316 test_expect_success 'disable filter with empty override' '
317         test_config_global filter.disable.smudge false &&
318         test_config_global filter.disable.clean false &&
319         test_config filter.disable.smudge false &&
320         test_config filter.disable.clean false &&
321
322         echo "*.disable filter=disable" >.gitattributes &&
323
324         echo test >test.disable &&
325         git -c filter.disable.clean= add test.disable 2>err &&
326         test_must_be_empty err &&
327         rm -f test.disable &&
328         git -c filter.disable.smudge= checkout -- test.disable 2>err &&
329         test_must_be_empty err
330 '
331
332 test_expect_success 'diff does not reuse worktree files that need cleaning' '
333         test_config filter.counter.clean "echo . >>count; sed s/^/clean:/" &&
334         echo "file filter=counter" >.gitattributes &&
335         test_commit one file &&
336         test_commit two file &&
337
338         >count &&
339         git diff-tree -p HEAD &&
340         test_line_count = 0 count
341 '
342
343 test_expect_success PERL 'required process filter should filter data' '
344         test_config_global filter.protocol.process "rot13-filter.pl debug.log clean smudge" &&
345         test_config_global filter.protocol.required true &&
346         rm -rf repo &&
347         mkdir repo &&
348         (
349                 cd repo &&
350                 git init &&
351
352                 echo "*.r filter=protocol" >.gitattributes &&
353                 git add . &&
354                 git commit -m "test commit 1" &&
355                 git branch empty-branch &&
356
357                 cp "$TEST_ROOT/test.o" test.r &&
358                 cp "$TEST_ROOT/test2.o" test2.r &&
359                 mkdir testsubdir &&
360                 cp "$TEST_ROOT/test3 '\''sq'\'',\$x=.o" "testsubdir/test3 '\''sq'\'',\$x=.r" &&
361                 >test4-empty.r &&
362
363                 S=$(test_file_size test.r) &&
364                 S2=$(test_file_size test2.r) &&
365                 S3=$(test_file_size "testsubdir/test3 '\''sq'\'',\$x=.r") &&
366                 M=$(git hash-object test.r) &&
367                 M2=$(git hash-object test2.r) &&
368                 M3=$(git hash-object "testsubdir/test3 '\''sq'\'',\$x=.r") &&
369                 EMPTY=$(git hash-object /dev/null) &&
370
371                 filter_git add . &&
372                 cat >expected.log <<-EOF &&
373                         START
374                         init handshake complete
375                         IN: clean test.r $S [OK] -- OUT: $S . [OK]
376                         IN: clean test2.r $S2 [OK] -- OUT: $S2 . [OK]
377                         IN: clean test4-empty.r 0 [OK] -- OUT: 0  [OK]
378                         IN: clean testsubdir/test3 '\''sq'\'',\$x=.r $S3 [OK] -- OUT: $S3 . [OK]
379                         STOP
380                 EOF
381                 test_cmp_count expected.log debug.log &&
382
383                 git commit -m "test commit 2" &&
384                 MAIN=$(git rev-parse --verify main) &&
385                 META="ref=refs/heads/main treeish=$MAIN" &&
386                 rm -f test2.r "testsubdir/test3 '\''sq'\'',\$x=.r" &&
387
388                 filter_git checkout --quiet --no-progress . &&
389                 cat >expected.log <<-EOF &&
390                         START
391                         init handshake complete
392                         IN: smudge test2.r blob=$M2 $S2 [OK] -- OUT: $S2 . [OK]
393                         IN: smudge testsubdir/test3 '\''sq'\'',\$x=.r blob=$M3 $S3 [OK] -- OUT: $S3 . [OK]
394                         STOP
395                 EOF
396                 test_cmp_exclude_clean expected.log debug.log &&
397
398                 # Make sure that the file appears dirty, so checkout below has to
399                 # run the configured filter.
400                 touch test.r &&
401                 filter_git checkout --quiet --no-progress empty-branch &&
402                 cat >expected.log <<-EOF &&
403                         START
404                         init handshake complete
405                         IN: clean test.r $S [OK] -- OUT: $S . [OK]
406                         STOP
407                 EOF
408                 test_cmp_exclude_clean expected.log debug.log &&
409
410                 filter_git checkout --quiet --no-progress main &&
411                 cat >expected.log <<-EOF &&
412                         START
413                         init handshake complete
414                         IN: smudge test.r $META blob=$M $S [OK] -- OUT: $S . [OK]
415                         IN: smudge test2.r $META blob=$M2 $S2 [OK] -- OUT: $S2 . [OK]
416                         IN: smudge test4-empty.r $META blob=$EMPTY 0 [OK] -- OUT: 0  [OK]
417                         IN: smudge testsubdir/test3 '\''sq'\'',\$x=.r $META blob=$M3 $S3 [OK] -- OUT: $S3 . [OK]
418                         STOP
419                 EOF
420                 test_cmp_exclude_clean expected.log debug.log &&
421
422                 test_cmp_committed_rot13 "$TEST_ROOT/test.o" test.r &&
423                 test_cmp_committed_rot13 "$TEST_ROOT/test2.o" test2.r &&
424                 test_cmp_committed_rot13 "$TEST_ROOT/test3 '\''sq'\'',\$x=.o" "testsubdir/test3 '\''sq'\'',\$x=.r"
425         )
426 '
427
428 test_expect_success PERL 'required process filter should filter data for various subcommands' '
429         test_config_global filter.protocol.process "rot13-filter.pl debug.log clean smudge" &&
430         test_config_global filter.protocol.required true &&
431         (
432                 cd repo &&
433
434                 S=$(test_file_size test.r) &&
435                 S2=$(test_file_size test2.r) &&
436                 S3=$(test_file_size "testsubdir/test3 '\''sq'\'',\$x=.r") &&
437                 M=$(git hash-object test.r) &&
438                 M2=$(git hash-object test2.r) &&
439                 M3=$(git hash-object "testsubdir/test3 '\''sq'\'',\$x=.r") &&
440                 EMPTY=$(git hash-object /dev/null) &&
441
442                 MAIN=$(git rev-parse --verify main) &&
443
444                 cp "$TEST_ROOT/test.o" test5.r &&
445                 git add test5.r &&
446                 git commit -m "test commit 3" &&
447                 git checkout empty-branch &&
448                 filter_git rebase --onto empty-branch main^^ main &&
449                 MAIN2=$(git rev-parse --verify main) &&
450                 META="ref=refs/heads/main treeish=$MAIN2" &&
451                 cat >expected.log <<-EOF &&
452                         START
453                         init handshake complete
454                         IN: smudge test.r $META blob=$M $S [OK] -- OUT: $S . [OK]
455                         IN: smudge test2.r $META blob=$M2 $S2 [OK] -- OUT: $S2 . [OK]
456                         IN: smudge test4-empty.r $META blob=$EMPTY 0 [OK] -- OUT: 0  [OK]
457                         IN: smudge test5.r $META blob=$M $S [OK] -- OUT: $S . [OK]
458                         IN: smudge testsubdir/test3 '\''sq'\'',\$x=.r $META blob=$M3 $S3 [OK] -- OUT: $S3 . [OK]
459                         STOP
460                 EOF
461                 test_cmp_exclude_clean expected.log debug.log &&
462
463                 git reset --hard empty-branch &&
464                 filter_git reset --hard $MAIN &&
465                 META="treeish=$MAIN" &&
466                 cat >expected.log <<-EOF &&
467                         START
468                         init handshake complete
469                         IN: smudge test.r $META blob=$M $S [OK] -- OUT: $S . [OK]
470                         IN: smudge test2.r $META blob=$M2 $S2 [OK] -- OUT: $S2 . [OK]
471                         IN: smudge test4-empty.r $META blob=$EMPTY 0 [OK] -- OUT: 0  [OK]
472                         IN: smudge testsubdir/test3 '\''sq'\'',\$x=.r $META blob=$M3 $S3 [OK] -- OUT: $S3 . [OK]
473                         STOP
474                 EOF
475                 test_cmp_exclude_clean expected.log debug.log &&
476
477                 git branch old-main $MAIN &&
478                 git reset --hard empty-branch &&
479                 filter_git reset --hard old-main &&
480                 META="ref=refs/heads/old-main treeish=$MAIN" &&
481                 cat >expected.log <<-EOF &&
482                         START
483                         init handshake complete
484                         IN: smudge test.r $META blob=$M $S [OK] -- OUT: $S . [OK]
485                         IN: smudge test2.r $META blob=$M2 $S2 [OK] -- OUT: $S2 . [OK]
486                         IN: smudge test4-empty.r $META blob=$EMPTY 0 [OK] -- OUT: 0  [OK]
487                         IN: smudge testsubdir/test3 '\''sq'\'',\$x=.r $META blob=$M3 $S3 [OK] -- OUT: $S3 . [OK]
488                         STOP
489                 EOF
490                 test_cmp_exclude_clean expected.log debug.log &&
491
492                 git checkout -b merge empty-branch &&
493                 git branch -f main $MAIN2 &&
494                 filter_git merge main &&
495                 META="treeish=$MAIN2" &&
496                 cat >expected.log <<-EOF &&
497                         START
498                         init handshake complete
499                         IN: smudge test.r $META blob=$M $S [OK] -- OUT: $S . [OK]
500                         IN: smudge test2.r $META blob=$M2 $S2 [OK] -- OUT: $S2 . [OK]
501                         IN: smudge test4-empty.r $META blob=$EMPTY 0 [OK] -- OUT: 0  [OK]
502                         IN: smudge test5.r $META blob=$M $S [OK] -- OUT: $S . [OK]
503                         IN: smudge testsubdir/test3 '\''sq'\'',\$x=.r $META blob=$M3 $S3 [OK] -- OUT: $S3 . [OK]
504                         STOP
505                 EOF
506                 test_cmp_exclude_clean expected.log debug.log &&
507
508                 filter_git archive main >/dev/null &&
509                 META="ref=refs/heads/main treeish=$MAIN2" &&
510                 cat >expected.log <<-EOF &&
511                         START
512                         init handshake complete
513                         IN: smudge test.r $META blob=$M $S [OK] -- OUT: $S . [OK]
514                         IN: smudge test2.r $META blob=$M2 $S2 [OK] -- OUT: $S2 . [OK]
515                         IN: smudge test4-empty.r $META blob=$EMPTY 0 [OK] -- OUT: 0  [OK]
516                         IN: smudge test5.r $META blob=$M $S [OK] -- OUT: $S . [OK]
517                         IN: smudge testsubdir/test3 '\''sq'\'',\$x=.r $META blob=$M3 $S3 [OK] -- OUT: $S3 . [OK]
518                         STOP
519                 EOF
520                 test_cmp_exclude_clean expected.log debug.log &&
521
522                 TREE="$(git rev-parse $MAIN2^{tree})" &&
523                 filter_git archive $TREE >/dev/null &&
524                 META="treeish=$TREE" &&
525                 cat >expected.log <<-EOF &&
526                         START
527                         init handshake complete
528                         IN: smudge test.r $META blob=$M $S [OK] -- OUT: $S . [OK]
529                         IN: smudge test2.r $META blob=$M2 $S2 [OK] -- OUT: $S2 . [OK]
530                         IN: smudge test4-empty.r $META blob=$EMPTY 0 [OK] -- OUT: 0  [OK]
531                         IN: smudge test5.r $META blob=$M $S [OK] -- OUT: $S . [OK]
532                         IN: smudge testsubdir/test3 '\''sq'\'',\$x=.r $META blob=$M3 $S3 [OK] -- OUT: $S3 . [OK]
533                         STOP
534                 EOF
535                 test_cmp_exclude_clean expected.log debug.log
536         )
537 '
538
539 test_expect_success PERL 'required process filter takes precedence' '
540         test_config_global filter.protocol.clean false &&
541         test_config_global filter.protocol.process "rot13-filter.pl debug.log clean" &&
542         test_config_global filter.protocol.required true &&
543         rm -rf repo &&
544         mkdir repo &&
545         (
546                 cd repo &&
547                 git init &&
548
549                 echo "*.r filter=protocol" >.gitattributes &&
550                 cp "$TEST_ROOT/test.o" test.r &&
551                 S=$(test_file_size test.r) &&
552
553                 # Check that the process filter is invoked here
554                 filter_git add . &&
555                 cat >expected.log <<-EOF &&
556                         START
557                         init handshake complete
558                         IN: clean test.r $S [OK] -- OUT: $S . [OK]
559                         STOP
560                 EOF
561                 test_cmp_count expected.log debug.log
562         )
563 '
564
565 test_expect_success PERL 'required process filter should be used only for "clean" operation only' '
566         test_config_global filter.protocol.process "rot13-filter.pl debug.log clean" &&
567         rm -rf repo &&
568         mkdir repo &&
569         (
570                 cd repo &&
571                 git init &&
572
573                 echo "*.r filter=protocol" >.gitattributes &&
574                 cp "$TEST_ROOT/test.o" test.r &&
575                 S=$(test_file_size test.r) &&
576
577                 filter_git add . &&
578                 cat >expected.log <<-EOF &&
579                         START
580                         init handshake complete
581                         IN: clean test.r $S [OK] -- OUT: $S . [OK]
582                         STOP
583                 EOF
584                 test_cmp_count expected.log debug.log &&
585
586                 rm test.r &&
587
588                 filter_git checkout --quiet --no-progress . &&
589                 # If the filter would be used for "smudge", too, we would see
590                 # "IN: smudge test.r 57 [OK] -- OUT: 57 . [OK]" here
591                 cat >expected.log <<-EOF &&
592                         START
593                         init handshake complete
594                         STOP
595                 EOF
596                 test_cmp_exclude_clean expected.log debug.log
597         )
598 '
599
600 test_expect_success PERL 'required process filter should process multiple packets' '
601         test_config_global filter.protocol.process "rot13-filter.pl debug.log clean smudge" &&
602         test_config_global filter.protocol.required true &&
603
604         rm -rf repo &&
605         mkdir repo &&
606         (
607                 cd repo &&
608                 git init &&
609
610                 # Generate data requiring 1, 2, 3 packets
611                 S=65516 && # PKTLINE_DATA_MAXLEN -> Maximal size of a packet
612                 generate_random_characters $(($S    )) 1pkt_1__.file &&
613                 generate_random_characters $(($S  +1)) 2pkt_1+1.file &&
614                 generate_random_characters $(($S*2-1)) 2pkt_2-1.file &&
615                 generate_random_characters $(($S*2  )) 2pkt_2__.file &&
616                 generate_random_characters $(($S*2+1)) 3pkt_2+1.file &&
617
618                 for FILE in "$TEST_ROOT"/*.file
619                 do
620                         cp "$FILE" . &&
621                         rot13.sh <"$FILE" >"$FILE.rot13"
622                 done &&
623
624                 echo "*.file filter=protocol" >.gitattributes &&
625                 filter_git add *.file .gitattributes &&
626                 cat >expected.log <<-EOF &&
627                         START
628                         init handshake complete
629                         IN: clean 1pkt_1__.file $(($S    )) [OK] -- OUT: $(($S    )) . [OK]
630                         IN: clean 2pkt_1+1.file $(($S  +1)) [OK] -- OUT: $(($S  +1)) .. [OK]
631                         IN: clean 2pkt_2-1.file $(($S*2-1)) [OK] -- OUT: $(($S*2-1)) .. [OK]
632                         IN: clean 2pkt_2__.file $(($S*2  )) [OK] -- OUT: $(($S*2  )) .. [OK]
633                         IN: clean 3pkt_2+1.file $(($S*2+1)) [OK] -- OUT: $(($S*2+1)) ... [OK]
634                         STOP
635                 EOF
636                 test_cmp_count expected.log debug.log &&
637
638                 M1="blob=$(git hash-object 1pkt_1__.file)" &&
639                 M2="blob=$(git hash-object 2pkt_1+1.file)" &&
640                 M3="blob=$(git hash-object 2pkt_2-1.file)" &&
641                 M4="blob=$(git hash-object 2pkt_2__.file)" &&
642                 M5="blob=$(git hash-object 3pkt_2+1.file)" &&
643                 rm -f *.file debug.log &&
644
645                 filter_git checkout --quiet --no-progress -- *.file &&
646                 cat >expected.log <<-EOF &&
647                         START
648                         init handshake complete
649                         IN: smudge 1pkt_1__.file $M1 $(($S    )) [OK] -- OUT: $(($S    )) . [OK]
650                         IN: smudge 2pkt_1+1.file $M2 $(($S  +1)) [OK] -- OUT: $(($S  +1)) .. [OK]
651                         IN: smudge 2pkt_2-1.file $M3 $(($S*2-1)) [OK] -- OUT: $(($S*2-1)) .. [OK]
652                         IN: smudge 2pkt_2__.file $M4 $(($S*2  )) [OK] -- OUT: $(($S*2  )) .. [OK]
653                         IN: smudge 3pkt_2+1.file $M5 $(($S*2+1)) [OK] -- OUT: $(($S*2+1)) ... [OK]
654                         STOP
655                 EOF
656                 test_cmp_exclude_clean expected.log debug.log &&
657
658                 for FILE in *.file
659                 do
660                         test_cmp_committed_rot13 "$TEST_ROOT/$FILE" $FILE
661                 done
662         )
663 '
664
665 test_expect_success PERL 'required process filter with clean error should fail' '
666         test_config_global filter.protocol.process "rot13-filter.pl debug.log clean smudge" &&
667         test_config_global filter.protocol.required true &&
668         rm -rf repo &&
669         mkdir repo &&
670         (
671                 cd repo &&
672                 git init &&
673
674                 echo "*.r filter=protocol" >.gitattributes &&
675
676                 cp "$TEST_ROOT/test.o" test.r &&
677                 echo "this is going to fail" >clean-write-fail.r &&
678                 echo "content-test3-subdir" >test3.r &&
679
680                 test_must_fail git add .
681         )
682 '
683
684 test_expect_success PERL 'process filter should restart after unexpected write failure' '
685         test_config_global filter.protocol.process "rot13-filter.pl debug.log clean smudge" &&
686         rm -rf repo &&
687         mkdir repo &&
688         (
689                 cd repo &&
690                 git init &&
691
692                 echo "*.r filter=protocol" >.gitattributes &&
693
694                 cp "$TEST_ROOT/test.o" test.r &&
695                 cp "$TEST_ROOT/test2.o" test2.r &&
696                 echo "this is going to fail" >smudge-write-fail.o &&
697                 cp smudge-write-fail.o smudge-write-fail.r &&
698
699                 S=$(test_file_size test.r) &&
700                 S2=$(test_file_size test2.r) &&
701                 SF=$(test_file_size smudge-write-fail.r) &&
702                 M=$(git hash-object test.r) &&
703                 M2=$(git hash-object test2.r) &&
704                 MF=$(git hash-object smudge-write-fail.r) &&
705                 rm -f debug.log &&
706
707                 git add . &&
708                 rm -f *.r &&
709
710                 rm -f debug.log &&
711                 git checkout --quiet --no-progress . 2>git-stderr.log &&
712
713                 grep "smudge write error at" git-stderr.log &&
714                 test_i18ngrep "error: external filter" git-stderr.log &&
715
716                 cat >expected.log <<-EOF &&
717                         START
718                         init handshake complete
719                         IN: smudge smudge-write-fail.r blob=$MF $SF [OK] -- [WRITE FAIL]
720                         START
721                         init handshake complete
722                         IN: smudge test.r blob=$M $S [OK] -- OUT: $S . [OK]
723                         IN: smudge test2.r blob=$M2 $S2 [OK] -- OUT: $S2 . [OK]
724                         STOP
725                 EOF
726                 test_cmp_exclude_clean expected.log debug.log &&
727
728                 test_cmp_committed_rot13 "$TEST_ROOT/test.o" test.r &&
729                 test_cmp_committed_rot13 "$TEST_ROOT/test2.o" test2.r &&
730
731                 # Smudge failed
732                 ! test_cmp smudge-write-fail.o smudge-write-fail.r &&
733                 rot13.sh <smudge-write-fail.o >expected &&
734                 git cat-file blob :smudge-write-fail.r >actual &&
735                 test_cmp expected actual
736         )
737 '
738
739 test_expect_success PERL 'process filter should not be restarted if it signals an error' '
740         test_config_global filter.protocol.process "rot13-filter.pl debug.log clean smudge" &&
741         rm -rf repo &&
742         mkdir repo &&
743         (
744                 cd repo &&
745                 git init &&
746
747                 echo "*.r filter=protocol" >.gitattributes &&
748
749                 cp "$TEST_ROOT/test.o" test.r &&
750                 cp "$TEST_ROOT/test2.o" test2.r &&
751                 echo "this will cause an error" >error.o &&
752                 cp error.o error.r &&
753
754                 S=$(test_file_size test.r) &&
755                 S2=$(test_file_size test2.r) &&
756                 SE=$(test_file_size error.r) &&
757                 M=$(git hash-object test.r) &&
758                 M2=$(git hash-object test2.r) &&
759                 ME=$(git hash-object error.r) &&
760                 rm -f debug.log &&
761
762                 git add . &&
763                 rm -f *.r &&
764
765                 filter_git checkout --quiet --no-progress . &&
766                 cat >expected.log <<-EOF &&
767                         START
768                         init handshake complete
769                         IN: smudge error.r blob=$ME $SE [OK] -- [ERROR]
770                         IN: smudge test.r blob=$M $S [OK] -- OUT: $S . [OK]
771                         IN: smudge test2.r blob=$M2 $S2 [OK] -- OUT: $S2 . [OK]
772                         STOP
773                 EOF
774                 test_cmp_exclude_clean expected.log debug.log &&
775
776                 test_cmp_committed_rot13 "$TEST_ROOT/test.o" test.r &&
777                 test_cmp_committed_rot13 "$TEST_ROOT/test2.o" test2.r &&
778                 test_cmp error.o error.r
779         )
780 '
781
782 test_expect_success PERL 'process filter abort stops processing of all further files' '
783         test_config_global filter.protocol.process "rot13-filter.pl debug.log clean smudge" &&
784         rm -rf repo &&
785         mkdir repo &&
786         (
787                 cd repo &&
788                 git init &&
789
790                 echo "*.r filter=protocol" >.gitattributes &&
791
792                 cp "$TEST_ROOT/test.o" test.r &&
793                 cp "$TEST_ROOT/test2.o" test2.r &&
794                 echo "error this blob and all future blobs" >abort.o &&
795                 cp abort.o abort.r &&
796
797                 M="blob=$(git hash-object abort.r)" &&
798                 rm -f debug.log &&
799                 SA=$(test_file_size abort.r) &&
800
801                 git add . &&
802                 rm -f *.r &&
803
804
805                 # Note: This test assumes that Git filters files in alphabetical
806                 # order ("abort.r" before "test.r").
807                 filter_git checkout --quiet --no-progress . &&
808                 cat >expected.log <<-EOF &&
809                         START
810                         init handshake complete
811                         IN: smudge abort.r $M $SA [OK] -- [ABORT]
812                         STOP
813                 EOF
814                 test_cmp_exclude_clean expected.log debug.log &&
815
816                 test_cmp "$TEST_ROOT/test.o" test.r &&
817                 test_cmp "$TEST_ROOT/test2.o" test2.r &&
818                 test_cmp abort.o abort.r
819         )
820 '
821
822 test_expect_success PERL 'invalid process filter must fail (and not hang!)' '
823         test_config_global filter.protocol.process cat &&
824         test_config_global filter.protocol.required true &&
825         rm -rf repo &&
826         mkdir repo &&
827         (
828                 cd repo &&
829                 git init &&
830
831                 echo "*.r filter=protocol" >.gitattributes &&
832
833                 cp "$TEST_ROOT/test.o" test.r &&
834                 test_must_fail git add . 2>git-stderr.log &&
835                 grep "expected git-filter-server" git-stderr.log
836         )
837 '
838
839 test_expect_success PERL 'delayed checkout in process filter' '
840         test_config_global filter.a.process "rot13-filter.pl a.log clean smudge delay" &&
841         test_config_global filter.a.required true &&
842         test_config_global filter.b.process "rot13-filter.pl b.log clean smudge delay" &&
843         test_config_global filter.b.required true &&
844
845         rm -rf repo &&
846         mkdir repo &&
847         (
848                 cd repo &&
849                 git init &&
850                 echo "*.a filter=a" >.gitattributes &&
851                 echo "*.b filter=b" >>.gitattributes &&
852                 cp "$TEST_ROOT/test.o" test.a &&
853                 cp "$TEST_ROOT/test.o" test-delay10.a &&
854                 cp "$TEST_ROOT/test.o" test-delay11.a &&
855                 cp "$TEST_ROOT/test.o" test-delay20.a &&
856                 cp "$TEST_ROOT/test.o" test-delay10.b &&
857                 git add . &&
858                 git commit -m "test commit"
859         ) &&
860
861         S=$(test_file_size "$TEST_ROOT/test.o") &&
862         PM="ref=refs/heads/main treeish=$(git -C repo rev-parse --verify main) " &&
863         M="${PM}blob=$(git -C repo rev-parse --verify main:test.a)" &&
864         cat >a.exp <<-EOF &&
865                 START
866                 init handshake complete
867                 IN: smudge test.a $M $S [OK] -- OUT: $S . [OK]
868                 IN: smudge test-delay10.a $M $S [OK] -- [DELAYED]
869                 IN: smudge test-delay11.a $M $S [OK] -- [DELAYED]
870                 IN: smudge test-delay20.a $M $S [OK] -- [DELAYED]
871                 IN: list_available_blobs test-delay10.a test-delay11.a [OK]
872                 IN: smudge test-delay10.a $M 0 [OK] -- OUT: $S . [OK]
873                 IN: smudge test-delay11.a $M 0 [OK] -- OUT: $S . [OK]
874                 IN: list_available_blobs test-delay20.a [OK]
875                 IN: smudge test-delay20.a $M 0 [OK] -- OUT: $S . [OK]
876                 IN: list_available_blobs [OK]
877                 STOP
878         EOF
879         cat >b.exp <<-EOF &&
880                 START
881                 init handshake complete
882                 IN: smudge test-delay10.b $M $S [OK] -- [DELAYED]
883                 IN: list_available_blobs test-delay10.b [OK]
884                 IN: smudge test-delay10.b $M 0 [OK] -- OUT: $S . [OK]
885                 IN: list_available_blobs [OK]
886                 STOP
887         EOF
888
889         rm -rf repo-cloned &&
890         filter_git clone repo repo-cloned &&
891         test_cmp_count a.exp repo-cloned/a.log &&
892         test_cmp_count b.exp repo-cloned/b.log &&
893
894         (
895                 cd repo-cloned &&
896                 test_cmp_committed_rot13 "$TEST_ROOT/test.o" test.a &&
897                 test_cmp_committed_rot13 "$TEST_ROOT/test.o" test-delay10.a &&
898                 test_cmp_committed_rot13 "$TEST_ROOT/test.o" test-delay11.a &&
899                 test_cmp_committed_rot13 "$TEST_ROOT/test.o" test-delay20.a &&
900                 test_cmp_committed_rot13 "$TEST_ROOT/test.o" test-delay10.b &&
901
902                 rm *.a *.b &&
903                 filter_git checkout . &&
904                 # We are not checking out a ref here, so filter out ref metadata.
905                 sed -e "s!$PM!!" ../a.exp >a.exp.filtered &&
906                 sed -e "s!$PM!!" ../b.exp >b.exp.filtered &&
907                 test_cmp_count a.exp.filtered a.log &&
908                 test_cmp_count b.exp.filtered b.log &&
909
910                 test_cmp_committed_rot13 "$TEST_ROOT/test.o" test.a &&
911                 test_cmp_committed_rot13 "$TEST_ROOT/test.o" test-delay10.a &&
912                 test_cmp_committed_rot13 "$TEST_ROOT/test.o" test-delay11.a &&
913                 test_cmp_committed_rot13 "$TEST_ROOT/test.o" test-delay20.a &&
914                 test_cmp_committed_rot13 "$TEST_ROOT/test.o" test-delay10.b
915         )
916 '
917
918 test_expect_success PERL 'missing file in delayed checkout' '
919         test_config_global filter.bug.process "rot13-filter.pl bug.log clean smudge delay" &&
920         test_config_global filter.bug.required true &&
921
922         rm -rf repo &&
923         mkdir repo &&
924         (
925                 cd repo &&
926                 git init &&
927                 echo "*.a filter=bug" >.gitattributes &&
928                 cp "$TEST_ROOT/test.o" missing-delay.a &&
929                 git add . &&
930                 git commit -m "test commit"
931         ) &&
932
933         rm -rf repo-cloned &&
934         test_must_fail git clone repo repo-cloned 2>git-stderr.log &&
935         grep "error: .missing-delay\.a. was not filtered properly" git-stderr.log
936 '
937
938 test_expect_success PERL 'invalid file in delayed checkout' '
939         test_config_global filter.bug.process "rot13-filter.pl bug.log clean smudge delay" &&
940         test_config_global filter.bug.required true &&
941
942         rm -rf repo &&
943         mkdir repo &&
944         (
945                 cd repo &&
946                 git init &&
947                 echo "*.a filter=bug" >.gitattributes &&
948                 cp "$TEST_ROOT/test.o" invalid-delay.a &&
949                 cp "$TEST_ROOT/test.o" unfiltered &&
950                 git add . &&
951                 git commit -m "test commit"
952         ) &&
953
954         rm -rf repo-cloned &&
955         test_must_fail git clone repo repo-cloned 2>git-stderr.log &&
956         grep "error: external filter .* signaled that .unfiltered. is now available although it has not been delayed earlier" git-stderr.log
957 '
958
959 for mode in 'case' 'utf-8'
960 do
961         case "$mode" in
962         case)   dir='A' symlink='a' mode_prereq='CASE_INSENSITIVE_FS' ;;
963         utf-8)
964                 dir=$(printf "\141\314\210") symlink=$(printf "\303\244")
965                 mode_prereq='UTF8_NFD_TO_NFC' ;;
966         esac
967
968         test_expect_success PERL,SYMLINKS,$mode_prereq \
969         "delayed checkout with $mode-collision don't write to the wrong place" '
970                 test_config_global filter.delay.process \
971                         "\"$TEST_ROOT/rot13-filter.pl\" --always-delay delayed.log clean smudge delay" &&
972                 test_config_global filter.delay.required true &&
973
974                 git init $mode-collision &&
975                 (
976                         cd $mode-collision &&
977                         mkdir target-dir &&
978
979                         empty_oid=$(printf "" | git hash-object -w --stdin) &&
980                         symlink_oid=$(printf "%s" "$PWD/target-dir" | git hash-object -w --stdin) &&
981                         attr_oid=$(echo "$dir/z filter=delay" | git hash-object -w --stdin) &&
982
983                         cat >objs <<-EOF &&
984                         100644 blob $empty_oid  $dir/x
985                         100644 blob $empty_oid  $dir/y
986                         100644 blob $empty_oid  $dir/z
987                         120000 blob $symlink_oid        $symlink
988                         100644 blob $attr_oid   .gitattributes
989                         EOF
990
991                         git update-index --index-info <objs &&
992                         git commit -m "test commit"
993                 ) &&
994
995                 git clone $mode-collision $mode-collision-cloned &&
996                 # Make sure z was really delayed
997                 grep "IN: smudge $dir/z .* \\[DELAYED\\]" $mode-collision-cloned/delayed.log &&
998
999                 # Should not create $dir/z at $symlink/z
1000                 test_path_is_missing $mode-collision/target-dir/z
1001         '
1002 done
1003
1004 test_expect_success PERL,SYMLINKS,CASE_INSENSITIVE_FS \
1005 "delayed checkout with submodule collision don't write to the wrong place" '
1006         git init collision-with-submodule &&
1007         (
1008                 cd collision-with-submodule &&
1009                 git config filter.delay.process "\"$TEST_ROOT/rot13-filter.pl\" --always-delay delayed.log clean smudge delay" &&
1010                 git config filter.delay.required true &&
1011
1012                 # We need Git to treat the submodule "a" and the
1013                 # leading dir "A" as different paths in the index.
1014                 git config --local core.ignoreCase false &&
1015
1016                 empty_oid=$(printf "" | git hash-object -w --stdin) &&
1017                 attr_oid=$(echo "A/B/y filter=delay" | git hash-object -w --stdin) &&
1018                 cat >objs <<-EOF &&
1019                 100644 blob $empty_oid  A/B/x
1020                 100644 blob $empty_oid  A/B/y
1021                 100644 blob $attr_oid   .gitattributes
1022                 EOF
1023                 git update-index --index-info <objs &&
1024
1025                 git init a &&
1026                 mkdir target-dir &&
1027                 symlink_oid=$(printf "%s" "$PWD/target-dir" | git -C a hash-object -w --stdin) &&
1028                 echo "120000 blob $symlink_oid  b" >objs &&
1029                 git -C a update-index --index-info <objs &&
1030                 git -C a commit -m sub &&
1031                 git submodule add ./a &&
1032                 git commit -m super &&
1033
1034                 git checkout --recurse-submodules . &&
1035                 grep "IN: smudge A/B/y .* \\[DELAYED\\]" delayed.log &&
1036                 test_path_is_missing target-dir/y
1037         )
1038 '
1039
1040 test_done