Skip to content

Commit bd9a755

Browse files
committed
Sparse Index: log why the index is being expanded (#691)
I will intend to send this upstream after the 2.47.0 release cycle, but this should get to our microsoft/git users for maximum impact. Customers have been struggling with explaining why the sparse index expansion advice message is showing up. The advice to run 'git clean' has not always helped folks, and sometimes it is very unclear why we are running into trouble. These changes introduce a way to log a reason for the expansion into the trace2 logs so it can be found by requesting that a user enable tracing. While testing this, I created the most standard case that happens, which is to have an existing directory match a sparse directory in the index. In this case, it showed that two log messages were required. See the last commit for this new log message. Together, these two places show this kind of message in the `GIT_TRACE2_PERF` output (trimmed for clarity): ``` region_enter | index | label:clear_skip_worktree_from_present_files_sparse data | sparse-index | ..skip-worktree sparsedir:<my-sparse-path>/ data | index | ..sparse_path_count:362 data | index | ..sparse_lstat_count:732 region_leave | index | label:clear_skip_worktree_from_present_files_sparse data | sparse-index | expansion-reason:failed to clear skip-worktree while sparse ``` I added some tests to demonstrate that these logs are recorded, but it also seems difficult to hit some of these cases.
2 parents 5d7750d + c69d14f commit bd9a755

26 files changed

+111
-37
lines changed

Documentation/technical/sparse-index.txt

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -206,3 +206,10 @@ Here are some commands that might be useful to update:
206206
* `git am`
207207
* `git clean`
208208
* `git stash`
209+
210+
In order to help identify the cases where remaining index expansion is
211+
occurring in user machines, calls to `ensure_full_index()` have been
212+
replaced with `ensure_full_index_with_reason()` or with
213+
`ensure_full_index_unaudited()`. These versions add tracing that should
214+
help identify the reason for the index expansion without needing full
215+
access to someone's repository.

builtin/checkout-index.c

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -156,7 +156,8 @@ static int checkout_all(const char *prefix, int prefix_length)
156156
* first entry inside the expanded sparse directory).
157157
*/
158158
if (ignore_skip_worktree) {
159-
ensure_full_index(the_repository->index);
159+
ensure_full_index_with_reason(the_repository->index,
160+
"checkout-index");
160161
ce = the_repository->index->cache[i];
161162
}
162163
}

builtin/commit.c

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -385,7 +385,7 @@ static int list_paths(struct string_list *list, const char *with_tree,
385385
}
386386

