]> git.ipfire.org Git - thirdparty/git.git/blob - t/t1092-sparse-checkout-compatibility.sh
Sync with 2.35.5
[thirdparty/git.git] / t / t1092-sparse-checkout-compatibility.sh
1 #!/bin/sh
2
3 test_description='compare full workdir to sparse workdir'
4
5 GIT_TEST_SPLIT_INDEX=0
6 GIT_TEST_SPARSE_INDEX=
7
8 . ./test-lib.sh
9
10 test_expect_success 'setup' '
11 git init initial-repo &&
12 (
13 GIT_TEST_SPARSE_INDEX=0 &&
14 cd initial-repo &&
15 echo a >a &&
16 echo "after deep" >e &&
17 echo "after folder1" >g &&
18 echo "after x" >z &&
19 mkdir folder1 folder2 deep before x &&
20 echo "before deep" >before/a &&
21 echo "before deep again" >before/b &&
22 mkdir deep/deeper1 deep/deeper2 deep/before deep/later &&
23 mkdir deep/deeper1/deepest &&
24 mkdir deep/deeper1/deepest2 &&
25 mkdir deep/deeper1/deepest3 &&
26 echo "after deeper1" >deep/e &&
27 echo "after deepest" >deep/deeper1/e &&
28 cp a folder1 &&
29 cp a folder2 &&
30 cp a x &&
31 cp a deep &&
32 cp a deep/before &&
33 cp a deep/deeper1 &&
34 cp a deep/deeper2 &&
35 cp a deep/later &&
36 cp a deep/deeper1/deepest &&
37 cp a deep/deeper1/deepest2 &&
38 cp a deep/deeper1/deepest3 &&
39 cp -r deep/deeper1/ deep/deeper2 &&
40 mkdir deep/deeper1/0 &&
41 mkdir deep/deeper1/0/0 &&
42 touch deep/deeper1/0/1 &&
43 touch deep/deeper1/0/0/0 &&
44 >folder1- &&
45 >folder1.x &&
46 >folder10 &&
47 cp -r deep/deeper1/0 folder1 &&
48 cp -r deep/deeper1/0 folder2 &&
49 echo >>folder1/0/0/0 &&
50 echo >>folder2/0/1 &&
51 git add . &&
52 git commit -m "initial commit" &&
53 git checkout -b base &&
54 for dir in folder1 folder2 deep
55 do
56 git checkout -b update-$dir base &&
57 echo "updated $dir" >$dir/a &&
58 git commit -a -m "update $dir" || return 1
59 done &&
60
61 git checkout -b rename-base base &&
62 cat >folder1/larger-content <<-\EOF &&
63 matching
64 lines
65 help
66 inexact
67 renames
68 EOF
69 cp folder1/larger-content folder2/ &&
70 cp folder1/larger-content deep/deeper1/ &&
71 git add . &&
72 git commit -m "add interesting rename content" &&
73
74 git checkout -b rename-out-to-out rename-base &&
75 mv folder1/a folder2/b &&
76 mv folder1/larger-content folder2/edited-content &&
77 echo >>folder2/edited-content &&
78 echo >>folder2/0/1 &&
79 echo stuff >>deep/deeper1/a &&
80 git add . &&
81 git commit -m "rename folder1/... to folder2/..." &&
82
83 git checkout -b rename-out-to-in rename-base &&
84 mv folder1/a deep/deeper1/b &&
85 echo more stuff >>deep/deeper1/a &&
86 rm folder2/0/1 &&
87 mkdir folder2/0/1 &&
88 echo >>folder2/0/1/1 &&
89 mv folder1/larger-content deep/deeper1/edited-content &&
90 echo >>deep/deeper1/edited-content &&
91 git add . &&
92 git commit -m "rename folder1/... to deep/deeper1/..." &&
93
94 git checkout -b rename-in-to-out rename-base &&
95 mv deep/deeper1/a folder1/b &&
96 echo >>folder2/0/1 &&
97 rm -rf folder1/0/0 &&
98 echo >>folder1/0/0 &&
99 mv deep/deeper1/larger-content folder1/edited-content &&
100 echo >>folder1/edited-content &&
101 git add . &&
102 git commit -m "rename deep/deeper1/... to folder1/..." &&
103
104 git checkout -b df-conflict-1 base &&
105 rm -rf folder1 &&
106 echo content >folder1 &&
107 git add . &&
108 git commit -m "dir to file" &&
109
110 git checkout -b df-conflict-2 base &&
111 rm -rf folder2 &&
112 echo content >folder2 &&
113 git add . &&
114 git commit -m "dir to file" &&
115
116 git checkout -b fd-conflict base &&
117 rm a &&
118 mkdir a &&
119 echo content >a/a &&
120 git add . &&
121 git commit -m "file to dir" &&
122
123 for side in left right
124 do
125 git checkout -b merge-$side base &&
126 echo $side >>deep/deeper2/a &&
127 echo $side >>folder1/a &&
128 echo $side >>folder2/a &&
129 git add . &&
130 git commit -m "$side" || return 1
131 done &&
132
133 git checkout -b deepest base &&
134 echo "updated deepest" >deep/deeper1/deepest/a &&
135 echo "updated deepest2" >deep/deeper1/deepest2/a &&
136 echo "updated deepest3" >deep/deeper1/deepest3/a &&
137 git commit -a -m "update deepest" &&
138
139 git checkout -f base &&
140 git reset --hard
141 )
142 '
143
144 init_repos () {
145 rm -rf full-checkout sparse-checkout sparse-index &&
146
147 # create repos in initial state
148 cp -r initial-repo full-checkout &&
149 git -C full-checkout reset --hard &&
150
151 cp -r initial-repo sparse-checkout &&
152 git -C sparse-checkout reset --hard &&
153
154 cp -r initial-repo sparse-index &&
155 git -C sparse-index reset --hard &&
156
157 # initialize sparse-checkout definitions
158 git -C sparse-checkout sparse-checkout init --cone &&
159 git -C sparse-checkout sparse-checkout set deep &&
160 git -C sparse-index sparse-checkout init --cone --sparse-index &&
161 test_cmp_config -C sparse-index true index.sparse &&
162 git -C sparse-index sparse-checkout set deep
163 }
164
165 run_on_sparse () {
166 (
167 cd sparse-checkout &&
168 GIT_PROGRESS_DELAY=100000 "$@" >../sparse-checkout-out 2>../sparse-checkout-err
169 ) &&
170 (
171 cd sparse-index &&
172 GIT_PROGRESS_DELAY=100000 "$@" >../sparse-index-out 2>../sparse-index-err
173 )
174 }
175
176 run_on_all () {
177 (
178 cd full-checkout &&
179 GIT_PROGRESS_DELAY=100000 "$@" >../full-checkout-out 2>../full-checkout-err
180 ) &&
181 run_on_sparse "$@"
182 }
183
184 test_all_match () {
185 run_on_all "$@" &&
186 test_cmp full-checkout-out sparse-checkout-out &&
187 test_cmp full-checkout-out sparse-index-out &&
188 test_cmp full-checkout-err sparse-checkout-err &&
189 test_cmp full-checkout-err sparse-index-err
190 }
191
192 test_sparse_match () {
193 run_on_sparse "$@" &&
194 test_cmp sparse-checkout-out sparse-index-out &&
195 test_cmp sparse-checkout-err sparse-index-err
196 }
197
198 test_sparse_unstaged () {
199 file=$1 &&
200 for repo in sparse-checkout sparse-index
201 do
202 # Skip "unmerged" paths
203 git -C $repo diff --staged --diff-filter=u -- "$file" >diff &&
204 test_must_be_empty diff || return 1
205 done
206 }
207
208 test_expect_success 'sparse-index contents' '
209 init_repos &&
210
211 git -C sparse-index ls-files --sparse --stage >cache &&
212 for dir in folder1 folder2 x
213 do
214 TREE=$(git -C sparse-index rev-parse HEAD:$dir) &&
215 grep "040000 $TREE 0 $dir/" cache \
216 || return 1
217 done &&
218
219 git -C sparse-index sparse-checkout set folder1 &&
220
221 git -C sparse-index ls-files --sparse --stage >cache &&
222 for dir in deep folder2 x
223 do
224 TREE=$(git -C sparse-index rev-parse HEAD:$dir) &&
225 grep "040000 $TREE 0 $dir/" cache \
226 || return 1
227 done &&
228
229 git -C sparse-index sparse-checkout set deep/deeper1 &&
230
231 git -C sparse-index ls-files --sparse --stage >cache &&
232 for dir in deep/deeper2 folder1 folder2 x
233 do
234 TREE=$(git -C sparse-index rev-parse HEAD:$dir) &&
235 grep "040000 $TREE 0 $dir/" cache \
236 || return 1
237 done &&
238
239 # Disabling the sparse-index replaces tree entries with full ones
240 git -C sparse-index sparse-checkout init --no-sparse-index &&
241 test_sparse_match git ls-files --stage --sparse
242 '
243
244 test_expect_success 'expanded in-memory index matches full index' '
245 init_repos &&
246 test_sparse_match git ls-files --stage
247 '
248
249 test_expect_success 'root directory cannot be sparse' '
250 init_repos &&
251
252 # Remove all in-cone files and directories from the index, collapse index
253 # with `git sparse-checkout reapply`
254 git -C sparse-index rm -r . &&
255 git -C sparse-index sparse-checkout reapply &&
256
257 # Verify sparse directories still present, root directory is not sparse
258 cat >expect <<-EOF &&
259 before/
260 folder1/
261 folder2/
262 x/
263 EOF
264 git -C sparse-index ls-files --sparse >actual &&
265 test_cmp expect actual
266 '
267
268 test_expect_success 'status with options' '
269 init_repos &&
270 test_sparse_match ls &&
271 test_all_match git status --porcelain=v2 &&
272 test_all_match git status --porcelain=v2 -z -u &&
273 test_all_match git status --porcelain=v2 -uno &&
274 run_on_all touch README.md &&
275 test_all_match git status --porcelain=v2 &&
276 test_all_match git status --porcelain=v2 -z -u &&
277 test_all_match git status --porcelain=v2 -uno &&
278 test_all_match git add README.md &&
279 test_all_match git status --porcelain=v2 &&
280 test_all_match git status --porcelain=v2 -z -u &&
281 test_all_match git status --porcelain=v2 -uno
282 '
283
284 test_expect_success 'status with diff in unexpanded sparse directory' '
285 init_repos &&
286 test_all_match git checkout rename-base &&
287 test_all_match git reset --soft rename-out-to-out &&
288 test_all_match git status --porcelain=v2
289 '
290
291 test_expect_success 'status reports sparse-checkout' '
292 init_repos &&
293 git -C sparse-checkout status >full &&
294 git -C sparse-index status >sparse &&
295 test_i18ngrep "You are in a sparse checkout with " full &&
296 test_i18ngrep "You are in a sparse checkout." sparse
297 '
298
299 test_expect_success 'add, commit, checkout' '
300 init_repos &&
301
302 write_script edit-contents <<-\EOF &&
303 echo text >>$1
304 EOF
305 run_on_all ../edit-contents README.md &&
306
307 test_all_match git add README.md &&
308 test_all_match git status --porcelain=v2 &&
309 test_all_match git commit -m "Add README.md" &&
310
311 test_all_match git checkout HEAD~1 &&
312 test_all_match git checkout - &&
313
314 run_on_all ../edit-contents README.md &&
315
316 test_all_match git add -A &&
317 test_all_match git status --porcelain=v2 &&
318 test_all_match git commit -m "Extend README.md" &&
319
320 test_all_match git checkout HEAD~1 &&
321 test_all_match git checkout - &&
322
323 run_on_all ../edit-contents deep/newfile &&
324
325 test_all_match git status --porcelain=v2 -uno &&
326 test_all_match git status --porcelain=v2 &&
327 test_all_match git add . &&
328 test_all_match git status --porcelain=v2 &&
329 test_all_match git commit -m "add deep/newfile" &&
330
331 test_all_match git checkout HEAD~1 &&
332 test_all_match git checkout -
333 '
334
335 test_expect_success 'deep changes during checkout' '
336 init_repos &&
337
338 test_sparse_match git sparse-checkout set deep/deeper1/deepest &&
339 test_all_match git checkout deepest &&
340 test_all_match git checkout base
341 '
342
343 test_expect_success 'add outside sparse cone' '
344 init_repos &&
345
346 run_on_sparse mkdir folder1 &&
347 run_on_sparse ../edit-contents folder1/a &&
348 run_on_sparse ../edit-contents folder1/newfile &&
349 test_sparse_match test_must_fail git add folder1/a &&
350 grep "Disable or modify the sparsity rules" sparse-checkout-err &&
351 test_sparse_unstaged folder1/a &&
352 test_sparse_match test_must_fail git add folder1/newfile &&
353 grep "Disable or modify the sparsity rules" sparse-checkout-err &&
354 test_sparse_unstaged folder1/newfile
355 '
356
357 test_expect_success 'commit including unstaged changes' '
358 init_repos &&
359
360 write_script edit-file <<-\EOF &&
361 echo $1 >$2
362 EOF
363
364 run_on_all ../edit-file 1 a &&
365 run_on_all ../edit-file 1 deep/a &&
366
367 test_all_match git commit -m "-a" -a &&
368 test_all_match git status --porcelain=v2 &&
369
370 run_on_all ../edit-file 2 a &&
371 run_on_all ../edit-file 2 deep/a &&
372
373 test_all_match git commit -m "--include" --include deep/a &&
374 test_all_match git status --porcelain=v2 &&
375 test_all_match git commit -m "--include" --include a &&
376 test_all_match git status --porcelain=v2 &&
377
378 run_on_all ../edit-file 3 a &&
379 run_on_all ../edit-file 3 deep/a &&
380
381 test_all_match git commit -m "--amend" -a --amend &&
382 test_all_match git status --porcelain=v2
383 '
384
385 test_expect_success 'status/add: outside sparse cone' '
386 init_repos &&
387
388 # folder1 is at HEAD, but outside the sparse cone
389 run_on_sparse mkdir folder1 &&
390 cp initial-repo/folder1/a sparse-checkout/folder1/a &&
391 cp initial-repo/folder1/a sparse-index/folder1/a &&
392
393 test_sparse_match git status &&
394
395 write_script edit-contents <<-\EOF &&
396 echo text >>$1
397 EOF
398 run_on_all ../edit-contents folder1/a &&
399 run_on_all ../edit-contents folder1/new &&
400
401 test_sparse_match git status --porcelain=v2 &&
402
403 # Adding the path outside of the sparse-checkout cone should fail.
404 test_sparse_match test_must_fail git add folder1/a &&
405 grep "Disable or modify the sparsity rules" sparse-checkout-err &&
406 test_sparse_unstaged folder1/a &&
407 test_all_match git add --refresh folder1/a &&
408 test_must_be_empty sparse-checkout-err &&
409 test_sparse_unstaged folder1/a &&
410 test_sparse_match test_must_fail git add folder1/new &&
411 grep "Disable or modify the sparsity rules" sparse-checkout-err &&
412 test_sparse_unstaged folder1/new &&
413 test_sparse_match git add --sparse folder1/a &&
414 test_sparse_match git add --sparse folder1/new &&
415
416 test_all_match git add --sparse . &&
417 test_all_match git status --porcelain=v2 &&
418 test_all_match git commit -m folder1/new &&
419 test_all_match git rev-parse HEAD^{tree} &&
420
421 run_on_all ../edit-contents folder1/newer &&
422 test_all_match git add --sparse folder1/ &&
423 test_all_match git status --porcelain=v2 &&
424 test_all_match git commit -m folder1/newer &&
425 test_all_match git rev-parse HEAD^{tree}
426 '
427
428 test_expect_success 'checkout and reset --hard' '
429 init_repos &&
430
431 test_all_match git checkout update-folder1 &&
432 test_all_match git status --porcelain=v2 &&
433
434 test_all_match git checkout update-deep &&
435 test_all_match git status --porcelain=v2 &&
436
437 test_all_match git checkout -b reset-test &&
438 test_all_match git reset --hard deepest &&
439 test_all_match git reset --hard update-folder1 &&
440 test_all_match git reset --hard update-folder2
441 '
442
443 test_expect_success 'diff --cached' '
444 init_repos &&
445
446 write_script edit-contents <<-\EOF &&
447 echo text >>README.md
448 EOF
449 run_on_all ../edit-contents &&
450
451 test_all_match git diff &&
452 test_all_match git diff --cached &&
453 test_all_match git add README.md &&
454 test_all_match git diff &&
455 test_all_match git diff --cached
456 '
457
458 # NEEDSWORK: sparse-checkout behaves differently from full-checkout when
459 # running this test with 'df-conflict-2' after 'df-conflict-1'.
460 test_expect_success 'diff with renames and conflicts' '
461 init_repos &&
462
463 for branch in rename-out-to-out \
464 rename-out-to-in \
465 rename-in-to-out \
466 df-conflict-1 \
467 fd-conflict
468 do
469 test_all_match git checkout rename-base &&
470 test_all_match git checkout $branch -- . &&
471 test_all_match git status --porcelain=v2 &&
472 test_all_match git diff --cached --no-renames &&
473 test_all_match git diff --cached --find-renames || return 1
474 done
475 '
476
477 test_expect_success 'diff with directory/file conflicts' '
478 init_repos &&
479
480 for branch in rename-out-to-out \
481 rename-out-to-in \
482 rename-in-to-out \
483 df-conflict-1 \
484 df-conflict-2 \
485 fd-conflict
486 do
487 git -C full-checkout reset --hard &&
488 test_sparse_match git reset --hard &&
489 test_all_match git checkout $branch &&
490 test_all_match git checkout rename-base -- . &&
491 test_all_match git status --porcelain=v2 &&
492 test_all_match git diff --cached --no-renames &&
493 test_all_match git diff --cached --find-renames || return 1
494 done
495 '
496
497 test_expect_success 'log with pathspec outside sparse definition' '
498 init_repos &&
499
500 test_all_match git log -- a &&
501 test_all_match git log -- folder1/a &&
502 test_all_match git log -- folder2/a &&
503 test_all_match git log -- deep/a &&
504 test_all_match git log -- deep/deeper1/a &&
505 test_all_match git log -- deep/deeper1/deepest/a &&
506
507 test_all_match git checkout update-folder1 &&
508 test_all_match git log -- folder1/a
509 '
510
511 test_expect_success 'blame with pathspec inside sparse definition' '
512 init_repos &&
513
514 for file in a \
515 deep/a \
516 deep/deeper1/a \
517 deep/deeper1/deepest/a
518 do
519 test_all_match git blame $file
520 done
521 '
522
523 # Without a revision specified, blame will error if passed any file that
524 # is not present in the working directory (even if the file is tracked).
525 # Here we just verify that this is also true with sparse checkouts.
526 test_expect_success 'blame with pathspec outside sparse definition' '
527 init_repos &&
528 test_sparse_match git sparse-checkout set &&
529
530 for file in a \
531 deep/a \
532 deep/deeper1/a \
533 deep/deeper1/deepest/a
534 do
535 test_sparse_match test_must_fail git blame $file &&
536 cat >expect <<-EOF &&
537 fatal: Cannot lstat '"'"'$file'"'"': No such file or directory
538 EOF
539 # We compare sparse-checkout-err and sparse-index-err in
540 # `test_sparse_match`. Given we know they are the same, we
541 # only check the content of sparse-index-err here.
542 test_cmp expect sparse-index-err
543 done
544 '
545
546 test_expect_success 'checkout and reset (mixed)' '
547 init_repos &&
548
549 test_all_match git checkout -b reset-test update-deep &&
550 test_all_match git reset deepest &&
551
552 # Because skip-worktree is preserved, resetting to update-folder1
553 # will show worktree changes for folder1/a in full-checkout, but not
554 # in sparse-checkout or sparse-index.
555 git -C full-checkout reset update-folder1 >full-checkout-out &&
556 test_sparse_match git reset update-folder1 &&
557 grep "M folder1/a" full-checkout-out &&
558 ! grep "M folder1/a" sparse-checkout-out &&
559 run_on_sparse test_path_is_missing folder1
560 '
561
562 test_expect_success 'checkout and reset (merge)' '
563 init_repos &&
564
565 write_script edit-contents <<-\EOF &&
566 echo text >>$1
567 EOF
568
569 test_all_match git checkout -b reset-test update-deep &&
570 run_on_all ../edit-contents a &&
571 test_all_match git reset --merge deepest &&
572 test_all_match git status --porcelain=v2 &&
573
574 test_all_match git reset --hard update-deep &&
575 run_on_all ../edit-contents deep/a &&
576 test_all_match test_must_fail git reset --merge deepest
577 '
578
579 test_expect_success 'checkout and reset (keep)' '
580 init_repos &&
581
582 write_script edit-contents <<-\EOF &&
583 echo text >>$1
584 EOF
585
586 test_all_match git checkout -b reset-test update-deep &&
587 run_on_all ../edit-contents a &&
588 test_all_match git reset --keep deepest &&
589 test_all_match git status --porcelain=v2 &&
590
591 test_all_match git reset --hard update-deep &&
592 run_on_all ../edit-contents deep/a &&
593 test_all_match test_must_fail git reset --keep deepest
594 '
595
596 test_expect_success 'reset with pathspecs inside sparse definition' '
597 init_repos &&
598
599 write_script edit-contents <<-\EOF &&
600 echo text >>$1
601 EOF
602
603 test_all_match git checkout -b reset-test update-deep &&
604 run_on_all ../edit-contents deep/a &&
605
606 test_all_match git reset base -- deep/a &&
607 test_all_match git status --porcelain=v2 &&
608
609 test_all_match git reset base -- nonexistent-file &&
610 test_all_match git status --porcelain=v2 &&
611
612 test_all_match git reset deepest -- deep &&
613 test_all_match git status --porcelain=v2
614 '
615
616 # Although the working tree differs between full and sparse checkouts after
617 # reset, the state of the index is the same.
618 test_expect_success 'reset with pathspecs outside sparse definition' '
619 init_repos &&
620 test_all_match git checkout -b reset-test base &&
621
622 test_sparse_match git reset update-folder1 -- folder1 &&
623 git -C full-checkout reset update-folder1 -- folder1 &&
624 test_all_match git ls-files -s -- folder1 &&
625
626 test_sparse_match git reset update-folder2 -- folder2/a &&
627 git -C full-checkout reset update-folder2 -- folder2/a &&
628 test_all_match git ls-files -s -- folder2/a
629 '
630
631 test_expect_success 'reset with wildcard pathspec' '
632 init_repos &&
633
634 test_all_match git reset update-deep -- deep\* &&
635 test_all_match git ls-files -s -- deep &&
636
637 test_all_match git reset deepest -- deep\*\*\* &&
638 test_all_match git ls-files -s -- deep &&
639
640 # The following `git reset`s result in updating the index on files with
641 # `skip-worktree` enabled. To avoid failing due to discrepencies in reported
642 # "modified" files, `test_sparse_match` reset is performed separately from
643 # "full-checkout" reset, then the index contents of all repos are verified.
644
645 test_sparse_match git reset update-folder1 -- \*/a &&
646 git -C full-checkout reset update-folder1 -- \*/a &&
647 test_all_match git ls-files -s -- deep/a folder1/a &&
648
649 test_sparse_match git reset update-folder2 -- folder\* &&
650 git -C full-checkout reset update-folder2 -- folder\* &&
651 test_all_match git ls-files -s -- folder10 folder1 folder2 &&
652
653 test_sparse_match git reset base -- folder1/\* &&
654 git -C full-checkout reset base -- folder1/\* &&
655 test_all_match git ls-files -s -- folder1
656 '
657
658 test_expect_success 'update-index modify outside sparse definition' '
659 init_repos &&
660
661 write_script edit-contents <<-\EOF &&
662 echo text >>$1
663 EOF
664
665 # Create & modify folder1/a
666 # Note that this setup is a manual way of reaching the erroneous
667 # condition in which a `skip-worktree` enabled, outside-of-cone file
668 # exists on disk. It is used here to ensure `update-index` is stable
669 # and behaves predictably if such a condition occurs.
670 run_on_sparse mkdir -p folder1 &&
671 run_on_sparse cp ../initial-repo/folder1/a folder1/a &&
672 run_on_all ../edit-contents folder1/a &&
673
674 # If file has skip-worktree enabled, but the file is present, it is
675 # treated the same as if skip-worktree is disabled
676 test_all_match git status --porcelain=v2 &&
677 test_all_match git update-index folder1/a &&
678 test_all_match git status --porcelain=v2 &&
679
680 # When skip-worktree is disabled (even on files outside sparse cone), file
681 # is updated in the index
682 test_sparse_match git update-index --no-skip-worktree folder1/a &&
683 test_all_match git status --porcelain=v2 &&
684 test_all_match git update-index folder1/a &&
685 test_all_match git status --porcelain=v2
686 '
687
688 test_expect_success 'update-index --add outside sparse definition' '
689 init_repos &&
690
691 write_script edit-contents <<-\EOF &&
692 echo text >>$1
693 EOF
694
695 # Create folder1, add new file
696 run_on_sparse mkdir -p folder1 &&
697 run_on_all ../edit-contents folder1/b &&
698
699 # The *untracked* out-of-cone file is added to the index because it does
700 # not have a `skip-worktree` bit to signal that it should be ignored
701 # (unlike in `git add`, which will fail due to the file being outside
702 # the sparse checkout definition).
703 test_all_match git update-index --add folder1/b &&
704 test_all_match git status --porcelain=v2
705 '
706
707 # NEEDSWORK: `--remove`, unlike the rest of `update-index`, does not ignore
708 # `skip-worktree` entries by default and will remove them from the index.
709 # The `--ignore-skip-worktree-entries` flag must be used in conjunction with
710 # `--remove` to ignore the `skip-worktree` entries and prevent their removal
711 # from the index.
712 test_expect_success 'update-index --remove outside sparse definition' '
713 init_repos &&
714
715 # When --ignore-skip-worktree-entries is _not_ specified:
716 # out-of-cone, not-on-disk files are removed from the index
717 test_sparse_match git update-index --remove folder1/a &&
718 cat >expect <<-EOF &&
719 D folder1/a
720 EOF
721 test_sparse_match git diff --cached --name-status &&
722 test_cmp expect sparse-checkout-out &&
723
724 # Reset the state
725 test_all_match git reset --hard &&
726
727 # When --ignore-skip-worktree-entries is specified, out-of-cone
728 # (skip-worktree) files are ignored
729 test_sparse_match git update-index --remove --ignore-skip-worktree-entries folder1/a &&
730 test_sparse_match git diff --cached --name-status &&
731 test_must_be_empty sparse-checkout-out &&
732
733 # Reset the state
734 test_all_match git reset --hard &&
735
736 # --force-remove supercedes --ignore-skip-worktree-entries, removing
737 # a skip-worktree file from the index (and disk) when both are specified
738 # with --remove
739 test_sparse_match git update-index --force-remove --ignore-skip-worktree-entries folder1/a &&
740 cat >expect <<-EOF &&
741 D folder1/a
742 EOF
743 test_sparse_match git diff --cached --name-status &&
744 test_cmp expect sparse-checkout-out
745 '
746
747 test_expect_success 'update-index with directories' '
748 init_repos &&
749
750 # update-index will exit silently when provided with a directory name
751 # containing a trailing slash
752 test_all_match git update-index deep/ folder1/ &&
753 grep "Ignoring path deep/" sparse-checkout-err &&
754 grep "Ignoring path folder1/" sparse-checkout-err &&
755
756 # When update-index is given a directory name WITHOUT a trailing slash, it will
757 # behave in different ways depending on the status of the directory on disk:
758 # * if it exists, the command exits with an error ("add individual files instead")
759 # * if it does NOT exist (e.g., in a sparse-checkout), it is assumed to be a
760 # file and either triggers an error ("does not exist and --remove not passed")
761 # or is ignored completely (when using --remove)
762 test_all_match test_must_fail git update-index deep &&
763 run_on_all test_must_fail git update-index folder1 &&
764 test_must_fail git -C full-checkout update-index --remove folder1 &&
765 test_sparse_match git update-index --remove folder1 &&
766 test_all_match git status --porcelain=v2
767 '
768
769 test_expect_success 'update-index --again file outside sparse definition' '
770 init_repos &&
771
772 test_all_match git checkout -b test-reupdate &&
773
774 # Update HEAD without modifying the index to introduce a difference in
775 # folder1/a
776 test_sparse_match git reset --soft update-folder1 &&
777
778 # Because folder1/a differs in the index vs HEAD,
779 # `git update-index --no-skip-worktree --again` will effectively perform
780 # `git update-index --no-skip-worktree folder1/a` and remove the skip-worktree
781 # flag from folder1/a
782 test_sparse_match git update-index --no-skip-worktree --again &&
783 test_sparse_match git status --porcelain=v2 &&
784
785 cat >expect <<-EOF &&
786 D folder1/a
787 EOF
788 test_sparse_match git diff --name-status &&
789 test_cmp expect sparse-checkout-out
790 '
791
792 test_expect_success 'update-index --cacheinfo' '
793 init_repos &&
794
795 deep_a_oid=$(git -C full-checkout rev-parse update-deep:deep/a) &&
796 folder2_oid=$(git -C full-checkout rev-parse update-folder2:folder2) &&
797 folder1_a_oid=$(git -C full-checkout rev-parse update-folder1:folder1/a) &&
798
799 test_all_match git update-index --cacheinfo 100644 $deep_a_oid deep/a &&
800 test_all_match git status --porcelain=v2 &&
801
802 # Cannot add sparse directory, even in sparse index case
803 test_all_match test_must_fail git update-index --add --cacheinfo 040000 $folder2_oid folder2/ &&
804
805 # Sparse match only: the new outside-of-cone entry is added *without* skip-worktree,
806 # so `git status` reports it as "deleted" in the worktree
807 test_sparse_match git update-index --add --cacheinfo 100644 $folder1_a_oid folder1/a &&
808 test_sparse_match git status --porcelain=v2 &&
809 cat >expect <<-EOF &&
810 MD folder1/a
811 EOF
812 test_sparse_match git status --short -- folder1/a &&
813 test_cmp expect sparse-checkout-out &&
814
815 # To return folder1/a to "normal" for a sparse checkout (ignored &
816 # outside-of-cone), add the skip-worktree flag.
817 test_sparse_match git update-index --skip-worktree folder1/a &&
818 cat >expect <<-EOF &&
819 S folder1/a
820 EOF
821 test_sparse_match git ls-files -t -- folder1/a &&
822 test_cmp expect sparse-checkout-out
823 '
824
825 for MERGE_TREES in "base HEAD update-folder2" \
826 "update-folder1 update-folder2" \
827 "update-folder2"
828 do
829 test_expect_success "'read-tree -mu $MERGE_TREES' with files outside sparse definition" '
830 init_repos &&
831
832 # Although the index matches, without --no-sparse-checkout, outside-of-
833 # definition files will not exist on disk for sparse checkouts
834 test_all_match git read-tree -mu $MERGE_TREES &&
835 test_all_match git status --porcelain=v2 &&
836 test_path_is_missing sparse-checkout/folder2 &&
837 test_path_is_missing sparse-index/folder2 &&
838
839 test_all_match git read-tree --reset -u HEAD &&
840 test_all_match git status --porcelain=v2 &&
841
842 test_all_match git read-tree -mu --no-sparse-checkout $MERGE_TREES &&
843 test_all_match git status --porcelain=v2 &&
844 test_cmp sparse-checkout/folder2/a sparse-index/folder2/a &&
845 test_cmp sparse-checkout/folder2/a full-checkout/folder2/a
846
847 '
848 done
849
850 test_expect_success 'read-tree --merge with edit/edit conflicts in sparse directories' '
851 init_repos &&
852
853 # Merge of multiple changes to same directory (but not same files) should
854 # succeed
855 test_all_match git read-tree -mu base rename-base update-folder1 &&
856 test_all_match git status --porcelain=v2 &&
857
858 test_all_match git reset --hard &&
859
860 test_all_match git read-tree -mu rename-base update-folder2 &&
861 test_all_match git status --porcelain=v2 &&
862
863 test_all_match git reset --hard &&
864
865 test_all_match test_must_fail git read-tree -mu base update-folder1 rename-out-to-in &&
866 test_all_match test_must_fail git read-tree -mu rename-out-to-in update-folder1
867 '
868
869 test_expect_success 'read-tree --prefix' '
870 init_repos &&
871
872 # If files differing between the index and target <commit-ish> exist
873 # inside the prefix, `read-tree --prefix` should fail
874 test_all_match test_must_fail git read-tree --prefix=deep/ deepest &&
875 test_all_match test_must_fail git read-tree --prefix=folder1/ update-folder1 &&
876
877 # If no differing index entries exist matching the prefix,
878 # `read-tree --prefix` updates the index successfully
879 test_all_match git rm -rf deep/deeper1/deepest/ &&
880 test_all_match git read-tree --prefix=deep/deeper1/deepest -u deepest &&
881 test_all_match git status --porcelain=v2 &&
882
883 test_all_match git rm -rf --sparse folder1/ &&
884 test_all_match git read-tree --prefix=folder1/ -u update-folder1 &&
885 test_all_match git status --porcelain=v2 &&
886
887 test_all_match git rm -rf --sparse folder2/0 &&
888 test_all_match git read-tree --prefix=folder2/0/ -u rename-out-to-out &&
889 test_all_match git status --porcelain=v2
890 '
891
892 test_expect_success 'read-tree --merge with directory-file conflicts' '
893 init_repos &&
894
895 test_all_match git checkout -b test-branch rename-base &&
896
897 # Although the index matches, without --no-sparse-checkout, outside-of-
898 # definition files will not exist on disk for sparse checkouts
899 test_sparse_match git read-tree -mu rename-out-to-out &&
900 test_sparse_match git status --porcelain=v2 &&
901 test_path_is_missing sparse-checkout/folder2 &&
902 test_path_is_missing sparse-index/folder2 &&
903
904 test_sparse_match git read-tree --reset -u HEAD &&
905 test_sparse_match git status --porcelain=v2 &&
906
907 test_sparse_match git read-tree -mu --no-sparse-checkout rename-out-to-out &&
908 test_sparse_match git status --porcelain=v2 &&
909 test_cmp sparse-checkout/folder2/0/1 sparse-index/folder2/0/1
910 '
911
912 test_expect_success 'merge, cherry-pick, and rebase' '
913 init_repos &&
914
915 for OPERATION in "merge -m merge" cherry-pick "rebase --apply" "rebase --merge"
916 do
917 test_all_match git checkout -B temp update-deep &&
918 test_all_match git $OPERATION update-folder1 &&
919 test_all_match git rev-parse HEAD^{tree} &&
920 test_all_match git $OPERATION update-folder2 &&
921 test_all_match git rev-parse HEAD^{tree} || return 1
922 done
923 '
924
925 test_expect_success 'merge with conflict outside cone' '
926 init_repos &&
927
928 test_all_match git checkout -b merge-tip merge-left &&
929 test_all_match git status --porcelain=v2 &&
930 test_all_match test_must_fail git merge -m merge merge-right &&
931 test_all_match git status --porcelain=v2 &&
932
933 # Resolve the conflict in different ways:
934 # 1. Revert to the base
935 test_all_match git checkout base -- deep/deeper2/a &&
936 test_all_match git status --porcelain=v2 &&
937
938 # 2. Add the file with conflict markers
939 test_sparse_match test_must_fail git add folder1/a &&
940 grep "Disable or modify the sparsity rules" sparse-checkout-err &&
941 test_sparse_unstaged folder1/a &&
942 test_all_match git add --sparse folder1/a &&
943 test_all_match git status --porcelain=v2 &&
944
945 # 3. Rename the file to another sparse filename and
946 # accept conflict markers as resolved content.
947 run_on_all mv folder2/a folder2/z &&
948 test_sparse_match test_must_fail git add folder2 &&
949 grep "Disable or modify the sparsity rules" sparse-checkout-err &&
950 test_sparse_unstaged folder2/z &&
951 test_all_match git add --sparse folder2 &&
952 test_all_match git status --porcelain=v2 &&
953
954 test_all_match git merge --continue &&
955 test_all_match git status --porcelain=v2 &&
956 test_all_match git rev-parse HEAD^{tree}
957 '
958
959 test_expect_success 'cherry-pick/rebase with conflict outside cone' '
960 init_repos &&
961
962 for OPERATION in cherry-pick rebase
963 do
964 test_all_match git checkout -B tip &&
965 test_all_match git reset --hard merge-left &&
966 test_all_match git status --porcelain=v2 &&
967 test_all_match test_must_fail git $OPERATION merge-right &&
968 test_all_match git status --porcelain=v2 &&
969
970 # Resolve the conflict in different ways:
971 # 1. Revert to the base
972 test_all_match git checkout base -- deep/deeper2/a &&
973 test_all_match git status --porcelain=v2 &&
974
975 # 2. Add the file with conflict markers
976 # NEEDSWORK: Even though the merge conflict removed the
977 # SKIP_WORKTREE bit from the index entry for folder1/a, we should
978 # warn that this is a problematic add.
979 test_sparse_match test_must_fail git add folder1/a &&
980 grep "Disable or modify the sparsity rules" sparse-checkout-err &&
981 test_sparse_unstaged folder1/a &&
982 test_all_match git add --sparse folder1/a &&
983 test_all_match git status --porcelain=v2 &&
984
985 # 3. Rename the file to another sparse filename and
986 # accept conflict markers as resolved content.
987 # NEEDSWORK: This mode now fails, because folder2/z is
988 # outside of the sparse-checkout cone and does not match an
989 # existing index entry with the SKIP_WORKTREE bit cleared.
990 run_on_all mv folder2/a folder2/z &&
991 test_sparse_match test_must_fail git add folder2 &&
992 grep "Disable or modify the sparsity rules" sparse-checkout-err &&
993 test_sparse_unstaged folder2/z &&
994 test_all_match git add --sparse folder2 &&
995 test_all_match git status --porcelain=v2 &&
996
997 test_all_match git $OPERATION --continue &&
998 test_all_match git status --porcelain=v2 &&
999 test_all_match git rev-parse HEAD^{tree} || return 1
1000 done
1001 '
1002
1003 test_expect_success 'merge with outside renames' '
1004 init_repos &&
1005
1006 for type in out-to-out out-to-in in-to-out
1007 do
1008 test_all_match git reset --hard &&
1009 test_all_match git checkout -f -b merge-$type update-deep &&
1010 test_all_match git merge -m "$type" rename-$type &&
1011 test_all_match git rev-parse HEAD^{tree} || return 1
1012 done
1013 '
1014
1015 # Sparse-index fails to convert the index in the
1016 # final 'git cherry-pick' command.
1017 test_expect_success 'cherry-pick with conflicts' '
1018 init_repos &&
1019
1020 write_script edit-conflict <<-\EOF &&
1021 echo $1 >conflict
1022 EOF
1023
1024 test_all_match git checkout -b to-cherry-pick &&
1025 run_on_all ../edit-conflict ABC &&
1026 test_all_match git add conflict &&
1027 test_all_match git commit -m "conflict to pick" &&
1028
1029 test_all_match git checkout -B base HEAD~1 &&
1030 run_on_all ../edit-conflict DEF &&
1031 test_all_match git add conflict &&
1032 test_all_match git commit -m "conflict in base" &&
1033
1034 test_all_match test_must_fail git cherry-pick to-cherry-pick
1035 '
1036
1037 test_expect_success 'checkout-index inside sparse definition' '
1038 init_repos &&
1039
1040 run_on_all rm -f deep/a &&
1041 test_all_match git checkout-index -- deep/a &&
1042 test_all_match git status --porcelain=v2 &&
1043
1044 echo test >>new-a &&
1045 run_on_all cp ../new-a a &&
1046 test_all_match test_must_fail git checkout-index -- a &&
1047 test_all_match git checkout-index -f -- a &&
1048 test_all_match git status --porcelain=v2
1049 '
1050
1051 test_expect_success 'checkout-index outside sparse definition' '
1052 init_repos &&
1053
1054 # Without --ignore-skip-worktree-bits, outside-of-cone files will trigger
1055 # an error
1056 test_sparse_match test_must_fail git checkout-index -- folder1/a &&
1057 test_i18ngrep "folder1/a has skip-worktree enabled" sparse-checkout-err &&
1058 test_path_is_missing folder1/a &&
1059
1060 # With --ignore-skip-worktree-bits, outside-of-cone files are checked out
1061 test_sparse_match git checkout-index --ignore-skip-worktree-bits -- folder1/a &&
1062 test_cmp sparse-checkout/folder1/a sparse-index/folder1/a &&
1063 test_cmp sparse-checkout/folder1/a full-checkout/folder1/a &&
1064
1065 run_on_sparse rm -rf folder1 &&
1066 echo test >new-a &&
1067 run_on_sparse mkdir -p folder1 &&
1068 run_on_all cp ../new-a folder1/a &&
1069
1070 test_all_match test_must_fail git checkout-index --ignore-skip-worktree-bits -- folder1/a &&
1071 test_all_match git checkout-index -f --ignore-skip-worktree-bits -- folder1/a &&
1072 test_cmp sparse-checkout/folder1/a sparse-index/folder1/a &&
1073 test_cmp sparse-checkout/folder1/a full-checkout/folder1/a
1074 '
1075
1076 test_expect_success 'checkout-index with folders' '
1077 init_repos &&
1078
1079 # Inside checkout definition
1080 test_all_match test_must_fail git checkout-index -f -- deep/ &&
1081
1082 # Outside checkout definition
1083 # Note: although all tests fail (as expected), the messaging differs. For
1084 # non-sparse index checkouts, the error is that the "file" does not appear
1085 # in the index; for sparse checkouts, the error is explicitly that the
1086 # entry is a sparse directory.
1087 run_on_all test_must_fail git checkout-index -f -- folder1/ &&
1088 test_cmp full-checkout-err sparse-checkout-err &&
1089 ! test_cmp full-checkout-err sparse-index-err &&
1090 grep "is a sparse directory" sparse-index-err
1091 '
1092
1093 test_expect_success 'checkout-index --all' '
1094 init_repos &&
1095
1096 test_all_match git checkout-index --all &&
1097 test_sparse_match test_path_is_missing folder1 &&
1098
1099 # --ignore-skip-worktree-bits will cause `skip-worktree` files to be
1100 # checked out, causing the outside-of-cone `folder1` to exist on-disk
1101 test_all_match git checkout-index --ignore-skip-worktree-bits --all &&
1102 test_all_match test_path_exists folder1
1103 '
1104
1105 test_expect_success 'clean' '
1106 init_repos &&
1107
1108 echo bogus >>.gitignore &&
1109 run_on_all cp ../.gitignore . &&
1110 test_all_match git add .gitignore &&
1111 test_all_match git commit -m "ignore bogus files" &&
1112
1113 run_on_sparse mkdir folder1 &&
1114 run_on_all mkdir -p deep/untracked-deep &&
1115 run_on_all touch folder1/bogus &&
1116 run_on_all touch folder1/untracked &&
1117 run_on_all touch deep/untracked-deep/bogus &&
1118 run_on_all touch deep/untracked-deep/untracked &&
1119
1120 test_all_match git status --porcelain=v2 &&
1121 test_all_match git clean -f &&
1122 test_all_match git status --porcelain=v2 &&
1123 test_sparse_match ls &&
1124 test_sparse_match ls folder1 &&
1125 run_on_all test_path_exists folder1/bogus &&
1126 run_on_all test_path_is_missing folder1/untracked &&
1127 run_on_all test_path_exists deep/untracked-deep/bogus &&
1128 run_on_all test_path_exists deep/untracked-deep/untracked &&
1129
1130 test_all_match git clean -fd &&
1131 test_all_match git status --porcelain=v2 &&
1132 test_sparse_match ls &&
1133 test_sparse_match ls folder1 &&
1134 run_on_all test_path_exists folder1/bogus &&
1135 run_on_all test_path_exists deep/untracked-deep/bogus &&
1136 run_on_all test_path_is_missing deep/untracked-deep/untracked &&
1137
1138 test_all_match git clean -xf &&
1139 test_all_match git status --porcelain=v2 &&
1140 test_sparse_match ls &&
1141 test_sparse_match ls folder1 &&
1142 run_on_all test_path_is_missing folder1/bogus &&
1143 run_on_all test_path_exists deep/untracked-deep/bogus &&
1144
1145 test_all_match git clean -xdf &&
1146 test_all_match git status --porcelain=v2 &&
1147 test_sparse_match ls &&
1148 test_sparse_match ls folder1 &&
1149 run_on_all test_path_is_missing deep/untracked-deep/bogus &&
1150
1151 test_sparse_match test_path_is_dir folder1
1152 '
1153
1154 test_expect_success 'submodule handling' '
1155 init_repos &&
1156
1157 test_sparse_match git sparse-checkout add modules &&
1158 test_all_match mkdir modules &&
1159 test_all_match touch modules/a &&
1160 test_all_match git add modules &&
1161 test_all_match git commit -m "add modules directory" &&
1162
1163 test_config_global protocol.file.allow always &&
1164
1165 run_on_all git submodule add "$(pwd)/initial-repo" modules/sub &&
1166 test_all_match git commit -m "add submodule" &&
1167
1168 # having a submodule prevents "modules" from collapse
1169 test_sparse_match git sparse-checkout set deep/deeper1 &&
1170 git -C sparse-index ls-files --sparse --stage >cache &&
1171 grep "100644 .* modules/a" cache &&
1172 grep "160000 $(git -C initial-repo rev-parse HEAD) 0 modules/sub" cache
1173 '
1174
1175 # When working with a sparse index, some commands will need to expand the
1176 # index to operate properly. If those commands also write the index back
1177 # to disk, they need to convert the index to sparse before writing.
1178 # This test verifies that both of these events are logged in trace2 logs.
1179 test_expect_success 'sparse-index is expanded and converted back' '
1180 init_repos &&
1181
1182 GIT_TRACE2_EVENT="$(pwd)/trace2.txt" \
1183 git -C sparse-index reset -- folder1/a &&
1184 test_region index convert_to_sparse trace2.txt &&
1185 test_region index ensure_full_index trace2.txt &&
1186
1187 # ls-files expands on read, but does not write.
1188 rm trace2.txt &&
1189 GIT_TRACE2_EVENT="$(pwd)/trace2.txt" GIT_TRACE2_EVENT_NESTING=10 \
1190 git -C sparse-index ls-files &&
1191 test_region index ensure_full_index trace2.txt
1192 '
1193
1194 test_expect_success 'index.sparse disabled inline uses full index' '
1195 init_repos &&
1196
1197 # When index.sparse is disabled inline with `git status`, the
1198 # index is expanded at the beginning of the execution then never
1199 # converted back to sparse. It is then written to disk as a full index.
1200 rm -f trace2.txt &&
1201 GIT_TRACE2_EVENT="$(pwd)/trace2.txt" GIT_TRACE2_EVENT_NESTING=10 \
1202 git -C sparse-index -c index.sparse=false status &&
1203 ! test_region index convert_to_sparse trace2.txt &&
1204 test_region index ensure_full_index trace2.txt &&
1205
1206 # Since index.sparse is set to true at a repo level, the index
1207 # is converted from full to sparse when read, then never expanded
1208 # over the course of `git status`. It is written to disk as a sparse
1209 # index.
1210 rm -f trace2.txt &&
1211 GIT_TRACE2_EVENT="$(pwd)/trace2.txt" GIT_TRACE2_EVENT_NESTING=10 \
1212 git -C sparse-index status &&
1213 test_region index convert_to_sparse trace2.txt &&
1214 ! test_region index ensure_full_index trace2.txt &&
1215
1216 # Now that the index has been written to disk as sparse, it is not
1217 # converted to sparse (or expanded to full) when read by `git status`.
1218 rm -f trace2.txt &&
1219 GIT_TRACE2_EVENT="$(pwd)/trace2.txt" GIT_TRACE2_EVENT_NESTING=10 \
1220 git -C sparse-index status &&
1221 ! test_region index convert_to_sparse trace2.txt &&
1222 ! test_region index ensure_full_index trace2.txt
1223 '
1224
1225 ensure_not_expanded () {
1226 rm -f trace2.txt &&
1227 echo >>sparse-index/untracked.txt &&
1228
1229 if test "$1" = "!"
1230 then
1231 shift &&
1232 test_must_fail env \
1233 GIT_TRACE2_EVENT="$(pwd)/trace2.txt" \
1234 git -C sparse-index "$@" || return 1
1235 else
1236 GIT_TRACE2_EVENT="$(pwd)/trace2.txt" \
1237 git -C sparse-index "$@" || return 1
1238 fi &&
1239 test_region ! index ensure_full_index trace2.txt
1240 }
1241
1242 test_expect_success 'sparse-index is not expanded' '
1243 init_repos &&
1244
1245 ensure_not_expanded status &&
1246 ensure_not_expanded ls-files --sparse &&
1247 ensure_not_expanded commit --allow-empty -m empty &&
1248 echo >>sparse-index/a &&
1249 ensure_not_expanded commit -a -m a &&
1250 echo >>sparse-index/a &&
1251 ensure_not_expanded commit --include a -m a &&
1252 echo >>sparse-index/deep/deeper1/a &&
1253 ensure_not_expanded commit --include deep/deeper1/a -m deeper &&
1254 ensure_not_expanded checkout rename-out-to-out &&
1255 ensure_not_expanded checkout - &&
1256 ensure_not_expanded switch rename-out-to-out &&
1257 ensure_not_expanded switch - &&
1258 ensure_not_expanded reset --hard &&
1259 ensure_not_expanded checkout rename-out-to-out -- deep/deeper1 &&
1260 ensure_not_expanded reset --hard &&
1261 ensure_not_expanded restore -s rename-out-to-out -- deep/deeper1 &&
1262
1263 echo >>sparse-index/README.md &&
1264 ensure_not_expanded add -A &&
1265 echo >>sparse-index/extra.txt &&
1266 ensure_not_expanded add extra.txt &&
1267 echo >>sparse-index/untracked.txt &&
1268 ensure_not_expanded add . &&
1269
1270 ensure_not_expanded checkout-index -f a &&
1271 ensure_not_expanded checkout-index -f --all &&
1272 for ref in update-deep update-folder1 update-folder2 update-deep
1273 do
1274 echo >>sparse-index/README.md &&
1275 ensure_not_expanded reset --hard $ref || return 1
1276 done &&
1277
1278 ensure_not_expanded reset --mixed base &&
1279 ensure_not_expanded reset --hard update-deep &&
1280 ensure_not_expanded reset --keep base &&
1281 ensure_not_expanded reset --merge update-deep &&
1282 ensure_not_expanded reset --hard &&
1283
1284 ensure_not_expanded reset base -- deep/a &&
1285 ensure_not_expanded reset base -- nonexistent-file &&
1286 ensure_not_expanded reset deepest -- deep &&
1287
1288 # Although folder1 is outside the sparse definition, it exists as a
1289 # directory entry in the index, so the pathspec will not force the
1290 # index to be expanded.
1291 ensure_not_expanded reset deepest -- folder1 &&
1292 ensure_not_expanded reset deepest -- folder1/ &&
1293
1294 # Wildcard identifies only in-cone files, no index expansion
1295 ensure_not_expanded reset deepest -- deep/\* &&
1296
1297 # Wildcard identifies only full sparse directories, no index expansion
1298 ensure_not_expanded reset deepest -- folder\* &&
1299
1300 ensure_not_expanded clean -fd &&
1301
1302 ensure_not_expanded checkout -f update-deep &&
1303 test_config -C sparse-index pull.twohead ort &&
1304 (
1305 sane_unset GIT_TEST_MERGE_ALGORITHM &&
1306 for OPERATION in "merge -m merge" cherry-pick rebase
1307 do
1308 ensure_not_expanded merge -m merge update-folder1 &&
1309 ensure_not_expanded merge -m merge update-folder2 || return 1
1310 done
1311 )
1312 '
1313
1314 test_expect_success 'sparse-index is not expanded: merge conflict in cone' '
1315 init_repos &&
1316
1317 for side in right left
1318 do
1319 git -C sparse-index checkout -b expand-$side base &&
1320 echo $side >sparse-index/deep/a &&
1321 git -C sparse-index commit -a -m "$side" || return 1
1322 done &&
1323
1324 (
1325 sane_unset GIT_TEST_MERGE_ALGORITHM &&
1326 git -C sparse-index config pull.twohead ort &&
1327 ensure_not_expanded ! merge -m merged expand-right
1328 )
1329 '
1330
1331 test_expect_success 'sparse index is not expanded: diff' '
1332 init_repos &&
1333
1334 write_script edit-contents <<-\EOF &&
1335 echo text >>$1
1336 EOF
1337
1338 # Add file within cone
1339 test_sparse_match git sparse-checkout set deep &&
1340 run_on_all ../edit-contents deep/testfile &&
1341 test_all_match git add deep/testfile &&
1342 run_on_all ../edit-contents deep/testfile &&
1343
1344 test_all_match git diff &&
1345 test_all_match git diff --cached &&
1346 ensure_not_expanded diff &&
1347 ensure_not_expanded diff --cached &&
1348
1349 # Add file outside cone
1350 test_all_match git reset --hard &&
1351 run_on_all mkdir newdirectory &&
1352 run_on_all ../edit-contents newdirectory/testfile &&
1353 test_sparse_match git sparse-checkout set newdirectory &&
1354 test_all_match git add newdirectory/testfile &&
1355 run_on_all ../edit-contents newdirectory/testfile &&
1356 test_sparse_match git sparse-checkout set &&
1357
1358 test_all_match git diff &&
1359 test_all_match git diff --cached &&
1360 ensure_not_expanded diff &&
1361 ensure_not_expanded diff --cached &&
1362
1363 # Merge conflict outside cone
1364 # The sparse checkout will report a warning that is not in the
1365 # full checkout, so we use `run_on_all` instead of
1366 # `test_all_match`
1367 run_on_all git reset --hard &&
1368 test_all_match git checkout merge-left &&
1369 test_all_match test_must_fail git merge merge-right &&
1370
1371 test_all_match git diff &&
1372 test_all_match git diff --cached &&
1373 ensure_not_expanded diff &&
1374 ensure_not_expanded diff --cached
1375 '
1376
1377 test_expect_success 'sparse index is not expanded: update-index' '
1378 init_repos &&
1379
1380 deep_a_oid=$(git -C full-checkout rev-parse update-deep:deep/a) &&
1381 ensure_not_expanded update-index --cacheinfo 100644 $deep_a_oid deep/a &&
1382
1383 echo "test" >sparse-index/README.md &&
1384 echo "test2" >sparse-index/a &&
1385 rm -f sparse-index/deep/a &&
1386
1387 ensure_not_expanded update-index --add README.md &&
1388 ensure_not_expanded update-index a &&
1389 ensure_not_expanded update-index --remove deep/a &&
1390
1391 ensure_not_expanded reset --soft update-deep &&
1392 ensure_not_expanded update-index --add --remove --again
1393 '
1394
1395 test_expect_success 'sparse index is not expanded: blame' '
1396 init_repos &&
1397
1398 for file in a \
1399 deep/a \
1400 deep/deeper1/a \
1401 deep/deeper1/deepest/a
1402 do
1403 ensure_not_expanded blame $file
1404 done
1405 '
1406
1407 test_expect_success 'sparse index is not expanded: fetch/pull' '
1408 init_repos &&
1409
1410 git -C sparse-index remote add full "file://$(pwd)/full-checkout" &&
1411 ensure_not_expanded fetch full &&
1412 git -C full-checkout commit --allow-empty -m "for pull merge" &&
1413 git -C sparse-index commit --allow-empty -m "for pull merge" &&
1414 ensure_not_expanded pull full base
1415 '
1416
1417 test_expect_success 'sparse index is not expanded: read-tree' '
1418 init_repos &&
1419
1420 ensure_not_expanded checkout -b test-branch update-folder1 &&
1421 for MERGE_TREES in "base HEAD update-folder2" \
1422 "base HEAD rename-base" \
1423 "base update-folder2" \
1424 "base rename-base" \
1425 "update-folder2"
1426 do
1427 ensure_not_expanded read-tree -mu $MERGE_TREES &&
1428 ensure_not_expanded reset --hard || return 1
1429 done &&
1430
1431 rm -rf sparse-index/deep/deeper2 &&
1432 ensure_not_expanded add . &&
1433 ensure_not_expanded commit -m "test" &&
1434
1435 ensure_not_expanded read-tree --prefix=deep/deeper2 -u deepest
1436 '
1437
1438 test_expect_success 'ls-files' '
1439 init_repos &&
1440
1441 # Use a smaller sparse-checkout for reduced output
1442 test_sparse_match git sparse-checkout set &&
1443
1444 # Behavior agrees by default. Sparse index is expanded.
1445 test_all_match git ls-files &&
1446
1447 # With --sparse, the sparse index data changes behavior.
1448 git -C sparse-index ls-files --sparse >actual &&
1449
1450 cat >expect <<-\EOF &&
1451 a
1452 before/
1453 deep/
1454 e
1455 folder1-
1456 folder1.x
1457 folder1/
1458 folder10
1459 folder2/
1460 g
1461 x/
1462 z
1463 EOF
1464
1465 test_cmp expect actual &&
1466
1467 # With --sparse and no sparse index, nothing changes.
1468 git -C sparse-checkout ls-files >dense &&
1469 git -C sparse-checkout ls-files --sparse >sparse &&
1470 test_cmp dense sparse &&
1471
1472 # Set up a strange condition of having a file edit
1473 # outside of the sparse-checkout cone. We want to verify
1474 # that all modes handle this the same, and detect the
1475 # modification.
1476 write_script edit-content <<-\EOF &&
1477 mkdir -p folder1 &&
1478 echo content >>folder1/a
1479 EOF
1480 run_on_all ../edit-content &&
1481
1482 test_all_match git ls-files --modified &&
1483
1484 git -C sparse-index ls-files --sparse --modified >sparse-index-out &&
1485 cat >expect <<-\EOF &&
1486 folder1/a
1487 EOF
1488 test_cmp expect sparse-index-out &&
1489
1490 # Add folder1 to the sparse-checkout cone and
1491 # check that ls-files shows the expanded files.
1492 test_sparse_match git sparse-checkout add folder1 &&
1493 test_all_match git ls-files --modified &&
1494
1495 test_all_match git ls-files &&
1496 git -C sparse-index ls-files --sparse >actual &&
1497
1498 cat >expect <<-\EOF &&
1499 a
1500 before/
1501 deep/
1502 e
1503 folder1-
1504 folder1.x
1505 folder1/0/0/0
1506 folder1/0/1
1507 folder1/a
1508 folder10
1509 folder2/
1510 g
1511 x/
1512 z
1513 EOF
1514
1515 test_cmp expect actual &&
1516
1517 # Double-check index expansion is avoided
1518 ensure_not_expanded ls-files --sparse
1519 '
1520
1521 # NEEDSWORK: a sparse-checkout behaves differently from a full checkout
1522 # in this scenario, but it shouldn't.
1523 test_expect_success 'reset mixed and checkout orphan' '
1524 init_repos &&
1525
1526 test_all_match git checkout rename-out-to-in &&
1527
1528 # Sparse checkouts do not agree with full checkouts about
1529 # how to report a directory/file conflict during a reset.
1530 # This command would fail with test_all_match because the
1531 # full checkout reports "T folder1/0/1" while a sparse
1532 # checkout reports "D folder1/0/1". This matches because
1533 # the sparse checkouts skip "adding" the other side of
1534 # the conflict.
1535 test_sparse_match git reset --mixed HEAD~1 &&
1536 test_sparse_match git ls-files --stage &&
1537 test_sparse_match git status --porcelain=v2 &&
1538
1539 # At this point, sparse-checkouts behave differently
1540 # from the full-checkout.
1541 test_sparse_match git checkout --orphan new-branch &&
1542 test_sparse_match git ls-files --stage &&
1543 test_sparse_match git status --porcelain=v2
1544 '
1545
1546 test_expect_success 'add everything with deep new file' '
1547 init_repos &&
1548
1549 run_on_sparse git sparse-checkout set deep/deeper1/deepest &&
1550
1551 run_on_all touch deep/deeper1/x &&
1552 test_all_match git add . &&
1553 test_all_match git status --porcelain=v2
1554 '
1555
1556 # NEEDSWORK: 'git checkout' behaves incorrectly in the case of
1557 # directory/file conflicts, even without sparse-checkout. Use this
1558 # test only as a documentation of the incorrect behavior, not a
1559 # measure of how it _should_ behave.
1560 test_expect_success 'checkout behaves oddly with df-conflict-1' '
1561 init_repos &&
1562
1563 test_sparse_match git sparse-checkout disable &&
1564
1565 write_script edit-content <<-\EOF &&
1566 echo content >>folder1/larger-content
1567 git add folder1
1568 EOF
1569
1570 run_on_all ../edit-content &&
1571 test_all_match git status --porcelain=v2 &&
1572
1573 git -C sparse-checkout sparse-checkout init --cone &&
1574 git -C sparse-index sparse-checkout init --cone --sparse-index &&
1575
1576 test_all_match git status --porcelain=v2 &&
1577
1578 # This checkout command should fail, because we have a staged
1579 # change to folder1/larger-content, but the destination changes
1580 # folder1 to a file.
1581 git -C full-checkout checkout df-conflict-1 \
1582 1>full-checkout-out \
1583 2>full-checkout-err &&
1584 git -C sparse-checkout checkout df-conflict-1 \
1585 1>sparse-checkout-out \
1586 2>sparse-checkout-err &&
1587 git -C sparse-index checkout df-conflict-1 \
1588 1>sparse-index-out \
1589 2>sparse-index-err &&
1590
1591 # Instead, the checkout deletes the folder1 file and adds the
1592 # folder1/larger-content file, leaving all other paths that were
1593 # in folder1/ as deleted (without any warning).
1594 cat >expect <<-EOF &&
1595 D folder1
1596 A folder1/larger-content
1597 EOF
1598 test_cmp expect full-checkout-out &&
1599 test_cmp expect sparse-checkout-out &&
1600
1601 # The sparse-index reports no output
1602 test_must_be_empty sparse-index-out &&
1603
1604 # stderr: Switched to branch df-conflict-1
1605 test_cmp full-checkout-err sparse-checkout-err &&
1606 test_cmp full-checkout-err sparse-checkout-err
1607 '
1608
1609 # NEEDSWORK: 'git checkout' behaves incorrectly in the case of
1610 # directory/file conflicts, even without sparse-checkout. Use this
1611 # test only as a documentation of the incorrect behavior, not a
1612 # measure of how it _should_ behave.
1613 test_expect_success 'checkout behaves oddly with df-conflict-2' '
1614 init_repos &&
1615
1616 test_sparse_match git sparse-checkout disable &&
1617
1618 write_script edit-content <<-\EOF &&
1619 echo content >>folder2/larger-content
1620 git add folder2
1621 EOF
1622
1623 run_on_all ../edit-content &&
1624 test_all_match git status --porcelain=v2 &&
1625
1626 git -C sparse-checkout sparse-checkout init --cone &&
1627 git -C sparse-index sparse-checkout init --cone --sparse-index &&
1628
1629 test_all_match git status --porcelain=v2 &&
1630
1631 # This checkout command should fail, because we have a staged
1632 # change to folder1/larger-content, but the destination changes
1633 # folder1 to a file.
1634 git -C full-checkout checkout df-conflict-2 \
1635 1>full-checkout-out \
1636 2>full-checkout-err &&
1637 git -C sparse-checkout checkout df-conflict-2 \
1638 1>sparse-checkout-out \
1639 2>sparse-checkout-err &&
1640 git -C sparse-index checkout df-conflict-2 \
1641 1>sparse-index-out \
1642 2>sparse-index-err &&
1643
1644 # The full checkout deviates from the df-conflict-1 case here!
1645 # It drops the change to folder1/larger-content and leaves the
1646 # folder1 path as-is on disk. The sparse-index behaves the same.
1647 test_must_be_empty full-checkout-out &&
1648 test_must_be_empty sparse-index-out &&
1649
1650 # In the sparse-checkout case, the checkout deletes the folder1
1651 # file and adds the folder1/larger-content file, leaving all other
1652 # paths that were in folder1/ as deleted (without any warning).
1653 cat >expect <<-EOF &&
1654 D folder2
1655 A folder2/larger-content
1656 EOF
1657 test_cmp expect sparse-checkout-out &&
1658
1659 # Switched to branch df-conflict-1
1660 test_cmp full-checkout-err sparse-checkout-err &&
1661 test_cmp full-checkout-err sparse-index-err
1662 '
1663
1664 test_done