Skip to content

Commit 97fefaf

Browse files
committed
Merge branch 'nd/checkout-paths-reduce-match-pathspec-calls'
Consolidate repeated pathspec matches on the same paths, while fixing a bug in "git checkout dir/" code started from an unmerged index. * nd/checkout-paths-reduce-match-pathspec-calls: checkout: avoid unnecessary match_pathspec calls
2 parents 961c512 + e721c15 commit 97fefaf

File tree

5 files changed

+80
-8
lines changed

5 files changed

+80
-8
lines changed

builtin/checkout.c

Lines changed: 36 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -271,24 +271,55 @@ static int checkout_paths(const struct checkout_opts *opts,
271271
;
272272
ps_matched = xcalloc(1, pos);
273273

274+
/*
275+
* Make sure all pathspecs participated in locating the paths
276+
* to be checked out.
277+
*/
274278
for (pos = 0; pos < active_nr; pos++) {
275279
struct cache_entry *ce = active_cache[pos];
280+
ce->ce_flags &= ~CE_MATCHED;
276281
if (opts->source_tree && !(ce->ce_flags & CE_UPDATE))
282+
/*
283+
* "git checkout tree-ish -- path", but this entry
284+
* is in the original index; it will not be checked
285+
* out to the working tree and it does not matter
286+
* if pathspec matched this entry. We will not do
287+
* anything to this entry at all.
288+
*/
277289
continue;
278-
match_pathspec(opts->pathspec, ce->name, ce_namelen(ce), 0, ps_matched);
290+
/*
291+
* Either this entry came from the tree-ish we are
292+
* checking the paths out of, or we are checking out
293+
* of the index.
294+
*
295+
* If it comes from the tree-ish, we already know it
296+
* matches the pathspec and could just stamp
297+
* CE_MATCHED to it from update_some(). But we still
298+
* need ps_matched and read_tree_recursive (and
299+
* eventually tree_entry_interesting) cannot fill
300+
* ps_matched yet. Once it can, we can avoid calling
301+
* match_pathspec() for _all_ entries when
302+
* opts->source_tree != NULL.
303+
*/
304+
if (match_pathspec(opts->pathspec, ce->name, ce_namelen(ce),
305+
0, ps_matched))
306+
ce->ce_flags |= CE_MATCHED;
279307
}
280308

281-
if (report_path_error(ps_matched, opts->pathspec, opts->prefix))
309+
if (report_path_error(ps_matched, opts->pathspec, opts->prefix)) {
310+
free(ps_matched);
282311
return 1;
312+
}
313+
free(ps_matched);
283314

284315
/* "checkout -m path" to recreate conflicted state */
285316
if (opts->merge)
286-
unmerge_cache(opts->pathspec);
317+
unmerge_marked_index(&the_index);
287318

288319
/* Any unmerged paths? */
289320
for (pos = 0; pos < active_nr; pos++) {
290321
struct cache_entry *ce = active_cache[pos];
291-
if (match_pathspec(opts->pathspec, ce->name, ce_namelen(ce), 0, NULL)) {
322+
if (ce->ce_flags & CE_MATCHED) {
292323
if (!ce_stage(ce))
293324
continue;
294325
if (opts->force) {
@@ -313,9 +344,7 @@ static int checkout_paths(const struct checkout_opts *opts,
313344
state.refresh_cache = 1;
314345
for (pos = 0; pos < active_nr; pos++) {
315346
struct cache_entry *ce = active_cache[pos];
316-
if (opts->source_tree && !(ce->ce_flags & CE_UPDATE))
317-
continue;
318-
if (match_pathspec(opts->pathspec, ce->name, ce_namelen(ce), 0, NULL)) {
347+
if (ce->ce_flags & CE_MATCHED) {
319348
if (!ce_stage(ce)) {
320349
errs |= checkout_entry(ce, &state, NULL);
321350
continue;

cache.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -162,6 +162,9 @@ struct cache_entry {
162162
#define CE_UNPACKED (1 << 24)
163163
#define CE_NEW_SKIP_WORKTREE (1 << 25)
164164

165+
/* used to temporarily mark paths matched by pathspecs */
166+
#define CE_MATCHED (1 << 26)
167+
165168
/*
166169
* Extended on-disk flags
167170
*/

resolve-undo.c

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -118,7 +118,7 @@ int unmerge_index_entry_at(struct index_state *istate, int pos)
118118
struct cache_entry *ce;
119119
struct string_list_item *item;
120120
struct resolve_undo_info *ru;
121-
int i, err = 0;
121+
int i, err = 0, matched;
122122

123123
if (!istate->resolve_undo)
124124
return pos;
@@ -137,13 +137,16 @@ int unmerge_index_entry_at(struct index_state *istate, int pos)
137137
ru = item->util;
138138
if (!ru)
139139
return pos;
140+
matched = ce->ce_flags & CE_MATCHED;
140141
remove_index_entry_at(istate, pos);
141142
for (i = 0; i < 3; i++) {
142143
struct cache_entry *nce;
143144
if (!ru->mode[i])
144145
continue;
145146
nce = make_cache_entry(ru->mode[i], ru->sha1[i],
146147
ce->name, i + 1, 0);
148+
if (matched)
149+
nce->ce_flags |= CE_MATCHED;
147150
if (add_index_entry(istate, nce, ADD_CACHE_OK_TO_ADD)) {
148151
err = 1;
149152
error("cannot unmerge '%s'", ce->name);
@@ -156,6 +159,20 @@ int unmerge_index_entry_at(struct index_state *istate, int pos)
156159
return unmerge_index_entry_at(istate, pos);
157160
}
158161

162+
void unmerge_marked_index(struct index_state *istate)
163+
{
164+
int i;
165+
166+
if (!istate->resolve_undo)
167+
return;
168+
169+
for (i = 0; i < istate->cache_nr; i++) {
170+
struct cache_entry *ce = istate->cache[i];
171+
if (ce->ce_flags & CE_MATCHED)
172+
i = unmerge_index_entry_at(istate, i);
173+
}
174+
}
175+
159176
void unmerge_index(struct index_state *istate, const char **pathspec)
160177
{
161178
int i;

resolve-undo.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,5 +12,6 @@ extern struct string_list *resolve_undo_read(const char *, unsigned long);
1212
extern void resolve_undo_clear_index(struct index_state *);
1313
extern int unmerge_index_entry_at(struct index_state *, int);
1414
extern void unmerge_index(struct index_state *, const char **);
15+
extern void unmerge_marked_index(struct index_state *);
1516

1617
#endif

t/t2022-checkout-paths.sh

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,4 +39,26 @@ test_expect_success 'checking out paths out of a tree does not clobber unrelated
3939
test_cmp expect.next2 dir/next2
4040
'
4141

42+
test_expect_success 'do not touch unmerged entries matching $path but not in $tree' '
43+
git checkout next &&
44+
git reset --hard &&
45+
46+
cat dir/common >expect.common &&
47+
EMPTY_SHA1=$(git hash-object -w --stdin </dev/null) &&
48+
git rm dir/next0 &&
49+
cat >expect.next0 <<-EOF &&
50+
100644 $EMPTY_SHA1 1 dir/next0
51+
100644 $EMPTY_SHA1 2 dir/next0
52+
EOF
53+
git update-index --index-info <expect.next0 &&
54+
55+
git checkout master dir &&
56+
57+
test_cmp expect.common dir/common &&
58+
test_path_is_file dir/master &&
59+
git diff --exit-code master dir/master &&
60+
git ls-files -s dir/next0 >actual.next0 &&
61+
test_cmp expect.next0 actual.next0
62+
'
63+
4264
test_done

0 commit comments

Comments
 (0)