Skip to content

Commit 9b9445c

Browse files
committed
Merge branch 'sy/sparse-rm'
"git rm" has become more aware of the sparse-index feature. * sy/sparse-rm: rm: integrate with sparse-index rm: expand the index only when necessary pathspec.h: move pathspec_needs_expanded_index() from reset.c to here t1092: add tests for `git-rm`
2 parents 80ffc84 + ede241c commit 9b9445c

File tree

6 files changed

+205
-88
lines changed

6 files changed

+205
-88
lines changed

builtin/reset.c

Lines changed: 1 addition & 83 deletions
Original file line numberDiff line numberDiff line change
@@ -174,88 +174,6 @@ static void update_index_from_diff(struct diff_queue_struct *q,
174174
}
175175
}
176176

177-
static int pathspec_needs_expanded_index(const struct pathspec *pathspec)
178-
{
179-
unsigned int i, pos;
180-
int res = 0;
181-
char *skip_worktree_seen = NULL;
182-
183-
/*
184-
* When using a magic pathspec, assume for the sake of simplicity that
185-
* the index needs to be expanded to match all matchable files.
186-
*/
187-
if (pathspec->magic)
188-
return 1;
189-
190-
for (i = 0; i < pathspec->nr; i++) {
191-
struct pathspec_item item = pathspec->items[i];
192-
193-
/*
194-
* If the pathspec item has a wildcard, the index should be expanded
195-
* if the pathspec has the possibility of matching a subset of entries inside
196-
* of a sparse directory (but not the entire directory).
197-
*
198-
* If the pathspec item is a literal path, the index only needs to be expanded
199-
* if a) the pathspec isn't in the sparse checkout cone (to make sure we don't
200-
* expand for in-cone files) and b) it doesn't match any sparse directories
201-
* (since we can reset whole sparse directories without expanding them).
202-
*/
203-
if (item.nowildcard_len < item.len) {
204-
/*
205-
* Special case: if the pattern is a path inside the cone
206-
* followed by only wildcards, the pattern cannot match
207-
* partial sparse directories, so we know we don't need to
208-
* expand the index.
209-
*
210-
* Examples:
211-
* - in-cone/foo***: doesn't need expanded index
212-
* - not-in-cone/bar*: may need expanded index
213-
* - **.c: may need expanded index
214-
*/
215-
if (strspn(item.original + item.nowildcard_len, "*") == item.len - item.nowildcard_len &&
216-
path_in_cone_mode_sparse_checkout(item.original, &the_index))
217-
continue;
218-
219-
for (pos = 0; pos < active_nr; pos++) {
220-
struct cache_entry *ce = active_cache[pos];
221-
222-
if (!S_ISSPARSEDIR(ce->ce_mode))
223-
continue;
224-
225-
/*
226-
* If the pre-wildcard length is longer than the sparse
227-
* directory name and the sparse directory is the first
228-
* component of the pathspec, need to expand the index.
229-
*/
230-
if (item.nowildcard_len > ce_namelen(ce) &&
231-
!strncmp(item.original, ce->name, ce_namelen(ce))) {
232-
res = 1;
233-
break;
234-
}
235-
236-
/*
237-
* If the pre-wildcard length is shorter than the sparse
238-
* directory and the pathspec does not match the whole
239-
* directory, need to expand the index.
240-
*/
241-
if (!strncmp(item.original, ce->name, item.nowildcard_len) &&
242-
wildmatch(item.original, ce->name, 0)) {
243-
res = 1;
244-
break;
245-
}
246-
}
247-
} else if (!path_in_cone_mode_sparse_checkout(item.original, &the_index) &&
248-
!matches_skip_worktree(pathspec, i, &skip_worktree_seen))
249-
res = 1;
250-
251-
if (res > 0)
252-
break;
253-
}
254-
255-
free(skip_worktree_seen);
256-
return res;
257-
}
258-
259177
static int read_from_tree(const struct pathspec *pathspec,
260178
struct object_id *tree_oid,
261179
int intent_to_add)
@@ -273,7 +191,7 @@ static int read_from_tree(const struct pathspec *pathspec,
273191
opt.change = diff_change;
274192
opt.add_remove = diff_addremove;
275193

276-
if (pathspec->nr && the_index.sparse_index && pathspec_needs_expanded_index(pathspec))
194+
if (pathspec->nr && pathspec_needs_expanded_index(&the_index, pathspec))
277195
ensure_full_index(&the_index);
278196

279197
if (do_diff_cache(tree_oid, &opt))

builtin/rm.c

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -287,6 +287,8 @@ int cmd_rm(int argc, const char **argv, const char *prefix)
287287
if (!index_only)
288288
setup_work_tree();
289289

290+
prepare_repo_settings(the_repository);
291+
the_repository->settings.command_requires_full_index = 0;
290292
hold_locked_index(&lock_file, LOCK_DIE_ON_ERROR);
291293

292294
if (read_cache() < 0)
@@ -296,8 +298,9 @@ int cmd_rm(int argc, const char **argv, const char *prefix)
296298

297299
seen = xcalloc(pathspec.nr, 1);
298300

299-
/* TODO: audit for interaction with sparse-index. */
300-
ensure_full_index(&the_index);
301+
if (pathspec_needs_expanded_index(&the_index, &pathspec))
302+
ensure_full_index(&the_index);
303+
301304
for (i = 0; i < active_nr; i++) {
302305
const struct cache_entry *ce = active_cache[i];
303306

pathspec.c

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -759,3 +759,92 @@ int match_pathspec_attrs(struct index_state *istate,
759759

760760
return 1;
761761
}
762+
763+
int pathspec_needs_expanded_index(struct index_state *istate,
764+
const struct pathspec *pathspec)
765+
{
766+
unsigned int i, pos;
767+
int res = 0;
768+
char *skip_worktree_seen = NULL;
769+
770+
/*
771+
* If index is not sparse, no index expansion is needed.
772+
*/
773+
if (!istate->sparse_index)
774+
return 0;
775+
776+
/*
777+
* When using a magic pathspec, assume for the sake of simplicity that
778+
* the index needs to be expanded to match all matchable files.
779+
*/
780+
if (pathspec->magic)
781+
return 1;
782+
783+
for (i = 0; i < pathspec->nr; i++) {
784+
struct pathspec_item item = pathspec->items[i];
785+
786+
/*
787+
* If the pathspec item has a wildcard, the index should be expanded
788+
* if the pathspec has the possibility of matching a subset of entries inside
789+
* of a sparse directory (but not the entire directory).
790+
*
791+
* If the pathspec item is a literal path, the index only needs to be expanded
792+
* if a) the pathspec isn't in the sparse checkout cone (to make sure we don't
793+
* expand for in-cone files) and b) it doesn't match any sparse directories
794+
* (since we can reset whole sparse directories without expanding them).
795+
*/
796+
if (item.nowildcard_len < item.len) {
797+
/*
798+
* Special case: if the pattern is a path inside the cone
799+
* followed by only wildcards, the pattern cannot match
800+
* partial sparse directories, so we know we don't need to
801+
* expand the index.
802+
*
803+
* Examples:
804+
* - in-cone/foo***: doesn't need expanded index
805+
* - not-in-cone/bar*: may need expanded index
806+
* - **.c: may need expanded index
807+
*/
808+
if (strspn(item.original + item.nowildcard_len, "*") == item.len - item.nowildcard_len &&
809+
path_in_cone_mode_sparse_checkout(item.original, istate))
810+
continue;
811+
812+
for (pos = 0; pos < istate->cache_nr; pos++) {
813+
struct cache_entry *ce = istate->cache[pos];
814+
815+
if (!S_ISSPARSEDIR(ce->ce_mode))
816+
continue;
817+
818+
/*
819+
* If the pre-wildcard length is longer than the sparse
820+
* directory name and the sparse directory is the first
821+
* component of the pathspec, need to expand the index.
822+
*/
823+
if (item.nowildcard_len > ce_namelen(ce) &&
824+
!strncmp(item.original, ce->name, ce_namelen(ce))) {
825+
res = 1;
826+
break;
827+
}
828+
829+
/*
830+
* If the pre-wildcard length is shorter than the sparse
831+
* directory and the pathspec does not match the whole
832+
* directory, need to expand the index.
833+
*/
834+
if (!strncmp(item.original, ce->name, item.nowildcard_len) &&
835+
wildmatch(item.original, ce->name, 0)) {
836+
res = 1;
837+
break;
838+
}
839+
}
840+
} else if (!path_in_cone_mode_sparse_checkout(item.original, istate) &&
841+
!matches_skip_worktree(pathspec, i, &skip_worktree_seen))
842+
res = 1;
843+
844+
if (res > 0)
845+
break;
846+
}
847+
848+
free(skip_worktree_seen);
849+
return res;
850+
}

pathspec.h

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -171,4 +171,16 @@ int match_pathspec_attrs(struct index_state *istate,
171171
const char *name, int namelen,
172172
const struct pathspec_item *item);
173173

174+
/*
175+
* Determine whether a pathspec will match only entire index entries (non-sparse
176+
* files and/or entire sparse directories). If the pathspec has the potential to
177+
* match partial contents of a sparse directory, return 1 to indicate the index
178+
* should be expanded to match the appropriate index entries.
179+
*
180+
* For the sake of simplicity, always return 1 if using a more complex "magic"
181+
* pathspec.
182+
*/
183+
int pathspec_needs_expanded_index(struct index_state *istate,
184+
const struct pathspec *pathspec);
185+
174186
#endif /* PATHSPEC_H */

t/perf/p2000-sparse-operations.sh

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -123,5 +123,6 @@ test_perf_on_all git blame $SPARSE_CONE/f3/a
123123
test_perf_on_all git read-tree -mu HEAD
124124
test_perf_on_all git checkout-index -f --all
125125
test_perf_on_all git update-index --add --remove $SPARSE_CONE/a
126+
test_perf_on_all "git rm -f $SPARSE_CONE/a && git checkout HEAD -- $SPARSE_CONE/a"
126127

127128
test_done

t/t1092-sparse-checkout-compatibility.sh

Lines changed: 97 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -937,7 +937,7 @@ test_expect_success 'read-tree --prefix' '
937937
test_all_match git read-tree --prefix=deep/deeper1/deepest -u deepest &&
938938
test_all_match git status --porcelain=v2 &&
939939
940-
test_all_match git rm -rf --sparse folder1/ &&
940+
run_on_all git rm -rf --sparse folder1/ &&
941941
test_all_match git read-tree --prefix=folder1/ -u update-folder1 &&
942942
test_all_match git status --porcelain=v2 &&
943943
@@ -1365,10 +1365,14 @@ ensure_not_expanded () {
13651365
shift &&
13661366
test_must_fail env \
13671367
GIT_TRACE2_EVENT="$(pwd)/trace2.txt" \
1368-
git -C sparse-index "$@" || return 1
1368+
git -C sparse-index "$@" \
1369+
>sparse-index-out \
1370+
2>sparse-index-error || return 1
13691371
else
13701372
GIT_TRACE2_EVENT="$(pwd)/trace2.txt" \
1371-
git -C sparse-index "$@" || return 1
1373+
git -C sparse-index "$@" \
1374+
>sparse-index-out \
1375+
2>sparse-index-error || return 1
13721376
fi &&
13731377
test_region ! index ensure_full_index trace2.txt
13741378
}
@@ -1878,4 +1882,94 @@ test_expect_success 'mv directory from out-of-cone to in-cone' '
18781882
grep -e "H deep/0/1" actual
18791883
'
18801884

1885+
test_expect_success 'rm pathspec inside sparse definition' '
1886+
init_repos &&
1887+
1888+
test_all_match git rm deep/a &&
1889+
test_all_match git status --porcelain=v2 &&
1890+
1891+
# test wildcard
1892+
run_on_all git reset --hard &&
1893+
test_all_match git rm deep/* &&
1894+
test_all_match git status --porcelain=v2 &&
1895+
1896+
# test recursive rm
1897+
run_on_all git reset --hard &&
1898+
test_all_match git rm -r deep &&
1899+
test_all_match git status --porcelain=v2
1900+
'
1901+
1902+
test_expect_success 'rm pathspec outside sparse definition' '
1903+
init_repos &&
1904+
1905+
for file in folder1/a folder1/0/1
1906+
do
1907+
test_sparse_match test_must_fail git rm $file &&
1908+
test_sparse_match test_must_fail git rm --cached $file &&
1909+
test_sparse_match git rm --sparse $file &&
1910+
test_sparse_match git status --porcelain=v2
1911+
done &&
1912+
1913+
cat >folder1-full <<-EOF &&
1914+
rm ${SQ}folder1/0/0/0${SQ}
1915+
rm ${SQ}folder1/0/1${SQ}
1916+
rm ${SQ}folder1/a${SQ}
1917+
EOF
1918+
1919+
cat >folder1-sparse <<-EOF &&
1920+
rm ${SQ}folder1/${SQ}
1921+
EOF
1922+
1923+
# test wildcard
1924+
run_on_sparse git reset --hard &&
1925+
run_on_sparse git sparse-checkout reapply &&
1926+
test_sparse_match test_must_fail git rm folder1/* &&
1927+
run_on_sparse git rm --sparse folder1/* &&
1928+
test_cmp folder1-full sparse-checkout-out &&
1929+
test_cmp folder1-sparse sparse-index-out &&
1930+
test_sparse_match git status --porcelain=v2 &&
1931+
1932+
# test recursive rm
1933+
run_on_sparse git reset --hard &&
1934+
run_on_sparse git sparse-checkout reapply &&
1935+
test_sparse_match test_must_fail git rm --sparse folder1 &&
1936+
run_on_sparse git rm --sparse -r folder1 &&
1937+
test_cmp folder1-full sparse-checkout-out &&
1938+
test_cmp folder1-sparse sparse-index-out &&
1939+
test_sparse_match git status --porcelain=v2
1940+
'
1941+
1942+
test_expect_success 'rm pathspec expands index when necessary' '
1943+
init_repos &&
1944+
1945+
# in-cone pathspec (do not expand)
1946+
ensure_not_expanded rm "deep/deep*" &&
1947+
test_must_be_empty sparse-index-err &&
1948+
1949+
# out-of-cone pathspec (expand)
1950+
! ensure_not_expanded rm --sparse "folder1/a*" &&
1951+
test_must_be_empty sparse-index-err &&
1952+
1953+
# pathspec that should expand index
1954+
! ensure_not_expanded rm "*/a" &&
1955+
test_must_be_empty sparse-index-err &&
1956+
1957+
! ensure_not_expanded rm "**a" &&
1958+
test_must_be_empty sparse-index-err
1959+
'
1960+
1961+
test_expect_success 'sparse index is not expanded: rm' '
1962+
init_repos &&
1963+
1964+
ensure_not_expanded rm deep/a &&
1965+
1966+
# test in-cone wildcard
1967+
git -C sparse-index reset --hard &&
1968+
ensure_not_expanded rm deep/* &&
1969+
1970+
# test recursive rm
1971+
git -C sparse-index reset --hard &&
1972+
ensure_not_expanded rm -r deep
1973+
'
1974+
18811975
test_done

0 commit comments

Comments
 (0)