Skip to content

Commit de87f1d

Browse files
authored
Merge pull request git-for-windows#423 from vdye/sparse-index/update-index
Sparse index: integrate with `git update-index`
2 parents b713582 + 51dfddf commit de87f1d

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
@@ -411,6 +411,9 @@ static int add_cacheinfo(unsigned int mode, const struct object_id *oid,
411411
if (!verify_path(path, mode))
412412
return error("Invalid path '%s'", path);
413413

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

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

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

10811090
git_config(git_default_config, NULL);
10821091

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

read-cache.c

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

1327-
if (!(option & ADD_CACHE_KEEP_CACHE_TREE))
1328-
cache_tree_invalidate_path(istate, ce->name);
1329-
13301327
/*
13311328
* If this entry's path sorts after the last entry in the index,
13321329
* we can avoid searching for it.
@@ -1337,6 +1334,13 @@ static int add_index_entry_with_check(struct index_state *istate, struct cache_e
13371334
else
13381335
pos = index_name_stage_pos(istate, ce->name, ce_namelen(ce), ce_stage(ce));
13391336

1337+
/*
1338+
* Cache tree path should be invalidated only after index_name_stage_pos,
1339+
* in case it expands a sparse index.
1340+
*/
1341+
if (!(option & ADD_CACHE_KEEP_CACHE_TREE))
1342+
cache_tree_invalidate_path(istate, ce->name);
1343+
13401344
/* existing match? Just replace it. */
13411345
if (pos >= 0) {
13421346
if (!new_only)

t/t1092-sparse-checkout-compatibility.sh

Lines changed: 165 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -621,6 +621,156 @@ test_expect_success 'reset with wildcard pathspec' '
621621
test_all_match git status --porcelain=v2
622622
'
623623

624+
# NEEDSWORK: although update-index executes without error on files outside
625+
# the sparse checkout definition, it does not actually add the file to the
626+
# index. This is also true when "--no-ignore-skip-worktree-entries" is
627+
# specified.
628+
test_expect_success 'update-index add outside sparse definition' '
629+
init_repos &&
630+
631+
write_script edit-contents <<-\EOF &&
632+
echo text >>$1
633+
EOF
634+
635+
run_on_sparse mkdir -p folder1 &&
636+
run_on_sparse cp ../initial-repo/folder1/a folder1/a &&
637+
638+
# Edit the file only in sparse checkouts so that, when checking the status
639+
# of the index, the unmodified full-checkout is compared to the "modified"
640+
# sparse checkouts.
641+
run_on_sparse ../edit-contents folder1/a &&
642+
643+
test_sparse_match git update-index --add folder1/a &&
644+
test_all_match git status --porcelain=v2 &&
645+
test_sparse_match git update-index --add --no-ignore-skip-worktree-entries folder1/a &&
646+
test_all_match git status --porcelain=v2
647+
'
648+
649+
test_expect_success 'update-index remove outside sparse definition' '
650+
init_repos &&
651+
652+
# When --remove is specified, files outside the sparse checkout definition
653+
# are considered "removed".
654+
rm -f full-checkout/folder1/a &&
655+
test_all_match git update-index --remove folder1/a &&
656+
test_all_match git status --porcelain=v2 &&
657+
658+
git reset --hard &&
659+
660+
# When --ignore-skip-worktree-entries is explicitly specified, a file
661+
# outside the sparse definition is not added to the index as "removed"
662+
# (thus matching the unmodified full-checkout).
663+
test_sparse_match git update-index --remove --ignore-skip-worktree-entries folder1/a &&
664+
test_all_match git status --porcelain=v2 &&
665+
666+
git reset --hard &&
667+
668+
# --force-remove supercedes --ignore-skip-worktree-entries and always
669+
# removes the file from the index.
670+
test_all_match git update-index --force-remove --ignore-skip-worktree-entries folder1/a &&
671+
test_all_match git status --porcelain=v2
672+
'
673+
674+
test_expect_success 'update-index folder add/remove' '
675+
init_repos &&
676+
677+
test_all_match test_must_fail git update-index --add --remove deep &&
678+
test_all_match test_must_fail git update-index --add --remove deep/ &&
679+
680+
# NEEDSWORK: attempting to update-index on an existing folder outside the
681+
# sparse checkout definition does not throw an error (as it does for folders
682+
# inside the definition, or in the full checkout). However, it is a no-op.
683+
test_sparse_match git update-index --add --remove folder1 &&
684+
test_sparse_match git update-index --add --remove folder1/ &&
685+
test_sparse_match git update-index --force-remove folder1/ &&
686+
test_all_match git status --porcelain=v2 &&
687+
688+
# New folders, even in sparse checkouts, throw an error on update-index
689+
run_on_all mkdir folder3 &&
690+
run_on_all cp a folder3/a &&
691+
run_on_all test_must_fail git update-index --add --remove folder3
692+
'
693+
694+
test_expect_success 'update-index with updated flags' '
695+
init_repos &&
696+
697+
# NEEDSWORK: updating flags runs inconsistently on directories, performing no
698+
# operation with warning text specifying the path being ignored if a trailing
699+
# slash is in the path, but throwing an error if there is no trailing slash.
700+
test_all_match test_must_fail git update-index --no-skip-worktree folder1 &&
701+
test_all_match git update-index --no-skip-worktree folder1/ &&
702+
test_all_match git status --porcelain=v2 &&
703+
704+
# Removing the skip-worktree bit from a file outside the sparse checkout
705+
# will cause the file to appear as unstaged and deleted.
706+
test_sparse_match git update-index --no-skip-worktree folder1/a &&
707+
rm -f full-checkout/folder1/a &&
708+
test_all_match git status --porcelain=v2
709+
'
710+
711+
test_expect_success 'update-index --again file outside sparse definition' '
712+
init_repos &&
713+
714+
write_script edit-contents <<-\EOF &&
715+
echo text >>$1
716+
EOF
717+
718+
# When a file is manually added and modified outside the checkout
719+
# definition, it will not be changed with `--again` because its changes are
720+
# not tracked in the index.
721+
run_on_sparse mkdir -p folder1 &&
722+
run_on_sparse ../edit-contents folder1/a &&
723+
test_sparse_match git update-index --again &&
724+
test_sparse_match git status --porcelain=v2 &&
725+
726+
# Update the sparse checkouts so that folder1/a is no longer skipped and
727+
# exists on-disk
728+
run_on_sparse cp ../initial-repo/folder1/a folder1/a &&
729+
test_sparse_match git update-index --no-skip-worktree folder1/a &&
730+
test_all_match git status --porcelain=v2 &&
731+
732+
# Stage change for commit
733+
run_on_all ../edit-contents folder1/a &&
734+
test_all_match git update-index folder1/a &&
735+
test_all_match git status --porcelain=v2 &&
736+
737+
# Modify the file
738+
run_on_all ../edit-contents folder1/a &&
739+
test_all_match git status --porcelain=v2 &&
740+
741+
# Run update-index --again, which re-stages the local changes
742+
test_all_match git update-index --again &&
743+
test_all_match git ls-files -s folder1/a &&
744+
test_all_match git status --porcelain=v2 &&
745+
746+
# Running update-index --again with staged changes after manually deleting
747+
# the file on disk will cause it to fail if --remove is not also specified
748+
run_on_all rm -f folder1/a &&
749+
test_all_match test_must_fail git update-index --again folder1 &&
750+
test_all_match git update-index --remove --again &&
751+
test_all_match git status --porcelain=v2
752+
'
753+
754+
test_expect_success 'update-index --cacheinfo' '
755+
init_repos &&
756+
757+
deep_a_oid=$(git -C full-checkout rev-parse update-deep:deep/a) &&
758+
folder2_oid=$(git -C full-checkout rev-parse update-folder2:folder2) &&
759+
folder1_a_oid=$(git -C full-checkout rev-parse update-folder1:folder1/a) &&
760+
761+
test_all_match git update-index --cacheinfo 100644 $deep_a_oid deep/a &&
762+
test_all_match git status --porcelain=v2 &&
763+
764+
# Cannot add sparse directory, even in sparse index case
765+
test_all_match test_must_fail git update-index --add --cacheinfo 040000 $folder2_oid folder2/ &&
766+
767+
# Sparse match only - because folder1/a is outside the sparse checkout
768+
# definition (and thus not on-disk), it will appear as "deleted" in
769+
# unstaged changes.
770+
test_all_match git update-index --add --cacheinfo 100644 $folder1_a_oid folder1/a &&
771+
test_sparse_match git status --porcelain=v2
772+
'
773+
624774
test_expect_success 'merge, cherry-pick, and rebase' '
625775
init_repos &&
626776
@@ -907,6 +1057,21 @@ test_expect_success 'sparse index is not expanded: sparse-checkout' '
9071057
ensure_not_expanded sparse-checkout set
9081058
'
9091059

1060+
test_expect_success 'sparse index is not expanded: update-index' '
1061+
init_repos &&
1062+
1063+
echo "test" >sparse-index/README.md &&
1064+
echo "test2" >sparse-index/a &&
1065+
rm -f sparse-index/deep/a &&
1066+
1067+
ensure_not_expanded update-index --add README.md &&
1068+
ensure_not_expanded update-index a &&
1069+
ensure_not_expanded update-index --remove deep/a &&
1070+
1071+
rm -f sparse-index/README.md sparse-index/a &&
1072+
ensure_not_expanded update-index --add --remove --again
1073+
'
1074+
9101075
# NEEDSWORK: a sparse-checkout behaves differently from a full checkout
9111076
# in this scenario, but it shouldn't.
9121077
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)