rebase: use correct base for --keep-base when a branch is given
[alt-git.git] / t / t1092-sparse-checkout-compatibility.sh
blob236ab5302844b21d5688ddaca5e98625c33406fa
1 #!/bin/sh
3 test_description='compare full workdir to sparse workdir'
5 GIT_TEST_SPLIT_INDEX=0
6 GIT_TEST_SPARSE_INDEX=
8 . ./test-lib.sh
10 test_expect_success 'setup' '
11 git init initial-repo &&
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
56 git checkout -b update-$dir base &&
57 echo "updated $dir" >$dir/a &&
58 git commit -a -m "update $dir" || return 1
59 done &&
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" &&
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/..." &&
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/..." &&
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/..." &&
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" &&
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" &&
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" &&
123 for side in left right
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 &&
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" &&
139 git checkout -f base &&
140 git reset --hard
144 init_repos () {
145 rm -rf full-checkout sparse-checkout sparse-index &&
147 # create repos in initial state
148 cp -r initial-repo full-checkout &&
149 git -C full-checkout reset --hard &&
151 cp -r initial-repo sparse-checkout &&
152 git -C sparse-checkout reset --hard &&
154 cp -r initial-repo sparse-index &&
155 git -C sparse-index reset --hard &&
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
165 run_on_sparse () {
167 cd sparse-checkout &&
168 GIT_PROGRESS_DELAY=100000 "$@" >../sparse-checkout-out 2>../sparse-checkout-err
169 ) &&
171 cd sparse-index &&
172 GIT_PROGRESS_DELAY=100000 "$@" >../sparse-index-out 2>../sparse-index-err
176 run_on_all () {
178 cd full-checkout &&
179 GIT_PROGRESS_DELAY=100000 "$@" >../full-checkout-out 2>../full-checkout-err
180 ) &&
181 run_on_sparse "$@"
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
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
198 test_sparse_unstaged () {
199 file=$1 &&
200 for repo in sparse-checkout sparse-index
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
208 test_expect_success 'sparse-index contents' '
209 init_repos &&
211 git -C sparse-index ls-files --sparse --stage >cache &&
212 for dir in folder1 folder2 x
214 TREE=$(git -C sparse-index rev-parse HEAD:$dir) &&
215 grep "040000 $TREE 0 $dir/" cache \
216 || return 1
217 done &&
219 git -C sparse-index sparse-checkout set folder1 &&
221 git -C sparse-index ls-files --sparse --stage >cache &&
222 for dir in deep folder2 x
224 TREE=$(git -C sparse-index rev-parse HEAD:$dir) &&
225 grep "040000 $TREE 0 $dir/" cache \
226 || return 1
227 done &&
229 git -C sparse-index sparse-checkout set deep/deeper1 &&
231 git -C sparse-index ls-files --sparse --stage >cache &&
232 for dir in deep/deeper2 folder1 folder2 x
234 TREE=$(git -C sparse-index rev-parse HEAD:$dir) &&
235 grep "040000 $TREE 0 $dir/" cache \
236 || return 1
237 done &&
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
244 test_expect_success 'expanded in-memory index matches full index' '
245 init_repos &&
246 test_sparse_match git ls-files --stage
249 test_expect_success 'root directory cannot be sparse' '
250 init_repos &&
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 &&
257 # Verify sparse directories still present, root directory is not sparse
258 cat >expect <<-EOF &&
259 before/
260 folder1/
261 folder2/
264 git -C sparse-index ls-files --sparse >actual &&
265 test_cmp expect actual
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
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
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
299 test_expect_success 'add, commit, checkout' '
300 init_repos &&
302 write_script edit-contents <<-\EOF &&
303 echo text >>$1
305 run_on_all ../edit-contents README.md &&
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" &&
311 test_all_match git checkout HEAD~1 &&
312 test_all_match git checkout - &&
314 run_on_all ../edit-contents README.md &&
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" &&
320 test_all_match git checkout HEAD~1 &&
321 test_all_match git checkout - &&
323 run_on_all ../edit-contents deep/newfile &&
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" &&
331 test_all_match git checkout HEAD~1 &&
332 test_all_match git checkout -
335 test_expect_success 'deep changes during checkout' '
336 init_repos &&
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
343 test_expect_success 'add outside sparse cone' '
344 init_repos &&
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
357 test_expect_success 'commit including unstaged changes' '
358 init_repos &&
360 write_script edit-file <<-\EOF &&
361 echo $1 >$2
364 run_on_all ../edit-file 1 a &&
365 run_on_all ../edit-file 1 deep/a &&
367 test_all_match git commit -m "-a" -a &&
368 test_all_match git status --porcelain=v2 &&
370 run_on_all ../edit-file 2 a &&
371 run_on_all ../edit-file 2 deep/a &&
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 &&
378 run_on_all ../edit-file 3 a &&
379 run_on_all ../edit-file 3 deep/a &&
381 test_all_match git commit -m "--amend" -a --amend &&
382 test_all_match git status --porcelain=v2
385 test_expect_success 'status/add: outside sparse cone' '
386 init_repos &&
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 &&
393 test_sparse_match git status &&
395 write_script edit-contents <<-\EOF &&
396 echo text >>$1
398 run_on_all ../edit-contents folder1/a &&
399 run_on_all ../edit-contents folder1/new &&
401 test_sparse_match git status --porcelain=v2 &&
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 &&
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} &&
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}
428 test_expect_success 'checkout and reset --hard' '
429 init_repos &&
431 test_all_match git checkout update-folder1 &&
432 test_all_match git status --porcelain=v2 &&
434 test_all_match git checkout update-deep &&
435 test_all_match git status --porcelain=v2 &&
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
443 test_expect_success 'diff --cached' '
444 init_repos &&
446 write_script edit-contents <<-\EOF &&
447 echo text >>README.md
449 run_on_all ../edit-contents &&
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
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 &&
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
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
477 test_expect_success 'diff with directory/file conflicts' '
478 init_repos &&
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
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
497 test_expect_success 'log with pathspec outside sparse definition' '
498 init_repos &&
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 &&
507 test_all_match git checkout update-folder1 &&
508 test_all_match git log -- folder1/a
511 test_expect_success 'blame with pathspec inside sparse definition' '
512 init_repos &&
514 for file in a \
515 deep/a \
516 deep/deeper1/a \
517 deep/deeper1/deepest/a
519 test_all_match git blame $file
520 done
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 &&
530 for file in a \
531 deep/a \
532 deep/deeper1/a \
533 deep/deeper1/deepest/a
535 test_sparse_match test_must_fail git blame $file &&
536 cat >expect <<-EOF &&
537 fatal: Cannot lstat '"'"'$file'"'"': No such file or directory
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
546 test_expect_success 'checkout and reset (mixed)' '
547 init_repos &&
549 test_all_match git checkout -b reset-test update-deep &&
550 test_all_match git reset deepest &&
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
562 test_expect_success 'checkout and reset (merge)' '
563 init_repos &&
565 write_script edit-contents <<-\EOF &&
566 echo text >>$1
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 &&
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
579 test_expect_success 'checkout and reset (keep)' '
580 init_repos &&
582 write_script edit-contents <<-\EOF &&
583 echo text >>$1
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 &&
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
596 test_expect_success 'reset with pathspecs inside sparse definition' '
597 init_repos &&
599 write_script edit-contents <<-\EOF &&
600 echo text >>$1
603 test_all_match git checkout -b reset-test update-deep &&
604 run_on_all ../edit-contents deep/a &&
606 test_all_match git reset base -- deep/a &&
607 test_all_match git status --porcelain=v2 &&
609 test_all_match git reset base -- nonexistent-file &&
610 test_all_match git status --porcelain=v2 &&
612 test_all_match git reset deepest -- deep &&
613 test_all_match git status --porcelain=v2
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 &&
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 &&
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
631 test_expect_success 'reset with wildcard pathspec' '
632 init_repos &&
634 test_all_match git reset update-deep -- deep\* &&
635 test_all_match git ls-files -s -- deep &&
637 test_all_match git reset deepest -- deep\*\*\* &&
638 test_all_match git ls-files -s -- deep &&
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.
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 &&
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 &&
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
658 test_expect_success 'update-index modify outside sparse definition' '
659 init_repos &&
661 write_script edit-contents <<-\EOF &&
662 echo text >>$1
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 &&
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 &&
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
688 test_expect_success 'update-index --add outside sparse definition' '
689 init_repos &&
691 write_script edit-contents <<-\EOF &&
692 echo text >>$1
695 # Create folder1, add new file
696 run_on_sparse mkdir -p folder1 &&
697 run_on_all ../edit-contents folder1/b &&
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
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 &&
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
721 test_sparse_match git diff --cached --name-status &&
722 test_cmp expect sparse-checkout-out &&
724 # Reset the state
725 test_all_match git reset --hard &&
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 &&
733 # Reset the state
734 test_all_match git reset --hard &&
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
743 test_sparse_match git diff --cached --name-status &&
744 test_cmp expect sparse-checkout-out
747 test_expect_success 'update-index with directories' '
748 init_repos &&
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 &&
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
769 test_expect_success 'update-index --again file outside sparse definition' '
770 init_repos &&
772 test_all_match git checkout -b test-reupdate &&
774 # Update HEAD without modifying the index to introduce a difference in
775 # folder1/a
776 test_sparse_match git reset --soft update-folder1 &&
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 &&
785 cat >expect <<-EOF &&
786 D folder1/a
788 test_sparse_match git diff --name-status &&
789 test_cmp expect sparse-checkout-out
792 test_expect_success 'update-index --cacheinfo' '
793 init_repos &&
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) &&
799 test_all_match git update-index --cacheinfo 100644 $deep_a_oid deep/a &&
800 test_all_match git status --porcelain=v2 &&
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/ &&
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
812 test_sparse_match git status --short -- folder1/a &&
813 test_cmp expect sparse-checkout-out &&
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
821 test_sparse_match git ls-files -t -- folder1/a &&
822 test_cmp expect sparse-checkout-out
825 for MERGE_TREES in "base HEAD update-folder2" \
826 "update-folder1 update-folder2" \
827 "update-folder2"
829 test_expect_success "'read-tree -mu $MERGE_TREES' with files outside sparse definition" '
830 init_repos &&
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 &&
839 test_all_match git read-tree --reset -u HEAD &&
840 test_all_match git status --porcelain=v2 &&
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
848 done
850 test_expect_success 'read-tree --merge with edit/edit conflicts in sparse directories' '
851 init_repos &&
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 &&
858 test_all_match git reset --hard &&
860 test_all_match git read-tree -mu rename-base update-folder2 &&
861 test_all_match git status --porcelain=v2 &&
863 test_all_match git reset --hard &&
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
869 test_expect_success 'read-tree --prefix' '
870 init_repos &&
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 &&
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 &&
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 &&
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
892 test_expect_success 'read-tree --merge with directory-file conflicts' '
893 init_repos &&
895 test_all_match git checkout -b test-branch rename-base &&
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 &&
904 test_sparse_match git read-tree --reset -u HEAD &&
905 test_sparse_match git status --porcelain=v2 &&
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
912 test_expect_success 'merge, cherry-pick, and rebase' '
913 init_repos &&
915 for OPERATION in "merge -m merge" cherry-pick "rebase --apply" "rebase --merge"
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
925 test_expect_success 'merge with conflict outside cone' '
926 init_repos &&
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 &&
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 &&
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 &&
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 &&
954 test_all_match git merge --continue &&
955 test_all_match git status --porcelain=v2 &&
956 test_all_match git rev-parse HEAD^{tree}
959 test_expect_success 'cherry-pick/rebase with conflict outside cone' '
960 init_repos &&
962 for OPERATION in cherry-pick rebase
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 &&
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 &&
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 &&
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 &&
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
1003 test_expect_success 'merge with outside renames' '
1004 init_repos &&
1006 for type in out-to-out out-to-in in-to-out
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
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 &&
1020 write_script edit-conflict <<-\EOF &&
1021 echo $1 >conflict
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" &&
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" &&
1034 test_all_match test_must_fail git cherry-pick to-cherry-pick
1037 test_expect_success 'checkout-index inside sparse definition' '
1038 init_repos &&
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 &&
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
1051 test_expect_success 'checkout-index outside sparse definition' '
1052 init_repos &&
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 &&
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 &&
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 &&
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
1076 test_expect_success 'checkout-index with folders' '
1077 init_repos &&
1079 # Inside checkout definition
1080 test_all_match test_must_fail git checkout-index -f -- deep/ &&
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
1093 test_expect_success 'checkout-index --all' '
1094 init_repos &&
1096 test_all_match git checkout-index --all &&
1097 test_sparse_match test_path_is_missing folder1 &&
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
1105 test_expect_success 'clean' '
1106 init_repos &&
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" &&
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 &&
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 &&
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 &&
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 &&
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 &&
1151 test_sparse_match test_path_is_dir folder1
1154 test_expect_success 'submodule handling' '
1155 init_repos &&
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" &&
1163 run_on_all git submodule add "$(pwd)/initial-repo" modules/sub &&
1164 test_all_match git commit -m "add submodule" &&
1166 # having a submodule prevents "modules" from collapse
1167 test_sparse_match git sparse-checkout set deep/deeper1 &&
1168 git -C sparse-index ls-files --sparse --stage >cache &&
1169 grep "100644 .* modules/a" cache &&
1170 grep "160000 $(git -C initial-repo rev-parse HEAD) 0 modules/sub" cache
1173 # When working with a sparse index, some commands will need to expand the
1174 # index to operate properly. If those commands also write the index back
1175 # to disk, they need to convert the index to sparse before writing.
1176 # This test verifies that both of these events are logged in trace2 logs.
1177 test_expect_success 'sparse-index is expanded and converted back' '
1178 init_repos &&
1180 GIT_TRACE2_EVENT="$(pwd)/trace2.txt" \
1181 git -C sparse-index reset -- folder1/a &&
1182 test_region index convert_to_sparse trace2.txt &&
1183 test_region index ensure_full_index trace2.txt &&
1185 # ls-files expands on read, but does not write.
1186 rm trace2.txt &&
1187 GIT_TRACE2_EVENT="$(pwd)/trace2.txt" GIT_TRACE2_EVENT_NESTING=10 \
1188 git -C sparse-index ls-files &&
1189 test_region index ensure_full_index trace2.txt
1192 test_expect_success 'index.sparse disabled inline uses full index' '
1193 init_repos &&
1195 # When index.sparse is disabled inline with `git status`, the
1196 # index is expanded at the beginning of the execution then never
1197 # converted back to sparse. It is then written to disk as a full index.
1198 rm -f trace2.txt &&
1199 GIT_TRACE2_EVENT="$(pwd)/trace2.txt" GIT_TRACE2_EVENT_NESTING=10 \
1200 git -C sparse-index -c index.sparse=false status &&
1201 ! test_region index convert_to_sparse trace2.txt &&
1202 test_region index ensure_full_index trace2.txt &&
1204 # Since index.sparse is set to true at a repo level, the index
1205 # is converted from full to sparse when read, then never expanded
1206 # over the course of `git status`. It is written to disk as a sparse
1207 # index.
1208 rm -f trace2.txt &&
1209 GIT_TRACE2_EVENT="$(pwd)/trace2.txt" GIT_TRACE2_EVENT_NESTING=10 \
1210 git -C sparse-index status &&
1211 test_region index convert_to_sparse trace2.txt &&
1212 ! test_region index ensure_full_index trace2.txt &&
1214 # Now that the index has been written to disk as sparse, it is not
1215 # converted to sparse (or expanded to full) when read by `git status`.
1216 rm -f trace2.txt &&
1217 GIT_TRACE2_EVENT="$(pwd)/trace2.txt" GIT_TRACE2_EVENT_NESTING=10 \
1218 git -C sparse-index status &&
1219 ! test_region index convert_to_sparse trace2.txt &&
1220 ! test_region index ensure_full_index trace2.txt
1223 ensure_not_expanded () {
1224 rm -f trace2.txt &&
1225 echo >>sparse-index/untracked.txt &&
1227 if test "$1" = "!"
1228 then
1229 shift &&
1230 test_must_fail env \
1231 GIT_TRACE2_EVENT="$(pwd)/trace2.txt" \
1232 git -C sparse-index "$@" || return 1
1233 else
1234 GIT_TRACE2_EVENT="$(pwd)/trace2.txt" \
1235 git -C sparse-index "$@" || return 1
1236 fi &&
1237 test_region ! index ensure_full_index trace2.txt
1240 test_expect_success 'sparse-index is not expanded' '
1241 init_repos &&
1243 ensure_not_expanded status &&
1244 ensure_not_expanded ls-files --sparse &&
1245 ensure_not_expanded commit --allow-empty -m empty &&
1246 echo >>sparse-index/a &&
1247 ensure_not_expanded commit -a -m a &&
1248 echo >>sparse-index/a &&
1249 ensure_not_expanded commit --include a -m a &&
1250 echo >>sparse-index/deep/deeper1/a &&
1251 ensure_not_expanded commit --include deep/deeper1/a -m deeper &&
1252 ensure_not_expanded checkout rename-out-to-out &&
1253 ensure_not_expanded checkout - &&
1254 ensure_not_expanded switch rename-out-to-out &&
1255 ensure_not_expanded switch - &&
1256 ensure_not_expanded reset --hard &&
1257 ensure_not_expanded checkout rename-out-to-out -- deep/deeper1 &&
1258 ensure_not_expanded reset --hard &&
1259 ensure_not_expanded restore -s rename-out-to-out -- deep/deeper1 &&
1261 echo >>sparse-index/README.md &&
1262 ensure_not_expanded add -A &&
1263 echo >>sparse-index/extra.txt &&
1264 ensure_not_expanded add extra.txt &&
1265 echo >>sparse-index/untracked.txt &&
1266 ensure_not_expanded add . &&
1268 ensure_not_expanded checkout-index -f a &&
1269 ensure_not_expanded checkout-index -f --all &&
1270 for ref in update-deep update-folder1 update-folder2 update-deep
1272 echo >>sparse-index/README.md &&
1273 ensure_not_expanded reset --hard $ref || return 1
1274 done &&
1276 ensure_not_expanded reset --mixed base &&
1277 ensure_not_expanded reset --hard update-deep &&
1278 ensure_not_expanded reset --keep base &&
1279 ensure_not_expanded reset --merge update-deep &&
1280 ensure_not_expanded reset --hard &&
1282 ensure_not_expanded reset base -- deep/a &&
1283 ensure_not_expanded reset base -- nonexistent-file &&
1284 ensure_not_expanded reset deepest -- deep &&
1286 # Although folder1 is outside the sparse definition, it exists as a
1287 # directory entry in the index, so the pathspec will not force the
1288 # index to be expanded.
1289 ensure_not_expanded reset deepest -- folder1 &&
1290 ensure_not_expanded reset deepest -- folder1/ &&
1292 # Wildcard identifies only in-cone files, no index expansion
1293 ensure_not_expanded reset deepest -- deep/\* &&
1295 # Wildcard identifies only full sparse directories, no index expansion
1296 ensure_not_expanded reset deepest -- folder\* &&
1298 ensure_not_expanded clean -fd &&
1300 ensure_not_expanded checkout -f update-deep &&
1301 test_config -C sparse-index pull.twohead ort &&
1303 sane_unset GIT_TEST_MERGE_ALGORITHM &&
1304 for OPERATION in "merge -m merge" cherry-pick rebase
1306 ensure_not_expanded merge -m merge update-folder1 &&
1307 ensure_not_expanded merge -m merge update-folder2 || return 1
1308 done
1312 test_expect_success 'sparse-index is not expanded: merge conflict in cone' '
1313 init_repos &&
1315 for side in right left
1317 git -C sparse-index checkout -b expand-$side base &&
1318 echo $side >sparse-index/deep/a &&
1319 git -C sparse-index commit -a -m "$side" || return 1
1320 done &&
1323 sane_unset GIT_TEST_MERGE_ALGORITHM &&
1324 git -C sparse-index config pull.twohead ort &&
1325 ensure_not_expanded ! merge -m merged expand-right
1329 test_expect_success 'sparse index is not expanded: diff' '
1330 init_repos &&
1332 write_script edit-contents <<-\EOF &&
1333 echo text >>$1
1336 # Add file within cone
1337 test_sparse_match git sparse-checkout set deep &&
1338 run_on_all ../edit-contents deep/testfile &&
1339 test_all_match git add deep/testfile &&
1340 run_on_all ../edit-contents deep/testfile &&
1342 test_all_match git diff &&
1343 test_all_match git diff --cached &&
1344 ensure_not_expanded diff &&
1345 ensure_not_expanded diff --cached &&
1347 # Add file outside cone
1348 test_all_match git reset --hard &&
1349 run_on_all mkdir newdirectory &&
1350 run_on_all ../edit-contents newdirectory/testfile &&
1351 test_sparse_match git sparse-checkout set newdirectory &&
1352 test_all_match git add newdirectory/testfile &&
1353 run_on_all ../edit-contents newdirectory/testfile &&
1354 test_sparse_match git sparse-checkout set &&
1356 test_all_match git diff &&
1357 test_all_match git diff --cached &&
1358 ensure_not_expanded diff &&
1359 ensure_not_expanded diff --cached &&
1361 # Merge conflict outside cone
1362 # The sparse checkout will report a warning that is not in the
1363 # full checkout, so we use `run_on_all` instead of
1364 # `test_all_match`
1365 run_on_all git reset --hard &&
1366 test_all_match git checkout merge-left &&
1367 test_all_match test_must_fail git merge merge-right &&
1369 test_all_match git diff &&
1370 test_all_match git diff --cached &&
1371 ensure_not_expanded diff &&
1372 ensure_not_expanded diff --cached
1375 test_expect_success 'sparse index is not expanded: update-index' '
1376 init_repos &&
1378 deep_a_oid=$(git -C full-checkout rev-parse update-deep:deep/a) &&
1379 ensure_not_expanded update-index --cacheinfo 100644 $deep_a_oid deep/a &&
1381 echo "test" >sparse-index/README.md &&
1382 echo "test2" >sparse-index/a &&
1383 rm -f sparse-index/deep/a &&
1385 ensure_not_expanded update-index --add README.md &&
1386 ensure_not_expanded update-index a &&
1387 ensure_not_expanded update-index --remove deep/a &&
1389 ensure_not_expanded reset --soft update-deep &&
1390 ensure_not_expanded update-index --add --remove --again
1393 test_expect_success 'sparse index is not expanded: blame' '
1394 init_repos &&
1396 for file in a \
1397 deep/a \
1398 deep/deeper1/a \
1399 deep/deeper1/deepest/a
1401 ensure_not_expanded blame $file
1402 done
1405 test_expect_success 'sparse index is not expanded: fetch/pull' '
1406 init_repos &&
1408 git -C sparse-index remote add full "file://$(pwd)/full-checkout" &&
1409 ensure_not_expanded fetch full &&
1410 git -C full-checkout commit --allow-empty -m "for pull merge" &&
1411 git -C sparse-index commit --allow-empty -m "for pull merge" &&
1412 ensure_not_expanded pull full base
1415 test_expect_success 'sparse index is not expanded: read-tree' '
1416 init_repos &&
1418 ensure_not_expanded checkout -b test-branch update-folder1 &&
1419 for MERGE_TREES in "base HEAD update-folder2" \
1420 "base HEAD rename-base" \
1421 "base update-folder2" \
1422 "base rename-base" \
1423 "update-folder2"
1425 ensure_not_expanded read-tree -mu $MERGE_TREES &&
1426 ensure_not_expanded reset --hard || return 1
1427 done &&
1429 rm -rf sparse-index/deep/deeper2 &&
1430 ensure_not_expanded add . &&
1431 ensure_not_expanded commit -m "test" &&
1433 ensure_not_expanded read-tree --prefix=deep/deeper2 -u deepest
1436 test_expect_success 'ls-files' '
1437 init_repos &&
1439 # Use a smaller sparse-checkout for reduced output
1440 test_sparse_match git sparse-checkout set &&
1442 # Behavior agrees by default. Sparse index is expanded.
1443 test_all_match git ls-files &&
1445 # With --sparse, the sparse index data changes behavior.
1446 git -C sparse-index ls-files --sparse >actual &&
1448 cat >expect <<-\EOF &&
1450 before/
1451 deep/
1453 folder1-
1454 folder1.x
1455 folder1/
1456 folder10
1457 folder2/
1463 test_cmp expect actual &&
1465 # With --sparse and no sparse index, nothing changes.
1466 git -C sparse-checkout ls-files >dense &&
1467 git -C sparse-checkout ls-files --sparse >sparse &&
1468 test_cmp dense sparse &&
1470 # Set up a strange condition of having a file edit
1471 # outside of the sparse-checkout cone. We want to verify
1472 # that all modes handle this the same, and detect the
1473 # modification.
1474 write_script edit-content <<-\EOF &&
1475 mkdir -p folder1 &&
1476 echo content >>folder1/a
1478 run_on_all ../edit-content &&
1480 test_all_match git ls-files --modified &&
1482 git -C sparse-index ls-files --sparse --modified >sparse-index-out &&
1483 cat >expect <<-\EOF &&
1484 folder1/a
1486 test_cmp expect sparse-index-out &&
1488 # Add folder1 to the sparse-checkout cone and
1489 # check that ls-files shows the expanded files.
1490 test_sparse_match git sparse-checkout add folder1 &&
1491 test_all_match git ls-files --modified &&
1493 test_all_match git ls-files &&
1494 git -C sparse-index ls-files --sparse >actual &&
1496 cat >expect <<-\EOF &&
1498 before/
1499 deep/
1501 folder1-
1502 folder1.x
1503 folder1/0/0/0
1504 folder1/0/1
1505 folder1/a
1506 folder10
1507 folder2/
1513 test_cmp expect actual &&
1515 # Double-check index expansion is avoided
1516 ensure_not_expanded ls-files --sparse
1519 # NEEDSWORK: a sparse-checkout behaves differently from a full checkout
1520 # in this scenario, but it shouldn't.
1521 test_expect_success 'reset mixed and checkout orphan' '
1522 init_repos &&
1524 test_all_match git checkout rename-out-to-in &&
1526 # Sparse checkouts do not agree with full checkouts about
1527 # how to report a directory/file conflict during a reset.
1528 # This command would fail with test_all_match because the
1529 # full checkout reports "T folder1/0/1" while a sparse
1530 # checkout reports "D folder1/0/1". This matches because
1531 # the sparse checkouts skip "adding" the other side of
1532 # the conflict.
1533 test_sparse_match git reset --mixed HEAD~1 &&
1534 test_sparse_match git ls-files --stage &&
1535 test_sparse_match git status --porcelain=v2 &&
1537 # At this point, sparse-checkouts behave differently
1538 # from the full-checkout.
1539 test_sparse_match git checkout --orphan new-branch &&
1540 test_sparse_match git ls-files --stage &&
1541 test_sparse_match git status --porcelain=v2
1544 test_expect_success 'add everything with deep new file' '
1545 init_repos &&
1547 run_on_sparse git sparse-checkout set deep/deeper1/deepest &&
1549 run_on_all touch deep/deeper1/x &&
1550 test_all_match git add . &&
1551 test_all_match git status --porcelain=v2
1554 # NEEDSWORK: 'git checkout' behaves incorrectly in the case of
1555 # directory/file conflicts, even without sparse-checkout. Use this
1556 # test only as a documentation of the incorrect behavior, not a
1557 # measure of how it _should_ behave.
1558 test_expect_success 'checkout behaves oddly with df-conflict-1' '
1559 init_repos &&
1561 test_sparse_match git sparse-checkout disable &&
1563 write_script edit-content <<-\EOF &&
1564 echo content >>folder1/larger-content
1565 git add folder1
1568 run_on_all ../edit-content &&
1569 test_all_match git status --porcelain=v2 &&
1571 git -C sparse-checkout sparse-checkout init --cone &&
1572 git -C sparse-index sparse-checkout init --cone --sparse-index &&
1574 test_all_match git status --porcelain=v2 &&
1576 # This checkout command should fail, because we have a staged
1577 # change to folder1/larger-content, but the destination changes
1578 # folder1 to a file.
1579 git -C full-checkout checkout df-conflict-1 \
1580 1>full-checkout-out \
1581 2>full-checkout-err &&
1582 git -C sparse-checkout checkout df-conflict-1 \
1583 1>sparse-checkout-out \
1584 2>sparse-checkout-err &&
1585 git -C sparse-index checkout df-conflict-1 \
1586 1>sparse-index-out \
1587 2>sparse-index-err &&
1589 # Instead, the checkout deletes the folder1 file and adds the
1590 # folder1/larger-content file, leaving all other paths that were
1591 # in folder1/ as deleted (without any warning).
1592 cat >expect <<-EOF &&
1593 D folder1
1594 A folder1/larger-content
1596 test_cmp expect full-checkout-out &&
1597 test_cmp expect sparse-checkout-out &&
1599 # The sparse-index reports no output
1600 test_must_be_empty sparse-index-out &&
1602 # stderr: Switched to branch df-conflict-1
1603 test_cmp full-checkout-err sparse-checkout-err &&
1604 test_cmp full-checkout-err sparse-checkout-err
1607 # NEEDSWORK: 'git checkout' behaves incorrectly in the case of
1608 # directory/file conflicts, even without sparse-checkout. Use this
1609 # test only as a documentation of the incorrect behavior, not a
1610 # measure of how it _should_ behave.
1611 test_expect_success 'checkout behaves oddly with df-conflict-2' '
1612 init_repos &&
1614 test_sparse_match git sparse-checkout disable &&
1616 write_script edit-content <<-\EOF &&
1617 echo content >>folder2/larger-content
1618 git add folder2
1621 run_on_all ../edit-content &&
1622 test_all_match git status --porcelain=v2 &&
1624 git -C sparse-checkout sparse-checkout init --cone &&
1625 git -C sparse-index sparse-checkout init --cone --sparse-index &&
1627 test_all_match git status --porcelain=v2 &&
1629 # This checkout command should fail, because we have a staged
1630 # change to folder1/larger-content, but the destination changes
1631 # folder1 to a file.
1632 git -C full-checkout checkout df-conflict-2 \
1633 1>full-checkout-out \
1634 2>full-checkout-err &&
1635 git -C sparse-checkout checkout df-conflict-2 \
1636 1>sparse-checkout-out \
1637 2>sparse-checkout-err &&
1638 git -C sparse-index checkout df-conflict-2 \
1639 1>sparse-index-out \
1640 2>sparse-index-err &&
1642 # The full checkout deviates from the df-conflict-1 case here!
1643 # It drops the change to folder1/larger-content and leaves the
1644 # folder1 path as-is on disk. The sparse-index behaves the same.
1645 test_must_be_empty full-checkout-out &&
1646 test_must_be_empty sparse-index-out &&
1648 # In the sparse-checkout case, the checkout deletes the folder1
1649 # file and adds the folder1/larger-content file, leaving all other
1650 # paths that were in folder1/ as deleted (without any warning).
1651 cat >expect <<-EOF &&
1652 D folder2
1653 A folder2/larger-content
1655 test_cmp expect sparse-checkout-out &&
1657 # Switched to branch df-conflict-1
1658 test_cmp full-checkout-err sparse-checkout-err &&
1659 test_cmp full-checkout-err sparse-index-err
1662 test_done