387387
/* TODO: audit for interaction with sparse-index. */
388-
ensure_full_index(the_repository->index);
388+
ensure_full_index_unaudited(the_repository->index);
389389
for (i = 0; i < the_repository->index->cache_nr; i++) {
390390
const struct cache_entry *ce = the_repository->index->cache[i];
391391
struct string_list_item *item;
@@ -1133,7 +1133,7 @@ static int prepare_to_commit(const char *index_file, const char *prefix,
11331133
int i, ita_nr = 0;
11341134

11351135
/* TODO: audit for interaction with sparse-index. */
1136-
ensure_full_index(the_repository->index);
1136+
ensure_full_index_unaudited(the_repository->index);
11371137
for (i = 0; i < the_repository->index->cache_nr; i++)
11381138
if (ce_intent_to_add(the_repository->index->cache[i]))
11391139
ita_nr++;

builtin/difftool.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -592,7 +592,7 @@ static int run_dir_diff(const char *extcmd, int symlinks, const char *prefix,
592592
ret = run_command(&cmd);
593593

594594
/* TODO: audit for interaction with sparse-index. */
595-
ensure_full_index(&wtindex);
595+
ensure_full_index_unaudited(&wtindex);
596596

597597
/*
598598
* If the diff includes working copy files and those

builtin/fsck.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -821,7 +821,7 @@ static void fsck_index(struct index_state *istate, const char *index_path,
821821
unsigned int i;
822822

823823
/* TODO: audit for interaction with sparse-index. */
824-
ensure_full_index(istate);
824+
ensure_full_index_unaudited(istate);
825825
for (i = 0; i < istate->cache_nr; i++) {
826826
unsigned int mode;
827827
struct blob *blob;

builtin/ls-files.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -413,7 +413,7 @@ static void show_files(struct repository *repo, struct dir_struct *dir)
413413
return;
414414

415415
if (!show_sparse_dirs)
416-
ensure_full_index(repo->index);
416+
ensure_full_index_with_reason(repo->index, "ls-files");
417417

418418
for (i = 0; i < repo->index->cache_nr; i++) {
419419
const struct cache_entry *ce = repo->index->cache[i];

builtin/merge-index.c

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@ static void merge_all(void)
6666
{
6767
int i;
6868
/* TODO: audit for interaction with sparse-index. */
69-
ensure_full_index(the_repository->index);
69+
ensure_full_index_unaudited(the_repository->index);
7070
for (i = 0; i < the_repository->index->cache_nr; i++) {
7171
const struct cache_entry *ce = the_repository->index->cache[i];
7272
if (!ce_stage(ce))
@@ -93,7 +93,7 @@ int cmd_merge_index(int argc,
9393
repo_read_index(the_repository);
9494

9595
/* TODO: audit for interaction with sparse-index. */
96-
ensure_full_index(the_repository->index);
96+
ensure_full_index_unaudited(the_repository->index);
9797

9898
i = 1;
9999
if (!strcmp(argv[i], "-o")) {

builtin/read-tree.c

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -226,7 +226,8 @@ int cmd_read_tree(int argc,
226226
setup_work_tree();
227227

228228
if (opts.skip_sparse_checkout)
229-
ensure_full_index(the_repository->index);
229+
ensure_full_index_with_reason(the_repository->index,
230+
"read-tree");
230231

231232
if (opts.merge) {
232233
switch (stage - 1) {

builtin/reset.c

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -262,7 +262,8 @@ static int read_from_tree(const struct pathspec *pathspec,
262262
opt.add_remove = diff_addremove;
263263

264264
if (pathspec->nr && pathspec_needs_expanded_index(the_repository->index, pathspec))
265-
ensure_full_index(the_repository->index);
265+
ensure_full_index_with_reason(the_repository->index,
266+
"reset pathspec");
266267

267268
if (do_diff_cache(tree_oid, &opt))
268269
return 1;

builtin/rm.c

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -313,7 +313,8 @@ int cmd_rm(int argc,
313313
seen = xcalloc(pathspec.nr, 1);
314314

315315
if (pathspec_needs_expanded_index(the_repository->index, &pathspec))
316-
ensure_full_index(the_repository->index);
316+
ensure_full_index_with_reason(the_repository->index,
317+
"rm pathspec");
317318

318319
for (i = 0; i < the_repository->index->cache_nr; i++) {
319320
const struct cache_entry *ce = the_repository->index->cache[i];

builtin/sparse-checkout.c

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -207,7 +207,8 @@ static void clean_tracked_sparse_directories(struct repository *r)
207207
strbuf_release(&path);
208208

209209
if (was_full)
210-
ensure_full_index(r->index);
210+
ensure_full_index_with_reason(r->index,
211+
"sparse-checkout:was full");
211212
}
212213

213214
static int update_working_directory(struct pattern_list *pl)
@@ -437,7 +438,8 @@ static int update_modes(int *cone_mode, int *sparse_index)
437438
the_repository->index->updated_workdir = 1;
438439

439440
if (!*sparse_index)
440-
ensure_full_index(the_repository->index);
441+
ensure_full_index_with_reason(the_repository->index,
442+
"sparse-checkout:disabling sparse index");
441443
}
442444

443445
return 0;

builtin/stash.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1560,7 +1560,7 @@ static int do_push_stash(const struct pathspec *ps, const char *stash_msg, int q
15601560
char *ps_matched = xcalloc(ps->nr, 1);
15611561

15621562
/* TODO: audit for interaction with sparse-index. */
1563-
ensure_full_index(the_repository->index);
1563+
ensure_full_index_unaudited(the_repository->index);
15641564
for (size_t i = 0; i < the_repository->index->cache_nr; i++)
15651565
ce_path_match(the_repository->index, the_repository->index->cache[i], ps,
15661566
ps_matched);

builtin/submodule--helper.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3400,7 +3400,7 @@ static void die_on_index_match(const char *path, int force)
34003400
char *ps_matched = xcalloc(ps.nr, 1);
34013401

34023402
/* TODO: audit for interaction with sparse-index. */
3403-
ensure_full_index(the_repository->index);
3403+
ensure_full_index_unaudited(the_repository->index);
34043404

34053405
/*
34063406
* Since there is only one pathspec, we just need to

builtin/update-index.c

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -714,7 +714,9 @@ static int do_reupdate(const char **paths,
714714
* to process each path individually
715715
*/
716716
if (S_ISSPARSEDIR(ce->ce_mode)) {
717-
ensure_full_index(the_repository->index);
717+
const char *fmt = "update-index:modified sparse dir '%s'";
718+
ensure_full_index_with_reason(the_repository->index,
719+
fmt, ce->name);
718720
goto redo;
719721
}
720722

entry.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -453,7 +453,7 @@ static void mark_colliding_entries(const struct checkout *state,
453453
ce->ce_flags |= CE_MATCHED;
454454

455455
/* TODO: audit for interaction with sparse-index. */
456-
ensure_full_index(state->istate);
456+
ensure_full_index_unaudited(state->istate);
457457
for (size_t i = 0; i < state->istate->cache_nr; i++) {
458458
struct cache_entry *dup = state->istate->cache[i];
459459

merge-ort.c

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4534,7 +4534,8 @@ static int record_conflicted_index_entries(struct merge_options *opt)
45344534
*/
45354535
strmap_for_each_entry(&opt->priv->conflicted, &iter, e) {
45364536
if (!path_in_sparse_checkout(e->key, index)) {
4537-
ensure_full_index(index);
4537+
const char *fmt = "merge-ort: path outside sparse checkout (%s)";
4538+
ensure_full_index_with_reason(index, fmt, e->key);
45384539
break;
45394540
}
45404541
}

merge-recursive.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -540,7 +540,7 @@ static struct string_list *get_unmerged(struct index_state *istate)
540540
string_list_init_dup(unmerged);
541541

542542
/* TODO: audit for interaction with sparse-index. */
543-
ensure_full_index(istate);
543+
ensure_full_index_unaudited(istate);
544544
for (i = 0; i < istate->cache_nr; i++) {
545545
struct string_list_item *item;
546546
struct stage_data *e;

read-cache.c

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -555,7 +555,9 @@ static int index_name_stage_pos(struct index_state *istate,
555555
if (S_ISSPARSEDIR(ce->ce_mode) &&
556556
ce_namelen(ce) < namelen &&
557557
!strncmp(name, ce->name, ce_namelen(ce))) {
558-
ensure_full_index(istate);
558+
const char *fmt = "searching for '%s' and found parent dir '%s'";
559+
ensure_full_index_with_reason(istate, fmt,
560+
name, ce->name);
559561
return index_name_stage_pos(istate, name, namelen, stage, search_mode);
560562
}
561563
}
@@ -2376,7 +2378,7 @@ int do_read_index(struct index_state *istate, const char *path, int must_exist)
23762378
*/
23772379
prepare_repo_settings(istate->repo);
23782380
if (istate->repo->settings.command_requires_full_index)
2379-
ensure_full_index(istate);
2381+
ensure_full_index_with_reason(istate, "incompatible builtin");
23802382
else
23812383
ensure_correct_sparsity(istate);
23822384

@@ -2588,7 +2590,7 @@ int repo_index_has_changes(struct repository *repo,
25882590
return opt.flags.has_changes != 0;
25892591
} else {
25902592
/* TODO: audit for interaction with sparse-index. */
2591-
ensure_full_index(istate);
2593+
ensure_full_index_unaudited(istate);
25922594
for (i = 0; sb && i < istate->cache_nr; i++) {
25932595
if (i)
25942596
strbuf_addch(sb, ' ');
@@ -3208,7 +3210,7 @@ static int do_write_locked_index(struct index_state *istate,
32083210
"%s", get_lock_file_path(lock));
32093211

32103212
if (was_full)
3211-
ensure_full_index(istate);
3213+
ensure_full_index_with_reason(istate, "re-expanding after write");
32123214

32133215
if (ret)
32143216
return ret;
@@ -3319,7 +3321,7 @@ static int write_shared_index(struct index_state *istate,
33193321
the_repository, "%s", get_tempfile_path(*temp));
33203322

33213323
if (was_full)
3322-
ensure_full_index(istate);
3324+
ensure_full_index_with_reason(istate, "re-expanding after write");
33233325

33243326
if (ret)
33253327
return ret;
@@ -3870,7 +3872,7 @@ void overlay_tree_on_index(struct index_state *istate,
38703872

38713873
/* Hoist the unmerged entries up to stage #3 to make room */
38723874
/* TODO: audit for interaction with sparse-index. */
3873-
ensure_full_index(istate);
3875+
ensure_full_index_unaudited(istate);
38743876
for (i = 0; i < istate->cache_nr; i++) {
38753877
struct cache_entry *ce = istate->cache[i];
38763878
if (!ce_stage(ce))

repository.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -434,7 +434,7 @@ int repo_read_index(struct repository *repo)
434434

435435
prepare_repo_settings(repo);
436436
if (repo->settings.command_requires_full_index)
437-
ensure_full_index(repo->index);
437+
ensure_full_index_with_reason(repo->index, "incompatible builtin");
438438

439439
/*
440440
* If sparse checkouts are in use, check whether paths with the

resolve-undo.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -161,7 +161,7 @@ void unmerge_index(struct index_state *istate, const struct pathspec *pathspec,
161161
return;
162162

163163
/* TODO: audit for interaction with sparse-index. */
164-
ensure_full_index(istate);
164+
ensure_full_index_unaudited(istate);
165165

166166
for_each_string_list_item(item, istate->resolve_undo) {
167167
const char *path = item->string;

revision.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1850,7 +1850,7 @@ static void do_add_index_objects_to_pending(struct rev_info *revs,
18501850
int i;
18511851

18521852
/* TODO: audit for interaction with sparse-index. */
1853-
ensure_full_index(istate);
1853+
ensure_full_index_unaudited(istate);
18541854
for (i = 0; i < istate->cache_nr; i++) {
18551855
struct cache_entry *ce = istate->cache[i];
18561856
struct blob *blob;

sequencer.c

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -797,7 +797,7 @@ static int do_recursive_merge(struct repository *r,
797797
merge_switch_to_result(&o, head_tree, &result, 1, show_output);
798798
clean = result.clean;
799799
} else {
800-
ensure_full_index(r->index);
800+
ensure_full_index_with_reason(r->index, "non-ort merge strategy");
801801
clean = merge_trees(&o, head_tree, next_tree, base_tree);
802802
if (is_rebase_i(opts) && clean <= 0)
803803
fputs(o.obuf.buf, stdout);
@@ -2574,7 +2574,7 @@ static int read_and_refresh_cache(struct repository *r,
25742574
* expand the sparse index.
25752575
*/
25762576
if (opts->strategy && strcmp(opts->strategy, "ort"))
2577-
ensure_full_index(r->index);
2577+
ensure_full_index_with_reason(r->index, "non-ort merge strategy");
25782578
return 0;
25792579
}
25802580

sparse-index.c

Lines changed: 27 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -463,6 +463,24 @@ void ensure_full_index(struct index_state *istate)
463463
expand_index(istate, NULL);
464464
}
465465

466+
void ensure_full_index_with_reason(struct index_state *istate,
467+
const char *fmt, ...)
468+
{
469+
va_list ap;
470+
struct strbuf why = STRBUF_INIT;
471+
if (!istate)
472+
BUG("ensure_full_index_with_reason() must get an index!");
473+
if (istate->sparse_index == INDEX_EXPANDED)
474+
return;
475+
476+
va_start(ap, fmt);
477+
strbuf_vaddf(&why, fmt, ap);
478+
trace2_data_string("sparse-index", istate->repo, "expansion-reason", why.buf);
479+
va_end(ap);
480+
strbuf_release(&why);
481+
ensure_full_index(istate);
482+
}
483+
466484
void ensure_correct_sparsity(struct index_state *istate)
467485
{
468486
/*
@@ -472,7 +490,8 @@ void ensure_correct_sparsity(struct index_state *istate)
472490
if (is_sparse_index_allowed(istate, 0))
473491
convert_to_sparse(istate, 0);
474492
else
475-
ensure_full_index(istate);
493+
ensure_full_index_with_reason(istate,
494+
"sparse index not allowed");
476495
}
477496

478497
struct path_found_data {
@@ -620,6 +639,8 @@ static int clear_skip_worktree_from_present_files_sparse(struct index_state *ist
620639
if (path_found(ce->name, &data)) {
621640
if (S_ISSPARSEDIR(ce->ce_mode)) {
622641
to_restart = 1;
642+
trace2_data_string("sparse-index", istate->repo,
643+
"skip-worktree sparsedir", ce->name);
623644
break;
624645
}
625646
ce->ce_flags &= ~CE_SKIP_WORKTREE;
@@ -675,7 +696,8 @@ void clear_skip_worktree_from_present_files(struct index_state *istate)
675696
return;
676697

677698
if (clear_skip_worktree_from_present_files_sparse(istate)) {
678-
ensure_full_index(istate);
699+
ensure_full_index_with_reason(istate,
700+
"failed to clear skip-worktree while sparse");
679701
clear_skip_worktree_from_present_files_full(istate);
680702
}
681703
}
@@ -738,7 +760,9 @@ void expand_to_path(struct index_state *istate,
738760
* in the index, perhaps it exists within this
739761
* sparse-directory. Expand accordingly.
740762
*/
741-
ensure_full_index(istate);
763+
const char *fmt = "found index entry for '%s'";
764+
ensure_full_index_with_reason(istate, fmt,
765+
path_mutable.buf);
742766
break;
743767
}
744768

sparse-index.h

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
#ifndef SPARSE_INDEX_H__
22
#define SPARSE_INDEX_H__
33

4+
#include "strbuf.h"
5+
46
/*
57
* If performing an operation where the index is supposed to expand to a
68
* full index, then disable the advice message by setting this global to
@@ -46,4 +48,16 @@ void expand_index(struct index_state *istate, struct pattern_list *pl);
4648

4749
void ensure_full_index(struct index_state *istate);
4850

51+
/**
52+
* If there is a clear reason why the sparse index is being expanded, then
53+
* trace the information for why the expansion is occurring.
54+
*/
55+
void ensure_full_index_with_reason(struct index_state *istate,
56+
const char *fmt,
57+
...);
58+
59+
#define ensure_full_index_unaudited(i) \
60+
ensure_full_index_with_reason((i), \
61+
"unaudited call (%s.%d)", __FILE__, __LINE__);
62+
4963
#endif

0 commit comments

Comments
 (0)