unpack-trees: skip stat on fsmonitor-valid files

The index might be aware that a file hasn't modified via fsmonitor, but
unpack-trees did not pay attention to it and checked via ie_match_stat
which can be inefficient on certain filesystems. This significantly slows
down commands that run oneway_merge, like checkout and reset --hard.

This patch makes oneway_merge check whether a file is considered
unchanged through fsmonitor and skips ie_match_stat on it. unpack-trees
also now correctly copies over fsmonitor validity state from the source
index. Finally, for correctness, we force a refresh of fsmonitor state in
tweak_fsmonitor.

After this change, commands like stash (that use reset --hard
internally) go from 8s or more to ~2s on a 250k file repository on a
mac.

Helped-by: Junio C Hamano <gitster@pobox.com>
Helped-by: Kevin Willford <Kevin.Willford@microsoft.com>
Signed-off-by: Utsav Shah <utsav@dropbox.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
This commit is contained in:
Utsav Shah 2019-11-20 08:32:17 +00:00 committed by Junio C Hamano
parent 61eea521fe
commit 679f2f9fdd
3 changed files with 29 additions and 9 deletions

View File

@ -191,13 +191,26 @@ void refresh_fsmonitor(struct index_state *istate)
} }
if (bol < query_result.len) if (bol < query_result.len)
fsmonitor_refresh_callback(istate, buf + bol); fsmonitor_refresh_callback(istate, buf + bol);
/* Now mark the untracked cache for fsmonitor usage */
if (istate->untracked)
istate->untracked->use_fsmonitor = 1;
} else { } else {
/* We only want to run the post index changed hook if we've actually changed entries, so keep track
* if we actually changed entries or not */
int is_cache_changed = 0;
/* Mark all entries invalid */ /* Mark all entries invalid */
for (i = 0; i < istate->cache_nr; i++) for (i = 0; i < istate->cache_nr; i++) {
istate->cache[i]->ce_flags &= ~CE_FSMONITOR_VALID; if (istate->cache[i]->ce_flags & CE_FSMONITOR_VALID) {
is_cache_changed = 1;
istate->cache[i]->ce_flags &= ~CE_FSMONITOR_VALID;
}
}
/* If we're going to check every file, ensure we save the results */ /* If we're going to check every file, ensure we save the results */
istate->cache_changed |= FSMONITOR_CHANGED; if (is_cache_changed)
istate->cache_changed |= FSMONITOR_CHANGED;
if (istate->untracked) if (istate->untracked)
istate->untracked->use_fsmonitor = 0; istate->untracked->use_fsmonitor = 0;
@ -259,9 +272,7 @@ void tweak_fsmonitor(struct index_state *istate)
(uintmax_t)istate->fsmonitor_dirty->bit_size, istate->cache_nr); (uintmax_t)istate->fsmonitor_dirty->bit_size, istate->cache_nr);
ewah_each_bit(istate->fsmonitor_dirty, fsmonitor_ewah_callback, istate); ewah_each_bit(istate->fsmonitor_dirty, fsmonitor_ewah_callback, istate);
/* Now mark the untracked cache for fsmonitor usage */ refresh_fsmonitor(istate);
if (istate->untracked)
istate->untracked->use_fsmonitor = 1;
} }
ewah_free(istate->fsmonitor_dirty); ewah_free(istate->fsmonitor_dirty);

View File

@ -106,6 +106,8 @@ EOF
# test that "update-index --fsmonitor-valid" sets the fsmonitor valid bit # test that "update-index --fsmonitor-valid" sets the fsmonitor valid bit
test_expect_success 'update-index --fsmonitor-valid" sets the fsmonitor valid bit' ' test_expect_success 'update-index --fsmonitor-valid" sets the fsmonitor valid bit' '
write_script .git/hooks/fsmonitor-test<<-\EOF &&
EOF
git update-index --fsmonitor && git update-index --fsmonitor &&
git update-index --fsmonitor-valid dir1/modified && git update-index --fsmonitor-valid dir1/modified &&
git update-index --fsmonitor-valid dir2/modified && git update-index --fsmonitor-valid dir2/modified &&
@ -164,6 +166,8 @@ EOF
# test that newly added files are marked valid # test that newly added files are marked valid
test_expect_success 'newly added files are marked valid' ' test_expect_success 'newly added files are marked valid' '
write_script .git/hooks/fsmonitor-test<<-\EOF &&
EOF
git add new && git add new &&
git add dir1/new && git add dir1/new &&
git add dir2/new && git add dir2/new &&
@ -218,11 +222,12 @@ test_expect_success '*only* files returned by the integration script get flagged
# Ensure commands that call refresh_index() to move the index back in time # Ensure commands that call refresh_index() to move the index back in time
# properly invalidate the fsmonitor cache # properly invalidate the fsmonitor cache
test_expect_success 'refresh_index() invalidates fsmonitor cache' ' test_expect_success 'refresh_index() invalidates fsmonitor cache' '
write_script .git/hooks/fsmonitor-test<<-\EOF &&
EOF
clean_repo && clean_repo &&
dirty_repo && dirty_repo &&
write_integration_script &&
git add . && git add . &&
write_script .git/hooks/fsmonitor-test<<-\EOF &&
EOF
git commit -m "to reset" && git commit -m "to reset" &&
git reset HEAD~1 && git reset HEAD~1 &&
git status >actual && git status >actual &&

View File

@ -1494,6 +1494,9 @@ int unpack_trees(unsigned len, struct tree_desc *t, struct unpack_trees_options
o->merge_size = len; o->merge_size = len;
mark_all_ce_unused(o->src_index); mark_all_ce_unused(o->src_index);
if (o->src_index->fsmonitor_last_update)
o->result.fsmonitor_last_update = o->src_index->fsmonitor_last_update;
/* /*
* Sparse checkout loop #1: set NEW_SKIP_WORKTREE on existing entries * Sparse checkout loop #1: set NEW_SKIP_WORKTREE on existing entries
*/ */
@ -2374,7 +2377,8 @@ int oneway_merge(const struct cache_entry * const *src,
if (old && same(old, a)) { if (old && same(old, a)) {
int update = 0; int update = 0;
if (o->reset && o->update && !ce_uptodate(old) && !ce_skip_worktree(old)) { if (o->reset && o->update && !ce_uptodate(old) && !ce_skip_worktree(old) &&
!(old->ce_flags & CE_FSMONITOR_VALID)) {
struct stat st; struct stat st;
if (lstat(old->name, &st) || if (lstat(old->name, &st) ||
ie_match_stat(o->src_index, old, &st, CE_MATCH_IGNORE_VALID|CE_MATCH_IGNORE_SKIP_WORKTREE)) ie_match_stat(o->src_index, old, &st, CE_MATCH_IGNORE_VALID|CE_MATCH_IGNORE_SKIP_WORKTREE))