Skip to content

Commit

Permalink
Merge pull request microsoft#426 from vdye/sparse-index/read-tree
Browse files Browse the repository at this point in the history
Sparse index: integrate with `read-tree`
  • Loading branch information
vdye authored and ldennington committed Jan 20, 2022
2 parents 252f88f + 7997adb commit b0b4927
Show file tree
Hide file tree
Showing 5 changed files with 206 additions and 2 deletions.
20 changes: 18 additions & 2 deletions builtin/read-tree.c
Original file line number Diff line number Diff line change
Expand Up @@ -160,15 +160,18 @@ int cmd_read_tree(int argc, const char **argv, const char *cmd_prefix)
argc = parse_options(argc, argv, cmd_prefix, read_tree_options,
read_tree_usage, 0);

hold_locked_index(&lock_file, LOCK_DIE_ON_ERROR);

prefix_set = opts.prefix ? 1 : 0;
if (1 < opts.merge + opts.reset + prefix_set)
die("Which one? -m, --reset, or --prefix?");

if (opts.reset)
opts.reset = UNPACK_RESET_OVERWRITE_UNTRACKED;

prepare_repo_settings(the_repository);
the_repository->settings.command_requires_full_index = 0;

hold_locked_index(&lock_file, LOCK_DIE_ON_ERROR);

/*
* NEEDSWORK
*
Expand Down Expand Up @@ -210,6 +213,9 @@ int cmd_read_tree(int argc, const char **argv, const char *cmd_prefix)
if (opts.merge && !opts.index_only)
setup_work_tree();

if (opts.skip_sparse_checkout)
ensure_full_index(&the_index);

if (opts.merge) {
switch (stage - 1) {
case 0:
Expand All @@ -219,11 +225,21 @@ int cmd_read_tree(int argc, const char **argv, const char *cmd_prefix)
opts.fn = opts.prefix ? bind_merge : oneway_merge;
break;
case 2:
/*
* TODO: update twoway_merge to handle edit/edit conflicts in
* sparse directories.
*/
ensure_full_index(&the_index);
opts.fn = twoway_merge;
opts.initial_checkout = is_cache_unborn();
break;
case 3:
default:
/*
* TODO: update threeway_merge to handle edit/edit conflicts in
* sparse directories.
*/
ensure_full_index(&the_index);
opts.fn = threeway_merge;
break;
}
Expand Down
1 change: 1 addition & 0 deletions t/perf/p2000-sparse-operations.sh
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,7 @@ test_perf_on_all git checkout -f -
test_perf_on_all git reset
test_perf_on_all git reset --hard
test_perf_on_all git reset -- does-not-exist
test_perf_on_all git read-tree -mu HEAD
test_perf_on_all git checkout-index -f --all
test_perf_on_all git update-index --add --remove
test_perf_on_all git diff
Expand Down
128 changes: 128 additions & 0 deletions t/t1092-sparse-checkout-compatibility.sh
Original file line number Diff line number Diff line change
Expand Up @@ -817,6 +817,117 @@ test_expect_success 'update-index --cacheinfo' '
test_sparse_match git status --porcelain=v2
'

test_expect_success 'read-tree --merge with files outside sparse definition' '
init_repos &&
test_all_match git checkout -b test-branch update-folder1 &&
for MERGE_TREES in "base HEAD update-folder2" \
"update-folder1 update-folder2" \
"update-folder2"
do
# Clean up and remove on-disk files
test_all_match git reset --hard HEAD &&
test_sparse_match git sparse-checkout reapply &&
# Although the index matches, without --no-sparse-checkout, outside-of-
# definition files will not exist on disk for sparse checkouts
test_all_match git read-tree -mu $MERGE_TREES &&
test_all_match git status --porcelain=v2 &&
test_path_is_missing sparse-checkout/folder2 &&
test_path_is_missing sparse-index/folder2 &&
test_all_match git read-tree --reset -u HEAD &&
test_all_match git status --porcelain=v2 &&
test_all_match git read-tree -mu --no-sparse-checkout $MERGE_TREES &&
test_all_match git status --porcelain=v2 &&
test_cmp sparse-checkout/folder2/a sparse-index/folder2/a &&
test_cmp sparse-checkout/folder2/a full-checkout/folder2/a || return 1
done
'

test_expect_success 'read-tree --merge with edit/edit conflicts in sparse directories' '
init_repos &&
# Merge of multiple changes to same directory (but not same files) should
# succeed
test_all_match git read-tree -mu base rename-base update-folder1 &&
test_all_match git status --porcelain=v2 &&
test_all_match git reset --hard &&
test_all_match git read-tree -mu rename-base update-folder2 &&
test_all_match git status --porcelain=v2 &&
test_all_match git reset --hard &&
test_all_match test_must_fail git read-tree -mu base update-folder1 rename-out-to-in &&
test_all_match test_must_fail git read-tree -mu rename-out-to-in update-folder1
'

test_expect_success 'read-tree --merge with modified file outside definition' '
init_repos &&
write_script edit-contents <<-\EOF &&
echo text >>$1
EOF
test_all_match git checkout -b test-branch update-folder1 &&
run_on_sparse mkdir -p folder2 &&
run_on_all ../edit-contents folder2/a &&
# With manually-modified file, full-checkout cannot merge, but it is ignored
# in sparse checkouts
test_must_fail git -C full-checkout read-tree -mu update-folder2 &&
test_sparse_match git read-tree -mu update-folder2 &&
test_sparse_match git status --porcelain=v2 &&
# Reset only the sparse checkouts to "undo" the merge. All three checkouts
# now have matching indexes and matching folder2/a on disk.
test_sparse_match git read-tree --reset -u HEAD &&
# When --no-sparse-checkout is specified, sparse checkouts identify the file
# on disk and prevent the merge
test_all_match test_must_fail git read-tree -mu --no-sparse-checkout update-folder2
'

