Skip to content

Commit 8e4df1b

Browse files
vdyeldennington
authored andcommitted
Merge pull request microsoft#423 from vdye/sparse-index/update-index
Sparse index: integrate with `git update-index`
2 parents 8d32542 + 27ad356 commit 8e4df1b

File tree

4 files changed

+195
-6
lines changed

4 files changed

+195
-6
lines changed

builtin/update-index.c

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -410,6 +410,9 @@ static int add_cacheinfo(unsigned int mode, const struct object_id *oid,
410410
if (!verify_path(path, mode))
411411
return error("Invalid path '%s'", path);
412412

413+
if (S_ISSPARSEDIR(mode))
414+
return error("%s: cannot add directory as cache entry", path);
415+
413416
len = strlen(path);
414417
ce = make_empty_cache_entry(&the_index, len);
415418

@@ -744,17 +747,23 @@ static int do_reupdate(int ac, const char **av,
744747
* commit. Update everything in the index.
745748
*/
746749
has_head = 0;
750+
747751
redo:
748-
/* TODO: audit for interaction with sparse-index. */
749-
ensure_full_index(&the_index);
750752
for (pos = 0; pos < active_nr; pos++) {
751753
const struct cache_entry *ce = active_cache[pos];
752754
struct cache_entry *old = NULL;
753755
int save_nr;
754756
char *path;
755757

756-
if (ce_stage(ce) || !ce_path_match(&the_index, ce, &pathspec, NULL))
758+
/*
759+
* We can safely skip re-updating sparse directories because if there
760+
* were any changes to re-update inside of the sparse directory, it
761+
* would not be sparse.
762+
*/
763+
if (S_ISSPARSEDIR(ce->ce_mode) || ce_stage(ce) ||
764+
!ce_path_match(&the_index, ce, &pathspec, NULL))
757765
continue;
766+
758767
if (has_head)
759768
old = read_one_ent(NULL, &head_oid,
760769
ce->name, ce_namelen(ce), 0);
@@ -1079,6 +1088,9 @@ int cmd_update_index(int argc, const char **argv, const char *prefix)
10791088

10801089
git_config(git_default_config, NULL);
10811090

1091+
prepare_repo_settings(r);
1092+
the_repository->settings.command_requires_full_index = 0;
1093+
10821094
/* we will diagnose later if it turns out that we need to update it */
10831095
newfd = hold_locked_index(&lock_file, 0);
10841096
if (newfd < 0)

read-cache.c

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1352,9 +1352,6 @@ static int add_index_entry_with_check(struct index_state *istate, struct cache_e
13521352
int skip_df_check = option & ADD_CACHE_SKIP_DFCHECK;
13531353
int new_only = option & ADD_CACHE_NEW_ONLY;
13541354

1355-
if (!(option & ADD_CACHE_KEEP_CACHE_TREE))
1356-
cache_tree_invalidate_path(istate, ce->name);
1357-
13581355
/*
13591356
* If this entry's path sorts after the last entry in the index,
13601357
* we can avoid searching for it.
@@ -1365,6 +1362,13 @@ static int add_index_entry_with_check(struct index_state *istate, struct cache_e
13651362
else
13661363
pos = index_name_stage_pos(istate, ce->name, ce_namelen(ce), ce_stage(ce), EXPAND_SPARSE);
13671364

1365+
/*
1366+
* Cache tree path should be invalidated only after index_name_stage_pos,
1367+
* in case it expands a sparse index.
1368+
*/
1369+
if (!(option & ADD_CACHE_KEEP_CACHE_TREE))
1370+
cache_tree_invalidate_path(istate, ce->name);
1371+
13681372
/* existing match? Just replace it. */
13691373
if (pos >= 0) {
13701374
if (!new_only)

t/t1092-sparse-checkout-compatibility.sh

Lines changed: 165 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -774,6 +774,156 @@ test_expect_success 'reset with wildcard pathspec' '
774774
test_all_match git status --porcelain=v2
775775
'
776776

777+
# NEEDSWORK: although update-index executes without error on files outside
778+
# the sparse checkout definition, it does not actually add the file to the
779+
# index. This is also true when "--no-ignore-skip-worktree-entries" is
780+
# specified.
781+
test_expect_success 'update-index add outside sparse definition' '
782+
init_repos &&
783+
784+
write_script edit-contents <<-\EOF &&
785+
echo text >>$1
786+
EOF
787+
788+
run_on_sparse mkdir -p folder1 &&
789+
run_on_sparse cp ../initial-repo/folder1/a folder1/a &&
790+
791+
# Edit the file only in sparse checkouts so that, when checking the status
792+
# of the index, the unmodified full-checkout is compared to the "modified"
793+
# sparse checkouts.
794+
run_on_sparse ../edit-contents folder1/a &&
795+
796+
test_sparse_match git update-index --add folder1/a &&
797+
test_all_match git status --porcelain=v2 &&
798+
test_sparse_match git update-index --add --no-ignore-skip-worktree-entries folder1/a &&
799+
test_all_match git status --porcelain=v2
800+
'
801+
802+
test_expect_success 'update-index remove outside sparse definition' '
803+
init_repos &&
804+
805+
# When --remove is specified, files outside the sparse checkout definition
806+
# are considered "removed".
807+
rm -f full-checkout/folder1/a &&
808+
test_all_match git update-index --remove folder1/a &&
809+
test_all_match git status --porcelain=v2 &&
810+
811+
git reset --hard &&
812+
813+
# When --ignore-skip-worktree-entries is explicitly specified, a file
814+
# outside the sparse definition is not added to the index as "removed"
815+
# (thus matching the unmodified full-checkout).
816+
test_sparse_match git update-index --remove --ignore-skip-worktree-entries folder1/a &&
817+
test_all_match git status --porcelain=v2 &&
818+
819+
git reset --hard &&
820+
821+
# --force-remove supercedes --ignore-skip-worktree-entries and always
822+
# removes the file from the index.
823+
test_all_match git update-index --force-remove --ignore-skip-worktree-entries folder1/a &&
824+
test_all_match git status --porcelain=v2
825+
'
826+
827+
test_expect_success 'update-index folder add/remove' '
828+
init_repos &&
829+
830+
test_all_match test_must_fail git update-index --add --remove deep &&
831+
test_all_match test_must_fail git update-index --add --remove deep/ &&
832+
833+
# NEEDSWORK: attempting to update-index on an existing folder outside the
834+
# sparse checkout definition does not throw an error (as it does for folders
835+
# inside the definition, or in the full checkout). However, it is a no-op.
836+
test_sparse_match git update-index --add --remove folder1 &&
837+
test_sparse_match git update-index --add --remove folder1/ &&
838+
test_sparse_match git update-index --force-remove folder1/ &&
839+
test_all_match git status --porcelain=v2 &&
840+
841+
# New folders, even in sparse checkouts, throw an error on update-index
842+
run_on_all mkdir folder3 &&
843+
run_on_all cp a folder3/a &&
844+
run_on_all test_must_fail git update-index --add --remove folder3
845+
'
846+
847+
test_expect_success 'update-index with updated flags' '
848+
init_repos &&
849+
850+
# NEEDSWORK: updating flags runs inconsistently on directories, performing no
851+
# operation with warning text specifying the path being ignored if a trailing
852+
# slash is in the path, but throwing an error if there is no trailing slash.
853+
test_all_match test_must_fail git update-index --no-skip-worktree folder1 &&
854+
test_all_match git update-index --no-skip-worktree folder1/ &&
855+
test_all_match git status --porcelain=v2 &&
856+
857+
# Removing the skip-worktree bit from a file outside the sparse checkout
858+
# will cause the file to appear as unstaged and deleted.
859+
test_sparse_match git update-index --no-skip-worktree folder1/a &&
860+
rm -f full-checkout/folder1/a &&
861+
test_all_match git status --porcelain=v2
862+
'
863+
864+
test_expect_success 'update-index --again file outside sparse definition' '
865+
init_repos &&
866+
867+
write_script edit-contents <<-\EOF &&
868+
echo text >>$1
869+
EOF
870+
871+
# When a file is manually added and modified outside the checkout
872+
# definition, it will not be changed with `--again` because its changes are
873+
# not tracked in the index.
874+
run_on_sparse mkdir -p folder1 &&
875+
run_on_sparse ../edit-contents folder1/a &&
876+
test_sparse_match git update-index --again &&
877+
test_sparse_match git status --porcelain=v2 &&
878+
879+
# Update the sparse checkouts so that folder1/a is no longer skipped and
880+
# exists on-disk
881+
run_on_sparse cp ../initial-repo/folder1/a folder1/a &&
882+
test_sparse_match git update-index --no-skip-worktree folder1/a &&
883+
test_all_match git status --porcelain=v2 &&
884+
885+
# Stage change for commit
886+
run_on_all ../edit-contents folder1/a &&
887+
test_all_match git update-index folder1/a &&
888+
test_all_match git status --porcelain=v2 &&
889+
890+
# Modify the file
891+
run_on_all ../edit-contents folder1/a &&
892+
test_all_match git status --porcelain=v2 &&
893+
894+
# Run update-index --again, which re-stages the local changes
895+
test_all_match git update-index --again &&
896+
test_all_match git ls-files -s folder1/a &&
897+
test_all_match git status --porcelain=v2 &&
898+
899+
# Running update-index --again with staged changes after manually deleting
900+
# the file on disk will cause it to fail if --remove is not also specified
901+
run_on_all rm -f folder1/a &&
902+
test_all_match test_must_fail git update-index --again folder1 &&
903+
test_all_match git update-index --remove --again &&
904+
test_all_match git status --porcelain=v2
905+
'
906+
907+
test_expect_success 'update-index --cacheinfo' '
908+
init_repos &&
909+
910+
deep_a_oid=$(git -C full-checkout rev-parse update-deep:deep/a) &&
911+
folder2_oid=$(git -C full-checkout rev-parse update-folder2:folder2) &&
912+
folder1_a_oid=$(git -C full-checkout rev-parse update-folder1:folder1/a) &&
913+
914+
test_all_match git update-index --cacheinfo 100644 $deep_a_oid deep/a &&
915+
test_all_match git status --porcelain=v2 &&
916+
917+
# Cannot add sparse directory, even in sparse index case
918+
test_all_match test_must_fail git update-index --add --cacheinfo 040000 $folder2_oid folder2/ &&
919+
920+
# Sparse match only - because folder1/a is outside the sparse checkout
921+
# definition (and thus not on-disk), it will appear as "deleted" in
922+
# unstaged changes.
923+
test_all_match git update-index --add --cacheinfo 100644 $folder1_a_oid folder1/a &&
924+
test_sparse_match git status --porcelain=v2
925+
'
926+
777927
test_expect_success 'merge, cherry-pick, and rebase' '
778928
init_repos &&
779929
@@ -1274,6 +1424,21 @@ test_expect_success 'sparse index is not expanded: sparse-checkout' '
12741424
ensure_not_expanded sparse-checkout set
12751425
'
12761426

1427+
test_expect_success 'sparse index is not expanded: update-index' '
1428+
init_repos &&
1429+
1430+
echo "test" >sparse-index/README.md &&
1431+
echo "test2" >sparse-index/a &&
1432+
rm -f sparse-index/deep/a &&
1433+
1434+
ensure_not_expanded update-index --add README.md &&
1435+
ensure_not_expanded update-index a &&
1436+
ensure_not_expanded update-index --remove deep/a &&
1437+
1438+
rm -f sparse-index/README.md sparse-index/a &&
1439+
ensure_not_expanded update-index --add --remove --again
1440+
'
1441+
12771442
# NEEDSWORK: a sparse-checkout behaves differently from a full checkout
12781443
# in this scenario, but it shouldn't.
12791444
test_expect_success 'reset mixed and checkout orphan' '

t/t2107-update-index-basic.sh

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,14 @@ test_expect_success '--cacheinfo mode,sha1,path (new syntax)' '
6464
test_cmp expect actual
6565
'
6666

67+
test_expect_success '--cacheinfo does not accept directory mode' '
68+
mkdir folder1 &&
69+
echo content >folder1/content &&
70+
git add folder1 &&
71+
folder1_oid=$(git ls-files -s folder1 | git hash-object --stdin) &&
72+
test_must_fail git update-index --add --cacheinfo 040000 $folder1_oid folder1/
73+
'
74+
6775
test_expect_success '.lock files cleaned up' '
6876
mkdir cleanup &&
6977
(

0 commit comments

Comments
 (0)