mirror of https://github.com/git/git.git
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
This commit is contained in:
commit
506d2a354a
|
@ -378,9 +378,6 @@ static int checkout_worktree(const struct checkout_opts *opts,
|
|||
if (pc_workers > 1)
|
||||
init_parallel_checkout();
|
||||
|
||||
/* TODO: audit for interaction with sparse-index. */
|
||||
ensure_full_index(&the_index);
|
||||
|
||||
for (pos = 0; pos < active_nr; pos++) {
|
||||
struct cache_entry *ce = active_cache[pos];
|
||||
if (ce->ce_flags & CE_MATCHED) {
|
||||
|
@ -530,8 +527,6 @@ static int checkout_paths(const struct checkout_opts *opts,
|
|||
* Make sure all pathspecs participated in locating the paths
|
||||
* to be checked out.
|
||||
*/
|
||||
/* TODO: audit for interaction with sparse-index. */
|
||||
ensure_full_index(&the_index);
|
||||
for (pos = 0; pos < active_nr; pos++)
|
||||
if (opts->overlay_mode)
|
||||
mark_ce_for_checkout_overlay(active_cache[pos],
|
||||
|
@ -1593,6 +1588,9 @@ static int checkout_main(int argc, const char **argv, const char *prefix,
|
|||
|
||||
git_config(git_checkout_config, opts);
|
||||
|
||||
prepare_repo_settings(the_repository);
|
||||
the_repository->settings.command_requires_full_index = 0;
|
||||
|
||||
opts->track = BRANCH_TRACK_UNSPECIFIED;
|
||||
|
||||
if (!opts->accept_pathspec && !opts->accept_ref)
|
||||
|
|
|
@ -1689,6 +1689,9 @@ int cmd_commit(int argc, const char **argv, const char *prefix)
|
|||
if (argc == 2 && !strcmp(argv[1], "-h"))
|
||||
usage_with_options(builtin_commit_usage, builtin_commit_options);
|
||||
|
||||
prepare_repo_settings(the_repository);
|
||||
the_repository->settings.command_requires_full_index = 0;
|
||||
|
||||
status_init_config(&s, git_commit_config);
|
||||
s.commit_template = 1;
|
||||
status_format = STATUS_FORMAT_NONE; /* Ignore status.short */
|
||||
|
|
|
@ -465,8 +465,6 @@ int cache_tree_update(struct index_state *istate, int flags)
|
|||
if (i)
|
||||
return i;
|
||||
|
||||
ensure_full_index(istate);
|
||||
|
||||
if (!istate->cache_tree)
|
||||
istate->cache_tree = cache_tree();
|
||||
|
||||
|
|
|
@ -170,6 +170,8 @@ int convert_to_sparse(struct index_state *istate)
|
|||
if (index_has_unmerged_entries(istate))
|
||||
return 0;
|
||||
|
||||
/* Clear and recompute the cache-tree */
|
||||
cache_tree_free(&istate->cache_tree);
|
||||
if (cache_tree_update(istate, 0)) {
|
||||
warning(_("unable to update cache-tree, staying full"));
|
||||
return -1;
|
||||
|
|
|
@ -6,7 +6,7 @@ test_description="test performance of Git operations using the index"
|
|||
|
||||
test_perf_default_repo
|
||||
|
||||
SPARSE_CONE=f2/f4/f1
|
||||
SPARSE_CONE=f2/f4
|
||||
|
||||
test_expect_success 'setup repo and indexes' '
|
||||
git reset --hard HEAD &&
|
||||
|
@ -27,7 +27,7 @@ test_expect_success 'setup repo and indexes' '
|
|||
OLD_COMMIT=$(git rev-parse HEAD) &&
|
||||
OLD_TREE=$(git rev-parse HEAD^{tree}) &&
|
||||
|
||||
for i in $(test_seq 1 4)
|
||||
for i in $(test_seq 1 3)
|
||||
do
|
||||
cat >in <<-EOF &&
|
||||
100755 blob $BLOB a
|
||||
|
@ -43,45 +43,57 @@ test_expect_success 'setup repo and indexes' '
|
|||
done &&
|
||||
|
||||
git sparse-checkout init --cone &&
|
||||
git branch -f wide $OLD_COMMIT &&
|
||||
git -c core.sparseCheckoutCone=true clone --branch=wide --sparse . full-index-v3 &&
|
||||
git sparse-checkout set $SPARSE_CONE &&
|
||||
git checkout -b wide $OLD_COMMIT &&
|
||||
|
||||
for l2 in f1 f2 f3 f4
|
||||
do
|
||||
echo more bogus >>$SPARSE_CONE/$l2/a &&
|
||||
git commit -a -m "edit $SPARSE_CONE/$l2/a" || return 1
|
||||
done &&
|
||||
|
||||
git -c core.sparseCheckoutCone=true clone --branch=wide --sparse . full-v3 &&
|
||||
(
|
||||
cd full-index-v3 &&
|
||||
cd full-v3 &&
|
||||
git sparse-checkout init --cone &&
|
||||
git sparse-checkout set $SPARSE_CONE &&
|
||||
git config index.version 3 &&
|
||||
git update-index --index-version=3
|
||||
git update-index --index-version=3 &&
|
||||
git checkout HEAD~4
|
||||
) &&
|
||||
git -c core.sparseCheckoutCone=true clone --branch=wide --sparse . full-index-v4 &&
|
||||
git -c core.sparseCheckoutCone=true clone --branch=wide --sparse . full-v4 &&
|
||||
(
|
||||
cd full-index-v4 &&
|
||||
cd full-v4 &&
|
||||
git sparse-checkout init --cone &&
|
||||
git sparse-checkout set $SPARSE_CONE &&
|
||||
git config index.version 4 &&
|
||||
git update-index --index-version=4
|
||||
git update-index --index-version=4 &&
|
||||
git checkout HEAD~4
|
||||
) &&
|
||||
git -c core.sparseCheckoutCone=true clone --branch=wide --sparse . sparse-index-v3 &&
|
||||
git -c core.sparseCheckoutCone=true clone --branch=wide --sparse . sparse-v3 &&
|
||||
(
|
||||
cd sparse-index-v3 &&
|
||||
cd sparse-v3 &&
|
||||
git sparse-checkout init --cone --sparse-index &&
|
||||
git sparse-checkout set $SPARSE_CONE &&
|
||||
git config index.version 3 &&
|
||||
git update-index --index-version=3
|
||||
git update-index --index-version=3 &&
|
||||
git checkout HEAD~4
|
||||
) &&
|
||||
git -c core.sparseCheckoutCone=true clone --branch=wide --sparse . sparse-index-v4 &&
|
||||
git -c core.sparseCheckoutCone=true clone --branch=wide --sparse . sparse-v4 &&
|
||||
(
|
||||
cd sparse-index-v4 &&
|
||||
cd sparse-v4 &&
|
||||
git sparse-checkout init --cone --sparse-index &&
|
||||
git sparse-checkout set $SPARSE_CONE &&
|
||||
git config index.version 4 &&
|
||||
git update-index --index-version=4
|
||||
git update-index --index-version=4 &&
|
||||
git checkout HEAD~4
|
||||
)
|
||||
'
|
||||
|
||||
test_perf_on_all () {
|
||||
command="$@"
|
||||
for repo in full-index-v3 full-index-v4 \
|
||||
sparse-index-v3 sparse-index-v4
|
||||
for repo in full-v3 full-v4 \
|
||||
sparse-v3 sparse-v4
|
||||
do
|
||||
test_perf "$command ($repo)" "
|
||||
(
|
||||
|
@ -97,5 +109,6 @@ test_perf_on_all git status
|
|||
test_perf_on_all git add -A
|
||||
test_perf_on_all git add .
|
||||
test_perf_on_all git commit -a -m A
|
||||
test_perf_on_all git checkout -f -
|
||||
|
||||
test_done
|
||||
|
|
|
@ -95,6 +95,25 @@ test_expect_success 'setup' '
|
|||
git add . &&
|
||||
git commit -m "rename deep/deeper1/... to folder1/..." &&
|
||||
|
||||
git checkout -b df-conflict-1 base &&
|
||||
rm -rf folder1 &&
|
||||
echo content >folder1 &&
|
||||
git add . &&
|
||||
git commit -m "dir to file" &&
|
||||
|
||||
git checkout -b df-conflict-2 base &&
|
||||
rm -rf folder2 &&
|
||||
echo content >folder2 &&
|
||||
git add . &&
|
||||
git commit -m "dir to file" &&
|
||||
|
||||
git checkout -b fd-conflict base &&
|
||||
rm a &&
|
||||
mkdir a &&
|
||||
echo content >a/a &&
|
||||
git add . &&
|
||||
git commit -m "file to dir" &&
|
||||
|
||||
git checkout -b deepest base &&
|
||||
echo "updated deepest" >deep/deeper1/deepest/a &&
|
||||
git commit -a -m "update deepest" &&
|
||||
|
@ -262,6 +281,34 @@ test_expect_success 'add, commit, checkout' '
|
|||
test_all_match git checkout -
|
||||
'
|
||||
|
||||
test_expect_success 'commit including unstaged changes' '
|
||||
init_repos &&
|
||||
|
||||
write_script edit-file <<-\EOF &&
|
||||
echo $1 >$2
|
||||
EOF
|
||||
|
||||
run_on_all ../edit-file 1 a &&
|
||||
run_on_all ../edit-file 1 deep/a &&
|
||||
|
||||
test_all_match git commit -m "-a" -a &&
|
||||
test_all_match git status --porcelain=v2 &&
|
||||
|
||||
run_on_all ../edit-file 2 a &&
|
||||
run_on_all ../edit-file 2 deep/a &&
|
||||
|
||||
test_all_match git commit -m "--include" --include deep/a &&
|
||||
test_all_match git status --porcelain=v2 &&
|
||||
test_all_match git commit -m "--include" --include a &&
|
||||
test_all_match git status --porcelain=v2 &&
|
||||
|
||||
run_on_all ../edit-file 3 a &&
|
||||
run_on_all ../edit-file 3 deep/a &&
|
||||
|
||||
test_all_match git commit -m "--amend" -a --amend &&
|
||||
test_all_match git status --porcelain=v2
|
||||
'
|
||||
|
||||
test_expect_success 'status/add: outside sparse cone' '
|
||||
init_repos &&
|
||||
|
||||
|
@ -330,10 +377,16 @@ test_expect_success 'diff --staged' '
|
|||
test_all_match git diff --staged
|
||||
'
|
||||
|
||||
# NEEDSWORK: sparse-checkout behaves differently from full-checkout when
|
||||
# running this test with 'df-conflict-2' after 'df-conflict-1'.
|
||||
test_expect_success 'diff with renames and conflicts' '
|
||||
init_repos &&
|
||||
|
||||
for branch in rename-out-to-out rename-out-to-in rename-in-to-out
|
||||
for branch in rename-out-to-out \
|
||||
rename-out-to-in \
|
||||
rename-in-to-out \
|
||||
df-conflict-1 \
|
||||
fd-conflict
|
||||
do
|
||||
test_all_match git checkout rename-base &&
|
||||
test_all_match git checkout $branch -- . &&
|
||||
|
@ -346,7 +399,12 @@ test_expect_success 'diff with renames and conflicts' '
|
|||
test_expect_success 'diff with directory/file conflicts' '
|
||||
init_repos &&
|
||||
|
||||
for branch in rename-out-to-out rename-out-to-in rename-in-to-out
|
||||
for branch in rename-out-to-out \
|
||||
rename-out-to-in \
|
||||
rename-in-to-out \
|
||||
df-conflict-1 \
|
||||
df-conflict-2 \
|
||||
fd-conflict
|
||||
do
|
||||
git -C full-checkout reset --hard &&
|
||||
test_sparse_match git reset --hard &&
|
||||
|
@ -514,14 +572,33 @@ test_expect_success 'sparse-index is expanded and converted back' '
|
|||
test_region index ensure_full_index trace2.txt
|
||||
'
|
||||
|
||||
test_expect_success 'sparse-index is not expanded' '
|
||||
init_repos &&
|
||||
|
||||
ensure_not_expanded () {
|
||||
rm -f trace2.txt &&
|
||||
echo >>sparse-index/untracked.txt &&
|
||||
GIT_TRACE2_EVENT="$(pwd)/trace2.txt" GIT_TRACE2_EVENT_NESTING=10 \
|
||||
git -C sparse-index status &&
|
||||
git -C sparse-index "$@" &&
|
||||
test_region ! index ensure_full_index trace2.txt
|
||||
}
|
||||
|
||||
test_expect_success 'sparse-index is not expanded' '
|
||||
init_repos &&
|
||||
|
||||
ensure_not_expanded status &&
|
||||
ensure_not_expanded commit --allow-empty -m empty &&
|
||||
echo >>sparse-index/a &&
|
||||
ensure_not_expanded commit -a -m a &&
|
||||
echo >>sparse-index/a &&
|
||||
ensure_not_expanded commit --include a -m a &&
|
||||
echo >>sparse-index/deep/deeper1/a &&
|
||||
ensure_not_expanded commit --include deep/deeper1/a -m deeper &&
|
||||
ensure_not_expanded checkout rename-out-to-out &&
|
||||
ensure_not_expanded checkout - &&
|
||||
ensure_not_expanded switch rename-out-to-out &&
|
||||
ensure_not_expanded switch - &&
|
||||
git -C sparse-index reset --hard &&
|
||||
ensure_not_expanded checkout rename-out-to-out -- deep/deeper1 &&
|
||||
git -C sparse-index reset --hard &&
|
||||
ensure_not_expanded restore -s rename-out-to-out -- deep/deeper1
|
||||
'
|
||||
|
||||
# NEEDSWORK: a sparse-checkout behaves differently from a full checkout
|
||||
|
@ -559,4 +636,112 @@ test_expect_success 'add everything with deep new file' '
|
|||
test_all_match git status --porcelain=v2
|
||||
'
|
||||
|
||||
# NEEDSWORK: 'git checkout' behaves incorrectly in the case of
|
||||
# directory/file conflicts, even without sparse-checkout. Use this
|
||||
# test only as a documentation of the incorrect behavior, not a
|
||||
# measure of how it _should_ behave.
|
||||
test_expect_success 'checkout behaves oddly with df-conflict-1' '
|
||||
init_repos &&
|
||||
|
||||
test_sparse_match git sparse-checkout disable &&
|
||||
|
||||
write_script edit-content <<-\EOF &&
|
||||
echo content >>folder1/larger-content
|
||||
git add folder1
|
||||
EOF
|
||||
|
||||
run_on_all ../edit-content &&
|
||||
test_all_match git status --porcelain=v2 &&
|
||||
|
||||
git -C sparse-checkout sparse-checkout init --cone &&
|
||||
git -C sparse-index sparse-checkout init --cone --sparse-index &&
|
||||
|
||||
test_all_match git status --porcelain=v2 &&
|
||||
|
||||
# This checkout command should fail, because we have a staged
|
||||
# change to folder1/larger-content, but the destination changes
|
||||
# folder1 to a file.
|
||||
git -C full-checkout checkout df-conflict-1 \
|
||||
1>full-checkout-out \
|
||||
2>full-checkout-err &&
|
||||
git -C sparse-checkout checkout df-conflict-1 \
|
||||
1>sparse-checkout-out \
|
||||
2>sparse-checkout-err &&
|
||||
git -C sparse-index checkout df-conflict-1 \
|
||||
1>sparse-index-out \
|
||||
2>sparse-index-err &&
|
||||
|
||||
# Instead, the checkout deletes the folder1 file and adds the
|
||||
# folder1/larger-content file, leaving all other paths that were
|
||||
# in folder1/ as deleted (without any warning).
|
||||
cat >expect <<-EOF &&
|
||||
D folder1
|
||||
A folder1/larger-content
|
||||
EOF
|
||||
test_cmp expect full-checkout-out &&
|
||||
test_cmp expect sparse-checkout-out &&
|
||||
|
||||
# The sparse-index reports no output
|
||||
test_must_be_empty sparse-index-out &&
|
||||
|
||||
# stderr: Switched to branch df-conflict-1
|
||||
test_cmp full-checkout-err sparse-checkout-err &&
|
||||
test_cmp full-checkout-err sparse-checkout-err
|
||||
'
|
||||
|
||||
# NEEDSWORK: 'git checkout' behaves incorrectly in the case of
|
||||
# directory/file conflicts, even without sparse-checkout. Use this
|
||||
# test only as a documentation of the incorrect behavior, not a
|
||||
# measure of how it _should_ behave.
|
||||
test_expect_success 'checkout behaves oddly with df-conflict-2' '
|
||||
init_repos &&
|
||||
|
||||
test_sparse_match git sparse-checkout disable &&
|
||||
|
||||
write_script edit-content <<-\EOF &&
|
||||
echo content >>folder2/larger-content
|
||||
git add folder2
|
||||
EOF
|
||||
|
||||
run_on_all ../edit-content &&
|
||||
test_all_match git status --porcelain=v2 &&
|
||||
|
||||
git -C sparse-checkout sparse-checkout init --cone &&
|
||||
git -C sparse-index sparse-checkout init --cone --sparse-index &&
|
||||
|
||||
test_all_match git status --porcelain=v2 &&
|
||||
|
||||
# This checkout command should fail, because we have a staged
|
||||
# change to folder1/larger-content, but the destination changes
|
||||
# folder1 to a file.
|
||||
git -C full-checkout checkout df-conflict-2 \
|
||||
1>full-checkout-out \
|
||||
2>full-checkout-err &&
|
||||
git -C sparse-checkout checkout df-conflict-2 \
|
||||
1>sparse-checkout-out \
|
||||
2>sparse-checkout-err &&
|
||||
git -C sparse-index checkout df-conflict-2 \
|
||||
1>sparse-index-out \
|
||||
2>sparse-index-err &&
|
||||
|
||||
# The full checkout deviates from the df-conflict-1 case here!
|
||||
# It drops the change to folder1/larger-content and leaves the
|
||||
# folder1 path as-is on disk. The sparse-index behaves the same.
|
||||
test_must_be_empty full-checkout-out &&
|
||||
test_must_be_empty sparse-index-out &&
|
||||
|
||||
# In the sparse-checkout case, the checkout deletes the folder1
|
||||
# file and adds the folder1/larger-content file, leaving all other
|
||||
# paths that were in folder1/ as deleted (without any warning).
|
||||
cat >expect <<-EOF &&
|
||||
D folder2
|
||||
A folder2/larger-content
|
||||
EOF
|
||||
test_cmp expect sparse-checkout-out &&
|
||||
|
||||
# Switched to branch df-conflict-1
|
||||
test_cmp full-checkout-err sparse-checkout-err &&
|
||||
test_cmp full-checkout-err sparse-index-err
|
||||
'
|
||||
|
||||
test_done
|
||||
|
|
|
@ -2608,6 +2608,17 @@ int twoway_merge(const struct cache_entry * const *src,
|
|||
same(current, oldtree) && !same(current, newtree)) {
|
||||
/* 20 or 21 */
|
||||
return merged_entry(newtree, current, o);
|
||||
} else if (current && !oldtree && newtree &&
|
||||
S_ISSPARSEDIR(current->ce_mode) != S_ISSPARSEDIR(newtree->ce_mode) &&
|
||||
ce_stage(current) == 0) {
|
||||
/*
|
||||
* This case is a directory/file conflict across the sparse-index
|
||||
* boundary. When we are changing from one path to another via
|
||||
* 'git checkout', then we want to replace one entry with another
|
||||
* via merged_entry(). If there are staged changes, then we should
|
||||
* reject the merge instead.
|
||||
*/
|
||||
return merged_entry(newtree, current, o);
|
||||
} else
|
||||
return reject_merge(current, o);
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue