Skip to content

Commit e6a6535

Browse files
TaoKgitster
authored andcommitted
untracked-cache: support '--untracked-files=all' if configured
Untracked cache was originally designed to only work with "--untracked-files=normal", and is bypassed when "--untracked-files=all" is requested, but this causes performance issues for UI tooling that wants to see "all" on a frequent basis. On the other hand, the conditions that altogether prevented applicability to the "all" mode no longer seem to apply, after several major refactors in recent years; this possibility was discussed in [email protected] and CABPp-BFiwzzUgiTj_zu+vF5x20L0=1cf25cHwk7KZQj2YkVzXw@mail.gmail.com, and somewhat confirmed experimentally by several users using a version of this patch to use untracked cache with -uall for about a year. When 'git status' runs without using the untracked cache, on a large repo, on windows, with fsmonitor, it can run very slowly. This can make GUIs that need to use "-uall" (and therefore currently bypass untracked cache) unusable when fsmonitor is enabled, on such large repos. To partially address this, align the supported directory flags for the stored untracked cache data with the git config. If a user specifies an '--untracked-files=' commandline parameter that does not align with their 'status.showuntrackedfiles' config value, then the untracked cache will be ignored - as it is for other unsupported situations like when a pathspec is specified. If the previously stored flags no longer match the current configuration, but the currently-applicable flags do match the current configuration, then discard the previously stored untracked cache data. For most users there will be no change in behavior. Users who need '--untracked-files=all' to perform well will now have the option of setting "status.showuntrackedfiles" to "all" for better / more consistent performance. Users who need '--untracked-files=all' to perform well for their tooling AND prefer to avoid the verbosity of "all" when running git status explicitly without options... are out of luck for now (no change). Users who have the "status.showuntrackedfiles" config set to "all" and yet frequently explicitly call 'git status --untracked-files=normal' (and use the untracked cache) are the only ones who will be disadvantaged by this change. Their "--untracked-files=normal" calls will, after this change, no longer use the untracked cache. Signed-off-by: Tao Klerks <[email protected]> Signed-off-by: Junio C Hamano <[email protected]>
1 parent a023186 commit e6a6535

File tree

2 files changed

+153
-16
lines changed

2 files changed

+153
-16
lines changed

dir.c

Lines changed: 72 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -2747,13 +2747,33 @@ static void set_untracked_ident(struct untracked_cache *uc)
27472747
strbuf_addch(&uc->ident, 0);
27482748
}
27492749

2750-
static void new_untracked_cache(struct index_state *istate)
2750+
static unsigned new_untracked_cache_flags(struct index_state *istate)
2751+
{
2752+
struct repository *repo = istate->repo;
2753+
char *val;
2754+
2755+
/*
2756+
* This logic is coordinated with the setting of these flags in
2757+
* wt-status.c#wt_status_collect_untracked(), and the evaluation
2758+
* of the config setting in commit.c#git_status_config()
2759+
*/
2760+
if (!repo_config_get_string(repo, "status.showuntrackedfiles", &val) &&
2761+
!strcmp(val, "all"))
2762+
return 0;
2763+
2764+
/*
2765+
* The default, if "all" is not set, is "normal" - leading us here.
2766+
* If the value is "none" then it really doesn't matter.
2767+
*/
2768+
return DIR_SHOW_OTHER_DIRECTORIES | DIR_HIDE_EMPTY_DIRECTORIES;
2769+
}
2770+
2771+
static void new_untracked_cache(struct index_state *istate, int flags)
27512772
{
27522773
struct untracked_cache *uc = xcalloc(1, sizeof(*uc));
27532774
strbuf_init(&uc->ident, 100);
27542775
uc->exclude_per_dir = ".gitignore";
2755-
/* should be the same flags used by git-status */
2756-
uc->dir_flags = DIR_SHOW_OTHER_DIRECTORIES | DIR_HIDE_EMPTY_DIRECTORIES;
2776+
uc->dir_flags = flags >= 0 ? flags : new_untracked_cache_flags(istate);
27572777
set_untracked_ident(uc);
27582778
istate->untracked = uc;
27592779
istate->cache_changed |= UNTRACKED_CHANGED;
@@ -2762,11 +2782,11 @@ static void new_untracked_cache(struct index_state *istate)
27622782
void add_untracked_cache(struct index_state *istate)
27632783
{
27642784
if (!istate->untracked) {
2765-
new_untracked_cache(istate);
2785+
new_untracked_cache(istate, -1);
27662786
} else {
27672787
if (!ident_in_untracked(istate->untracked)) {
27682788
free_untracked_cache(istate->untracked);
2769-
new_untracked_cache(istate);
2789+
new_untracked_cache(istate, -1);
27702790
}
27712791
}
27722792
}
@@ -2814,17 +2834,9 @@ static struct untracked_cache_dir *validate_untracked_cache(struct dir_struct *d
28142834
if (base_len || (pathspec && pathspec->nr))
28152835
return NULL;
28162836

2817-
/* Different set of flags may produce different results */
2818-
if (dir->flags != dir->untracked->dir_flags ||
2819-
/*
2820-
* See treat_directory(), case index_nonexistent. Without
2821-
* this flag, we may need to also cache .git file content
2822-
* for the resolve_gitlink_ref() call, which we don't.
2823-
*/
2824-
!(dir->flags & DIR_SHOW_OTHER_DIRECTORIES) ||
2825-
/* We don't support collecting ignore files */
2826-
(dir->flags & (DIR_SHOW_IGNORED | DIR_SHOW_IGNORED_TOO |
2827-
DIR_COLLECT_IGNORED)))
2837+
/* We don't support collecting ignore files */
2838+
if (dir->flags & (DIR_SHOW_IGNORED | DIR_SHOW_IGNORED_TOO |
2839+
DIR_COLLECT_IGNORED))
28282840
return NULL;
28292841

28302842
/*
@@ -2847,6 +2859,50 @@ static struct untracked_cache_dir *validate_untracked_cache(struct dir_struct *d
28472859
return NULL;
28482860
}
28492861

2862+
/*
2863+
* If the untracked structure we received does not have the same flags
2864+
* as requested in this run, we're going to need to either discard the
2865+
* existing structure (and potentially later recreate), or bypass the
2866+
* untracked cache mechanism for this run.
2867+
*/
2868+
if (dir->flags != dir->untracked->dir_flags) {
2869+
/*
2870+
* If the untracked structure we received does not have the same flags
2871+
* as configured, then we need to reset / create a new "untracked"
2872+
* structure to match the new config.
2873+
*
2874+
* Keeping the saved and used untracked cache consistent with the
2875+
* configuration provides an opportunity for frequent users of
2876+
* "git status -uall" to leverage the untracked cache by aligning their
2877+
* configuration - setting "status.showuntrackedfiles" to "all" or
2878+
* "normal" as appropriate.
2879+
*
2880+
* Previously using -uall (or setting "status.showuntrackedfiles" to
2881+
* "all") was incompatible with untracked cache and *consistently*
2882+
* caused surprisingly bad performance (with fscache and fsmonitor
2883+
* enabled) on Windows.
2884+
*
2885+
* IMPROVEMENT OPPORTUNITY: If we reworked the untracked cache storage
2886+
* to not be as bound up with the desired output in a given run,
2887+
* and instead iterated through and stored enough information to
2888+
* correctly serve both "modes", then users could get peak performance
2889+
* with or without '-uall' regardless of their
2890+
* "status.showuntrackedfiles" config.
2891+
*/
2892+
if (dir->untracked->dir_flags != new_untracked_cache_flags(istate)) {
2893+
free_untracked_cache(istate->untracked);
2894+
new_untracked_cache(istate, dir->flags);
2895+
dir->untracked = istate->untracked;
2896+
}
2897+
else {
2898+
/*
2899+
* Current untracked cache data is consistent with config, but not
2900+
* usable in this request/run; just bypass untracked cache.
2901+
*/
2902+
return NULL;
2903+
}
2904+
}
2905+
28502906
if (!dir->untracked->root) {
28512907
/* Untracked cache existed but is not initialized; fix that */
28522908
FLEX_ALLOC_STR(dir->untracked->root, name, "");

t/t7063-status-untracked-cache.sh

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -222,6 +222,87 @@ test_expect_success 'untracked cache remains after bypass' '
222222
test_cmp ../dump.expect ../actual
223223
'
224224

225+
test_expect_success 'if -uall is configured, untracked cache gets populated by default' '
226+
test_config status.showuntrackedfiles all &&
227+
: >../trace.output &&
228+
GIT_TRACE2_PERF="$TRASH_DIRECTORY/trace.output" \
229+
git status --porcelain >../actual &&
230+
iuc status --porcelain >../status.iuc &&
231+
test_cmp ../status_uall.expect ../status.iuc &&
232+
test_cmp ../status_uall.expect ../actual &&
233+
get_relevant_traces ../trace.output ../trace.relevant &&
234+
cat >../trace.expect <<EOF &&
235+
....path:
236+
....node-creation:3
237+
....gitignore-invalidation:1
238+
....directory-invalidation:0
239+
....opendir:4
240+
EOF
241+
test_cmp ../trace.expect ../trace.relevant
242+
'
243+
244+
cat >../dump_uall.expect <<EOF &&
245+
info/exclude $EMPTY_BLOB
246+
core.excludesfile $ZERO_OID
247+
exclude_per_dir .gitignore
248+
flags 00000000
249+
/ $ZERO_OID recurse valid
250+
three
251+
/done/ $ZERO_OID recurse valid
252+
/dthree/ $ZERO_OID recurse valid
253+
three
254+
/dtwo/ $ZERO_OID recurse valid
255+
two
256+
EOF
257+
258+
test_expect_success 'if -uall was configured, untracked cache is populated' '
259+
test-tool dump-untracked-cache >../actual &&
260+
test_cmp ../dump_uall.expect ../actual
261+
'
262+
263+
test_expect_success 'if -uall is configured, untracked cache is used by default' '
264+
test_config status.showuntrackedfiles all &&
265+
: >../trace.output &&
266+
GIT_TRACE2_PERF="$TRASH_DIRECTORY/trace.output" \
267+
git status --porcelain >../actual &&
268+
iuc status --porcelain >../status.iuc &&
269+
test_cmp ../status_uall.expect ../status.iuc &&
270+
test_cmp ../status_uall.expect ../actual &&
271+
get_relevant_traces ../trace.output ../trace.relevant &&
272+
cat >../trace.expect <<EOF &&
273+
....path:
274+
....node-creation:0
275+
....gitignore-invalidation:0
276+
....directory-invalidation:0
277+
....opendir:0
278+
EOF
279+
test_cmp ../trace.expect ../trace.relevant
280+
'
281+
282+
# Bypassing the untracked cache here is not desirable from an
283+
# end-user perspective, but is expected in the current design.
284+
# The untracked cache data stored for a -all run cannot be
285+
# correctly used in a -unormal run - it would yield incorrect
286+
# output.
287+
test_expect_success 'if -uall is configured, untracked cache is bypassed with -unormal' '
288+
test_config status.showuntrackedfiles all &&
289+
: >../trace.output &&
290+
GIT_TRACE2_PERF="$TRASH_DIRECTORY/trace.output" \
291+
git status -unormal --porcelain >../actual &&
292+
iuc status -unormal --porcelain >../status.iuc &&
293+
test_cmp ../status.expect ../status.iuc &&
294+
test_cmp ../status.expect ../actual &&
295+
get_relevant_traces ../trace.output ../trace.relevant &&
296+
cat >../trace.expect <<EOF &&
297+
....path:
298+
EOF
299+
test_cmp ../trace.expect ../trace.relevant
300+
'
301+
302+
test_expect_success 'repopulate untracked cache for -unormal' '
303+
git status --porcelain
304+
'
305+
225306
test_expect_success 'modify in root directory, one dir invalidation' '
226307
: >four &&
227308
test-tool chmtime =-240 four &&

0 commit comments

Comments
 (0)