]> git.ipfire.org Git - thirdparty/git.git/blob - t/t1092-sparse-checkout-compatibility.sh
Merge branch 'vd/sparse-sparsity-fix-on-read'
[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 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 &&
24 cp a folder1 &&
25 cp a folder2 &&
26 cp a x &&
27 cp a deep &&
28 cp a deep/before &&
29 cp a deep/deeper1 &&
30 cp a deep/deeper2 &&
31 cp a deep/later &&
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 &&
38 >folder1- &&
39 >folder1.x &&
40 >folder10 &&
41 cp -r deep/deeper1/0 folder1 &&
42 cp -r deep/deeper1/0 folder2 &&
43 echo >>folder1/0/0/0 &&
44 echo >>folder2/0/1 &&
45 git add . &&
46 git commit -m "initial commit" &&
47 git checkout -b base &&
48 for dir in folder1 folder2 deep
49 do
50 git checkout -b update-$dir base &&
51 echo "updated $dir" >$dir/a &&
52 git commit -a -m "update $dir" || return 1
53 done &&
54
55 git checkout -b rename-base base &&
56 cat >folder1/larger-content <<-\EOF &&
57 matching
58 lines
59 help
60 inexact
61 renames
62 EOF
63 cp folder1/larger-content folder2/ &&
64 cp folder1/larger-content deep/deeper1/ &&
65 git add . &&
66 git commit -m "add interesting rename content" &&
67
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 &&
72 echo >>folder2/0/1 &&
73 echo stuff >>deep/deeper1/a &&
74 git add . &&
75 git commit -m "rename folder1/... to folder2/..." &&
76
77 git checkout -b rename-out-to-in rename-base &&
78 mv folder1/a deep/deeper1/b &&
79 echo more stuff >>deep/deeper1/a &&
80 rm folder2/0/1 &&
81 mkdir folder2/0/1 &&
82 echo >>folder2/0/1/1 &&
83 mv folder1/larger-content deep/deeper1/edited-content &&
84 echo >>deep/deeper1/edited-content &&
85 git add . &&
86 git commit -m "rename folder1/... to deep/deeper1/..." &&
87
88 git checkout -b rename-in-to-out rename-base &&
89 mv deep/deeper1/a folder1/b &&
90 echo >>folder2/0/1 &&
91 rm -rf folder1/0/0 &&
92 echo >>folder1/0/0 &&
93 mv deep/deeper1/larger-content folder1/edited-content &&
94 echo >>folder1/edited-content &&
95 git add . &&
96 git commit -m "rename deep/deeper1/... to folder1/..." &&
97
98 git checkout -b df-conflict-1 base &&
99 rm -rf folder1 &&
100 echo content >folder1 &&
101 git add . &&
102 git commit -m "dir to file" &&
103
104 git checkout -b df-conflict-2 base &&
105 rm -rf folder2 &&
106 echo content >folder2 &&
107 git add . &&
108 git commit -m "dir to file" &&
109
110 git checkout -b fd-conflict base &&
111 rm a &&
112 mkdir a &&
113 echo content >a/a &&
114 git add . &&
115 git commit -m "file to dir" &&
116
117 for side in left right
118 do
119 git checkout -b merge-$side base &&
120 echo $side >>deep/deeper2/a &&
121 echo $side >>folder1/a &&
122 echo $side >>folder2/a &&
123 git add . &&
124 git commit -m "$side" || return 1
125 done &&
126
127 git checkout -b deepest base &&
128 echo "updated deepest" >deep/deeper1/deepest/a &&
129 git commit -a -m "update deepest" &&
130
131 git checkout -f base &&
132 git reset --hard
133 )
134 '
135
136 init_repos () {
137 rm -rf full-checkout sparse-checkout sparse-index &&
138
139 # create repos in initial state
140 cp -r initial-repo full-checkout &&
141 git -C full-checkout reset --hard &&
142
143 cp -r initial-repo sparse-checkout &&
144 git -C sparse-checkout reset --hard &&
145
146 cp -r initial-repo sparse-index &&
147 git -C sparse-index reset --hard &&
148
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
155 }
156
157 run_on_sparse () {
158 (
159 cd sparse-checkout &&
160 GIT_PROGRESS_DELAY=100000 "$@" >../sparse-checkout-out 2>../sparse-checkout-err
161 ) &&
162 (
163 cd sparse-index &&
164 GIT_PROGRESS_DELAY=100000 "$@" >../sparse-index-out 2>../sparse-index-err
165 )
166 }
167
168 run_on_all () {
169 (
170 cd full-checkout &&
171 GIT_PROGRESS_DELAY=100000 "$@" >../full-checkout-out 2>../full-checkout-err
172 ) &&
173 run_on_sparse "$@"
174 }
175
176 test_all_match () {
177 run_on_all "$@" &&
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
182 }
183
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
188 }
189
190 test_sparse_unstaged () {
191 file=$1 &&
192 for repo in sparse-checkout sparse-index
193 do
194 # Skip "unmerged" paths
195 git -C $repo diff --staged --diff-filter=u -- "$file" >diff &&
196 test_must_be_empty diff || return 1
197 done
198 }
199
200 test_expect_success 'sparse-index contents' '
201 init_repos &&
202
203 test-tool -C sparse-index read-cache --table >cache &&
204 for dir in folder1 folder2 x
205 do
206 TREE=$(git -C sparse-index rev-parse HEAD:$dir) &&
207 grep "040000 tree $TREE $dir/" cache \
208 || return 1
209 done &&
210
211 git -C sparse-index sparse-checkout set folder1 &&
212
213 test-tool -C sparse-index read-cache --table >cache &&
214 for dir in deep folder2 x
215 do
216 TREE=$(git -C sparse-index rev-parse HEAD:$dir) &&
217 grep "040000 tree $TREE $dir/" cache \
218 || return 1
219 done &&
220
221 git -C sparse-index sparse-checkout set deep/deeper1 &&
222
223 test-tool -C sparse-index read-cache --table >cache &&
224 for dir in deep/deeper2 folder1 folder2 x
225 do
226 TREE=$(git -C sparse-index rev-parse HEAD:$dir) &&
227 grep "040000 tree $TREE $dir/" cache \
228 || return 1
229 done &&
230
231 # Disabling the sparse-index removes tree entries with full ones
232 git -C sparse-index sparse-checkout init --no-sparse-index &&
233
234 test-tool -C sparse-index read-cache --table >cache &&
235 ! grep "040000 tree" cache &&
236 test_sparse_match test-tool read-cache --table
237 '
238
239 test_expect_success 'expanded in-memory index matches full index' '
240 init_repos &&
241 test_sparse_match test-tool read-cache --expand --table
242 '
243
244 test_expect_success 'status with options' '
245 init_repos &&
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
258 '
259
260 test_expect_success 'status reports sparse-checkout' '
261 init_repos &&
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
266 '
267
268 test_expect_success 'add, commit, checkout' '
269 init_repos &&
270
271 write_script edit-contents <<-\EOF &&
272 echo text >>$1
273 EOF
274 run_on_all ../edit-contents README.md &&
275
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" &&
279
280 test_all_match git checkout HEAD~1 &&
281 test_all_match git checkout - &&
282
283 run_on_all ../edit-contents README.md &&
284
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" &&
288
289 test_all_match git checkout HEAD~1 &&
290 test_all_match git checkout - &&
291
292 run_on_all ../edit-contents deep/newfile &&
293
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" &&
299
300 test_all_match git checkout HEAD~1 &&
301 test_all_match git checkout -
302 '
303
304 test_expect_success 'add outside sparse cone' '
305 init_repos &&
306
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
316 '
317
318 test_expect_success 'commit including unstaged changes' '
319 init_repos &&
320
321 write_script edit-file <<-\EOF &&
322 echo $1 >$2
323 EOF
324
325 run_on_all ../edit-file 1 a &&
326 run_on_all ../edit-file 1 deep/a &&
327
328 test_all_match git commit -m "-a" -a &&
329 test_all_match git status --porcelain=v2 &&
330
331 run_on_all ../edit-file 2 a &&
332 run_on_all ../edit-file 2 deep/a &&
333
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 &&
338
339 run_on_all ../edit-file 3 a &&
340 run_on_all ../edit-file 3 deep/a &&
341
342 test_all_match git commit -m "--amend" -a --amend &&
343 test_all_match git status --porcelain=v2
344 '
345
346 test_expect_success 'status/add: outside sparse cone' '
347 init_repos &&
348
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 &&
353
354 test_sparse_match git status &&
355
356 write_script edit-contents <<-\EOF &&
357 echo text >>$1
358 EOF
359 run_on_sparse ../edit-contents folder1/a &&
360 run_on_all ../edit-contents folder1/new &&
361
362 test_sparse_match git status --porcelain=v2 &&
363
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 &&
376
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} &&
381
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}
387 '
388
389 test_expect_success 'checkout and reset --hard' '
390 init_repos &&
391
392 test_all_match git checkout update-folder1 &&
393 test_all_match git status --porcelain=v2 &&
394
395 test_all_match git checkout update-deep &&
396 test_all_match git status --porcelain=v2 &&
397
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
402 '
403
404 test_expect_success 'diff --staged' '
405 init_repos &&
406
407 write_script edit-contents <<-\EOF &&
408 echo text >>README.md
409 EOF
410 run_on_all ../edit-contents &&
411
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
417 '
418
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' '
422 init_repos &&
423
424 for branch in rename-out-to-out \
425 rename-out-to-in \
426 rename-in-to-out \
427 df-conflict-1 \
428 fd-conflict
429 do
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
435 done
436 '
437
438 test_expect_success 'diff with directory/file conflicts' '
439 init_repos &&
440
441 for branch in rename-out-to-out \
442 rename-out-to-in \
443 rename-in-to-out \
444 df-conflict-1 \
445 df-conflict-2 \
446 fd-conflict
447 do
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
455 done
456 '
457
458 test_expect_success 'log with pathspec outside sparse definition' '
459 init_repos &&
460
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 &&
467
468 test_all_match git checkout update-folder1 &&
469 test_all_match git log -- folder1/a
470 '
471
472 test_expect_success 'blame with pathspec inside sparse definition' '
473 init_repos &&
474
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
479 '
480
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' '
484 init_repos &&
485
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
490 '
491
492 # NEEDSWORK: a sparse-checkout behaves differently from a full checkout
493 # in this scenario, but it shouldn't.
494 test_expect_failure 'checkout and reset (mixed)' '
495 init_repos &&
496
497 test_all_match git checkout -b reset-test update-deep &&
498 test_all_match git reset deepest &&
499 test_all_match git reset update-folder1 &&
500 test_all_match git reset update-folder2
501 '
502
503 # NEEDSWORK: a sparse-checkout behaves differently from a full checkout
504 # in this scenario, but it shouldn't.
505 test_expect_success 'checkout and reset (mixed) [sparse]' '
506 init_repos &&
507
508 test_sparse_match git checkout -b reset-test update-deep &&
509 test_sparse_match git reset deepest &&
510 test_sparse_match git reset update-folder1 &&
511 test_sparse_match git reset update-folder2
512 '
513
514 test_expect_success 'merge, cherry-pick, and rebase' '
515 init_repos &&
516
517 for OPERATION in "merge -m merge" cherry-pick "rebase --apply" "rebase --merge"
518 do
519 test_all_match git checkout -B temp update-deep &&
520 test_all_match git $OPERATION update-folder1 &&
521 test_all_match git rev-parse HEAD^{tree} &&
522 test_all_match git $OPERATION update-folder2 &&
523 test_all_match git rev-parse HEAD^{tree} || return 1
524 done
525 '
526
527 test_expect_success 'merge with conflict outside cone' '
528 init_repos &&
529
530 test_all_match git checkout -b merge-tip merge-left &&
531 test_all_match git status --porcelain=v2 &&
532 test_all_match test_must_fail git merge -m merge merge-right &&
533 test_all_match git status --porcelain=v2 &&
534
535 # Resolve the conflict in different ways:
536 # 1. Revert to the base
537 test_all_match git checkout base -- deep/deeper2/a &&
538 test_all_match git status --porcelain=v2 &&
539
540 # 2. Add the file with conflict markers
541 test_sparse_match test_must_fail git add folder1/a &&
542 grep "Disable or modify the sparsity rules" sparse-checkout-err &&
543 test_sparse_unstaged folder1/a &&
544 test_all_match git add --sparse folder1/a &&
545 test_all_match git status --porcelain=v2 &&
546
547 # 3. Rename the file to another sparse filename and
548 # accept conflict markers as resolved content.
549 run_on_all mv folder2/a folder2/z &&
550 test_sparse_match test_must_fail git add folder2 &&
551 grep "Disable or modify the sparsity rules" sparse-checkout-err &&
552 test_sparse_unstaged folder2/z &&
553 test_all_match git add --sparse folder2 &&
554 test_all_match git status --porcelain=v2 &&
555
556 test_all_match git merge --continue &&
557 test_all_match git status --porcelain=v2 &&
558 test_all_match git rev-parse HEAD^{tree}
559 '
560
561 test_expect_success 'cherry-pick/rebase with conflict outside cone' '
562 init_repos &&
563
564 for OPERATION in cherry-pick rebase
565 do
566 test_all_match git checkout -B tip &&
567 test_all_match git reset --hard merge-left &&
568 test_all_match git status --porcelain=v2 &&
569 test_all_match test_must_fail git $OPERATION merge-right &&
570 test_all_match git status --porcelain=v2 &&
571
572 # Resolve the conflict in different ways:
573 # 1. Revert to the base
574 test_all_match git checkout base -- deep/deeper2/a &&
575 test_all_match git status --porcelain=v2 &&
576
577 # 2. Add the file with conflict markers
578 # NEEDSWORK: Even though the merge conflict removed the
579 # SKIP_WORKTREE bit from the index entry for folder1/a, we should
580 # warn that this is a problematic add.
581 test_sparse_match test_must_fail git add folder1/a &&
582 grep "Disable or modify the sparsity rules" sparse-checkout-err &&
583 test_sparse_unstaged folder1/a &&
584 test_all_match git add --sparse folder1/a &&
585 test_all_match git status --porcelain=v2 &&
586
587 # 3. Rename the file to another sparse filename and
588 # accept conflict markers as resolved content.
589 # NEEDSWORK: This mode now fails, because folder2/z is
590 # outside of the sparse-checkout cone and does not match an
591 # existing index entry with the SKIP_WORKTREE bit cleared.
592 run_on_all mv folder2/a folder2/z &&
593 test_sparse_match test_must_fail git add folder2 &&
594 grep "Disable or modify the sparsity rules" sparse-checkout-err &&
595 test_sparse_unstaged folder2/z &&
596 test_all_match git add --sparse folder2 &&
597 test_all_match git status --porcelain=v2 &&
598
599 test_all_match git $OPERATION --continue &&
600 test_all_match git status --porcelain=v2 &&
601 test_all_match git rev-parse HEAD^{tree} || return 1
602 done
603 '
604
605 test_expect_success 'merge with outside renames' '
606 init_repos &&
607
608 for type in out-to-out out-to-in in-to-out
609 do
610 test_all_match git reset --hard &&
611 test_all_match git checkout -f -b merge-$type update-deep &&
612 test_all_match git merge -m "$type" rename-$type &&
613 test_all_match git rev-parse HEAD^{tree} || return 1
614 done
615 '
616
617 # Sparse-index fails to convert the index in the
618 # final 'git cherry-pick' command.
619 test_expect_success 'cherry-pick with conflicts' '
620 init_repos &&
621
622 write_script edit-conflict <<-\EOF &&
623 echo $1 >conflict
624 EOF
625
626 test_all_match git checkout -b to-cherry-pick &&
627 run_on_all ../edit-conflict ABC &&
628 test_all_match git add conflict &&
629 test_all_match git commit -m "conflict to pick" &&
630
631 test_all_match git checkout -B base HEAD~1 &&
632 run_on_all ../edit-conflict DEF &&
633 test_all_match git add conflict &&
634 test_all_match git commit -m "conflict in base" &&
635
636 test_all_match test_must_fail git cherry-pick to-cherry-pick
637 '
638
639 test_expect_success 'clean' '
640 init_repos &&
641
642 echo bogus >>.gitignore &&
643 run_on_all cp ../.gitignore . &&
644 test_all_match git add .gitignore &&
645 test_all_match git commit -m "ignore bogus files" &&
646
647 run_on_sparse mkdir folder1 &&
648 run_on_all touch folder1/bogus &&
649
650 test_all_match git status --porcelain=v2 &&
651 test_all_match git clean -f &&
652 test_all_match git status --porcelain=v2 &&
653 test_sparse_match ls &&
654 test_sparse_match ls folder1 &&
655
656 test_all_match git clean -xf &&
657 test_all_match git status --porcelain=v2 &&
658 test_sparse_match ls &&
659 test_sparse_match ls folder1 &&
660
661 test_all_match git clean -xdf &&
662 test_all_match git status --porcelain=v2 &&
663 test_sparse_match ls &&
664 test_sparse_match ls folder1 &&
665
666 test_sparse_match test_path_is_dir folder1
667 '
668
669 test_expect_success 'submodule handling' '
670 init_repos &&
671
672 test_sparse_match git sparse-checkout add modules &&
673 test_all_match mkdir modules &&
674 test_all_match touch modules/a &&
675 test_all_match git add modules &&
676 test_all_match git commit -m "add modules directory" &&
677
678 run_on_all git submodule add "$(pwd)/initial-repo" modules/sub &&
679 test_all_match git commit -m "add submodule" &&
680
681 # having a submodule prevents "modules" from collapse
682 test_sparse_match git sparse-checkout set deep/deeper1 &&
683 test-tool -C sparse-index read-cache --table >cache &&
684 grep "100644 blob .* modules/a" cache &&
685 grep "160000 commit $(git -C initial-repo rev-parse HEAD) modules/sub" cache
686 '
687
688 test_expect_success 'sparse-index is expanded and converted back' '
689 init_repos &&
690
691 GIT_TRACE2_EVENT="$(pwd)/trace2.txt" GIT_TRACE2_EVENT_NESTING=10 \
692 git -C sparse-index -c core.fsmonitor="" reset --hard &&
693 test_region index convert_to_sparse trace2.txt &&
694 test_region index ensure_full_index trace2.txt
695 '
696
697 test_expect_success 'index.sparse disabled inline uses full index' '
698 init_repos &&
699
700 # When index.sparse is disabled inline with `git status`, the
701 # index is expanded at the beginning of the execution then never
702 # converted back to sparse. It is then written to disk as a full index.
703 rm -f trace2.txt &&
704 GIT_TRACE2_EVENT="$(pwd)/trace2.txt" GIT_TRACE2_EVENT_NESTING=10 \
705 git -C sparse-index -c index.sparse=false status &&
706 ! test_region index convert_to_sparse trace2.txt &&
707 test_region index ensure_full_index trace2.txt &&
708
709 # Since index.sparse is set to true at a repo level, the index
710 # is converted from full to sparse when read, then never expanded
711 # over the course of `git status`. It is written to disk as a sparse
712 # index.
713 rm -f trace2.txt &&
714 GIT_TRACE2_EVENT="$(pwd)/trace2.txt" GIT_TRACE2_EVENT_NESTING=10 \
715 git -C sparse-index status &&
716 test_region index convert_to_sparse trace2.txt &&
717 ! test_region index ensure_full_index trace2.txt &&
718
719 # Now that the index has been written to disk as sparse, it is not
720 # converted to sparse (or expanded to full) when read by `git status`.
721 rm -f trace2.txt &&
722 GIT_TRACE2_EVENT="$(pwd)/trace2.txt" GIT_TRACE2_EVENT_NESTING=10 \
723 git -C sparse-index status &&
724 ! test_region index convert_to_sparse trace2.txt &&
725 ! test_region index ensure_full_index trace2.txt
726 '
727
728 ensure_not_expanded () {
729 rm -f trace2.txt &&
730 echo >>sparse-index/untracked.txt &&
731
732 if test "$1" = "!"
733 then
734 shift &&
735 test_must_fail env \
736 GIT_TRACE2_EVENT="$(pwd)/trace2.txt" GIT_TRACE2_EVENT_NESTING=10 \
737 git -C sparse-index "$@" || return 1
738 else
739 GIT_TRACE2_EVENT="$(pwd)/trace2.txt" GIT_TRACE2_EVENT_NESTING=10 \
740 git -C sparse-index "$@" || return 1
741 fi &&
742 test_region ! index ensure_full_index trace2.txt
743 }
744
745 test_expect_success 'sparse-index is not expanded' '
746 init_repos &&
747
748 ensure_not_expanded status &&
749 ensure_not_expanded commit --allow-empty -m empty &&
750 echo >>sparse-index/a &&
751 ensure_not_expanded commit -a -m a &&
752 echo >>sparse-index/a &&
753 ensure_not_expanded commit --include a -m a &&
754 echo >>sparse-index/deep/deeper1/a &&
755 ensure_not_expanded commit --include deep/deeper1/a -m deeper &&
756 ensure_not_expanded checkout rename-out-to-out &&
757 ensure_not_expanded checkout - &&
758 ensure_not_expanded switch rename-out-to-out &&
759 ensure_not_expanded switch - &&
760 git -C sparse-index reset --hard &&
761 ensure_not_expanded checkout rename-out-to-out -- deep/deeper1 &&
762 git -C sparse-index reset --hard &&
763 ensure_not_expanded restore -s rename-out-to-out -- deep/deeper1 &&
764
765 echo >>sparse-index/README.md &&
766 ensure_not_expanded add -A &&
767 echo >>sparse-index/extra.txt &&
768 ensure_not_expanded add extra.txt &&
769 echo >>sparse-index/untracked.txt &&
770 ensure_not_expanded add . &&
771
772 ensure_not_expanded checkout -f update-deep &&
773 test_config -C sparse-index pull.twohead ort &&
774 (
775 sane_unset GIT_TEST_MERGE_ALGORITHM &&
776 for OPERATION in "merge -m merge" cherry-pick rebase
777 do
778 ensure_not_expanded merge -m merge update-folder1 &&
779 ensure_not_expanded merge -m merge update-folder2 || return 1
780 done
781 )
782 '
783
784 test_expect_success 'sparse-index is not expanded: merge conflict in cone' '
785 init_repos &&
786
787 for side in right left
788 do
789 git -C sparse-index checkout -b expand-$side base &&
790 echo $side >sparse-index/deep/a &&
791 git -C sparse-index commit -a -m "$side" || return 1
792 done &&
793
794 (
795 sane_unset GIT_TEST_MERGE_ALGORITHM &&
796 git -C sparse-index config pull.twohead ort &&
797 ensure_not_expanded ! merge -m merged expand-right
798 )
799 '
800
801 # NEEDSWORK: a sparse-checkout behaves differently from a full checkout
802 # in this scenario, but it shouldn't.
803 test_expect_success 'reset mixed and checkout orphan' '
804 init_repos &&
805
806 test_all_match git checkout rename-out-to-in &&
807
808 # Sparse checkouts do not agree with full checkouts about
809 # how to report a directory/file conflict during a reset.
810 # This command would fail with test_all_match because the
811 # full checkout reports "T folder1/0/1" while a sparse
812 # checkout reports "D folder1/0/1". This matches because
813 # the sparse checkouts skip "adding" the other side of
814 # the conflict.
815 test_sparse_match git reset --mixed HEAD~1 &&
816 test_sparse_match test-tool read-cache --table --expand &&
817 test_sparse_match git status --porcelain=v2 &&
818
819 # At this point, sparse-checkouts behave differently
820 # from the full-checkout.
821 test_sparse_match git checkout --orphan new-branch &&
822 test_sparse_match test-tool read-cache --table --expand &&
823 test_sparse_match git status --porcelain=v2
824 '
825
826 test_expect_success 'add everything with deep new file' '
827 init_repos &&
828
829 run_on_sparse git sparse-checkout set deep/deeper1/deepest &&
830
831 run_on_all touch deep/deeper1/x &&
832 test_all_match git add . &&
833 test_all_match git status --porcelain=v2
834 '
835
836 # NEEDSWORK: 'git checkout' behaves incorrectly in the case of
837 # directory/file conflicts, even without sparse-checkout. Use this
838 # test only as a documentation of the incorrect behavior, not a
839 # measure of how it _should_ behave.
840 test_expect_success 'checkout behaves oddly with df-conflict-1' '
841 init_repos &&
842
843 test_sparse_match git sparse-checkout disable &&
844
845 write_script edit-content <<-\EOF &&
846 echo content >>folder1/larger-content
847 git add folder1
848 EOF
849
850 run_on_all ../edit-content &&
851 test_all_match git status --porcelain=v2 &&
852
853 git -C sparse-checkout sparse-checkout init --cone &&
854 git -C sparse-index sparse-checkout init --cone --sparse-index &&
855
856 test_all_match git status --porcelain=v2 &&
857
858 # This checkout command should fail, because we have a staged
859 # change to folder1/larger-content, but the destination changes
860 # folder1 to a file.
861 git -C full-checkout checkout df-conflict-1 \
862 1>full-checkout-out \
863 2>full-checkout-err &&
864 git -C sparse-checkout checkout df-conflict-1 \
865 1>sparse-checkout-out \
866 2>sparse-checkout-err &&
867 git -C sparse-index checkout df-conflict-1 \
868 1>sparse-index-out \
869 2>sparse-index-err &&
870
871 # Instead, the checkout deletes the folder1 file and adds the
872 # folder1/larger-content file, leaving all other paths that were
873 # in folder1/ as deleted (without any warning).
874 cat >expect <<-EOF &&
875 D folder1
876 A folder1/larger-content
877 EOF
878 test_cmp expect full-checkout-out &&
879 test_cmp expect sparse-checkout-out &&
880
881 # The sparse-index reports no output
882 test_must_be_empty sparse-index-out &&
883
884 # stderr: Switched to branch df-conflict-1
885 test_cmp full-checkout-err sparse-checkout-err &&
886 test_cmp full-checkout-err sparse-checkout-err
887 '
888
889 # NEEDSWORK: 'git checkout' behaves incorrectly in the case of
890 # directory/file conflicts, even without sparse-checkout. Use this
891 # test only as a documentation of the incorrect behavior, not a
892 # measure of how it _should_ behave.
893 test_expect_success 'checkout behaves oddly with df-conflict-2' '
894 init_repos &&
895
896 test_sparse_match git sparse-checkout disable &&
897
898 write_script edit-content <<-\EOF &&
899 echo content >>folder2/larger-content
900 git add folder2
901 EOF
902
903 run_on_all ../edit-content &&
904 test_all_match git status --porcelain=v2 &&
905
906 git -C sparse-checkout sparse-checkout init --cone &&
907 git -C sparse-index sparse-checkout init --cone --sparse-index &&
908
909 test_all_match git status --porcelain=v2 &&
910
911 # This checkout command should fail, because we have a staged
912 # change to folder1/larger-content, but the destination changes
913 # folder1 to a file.
914 git -C full-checkout checkout df-conflict-2 \
915 1>full-checkout-out \
916 2>full-checkout-err &&
917 git -C sparse-checkout checkout df-conflict-2 \
918 1>sparse-checkout-out \
919 2>sparse-checkout-err &&
920 git -C sparse-index checkout df-conflict-2 \
921 1>sparse-index-out \
922 2>sparse-index-err &&
923
924 # The full checkout deviates from the df-conflict-1 case here!
925 # It drops the change to folder1/larger-content and leaves the
926 # folder1 path as-is on disk. The sparse-index behaves the same.
927 test_must_be_empty full-checkout-out &&
928 test_must_be_empty sparse-index-out &&
929
930 # In the sparse-checkout case, the checkout deletes the folder1
931 # file and adds the folder1/larger-content file, leaving all other
932 # paths that were in folder1/ as deleted (without any warning).
933 cat >expect <<-EOF &&
934 D folder2
935 A folder2/larger-content
936 EOF
937 test_cmp expect sparse-checkout-out &&
938
939 # Switched to branch df-conflict-1
940 test_cmp full-checkout-err sparse-checkout-err &&
941 test_cmp full-checkout-err sparse-index-err
942 '
943
944 test_done