FSMonitor Integration¶
Deep dive into how Gity implements Git's fsmonitor protocol.
Overview¶
Git's fsmonitor feature allows an external process to tell Git which files have changed since the last query. This avoids expensive full-tree scans.
Gity implements fsmonitor protocol version 2 (Git 2.37+).
Protocol Details¶
Query Format¶
Git invokes the fsmonitor helper with:
- version — Protocol version (must be
2) - token — Opaque token from the previous response (or empty for first query)
Response Format¶
The helper outputs NUL-separated data:
- new_token — Token for Git to use in the next query
- path1, path2, ... — Relative paths that changed
If nothing changed:
Example Session¶
# First query (no previous token)
$ gity fsmonitor-helper 2 ""
1\0
# Nothing changed since gen=1
$ gity fsmonitor-helper 2 "1"
1\0
# User edits file.rs
$ gity fsmonitor-helper 2 "1"
2\0file.rs\0
# Nothing changed since gen=2
$ gity fsmonitor-helper 2 "2"
2\0
Token Semantics¶
Gity uses a generation counter as the token:
- Each filesystem event increments the generation
- Querying with an old generation returns all paths since then
- Querying with the current generation returns an empty list
This provides consistency: you always see all changes that occurred between tokens.
Working Tree Filtering¶
The file watcher sees all filesystem events, including changes inside .git/:
.git/HEAD— Branch switches.git/index— Staging changes.git/refs/*— New commits, tags
However, Git's fsmonitor contract expects only working tree paths. The .git directory is managed by Git itself.
Gity filters internal paths before responding:
This ensures:
| Event | Reported to Git? |
|---|---|
src/main.rs changed |
Yes |
.git/HEAD changed |
No |
.git/index changed |
No |
submodule/.git/HEAD changed |
No |
submodule/src/lib.rs changed |
Yes |
Branch Switches¶
When you run git checkout <branch>:
- Git updates
.git/HEAD - Git checks out files from the new branch
- Watcher sees
.gitchanges AND working tree changes - Gity filters out
.gitpaths - Git receives only working tree paths
- Git updates its index correctly
This "just works" because only actual working tree changes are reported.
Edge Cases¶
Ignored Files¶
Gity reports all changed paths, including files matching .gitignore:
This is correct: fsmonitor reports filesystem changes, Git applies ignore rules.
Submodules¶
Submodules have their own .git directory. Gity filters appropriately:
submodule/.git/HEAD→ Filtered (internal)submodule/src/lib.rs→ Reported (working tree)
Note
Submodules should be registered separately if you want acceleration within them.
Nested Repositories¶
Non-submodule nested repos (e.g., vendored dependencies):
Gity filters vendor/somelib/.git/* but reports vendor/somelib/src/*.
The nested repo won't get fsmonitor acceleration unless separately registered.
Symlinks¶
- Linux/macOS: Symlink targets are not followed
- Windows: May be resolved depending on filesystem config
Only the symlink itself is reported, not target content.
Rapid Changes¶
Build systems can generate rapid changes:
- OS watcher batches events
- Gity coalesces changes to the same path
- Generation advances once per batch
Case Sensitivity¶
Gity preserves path case as reported by the filesystem. On case-insensitive filesystems (Windows, macOS default), Git handles normalization.
Platform Considerations¶
Linux: inotify Limits¶
Large repos may exceed watch limits:
# Check limit
cat /proc/sys/fs/inotify/max_user_watches
# Increase temporarily
sudo sysctl fs.inotify.max_user_watches=524288
# Increase permanently
echo "fs.inotify.max_user_watches=524288" | sudo tee -a /etc/sysctl.conf
macOS: FSEvents Latency¶
FSEvents has inherent latency (~300ms-1s). Status immediately after saving may use stale cache; the next query will be correct.
Windows: Long Paths¶
Enable long path support if you have deeply nested files:
WSL2¶
File watching only works for repos on the Linux filesystem (~/), not Windows filesystem (/mnt/c/). See WSL2 Guide.
Network Filesystems¶
File notifications are unreliable on NFS, SMB, SSHFS. Consider disabling fsmonitor:
Reconciliation¶
If the daemon was offline, events may have been missed. On startup:
- Compare watcher token with stored token
- If drift detected, mark repo for reconciliation
- Next query triggers full scan
- Synthetic events generated for any differences
- Normal watching resumes
Git Configuration¶
When registered, Gity sets:
| Setting | Purpose |
|---|---|
fsmonitor |
Use Gity for change detection |
untrackedCache |
Cache untracked files (complements fsmonitor) |
manyFiles |
Enable large-repo optimizations |
Debugging¶
Verify fsmonitor is active¶
Test the helper directly¶
View real-time events¶
Check health¶
Performance¶
Typical response times:
| Scenario | Response Time |
|---|---|
| No changes | < 1ms |
| Few files changed | < 5ms |
| Many files changed | < 50ms |
| After reconciliation | 100-500ms (one-time) |
The fsmonitor response is essentially a cache lookup plus IPC round-trip.