3 test_description
='compare full workdir to sparse workdir'
10 test_expect_success
'setup' '
11 git init initial-repo &&
13 GIT_TEST_SPARSE_INDEX=0 &&
16 echo "after deep" >e &&
17 echo "after folder1" >g &&
19 mkdir folder1 folder2 deep x &&
20 mkdir deep/deeper1 deep/deeper2 deep/before deep/later &&
21 mkdir deep/deeper1/deepest &&
22 echo "after deeper1" >deep/e &&
23 echo "after deepest" >deep/deeper1/e &&
32 cp a deep/deeper1/deepest &&
33 cp -r deep/deeper1/deepest deep/deeper2 &&
34 mkdir deep/deeper1/0 &&
35 mkdir deep/deeper1/0/0 &&
36 touch deep/deeper1/0/1 &&
37 touch deep/deeper1/0/0/0 &&
41 cp -r deep/deeper1/0 folder1 &&
42 cp -r deep/deeper1/0 folder2 &&
43 echo >>folder1/0/0/0 &&
46 git commit -m "initial commit" &&
47 git checkout -b base &&
48 for dir in folder1 folder2 deep
50 git checkout -b update-$dir base &&
51 echo "updated $dir" >$dir/a &&
52 git commit -a -m "update $dir" || return 1
55 git checkout -b rename-base base &&
56 cat >folder1/larger-content <<-\EOF &&
63 cp folder1/larger-content folder2/ &&
64 cp folder1/larger-content deep/deeper1/ &&
66 git commit -m "add interesting rename content" &&
68 git checkout -b rename-out-to-out rename-base &&
69 mv folder1/a folder2/b &&
70 mv folder1/larger-content folder2/edited-content &&
71 echo >>folder2/edited-content &&
73 echo stuff >>deep/deeper1/a &&
75 git commit -m "rename folder1/... to folder2/..." &&
77 git checkout -b rename-out-to-in rename-base &&
78 mv folder1/a deep/deeper1/b &&
79 echo more stuff >>deep/deeper1/a &&
82 echo >>folder2/0/1/1 &&
83 mv folder1/larger-content deep/deeper1/edited-content &&
84 echo >>deep/deeper1/edited-content &&
86 git commit -m "rename folder1/... to deep/deeper1/..." &&
88 git checkout -b rename-in-to-out rename-base &&
89 mv deep/deeper1/a folder1/b &&
93 mv deep/deeper1/larger-content folder1/edited-content &&
94 echo >>folder1/edited-content &&
96 git commit -m "rename deep/deeper1/... to folder1/..." &&
98 git checkout -b df-conflict-1 base &&
100 echo content >folder1 &&
102 git commit -m "dir to file" &&
104 git checkout -b df-conflict-2 base &&
106 echo content >folder2 &&
108 git commit -m "dir to file" &&
110 git checkout -b fd-conflict base &&
115 git commit -m "file to dir" &&
117 for side in left right
119 git checkout -b merge-$side base &&
120 echo $side >>deep/deeper2/a &&
121 echo $side >>folder1/a &&
122 echo $side >>folder2/a &&
124 git commit -m "$side" || return 1
127 git checkout -b deepest base &&
128 echo "updated deepest" >deep/deeper1/deepest/a &&
129 git commit -a -m "update deepest" &&
131 git checkout -f base &&
137 rm -rf full-checkout sparse-checkout sparse-index
&&
139 # create repos in initial state
140 cp -r initial-repo full-checkout
&&
141 git
-C full-checkout
reset --hard &&
143 cp -r initial-repo sparse-checkout
&&
144 git
-C sparse-checkout
reset --hard &&
146 cp -r initial-repo sparse-index
&&
147 git
-C sparse-index
reset --hard &&
149 # initialize sparse-checkout definitions
150 git
-C sparse-checkout sparse-checkout init
--cone &&
151 git
-C sparse-checkout sparse-checkout
set deep
&&
152 git
-C sparse-index sparse-checkout init
--cone --sparse-index &&
153 test_cmp_config
-C sparse-index true index.sparse
&&
154 git
-C sparse-index sparse-checkout
set deep
159 cd sparse-checkout
&&
160 GIT_PROGRESS_DELAY
=100000 "$@" >..
/sparse-checkout-out
2>..
/sparse-checkout-err
164 GIT_PROGRESS_DELAY
=100000 "$@" >..
/sparse-index-out
2>..
/sparse-index-err
171 GIT_PROGRESS_DELAY
=100000 "$@" >..
/full-checkout-out
2>..
/full-checkout-err
178 test_cmp full-checkout-out sparse-checkout-out
&&
179 test_cmp full-checkout-out sparse-index-out
&&
180 test_cmp full-checkout-err sparse-checkout-err
&&
181 test_cmp full-checkout-err sparse-index-err
184 test_sparse_match
() {
185 run_on_sparse
"$@" &&
186 test_cmp sparse-checkout-out sparse-index-out
&&
187 test_cmp sparse-checkout-err sparse-index-err
190 test_sparse_unstaged
() {
192 for repo
in sparse-checkout sparse-index
194 # Skip "unmerged" paths
195 git
-C $repo diff --staged --diff-filter=u
-- "$file" >diff &&
196 test_must_be_empty
diff ||
return 1
200 test_expect_success
'sparse-index contents' '
203 test-tool -C sparse-index read-cache --table >cache &&
204 for dir in folder1 folder2 x
206 TREE=$(git -C sparse-index rev-parse HEAD:$dir) &&
207 grep "040000 tree $TREE $dir/" cache \
211 git -C sparse-index sparse-checkout set folder1 &&
213 test-tool -C sparse-index read-cache --table >cache &&
214 for dir in deep folder2 x
216 TREE=$(git -C sparse-index rev-parse HEAD:$dir) &&
217 grep "040000 tree $TREE $dir/" cache \
221 git -C sparse-index sparse-checkout set deep/deeper1 &&
223 test-tool -C sparse-index read-cache --table >cache &&
224 for dir in deep/deeper2 folder1 folder2 x
226 TREE=$(git -C sparse-index rev-parse HEAD:$dir) &&
227 grep "040000 tree $TREE $dir/" cache \
231 # Disabling the sparse-index removes tree entries with full ones
232 git -C sparse-index sparse-checkout init --no-sparse-index &&
234 test-tool -C sparse-index read-cache --table >cache &&
235 ! grep "040000 tree" cache &&
236 test_sparse_match test-tool read-cache --table
239 test_expect_success
'expanded in-memory index matches full index' '
241 test_sparse_match test-tool read-cache --expand --table
244 test_expect_success
'status with options' '
246 test_sparse_match ls &&
247 test_all_match git status --porcelain=v2 &&
248 test_all_match git status --porcelain=v2 -z -u &&
249 test_all_match git status --porcelain=v2 -uno &&
250 run_on_all touch README.md &&
251 test_all_match git status --porcelain=v2 &&
252 test_all_match git status --porcelain=v2 -z -u &&
253 test_all_match git status --porcelain=v2 -uno &&
254 test_all_match git add README.md &&
255 test_all_match git status --porcelain=v2 &&
256 test_all_match git status --porcelain=v2 -z -u &&
257 test_all_match git status --porcelain=v2 -uno
260 test_expect_success
'status reports sparse-checkout' '
262 git -C sparse-checkout status >full &&
263 git -C sparse-index status >sparse &&
264 test_i18ngrep "You are in a sparse checkout with " full &&
265 test_i18ngrep "You are in a sparse checkout." sparse
268 test_expect_success
'add, commit, checkout' '
271 write_script edit-contents <<-\EOF &&
274 run_on_all ../edit-contents README.md &&
276 test_all_match git add README.md &&
277 test_all_match git status --porcelain=v2 &&
278 test_all_match git commit -m "Add README.md" &&
280 test_all_match git checkout HEAD~1 &&
281 test_all_match git checkout - &&
283 run_on_all ../edit-contents README.md &&
285 test_all_match git add -A &&
286 test_all_match git status --porcelain=v2 &&
287 test_all_match git commit -m "Extend README.md" &&
289 test_all_match git checkout HEAD~1 &&
290 test_all_match git checkout - &&
292 run_on_all ../edit-contents deep/newfile &&
294 test_all_match git status --porcelain=v2 -uno &&
295 test_all_match git status --porcelain=v2 &&
296 test_all_match git add . &&
297 test_all_match git status --porcelain=v2 &&
298 test_all_match git commit -m "add deep/newfile" &&
300 test_all_match git checkout HEAD~1 &&
301 test_all_match git checkout -
304 test_expect_success
'add outside sparse cone' '
307 run_on_sparse mkdir folder1 &&
308 run_on_sparse ../edit-contents folder1/a &&
309 run_on_sparse ../edit-contents folder1/newfile &&
310 test_sparse_match test_must_fail git add folder1/a &&
311 grep "Disable or modify the sparsity rules" sparse-checkout-err &&
312 test_sparse_unstaged folder1/a &&
313 test_sparse_match test_must_fail git add folder1/newfile &&
314 grep "Disable or modify the sparsity rules" sparse-checkout-err &&
315 test_sparse_unstaged folder1/newfile
318 test_expect_success
'commit including unstaged changes' '
321 write_script edit-file <<-\EOF &&
325 run_on_all ../edit-file 1 a &&
326 run_on_all ../edit-file 1 deep/a &&
328 test_all_match git commit -m "-a" -a &&
329 test_all_match git status --porcelain=v2 &&
331 run_on_all ../edit-file 2 a &&
332 run_on_all ../edit-file 2 deep/a &&
334 test_all_match git commit -m "--include" --include deep/a &&
335 test_all_match git status --porcelain=v2 &&
336 test_all_match git commit -m "--include" --include a &&
337 test_all_match git status --porcelain=v2 &&
339 run_on_all ../edit-file 3 a &&
340 run_on_all ../edit-file 3 deep/a &&
342 test_all_match git commit -m "--amend" -a --amend &&
343 test_all_match git status --porcelain=v2
346 test_expect_success
'status/add: outside sparse cone' '
349 # folder1 is at HEAD, but outside the sparse cone
350 run_on_sparse mkdir folder1 &&
351 cp initial-repo/folder1/a sparse-checkout/folder1/a &&
352 cp initial-repo/folder1/a sparse-index/folder1/a &&
354 test_sparse_match git status &&
356 write_script edit-contents <<-\EOF &&
359 run_on_sparse ../edit-contents folder1/a &&
360 run_on_all ../edit-contents folder1/new &&
362 test_sparse_match git status --porcelain=v2 &&
364 # Adding the path outside of the sparse-checkout cone should fail.
365 test_sparse_match test_must_fail git add folder1/a &&
366 grep "Disable or modify the sparsity rules" sparse-checkout-err &&
367 test_sparse_unstaged folder1/a &&
368 test_sparse_match test_must_fail git add --refresh folder1/a &&
369 grep "Disable or modify the sparsity rules" sparse-checkout-err &&
370 test_sparse_unstaged folder1/a &&
371 test_sparse_match test_must_fail git add folder1/new &&
372 grep "Disable or modify the sparsity rules" sparse-checkout-err &&
373 test_sparse_unstaged folder1/new &&
374 test_sparse_match git add --sparse folder1/a &&
375 test_sparse_match git add --sparse folder1/new &&
377 test_all_match git add --sparse . &&
378 test_all_match git status --porcelain=v2 &&
379 test_all_match git commit -m folder1/new &&
380 test_all_match git rev-parse HEAD^{tree} &&
382 run_on_all ../edit-contents folder1/newer &&
383 test_all_match git add --sparse folder1/ &&
384 test_all_match git status --porcelain=v2 &&
385 test_all_match git commit -m folder1/newer &&
386 test_all_match git rev-parse HEAD^{tree}
389 test_expect_success
'checkout and reset --hard' '
392 test_all_match git checkout update-folder1 &&
393 test_all_match git status --porcelain=v2 &&
395 test_all_match git checkout update-deep &&
396 test_all_match git status --porcelain=v2 &&
398 test_all_match git checkout -b reset-test &&
399 test_all_match git reset --hard deepest &&
400 test_all_match git reset --hard update-folder1 &&
401 test_all_match git reset --hard update-folder2
404 test_expect_success
'diff --staged' '
407 write_script edit-contents <<-\EOF &&
408 echo text >>README.md
410 run_on_all ../edit-contents &&
412 test_all_match git diff &&
413 test_all_match git diff --staged &&
414 test_all_match git add README.md &&
415 test_all_match git diff &&
416 test_all_match git diff --staged
419 # NEEDSWORK: sparse-checkout behaves differently from full-checkout when
420 # running this test with 'df-conflict-2' after 'df-conflict-1'.
421 test_expect_success
'diff with renames and conflicts' '
424 for branch in rename-out-to-out \
430 test_all_match git checkout rename-base &&
431 test_all_match git checkout $branch -- . &&
432 test_all_match git status --porcelain=v2 &&
433 test_all_match git diff --staged --no-renames &&
434 test_all_match git diff --staged --find-renames || return 1
438 test_expect_success
'diff with directory/file conflicts' '
441 for branch in rename-out-to-out \
448 git -C full-checkout reset --hard &&
449 test_sparse_match git reset --hard &&
450 test_all_match git checkout $branch &&
451 test_all_match git checkout rename-base -- . &&
452 test_all_match git status --porcelain=v2 &&
453 test_all_match git diff --staged --no-renames &&
454 test_all_match git diff --staged --find-renames || return 1
458 test_expect_success
'log with pathspec outside sparse definition' '
461 test_all_match git log -- a &&
462 test_all_match git log -- folder1/a &&
463 test_all_match git log -- folder2/a &&
464 test_all_match git log -- deep/a &&
465 test_all_match git log -- deep/deeper1/a &&
466 test_all_match git log -- deep/deeper1/deepest/a &&
468 test_all_match git checkout update-folder1 &&
469 test_all_match git log -- folder1/a
472 test_expect_success
'blame with pathspec inside sparse definition' '
475 test_all_match git blame a &&
476 test_all_match git blame deep/a &&
477 test_all_match git blame deep/deeper1/a &&
478 test_all_match git blame deep/deeper1/deepest/a
481 # TODO: blame currently does not support blaming files outside of the
482 # sparse definition. It complains that the file doesn't exist locally.
483 test_expect_failure
'blame with pathspec outside sparse definition' '
486 test_all_match git blame folder1/a &&
487 test_all_match git blame folder2/a &&
488 test_all_match git blame deep/deeper2/a &&
489 test_all_match git blame deep/deeper2/deepest/a
492 test_expect_success
'checkout and reset (mixed)' '
495 test_all_match git checkout -b reset-test update-deep &&
496 test_all_match git reset deepest &&
498 # Because skip-worktree is preserved, resetting to update-folder1
499 # will show worktree changes for folder1/a in full-checkout, but not
500 # in sparse-checkout or sparse-index.
501 git -C full-checkout reset update-folder1 >full-checkout-out &&
502 test_sparse_match git reset update-folder1 &&
503 grep "M folder1/a" full-checkout-out &&
504 ! grep "M folder1/a" sparse-checkout-out &&
505 run_on_sparse test_path_is_missing folder1
508 test_expect_success
'checkout and reset (merge)' '
511 write_script edit-contents <<-\EOF &&
515 test_all_match git checkout -b reset-test update-deep &&
516 run_on_all ../edit-contents a &&
517 test_all_match git reset --merge deepest &&
518 test_all_match git status --porcelain=v2 &&
520 test_all_match git reset --hard update-deep &&
521 run_on_all ../edit-contents deep/a &&
522 test_all_match test_must_fail git reset --merge deepest
525 test_expect_success
'checkout and reset (keep)' '
528 write_script edit-contents <<-\EOF &&
532 test_all_match git checkout -b reset-test update-deep &&
533 run_on_all ../edit-contents a &&
534 test_all_match git reset --keep deepest &&
535 test_all_match git status --porcelain=v2 &&
537 test_all_match git reset --hard update-deep &&
538 run_on_all ../edit-contents deep/a &&
539 test_all_match test_must_fail git reset --keep deepest
542 test_expect_success
'reset with pathspecs inside sparse definition' '
545 write_script edit-contents <<-\EOF &&
549 test_all_match git checkout -b reset-test update-deep &&
550 run_on_all ../edit-contents deep/a &&
552 test_all_match git reset base -- deep/a &&
553 test_all_match git status --porcelain=v2 &&
555 test_all_match git reset base -- nonexistent-file &&
556 test_all_match git status --porcelain=v2 &&
558 test_all_match git reset deepest -- deep &&
559 test_all_match git status --porcelain=v2
562 # Although the working tree differs between full and sparse checkouts after
563 # reset, the state of the index is the same.
564 test_expect_success
'reset with pathspecs outside sparse definition' '
566 test_all_match git checkout -b reset-test base &&
568 test_sparse_match git reset update-folder1 -- folder1 &&
569 git -C full-checkout reset update-folder1 -- folder1 &&
570 test_sparse_match git status --porcelain=v2 &&
571 test_all_match git rev-parse HEAD:folder1 &&
573 test_sparse_match git reset update-folder2 -- folder2/a &&
574 git -C full-checkout reset update-folder2 -- folder2/a &&
575 test_sparse_match git status --porcelain=v2 &&
576 test_all_match git rev-parse HEAD:folder2/a
579 test_expect_success
'reset with wildcard pathspec' '
582 test_all_match git reset update-deep -- deep\* &&
583 test_all_match git ls-files -s -- deep &&
585 test_all_match git reset deepest -- deep\*\*\* &&
586 test_all_match git ls-files -s -- deep &&
588 # The following `git reset`s result in updating the index on files with
589 # `skip-worktree` enabled. To avoid failing due to discrepencies in reported
590 # "modified" files, `test_sparse_match` reset is performed separately from
591 # "full-checkout" reset, then the index contents of all repos are verified.
593 test_sparse_match git reset update-folder1 -- \*/a &&
594 git -C full-checkout reset update-folder1 -- \*/a &&
595 test_all_match git ls-files -s -- deep/a folder1/a &&
597 test_sparse_match git reset update-folder2 -- folder\* &&
598 git -C full-checkout reset update-folder2 -- folder\* &&
599 test_all_match git ls-files -s -- folder10 folder1 folder2 &&
601 test_sparse_match git reset base -- folder1/\* &&
602 git -C full-checkout reset base -- folder1/\* &&
603 test_all_match git ls-files -s -- folder1
606 test_expect_success
'merge, cherry-pick, and rebase' '
609 for OPERATION in "merge -m merge" cherry-pick "rebase --apply" "rebase --merge"
611 test_all_match git checkout -B temp update-deep &&
612 test_all_match git $OPERATION update-folder1 &&
613 test_all_match git rev-parse HEAD^{tree} &&
614 test_all_match git $OPERATION update-folder2 &&
615 test_all_match git rev-parse HEAD^{tree} || return 1
619 test_expect_success
'merge with conflict outside cone' '
622 test_all_match git checkout -b merge-tip merge-left &&
623 test_all_match git status --porcelain=v2 &&
624 test_all_match test_must_fail git merge -m merge merge-right &&
625 test_all_match git status --porcelain=v2 &&
627 # Resolve the conflict in different ways:
628 # 1. Revert to the base
629 test_all_match git checkout base -- deep/deeper2/a &&
630 test_all_match git status --porcelain=v2 &&
632 # 2. Add the file with conflict markers
633 test_sparse_match test_must_fail git add folder1/a &&
634 grep "Disable or modify the sparsity rules" sparse-checkout-err &&
635 test_sparse_unstaged folder1/a &&
636 test_all_match git add --sparse folder1/a &&
637 test_all_match git status --porcelain=v2 &&
639 # 3. Rename the file to another sparse filename and
640 # accept conflict markers as resolved content.
641 run_on_all mv folder2/a folder2/z &&
642 test_sparse_match test_must_fail git add folder2 &&
643 grep "Disable or modify the sparsity rules" sparse-checkout-err &&
644 test_sparse_unstaged folder2/z &&
645 test_all_match git add --sparse folder2 &&
646 test_all_match git status --porcelain=v2 &&
648 test_all_match git merge --continue &&
649 test_all_match git status --porcelain=v2 &&
650 test_all_match git rev-parse HEAD^{tree}
653 test_expect_success
'cherry-pick/rebase with conflict outside cone' '
656 for OPERATION in cherry-pick rebase
658 test_all_match git checkout -B tip &&
659 test_all_match git reset --hard merge-left &&
660 test_all_match git status --porcelain=v2 &&
661 test_all_match test_must_fail git $OPERATION merge-right &&
662 test_all_match git status --porcelain=v2 &&
664 # Resolve the conflict in different ways:
665 # 1. Revert to the base
666 test_all_match git checkout base -- deep/deeper2/a &&
667 test_all_match git status --porcelain=v2 &&
669 # 2. Add the file with conflict markers
670 # NEEDSWORK: Even though the merge conflict removed the
671 # SKIP_WORKTREE bit from the index entry for folder1/a, we should
672 # warn that this is a problematic add.
673 test_sparse_match test_must_fail git add folder1/a &&
674 grep "Disable or modify the sparsity rules" sparse-checkout-err &&
675 test_sparse_unstaged folder1/a &&
676 test_all_match git add --sparse folder1/a &&
677 test_all_match git status --porcelain=v2 &&
679 # 3. Rename the file to another sparse filename and
680 # accept conflict markers as resolved content.
681 # NEEDSWORK: This mode now fails, because folder2/z is
682 # outside of the sparse-checkout cone and does not match an
683 # existing index entry with the SKIP_WORKTREE bit cleared.
684 run_on_all mv folder2/a folder2/z &&
685 test_sparse_match test_must_fail git add folder2 &&
686 grep "Disable or modify the sparsity rules" sparse-checkout-err &&
687 test_sparse_unstaged folder2/z &&
688 test_all_match git add --sparse folder2 &&
689 test_all_match git status --porcelain=v2 &&
691 test_all_match git $OPERATION --continue &&
692 test_all_match git status --porcelain=v2 &&
693 test_all_match git rev-parse HEAD^{tree} || return 1
697 test_expect_success
'merge with outside renames' '
700 for type in out-to-out out-to-in in-to-out
702 test_all_match git reset --hard &&
703 test_all_match git checkout -f -b merge-$type update-deep &&
704 test_all_match git merge -m "$type" rename-$type &&
705 test_all_match git rev-parse HEAD^{tree} || return 1
709 # Sparse-index fails to convert the index in the
710 # final 'git cherry-pick' command.
711 test_expect_success
'cherry-pick with conflicts' '
714 write_script edit-conflict <<-\EOF &&
718 test_all_match git checkout -b to-cherry-pick &&
719 run_on_all ../edit-conflict ABC &&
720 test_all_match git add conflict &&
721 test_all_match git commit -m "conflict to pick" &&
723 test_all_match git checkout -B base HEAD~1 &&
724 run_on_all ../edit-conflict DEF &&
725 test_all_match git add conflict &&
726 test_all_match git commit -m "conflict in base" &&
728 test_all_match test_must_fail git cherry-pick to-cherry-pick
731 test_expect_success
'clean' '
734 echo bogus >>.gitignore &&
735 run_on_all cp ../.gitignore . &&
736 test_all_match git add .gitignore &&
737 test_all_match git commit -m "ignore bogus files" &&
739 run_on_sparse mkdir folder1 &&
740 run_on_all touch folder1/bogus &&
742 test_all_match git status --porcelain=v2 &&
743 test_all_match git clean -f &&
744 test_all_match git status --porcelain=v2 &&
745 test_sparse_match ls &&
746 test_sparse_match ls folder1 &&
748 test_all_match git clean -xf &&
749 test_all_match git status --porcelain=v2 &&
750 test_sparse_match ls &&
751 test_sparse_match ls folder1 &&
753 test_all_match git clean -xdf &&
754 test_all_match git status --porcelain=v2 &&
755 test_sparse_match ls &&
756 test_sparse_match ls folder1 &&
758 test_sparse_match test_path_is_dir folder1
761 test_expect_success
'submodule handling' '
764 test_sparse_match git sparse-checkout add modules &&
765 test_all_match mkdir modules &&
766 test_all_match touch modules/a &&
767 test_all_match git add modules &&
768 test_all_match git commit -m "add modules directory" &&
770 run_on_all git submodule add "$(pwd)/initial-repo" modules/sub &&
771 test_all_match git commit -m "add submodule" &&
773 # having a submodule prevents "modules" from collapse
774 test_sparse_match git sparse-checkout set deep/deeper1 &&
775 test-tool -C sparse-index read-cache --table >cache &&
776 grep "100644 blob .* modules/a" cache &&
777 grep "160000 commit $(git -C initial-repo rev-parse HEAD) modules/sub" cache
780 # When working with a sparse index, some commands will need to expand the
781 # index to operate properly. If those commands also write the index back
782 # to disk, they need to convert the index to sparse before writing.
783 # This test verifies that both of these events are logged in trace2 logs.
784 test_expect_success
'sparse-index is expanded and converted back' '
787 GIT_TRACE2_EVENT="$(pwd)/trace2.txt" \
788 git -C sparse-index reset -- folder1/a &&
789 test_region index convert_to_sparse trace2.txt &&
790 test_region index ensure_full_index trace2.txt
793 test_expect_success
'index.sparse disabled inline uses full index' '
796 # When index.sparse is disabled inline with `git status`, the
797 # index is expanded at the beginning of the execution then never
798 # converted back to sparse. It is then written to disk as a full index.
800 GIT_TRACE2_EVENT="$(pwd)/trace2.txt" GIT_TRACE2_EVENT_NESTING=10 \
801 git -C sparse-index -c index.sparse=false status &&
802 ! test_region index convert_to_sparse trace2.txt &&
803 test_region index ensure_full_index trace2.txt &&
805 # Since index.sparse is set to true at a repo level, the index
806 # is converted from full to sparse when read, then never expanded
807 # over the course of `git status`. It is written to disk as a sparse
810 GIT_TRACE2_EVENT="$(pwd)/trace2.txt" GIT_TRACE2_EVENT_NESTING=10 \
811 git -C sparse-index status &&
812 test_region index convert_to_sparse trace2.txt &&
813 ! test_region index ensure_full_index trace2.txt &&
815 # Now that the index has been written to disk as sparse, it is not
816 # converted to sparse (or expanded to full) when read by `git status`.
818 GIT_TRACE2_EVENT="$(pwd)/trace2.txt" GIT_TRACE2_EVENT_NESTING=10 \
819 git -C sparse-index status &&
820 ! test_region index convert_to_sparse trace2.txt &&
821 ! test_region index ensure_full_index trace2.txt
824 ensure_not_expanded
() {
826 echo >>sparse-index
/untracked.txt
&&
832 GIT_TRACE2_EVENT
="$(pwd)/trace2.txt" \
833 git
-C sparse-index
"$@" ||
return 1
835 GIT_TRACE2_EVENT
="$(pwd)/trace2.txt" \
836 git
-C sparse-index
"$@" ||
return 1
838 test_region
! index ensure_full_index trace2.txt
841 test_expect_success
'sparse-index is not expanded' '
844 ensure_not_expanded status &&
845 ensure_not_expanded commit --allow-empty -m empty &&
846 echo >>sparse-index/a &&
847 ensure_not_expanded commit -a -m a &&
848 echo >>sparse-index/a &&
849 ensure_not_expanded commit --include a -m a &&
850 echo >>sparse-index/deep/deeper1/a &&
851 ensure_not_expanded commit --include deep/deeper1/a -m deeper &&
852 ensure_not_expanded checkout rename-out-to-out &&
853 ensure_not_expanded checkout - &&
854 ensure_not_expanded switch rename-out-to-out &&
855 ensure_not_expanded switch - &&
856 ensure_not_expanded reset --hard &&
857 ensure_not_expanded checkout rename-out-to-out -- deep/deeper1 &&
858 ensure_not_expanded reset --hard &&
859 ensure_not_expanded restore -s rename-out-to-out -- deep/deeper1 &&
861 echo >>sparse-index/README.md &&
862 ensure_not_expanded add -A &&
863 echo >>sparse-index/extra.txt &&
864 ensure_not_expanded add extra.txt &&
865 echo >>sparse-index/untracked.txt &&
866 ensure_not_expanded add . &&
868 for ref in update-deep update-folder1 update-folder2 update-deep
870 echo >>sparse-index/README.md &&
871 ensure_not_expanded reset --hard $ref || return 1
874 ensure_not_expanded reset --mixed base &&
875 ensure_not_expanded reset --hard update-deep &&
876 ensure_not_expanded reset --keep base &&
877 ensure_not_expanded reset --merge update-deep &&
878 ensure_not_expanded reset --hard &&
880 ensure_not_expanded reset base -- deep/a &&
881 ensure_not_expanded reset base -- nonexistent-file &&
882 ensure_not_expanded reset deepest -- deep &&
884 # Although folder1 is outside the sparse definition, it exists as a
885 # directory entry in the index, so the pathspec will not force the
886 # index to be expanded.
887 ensure_not_expanded reset deepest -- folder1 &&
888 ensure_not_expanded reset deepest -- folder1/ &&
890 # Wildcard identifies only in-cone files, no index expansion
891 ensure_not_expanded reset deepest -- deep/\* &&
893 # Wildcard identifies only full sparse directories, no index expansion
894 ensure_not_expanded reset deepest -- folder\* &&
896 ensure_not_expanded checkout -f update-deep &&
897 test_config -C sparse-index pull.twohead ort &&
899 sane_unset GIT_TEST_MERGE_ALGORITHM &&
900 for OPERATION in "merge -m merge" cherry-pick rebase
902 ensure_not_expanded merge -m merge update-folder1 &&
903 ensure_not_expanded merge -m merge update-folder2 || return 1
908 test_expect_success
'sparse-index is not expanded: merge conflict in cone' '
911 for side in right left
913 git -C sparse-index checkout -b expand-$side base &&
914 echo $side >sparse-index/deep/a &&
915 git -C sparse-index commit -a -m "$side" || return 1
919 sane_unset GIT_TEST_MERGE_ALGORITHM &&
920 git -C sparse-index config pull.twohead ort &&
921 ensure_not_expanded ! merge -m merged expand-right
925 # NEEDSWORK: a sparse-checkout behaves differently from a full checkout
926 # in this scenario, but it shouldn't.
927 test_expect_success
'reset mixed and checkout orphan' '
930 test_all_match git checkout rename-out-to-in &&
932 # Sparse checkouts do not agree with full checkouts about
933 # how to report a directory/file conflict during a reset.
934 # This command would fail with test_all_match because the
935 # full checkout reports "T folder1/0/1" while a sparse
936 # checkout reports "D folder1/0/1". This matches because
937 # the sparse checkouts skip "adding" the other side of
939 test_sparse_match git reset --mixed HEAD~1 &&
940 test_sparse_match test-tool read-cache --table --expand &&
941 test_sparse_match git status --porcelain=v2 &&
943 # At this point, sparse-checkouts behave differently
944 # from the full-checkout.
945 test_sparse_match git checkout --orphan new-branch &&
946 test_sparse_match test-tool read-cache --table --expand &&
947 test_sparse_match git status --porcelain=v2
950 test_expect_success
'add everything with deep new file' '
953 run_on_sparse git sparse-checkout set deep/deeper1/deepest &&
955 run_on_all touch deep/deeper1/x &&
956 test_all_match git add . &&
957 test_all_match git status --porcelain=v2
960 # NEEDSWORK: 'git checkout' behaves incorrectly in the case of
961 # directory/file conflicts, even without sparse-checkout. Use this
962 # test only as a documentation of the incorrect behavior, not a
963 # measure of how it _should_ behave.
964 test_expect_success
'checkout behaves oddly with df-conflict-1' '
967 test_sparse_match git sparse-checkout disable &&
969 write_script edit-content <<-\EOF &&
970 echo content >>folder1/larger-content
974 run_on_all ../edit-content &&
975 test_all_match git status --porcelain=v2 &&
977 git -C sparse-checkout sparse-checkout init --cone &&
978 git -C sparse-index sparse-checkout init --cone --sparse-index &&
980 test_all_match git status --porcelain=v2 &&
982 # This checkout command should fail, because we have a staged
983 # change to folder1/larger-content, but the destination changes
985 git -C full-checkout checkout df-conflict-1 \
986 1>full-checkout-out \
987 2>full-checkout-err &&
988 git -C sparse-checkout checkout df-conflict-1 \
989 1>sparse-checkout-out \
990 2>sparse-checkout-err &&
991 git -C sparse-index checkout df-conflict-1 \
993 2>sparse-index-err &&
995 # Instead, the checkout deletes the folder1 file and adds the
996 # folder1/larger-content file, leaving all other paths that were
997 # in folder1/ as deleted (without any warning).
998 cat >expect <<-EOF &&
1000 A folder1/larger-content
1002 test_cmp expect full-checkout-out &&
1003 test_cmp expect sparse-checkout-out &&
1005 # The sparse-index reports no output
1006 test_must_be_empty sparse-index-out &&
1008 # stderr: Switched to branch df-conflict-1
1009 test_cmp full-checkout-err sparse-checkout-err &&
1010 test_cmp full-checkout-err sparse-checkout-err
1013 # NEEDSWORK: 'git checkout' behaves incorrectly in the case of
1014 # directory/file conflicts, even without sparse-checkout. Use this
1015 # test only as a documentation of the incorrect behavior, not a
1016 # measure of how it _should_ behave.
1017 test_expect_success
'checkout behaves oddly with df-conflict-2' '
1020 test_sparse_match git sparse-checkout disable &&
1022 write_script edit-content <<-\EOF &&
1023 echo content >>folder2/larger-content
1027 run_on_all ../edit-content &&
1028 test_all_match git status --porcelain=v2 &&
1030 git -C sparse-checkout sparse-checkout init --cone &&
1031 git -C sparse-index sparse-checkout init --cone --sparse-index &&
1033 test_all_match git status --porcelain=v2 &&
1035 # This checkout command should fail, because we have a staged
1036 # change to folder1/larger-content, but the destination changes
1037 # folder1 to a file.
1038 git -C full-checkout checkout df-conflict-2 \
1039 1>full-checkout-out \
1040 2>full-checkout-err &&
1041 git -C sparse-checkout checkout df-conflict-2 \
1042 1>sparse-checkout-out \
1043 2>sparse-checkout-err &&
1044 git -C sparse-index checkout df-conflict-2 \
1045 1>sparse-index-out \
1046 2>sparse-index-err &&
1048 # The full checkout deviates from the df-conflict-1 case here!
1049 # It drops the change to folder1/larger-content and leaves the
1050 # folder1 path as-is on disk. The sparse-index behaves the same.
1051 test_must_be_empty full-checkout-out &&
1052 test_must_be_empty sparse-index-out &&
1054 # In the sparse-checkout case, the checkout deletes the folder1
1055 # file and adds the folder1/larger-content file, leaving all other
1056 # paths that were in folder1/ as deleted (without any warning).
1057 cat >expect <<-EOF &&
1059 A folder2/larger-content
1061 test_cmp expect sparse-checkout-out &&
1063 # Switched to branch df-conflict-1
1064 test_cmp full-checkout-err sparse-checkout-err &&
1065 test_cmp full-checkout-err sparse-index-err