Skip to content

Commit 3a7598c

Browse files
vdyederrickstolee
authored andcommitted
Merge pull request #426 from vdye/sparse-index/read-tree
Sparse index: integrate with `read-tree`
2 parents 3e0b6b8 + 3ecff62 commit 3a7598c

5 files changed

Lines changed: 206 additions & 2 deletions

File tree

builtin/read-tree.c

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -160,15 +160,18 @@ int cmd_read_tree(int argc, const char **argv, const char *cmd_prefix)
160160
argc = parse_options(argc, argv, cmd_prefix, read_tree_options,
161161
read_tree_usage, 0);
162162

163-
hold_locked_index(&lock_file, LOCK_DIE_ON_ERROR);
164-
165163
prefix_set = opts.prefix ? 1 : 0;
166164
if (1 < opts.merge + opts.reset + prefix_set)
167165
die("Which one? -m, --reset, or --prefix?");
168166

169167
if (opts.reset)
170168
opts.reset = UNPACK_RESET_OVERWRITE_UNTRACKED;
171169

170+
prepare_repo_settings(the_repository);
171+
the_repository->settings.command_requires_full_index = 0;
172+
173+
hold_locked_index(&lock_file, LOCK_DIE_ON_ERROR);
174+
172175
/*
173176
* NEEDSWORK
174177
*
@@ -210,6 +213,9 @@ int cmd_read_tree(int argc, const char **argv, const char *cmd_prefix)
210213
if (opts.merge && !opts.index_only)
211214
setup_work_tree();
212215

216+
if (opts.skip_sparse_checkout)
217+
ensure_full_index(&the_index);
218+
213219
if (opts.merge) {
214220
switch (stage - 1) {
215221
case 0:
@@ -219,11 +225,21 @@ int cmd_read_tree(int argc, const char **argv, const char *cmd_prefix)
219225
opts.fn = opts.prefix ? bind_merge : oneway_merge;
220226
break;
221227
case 2:
228+
/*
229+
* TODO: update twoway_merge to handle edit/edit conflicts in
230+
* sparse directories.
231+
*/
232+
ensure_full_index(&the_index);
222233
opts.fn = twoway_merge;
223234
opts.initial_checkout = is_cache_unborn();
224235
break;
225236
case 3:
226237
default:
238+
/*
239+
* TODO: update threeway_merge to handle edit/edit conflicts in
240+
* sparse directories.
241+
*/
242+
ensure_full_index(&the_index);
227243
opts.fn = threeway_merge;
228244
break;
229245
}

t/perf/p2000-sparse-operations.sh

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,7 @@ test_perf_on_all git checkout -f -
113113
test_perf_on_all git reset
114114
test_perf_on_all git reset --hard
115115
test_perf_on_all git reset -- does-not-exist
116+
test_perf_on_all git read-tree -mu HEAD
116117
test_perf_on_all git checkout-index -f --all
117118
test_perf_on_all git update-index --add --remove
118119
test_perf_on_all git diff

t/t1092-sparse-checkout-compatibility.sh

Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -805,6 +805,117 @@ test_expect_success 'update-index --cacheinfo' '
805805
test_sparse_match git status --porcelain=v2
806806
'
807807

