Raw
1 #!/bin/sh
2
3 test_description='compare full workdir to sparse workdir'
4
5 GIT_TEST_SPLIT_INDEX=0
6 GIT_TEST_SPARSE_INDEX=
7
8 . ./test-lib.sh
9
10 test_expect_success 'setup' '
11 git init initial-repo &&
12 (
13 GIT_TEST_SPARSE_INDEX=0 &&
14 cd initial-repo &&
15 echo a >a &&
16 echo "after deep" >e &&
17 echo "after folder1" >g &&
18 echo "after x" >z &&
19 mkdir folder1 folder2 deep before x &&
20 echo "before deep" >before/a &&
21 echo "before deep again" >before/b &&
22 mkdir deep/deeper1 deep/deeper2 deep/before deep/later &&
23 mkdir deep/deeper1/deepest &&
24 mkdir deep/deeper1/deepest2 &&
25 mkdir deep/deeper1/deepest3 &&
26 echo "after deeper1" >deep/e &&
27 echo "after deepest" >deep/deeper1/e &&
28 cp a folder1 &&
29 cp a folder2 &&
30 cp a x &&
31 cp a deep &&
32 cp a deep/before &&
33 cp a deep/deeper1 &&
34 cp a deep/deeper2 &&
35 cp a deep/later &&
36 cp a deep/deeper1/deepest &&
37 cp a deep/deeper1/deepest2 &&
38 cp a deep/deeper1/deepest3 &&
39 cp -r deep/deeper1/ deep/deeper2 &&
40 mkdir deep/deeper1/0 &&
41 mkdir deep/deeper1/0/0 &&
42 touch deep/deeper1/0/1 &&
43 touch deep/deeper1/0/0/0 &&
44 >folder1- &&
45 >folder1.x &&
46 >folder10 &&
47 cp -r deep/deeper1/0 folder1 &&
48 cp -r deep/deeper1/0 folder2 &&
49 echo >>folder1/0/0/0 &&
50 echo >>folder2/0/1 &&
51 git add . &&
52 git commit -m "initial commit" &&
53 git checkout -b base &&
54 for dir in folder1 folder2 deep
55 do
56 git checkout -b update-$dir base &&
57 echo "updated $dir" >$dir/a &&
58 git commit -a -m "update $dir" || return 1
59 done &&
60
61 git checkout -b rename-base base &&
62 cat >folder1/larger-content <<-\EOF &&
63 matching
64 lines
65 help
66 inexact
67 renames
68 EOF
69 cp folder1/larger-content folder2/ &&
70 cp folder1/larger-content deep/deeper1/ &&
71 git add . &&
72 git commit -m "add interesting rename content" &&
73
74 git checkout -b rename-out-to-out rename-base &&
75 mv folder1/a folder2/b &&
76 mv folder1/larger-content folder2/edited-content &&
77 echo >>folder2/edited-content &&
78 echo >>folder2/0/1 &&
79 echo stuff >>deep/deeper1/a &&
80 git add . &&
81 git commit -m "rename folder1/... to folder2/..." &&
82
83 git checkout -b rename-out-to-in rename-base &&
84 mv folder1/a deep/deeper1/b &&
85 echo more stuff >>deep/deeper1/a &&
86 rm folder2/0/1 &&
87 mkdir folder2/0/1 &&
88 echo >>folder2/0/1/1 &&
89 mv folder1/larger-content deep/deeper1/edited-content &&
90 echo >>deep/deeper1/edited-content &&
91 git add . &&
92 git commit -m "rename folder1/... to deep/deeper1/..." &&
93
94 git checkout -b rename-in-to-out rename-base &&
95 mv deep/deeper1/a folder1/b &&
96 echo >>folder2/0/1 &&
97 rm -rf folder1/0/0 &&
98 echo >>folder1/0/0 &&
99 mv deep/deeper1/larger-content folder1/edited-content &&
100 echo >>folder1/edited-content &&
101 git add . &&
102 git commit -m "rename deep/deeper1/... to folder1/..." &&
103
104 git checkout -b df-conflict-1 base &&
105 rm -rf folder1 &&
106 echo content >folder1 &&
107 git add . &&
108 git commit -m "dir to file" &&
109
110 git checkout -b df-conflict-2 base &&
111 rm -rf folder2 &&
112 echo content >folder2 &&
113 git add . &&
114 git commit -m "dir to file" &&
115
116 git checkout -b fd-conflict base &&
117 rm a &&
118 mkdir a &&
119 echo content >a/a &&
120 git add . &&
121 git commit -m "file to dir" &&
122
123 for side in left right
124 do
125 git checkout -b merge-$side base &&
126 echo $side >>deep/deeper2/a &&
127 echo $side >>folder1/a &&
128 echo $side >>folder2/a &&
129 git add . &&
130 git commit -m "$side" || return 1
131 done &&
132
133 git checkout -b deepest base &&
134 echo "updated deepest" >deep/deeper1/deepest/a &&
135 echo "updated deepest2" >deep/deeper1/deepest2/a &&
136 echo "updated deepest3" >deep/deeper1/deepest3/a &&
137 git commit -a -m "update deepest" &&
138
139 git checkout -f base &&
140 git reset --hard
141 )
142 '
143
144 init_repos () {
145 rm -rf full-checkout sparse-checkout sparse-index &&
146
147 # create repos in initial state
148 cp -r initial-repo full-checkout &&
149 git -C full-checkout reset --hard &&
150
151 cp -r initial-repo sparse-checkout &&
152 git -C sparse-checkout reset --hard &&
153
154 cp -r initial-repo sparse-index &&
155 git -C sparse-index reset --hard &&
156
157 # initialize sparse-checkout definitions
158 git -C sparse-checkout sparse-checkout init --cone &&
159 git -C sparse-checkout sparse-checkout set deep &&
160 git -C sparse-index sparse-checkout init --cone --sparse-index &&
161 test_cmp_config -C sparse-index true index.sparse &&
162 git -C sparse-index sparse-checkout set deep &&
163
164 # Disable this message to keep stderr the same.
165 git -C sparse-index config advice.sparseIndexExpanded false
166 }
167
168 init_repos_as_submodules () {
169 git reset --hard &&
170 init_repos &&
171 git submodule add ./full-checkout &&
172 git submodule add ./sparse-checkout &&
173 git submodule add ./sparse-index &&
174
175 git submodule status >actual &&
176 grep full-checkout actual &&
177 grep sparse-checkout actual &&
178 grep sparse-index actual
179 }
180
181 run_on_sparse () {
182 cat >run-on-sparse-input &&
183
184 (
185 cd sparse-checkout &&
186 GIT_PROGRESS_DELAY=100000 "$@" >../sparse-checkout-out 2>../sparse-checkout-err
187 ) <run-on-sparse-input &&
188 (
189 cd sparse-index &&
190 GIT_PROGRESS_DELAY=100000 "$@" >../sparse-index-out 2>../sparse-index-err
191 ) <run-on-sparse-input
192 }
193
194 run_on_all () {
195 cat >run-on-all-input &&
196
197 (
198 cd full-checkout &&
199 GIT_PROGRESS_DELAY=100000 "$@" >../full-checkout-out 2>../full-checkout-err
200 ) <run-on-all-input &&
201 run_on_sparse "$@" <run-on-all-input
202 }
203
204 test_all_match () {
205 run_on_all "$@" &&
206 test_cmp full-checkout-out sparse-checkout-out &&
207 test_cmp full-checkout-out sparse-index-out &&
208 test_cmp full-checkout-err sparse-checkout-err &&
209 test_cmp full-checkout-err sparse-index-err
210 }
211
212 test_sparse_match () {
213 run_on_sparse "$@" &&
214 test_cmp sparse-checkout-out sparse-index-out &&
215 test_cmp sparse-checkout-err sparse-index-err
216 }
217
218 test_sparse_unstaged () {
219 file=$1 &&
220 for repo in sparse-checkout sparse-index
221 do
222 # Skip "unmerged" paths
223 git -C $repo diff --staged --diff-filter=u -- "$file" >diff &&
224 test_must_be_empty diff || return 1
225 done
226 }
227
228 # Usage: test_sparse_checkout_set "<c1> ... <cN>" "<s1> ... <sM>"
229 # Verifies that "git sparse-checkout set <c1> ... <cN>" succeeds and
230 # leaves the sparse index in a state where <s1> ... <sM> are sparse
231 # directories (and <c1> ... <cN> are not).
232 test_sparse_checkout_set () {
233 CONE_DIRS=$1 &&
234 SPARSE_DIRS=$2 &&
235 git -C sparse-index sparse-checkout set --skip-checks $CONE_DIRS &&
236 git -C sparse-index ls-files --sparse --stage >cache &&
237
238 # Check that the directories outside of the sparse-checkout cone
239 # have sparse directory entries.
240 for dir in $SPARSE_DIRS
241 do
242 TREE=$(git -C sparse-index rev-parse HEAD:$dir) &&
243 grep "040000 $TREE 0 $dir/" cache \
244 || return 1
245 done &&
246
247 # Check that the directories in the sparse-checkout cone
248 # are not sparse directory entries.
249 for dir in $CONE_DIRS
250 do
251 # Allow TREE to not exist because
252 # $dir does not exist at HEAD.
253 TREE=$(git -C sparse-index rev-parse HEAD:$dir) ||
254 ! grep "040000 $TREE 0 $dir/" cache \
255 || return 1
256 done
257 }
258
259 test_expect_success 'sparse-index contents' '
260 init_repos &&
261
262 # Remove deep, add three other directories.
263 test_sparse_checkout_set \
264 "folder1 folder2 x" \
265 "before deep" &&
266
267 # Remove folder1, add deep
268 test_sparse_checkout_set \
269 "deep folder2 x" \
270 "before folder1" &&
271
272 # Replace deep with deep/deeper2 (dropping deep/deeper1)
273 # Add folder1
274 test_sparse_checkout_set \
275 "deep/deeper2 folder1 folder2 x" \
276 "before deep/deeper1" &&
277
278 # Replace deep/deeper2 with deep/deeper1
279 # Replace folder1 with folder1/0/0
280 # Replace folder2 with non-existent folder2/2/3
281 # Add non-existent "bogus"
282 test_sparse_checkout_set \
283 "bogus deep/deeper1 folder1/0/0 folder2/2/3 x" \
284 "before deep/deeper2 folder2/0" &&
285
286 # Drop down to only files at root
287 test_sparse_checkout_set \
288 "" \
289 "before deep folder1 folder2 x" &&
290
291 # Disabling the sparse-index replaces tree entries with full ones
292 git -C sparse-index sparse-checkout init --no-sparse-index &&
293 test_sparse_match git ls-files --stage --sparse
294 '
295
296 test_expect_success 'expanded in-memory index matches full index' '
297 init_repos &&
298 test_sparse_match git ls-files --stage
299 '
300
301 test_expect_success 'root directory cannot be sparse' '
302 init_repos &&
303
304 # Remove all in-cone files and directories from the index, collapse index
305 # with `git sparse-checkout reapply`
306 git -C sparse-index rm -r . &&
307 git -C sparse-index sparse-checkout reapply &&
308
309 # Verify sparse directories still present, root directory is not sparse
310 cat >expect <<-EOF &&
311 before/
312 folder1/
313 folder2/
314 x/
315 EOF
316 git -C sparse-index ls-files --sparse >actual &&
317 test_cmp expect actual
318 '
319
320 test_expect_success 'status with options' '
321 init_repos &&
322 test_sparse_match ls &&
323 test_all_match git status --porcelain=v2 &&
324 test_all_match git status --porcelain=v2 -z -u &&
325 test_all_match git status --porcelain=v2 -uno &&
326 run_on_all touch README.md &&
327 test_all_match git status --porcelain=v2 &&
328 test_all_match git status --porcelain=v2 -z -u &&
329 test_all_match git status --porcelain=v2 -uno &&
330 test_all_match git add README.md &&
331 test_all_match git status --porcelain=v2 &&
332 test_all_match git status --porcelain=v2 -z -u &&
333 test_all_match git status --porcelain=v2 -uno
334 '
335
336 test_expect_success 'status with diff in unexpanded sparse directory' '
337 init_repos &&
338 test_all_match git checkout rename-base &&
339 test_all_match git reset --soft rename-out-to-out &&
340 test_all_match git status --porcelain=v2
341 '
342
343 test_expect_success 'status reports sparse-checkout' '
344 init_repos &&
345 git -C sparse-checkout status >full &&
346 git -C sparse-index status >sparse &&
347 test_grep "You are in a sparse checkout with " full &&
348 test_grep "You are in a sparse checkout." sparse
349 '
350
351 test_expect_success 'add, commit, checkout' '
352 init_repos &&
353
354 write_script edit-contents <<-\EOF &&
355 echo text >>$1
356 EOF
357 run_on_all ../edit-contents README.md &&
358
359 test_all_match git add README.md &&
360 test_all_match git status --porcelain=v2 &&
361 test_all_match git commit -m "Add README.md" &&
362
363 test_all_match git checkout HEAD~1 &&
364 test_all_match git checkout - &&
365
366 run_on_all ../edit-contents README.md &&
367
368 test_all_match git add -A &&
369 test_all_match git status --porcelain=v2 &&
370 test_all_match git commit -m "Extend README.md" &&
371
372 test_all_match git checkout HEAD~1 &&
373 test_all_match git checkout - &&
374
375 run_on_all ../edit-contents deep/newfile &&
376
377 test_all_match git status --porcelain=v2 -uno &&
378 test_all_match git status --porcelain=v2 &&
379 test_all_match git add . &&
380 test_all_match git status --porcelain=v2 &&
381 test_all_match git commit -m "add deep/newfile" &&
382
383 test_all_match git checkout HEAD~1 &&
384 test_all_match git checkout -
385 '
386
387 test_expect_success 'intent-to-add entries outside sparse-checkout' '
388 init_repos &&
389
390 write_script edit-contents <<-\EOF &&
391 echo text >>$1
392 EOF
393
394 test_sparse_match git sparse-checkout set deep folder1 &&
395 run_on_sparse mkdir -p folder1 &&
396 run_on_all ../edit-contents folder1/newita &&
397 test_sparse_match git add -N folder1/newita &&
398
399 test_sparse_match git sparse-checkout set deep &&
400 test_sparse_match git status --porcelain=v2 &&
401 test_sparse_match git ls-files --stage
402 '
403
404 test_expect_success 'intent-to-add with --sparse outside sparse-checkout' '
405 init_repos &&
406
407 write_script edit-contents <<-\EOF &&
408 echo text >>$1
409 EOF
410
411 run_on_all mkdir -p folder1 &&
412 run_on_all ../edit-contents folder1/newita &&
413 test_all_match git add --sparse --intent-to-add folder1/newita &&
414
415 test_all_match git status --porcelain=v2 &&
416 test_all_match git ls-files --stage &&
417 test_all_match git diff --cached --stat &&
418
419 # Ensure sparse index stores correct sparse directories and
420 # intent-to-add path.
421 git -C sparse-index ls-files --format="%(path)" --sparse >out &&
422
423 # These paths should be present in index as-is.
424 test_grep "^before/\$" out &&
425 test_grep "^folder1/newita\$" out &&
426 test_grep "^folder2/\$" out &&
427 test_grep "^x/\$" out &&
428
429 # folder/0/ could theoretically be collapsed to a sparse
430 # directory entry, but the current implementation avoids the
431 # reduction because of folder1/newita
432 test_grep "^folder1/0/0/0\$" out
433 '
434
435 test_expect_success 'git add, checkout, and reset with -p' '
436 init_repos &&
437
438 write_script edit-contents <<-\EOF &&
439 echo text >>$1
440 EOF
441
442 # Does not expand when edits are within sparse checkout.
443 run_on_all ../edit-contents deep/a &&
444 run_on_all ../edit-contents deep/deeper1/a &&
445
446 test_write_lines y n >in &&
447 run_on_all git add -p <in &&
448 test_all_match git status --porcelain=v2 &&
449 test_all_match git reset -p <in &&
450
451 test_write_lines u 1 "" q >in &&
452 run_on_all git add -i <in &&
453 test_all_match git status --porcelain=v2 &&
454 test_all_match git reset --hard &&
455
456 run_on_sparse mkdir -p folder1 &&
457 run_on_all ../edit-contents folder1/a &&
458 test_write_lines y n y >in &&
459 run_on_all git add -p <in &&
460 test_sparse_match git status --porcelain=v2 &&
461 test_sparse_match git reset &&
462 test_write_lines u 2 3 "" q >in &&
463 run_on_all git add -i <in &&
464 test_sparse_match git status --porcelain=v2 &&
465
466 run_on_all git add --sparse folder1 &&
467 run_on_all git commit -m "take changes" &&
468 test_write_lines y n y >in &&
469 test_sparse_match git checkout HEAD~1 --patch <in &&
470 test_sparse_match git status --porcelain=v2
471 '
472
473 test_expect_success 'deep changes during checkout' '
474 init_repos &&
475
476 test_sparse_match git sparse-checkout set deep/deeper1/deepest &&
477 test_all_match git checkout deepest &&
478 test_all_match git checkout base
479 '
480
481 test_expect_success 'checkout with modified sparse directory' '
482 init_repos &&
483
484 test_all_match git checkout rename-in-to-out -- . &&
485 test_sparse_match git sparse-checkout reapply &&
486 test_all_match git checkout base
487 '
488
489 test_expect_success 'checkout orphan then non-orphan' '
490 init_repos &&
491
492 test_all_match git checkout --orphan test-orphan &&
493 test_all_match git status --porcelain=v2 &&
494 test_all_match git checkout base &&
495 test_all_match git status --porcelain=v2
496 '
497
498 test_expect_success 'add outside sparse cone' '
499 init_repos &&
500
501 run_on_sparse mkdir folder1 &&
502 run_on_sparse ../edit-contents folder1/a &&
503 run_on_sparse ../edit-contents folder1/newfile &&
504 test_sparse_match test_must_fail git add folder1/a &&
505 test_grep "Disable or modify the sparsity rules" sparse-checkout-err &&
506 test_sparse_unstaged folder1/a &&
507 test_sparse_match test_must_fail git add folder1/newfile &&
508 test_grep "Disable or modify the sparsity rules" sparse-checkout-err &&
509 test_sparse_unstaged folder1/newfile
510 '
511
512 test_expect_success 'commit including unstaged changes' '
513 init_repos &&
514
515 write_script edit-file <<-\EOF &&
516 echo $1 >$2
517 EOF
518
519 run_on_all ../edit-file 1 a &&
520 run_on_all ../edit-file 1 deep/a &&
521
522 test_all_match git commit -m "-a" -a &&
523 test_all_match git status --porcelain=v2 &&
524
525 run_on_all ../edit-file 2 a &&
526 run_on_all ../edit-file 2 deep/a &&
527
528 test_all_match git commit -m "--include" --include deep/a &&
529 test_all_match git status --porcelain=v2 &&
530 test_all_match git commit -m "--include" --include a &&
531 test_all_match git status --porcelain=v2 &&
532
533 run_on_all ../edit-file 3 a &&
534 run_on_all ../edit-file 3 deep/a &&
535
536 test_all_match git commit -m "--amend" -a --amend &&
537 test_all_match git status --porcelain=v2
538 '
539
540 test_expect_success 'status/add: outside sparse cone' '
541 init_repos &&
542
543 # folder1 is at HEAD, but outside the sparse cone
544 run_on_sparse mkdir folder1 &&
545 cp initial-repo/folder1/a sparse-checkout/folder1/a &&
546 cp initial-repo/folder1/a sparse-index/folder1/a &&
547
548 test_sparse_match git status &&
549
550 write_script edit-contents <<-\EOF &&
551 echo text >>$1
552 EOF
553 run_on_all ../edit-contents folder1/a &&
554 run_on_all ../edit-contents folder1/new &&
555
556 test_sparse_match git status --porcelain=v2 &&
557
558 # Adding the path outside of the sparse-checkout cone should fail.
559 test_sparse_match test_must_fail git add folder1/a &&
560 test_grep "Disable or modify the sparsity rules" sparse-checkout-err &&
561 test_sparse_unstaged folder1/a &&
562 test_all_match git add --refresh folder1/a &&
563 test_must_be_empty sparse-checkout-err &&
564 test_sparse_unstaged folder1/a &&
565 test_sparse_match test_must_fail git add folder1/new &&
566 test_grep "Disable or modify the sparsity rules" sparse-checkout-err &&
567 test_sparse_unstaged folder1/new &&
568 test_sparse_match git add --sparse folder1/a &&
569 test_sparse_match git add --sparse folder1/new &&
570
571 test_all_match git add --sparse . &&
572 test_all_match git status --porcelain=v2 &&
573 test_all_match git commit -m folder1/new &&
574 test_all_match git rev-parse HEAD^{tree} &&
575
576 run_on_all ../edit-contents folder1/newer &&
577 test_all_match git add --sparse folder1/ &&
578 test_all_match git status --porcelain=v2 &&
579 test_all_match git commit -m folder1/newer &&
580 test_all_match git rev-parse HEAD^{tree}
581 '
582
583 test_expect_success 'checkout and reset --hard' '
584 init_repos &&
585
586 test_all_match git checkout update-folder1 &&
587 test_all_match git status --porcelain=v2 &&
588
589 test_all_match git checkout update-deep &&
590 test_all_match git status --porcelain=v2 &&
591
592 test_all_match git checkout -b reset-test &&
593 test_all_match git reset --hard deepest &&
594 test_all_match git reset --hard update-folder1 &&
595 test_all_match git reset --hard update-folder2
596 '
597
598 test_expect_success 'diff --cached' '
599 init_repos &&
600
601 write_script edit-contents <<-\EOF &&
602 echo text >>README.md
603 EOF
604 run_on_all ../edit-contents &&
605
606 test_all_match git diff &&
607 test_all_match git diff --cached &&
608 test_all_match git add README.md &&
609 test_all_match git diff &&
610 test_all_match git diff --cached
611 '
612
613 # NEEDSWORK: sparse-checkout behaves differently from full-checkout when
614 # running this test with 'df-conflict-2' after 'df-conflict-1'.
615 test_expect_success 'diff with renames and conflicts' '
616 init_repos &&
617
618 for branch in rename-out-to-out \
619 rename-out-to-in \
620 rename-in-to-out \
621 df-conflict-1 \
622 fd-conflict
623 do
624 test_all_match git checkout rename-base &&
625 test_all_match git checkout $branch -- . &&
626 test_all_match git status --porcelain=v2 &&
627 test_all_match git diff --cached --no-renames &&
628 test_all_match git diff --cached --find-renames || return 1
629 done
630 '
631
632 test_expect_success 'diff with directory/file conflicts' '
633 init_repos &&
634
635 for branch in rename-out-to-out \
636 rename-out-to-in \
637 rename-in-to-out \
638 df-conflict-1 \
639 df-conflict-2 \
640 fd-conflict
641 do
642 git -C full-checkout reset --hard &&
643 test_sparse_match git reset --hard &&
644 test_all_match git checkout $branch &&
645 test_all_match git checkout rename-base -- . &&
646 test_all_match git status --porcelain=v2 &&
647 test_all_match git diff --cached --no-renames &&
648 test_all_match git diff --cached --find-renames || return 1
649 done
650 '
651
652 test_expect_success 'log with pathspec outside sparse definition' '
653 init_repos &&
654
655 test_all_match git log -- a &&
656 test_all_match git log -- folder1/a &&
657 test_all_match git log -- folder2/a &&
658 test_all_match git log -- deep/a &&
659 test_all_match git log -- deep/deeper1/a &&
660 test_all_match git log -- deep/deeper1/deepest/a &&
661
662 test_all_match git checkout update-folder1 &&
663 test_all_match git log -- folder1/a
664 '
665
666 test_expect_success 'blame with pathspec inside sparse definition' '
667 init_repos &&
668
669 for file in a \
670 deep/a \
671 deep/deeper1/a \
672 deep/deeper1/deepest/a
673 do
674 test_all_match git blame $file || return 1
675 done
676 '
677
678 # Without a revision specified, blame will error if passed any file that
679 # is not present in the working directory (even if the file is tracked).
680 # Here we just verify that this is also true with sparse checkouts.
681 test_expect_success 'blame with pathspec outside sparse definition' '
682 init_repos &&
683 test_sparse_match git sparse-checkout set &&
684
685 for file in \
686 deep/a \
687 deep/deeper1/a \
688 deep/deeper1/deepest/a
689 do
690 test_sparse_match test_must_fail git blame $file &&
691 cat >expect <<-EOF &&
692 fatal: Cannot lstat '"'"'$file'"'"': No such file or directory
693 EOF
694 # We compare sparse-checkout-err and sparse-index-err in
695 # `test_sparse_match`. Given we know they are the same, we
696 # only check the content of sparse-index-err here.
697 test_cmp expect sparse-index-err || return 1
698 done
699 '
700
701 test_expect_success 'checkout and reset (mixed)' '
702 init_repos &&
703
704 test_all_match git checkout -b reset-test update-deep &&
705 test_all_match git reset deepest &&
706
707 # Because skip-worktree is preserved, resetting to update-folder1
708 # will show worktree changes for folder1/a in full-checkout, but not
709 # in sparse-checkout or sparse-index.
710 git -C full-checkout reset update-folder1 >full-checkout-out &&
711 test_sparse_match git reset update-folder1 &&
712 test_grep "M folder1/a" full-checkout-out &&
713 test_grep ! "M folder1/a" sparse-checkout-out &&
714 run_on_sparse test_path_is_missing folder1
715 '
716
717 test_expect_success 'checkout and reset (merge)' '
718 init_repos &&
719
720 write_script edit-contents <<-\EOF &&
721 echo text >>$1
722 EOF
723
724 test_all_match git checkout -b reset-test update-deep &&
725 run_on_all ../edit-contents a &&
726 test_all_match git reset --merge deepest &&
727 test_all_match git status --porcelain=v2 &&
728
729 test_all_match git reset --hard update-deep &&
730 run_on_all ../edit-contents deep/a &&
731 test_all_match test_must_fail git reset --merge deepest
732 '
733
734 test_expect_success 'checkout and reset (keep)' '
735 init_repos &&
736
737 write_script edit-contents <<-\EOF &&
738 echo text >>$1
739 EOF
740
741 test_all_match git checkout -b reset-test update-deep &&
742 run_on_all ../edit-contents a &&
743 test_all_match git reset --keep deepest &&
744 test_all_match git status --porcelain=v2 &&
745
746 test_all_match git reset --hard update-deep &&
747 run_on_all ../edit-contents deep/a &&
748 test_all_match test_must_fail git reset --keep deepest
749 '
750
751 test_expect_success 'reset with pathspecs inside sparse definition' '
752 init_repos &&
753
754 write_script edit-contents <<-\EOF &&
755 echo text >>$1
756 EOF
757
758 test_all_match git checkout -b reset-test update-deep &&
759 run_on_all ../edit-contents deep/a &&
760
761 test_all_match git reset base -- deep/a &&
762 test_all_match git status --porcelain=v2 &&
763
764 test_all_match git reset base -- nonexistent-file &&
765 test_all_match git status --porcelain=v2 &&
766
767 test_all_match git reset deepest -- deep &&
768 test_all_match git status --porcelain=v2
769 '
770
771 # Although the working tree differs between full and sparse checkouts after
772 # reset, the state of the index is the same.
773 test_expect_success 'reset with pathspecs outside sparse definition' '
774 init_repos &&
775 test_all_match git checkout -b reset-test base &&
776
777 test_sparse_match git reset update-folder1 -- folder1 &&
778 git -C full-checkout reset update-folder1 -- folder1 &&
779 test_all_match git ls-files -s -- folder1 &&
780
781 test_sparse_match git reset update-folder2 -- folder2/a &&
782 git -C full-checkout reset update-folder2 -- folder2/a &&
783 test_all_match git ls-files -s -- folder2/a
784 '
785
786 test_expect_success 'reset with wildcard pathspec' '
787 init_repos &&
788
789 test_all_match git reset update-deep -- deep\* &&
790 test_all_match git ls-files -s -- deep &&
791
792 test_all_match git reset deepest -- deep\*\*\* &&
793 test_all_match git ls-files -s -- deep &&
794
795 # The following `git reset`s result in updating the index on files with
796 # `skip-worktree` enabled. To avoid failing due to discrepancies in reported
797 # "modified" files, `test_sparse_match` reset is performed separately from
798 # "full-checkout" reset, then the index contents of all repos are verified.
799
800 test_sparse_match git reset update-folder1 -- \*/a &&
801 git -C full-checkout reset update-folder1 -- \*/a &&
802 test_all_match git ls-files -s -- deep/a folder1/a &&
803
804 test_sparse_match git reset update-folder2 -- folder\* &&
805 git -C full-checkout reset update-folder2 -- folder\* &&
806 test_all_match git ls-files -s -- folder10 folder1 folder2 &&
807
808 test_sparse_match git reset base -- folder1/\* &&
809 git -C full-checkout reset base -- folder1/\* &&
810 test_all_match git ls-files -s -- folder1
811 '
812
813 test_expect_success 'reset hard with removed sparse dir' '
814 init_repos &&
815
816 run_on_all git rm -r --sparse folder1 &&
817 test_all_match git status --porcelain=v2 &&
818
819 test_all_match git reset --hard &&
820 test_all_match git status --porcelain=v2 &&
821
822 cat >expect <<-\EOF &&
823 folder1/
824 EOF
825
826 git -C sparse-index ls-files --sparse folder1 >out &&
827 test_cmp expect out
828 '
829
830 test_expect_success 'update-index modify outside sparse definition' '
831 init_repos &&
832
833 write_script edit-contents <<-\EOF &&
834 echo text >>$1
835 EOF
836
837 # Create & modify folder1/a
838 # Note that this setup is a manual way of reaching the erroneous
839 # condition in which a `skip-worktree` enabled, outside-of-cone file
840 # exists on disk. It is used here to ensure `update-index` is stable
841 # and behaves predictably if such a condition occurs.
842 run_on_sparse mkdir -p folder1 &&
843 run_on_sparse cp ../initial-repo/folder1/a folder1/a &&
844 run_on_all ../edit-contents folder1/a &&
845
846 # If file has skip-worktree enabled, but the file is present, it is
847 # treated the same as if skip-worktree is disabled
848 test_all_match git status --porcelain=v2 &&
849 test_all_match git update-index folder1/a &&
850 test_all_match git status --porcelain=v2 &&
851
852 # When skip-worktree is disabled (even on files outside sparse cone), file
853 # is updated in the index
854 test_sparse_match git update-index --no-skip-worktree folder1/a &&
855 test_all_match git status --porcelain=v2 &&
856 test_all_match git update-index folder1/a &&
857 test_all_match git status --porcelain=v2
858 '
859
860 test_expect_success 'update-index --add outside sparse definition' '
861 init_repos &&
862
863 write_script edit-contents <<-\EOF &&
864 echo text >>$1
865 EOF
866
867 # Create folder1, add new file
868 run_on_sparse mkdir -p folder1 &&
869 run_on_all ../edit-contents folder1/b &&
870
871 # The *untracked* out-of-cone file is added to the index because it does
872 # not have a `skip-worktree` bit to signal that it should be ignored
873 # (unlike in `git add`, which will fail due to the file being outside
874 # the sparse checkout definition).
875 test_all_match git update-index --add folder1/b &&
876 test_all_match git status --porcelain=v2
877 '
878
879 # NEEDSWORK: `--remove`, unlike the rest of `update-index`, does not ignore
880 # `skip-worktree` entries by default and will remove them from the index.
881 # The `--ignore-skip-worktree-entries` flag must be used in conjunction with
882 # `--remove` to ignore the `skip-worktree` entries and prevent their removal
883 # from the index.
884 test_expect_success 'update-index --remove outside sparse definition' '
885 init_repos &&
886
887 # When --ignore-skip-worktree-entries is _not_ specified:
888 # out-of-cone, not-on-disk files are removed from the index
889 test_sparse_match git update-index --remove folder1/a &&
890 cat >expect <<-EOF &&
891 D folder1/a
892 EOF
893 test_sparse_match git diff --cached --name-status &&
894 test_cmp expect sparse-checkout-out &&
895
896 test_sparse_match git diff-index --cached HEAD &&
897
898 # Reset the state
899 test_all_match git reset --hard &&
900
901 # When --ignore-skip-worktree-entries is specified, out-of-cone
902 # (skip-worktree) files are ignored
903 test_sparse_match git update-index --remove --ignore-skip-worktree-entries folder1/a &&
904 test_sparse_match git diff --cached --name-status &&
905 test_must_be_empty sparse-checkout-out &&
906
907 test_sparse_match git diff-index --cached HEAD &&
908
909 # Reset the state
910 test_all_match git reset --hard &&
911
912 # --force-remove supersedes --ignore-skip-worktree-entries, removing
913 # a skip-worktree file from the index (and disk) when both are specified
914 # with --remove
915 test_sparse_match git update-index --force-remove --ignore-skip-worktree-entries folder1/a &&
916 cat >expect <<-EOF &&
917 D folder1/a
918 EOF
919 test_sparse_match git diff --cached --name-status &&
920 test_cmp expect sparse-checkout-out &&
921
922 test_sparse_match git diff-index --cached HEAD
923 '
924
925 test_expect_success 'update-index with directories' '
926 init_repos &&
927
928 # update-index will exit silently when provided with a directory name
929 # containing a trailing slash
930 test_all_match git update-index deep/ folder1/ &&
931 test_grep "Ignoring path deep/" sparse-checkout-err &&
932 test_grep "Ignoring path folder1/" sparse-checkout-err &&
933
934 # When update-index is given a directory name WITHOUT a trailing slash, it will
935 # behave in different ways depending on the status of the directory on disk:
936 # * if it exists, the command exits with an error ("add individual files instead")
937 # * if it does NOT exist (e.g., in a sparse-checkout), it is assumed to be a
938 # file and either triggers an error ("does not exist and --remove not passed")
939 # or is ignored completely (when using --remove)
940 test_all_match test_must_fail git update-index deep &&
941 run_on_all test_must_fail git update-index folder1 &&
942 test_must_fail git -C full-checkout update-index --remove folder1 &&
943 test_sparse_match git update-index --remove folder1 &&
944 test_all_match git status --porcelain=v2
945 '
946
947 test_expect_success 'update-index --again file outside sparse definition' '
948 init_repos &&
949
950 test_all_match git checkout -b test-reupdate &&
951
952 # Update HEAD without modifying the index to introduce a difference in
953 # folder1/a
954 test_sparse_match git reset --soft update-folder1 &&
955
956 # Because folder1/a differs in the index vs HEAD,
957 # `git update-index --no-skip-worktree --again` will effectively perform
958 # `git update-index --no-skip-worktree folder1/a` and remove the skip-worktree
959 # flag from folder1/a
960 test_sparse_match git update-index --no-skip-worktree --again &&
961 test_sparse_match git status --porcelain=v2 &&
962
963 cat >expect <<-EOF &&
964 D folder1/a
965 EOF
966 test_sparse_match git diff --name-status &&
967 test_cmp expect sparse-checkout-out
968 '
969
970 test_expect_success 'update-index --cacheinfo' '
971 init_repos &&
972
973 deep_a_oid=$(git -C full-checkout rev-parse update-deep:deep/a) &&
974 folder2_oid=$(git -C full-checkout rev-parse update-folder2:folder2) &&
975 folder1_a_oid=$(git -C full-checkout rev-parse update-folder1:folder1/a) &&
976
977 test_all_match git update-index --cacheinfo 100644 $deep_a_oid deep/a &&
978 test_all_match git status --porcelain=v2 &&
979
980 # Cannot add sparse directory, even in sparse index case
981 test_all_match test_must_fail git update-index --add --cacheinfo 040000 $folder2_oid folder2/ &&
982
983 # Sparse match only: the new outside-of-cone entry is added *without* skip-worktree,
984 # so `git status` reports it as "deleted" in the worktree
985 test_sparse_match git update-index --add --cacheinfo 100644 $folder1_a_oid folder1/a &&
986 test_sparse_match git status --porcelain=v2 &&
987 cat >expect <<-EOF &&
988 MD folder1/a
989 EOF
990 test_sparse_match git status --short -- folder1/a &&
991 test_cmp expect sparse-checkout-out &&
992
993 # To return folder1/a to "normal" for a sparse checkout (ignored &
994 # outside-of-cone), add the skip-worktree flag.
995 test_sparse_match git update-index --skip-worktree folder1/a &&
996 cat >expect <<-EOF &&
997 S folder1/a
998 EOF
999 test_sparse_match git ls-files -t -- folder1/a &&
1000 test_cmp expect sparse-checkout-out
1001 '
1002
1003 for MERGE_TREES in "base HEAD update-folder2" \
1004 "update-folder1 update-folder2" \
1005 "update-folder2"
1006 do
1007 test_expect_success "'read-tree -mu $MERGE_TREES' with files outside sparse definition" '
1008 init_repos &&
1009
1010 # Although the index matches, without --no-sparse-checkout, outside-of-
1011 # definition files will not exist on disk for sparse checkouts
1012 test_all_match git read-tree -mu $MERGE_TREES &&
1013 test_all_match git status --porcelain=v2 &&
1014 test_path_is_missing sparse-checkout/folder2 &&
1015 test_path_is_missing sparse-index/folder2 &&
1016
1017 test_all_match git read-tree --reset -u HEAD &&
1018 test_all_match git status --porcelain=v2 &&
1019
1020 test_all_match git read-tree -mu --no-sparse-checkout $MERGE_TREES &&
1021 test_all_match git status --porcelain=v2 &&
1022 test_cmp sparse-checkout/folder2/a sparse-index/folder2/a &&
1023 test_cmp sparse-checkout/folder2/a full-checkout/folder2/a
1024
1025 '
1026 done
1027
1028 test_expect_success 'read-tree --merge with edit/edit conflicts in sparse directories' '
1029 init_repos &&
1030
1031 # Merge of multiple changes to same directory (but not same files) should
1032 # succeed
1033 test_all_match git read-tree -mu base rename-base update-folder1 &&
1034 test_all_match git status --porcelain=v2 &&
1035
1036 test_all_match git reset --hard &&
1037
1038 test_all_match git read-tree -mu rename-base update-folder2 &&
1039 test_all_match git status --porcelain=v2 &&
1040
1041 test_all_match git reset --hard &&
1042
1043 test_all_match test_must_fail git read-tree -mu base update-folder1 rename-out-to-in &&
1044 test_all_match test_must_fail git read-tree -mu rename-out-to-in update-folder1
1045 '
1046
1047 test_expect_success 'read-tree --prefix' '
1048 init_repos &&
1049
1050 # If files differing between the index and target <commit-ish> exist
1051 # inside the prefix, `read-tree --prefix` should fail
1052 test_all_match test_must_fail git read-tree --prefix=deep/ deepest &&
1053 test_all_match test_must_fail git read-tree --prefix=folder1/ update-folder1 &&
1054
1055 # If no differing index entries exist matching the prefix,
1056 # `read-tree --prefix` updates the index successfully
1057 test_all_match git rm -rf deep/deeper1/deepest/ &&
1058 test_all_match git read-tree --prefix=deep/deeper1/deepest -u deepest &&
1059 test_all_match git status --porcelain=v2 &&
1060
1061 run_on_all git rm -rf --sparse folder1/ &&
1062 test_all_match git read-tree --prefix=folder1/ -u update-folder1 &&
1063 test_all_match git status --porcelain=v2 &&
1064
1065 test_all_match git rm -rf --sparse folder2/0 &&
1066 test_all_match git read-tree --prefix=folder2/0/ -u rename-out-to-out &&
1067 test_all_match git status --porcelain=v2
1068 '
1069
1070 test_expect_success 'read-tree --merge with directory-file conflicts' '
1071 init_repos &&
1072
1073 test_all_match git checkout -b test-branch rename-base &&
1074
1075 # Although the index matches, without --no-sparse-checkout, outside-of-
1076 # definition files will not exist on disk for sparse checkouts
1077 test_sparse_match git read-tree -mu rename-out-to-out &&
1078 test_sparse_match git status --porcelain=v2 &&
1079 test_path_is_missing sparse-checkout/folder2 &&
1080 test_path_is_missing sparse-index/folder2 &&
1081
1082 test_sparse_match git read-tree --reset -u HEAD &&
1083 test_sparse_match git status --porcelain=v2 &&
1084
1085 test_sparse_match git read-tree -mu --no-sparse-checkout rename-out-to-out &&
1086 test_sparse_match git status --porcelain=v2 &&
1087 test_cmp sparse-checkout/folder2/0/1 sparse-index/folder2/0/1
1088 '
1089
1090 test_expect_success 'merge, cherry-pick, and rebase' '
1091 init_repos &&
1092
1093 for OPERATION in "merge -m merge" cherry-pick "rebase --apply" "rebase --merge"
1094 do
1095 test_all_match git checkout -B temp update-deep &&
1096 test_all_match git $OPERATION update-folder1 &&
1097 test_all_match git rev-parse HEAD^{tree} &&
1098 test_all_match git $OPERATION update-folder2 &&
1099 test_all_match git rev-parse HEAD^{tree} || return 1
1100 done
1101 '
1102
1103 test_expect_success 'merge with conflict outside cone' '
1104 init_repos &&
1105
1106 test_all_match git checkout -b merge-tip merge-left &&
1107 test_all_match git status --porcelain=v2 &&
1108 test_all_match test_must_fail git merge -m merge merge-right &&
1109 test_all_match git status --porcelain=v2 &&
1110
1111 # Resolve the conflict in different ways:
1112 # 1. Revert to the base
1113 test_all_match git checkout base -- deep/deeper2/a &&
1114 test_all_match git status --porcelain=v2 &&
1115
1116 # 2. Add the file with conflict markers
1117 test_sparse_match test_must_fail git add folder1/a &&
1118 test_grep "Disable or modify the sparsity rules" sparse-checkout-err &&
1119 test_sparse_unstaged folder1/a &&
1120 test_all_match git add --sparse folder1/a &&
1121 test_all_match git status --porcelain=v2 &&
1122
1123 # 3. Rename the file to another sparse filename and
1124 # accept conflict markers as resolved content.
1125 run_on_all mv folder2/a folder2/z &&
1126 test_sparse_match test_must_fail git add folder2 &&
1127 test_grep "Disable or modify the sparsity rules" sparse-checkout-err &&
1128 test_sparse_unstaged folder2/z &&
1129 test_all_match git add --sparse folder2 &&
1130 test_all_match git status --porcelain=v2 &&
1131
1132 test_all_match git merge --continue &&
1133 test_all_match git status --porcelain=v2 &&
1134 test_all_match git rev-parse HEAD^{tree}
1135 '
1136
1137 test_expect_success 'cherry-pick/rebase with conflict outside cone' '
1138 init_repos &&
1139
1140 for OPERATION in cherry-pick rebase
1141 do
1142 test_all_match git checkout -B tip &&
1143 test_all_match git reset --hard merge-left &&
1144 test_all_match git status --porcelain=v2 &&
1145 test_all_match test_must_fail git $OPERATION merge-right &&
1146 test_all_match git status --porcelain=v2 &&
1147
1148 # Resolve the conflict in different ways:
1149 # 1. Revert to the base
1150 test_all_match git checkout base -- deep/deeper2/a &&
1151 test_all_match git status --porcelain=v2 &&
1152
1153 # 2. Add the file with conflict markers
1154 # NEEDSWORK: Even though the merge conflict removed the
1155 # SKIP_WORKTREE bit from the index entry for folder1/a, we should
1156 # warn that this is a problematic add.
1157 test_sparse_match test_must_fail git add folder1/a &&
1158 test_grep "Disable or modify the sparsity rules" sparse-checkout-err &&
1159 test_sparse_unstaged folder1/a &&
1160 test_all_match git add --sparse folder1/a &&
1161 test_all_match git status --porcelain=v2 &&
1162
1163 # 3. Rename the file to another sparse filename and
1164 # accept conflict markers as resolved content.
1165 # NEEDSWORK: This mode now fails, because folder2/z is
1166 # outside of the sparse-checkout cone and does not match an
1167 # existing index entry with the SKIP_WORKTREE bit cleared.
1168 run_on_all mv folder2/a folder2/z &&
1169 test_sparse_match test_must_fail git add folder2 &&
1170 test_grep "Disable or modify the sparsity rules" sparse-checkout-err &&
1171 test_sparse_unstaged folder2/z &&
1172 test_all_match git add --sparse folder2 &&
1173 test_all_match git status --porcelain=v2 &&
1174
1175 test_all_match git $OPERATION --continue &&
1176 test_all_match git status --porcelain=v2 &&
1177 test_all_match git rev-parse HEAD^{tree} || return 1
1178 done
1179 '
1180
1181 test_expect_success 'merge with outside renames' '
1182 init_repos &&
1183
1184 for type in out-to-out out-to-in in-to-out
1185 do
1186 test_all_match git reset --hard &&
1187 test_all_match git checkout -f -b merge-$type update-deep &&
1188 test_all_match git merge -m "$type" rename-$type &&
1189 test_all_match git rev-parse HEAD^{tree} || return 1
1190 done
1191 '
1192
1193 # Sparse-index fails to convert the index in the
1194 # final 'git cherry-pick' command.
1195 test_expect_success 'cherry-pick with conflicts' '
1196 init_repos &&
1197
1198 write_script edit-conflict <<-\EOF &&
1199 echo $1 >conflict
1200 EOF
1201
1202 test_all_match git checkout -b to-cherry-pick &&
1203 run_on_all ../edit-conflict ABC &&
1204 test_all_match git add conflict &&
1205 test_all_match git commit -m "conflict to pick" &&
1206
1207 test_all_match git checkout -B base HEAD~1 &&
1208 run_on_all ../edit-conflict DEF &&
1209 test_all_match git add conflict &&
1210 test_all_match git commit -m "conflict in base" &&
1211
1212 test_all_match test_must_fail git cherry-pick to-cherry-pick
1213 '
1214
1215 test_expect_success 'stash' '
1216 init_repos &&
1217
1218 write_script edit-contents <<-\EOF &&
1219 echo text >>$1
1220 EOF
1221
1222 # Stash a sparse directory (folder1)
1223 test_all_match git checkout -b test-branch rename-base &&
1224 test_all_match git reset --soft rename-out-to-out &&
1225 test_all_match git stash &&
1226 test_all_match git status --porcelain=v2 &&
1227
1228 # Apply the sparse directory stash without reinstating the index
1229 test_all_match git stash apply -q &&
1230 test_all_match git status --porcelain=v2 &&
1231
1232 # Reset to state where stash can be applied
1233 test_sparse_match git sparse-checkout reapply &&
1234 test_all_match git reset --hard rename-out-to-out &&
1235
1236 # Apply the sparse directory stash *with* reinstating the index
1237 test_all_match git stash apply --index -q &&
1238 test_all_match git status --porcelain=v2 &&
1239
1240 # Reset to state where we will get a conflict applying the stash
1241 test_sparse_match git sparse-checkout reapply &&
1242 test_all_match git reset --hard update-folder1 &&
1243
1244 # Apply the sparse directory stash with conflicts
1245 test_all_match test_must_fail git stash apply --index -q &&
1246 test_all_match test_must_fail git stash apply -q &&
1247 test_all_match git status --porcelain=v2 &&
1248
1249 # Reset to base branch
1250 test_sparse_match git sparse-checkout reapply &&
1251 test_all_match git reset --hard base &&
1252
1253 # Stash & unstash an untracked file outside of the sparse checkout
1254 # definition.
1255 run_on_sparse mkdir -p folder1 &&
1256 run_on_all ../edit-contents folder1/new &&
1257 test_all_match git stash -u &&
1258 test_all_match git status --porcelain=v2 &&
1259
1260 test_all_match git stash pop -q &&
1261 test_all_match git status --porcelain=v2
1262 '
1263
1264 test_expect_success 'checkout-index inside sparse definition' '
1265 init_repos &&
1266
1267 run_on_all rm -f deep/a &&
1268 test_all_match git checkout-index -- deep/a &&
1269 test_all_match git status --porcelain=v2 &&
1270
1271 echo test >>new-a &&
1272 run_on_all cp ../new-a a &&
1273 test_all_match test_must_fail git checkout-index -- a &&
1274 test_all_match git checkout-index -f -- a &&
1275 test_all_match git status --porcelain=v2
1276 '
1277
1278 test_expect_success 'checkout-index outside sparse definition' '
1279 init_repos &&
1280
1281 # Without --ignore-skip-worktree-bits, outside-of-cone files will trigger
1282 # an error
1283 test_sparse_match test_must_fail git checkout-index -- folder1/a &&
1284 test_grep "folder1/a has skip-worktree enabled" sparse-checkout-err &&
1285 test_path_is_missing folder1/a &&
1286
1287 # With --ignore-skip-worktree-bits, outside-of-cone files are checked out
1288 test_sparse_match git checkout-index --ignore-skip-worktree-bits -- folder1/a &&
1289 test_cmp sparse-checkout/folder1/a sparse-index/folder1/a &&
1290 test_cmp sparse-checkout/folder1/a full-checkout/folder1/a &&
1291
1292 run_on_sparse rm -rf folder1 &&
1293 echo test >new-a &&
1294 run_on_sparse mkdir -p folder1 &&
1295 run_on_all cp ../new-a folder1/a &&
1296
1297 test_all_match test_must_fail git checkout-index --ignore-skip-worktree-bits -- folder1/a &&
1298 test_all_match git checkout-index -f --ignore-skip-worktree-bits -- folder1/a &&
1299 test_cmp sparse-checkout/folder1/a sparse-index/folder1/a &&
1300 test_cmp sparse-checkout/folder1/a full-checkout/folder1/a
1301 '
1302
1303 test_expect_success 'checkout-index with folders' '
1304 init_repos &&
1305
1306 # Inside checkout definition
1307 test_all_match test_must_fail git checkout-index -f -- deep/ &&
1308
1309 # Outside checkout definition
1310 # Note: although all tests fail (as expected), the messaging differs. For
1311 # non-sparse index checkouts, the error is that the "file" does not appear
1312 # in the index; for sparse checkouts, the error is explicitly that the
1313 # entry is a sparse directory.
1314 run_on_all test_must_fail git checkout-index -f -- folder1/ &&
1315 test_cmp full-checkout-err sparse-checkout-err &&
1316 ! test_cmp full-checkout-err sparse-index-err &&
1317 test_grep "is a sparse directory" sparse-index-err
1318 '
1319
1320 test_expect_success 'checkout-index --all' '
1321 init_repos &&
1322
1323 test_all_match git checkout-index --all &&
1324 test_sparse_match test_path_is_missing folder1 &&
1325
1326 # --ignore-skip-worktree-bits will cause `skip-worktree` files to be
1327 # checked out, causing the outside-of-cone `folder1` to exist on-disk
1328 test_all_match git checkout-index --ignore-skip-worktree-bits --all &&
1329 test_all_match test_path_exists folder1
1330 '
1331
1332 test_expect_success 'clean' '
1333 init_repos &&
1334
1335 echo bogus >>.gitignore &&
1336 run_on_all cp ../.gitignore . &&
1337 test_all_match git add .gitignore &&
1338 test_all_match git commit -m "ignore bogus files" &&
1339
1340 run_on_sparse mkdir folder1 &&
1341 run_on_all mkdir -p deep/untracked-deep &&
1342 run_on_all touch folder1/bogus &&
1343 run_on_all touch folder1/untracked &&
1344 run_on_all touch deep/untracked-deep/bogus &&
1345 run_on_all touch deep/untracked-deep/untracked &&
1346
1347 test_all_match git status --porcelain=v2 &&
1348 test_all_match git clean -f &&
1349 test_all_match git status --porcelain=v2 &&
1350 test_sparse_match ls &&
1351 test_sparse_match ls folder1 &&
1352 run_on_all test_path_exists folder1/bogus &&
1353 run_on_all test_path_is_missing folder1/untracked &&
1354 run_on_all test_path_exists deep/untracked-deep/bogus &&
1355 run_on_all test_path_exists deep/untracked-deep/untracked &&
1356
1357 test_all_match git clean -fd &&
1358 test_all_match git status --porcelain=v2 &&
1359 test_sparse_match ls &&
1360 test_sparse_match ls folder1 &&
1361 run_on_all test_path_exists folder1/bogus &&
1362 run_on_all test_path_exists deep/untracked-deep/bogus &&
1363 run_on_all test_path_is_missing deep/untracked-deep/untracked &&
1364
1365 test_all_match git clean -xf &&
1366 test_all_match git status --porcelain=v2 &&
1367 test_sparse_match ls &&
1368 test_sparse_match ls folder1 &&
1369 run_on_all test_path_is_missing folder1/bogus &&
1370 run_on_all test_path_exists deep/untracked-deep/bogus &&
1371
1372 test_all_match git clean -xdf &&
1373 test_all_match git status --porcelain=v2 &&
1374 test_sparse_match ls &&
1375 test_sparse_match ls folder1 &&
1376 run_on_all test_path_is_missing deep/untracked-deep/bogus &&
1377
1378 test_sparse_match test_path_is_dir folder1
1379 '
1380
1381 for builtin in show rev-parse
1382 do
1383 test_expect_success "$builtin (cached blobs/trees)" "
1384 init_repos &&
1385
1386 test_all_match git $builtin :a &&
1387 test_all_match git $builtin :deep/a &&
1388 test_sparse_match git $builtin :folder1/a &&
1389
1390 # The error message differs depending on whether
1391 # the directory exists in the worktree.
1392 test_all_match test_must_fail git $builtin :deep/ &&
1393 test_must_fail git -C full-checkout $builtin :folder1/ &&
1394 test_sparse_match test_must_fail git $builtin :folder1/ &&
1395
1396 # Change the sparse cone for an extra case:
1397 run_on_sparse git sparse-checkout set deep/deeper1 &&
1398
1399 # deep/deeper2 is a sparse directory in the sparse index.
1400 test_sparse_match test_must_fail git $builtin :deep/deeper2/ &&
1401
1402 # deep/deeper2/deepest is not in the sparse index, but
1403 # will trigger an index expansion.
1404 test_sparse_match test_must_fail git $builtin :deep/deeper2/deepest/
1405 "
1406 done
1407
1408 test_expect_success 'submodule handling' '
1409 init_repos &&
1410
1411 test_sparse_match git sparse-checkout add modules &&
1412 test_all_match mkdir modules &&
1413 test_all_match touch modules/a &&
1414 test_all_match git add modules &&
1415 test_all_match git commit -m "add modules directory" &&
1416
1417 test_config_global protocol.file.allow always &&
1418
1419 run_on_all git submodule add "$(pwd)/initial-repo" modules/sub &&
1420 test_all_match git commit -m "add submodule" &&
1421
1422 # having a submodule prevents "modules" from collapse
1423 test_sparse_match git sparse-checkout set deep/deeper1 &&
1424 git -C sparse-index ls-files --sparse --stage >cache &&
1425 test_grep "100644 .* modules/a" cache &&
1426 test_grep "160000 $(git -C initial-repo rev-parse HEAD) 0 modules/sub" cache
1427 '
1428
1429 test_expect_success 'git apply functionality' '
1430 init_repos &&
1431
1432 test_all_match git checkout base &&
1433
1434 git -C full-checkout diff base..merge-right -- deep >patch-in-sparse &&
1435 git -C full-checkout diff base..merge-right -- folder2 >patch-outside &&
1436
1437 # Apply a patch to a file inside the sparse definition
1438 test_all_match git apply --index --stat ../patch-in-sparse &&
1439 test_all_match git status --porcelain=v2 &&
1440
1441 # Apply a patch to a file outside the sparse definition
1442 test_sparse_match test_must_fail git apply ../patch-outside &&
1443 test_grep "No such file or directory" sparse-checkout-err &&
1444
1445 # But it works with --index and --cached
1446 test_all_match git apply --index --stat ../patch-outside &&
1447 test_all_match git status --porcelain=v2 &&
1448 test_all_match git reset --hard &&
1449 test_all_match git apply --cached --stat ../patch-outside &&
1450 test_all_match git status --porcelain=v2
1451 '
1452
1453 # When working with a sparse index, some commands will need to expand the
1454 # index to operate properly. If those commands also write the index back
1455 # to disk, they need to convert the index to sparse before writing.
1456 # This test verifies that both of these events are logged in trace2 logs.
1457 test_expect_success 'sparse-index is expanded and converted back' '
1458 init_repos &&
1459
1460 GIT_TRACE2_EVENT="$(pwd)/trace2.txt" \
1461 git -C sparse-index reset -- folder1/a &&
1462 test_region index convert_to_sparse trace2.txt &&
1463 test_region index ensure_full_index trace2.txt &&
1464
1465 # ls-files expands on read, but does not write.
1466 rm trace2.txt &&
1467 GIT_TRACE2_EVENT="$(pwd)/trace2.txt" GIT_TRACE2_EVENT_NESTING=10 \
1468 git -C sparse-index ls-files &&
1469 test_region index ensure_full_index trace2.txt
1470 '
1471
1472 test_expect_success 'index.sparse disabled inline uses full index' '
1473 init_repos &&
1474
1475 # When index.sparse is disabled inline with `git status`, the
1476 # index is expanded at the beginning of the execution then never
1477 # converted back to sparse. It is then written to disk as a full index.
1478 rm -f trace2.txt &&
1479 GIT_TRACE2_EVENT="$(pwd)/trace2.txt" GIT_TRACE2_EVENT_NESTING=10 \
1480 git -C sparse-index -c index.sparse=false status &&
1481 ! test_region index convert_to_sparse trace2.txt &&
1482 test_region index ensure_full_index trace2.txt &&
1483
1484 # Since index.sparse is set to true at a repo level, the index
1485 # is converted from full to sparse when read, then never expanded
1486 # over the course of `git status`. It is written to disk as a sparse
1487 # index.
1488 rm -f trace2.txt &&
1489 GIT_TRACE2_EVENT="$(pwd)/trace2.txt" GIT_TRACE2_EVENT_NESTING=10 \
1490 git -C sparse-index status &&
1491 test_region index convert_to_sparse trace2.txt &&
1492 ! test_region index ensure_full_index trace2.txt &&
1493
1494 # Now that the index has been written to disk as sparse, it is not
1495 # converted to sparse (or expanded to full) when read by `git status`.
1496 rm -f trace2.txt &&
1497 GIT_TRACE2_EVENT="$(pwd)/trace2.txt" GIT_TRACE2_EVENT_NESTING=10 \
1498 git -C sparse-index status &&
1499 ! test_region index convert_to_sparse trace2.txt &&
1500 ! test_region index ensure_full_index trace2.txt
1501 '
1502
1503 run_sparse_index_trace2 () {
1504 rm -f trace2.txt &&
1505 if test -z "$WITHOUT_UNTRACKED_TXT"
1506 then
1507 echo >>sparse-index/untracked.txt
1508 fi &&
1509
1510 if test "$1" = "!"
1511 then
1512 shift &&
1513 test_must_fail env \
1514 GIT_TRACE2_EVENT="$(pwd)/trace2.txt" \
1515 git -C sparse-index "$@" \
1516 >sparse-index-out \
1517 2>sparse-index-error || return 1
1518 else
1519 GIT_TRACE2_EVENT="$(pwd)/trace2.txt" \
1520 git -C sparse-index "$@" \
1521 >sparse-index-out \
1522 2>sparse-index-error || return 1
1523 fi
1524 }
1525
1526 ensure_expanded () {
1527 run_sparse_index_trace2 "$@" &&
1528 test_region index ensure_full_index trace2.txt
1529 }
1530
1531 ensure_not_expanded () {
1532 run_sparse_index_trace2 "$@" &&
1533 test_region ! index ensure_full_index trace2.txt
1534 }
1535
1536 test_expect_success 'sparse-index is not expanded' '
1537 init_repos &&
1538
1539 ensure_not_expanded status &&
1540 ensure_not_expanded ls-files --sparse &&
1541 ensure_not_expanded commit --allow-empty -m empty &&
1542 echo >>sparse-index/a &&
1543 ensure_not_expanded commit -a -m a &&
1544 echo >>sparse-index/a &&
1545 ensure_not_expanded commit --include a -m a &&
1546 echo >>sparse-index/deep/deeper1/a &&
1547 ensure_not_expanded commit --include deep/deeper1/a -m deeper &&
1548 ensure_not_expanded checkout rename-out-to-out &&
1549 ensure_not_expanded checkout - &&
1550 ensure_not_expanded switch rename-out-to-out &&
1551 ensure_not_expanded switch - &&
1552 ensure_not_expanded reset --hard &&
1553 ensure_not_expanded checkout rename-out-to-out -- deep/deeper1 &&
1554 ensure_not_expanded reset --hard &&
1555 ensure_not_expanded restore -s rename-out-to-out -- deep/deeper1 &&
1556
1557 ensure_not_expanded ls-files deep/deeper1 &&
1558
1559 echo >>sparse-index/README.md &&
1560 ensure_not_expanded add -A &&
1561 echo >>sparse-index/extra.txt &&
1562 ensure_not_expanded add extra.txt &&
1563 echo >>sparse-index/untracked.txt &&
1564 ensure_not_expanded add . &&
1565
1566 ensure_not_expanded checkout-index -f a &&
1567 ensure_not_expanded checkout-index -f --all &&
1568 for ref in update-deep update-folder1 update-folder2 update-deep
1569 do
1570 echo >>sparse-index/README.md &&
1571 ensure_not_expanded reset --hard $ref || return 1
1572 done &&
1573
1574 ensure_not_expanded reset --mixed base &&
1575 ensure_not_expanded reset --hard update-deep &&
1576 ensure_not_expanded reset --keep base &&
1577 ensure_not_expanded reset --merge update-deep &&
1578 ensure_not_expanded reset --hard &&
1579
1580 ensure_not_expanded reset base -- deep/a &&
1581 ensure_not_expanded reset base -- nonexistent-file &&
1582 ensure_not_expanded reset deepest -- deep &&
1583
1584 # Although folder1 is outside the sparse definition, it exists as a
1585 # directory entry in the index, so the pathspec will not force the
1586 # index to be expanded.
1587 ensure_not_expanded reset deepest -- folder1 &&
1588 ensure_not_expanded reset deepest -- folder1/ &&
1589
1590 # Wildcard identifies only in-cone files, no index expansion
1591 ensure_not_expanded reset deepest -- deep/\* &&
1592
1593 # Wildcard identifies only full sparse directories, no index expansion
1594 ensure_not_expanded reset deepest -- folder\* &&
1595
1596 ensure_not_expanded clean -fd &&
1597
1598 ensure_not_expanded checkout -f update-deep &&
1599 test_config -C sparse-index pull.twohead ort &&
1600 (
1601 for OPERATION in "merge -m merge" cherry-pick rebase
1602 do
1603 ensure_not_expanded merge -m merge update-folder1 &&
1604 ensure_not_expanded merge -m merge update-folder2 || return 1
1605 done
1606 )
1607 '
1608
1609 test_expect_success 'sparse-index is not expanded: merge conflict in cone' '
1610 init_repos &&
1611
1612 for side in right left
1613 do
1614 git -C sparse-index checkout -b expand-$side base &&
1615 echo $side >sparse-index/deep/a &&
1616 git -C sparse-index commit -a -m "$side" || return 1
1617 done &&
1618
1619 (
1620 git -C sparse-index config pull.twohead ort &&
1621 ensure_not_expanded ! merge -m merged expand-right
1622 )
1623 '
1624
1625 test_expect_success 'sparse-index is not expanded: stash' '
1626 init_repos &&
1627
1628 echo >>sparse-index/a &&
1629 ensure_not_expanded stash &&
1630 ensure_not_expanded stash list &&
1631 ensure_not_expanded stash show stash@{0} &&
1632 ensure_not_expanded stash apply stash@{0} &&
1633 ensure_not_expanded stash drop stash@{0} &&
1634
1635 echo >>sparse-index/deep/new &&
1636 ensure_not_expanded stash -u &&
1637 (
1638 WITHOUT_UNTRACKED_TXT=1 &&
1639 ensure_not_expanded stash pop
1640 ) &&
1641
1642 ensure_not_expanded stash create &&
1643 oid=$(git -C sparse-index stash create) &&
1644 ensure_not_expanded stash store -m "test" $oid &&
1645 ensure_not_expanded reset --hard &&
1646 ensure_not_expanded stash pop
1647 '
1648
1649 test_expect_success 'sparse-index is not expanded: stash in-cone pathspec' '
1650 init_repos &&
1651
1652 echo unrelated >>sparse-index/deep/e &&
1653 echo literal >>sparse-index/deep/a &&
1654 ensure_not_expanded stash push -- deep/a &&
1655 test_grep ! literal sparse-index/deep/a &&
1656 test_grep unrelated sparse-index/deep/e &&
1657 ensure_not_expanded stash pop &&
1658 test_grep literal sparse-index/deep/a &&
1659
1660 echo prefixed >>sparse-index/deep/a &&
1661 ensure_not_expanded -C deep stash push -- a &&
1662 test_grep ! prefixed sparse-index/deep/a &&
1663 test_grep unrelated sparse-index/deep/e &&
1664 ensure_not_expanded stash pop &&
1665 test_grep prefixed sparse-index/deep/a &&
1666
1667 echo wildcard >>sparse-index/deep/a &&
1668 ensure_not_expanded stash push -- "deep/a*" &&
1669 test_grep ! wildcard sparse-index/deep/a &&
1670 test_grep unrelated sparse-index/deep/e &&
1671 ensure_not_expanded stash pop &&
1672 test_grep wildcard sparse-index/deep/a &&
1673
1674 echo pathspec-file >>sparse-index/deep/a &&
1675 echo deep/a >pathspec-file &&
1676 ensure_not_expanded stash push --pathspec-from-file=../pathspec-file &&
1677 test_grep ! pathspec-file sparse-index/deep/a &&
1678 test_grep unrelated sparse-index/deep/e &&
1679 ensure_not_expanded stash pop &&
1680 test_grep pathspec-file sparse-index/deep/a &&
1681
1682 echo multiple-a >>sparse-index/deep/a &&
1683 echo multiple-e >>sparse-index/deep/e &&
1684 ensure_not_expanded stash push -- deep/a deep/e &&
1685 test_grep ! multiple-a sparse-index/deep/a &&
1686 test_grep ! multiple-e sparse-index/deep/e &&
1687 ensure_not_expanded stash pop &&
1688 test_grep multiple-a sparse-index/deep/a &&
1689 test_grep multiple-e sparse-index/deep/e &&
1690
1691 echo staged >>sparse-index/deep/a &&
1692 git -C sparse-index add deep/a &&
1693 ensure_not_expanded stash push --staged -- deep/a &&
1694 test_grep ! staged sparse-index/deep/a &&
1695 test_grep unrelated sparse-index/deep/e &&
1696 ensure_not_expanded stash pop --index &&
1697 test_grep staged sparse-index/deep/a &&
1698 test_must_fail git -C sparse-index diff --cached --quiet -- deep/a &&
1699
1700 ensure_not_expanded ! stash push -- deep/does-not-exist &&
1701 test_grep "did not match any file" sparse-index-error
1702 '
1703
1704 test_expect_success 'describe tested on all' '
1705 init_repos &&
1706
1707 # Add tag to be read by describe
1708
1709 run_on_all git tag -a v1.0 -m "Version 1" &&
1710 test_all_match git describe --dirty &&
1711 run_on_all rm g &&
1712 test_all_match git describe --dirty
1713 '
1714
1715 test_expect_success 'ls-files filtering and expansion' '
1716 init_repos &&
1717
1718 # This filtering will hit a sparse directory midway
1719 # through the iteration.
1720 test_all_match git ls-files deep &&
1721
1722 # This pathspec will filter the index to only a sparse
1723 # directory.
1724 test_all_match git ls-files folder1
1725 '
1726
1727 test_expect_success 'sparse-index is not expanded: describe' '
1728 init_repos &&
1729
1730 # Add tag to be read by describe
1731
1732 git -C sparse-index tag -a v1.0 -m "Version 1" &&
1733
1734 ensure_not_expanded describe --dirty &&
1735 echo "test" >>sparse-index/g &&
1736 ensure_not_expanded describe --dirty &&
1737 ensure_not_expanded describe
1738 '
1739
1740 test_expect_success 'sparse index is not expanded: diff and diff-index' '
1741 init_repos &&
1742
1743 write_script edit-contents <<-\EOF &&
1744 echo text >>$1
1745 EOF
1746
1747 # Add file within cone
1748 test_sparse_match git sparse-checkout set deep &&
1749 run_on_all ../edit-contents deep/testfile &&
1750 test_all_match git add deep/testfile &&
1751 run_on_all ../edit-contents deep/testfile &&
1752
1753 test_all_match git diff &&
1754 test_all_match git diff --cached &&
1755 ensure_not_expanded diff &&
1756 ensure_not_expanded diff --cached &&
1757 ensure_not_expanded diff-index --cached HEAD &&
1758
1759 # Add file outside cone
1760 test_all_match git reset --hard &&
1761 run_on_all mkdir newdirectory &&
1762 run_on_all ../edit-contents newdirectory/testfile &&
1763 test_sparse_match git sparse-checkout set newdirectory &&
1764 test_all_match git add newdirectory/testfile &&
1765 run_on_all ../edit-contents newdirectory/testfile &&
1766 test_sparse_match git sparse-checkout set &&
1767
1768 test_all_match git diff &&
1769 test_all_match git diff --cached &&
1770 ensure_not_expanded diff &&
1771 ensure_not_expanded diff --cached &&
1772 ensure_not_expanded diff-index --cached HEAD &&
1773
1774 # Merge conflict outside cone
1775 # The sparse checkout will report a warning that is not in the
1776 # full checkout, so we use `run_on_all` instead of
1777 # `test_all_match`
1778 run_on_all git reset --hard &&
1779 test_all_match git checkout merge-left &&
1780 test_all_match test_must_fail git merge merge-right &&
1781
1782 test_all_match git diff &&
1783 test_all_match git diff --cached &&
1784 ensure_not_expanded diff &&
1785 ensure_not_expanded diff --cached &&
1786 ensure_not_expanded diff-index --cached HEAD
1787 '
1788
1789 test_expect_success 'sparse index is not expanded: show and rev-parse' '
1790 init_repos &&
1791
1792 ensure_not_expanded show :a &&
1793 ensure_not_expanded show :deep/a &&
1794 ensure_not_expanded rev-parse :a &&
1795 ensure_not_expanded rev-parse :deep/a
1796 '
1797
1798 test_expect_success 'sparse index is not expanded: update-index' '
1799 init_repos &&
1800
1801 deep_a_oid=$(git -C full-checkout rev-parse update-deep:deep/a) &&
1802 ensure_not_expanded update-index --cacheinfo 100644 $deep_a_oid deep/a &&
1803
1804 echo "test" >sparse-index/README.md &&
1805 echo "test2" >sparse-index/a &&
1806 rm -f sparse-index/deep/a &&
1807
1808 ensure_not_expanded update-index --add README.md &&
1809 ensure_not_expanded update-index a &&
1810 ensure_not_expanded update-index --remove deep/a &&
1811
1812 ensure_not_expanded reset --soft update-deep &&
1813 ensure_not_expanded update-index --add --remove --again
1814 '
1815
1816 test_expect_success 'sparse index is not expanded: blame' '
1817 init_repos &&
1818
1819 for file in a \
1820 deep/a \
1821 deep/deeper1/a \
1822 deep/deeper1/deepest/a
1823 do
1824 ensure_not_expanded blame $file || return 1
1825 done
1826 '
1827
1828 test_expect_success 'sparse index is not expanded: fetch/pull' '
1829 init_repos &&
1830
1831 git -C sparse-index remote add full "file://$(pwd)/full-checkout" &&
1832 ensure_not_expanded fetch full &&
1833 git -C full-checkout commit --allow-empty -m "for pull merge" &&
1834 git -C sparse-index commit --allow-empty -m "for pull merge" &&
1835 ensure_not_expanded pull full base
1836 '
1837
1838 test_expect_success 'sparse index is not expanded: read-tree' '
1839 init_repos &&
1840
1841 ensure_not_expanded checkout -b test-branch update-folder1 &&
1842 for MERGE_TREES in "base HEAD update-folder2" \
1843 "base HEAD rename-base" \
1844 "base update-folder2" \
1845 "base rename-base" \
1846 "update-folder2"
1847 do
1848 ensure_not_expanded read-tree -mu $MERGE_TREES &&
1849 ensure_not_expanded reset --hard || return 1
1850 done &&
1851
1852 rm -rf sparse-index/deep/deeper2 &&
1853 ensure_not_expanded add . &&
1854 ensure_not_expanded commit -m "test" &&
1855
1856 ensure_not_expanded read-tree --prefix=deep/deeper2 -u deepest
1857 '
1858
1859 test_expect_success 'ls-files' '
1860 init_repos &&
1861
1862 # Use a smaller sparse-checkout for reduced output
1863 test_sparse_match git sparse-checkout set &&
1864
1865 # Behavior agrees by default. Sparse index is expanded.
1866 test_all_match git ls-files &&
1867
1868 # With --sparse, the sparse index data changes behavior.
1869 git -C sparse-index ls-files --sparse >actual &&
1870
1871 cat >expect <<-\EOF &&
1872 a
1873 before/
1874 deep/
1875 e
1876 folder1-
1877 folder1.x
1878 folder1/
1879 folder10
1880 folder2/
1881 g
1882 x/
1883 z
1884 EOF
1885
1886 test_cmp expect actual &&
1887
1888 # With --sparse and no sparse index, nothing changes.
1889 git -C sparse-checkout ls-files >dense &&
1890 git -C sparse-checkout ls-files --sparse >sparse &&
1891 test_cmp dense sparse &&
1892
1893 # Set up a strange condition of having a file edit
1894 # outside of the sparse-checkout cone. We want to verify
1895 # that all modes handle this the same, and detect the
1896 # modification.
1897 write_script edit-content <<-\EOF &&
1898 mkdir -p folder1 &&
1899 echo content >>folder1/a
1900 EOF
1901 run_on_all ../edit-content &&
1902
1903 test_all_match git ls-files --modified &&
1904
1905 git -C sparse-index ls-files --sparse --modified >sparse-index-out &&
1906 cat >expect <<-\EOF &&
1907 folder1/a
1908 EOF
1909 test_cmp expect sparse-index-out &&
1910
1911 # Add folder1 to the sparse-checkout cone and
1912 # check that ls-files shows the expanded files.
1913 test_sparse_match git sparse-checkout add folder1 &&
1914 test_all_match git ls-files --modified &&
1915
1916 test_all_match git ls-files &&
1917 git -C sparse-index ls-files --sparse >actual &&
1918
1919 cat >expect <<-\EOF &&
1920 a
1921 before/
1922 deep/
1923 e
1924 folder1-
1925 folder1.x
1926 folder1/0/0/0
1927 folder1/0/1
1928 folder1/a
1929 folder10
1930 folder2/
1931 g
1932 x/
1933 z
1934 EOF
1935
1936 test_cmp expect actual &&
1937
1938 # Double-check index expansion is avoided
1939 ensure_not_expanded ls-files --sparse
1940 '
1941
1942 test_expect_success 'sparse index is not expanded: sparse-checkout' '
1943 init_repos &&
1944
1945 ensure_not_expanded sparse-checkout set deep/deeper2 &&
1946 ensure_not_expanded sparse-checkout set deep/deeper1 &&
1947 ensure_not_expanded sparse-checkout set deep &&
1948 ensure_not_expanded sparse-checkout add folder1 &&
1949 ensure_not_expanded sparse-checkout set deep/deeper1 &&
1950 ensure_not_expanded sparse-checkout set folder2 &&
1951
1952 # Demonstrate that the checks that "folder1/a" is a file
1953 # do not cause a sparse-index expansion (since it is in the
1954 # sparse-checkout cone).
1955 echo >>sparse-index/folder2/a &&
1956 git -C sparse-index add folder2/a &&
1957
1958 ensure_not_expanded sparse-checkout add folder1 &&
1959
1960 # Skip checks here, since deep/deeper1 is inside a sparse directory
1961 # that must be expanded to check whether `deep/deeper1` is a file
1962 # or not.
1963 ensure_not_expanded sparse-checkout set --skip-checks deep/deeper1 &&
1964 ensure_not_expanded sparse-checkout set
1965 '
1966
1967 # NEEDSWORK: a sparse-checkout behaves differently from a full checkout
1968 # in this scenario, but it shouldn't.
1969 test_expect_success 'reset mixed and checkout orphan' '
1970 init_repos &&
1971
1972 test_all_match git checkout rename-out-to-in &&
1973
1974 # Sparse checkouts do not agree with full checkouts about
1975 # how to report a directory/file conflict during a reset.
1976 # This command would fail with test_all_match because the
1977 # full checkout reports "T folder1/0/1" while a sparse
1978 # checkout reports "D folder1/0/1". This matches because
1979 # the sparse checkouts skip "adding" the other side of
1980 # the conflict.
1981 test_sparse_match git reset --mixed HEAD~1 &&
1982 test_sparse_match git ls-files --stage &&
1983 test_sparse_match git status --porcelain=v2 &&
1984
1985 # At this point, sparse-checkouts behave differently
1986 # from the full-checkout.
1987 test_sparse_match git checkout --orphan new-branch &&
1988 test_sparse_match git ls-files --stage &&
1989 test_sparse_match git status --porcelain=v2
1990 '
1991
1992 test_expect_success 'add everything with deep new file' '
1993 init_repos &&
1994
1995 run_on_sparse git sparse-checkout set deep/deeper1/deepest &&
1996
1997 run_on_all touch deep/deeper1/x &&
1998 test_all_match git add . &&
1999 test_all_match git status --porcelain=v2
2000 '
2001
2002 # NEEDSWORK: 'git checkout' behaves incorrectly in the case of
2003 # directory/file conflicts, even without sparse-checkout. Use this
2004 # test only as a documentation of the incorrect behavior, not a
2005 # measure of how it _should_ behave.
2006 test_expect_success 'checkout behaves oddly with df-conflict-1' '
2007 init_repos &&
2008
2009 test_sparse_match git sparse-checkout disable &&
2010
2011 write_script edit-content <<-\EOF &&
2012 echo content >>folder1/larger-content
2013 git add folder1
2014 EOF
2015
2016 run_on_all ../edit-content &&
2017 test_all_match git status --porcelain=v2 &&
2018
2019 git -C sparse-checkout sparse-checkout init --cone &&
2020 git -C sparse-index sparse-checkout init --cone --sparse-index &&
2021
2022 test_all_match git status --porcelain=v2 &&
2023
2024 # This checkout command should fail, because we have a staged
2025 # change to folder1/larger-content, but the destination changes
2026 # folder1 to a file.
2027 git -C full-checkout checkout df-conflict-1 \
2028 1>full-checkout-out \
2029 2>full-checkout-err &&
2030 git -C sparse-checkout checkout df-conflict-1 \
2031 1>sparse-checkout-out \
2032 2>sparse-checkout-err &&
2033 git -C sparse-index checkout df-conflict-1 \
2034 1>sparse-index-out \
2035 2>sparse-index-err &&
2036
2037 # Instead, the checkout deletes the folder1 file and adds the
2038 # folder1/larger-content file, leaving all other paths that were
2039 # in folder1/ as deleted (without any warning).
2040 cat >expect <<-EOF &&
2041 D folder1
2042 A folder1/larger-content
2043 EOF
2044 test_cmp expect full-checkout-out &&
2045 test_cmp expect sparse-checkout-out &&
2046
2047 # The sparse-index reports no output
2048 test_must_be_empty sparse-index-out &&
2049
2050 # stderr: Switched to branch df-conflict-1
2051 test_cmp full-checkout-err sparse-checkout-err &&
2052 test_cmp full-checkout-err sparse-checkout-err
2053 '
2054
2055 # NEEDSWORK: 'git checkout' behaves incorrectly in the case of
2056 # directory/file conflicts, even without sparse-checkout. Use this
2057 # test only as a documentation of the incorrect behavior, not a
2058 # measure of how it _should_ behave.
2059 test_expect_success 'checkout behaves oddly with df-conflict-2' '
2060 init_repos &&
2061
2062 test_sparse_match git sparse-checkout disable &&
2063
2064 write_script edit-content <<-\EOF &&
2065 echo content >>folder2/larger-content
2066 git add folder2
2067 EOF
2068
2069 run_on_all ../edit-content &&
2070 test_all_match git status --porcelain=v2 &&
2071
2072 git -C sparse-checkout sparse-checkout init --cone &&
2073 git -C sparse-index sparse-checkout init --cone --sparse-index &&
2074
2075 test_all_match git status --porcelain=v2 &&
2076
2077 # This checkout command should fail, because we have a staged
2078 # change to folder1/larger-content, but the destination changes
2079 # folder1 to a file.
2080 git -C full-checkout checkout df-conflict-2 \
2081 1>full-checkout-out \
2082 2>full-checkout-err &&
2083 git -C sparse-checkout checkout df-conflict-2 \
2084 1>sparse-checkout-out \
2085 2>sparse-checkout-err &&
2086 git -C sparse-index checkout df-conflict-2 \
2087 1>sparse-index-out \
2088 2>sparse-index-err &&
2089
2090 # The full checkout deviates from the df-conflict-1 case here!
2091 # It drops the change to folder1/larger-content and leaves the
2092 # folder1 path as-is on disk. The sparse-index behaves the same.
2093 test_must_be_empty full-checkout-out &&
2094 test_must_be_empty sparse-index-out &&
2095
2096 # In the sparse-checkout case, the checkout deletes the folder1
2097 # file and adds the folder1/larger-content file, leaving all other
2098 # paths that were in folder1/ as deleted (without any warning).
2099 cat >expect <<-EOF &&
2100 D folder2
2101 A folder2/larger-content
2102 EOF
2103 test_cmp expect sparse-checkout-out &&
2104
2105 # Switched to branch df-conflict-1
2106 test_cmp full-checkout-err sparse-checkout-err &&
2107 test_cmp full-checkout-err sparse-index-err
2108 '
2109
2110 test_expect_success 'mv directory from out-of-cone to in-cone' '
2111 init_repos &&
2112
2113 # <source> as a sparse directory (or SKIP_WORKTREE_DIR without enabling
2114 # sparse index).
2115 test_all_match git mv --sparse folder1 deep &&
2116 test_all_match git status --porcelain=v2 &&
2117 test_sparse_match git ls-files -t &&
2118 git -C sparse-checkout ls-files -t >actual &&
2119 test_grep -e "H deep/folder1/0/0/0" actual &&
2120 test_grep -e "H deep/folder1/0/1" actual &&
2121 test_grep -e "H deep/folder1/a" actual &&
2122
2123 test_all_match git reset --hard &&
2124
2125 # <source> as a directory deeper than sparse index boundary (where
2126 # sparse index will expand).
2127 test_sparse_match git mv --sparse folder1/0 deep &&
2128 test_sparse_match git status --porcelain=v2 &&
2129 test_sparse_match git ls-files -t &&
2130 git -C sparse-checkout ls-files -t >actual &&
2131 test_grep -e "H deep/0/0/0" actual &&
2132 test_grep -e "H deep/0/1" actual
2133 '
2134
2135 test_expect_success 'rm pathspec inside sparse definition' '
2136 init_repos &&
2137
2138 test_all_match git rm deep/a &&
2139 test_all_match git status --porcelain=v2 &&
2140
2141 # test wildcard
2142 run_on_all git reset --hard &&
2143 test_all_match git rm deep/* &&
2144 test_all_match git status --porcelain=v2 &&
2145
2146 # test recursive rm
2147 run_on_all git reset --hard &&
2148 test_all_match git rm -r deep &&
2149 test_all_match git status --porcelain=v2
2150 '
2151
2152 test_expect_success 'rm pathspec outside sparse definition' '
2153 init_repos &&
2154
2155 for file in folder1/a folder1/0/1
2156 do
2157 test_sparse_match test_must_fail git rm $file &&
2158 test_sparse_match test_must_fail git rm --cached $file &&
2159 test_sparse_match git rm --sparse $file &&
2160 test_sparse_match git status --porcelain=v2 || return 1
2161 done &&
2162
2163 cat >folder1-full <<-EOF &&
2164 rm ${SQ}folder1/0/0/0${SQ}
2165 rm ${SQ}folder1/0/1${SQ}
2166 rm ${SQ}folder1/a${SQ}
2167 EOF
2168
2169 cat >folder1-sparse <<-EOF &&
2170 rm ${SQ}folder1/${SQ}
2171 EOF
2172
2173 # test wildcard
2174 run_on_sparse git reset --hard &&
2175 run_on_sparse git sparse-checkout reapply &&
2176 test_sparse_match test_must_fail git rm folder1/* &&
2177 run_on_sparse git rm --sparse folder1/* &&
2178 test_cmp folder1-full sparse-checkout-out &&
2179 test_cmp folder1-sparse sparse-index-out &&
2180 test_sparse_match git status --porcelain=v2 &&
2181
2182 # test recursive rm
2183 run_on_sparse git reset --hard &&
2184 run_on_sparse git sparse-checkout reapply &&
2185 test_sparse_match test_must_fail git rm --sparse folder1 &&
2186 run_on_sparse git rm --sparse -r folder1 &&
2187 test_cmp folder1-full sparse-checkout-out &&
2188 test_cmp folder1-sparse sparse-index-out &&
2189 test_sparse_match git status --porcelain=v2
2190 '
2191
2192 test_expect_success 'rm pathspec expands index when necessary' '
2193 init_repos &&
2194
2195 # in-cone pathspec (do not expand)
2196 ensure_not_expanded rm "deep/deep*" &&
2197 test_must_be_empty sparse-index-err &&
2198
2199 # out-of-cone pathspec (expand)
2200 ! ensure_not_expanded rm --sparse "folder1/a*" &&
2201 test_must_be_empty sparse-index-err &&
2202
2203 # pathspec that should expand index
2204 ! ensure_not_expanded rm "*/a" &&
2205 test_must_be_empty sparse-index-err &&
2206
2207 ! ensure_not_expanded rm "**a" &&
2208 test_must_be_empty sparse-index-err
2209 '
2210
2211 test_expect_success 'sparse index is not expanded: rm' '
2212 init_repos &&
2213
2214 ensure_not_expanded rm deep/a &&
2215
2216 # test in-cone wildcard
2217 git -C sparse-index reset --hard &&
2218 ensure_not_expanded rm deep/* &&
2219
2220 # test recursive rm
2221 git -C sparse-index reset --hard &&
2222 ensure_not_expanded rm -r deep
2223 '
2224
2225 test_expect_success 'sparse index is not expanded: prefixed wildcard pathspec' '
2226 init_repos &&
2227
2228 ensure_not_expanded -C deep rm --dry-run -- "a*" &&
2229 ensure_not_expanded -C deep reset base -- "a*"
2230 '
2231
2232 test_expect_success 'grep with and --cached' '
2233 init_repos &&
2234
2235 test_all_match git grep --cached a &&
2236 test_all_match git grep --cached a -- "folder1/*"
2237 '
2238
2239 test_expect_success 'grep is not expanded' '
2240 init_repos &&
2241
2242 ensure_not_expanded grep a &&
2243 ensure_not_expanded grep a -- deep/* &&
2244
2245 # All files within the folder1/* pathspec are sparse,
2246 # so this command does not find any matches
2247 ensure_not_expanded ! grep a -- folder1/* &&
2248
2249 # test out-of-cone pathspec with or without wildcard
2250 ensure_not_expanded grep --cached a -- "folder1/a" &&
2251 ensure_not_expanded grep --cached a -- "folder1/*" &&
2252
2253 # test in-cone pathspec with or without wildcard
2254 ensure_not_expanded grep --cached a -- "deep/a" &&
2255 ensure_not_expanded grep --cached a -- "deep/*"
2256 '
2257
2258 # NEEDSWORK: when running `grep` in the superproject with --recurse-submodules,
2259 # Git expands the index of the submodules unexpectedly. Even though `grep`
2260 # builtin is marked as "command_requires_full_index = 0", this config is only
2261 # useful for the superproject. Namely, the submodules have their own configs,
2262 # which are _not_ populated by the one-time sparse-index feature switch.
2263 test_expect_failure 'grep within submodules is not expanded' '
2264 init_repos_as_submodules &&
2265
2266 # do not use ensure_not_expanded() here, because `grep` should be
2267 # run in the superproject, not in "./sparse-index"
2268 GIT_TRACE2_EVENT="$(pwd)/trace2.txt" \
2269 git grep --cached --recurse-submodules a -- "*/folder1/*" &&
2270 test_region ! index ensure_full_index trace2.txt
2271 '
2272
2273 # NEEDSWORK: this test is not actually testing the code. The design purpose
2274 # of this test is to verify the grep result when the submodules are using a
2275 # sparse-index. Namely, we want "folder1/" as a tree (a sparse directory); but
2276 # because of the index expansion, we are now grepping the "folder1/a" blob.
2277 # Because of the problem stated above 'grep within submodules is not expanded',
2278 # we don't have the ideal test environment yet.
2279 test_expect_success 'grep sparse directory within submodules' '
2280 init_repos_as_submodules &&
2281
2282 cat >expect <<-\EOF &&
2283 full-checkout/folder1/a:a
2284 sparse-checkout/folder1/a:a
2285 sparse-index/folder1/a:a
2286 EOF
2287 git grep --cached --recurse-submodules a -- "*/folder1/*" >actual &&
2288 test_cmp actual expect
2289 '
2290
2291 test_expect_success 'write-tree' '
2292 init_repos &&
2293
2294 test_all_match git write-tree &&
2295
2296 write_script edit-contents <<-\EOF &&
2297 echo text >>"$1"
2298 EOF
2299
2300 # make a change inside the sparse cone
2301 run_on_all ../edit-contents deep/a &&
2302 test_all_match git update-index deep/a &&
2303 test_all_match git write-tree &&
2304 test_all_match git status --porcelain=v2 &&
2305
2306 # make a change outside the sparse cone
2307 run_on_all mkdir -p folder1 &&
2308 run_on_all cp a folder1/a &&
2309 run_on_all ../edit-contents folder1/a &&
2310 test_all_match git update-index folder1/a &&
2311 test_all_match git write-tree &&
2312 test_all_match git status --porcelain=v2 &&
2313
2314 # check that SKIP_WORKTREE files are not materialized
2315 test_path_is_missing sparse-checkout/folder2/a &&
2316 test_path_is_missing sparse-index/folder2/a
2317 '
2318
2319 test_expect_success 'sparse-index is not expanded: write-tree' '
2320 init_repos &&
2321
2322 ensure_not_expanded write-tree &&
2323
2324 echo "test1" >>sparse-index/a &&
2325 git -C sparse-index update-index a &&
2326 ensure_not_expanded write-tree
2327 '
2328
2329 test_expect_success 'diff-files with pathspec inside sparse definition' '
2330 init_repos &&
2331
2332 write_script edit-contents <<-\EOF &&
2333 echo text >>"$1"
2334 EOF
2335
2336 run_on_all ../edit-contents deep/a &&
2337
2338 test_all_match git diff-files &&
2339
2340 test_all_match git diff-files -- deep/a &&
2341
2342 # test wildcard
2343 test_all_match git diff-files -- "deep/*"
2344 '
2345
2346 test_expect_success 'diff-files with pathspec outside sparse definition' '
2347 init_repos &&
2348
2349 test_sparse_match git diff-files -- folder2/a &&
2350
2351 write_script edit-contents <<-\EOF &&
2352 echo text >>"$1"
2353 EOF
2354
2355 # The directory "folder1" is outside the cone of interest
2356 # and will not exist in the sparse checkout repositories.
2357 # Create it as needed, add file "folder1/a" there with
2358 # contents that is different from the staged version.
2359 run_on_all mkdir -p folder1 &&
2360 run_on_all cp a folder1/a &&
2361
2362 run_on_all ../edit-contents folder1/a &&
2363 test_all_match git diff-files &&
2364 test_all_match git diff-files -- folder1/a &&
2365 test_all_match git diff-files -- "folder*/a"
2366 '
2367
2368 test_expect_success 'sparse index is not expanded: diff-files' '
2369 init_repos &&
2370
2371 write_script edit-contents <<-\EOF &&
2372 echo text >>"$1"
2373 EOF
2374
2375 run_on_all ../edit-contents deep/a &&
2376
2377 ensure_not_expanded diff-files &&
2378 ensure_not_expanded diff-files -- deep/a &&
2379 ensure_not_expanded diff-files -- "deep/*"
2380 '
2381
2382 test_expect_success 'diff-tree' '
2383 init_repos &&
2384
2385 # Test change inside sparse cone
2386 tree1=$(git -C sparse-index rev-parse HEAD^{tree}) &&
2387 tree2=$(git -C sparse-index rev-parse update-deep^{tree}) &&
2388 test_all_match git diff-tree $tree1 $tree2 &&
2389 test_all_match git diff-tree $tree1 $tree2 -- deep/a &&
2390 test_all_match git diff-tree HEAD update-deep &&
2391 test_all_match git diff-tree HEAD update-deep -- deep/a &&
2392
2393 # Test change outside sparse cone
2394 tree3=$(git -C sparse-index rev-parse update-folder1^{tree}) &&
2395 test_all_match git diff-tree $tree1 $tree3 &&
2396 test_all_match git diff-tree $tree1 $tree3 -- folder1/a &&
2397 test_all_match git diff-tree HEAD update-folder1 &&
2398 test_all_match git diff-tree HEAD update-folder1 -- folder1/a &&
2399
2400 # Check that SKIP_WORKTREE files are not materialized
2401 test_path_is_missing sparse-checkout/folder1/a &&
2402 test_path_is_missing sparse-index/folder1/a &&
2403 test_path_is_missing sparse-checkout/folder2/a &&
2404 test_path_is_missing sparse-index/folder2/a
2405 '
2406
2407 test_expect_success 'sparse-index is not expanded: diff-tree' '
2408 init_repos &&
2409
2410 tree1=$(git -C sparse-index rev-parse HEAD^{tree}) &&
2411 tree2=$(git -C sparse-index rev-parse update-deep^{tree}) &&
2412 tree3=$(git -C sparse-index rev-parse update-folder1^{tree}) &&
2413
2414 ensure_not_expanded diff-tree $tree1 $tree2 &&
2415 ensure_not_expanded diff-tree $tree1 $tree2 -- deep/a &&
2416 ensure_not_expanded diff-tree HEAD update-deep &&
2417 ensure_not_expanded diff-tree HEAD update-deep -- deep/a &&
2418 ensure_not_expanded diff-tree $tree1 $tree3 &&
2419 ensure_not_expanded diff-tree $tree1 $tree3 -- folder1/a &&
2420 ensure_not_expanded diff-tree HEAD update-folder1 &&
2421 ensure_not_expanded diff-tree HEAD update-folder1 -- folder1/a
2422 '
2423
2424 test_expect_success 'worktree' '
2425 init_repos &&
2426
2427 write_script edit-contents <<-\EOF &&
2428 echo text >>"$1"
2429 EOF
2430
2431 for repo in full-checkout sparse-checkout sparse-index
2432 do
2433 worktree=${repo}-wt &&
2434 git -C $repo worktree add ../$worktree &&
2435
2436 # Compare worktree content with "ls"
2437 (cd $repo && ls) >worktree_contents &&
2438 (cd $worktree && ls) >new_worktree_contents &&
2439 test_cmp worktree_contents new_worktree_contents &&
2440
2441 # Compare index content with "ls-files --sparse"
2442 git -C $repo ls-files --sparse >index_contents &&
2443 git -C $worktree ls-files --sparse >new_index_contents &&
2444 test_cmp index_contents new_index_contents &&
2445
2446 git -C $repo worktree remove ../$worktree || return 1
2447 done &&
2448
2449 test_all_match git worktree add .worktrees/hotfix &&
2450 run_on_all ../edit-contents .worktrees/hotfix/deep/a &&
2451 test_all_match test_must_fail git worktree remove .worktrees/hotfix
2452 '
2453
2454 test_expect_success 'worktree is not expanded' '
2455 init_repos &&
2456
2457 ensure_not_expanded worktree add .worktrees/hotfix &&
2458 ensure_not_expanded worktree remove .worktrees/hotfix
2459 '
2460
2461 test_expect_success 'check-attr with pathspec inside sparse definition' '
2462 init_repos &&
2463
2464 echo "a -crlf myAttr" >>.gitattributes &&
2465 run_on_all cp ../.gitattributes ./deep &&
2466
2467 test_all_match git check-attr -a -- deep/a &&
2468
2469 test_all_match git add deep/.gitattributes &&
2470 test_all_match git check-attr -a --cached -- deep/a
2471 '
2472
2473 test_expect_success 'check-attr with pathspec outside sparse definition' '
2474 init_repos &&
2475
2476 echo "a -crlf myAttr" >>.gitattributes &&
2477 run_on_sparse mkdir folder1 &&
2478 run_on_all cp ../.gitattributes ./folder1 &&
2479 run_on_all cp a folder1/a &&
2480
2481 test_all_match git check-attr -a -- folder1/a &&
2482
2483 git -C full-checkout add folder1/.gitattributes &&
2484 test_sparse_match git add --sparse folder1/.gitattributes &&
2485 test_all_match git commit -m "add .gitattributes" &&
2486 test_sparse_match git sparse-checkout reapply &&
2487 test_all_match git check-attr -a --cached -- folder1/a
2488 '
2489
2490 # NEEDSWORK: The 'diff --check' test is left as 'test_expect_failure' due
2491 # to an underlying issue in oneway_diff() within diff-lib.c.
2492 # 'do_oneway_diff()' is not called as expected for paths that could match
2493 # inside of a sparse directory. Specifically, the 'ce_path_match()' function
2494 # fails to recognize files inside a sparse directory (e.g., when 'folder1/'
2495 # is a sparse directory, 'folder1/a' cannot be recognized). The goal is to
2496 # proceed with 'do_oneway_diff()' if the pathspec could match inside of a
2497 # sparse directory.
2498 test_expect_failure 'diff --check with pathspec outside sparse definition' '
2499 init_repos &&
2500
2501 write_script edit-contents <<-\EOF &&
2502 echo "a " >"$1"
2503 EOF
2504
2505 test_all_match git config core.whitespace -trailing-space,-space-before-tab &&
2506
2507 echo "a whitespace=trailing-space,space-before-tab" >>.gitattributes &&
2508 run_on_all mkdir -p folder1 &&
2509 run_on_all cp ../.gitattributes ./folder1 &&
2510 test_all_match git add --sparse folder1/.gitattributes &&
2511 run_on_all ../edit-contents folder1/a &&
2512 test_all_match git add --sparse folder1/a &&
2513
2514 test_sparse_match git sparse-checkout reapply &&
2515 test_all_match test_must_fail git diff --check --cached -- folder1/a
2516 '
2517
2518 test_expect_success 'sparse-index is not expanded: check-attr' '
2519 init_repos &&
2520
2521 echo "a -crlf myAttr" >>.gitattributes &&
2522 mkdir ./sparse-index/folder1 &&
2523 cp ./sparse-index/a ./sparse-index/folder1/a &&
2524 cp .gitattributes ./sparse-index/deep &&
2525 cp .gitattributes ./sparse-index/folder1 &&
2526
2527 git -C sparse-index add deep/.gitattributes &&
2528 git -C sparse-index add --sparse folder1/.gitattributes &&
2529 ensure_not_expanded check-attr -a --cached -- deep/a &&
2530 ensure_not_expanded check-attr -a --cached -- folder1/a
2531 '
2532
2533 test_expect_success 'sparse-index is not expanded: git apply' '
2534 init_repos &&
2535
2536 git -C sparse-index checkout base &&
2537 git -C full-checkout diff base..merge-right -- deep >patch-in-sparse &&
2538 git -C full-checkout diff base..merge-right -- folder2 >patch-outside &&
2539
2540 # Apply a patch to a file inside the sparse definition
2541 ensure_not_expanded apply --index --stat ../patch-in-sparse &&
2542
2543 # Apply a patch to a file outside the sparse definition
2544 # Fails when caring about the worktree.
2545 ensure_not_expanded ! apply ../patch-outside &&
2546
2547 # Expands when using --index.
2548 ensure_expanded apply --index ../patch-outside &&
2549
2550 # Does not when index is partially expanded.
2551 git -C sparse-index reset --hard &&
2552 ensure_not_expanded apply --cached ../patch-outside &&
2553
2554 # Try again with a reset and collapsed index.
2555 git -C sparse-index reset --hard &&
2556 git -C sparse-index sparse-checkout reapply &&
2557
2558 # Expands when index is collapsed.
2559 ensure_expanded apply --cached ../patch-outside
2560 '
2561
2562 test_expect_success 'sparse-index is not expanded: git add -p' '
2563 init_repos &&
2564
2565 # Does not expand when edits are within sparse checkout.
2566 echo "new content" >sparse-index/deep/a &&
2567 echo "new content" >sparse-index/deep/deeper1/a &&
2568 test_write_lines y n >in &&
2569 ensure_not_expanded add -p <in &&
2570 git -C sparse-index reset &&
2571 ensure_not_expanded add -i <in &&
2572
2573 # -p does expand when edits are outside sparse checkout.
2574 mkdir -p sparse-index/folder1 &&
2575 echo "new content" >sparse-index/folder1/a &&
2576 test_write_lines y n y >in &&
2577 ensure_expanded add -p <in &&
2578
2579 # Fully reset the index.
2580 git -C sparse-index reset --hard &&
2581 git -C sparse-index sparse-checkout reapply &&
2582
2583 # -i does expand when edits are outside sparse checkout.
2584 mkdir -p sparse-index/folder1 &&
2585 echo "new content" >sparse-index/folder1/a &&
2586 test_write_lines u 2 3 "" q >in &&
2587 ensure_expanded add -i <in
2588 '
2589
2590 test_expect_success 'sparse-index is not expanded: checkout -p, reset -p' '
2591 init_repos &&
2592
2593 # Does not expand when edits are within sparse checkout.
2594 echo "new content" >sparse-index/deep/a &&
2595 echo "new content" >sparse-index/deep/deeper1/a &&
2596 git -C sparse-index commit -a -m "inside-changes" &&
2597
2598 test_write_lines y y >in &&
2599 ensure_not_expanded checkout HEAD~1 --patch <in &&
2600
2601 echo "new content" >sparse-index/deep/a &&
2602 echo "new content" >sparse-index/deep/deeper1/a &&
2603 git -C sparse-index add . &&
2604 ensure_not_expanded reset --patch <in &&
2605
2606 # -p does expand when edits are outside sparse checkout.
2607 mkdir -p sparse-index/folder1 &&
2608 echo "new content" >sparse-index/folder1/a &&
2609 git -C sparse-index add --sparse folder1 &&
2610 git -C sparse-index sparse-checkout reapply &&
2611 ensure_expanded reset --patch <in &&
2612
2613 # Fully reset the index.
2614 mkdir -p sparse-index/folder1 &&
2615 echo "new content" >sparse-index/folder1/a &&
2616 git -C sparse-index add --sparse folder1 &&
2617 git -C sparse-index commit -m "folder1 change" &&
2618 git -C sparse-index sparse-checkout reapply &&
2619 ensure_expanded checkout HEAD~1 --patch <in
2620 '
2621
2622 test_expect_success 'advice.sparseIndexExpanded' '
2623 init_repos &&
2624
2625 git -C sparse-index config --unset advice.sparseIndexExpanded &&
2626 git -C sparse-index sparse-checkout set deep/deeper1 &&
2627 mkdir -p sparse-index/deep/deeper2/deepest &&
2628 touch sparse-index/deep/deeper2/deepest/bogus &&
2629 git -C sparse-index status 2>err &&
2630 test_grep "The sparse index is expanding to a full index" err &&
2631
2632 git -C sparse-index sparse-checkout disable 2>err &&
2633 test_line_count = 0 err
2634 '
2635
2636 test_expect_success 'cat-file -p' '
2637 init_repos &&
2638 echo "new content" >>full-checkout/deep/a &&
2639 echo "new content" >>sparse-checkout/deep/a &&
2640 echo "new content" >>sparse-index/deep/a &&
2641 run_on_all git add deep/a &&
2642
2643 test_all_match git cat-file -p :deep/a &&
2644 ensure_not_expanded cat-file -p :deep/a &&
2645 test_all_match git cat-file -p :folder1/a &&
2646 ensure_expanded cat-file -p :folder1/a
2647 '
2648
2649 test_expect_success 'cat-file --batch' '
2650 init_repos &&
2651 echo "new content" >>full-checkout/deep/a &&
2652 echo "new content" >>sparse-checkout/deep/a &&
2653 echo "new content" >>sparse-index/deep/a &&
2654 run_on_all git add deep/a &&
2655
2656 echo ":deep/a" >in &&
2657 test_all_match git cat-file --batch <in &&
2658 ensure_not_expanded cat-file --batch <in &&
2659
2660 echo ":folder1/a" >in &&
2661 test_all_match git cat-file --batch <in &&
2662 ensure_expanded cat-file --batch <in &&
2663
2664 cat >in <<-\EOF &&
2665 :deep/a
2666 :folder1/a
2667 EOF
2668 test_all_match git cat-file --batch <in &&
2669 ensure_expanded cat-file --batch <in
2670 '
2671
2672 test_expect_success 'merge -s ours' '
2673 init_repos &&
2674
2675 test_all_match git rev-parse HEAD^{tree} &&
2676 test_all_match git merge -s ours merge-right &&
2677 test_all_match git rev-parse HEAD^{tree} &&
2678 test_all_match git rev-parse HEAD^2
2679 '
2680
2681 test_expect_success 'sparse-index is not expanded: merge-ours' '
2682 init_repos &&
2683 ensure_not_expanded merge -s ours merge-right
2684 '
2685
2686 test_expect_success 'restore --staged with sparse definition' '
2687 init_repos &&
2688
2689 # Stage changes within the sparse definition
2690 test_all_match git checkout -b restore-staged-1 base &&
2691 test_all_match git reset --soft update-deep &&
2692 test_all_match git restore --staged . &&
2693 test_all_match git status --porcelain=v2 &&
2694 test_all_match git diff --cached
2695 '
2696
2697 test_expect_success 'restore --staged with outside sparse definition' '
2698 init_repos &&
2699
2700 # Stage changes that include paths outside the sparse definition.
2701 # Although the working tree differs between full and sparse checkouts
2702 # after restore, the state of the index should be the same.
2703 test_all_match git checkout -b restore-staged-2 base &&
2704 test_all_match git reset --soft update-folder1 &&
2705 test_sparse_match git restore --staged . &&
2706 git -C full-checkout restore --staged . &&
2707 test_all_match git ls-files -s -- folder1 &&
2708 test_all_match git diff --cached -- folder1
2709 '
2710
2711 test_expect_success 'restore --staged with wildcards' '
2712 init_repos &&
2713
2714 test_all_match git checkout -b restore-staged-3 base &&
2715 test_all_match git reset --soft update-deep &&
2716 test_all_match git restore --staged "deep/*" &&
2717 test_all_match git status --porcelain=v2 &&
2718 test_all_match git diff --cached
2719 '
2720
2721 test_expect_success 'sparse-index is not expanded: restore --staged' '
2722 init_repos &&
2723
2724 git -C sparse-index checkout -b restore-staged-exp base &&
2725 git -C sparse-index reset --soft update-folder1 &&
2726 ensure_not_expanded restore --staged .
2727 '
2728
2729 test_expect_success 'sparse-index is not expanded: restore --source --staged' '
2730 init_repos &&
2731
2732 git -C sparse-index checkout -b restore-source-staged base &&
2733 ensure_not_expanded restore --source update-folder1 --staged .
2734 '
2735
2736 test_done