Using fsmonitor and the untracked cache Jump to heading
git status has to answer two questions: which tracked files changed, and which untracked files exist. Without help it answers both by examining the whole working tree β a stat call for every tracked file and a scan of every directory for untracked ones. In a tree with hundreds of thousands of files that takes seconds, and every editor, prompt and Git command that checks status pays it. The built-in filesystem monitor daemon (core.fsmonitor) listens for change events from the operating system, so Git examines only paths that changed since last time. The untracked cache remembers directory contents and skips directories whose modification time has not changed. Together they turn status from proportional to tree size into proportional to what changed. This page enables them, checks they are working, and covers the situations where they should be off, within large repository performance.
When to use this approach Jump to heading
git statustakes more than a second in your working tree.- Shell prompts or editors showing Git status feel sluggish.
- The tree has hundreds of thousands of files, as large monorepos do.
- You have already reduced the tree with sparse checkout for large monorepos and status is still slow.
Step 1 β Measure status and find the slow phase Jump to heading
Trace status to see whether time goes to checking tracked files (refresh) or scanning for untracked ones.
time git status >/dev/null
GIT_TRACE2_PERF=1 git status 2>&1 >/dev/null | grep -E 'refresh|untracked|read_directory' | awk -F'|' '{print $(NF-1), $NF}' | tail -n 8 Large βrefreshβ time is what fsmonitor removes; large βread_directoryβ or untracked time is what the untracked cache removes. Often both matter.
Step 2 β Enable the built-in fsmonitor daemon Jump to heading
Git ships a filesystem monitor daemon on macOS and Windows; on Linux, recent Git versions support it as well. Enable it per repository.
git config core.fsmonitor true
git config core.untrackedCache true
git fsmonitor--daemon status # starts automatically on the next command
git status >/dev/null # first run primes the daemon
time git status >/dev/null # subsequent runs use it On platforms where the built-in daemon is unavailable, core.fsmonitor can name a hook script that queries an external watcher such as Watchman; the effect is the same.
Step 3 β Verify both are actually in use Jump to heading
Configuration being set does not prove the feature works β an unsupported filesystem silently disables it. Check directly.
git fsmonitor--daemon status # "fsmonitor-daemon is watching '/path/to/repo'"
git update-index --test-untracked-cache # checks the filesystem supports it
git update-index --untracked-cache # turns it on in the index
GIT_TRACE_FSMONITOR=1 git status 2>&1 >/dev/null | head -n 5 Step 4 β Handle network drives, containers and VMs Jump to heading
Filesystem events are unreliable or absent on network filesystems, some container bind mounts and shared folders in virtual machines. The daemon refuses to watch remote filesystems by default, which is the right choice; keep large repositories on local disks.
git config fsmonitor.allowRemote false # the default; do not override without testing carefully
df -T . | tail -n 1 # check the filesystem type of the working tree Step 5 β Leave CI alone Jump to heading
A CI job clones, builds and exits. The daemon would start, scan everything once and be thrown away, and the untracked cache has nothing to reuse. Leave both disabled in CI, and make sure a global configuration meant for developers does not enable them there.
# In CI, override anything inherited from an image's global config
git config --global core.fsmonitor false
git config --global core.untrackedCache false Step 6 β Roll out to the team with includeIf Jump to heading
Enable the features for large repositories only, using conditional includes keyed on the repository location, so small repositories do not run a daemon each.
# ~/.gitconfig
[includeIf "gitdir:~/src/monorepo/"]
path = ~/.config/git/large-repo.gitconfig # ~/.config/git/large-repo.gitconfig
[core]
fsmonitor = true
untrackedCache = true
[feature]
manyFiles = true feature.manyFiles also enables a newer index format suited to large trees. The pattern is covered in shipping a team gitconfig with includeIf.
Step 7 β Troubleshoot a daemon that seems stuck Jump to heading
If status suddenly misses a change or reports stale results, restart the daemon and rebuild the cache. Both are safe; the worst outcome is one slow status.
git fsmonitor--daemon stop
git update-index --no-untracked-cache && git update-index --untracked-cache
git status Validation checklist Jump to heading
Frequently Asked Questions Jump to heading
Does fsmonitor change what status reports? Jump to heading
No. It changes how Git finds candidate changes, not the answer. If results ever differ, restarting the daemon fixes it, and that is a bug worth reporting.
How much memory does the daemon use? Jump to heading
Proportional to the number of directories watched, typically modest. One daemon runs per repository, which is why enabling it only for large repositories is sensible.
Does the untracked cache help if I have few untracked files? Jump to heading
Yes. The cost it removes is reading directories to look for untracked files, which happens whether or not any are found.
Related Jump to heading
- Large Repository Performance β the parent topic.
- Diagnosing a Slow git status β finding which cause applies.
- Using Scalar for Very Large Repositories β enables these settings automatically.
- Sparse Checkout for Large Monorepos β shrinking the tree first.