808+
test_expect_success 'read-tree --merge with files outside sparse definition' '
809+
init_repos &&
810+
811+
test_all_match git checkout -b test-branch update-folder1 &&
812+
for MERGE_TREES in "base HEAD update-folder2" \
813+
"update-folder1 update-folder2" \
814+
"update-folder2"
815+
do
816+
# Clean up and remove on-disk files
817+
test_all_match git reset --hard HEAD &&
818+
test_sparse_match git sparse-checkout reapply &&
819+
820+
# Although the index matches, without --no-sparse-checkout, outside-of-
821+
# definition files will not exist on disk for sparse checkouts
822+
test_all_match git read-tree -mu $MERGE_TREES &&
823+
test_all_match git status --porcelain=v2 &&
824+
test_path_is_missing sparse-checkout/folder2 &&
825+
test_path_is_missing sparse-index/folder2 &&
826+
827+
test_all_match git read-tree --reset -u HEAD &&
828+
test_all_match git status --porcelain=v2 &&
829+
830+
test_all_match git read-tree -mu --no-sparse-checkout $MERGE_TREES &&
831+
test_all_match git status --porcelain=v2 &&
832+
test_cmp sparse-checkout/folder2/a sparse-index/folder2/a &&
833+
test_cmp sparse-checkout/folder2/a full-checkout/folder2/a || return 1
834+
done
835+
'
836+
837+
test_expect_success 'read-tree --merge with edit/edit conflicts in sparse directories' '
838+
init_repos &&
839+
840+
# Merge of multiple changes to same directory (but not same files) should
841+
# succeed
842+
test_all_match git read-tree -mu base rename-base update-folder1 &&
843+
test_all_match git status --porcelain=v2 &&
844+
845+
test_all_match git reset --hard &&
846+
847+
test_all_match git read-tree -mu rename-base update-folder2 &&
848+
test_all_match git status --porcelain=v2 &&
849+
850+
test_all_match git reset --hard &&
851+
852+
test_all_match test_must_fail git read-tree -mu base update-folder1 rename-out-to-in &&
853+
test_all_match test_must_fail git read-tree -mu rename-out-to-in update-folder1
854+
'
855+
856+
test_expect_success 'read-tree --merge with modified file outside definition' '
857+
init_repos &&
858+
859+
write_script edit-contents <<-\EOF &&
860+
echo text >>$1
861+
EOF
862+
863+
test_all_match git checkout -b test-branch update-folder1 &&
864+
run_on_sparse mkdir -p folder2 &&
865+
run_on_all ../edit-contents folder2/a &&
866+
867+
# With manually-modified file, full-checkout cannot merge, but it is ignored
868+
# in sparse checkouts
869+
test_must_fail git -C full-checkout read-tree -mu update-folder2 &&
870+
test_sparse_match git read-tree -mu update-folder2 &&
871+
test_sparse_match git status --porcelain=v2 &&
872+
873+
# Reset only the sparse checkouts to "undo" the merge. All three checkouts
874+
# now have matching indexes and matching folder2/a on disk.
875+
test_sparse_match git read-tree --reset -u HEAD &&
876+
877+
# When --no-sparse-checkout is specified, sparse checkouts identify the file
878+
# on disk and prevent the merge
879+
test_all_match test_must_fail git read-tree -mu --no-sparse-checkout update-folder2
880+
'
881+
882+
test_expect_success 'read-tree --prefix outside sparse definition' '
883+
init_repos &&
884+
885+
# Cannot read-tree --prefix with a single argument when files exist within
886+
# prefix
887+
test_all_match test_must_fail git read-tree --prefix=folder1/ -u update-folder1 &&
888+
889+
test_all_match git read-tree --prefix=folder2/0 -u rename-base &&
890+
test_path_is_missing sparse-checkout/folder2 &&
891+
test_path_is_missing sparse-index/folder2 &&
892+
893+
test_all_match git read-tree --reset -u HEAD &&
894+
test_all_match git read-tree --prefix=folder2/0 -u --no-sparse-checkout rename-base &&
895+
test_cmp sparse-checkout/folder2/0/a sparse-index/folder2/0/a &&
896+
test_cmp sparse-checkout/folder2/0/a full-checkout/folder2/0/a
897+
'
898+
899+
test_expect_success 'read-tree --merge with directory-file conflicts' '
900+
init_repos &&
901+
902+
test_all_match git checkout -b test-branch rename-base &&
903+
904+
# Although the index matches, without --no-sparse-checkout, outside-of-
905+
# definition files will not exist on disk for sparse checkouts
906+
test_sparse_match git read-tree -mu rename-out-to-out &&
907+
test_sparse_match git status --porcelain=v2 &&
908+
test_path_is_missing sparse-checkout/folder2 &&
909+
test_path_is_missing sparse-index/folder2 &&
910+
911+
test_sparse_match git read-tree --reset -u HEAD &&
912+
test_sparse_match git status --porcelain=v2 &&
913+
914+
test_sparse_match git read-tree -mu --no-sparse-checkout rename-out-to-out &&
915+
test_sparse_match git status --porcelain=v2 &&
916+
test_cmp sparse-checkout/folder2/0/1 sparse-index/folder2/0/1
917+
'
918+
808919
test_expect_success 'merge, cherry-pick, and rebase' '
809920
init_repos &&
810921
@@ -1221,6 +1332,23 @@ test_expect_success 'sparse index is not expanded: update-index' '
12211332
ensure_not_expanded update-index --add --remove --again
12221333
'
12231334

