Merge branch 'ab/userdiff-tests'
[git] / t / t1091-sparse-checkout-builtin.sh
1 #!/bin/sh
2
3 test_description='sparse checkout builtin tests'
4
5 GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main
6 export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME
7
8 . ./test-lib.sh
9
10 list_files() {
11         # Do not replace this with 'ls "$1"', as "ls" with BSD-lineage
12         # enables "-A" by default for root and ends up including ".git" and
13         # such in its output. (Note, though, that running the test suite as
14         # root is generally not recommended.)
15         (cd "$1" && printf '%s\n' *)
16 }
17
18 check_files() {
19         list_files "$1" >actual &&
20         shift &&
21         printf "%s\n" $@ >expect &&
22         test_cmp expect actual
23 }
24
25 test_expect_success 'setup' '
26         git init repo &&
27         (
28                 cd repo &&
29                 echo "initial" >a &&
30                 mkdir folder1 folder2 deep &&
31                 mkdir deep/deeper1 deep/deeper2 &&
32                 mkdir deep/deeper1/deepest &&
33                 cp a folder1 &&
34                 cp a folder2 &&
35                 cp a deep &&
36                 cp a deep/deeper1 &&
37                 cp a deep/deeper2 &&
38                 cp a deep/deeper1/deepest &&
39                 git add . &&
40                 git commit -m "initial commit"
41         )
42 '
43
44 test_expect_success 'git sparse-checkout list (empty)' '
45         git -C repo sparse-checkout list >list 2>err &&
46         test_must_be_empty list &&
47         test_i18ngrep "this worktree is not sparse (sparse-checkout file may not exist)" err
48 '
49
50 test_expect_success 'git sparse-checkout list (populated)' '
51         test_when_finished rm -f repo/.git/info/sparse-checkout &&
52         cat >repo/.git/info/sparse-checkout <<-\EOF &&
53         /folder1/*
54         /deep/
55         **/a
56         !*bin*
57         EOF
58         cp repo/.git/info/sparse-checkout expect &&
59         git -C repo sparse-checkout list >list &&
60         test_cmp expect list
61 '
62
63 test_expect_success 'git sparse-checkout init' '
64         git -C repo sparse-checkout init &&
65         cat >expect <<-\EOF &&
66         /*
67         !/*/
68         EOF
69         test_cmp expect repo/.git/info/sparse-checkout &&
70         test_cmp_config -C repo true core.sparsecheckout &&
71         check_files repo a
72 '
73
74 test_expect_success 'git sparse-checkout list after init' '
75         git -C repo sparse-checkout list >actual &&
76         cat >expect <<-\EOF &&
77         /*
78         !/*/
79         EOF
80         test_cmp expect actual
81 '
82
83 test_expect_success 'init with existing sparse-checkout' '
84         echo "*folder*" >> repo/.git/info/sparse-checkout &&
85         git -C repo sparse-checkout init &&
86         cat >expect <<-\EOF &&
87         /*
88         !/*/
89         *folder*
90         EOF
91         test_cmp expect repo/.git/info/sparse-checkout &&
92         check_files repo a folder1 folder2
93 '
94
95 test_expect_success 'clone --sparse' '
96         git clone --sparse "file://$(pwd)/repo" clone &&
97         git -C clone sparse-checkout list >actual &&
98         cat >expect <<-\EOF &&
99         /*
100         !/*/
101         EOF
102         test_cmp expect actual &&
103         check_files clone a
104 '
105
106 test_expect_success 'interaction with clone --no-checkout (unborn index)' '
107         git clone --no-checkout "file://$(pwd)/repo" clone_no_checkout &&
108         git -C clone_no_checkout sparse-checkout init --cone &&
109         git -C clone_no_checkout sparse-checkout set folder1 &&
110
111         git -C clone_no_checkout sparse-checkout list >actual &&
112         cat >expect <<-\EOF &&
113         folder1
114         EOF
115         test_cmp expect actual &&
116
117         # nothing checked out, expect "No such file or directory"
118         ! ls clone_no_checkout/* >actual &&
119         test_must_be_empty actual &&
120         test_path_is_missing clone_no_checkout/.git/index &&
121
122         # No branch is checked out until we manually switch to one
123         git -C clone_no_checkout switch main &&
124         test_path_is_file clone_no_checkout/.git/index &&
125         check_files clone_no_checkout a folder1
126 '
127
128 test_expect_success 'set enables config' '
129         git init empty-config &&
130         (
131                 cd empty-config &&
132                 test_commit test file &&
133                 test_path_is_missing .git/config.worktree &&
134                 git sparse-checkout set nothing &&
135                 test_path_is_file .git/config.worktree &&
136                 test_cmp_config true core.sparseCheckout
137         )
138 '
139
140 test_expect_success 'set sparse-checkout using builtin' '
141         git -C repo sparse-checkout set "/*" "!/*/" "*folder*" &&
142         cat >expect <<-\EOF &&
143         /*
144         !/*/
145         *folder*
146         EOF
147         git -C repo sparse-checkout list >actual &&
148         test_cmp expect actual &&
149         test_cmp expect repo/.git/info/sparse-checkout &&
150         check_files repo a folder1 folder2
151 '
152
153 test_expect_success 'set sparse-checkout using --stdin' '
154         cat >expect <<-\EOF &&
155         /*
156         !/*/
157         /folder1/
158         /folder2/
159         EOF
160         git -C repo sparse-checkout set --stdin <expect &&
161         git -C repo sparse-checkout list >actual &&
162         test_cmp expect actual &&
163         test_cmp expect repo/.git/info/sparse-checkout &&
164         check_files repo "a folder1 folder2"
165 '
166
167 test_expect_success 'add to sparse-checkout' '
168         cat repo/.git/info/sparse-checkout >expect &&
169         cat >add <<-\EOF &&
170         pattern1
171         /folder1/
172         pattern2
173         EOF
174         cat add >>expect &&
175         git -C repo sparse-checkout add --stdin <add &&
176         git -C repo sparse-checkout list >actual &&
177         test_cmp expect actual &&
178         test_cmp expect repo/.git/info/sparse-checkout &&
179         check_files repo "a folder1 folder2"
180 '
181
182 test_expect_success 'cone mode: match patterns' '
183         git -C repo config --worktree core.sparseCheckoutCone true &&
184         rm -rf repo/a repo/folder1 repo/folder2 &&
185         git -C repo read-tree -mu HEAD 2>err &&
186         test_i18ngrep ! "disabling cone patterns" err &&
187         git -C repo reset --hard &&
188         check_files repo a folder1 folder2
189 '
190
191 test_expect_success 'cone mode: warn on bad pattern' '
192         test_when_finished mv sparse-checkout repo/.git/info/ &&
193         cp repo/.git/info/sparse-checkout . &&
194         echo "!/deep/deeper/*" >>repo/.git/info/sparse-checkout &&
195         git -C repo read-tree -mu HEAD 2>err &&
196         test_i18ngrep "unrecognized negative pattern" err
197 '
198
199 test_expect_success 'sparse-checkout disable' '
200         test_when_finished rm -rf repo/.git/info/sparse-checkout &&
201         git -C repo sparse-checkout disable &&
202         test_path_is_file repo/.git/info/sparse-checkout &&
203         git -C repo config --list >config &&
204         test_must_fail git config core.sparseCheckout &&
205         check_files repo a deep folder1 folder2
206 '
207
208 test_expect_success 'cone mode: init and set' '
209         git -C repo sparse-checkout init --cone &&
210         git -C repo config --list >config &&
211         test_i18ngrep "core.sparsecheckoutcone=true" config &&
212         list_files repo >dir  &&
213         echo a >expect &&
214         test_cmp expect dir &&
215         git -C repo sparse-checkout set deep/deeper1/deepest/ 2>err &&
216         test_must_be_empty err &&
217         check_files repo a deep &&
218         check_files repo/deep a deeper1 &&
219         check_files repo/deep/deeper1 a deepest &&
220         cat >expect <<-\EOF &&
221         /*
222         !/*/
223         /deep/
224         !/deep/*/
225         /deep/deeper1/
226         !/deep/deeper1/*/
227         /deep/deeper1/deepest/
228         EOF
229         test_cmp expect repo/.git/info/sparse-checkout &&
230         git -C repo sparse-checkout set --stdin 2>err <<-\EOF &&
231         folder1
232         folder2
233         EOF
234         test_must_be_empty err &&
235         check_files repo a folder1 folder2
236 '
237
238 test_expect_success 'cone mode: list' '
239         cat >expect <<-\EOF &&
240         folder1
241         folder2
242         EOF
243         git -C repo sparse-checkout set --stdin <expect &&
244         git -C repo sparse-checkout list >actual 2>err &&
245         test_must_be_empty err &&
246         test_cmp expect actual
247 '
248
249 test_expect_success 'cone mode: set with nested folders' '
250         git -C repo sparse-checkout set deep deep/deeper1/deepest 2>err &&
251         test_line_count = 0 err &&
252         cat >expect <<-\EOF &&
253         /*
254         !/*/
255         /deep/
256         EOF
257         test_cmp repo/.git/info/sparse-checkout expect
258 '
259
260 test_expect_success 'cone mode: add independent path' '
261         git -C repo sparse-checkout set deep/deeper1 &&
262         git -C repo sparse-checkout add folder1 &&
263         cat >expect <<-\EOF &&
264         /*
265         !/*/
266         /deep/
267         !/deep/*/
268         /deep/deeper1/
269         /folder1/
270         EOF
271         test_cmp expect repo/.git/info/sparse-checkout &&
272         check_files repo a deep folder1
273 '
274
275 test_expect_success 'cone mode: add sibling path' '
276         git -C repo sparse-checkout set deep/deeper1 &&
277         git -C repo sparse-checkout add deep/deeper2 &&
278         cat >expect <<-\EOF &&
279         /*
280         !/*/
281         /deep/
282         !/deep/*/
283         /deep/deeper1/
284         /deep/deeper2/
285         EOF
286         test_cmp expect repo/.git/info/sparse-checkout &&
287         check_files repo a deep
288 '
289
290 test_expect_success 'cone mode: add parent path' '
291         git -C repo sparse-checkout set deep/deeper1 folder1 &&
292         git -C repo sparse-checkout add deep &&
293         cat >expect <<-\EOF &&
294         /*
295         !/*/
296         /deep/
297         /folder1/
298         EOF
299         test_cmp expect repo/.git/info/sparse-checkout &&
300         check_files repo a deep folder1
301 '
302
303 test_expect_success 'not-up-to-date does not block rest of sparsification' '
304         test_when_finished git -C repo sparse-checkout disable &&
305         test_when_finished git -C repo reset --hard &&
306         git -C repo sparse-checkout set deep &&
307
308         echo update >repo/deep/deeper2/a &&
309         cp repo/.git/info/sparse-checkout expect &&
310         test_write_lines "!/deep/*/" "/deep/deeper1/" >>expect &&
311
312         git -C repo sparse-checkout set deep/deeper1 2>err &&
313
314         test_i18ngrep "The following paths are not up to date" err &&
315         test_cmp expect repo/.git/info/sparse-checkout &&
316         check_files repo/deep a deeper1 deeper2 &&
317         check_files repo/deep/deeper1 a deepest &&
318         check_files repo/deep/deeper1/deepest a &&
319         check_files repo/deep/deeper2 a
320 '
321
322 test_expect_success 'revert to old sparse-checkout on empty update' '
323         git init empty-test &&
324         (
325                 echo >file &&
326                 git add file &&
327                 git commit -m "test" &&
328                 git sparse-checkout set nothing 2>err &&
329                 test_i18ngrep ! "Sparse checkout leaves no entry on working directory" err &&
330                 test_i18ngrep ! ".git/index.lock" err &&
331                 git sparse-checkout set file
332         )
333 '
334
335 test_expect_success 'fail when lock is taken' '
336         test_when_finished rm -rf repo/.git/info/sparse-checkout.lock &&
337         touch repo/.git/info/sparse-checkout.lock &&
338         test_must_fail git -C repo sparse-checkout set deep 2>err &&
339         test_i18ngrep "Unable to create .*\.lock" err
340 '
341
342 test_expect_success '.gitignore should not warn about cone mode' '
343         git -C repo config --worktree core.sparseCheckoutCone true &&
344         echo "**/bin/*" >repo/.gitignore &&
345         git -C repo reset --hard 2>err &&
346         test_i18ngrep ! "disabling cone patterns" err
347 '
348
349 test_expect_success 'sparse-checkout (init|set|disable) warns with dirty status' '
350         git clone repo dirty &&
351         echo dirty >dirty/folder1/a &&
352
353         git -C dirty sparse-checkout init 2>err &&
354         test_i18ngrep "warning.*The following paths are not up to date" err &&
355
356         git -C dirty sparse-checkout set /folder2/* /deep/deeper1/* 2>err &&
357         test_i18ngrep "warning.*The following paths are not up to date" err &&
358         test_path_is_file dirty/folder1/a &&
359
360         git -C dirty sparse-checkout disable 2>err &&
361         test_must_be_empty err &&
362
363         git -C dirty reset --hard &&
364         git -C dirty sparse-checkout init &&
365         git -C dirty sparse-checkout set /folder2/* /deep/deeper1/* &&
366         test_path_is_missing dirty/folder1/a &&
367         git -C dirty sparse-checkout disable &&
368         test_path_is_file dirty/folder1/a
369 '
370
371 test_expect_success 'sparse-checkout (init|set|disable) warns with unmerged status' '
372         git clone repo unmerged &&
373
374         cat >input <<-EOF &&
375         0 $ZERO_OID     folder1/a
376         100644 $(git -C unmerged rev-parse HEAD:folder1/a) 1    folder1/a
377         EOF
378         git -C unmerged update-index --index-info <input &&
379
380         git -C unmerged sparse-checkout init 2>err &&
381         test_i18ngrep "warning.*The following paths are unmerged" err &&
382
383         git -C unmerged sparse-checkout set /folder2/* /deep/deeper1/* 2>err &&
384         test_i18ngrep "warning.*The following paths are unmerged" err &&
385         test_path_is_file dirty/folder1/a &&
386
387         git -C unmerged sparse-checkout disable 2>err &&
388         test_i18ngrep "warning.*The following paths are unmerged" err &&
389
390         git -C unmerged reset --hard &&
391         git -C unmerged sparse-checkout init &&
392         git -C unmerged sparse-checkout set /folder2/* /deep/deeper1/* &&
393         git -C unmerged sparse-checkout disable
394 '
395
396 test_expect_success 'sparse-checkout reapply' '
397         git clone repo tweak &&
398
399         echo dirty >tweak/deep/deeper2/a &&
400
401         cat >input <<-EOF &&
402         0 $ZERO_OID     folder1/a
403         100644 $(git -C tweak rev-parse HEAD:folder1/a) 1       folder1/a
404         EOF
405         git -C tweak update-index --index-info <input &&
406
407         git -C tweak sparse-checkout init --cone 2>err &&
408         test_i18ngrep "warning.*The following paths are not up to date" err &&
409         test_i18ngrep "warning.*The following paths are unmerged" err &&
410
411         git -C tweak sparse-checkout set folder2 deep/deeper1 2>err &&
412         test_i18ngrep "warning.*The following paths are not up to date" err &&
413         test_i18ngrep "warning.*The following paths are unmerged" err &&
414
415         git -C tweak sparse-checkout reapply 2>err &&
416         test_i18ngrep "warning.*The following paths are not up to date" err &&
417         test_path_is_file tweak/deep/deeper2/a &&
418         test_i18ngrep "warning.*The following paths are unmerged" err &&
419         test_path_is_file tweak/folder1/a &&
420
421         git -C tweak checkout HEAD deep/deeper2/a &&
422         git -C tweak sparse-checkout reapply 2>err &&
423         test_i18ngrep ! "warning.*The following paths are not up to date" err &&
424         test_path_is_missing tweak/deep/deeper2/a &&
425         test_i18ngrep "warning.*The following paths are unmerged" err &&
426         test_path_is_file tweak/folder1/a &&
427
428         git -C tweak add folder1/a &&
429         git -C tweak sparse-checkout reapply 2>err &&
430         test_must_be_empty err &&
431         test_path_is_missing tweak/deep/deeper2/a &&
432         test_path_is_missing tweak/folder1/a &&
433
434         git -C tweak sparse-checkout disable
435 '
436
437 test_expect_success 'cone mode: set with core.ignoreCase=true' '
438         rm repo/.git/info/sparse-checkout &&
439         git -C repo sparse-checkout init --cone &&
440         git -C repo -c core.ignoreCase=true sparse-checkout set folder1 &&
441         cat >expect <<-\EOF &&
442         /*
443         !/*/
444         /folder1/
445         EOF
446         test_cmp expect repo/.git/info/sparse-checkout &&
447         check_files repo a folder1
448 '
449
450 test_expect_success 'interaction with submodules' '
451         git clone repo super &&
452         (
453                 cd super &&
454                 mkdir modules &&
455                 git submodule add ../repo modules/child &&
456                 git add . &&
457                 git commit -m "add submodule" &&
458                 git sparse-checkout init --cone &&
459                 git sparse-checkout set folder1
460         ) &&
461         check_files super a folder1 modules &&
462         check_files super/modules/child a deep folder1 folder2
463 '
464
465 test_expect_success 'different sparse-checkouts with worktrees' '
466         git -C repo worktree add --detach ../worktree &&
467         check_files worktree "a deep folder1 folder2" &&
468         git -C worktree sparse-checkout init --cone &&
469         git -C repo sparse-checkout set folder1 &&
470         git -C worktree sparse-checkout set deep/deeper1 &&
471         check_files repo a folder1 &&
472         check_files worktree a deep
473 '
474
475 test_expect_success 'set using filename keeps file on-disk' '
476         git -C repo sparse-checkout set a deep &&
477         cat >expect <<-\EOF &&
478         /*
479         !/*/
480         /a/
481         /deep/
482         EOF
483         test_cmp expect repo/.git/info/sparse-checkout &&
484         check_files repo a deep
485 '
486
487 check_read_tree_errors () {
488         REPO=$1
489         FILES=$2
490         ERRORS=$3
491         git -C $REPO -c core.sparseCheckoutCone=false read-tree -mu HEAD 2>err &&
492         test_must_be_empty err &&
493         check_files $REPO "$FILES" &&
494         git -C $REPO read-tree -mu HEAD 2>err &&
495         if test -z "$ERRORS"
496         then
497                 test_must_be_empty err
498         else
499                 test_i18ngrep "$ERRORS" err
500         fi &&
501         check_files $REPO $FILES
502 }
503
504 test_expect_success 'pattern-checks: /A/**' '
505         cat >repo/.git/info/sparse-checkout <<-\EOF &&
506         /*
507         !/*/
508         /folder1/**
509         EOF
510         check_read_tree_errors repo "a folder1" "disabling cone pattern matching"
511 '
512
513 test_expect_success 'pattern-checks: /A/**/B/' '
514         cat >repo/.git/info/sparse-checkout <<-\EOF &&
515         /*
516         !/*/
517         /deep/**/deepest
518         EOF
519         check_read_tree_errors repo "a deep" "disabling cone pattern matching" &&
520         check_files repo/deep "deeper1" &&
521         check_files repo/deep/deeper1 "deepest"
522 '
523
524 test_expect_success 'pattern-checks: too short' '
525         cat >repo/.git/info/sparse-checkout <<-\EOF &&
526         /*
527         !/*/
528         /
529         EOF
530         check_read_tree_errors repo "a" "disabling cone pattern matching"
531 '
532 test_expect_success 'pattern-checks: not too short' '
533         cat >repo/.git/info/sparse-checkout <<-\EOF &&
534         /*
535         !/*/
536         /b/
537         EOF
538         git -C repo read-tree -mu HEAD 2>err &&
539         test_must_be_empty err &&
540         check_files repo a
541 '
542
543 test_expect_success 'pattern-checks: trailing "*"' '
544         cat >repo/.git/info/sparse-checkout <<-\EOF &&
545         /*
546         !/*/
547         /a*
548         EOF
549         check_read_tree_errors repo "a" "disabling cone pattern matching"
550 '
551
552 test_expect_success 'pattern-checks: starting "*"' '
553         cat >repo/.git/info/sparse-checkout <<-\EOF &&
554         /*
555         !/*/
556         *eep/
557         EOF
558         check_read_tree_errors repo "a deep" "disabling cone pattern matching"
559 '
560
561 test_expect_success 'pattern-checks: contained glob characters' '
562         for c in "[a]" "\\" "?" "*"
563         do
564                 cat >repo/.git/info/sparse-checkout <<-EOF &&
565                 /*
566                 !/*/
567                 something$c-else/
568                 EOF
569                 check_read_tree_errors repo "a" "disabling cone pattern matching"
570         done
571 '
572
573 test_expect_success BSLASHPSPEC 'pattern-checks: escaped characters' '
574         git clone repo escaped &&
575         TREEOID=$(git -C escaped rev-parse HEAD:folder1) &&
576         NEWTREE=$(git -C escaped mktree <<-EOF
577         $(git -C escaped ls-tree HEAD)
578         040000 tree $TREEOID    zbad\\dir
579         040000 tree $TREEOID    zdoes*exist
580         040000 tree $TREEOID    zglob[!a]?
581         EOF
582         ) &&
583         COMMIT=$(git -C escaped commit-tree $NEWTREE -p HEAD) &&
584         git -C escaped reset --hard $COMMIT &&
585         check_files escaped "a deep folder1 folder2 zbad\\dir zdoes*exist" zglob[!a]? &&
586         git -C escaped sparse-checkout init --cone &&
587         git -C escaped sparse-checkout set zbad\\dir/bogus "zdoes*not*exist" "zdoes*exist" "zglob[!a]?" &&
588         cat >expect <<-\EOF &&
589         /*
590         !/*/
591         /zbad\\dir/
592         !/zbad\\dir/*/
593         /zbad\\dir/bogus/
594         /zdoes\*exist/
595         /zdoes\*not\*exist/
596         /zglob\[!a]\?/
597         EOF
598         test_cmp expect escaped/.git/info/sparse-checkout &&
599         check_read_tree_errors escaped "a zbad\\dir zdoes*exist zglob[!a]?" &&
600         git -C escaped ls-tree -d --name-only HEAD >list-expect &&
601         git -C escaped sparse-checkout set --stdin <list-expect &&
602         cat >expect <<-\EOF &&
603         /*
604         !/*/
605         /deep/
606         /folder1/
607         /folder2/
608         /zbad\\dir/
609         /zdoes\*exist/
610         /zglob\[!a]\?/
611         EOF
612         test_cmp expect escaped/.git/info/sparse-checkout &&
613         check_files escaped "a deep folder1 folder2 zbad\\dir zdoes*exist" zglob[!a]? &&
614         git -C escaped sparse-checkout list >list-actual &&
615         test_cmp list-expect list-actual
616 '
617
618 test_expect_success MINGW 'cone mode replaces backslashes with slashes' '
619         git -C repo sparse-checkout set deep\\deeper1 &&
620         cat >expect <<-\EOF &&
621         /*
622         !/*/
623         /deep/
624         !/deep/*/
625         /deep/deeper1/
626         EOF
627         test_cmp expect repo/.git/info/sparse-checkout &&
628         check_files repo a deep &&
629         check_files repo/deep a deeper1
630 '
631
632 test_done