test_expect_success 'read-tree --prefix outside sparse definition' '
init_repos &&
# Cannot read-tree --prefix with a single argument when files exist within
# prefix
test_all_match test_must_fail git read-tree --prefix=folder1/ -u update-folder1 &&
test_all_match git read-tree --prefix=folder2/0 -u rename-base &&
test_path_is_missing sparse-checkout/folder2 &&
test_path_is_missing sparse-index/folder2 &&
test_all_match git read-tree --reset -u HEAD &&
test_all_match git read-tree --prefix=folder2/0 -u --no-sparse-checkout rename-base &&
test_cmp sparse-checkout/folder2/0/a sparse-index/folder2/0/a &&
test_cmp sparse-checkout/folder2/0/a full-checkout/folder2/0/a
'

test_expect_success 'read-tree --merge with directory-file conflicts' '
init_repos &&
test_all_match git checkout -b test-branch rename-base &&
# Although the index matches, without --no-sparse-checkout, outside-of-
# definition files will not exist on disk for sparse checkouts
test_sparse_match git read-tree -mu rename-out-to-out &&
test_sparse_match git status --porcelain=v2 &&
test_path_is_missing sparse-checkout/folder2 &&
test_path_is_missing sparse-index/folder2 &&
test_sparse_match git read-tree --reset -u HEAD &&
test_sparse_match git status --porcelain=v2 &&
test_sparse_match git read-tree -mu --no-sparse-checkout rename-out-to-out &&
test_sparse_match git status --porcelain=v2 &&
test_cmp sparse-checkout/folder2/0/1 sparse-index/folder2/0/1
'

test_expect_success 'merge, cherry-pick, and rebase' '
init_repos &&
Expand Down Expand Up @@ -1395,6 +1506,23 @@ test_expect_success 'sparse index is not expanded: update-index' '
ensure_not_expanded update-index --add --remove --again
'

test_expect_success 'sparse index is not expanded: read-tree' '
init_repos &&
ensure_not_expanded checkout -b test-branch update-folder1 &&
for MERGE_TREES in "update-folder2"
do
ensure_not_expanded read-tree -mu $MERGE_TREES &&
ensure_not_expanded reset --hard HEAD || return 1
done &&
rm -rf sparse-index/deep/deeper2 &&
ensure_not_expanded add . &&
ensure_not_expanded commit -m "test" &&
ensure_not_expanded read-tree --prefix=deep/deeper2 -u deepest
'

# NEEDSWORK: a sparse-checkout behaves differently from a full checkout
# in this scenario, but it shouldn't.
test_expect_success 'reset mixed and checkout orphan' '
Expand Down
52 changes: 52 additions & 0 deletions unpack-trees.c
Original file line number Diff line number Diff line change
Expand Up @@ -1772,6 +1772,58 @@ int unpack_trees(unsigned len, struct tree_desc *t, struct unpack_trees_options
setup_standard_excludes(o->dir);
}

/*
* If the prefix is equal to or contained within a sparse directory, the
* index needs to be expanded to traverse with the specified prefix. Note
* that only the src_index is checked because the prefix is only specified
* in cases where src_index == dst_index.
*/
if (o->prefix && o->src_index->sparse_index) {
int i, ce_len;
struct cache_entry *ce;
int prefix_len = strlen(o->prefix);

if (prefix_len > 0) {
for (i = 0; i < o->src_index->cache_nr; i++) {
ce = o->src_index->cache[i];
ce_len = ce_namelen(ce);

if (!S_ISSPARSEDIR(ce->ce_mode))
continue;

/*
* Normalize comparison length for cache entry vs. prefix -
* either may have a trailing slash, which we do not want to
* compare (can assume both are directories).
*/
if (ce->name[ce_len - 1] == '/')
ce_len--;
if (o->prefix[prefix_len - 1] == '/')
prefix_len--;

/*
* If prefix length is shorter, then it is either a parent to
* this sparse directory, or a completely different path. In
* either case, we don't need to expand the index
*/
if (prefix_len < ce_len)
continue;

/*
* Avoid the case of expanding the index with a prefix
* a/beta for a sparse directory a/b.
*/
if (ce_len < prefix_len && o->prefix[ce_len] != '/')
continue;

if (!strncmp(ce->name, o->prefix, ce_len)) {
ensure_full_index(o->src_index);
break;
}
}
}
}

if (!core_apply_sparse_checkout || !o->update)
o->skip_sparse_checkout = 1;
if (!o->skip_sparse_checkout && !o->pl) {
Expand Down
7 changes: 7 additions & 0 deletions wt-status.c
Original file line number Diff line number Diff line change
Expand Up @@ -651,6 +651,13 @@ static void wt_status_collect_changes_index(struct wt_status *s)
rev.diffopt.detect_rename = s->detect_rename >= 0 ? s->detect_rename : rev.diffopt.detect_rename;
rev.diffopt.rename_limit = s->rename_limit >= 0 ? s->rename_limit : rev.diffopt.rename_limit;
rev.diffopt.rename_score = s->rename_score >= 0 ? s->rename_score : rev.diffopt.rename_score;

/*
* The `recursive` flag must be set to properly perform a diff on sparse
* directory entries, if they exist
*/
rev.diffopt.flags.recursive = 1;

copy_pathspec(&rev.prune_data, &s->pathspec);
run_diff_index(&rev, 1);
object_array_clear(&rev.pending);
Expand Down

0 comments on commit b0b4927

Please sign in to comment.