1335+
test_expect_success 'sparse index is not expanded: read-tree' '
1336+
init_repos &&
1337+
1338+
ensure_not_expanded checkout -b test-branch update-folder1 &&
1339+
for MERGE_TREES in "update-folder2"
1340+
do
1341+
ensure_not_expanded read-tree -mu $MERGE_TREES &&
1342+
ensure_not_expanded reset --hard HEAD || return 1
1343+
done &&
1344+
1345+
rm -rf sparse-index/deep/deeper2 &&
1346+
ensure_not_expanded add . &&
1347+
ensure_not_expanded commit -m "test" &&
1348+
1349+
ensure_not_expanded read-tree --prefix=deep/deeper2 -u deepest
1350+
'
1351+
12241352
# NEEDSWORK: a sparse-checkout behaves differently from a full checkout
12251353
# in this scenario, but it shouldn't.
12261354
test_expect_success 'reset mixed and checkout orphan' '

unpack-trees.c

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1756,6 +1756,58 @@ int unpack_trees(unsigned len, struct tree_desc *t, struct unpack_trees_options
17561756
setup_standard_excludes(o->dir);
17571757
}
17581758

1759+
/*
1760+
* If the prefix is equal to or contained within a sparse directory, the
1761+
* index needs to be expanded to traverse with the specified prefix. Note
1762+
* that only the src_index is checked because the prefix is only specified
1763+
* in cases where src_index == dst_index.
1764+
*/
1765+
if (o->prefix && o->src_index->sparse_index) {
1766+
int i, ce_len;
1767+
struct cache_entry *ce;
1768+
int prefix_len = strlen(o->prefix);
1769+
1770+
if (prefix_len > 0) {
1771+
for (i = 0; i < o->src_index->cache_nr; i++) {
1772+
ce = o->src_index->cache[i];
1773+
ce_len = ce_namelen(ce);
1774+
1775+
if (!S_ISSPARSEDIR(ce->ce_mode))
1776+
continue;
1777+
1778+
/*
1779+
* Normalize comparison length for cache entry vs. prefix -
1780+
* either may have a trailing slash, which we do not want to
1781+
* compare (can assume both are directories).
1782+
*/
1783+
if (ce->name[ce_len - 1] == '/')
1784+
ce_len--;
1785+
if (o->prefix[prefix_len - 1] == '/')
1786+
prefix_len--;
1787+
1788+
/*
1789+
* If prefix length is shorter, then it is either a parent to
1790+
* this sparse directory, or a completely different path. In
1791+
* either case, we don't need to expand the index
1792+
*/
1793+
if (prefix_len < ce_len)
1794+
continue;
1795+
1796+
/*
1797+
* Avoid the case of expanding the index with a prefix
1798+
* a/beta for a sparse directory a/b.
1799+
*/
1800+
if (ce_len < prefix_len && o->prefix[ce_len] != '/')
1801+
continue;
1802+
1803+
if (!strncmp(ce->name, o->prefix, ce_len)) {
1804+
ensure_full_index(o->src_index);
1805+
break;
1806+
}
1807+
}
1808+
}
1809+
}
1810+
17591811
if (!core_apply_sparse_checkout || !o->update)
17601812
o->skip_sparse_checkout = 1;
17611813
if (!o->skip_sparse_checkout && !o->pl) {

wt-status.c

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -651,6 +651,13 @@ static void wt_status_collect_changes_index(struct wt_status *s)
651651
rev.diffopt.detect_rename = s->detect_rename >= 0 ? s->detect_rename : rev.diffopt.detect_rename;
652652
rev.diffopt.rename_limit = s->rename_limit >= 0 ? s->rename_limit : rev.diffopt.rename_limit;
653653
rev.diffopt.rename_score = s->rename_score >= 0 ? s->rename_score : rev.diffopt.rename_score;
654+
655+
/*
656+
* The `recursive` flag must be set to properly perform a diff on sparse
657+
* directory entries, if they exist
658+
*/
659+
rev.diffopt.flags.recursive = 1;
660+
654661
copy_pathspec(&rev.prune_data, &s->pathspec);
655662
run_diff_index(&rev, 1);
656663
object_array_clear(&rev.pending);

0 commit comments

Comments
 (0)