Skip to content

Commit 4d1cfc1

Browse files
vdyegitster
authored andcommitted
reset: make --mixed sparse-aware
Remove the `ensure_full_index` guard on `read_from_tree` and update `git reset --mixed` to ensure it can use sparse directory index entries wherever possible. Sparse directory entries are reset using `diff_tree_oid`, which requires `change` and `add_remove` functions to process the internal contents of the sparse directory. The `recursive` diff option handles cases in which `reset --mixed` must diff/merge files that are nested multiple levels deep in a sparse directory. The use of pathspecs with `git reset --mixed` introduces scenarios in which internal contents of sparse directories may be matched by the pathspec. In order to reset *all* files in the repo that may match the pathspec, the following conditions on the pathspec require index expansion before performing the reset: * "magic" pathspecs * wildcard pathspecs that do not match only in-cone files or entire sparse directories * literal pathspecs matching something outside the sparse checkout definition Helped-by: Elijah Newren <[email protected]> Signed-off-by: Victoria Dye <[email protected]> Signed-off-by: Junio C Hamano <[email protected]>
1 parent 20ec2d0 commit 4d1cfc1

File tree

2 files changed

+102
-2
lines changed

2 files changed

+102
-2
lines changed

builtin/reset.c

Lines changed: 85 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -148,7 +148,9 @@ static void update_index_from_diff(struct diff_queue_struct *q,
148148
* If the file 1) corresponds to an existing index entry with
149149
* skip-worktree set, or 2) does not exist in the index but is
150150
* outside the sparse checkout definition, add a skip-worktree bit
151-
* to the new index entry.
151+
* to the new index entry. Note that a sparse index will be expanded
152+
* if this entry is outside the sparse cone - this is necessary
153+
* to properly construct the reset sparse directory.
152154
*/
153155
pos = cache_name_pos(one->path, strlen(one->path));
154156
if ((pos >= 0 && ce_skip_worktree(active_cache[pos])) ||
@@ -166,6 +168,82 @@ static void update_index_from_diff(struct diff_queue_struct *q,
166168
}
167169
}
168170

171+
static int pathspec_needs_expanded_index(const struct pathspec *pathspec)
172+
{
173+
unsigned int i, pos;
174+
int res = 0;
175+
char *skip_worktree_seen = NULL;
176+
177+
/*
178+
* When using a magic pathspec, assume for the sake of simplicity that
179+
* the index needs to be expanded to match all matchable files.
180+
*/
181+
if (pathspec->magic)
182+
return 1;
183+
184+
for (i = 0; i < pathspec->nr; i++) {
185+
struct pathspec_item item = pathspec->items[i];
186+
187+
/*
188+
* If the pathspec item has a wildcard, the index should be expanded
189+
* if the pathspec has the possibility of matching a subset of entries inside
190+
* of a sparse directory (but not the entire directory).
191+
*
192+
* If the pathspec item is a literal path, the index only needs to be expanded
193+
* if a) the pathspec isn't in the sparse checkout cone (to make sure we don't
194+
* expand for in-cone files) and b) it doesn't match any sparse directories
195+
* (since we can reset whole sparse directories without expanding them).
196+
*/
197+
if (item.nowildcard_len < item.len) {
198+
/*
199+
* Special case: if the pattern is a path inside the cone
200+
* followed by only wildcards, the pattern cannot match
201+
* partial sparse directories, so we don't expand the index.
202+
*/
203+
if (path_in_cone_mode_sparse_checkout(item.original, &the_index) &&
204+
strspn(item.original + item.nowildcard_len, "*") == item.len - item.nowildcard_len)
205+
continue;
206+
207+
for (pos = 0; pos < active_nr; pos++) {
208+
struct cache_entry *ce = active_cache[pos];
209+
210+
if (!S_ISSPARSEDIR(ce->ce_mode))
211+
continue;
212+
213+
/*
214+
* If the pre-wildcard length is longer than the sparse
215+
* directory name and the sparse directory is the first
216+
* component of the pathspec, need to expand the index.
217+
*/
218+
if (item.nowildcard_len > ce_namelen(ce) &&
219+
!strncmp(item.original, ce->name, ce_namelen(ce))) {
220+
res = 1;
221+
break;
222+
}
223+
224+
/*
225+
* If the pre-wildcard length is shorter than the sparse
226+
* directory and the pathspec does not match the whole
227+
* directory, need to expand the index.
228+
*/
229+
if (!strncmp(item.original, ce->name, item.nowildcard_len) &&
230+
wildmatch(item.original, ce->name, 0)) {
231+
res = 1;
232+
break;
233+
}
234+
}
235+
} else if (!path_in_cone_mode_sparse_checkout(item.original, &the_index) &&
236+
!matches_skip_worktree(pathspec, i, &skip_worktree_seen))
237+
res = 1;
238+
239+
if (res > 0)
240+
break;
241+
}
242+
243+
free(skip_worktree_seen);
244+
return res;
245+
}
246+
169247
static int read_from_tree(const struct pathspec *pathspec,
170248
struct object_id *tree_oid,
171249
int intent_to_add)
@@ -178,9 +256,14 @@ static int read_from_tree(const struct pathspec *pathspec,
178256
opt.format_callback = update_index_from_diff;
179257
opt.format_callback_data = &intent_to_add;
180258
opt.flags.override_submodule_config = 1;
259+
opt.flags.recursive = 1;
181260
opt.repo = the_repository;
261+
opt.change = diff_change;
262+
opt.add_remove = diff_addremove;
263+
264+
if (pathspec->nr && the_index.sparse_index && pathspec_needs_expanded_index(pathspec))
265+
ensure_full_index(&the_index);
182266

183-
ensure_full_index(&the_index);
184267
if (do_diff_cache(tree_oid, &opt))
185268
return 1;
186269
diffcore_std(&opt);

t/t1092-sparse-checkout-compatibility.sh

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -795,11 +795,28 @@ test_expect_success 'sparse-index is not expanded' '
795795
ensure_not_expanded reset --hard $ref || return 1
796796
done &&
797797
798+
ensure_not_expanded reset --mixed base &&
798799
ensure_not_expanded reset --hard update-deep &&
799800
ensure_not_expanded reset --keep base &&
800801
ensure_not_expanded reset --merge update-deep &&
801802
ensure_not_expanded reset --hard &&
802803
804+
ensure_not_expanded reset base -- deep/a &&
805+
ensure_not_expanded reset base -- nonexistent-file &&
806+
ensure_not_expanded reset deepest -- deep &&
807+
808+
# Although folder1 is outside the sparse definition, it exists as a
809+
# directory entry in the index, so the pathspec will not force the
810+
# index to be expanded.
811+
ensure_not_expanded reset deepest -- folder1 &&
812+
ensure_not_expanded reset deepest -- folder1/ &&
813+
814+
# Wildcard identifies only in-cone files, no index expansion
815+
ensure_not_expanded reset deepest -- deep/\* &&
816+
817+
# Wildcard identifies only full sparse directories, no index expansion
818+
ensure_not_expanded reset deepest -- folder\* &&
819+
803820
ensure_not_expanded checkout -f update-deep &&
804821
test_config -C sparse-index pull.twohead ort &&
805822
(

0 commit comments

Comments
 (0)