How to Speed Up `git status` in a Large Monorepo

A practical guide to making git status return in milliseconds, not seconds — covering fsmonitor v2, untrackedCache, partial clones, and when each technique actually helps.

  • #git
  • #performance
  • #monorepo
  • #fsmonitor

If you’ve ever typed git status in a 200,000-file monorepo and watched the cursor blink for eight seconds, you know the problem. Git is not slow — your filesystem is slow, and Git is honest about it. Every call walks the working tree, stats every file, and compares it to the index. At scale, that walk dominates the wall-clock time.

The good news: Git has had the hooks to fix this for years. The bad news: turning them on is fiddly, and most teams never do. This guide shows you the levers that actually matter, in the order they pay off.

1. The single biggest lever: fsmonitor

fsmonitor is a Git feature that lets a background process answer the question “which files have changed since the last commit?” instead of forcing Git to scan the whole tree itself. When the helper says “only these three files”, Git only scans those three.

# With gity (one command):
gity register ~/work/monorepo

# Or, with native Git + a helper of your choice:
git config core.fsmonitor /path/to/helper
git config core.untrackedCache true
git update-index --fsmonitor

On a typical 250k-file monorepo, this brings a warm git status from 1.4 seconds to under 30 milliseconds. That’s roughly a 50× speedup with no behaviour change.

2. Always enable untrackedCache

git config core.untrackedCache true caches the list of untracked files between status calls. Without it, Git rebuilds the untracked-file list from scratch every single time, even when nothing has changed. It works on every platform and there is no reason to leave it off.

The cache works by recording the stat() of each directory; when a directory’s mtime hasn’t changed, Git trusts that its children haven’t changed either. This is especially powerful in monorepos with a lot of node_modules, target, or dist directories that change rarely.

3. Tune feature.manyFiles and index.version

git config feature.manyFiles true

This single flag enables a bundle of optimizations: core.untrackedCache=true, index.version=4 (a more compact index format), and index.skipHash=true (skips an integrity hash that is rarely useful in practice).

index.version=4 reduces the on-disk index by 30–50% in large repos. Smaller index = faster mmap() = faster everything that touches the index.

4. Sparse checkout (when applicable)

If your team works on a logical subset of the monorepo — say, one app out of thirty — you can use sparse checkout to only materialize the directories you care about. The other directories stay on the server.

git sparse-checkout init --cone
git sparse-checkout set apps/payments libs/shared

Sparse checkout doesn’t help git status per se; it makes the working tree itself smaller, so the status walk has fewer files to consider. For developers who only touch a small slice of the repo, this is often the difference between “fast” and “instant.”

5. Background maintenance

Once git status is fast, the next bottleneck is usually git fetch and git checkout against repos with millions of objects. git maintenance start schedules background prefetch, repack, and commit-graph updates so your repo stays compact.

git maintenance start

With gity, this is folded into the daemon — registered repos get prefetch and maintenance for free, scheduled when your machine is idle and throttled when it’s not.

6. Per-OS quirks worth knowing

  • macOS: APFS’s snapshotting means stat() calls have higher per-call overhead than people expect. fsmonitor helps disproportionately on macOS for this reason.
  • Linux: ext4 and xfs are very fast for stat(), so the absolute baseline is faster — but a 250k-file walk still costs 800ms+, and fsmonitor still wins.
  • Windows: NTFS is the slowest of the three for many-small-file workloads. fsmonitor (via ReadDirectoryChangesW) is essentially required for any large repo on Windows.

Putting it all together

For a 250k-file monorepo, our usual recommendation is:

  1. gity register <path> (or set up fsmonitor manually with your helper of choice)
  2. git config feature.manyFiles true
  3. git maintenance start (or let gity handle it)

That’s it. With those three commands, git status typically drops from multiple seconds to single-digit milliseconds, IDE polling stops eating CPU, and CI runs that involve repeated git status calls get measurably faster.

If you want the deeper picture, our article on the fsmonitor protocol covers exactly what gets exchanged on the wire and why it’s so fast.

Try it now

cargo install gity
gity register .
git status   # was seconds, now milliseconds

If you maintain a monorepo over 100k files and your team has lived with slow git status for a while, this is the single highest-leverage change you can ship today.

Frequently asked questions

Why is `git status` slow in my large repo?

By default, `git status` walks the entire working tree to compare on-disk state against the index. In a repository with hundreds of thousands of files, that walk takes seconds — most of the time is filesystem traversal, not Git logic itself.

What is the single biggest speed win for `git status`?

Enabling fsmonitor. A background process tells Git which files changed, so Git skips the full tree walk. With gity, this is one command: `gity register <path>`. With native Git, you set `core.fsmonitor` and supply a helper. Either way, the speedup is typically 10–500x.

Does `untrackedCache` matter?

Yes — `git config core.untrackedCache true` caches the list of untracked files between calls. Combined with fsmonitor it brings cold-start latency down by another 30–60%. It's safe and should always be enabled in any repo over a few thousand files.