Skip to content

Commit 506d2a3

Browse files
committed
Merge branch 'ds/commit-and-checkout-with-sparse-index'
"git checkout" and "git commit" learn to work without unnecessarily expanding sparse indexes. * ds/commit-and-checkout-with-sparse-index: unpack-trees: resolve sparse-directory/file conflicts t1092: document bad 'git checkout' behavior checkout: stop expanding sparse indexes sparse-index: recompute cache-tree commit: integrate with sparse-index p2000: compress repo names p2000: add 'git checkout -' test and decrease depth
2 parents 58705b4 + e05cdb1 commit 506d2a3

File tree

7 files changed

+240
-30
lines changed

7 files changed

+240
-30
lines changed

builtin/checkout.c

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -378,9 +378,6 @@ static int checkout_worktree(const struct checkout_opts *opts,
378378
if (pc_workers > 1)
379379
init_parallel_checkout();
380380

381-
/* TODO: audit for interaction with sparse-index. */
382-
ensure_full_index(&the_index);
383-
384381
for (pos = 0; pos < active_nr; pos++) {
385382
struct cache_entry *ce = active_cache[pos];
386383
if (ce->ce_flags & CE_MATCHED) {
@@ -530,8 +527,6 @@ static int checkout_paths(const struct checkout_opts *opts,
530527
* Make sure all pathspecs participated in locating the paths
531528
* to be checked out.
532529
*/
533-
/* TODO: audit for interaction with sparse-index. */
534-
ensure_full_index(&the_index);
535530
for (pos = 0; pos < active_nr; pos++)
536531
if (opts->overlay_mode)
537532
mark_ce_for_checkout_overlay(active_cache[pos],
@@ -1593,6 +1588,9 @@ static int checkout_main(int argc, const char **argv, const char *prefix,
15931588

15941589
git_config(git_checkout_config, opts);
15951590

1591+
prepare_repo_settings(the_repository);
1592+
the_repository->settings.command_requires_full_index = 0;
1593+
15961594
opts->track = BRANCH_TRACK_UNSPECIFIED;
15971595

15981596
if (!opts->accept_pathspec && !opts->accept_ref)

builtin/commit.c

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1689,6 +1689,9 @@ int cmd_commit(int argc, const char **argv, const char *prefix)
16891689
if (argc == 2 && !strcmp(argv[1], "-h"))
16901690
usage_with_options(builtin_commit_usage, builtin_commit_options);
16911691

1692+
prepare_repo_settings(the_repository);
1693+
the_repository->settings.command_requires_full_index = 0;
1694+
16921695
status_init_config(&s, git_commit_config);
16931696
s.commit_template = 1;
16941697
status_format = STATUS_FORMAT_NONE; /* Ignore status.short */

cache-tree.c

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -465,8 +465,6 @@ int cache_tree_update(struct index_state *istate, int flags)
465465
if (i)
466466
return i;
467467

468-
ensure_full_index(istate);
469-
470468
if (!istate->cache_tree)
471469
istate->cache_tree = cache_tree();
472470

sparse-index.c

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -170,6 +170,8 @@ int convert_to_sparse(struct index_state *istate)
170170
if (index_has_unmerged_entries(istate))
171171
return 0;
172172

173+
/* Clear and recompute the cache-tree */
174+
cache_tree_free(&istate->cache_tree);
173175
if (cache_tree_update(istate, 0)) {
174176
warning(_("unable to update cache-tree, staying full"));
175177
return -1;

t/perf/p2000-sparse-operations.sh

Lines changed: 30 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ test_description="test performance of Git operations using the index"
66

77
test_perf_default_repo
88

9-
SPARSE_CONE=f2/f4/f1
9+
SPARSE_CONE=f2/f4
1010

1111
test_expect_success 'setup repo and indexes' '
1212
git reset --hard HEAD &&
@@ -27,7 +27,7 @@ test_expect_success 'setup repo and indexes' '
2727
OLD_COMMIT=$(git rev-parse HEAD) &&
2828
OLD_TREE=$(git rev-parse HEAD^{tree}) &&
2929
30-
for i in $(test_seq 1 4)
30+
for i in $(test_seq 1 3)
3131
do
3232
cat >in <<-EOF &&
3333
100755 blob $BLOB a
@@ -43,45 +43,57 @@ test_expect_success 'setup repo and indexes' '
4343
done &&
4444
4545
git sparse-checkout init --cone &&
46-
git branch -f wide $OLD_COMMIT &&
47-
git -c core.sparseCheckoutCone=true clone --branch=wide --sparse . full-index-v3 &&
46+
git sparse-checkout set $SPARSE_CONE &&
47+
git checkout -b wide $OLD_COMMIT &&
48+
49+
for l2 in f1 f2 f3 f4
50+
do
51+
echo more bogus >>$SPARSE_CONE/$l2/a &&
52+
git commit -a -m "edit $SPARSE_CONE/$l2/a" || return 1
53+
done &&
54+
55+
git -c core.sparseCheckoutCone=true clone --branch=wide --sparse . full-v3 &&
4856
(
49-
cd full-index-v3 &&
57+
cd full-v3 &&
5058
git sparse-checkout init --cone &&
5159
git sparse-checkout set $SPARSE_CONE &&
5260
git config index.version 3 &&
53-
git update-index --index-version=3
61+
git update-index --index-version=3 &&
62+
git checkout HEAD~4
5463
) &&
55-
git -c core.sparseCheckoutCone=true clone --branch=wide --sparse . full-index-v4 &&
64+
git -c core.sparseCheckoutCone=true clone --branch=wide --sparse . full-v4 &&
5665
(
57-
cd full-index-v4 &&
66+
cd full-v4 &&
5867
git sparse-checkout init --cone &&
5968
git sparse-checkout set $SPARSE_CONE &&
6069
git config index.version 4 &&
61-
git update-index --index-version=4
70+
git update-index --index-version=4 &&
71+
git checkout HEAD~4
6272
) &&
63-
git -c core.sparseCheckoutCone=true clone --branch=wide --sparse . sparse-index-v3 &&
73+
git -c core.sparseCheckoutCone=true clone --branch=wide --sparse . sparse-v3 &&
6474
(
65-
cd sparse-index-v3 &&
75+
cd sparse-v3 &&
6676
git sparse-checkout init --cone --sparse-index &&
6777
git sparse-checkout set $SPARSE_CONE &&
6878
git config index.version 3 &&
69-
git update-index --index-version=3
79+
git update-index --index-version=3 &&
80+
git checkout HEAD~4
7081
) &&
71-
git -c core.sparseCheckoutCone=true clone --branch=wide --sparse . sparse-index-v4 &&
82+
git -c core.sparseCheckoutCone=true clone --branch=wide --sparse . sparse-v4 &&
7283
(
73-
cd sparse-index-v4 &&
84+
cd sparse-v4 &&
7485
git sparse-checkout init --cone --sparse-index &&
7586
git sparse-checkout set $SPARSE_CONE &&
7687
git config index.version 4 &&
77-
git update-index --index-version=4
88+
git update-index --index-version=4 &&
89+
git checkout HEAD~4
7890
)
7991
'
8092

8193
test_perf_on_all () {
8294
command="$@"
83-
for repo in full-index-v3 full-index-v4 \
84-
sparse-index-v3 sparse-index-v4
95+
for repo in full-v3 full-v4 \
96+
sparse-v3 sparse-v4
8597
do
8698
test_perf "$command ($repo)" "
8799
(
@@ -97,5 +109,6 @@ test_perf_on_all git status
97109
test_perf_on_all git add -A
98110
test_perf_on_all git add .
99111
test_perf_on_all git commit -a -m A
112+
test_perf_on_all git checkout -f -
100113

101114
test_done

t/t1092-sparse-checkout-compatibility.sh

Lines changed: 191 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,25 @@ test_expect_success 'setup' '
9595
git add . &&
9696
git commit -m "rename deep/deeper1/... to folder1/..." &&
9797
98+
git checkout -b df-conflict-1 base &&
99+
rm -rf folder1 &&
100+
echo content >folder1 &&
101+
git add . &&
102+
git commit -m "dir to file" &&
103+
104+
git checkout -b df-conflict-2 base &&
105+
rm -rf folder2 &&
106+
echo content >folder2 &&
107+
git add . &&
108+
git commit -m "dir to file" &&
109+
110+
git checkout -b fd-conflict base &&
111+
rm a &&
112+
mkdir a &&
113+
echo content >a/a &&
114+
git add . &&
115+
git commit -m "file to dir" &&
116+
98117
git checkout -b deepest base &&
99118
echo "updated deepest" >deep/deeper1/deepest/a &&
100119
git commit -a -m "update deepest" &&
@@ -262,6 +281,34 @@ test_expect_success 'add, commit, checkout' '
262281
test_all_match git checkout -
263282
'
264283

284+
test_expect_success 'commit including unstaged changes' '
285+
init_repos &&
286+
287+
write_script edit-file <<-\EOF &&
288+
echo $1 >$2
289+
EOF
290+
291+
run_on_all ../edit-file 1 a &&
292+
run_on_all ../edit-file 1 deep/a &&
293+
294+
test_all_match git commit -m "-a" -a &&
295+
test_all_match git status --porcelain=v2 &&
296+
297+
run_on_all ../edit-file 2 a &&
298+
run_on_all ../edit-file 2 deep/a &&
299+
300+
test_all_match git commit -m "--include" --include deep/a &&
301+
test_all_match git status --porcelain=v2 &&
302+
test_all_match git commit -m "--include" --include a &&
303+
test_all_match git status --porcelain=v2 &&
304+
305+
run_on_all ../edit-file 3 a &&
306+
run_on_all ../edit-file 3 deep/a &&
307+
308+
test_all_match git commit -m "--amend" -a --amend &&
309+
test_all_match git status --porcelain=v2
310+
'
311+
265312
test_expect_success 'status/add: outside sparse cone' '
266313
init_repos &&
267314
@@ -330,10 +377,16 @@ test_expect_success 'diff --staged' '
330377
test_all_match git diff --staged
331378
'
332379

380+
# NEEDSWORK: sparse-checkout behaves differently from full-checkout when
381+
# running this test with 'df-conflict-2' after 'df-conflict-1'.
333382
test_expect_success 'diff with renames and conflicts' '
334383
init_repos &&
335384
336-
for branch in rename-out-to-out rename-out-to-in rename-in-to-out
385+
for branch in rename-out-to-out \
386+
rename-out-to-in \
387+
rename-in-to-out \
388+
df-conflict-1 \
389+
fd-conflict
337390
do
338391
test_all_match git checkout rename-base &&
339392
test_all_match git checkout $branch -- . &&
@@ -346,7 +399,12 @@ test_expect_success 'diff with renames and conflicts' '
346399
test_expect_success 'diff with directory/file conflicts' '
347400
init_repos &&
348401
349-
for branch in rename-out-to-out rename-out-to-in rename-in-to-out
402+
for branch in rename-out-to-out \
403+
rename-out-to-in \
404+
rename-in-to-out \
405+
df-conflict-1 \
406+
df-conflict-2 \
407+
fd-conflict
350408
do
351409
git -C full-checkout reset --hard &&
352410
test_sparse_match git reset --hard &&
@@ -514,14 +572,33 @@ test_expect_success 'sparse-index is expanded and converted back' '
514572
test_region index ensure_full_index trace2.txt
515573
'
516574

517-
test_expect_success 'sparse-index is not expanded' '
518-
init_repos &&
519-
575+
ensure_not_expanded () {
520576
rm -f trace2.txt &&
521577
echo >>sparse-index/untracked.txt &&
522578
GIT_TRACE2_EVENT="$(pwd)/trace2.txt" GIT_TRACE2_EVENT_NESTING=10 \
523-
git -C sparse-index status &&
579+
git -C sparse-index "$@" &&
524580
test_region ! index ensure_full_index trace2.txt
581+
}
582+
583+
test_expect_success 'sparse-index is not expanded' '
584+
init_repos &&
585+
586+
ensure_not_expanded status &&
587+
ensure_not_expanded commit --allow-empty -m empty &&
588+
echo >>sparse-index/a &&
589+
ensure_not_expanded commit -a -m a &&
590+
echo >>sparse-index/a &&
591+
ensure_not_expanded commit --include a -m a &&
592+
echo >>sparse-index/deep/deeper1/a &&
593+
ensure_not_expanded commit --include deep/deeper1/a -m deeper &&
594+
ensure_not_expanded checkout rename-out-to-out &&
595+
ensure_not_expanded checkout - &&
596+
ensure_not_expanded switch rename-out-to-out &&
597+
ensure_not_expanded switch - &&
598+
git -C sparse-index reset --hard &&
599+
ensure_not_expanded checkout rename-out-to-out -- deep/deeper1 &&
600+
git -C sparse-index reset --hard &&
601+
ensure_not_expanded restore -s rename-out-to-out -- deep/deeper1
525602
'
526603

527604
# NEEDSWORK: a sparse-checkout behaves differently from a full checkout
@@ -559,4 +636,112 @@ test_expect_success 'add everything with deep new file' '
559636
test_all_match git status --porcelain=v2
560637
'
561638

639+
# NEEDSWORK: 'git checkout' behaves incorrectly in the case of
640+
# directory/file conflicts, even without sparse-checkout. Use this
641+
# test only as a documentation of the incorrect behavior, not a
642+
# measure of how it _should_ behave.
643+
test_expect_success 'checkout behaves oddly with df-conflict-1' '
644+
init_repos &&
645+
646+
test_sparse_match git sparse-checkout disable &&
647+
648+
write_script edit-content <<-\EOF &&
649+
echo content >>folder1/larger-content
650+
git add folder1
651+
EOF
652+
653+
run_on_all ../edit-content &&
654+
test_all_match git status --porcelain=v2 &&
655+
656+
git -C sparse-checkout sparse-checkout init --cone &&
657+
git -C sparse-index sparse-checkout init --cone --sparse-index &&
658+
659+
test_all_match git status --porcelain=v2 &&
660+
661+
# This checkout command should fail, because we have a staged
662+
# change to folder1/larger-content, but the destination changes
663+
# folder1 to a file.
664+
git -C full-checkout checkout df-conflict-1 \
665+
1>full-checkout-out \
666+
2>full-checkout-err &&
667+
git -C sparse-checkout checkout df-conflict-1 \
668+
1>sparse-checkout-out \
669+
2>sparse-checkout-err &&
670+
git -C sparse-index checkout df-conflict-1 \
671+
1>sparse-index-out \
672+
2>sparse-index-err &&
673+
674+
# Instead, the checkout deletes the folder1 file and adds the
675+
# folder1/larger-content file, leaving all other paths that were
676+
# in folder1/ as deleted (without any warning).
677+
cat >expect <<-EOF &&
678+
D folder1
679+
A folder1/larger-content
680+
EOF
681+
test_cmp expect full-checkout-out &&
682+
test_cmp expect sparse-checkout-out &&
683+
684+
# The sparse-index reports no output
685+
test_must_be_empty sparse-index-out &&
686+
687+
# stderr: Switched to branch df-conflict-1
688+
test_cmp full-checkout-err sparse-checkout-err &&
689+
test_cmp full-checkout-err sparse-checkout-err
690+
'
691+
692+
# NEEDSWORK: 'git checkout' behaves incorrectly in the case of
693+
# directory/file conflicts, even without sparse-checkout. Use this
694+
# test only as a documentation of the incorrect behavior, not a
695+
# measure of how it _should_ behave.
696+
test_expect_success 'checkout behaves oddly with df-conflict-2' '
697+
init_repos &&
698+
699+
test_sparse_match git sparse-checkout disable &&
700+
701+
write_script edit-content <<-\EOF &&
702+
echo content >>folder2/larger-content
703+
git add folder2
704+
EOF
705+
706+
run_on_all ../edit-content &&
707+
test_all_match git status --porcelain=v2 &&
708+
709+
git -C sparse-checkout sparse-checkout init --cone &&
710+
git -C sparse-index sparse-checkout init --cone --sparse-index &&
711+
712+
test_all_match git status --porcelain=v2 &&
713+
714+
# This checkout command should fail, because we have a staged
715+
# change to folder1/larger-content, but the destination changes
716+
# folder1 to a file.
717+
git -C full-checkout checkout df-conflict-2 \
718+
1>full-checkout-out \
719+
2>full-checkout-err &&
720+
git -C sparse-checkout checkout df-conflict-2 \
721+
1>sparse-checkout-out \
722+
2>sparse-checkout-err &&
723+
git -C sparse-index checkout df-conflict-2 \
724+
1>sparse-index-out \
725+
2>sparse-index-err &&
726+
727+
# The full checkout deviates from the df-conflict-1 case here!
728+
# It drops the change to folder1/larger-content and leaves the
729+
# folder1 path as-is on disk. The sparse-index behaves the same.
730+
test_must_be_empty full-checkout-out &&
731+
test_must_be_empty sparse-index-out &&
732+
733+
# In the sparse-checkout case, the checkout deletes the folder1
734+
# file and adds the folder1/larger-content file, leaving all other
735+
# paths that were in folder1/ as deleted (without any warning).
736+
cat >expect <<-EOF &&
737+
D folder2
738+
A folder2/larger-content
739+
EOF
740+
test_cmp expect sparse-checkout-out &&
741+
742+
# Switched to branch df-conflict-1
743+
test_cmp full-checkout-err sparse-checkout-err &&
744+
test_cmp full-checkout-err sparse-index-err
745+
'
746+
562747
test_done

0 commit comments

Comments
 (0)