Summary
While tgrep serve is running, the file watcher does not notice changes to .ignore files. As a result the watcher keeps applying the ignore rules it loaded at startup and happily indexes files and folders that a newly added/edited .ignore says to exclude — so the live overlay ends up containing content that a fresh tgrep index over the same tree would never index.
Root cause
tgrep-cli/src/serve.rs:
fn is_ignore_rules_file(root: &Path, path: &Path) -> bool {
path.file_name().and_then(|name| name.to_str()) == Some(".gitignore")
|| path == root.join(tgrep_core::gitignore::P4IGNORE_FILENAME)
}
handle_fs_event uses this predicate to decide whether an event should set ignore_rules_dirty and schedule schedule_ignore_rules_refresh, which is what rebuilds and republishes ServerState::gitignore.
The predicate covers .gitignore (at any depth) and root-level p4ignore.ini, but it omits .ignore, even though .ignore is a first-class ignore source everywhere else in the codebase:
walker::walk_dir collects ignore_files separately from gitignore_files.
gitignore::matcher_from_ignore_paths_with_options feeds both into the matcher, and .ignore even outranks .gitignore.
- Unlike
.gitignore, .ignore is not git-gated — it applies outside a repo too, which is exactly the case covered by tgrep-cli/tests/watcher_dot_ignore.rs.
Because the event never matches, the .ignore write also falls through to the normal reindex path where should_skip_watcher_path drops it (any dot-prefixed segment is skipped), so nothing at all happens.
Impact
- Create or edit
.ignore while the server is up → files under the newly ignored directory keep getting indexed and remain searchable.
- Delete or relax
.ignore → the stale matcher keeps excluding paths that should now be indexed, until the next full stale check or restart.
- In-memory overlay and on-disk index diverge, which is the exact class of drift
should_skip_watcher_path exists to prevent.
.gitignore and root p4ignore.ini do not have this problem; only .ignore is missed.
Reproduction
tgrep index <root> --index-path <idx> on a tree with no .git directory.
tgrep serve --index-path <idx> <root>, wait for the watcher to come up.
echo "secret/" > <root>/.ignore
mkdir <root>/secret && echo "leak_marker" > <root>/secret/creds.txt
- Search
leak_marker → it matches. Expected: no matches, because secret/ is excluded by .ignore.
The existing tgrep-cli/tests/watcher_dot_ignore.rs passes only because its .ignore exists before the initial index/walk, so the startup matcher already contains the rule.
Expected behavior
A create/modify/remove event for a .ignore file should be treated exactly like a .gitignore event: mark ignore_rules_dirty and schedule the background stale refresh so the matcher is rebuilt and republished.
Proposed fix
Extend is_ignore_rules_file to recognize .ignore alongside .gitignore (matching by file name at any depth, since nested .ignore files are honored by the matcher), keeping p4ignore.ini root-scoped as it is today. Add unit coverage in serve.rs plus an end-to-end test that writes .ignore after the server is live.
Summary
While
tgrep serveis running, the file watcher does not notice changes to.ignorefiles. As a result the watcher keeps applying the ignore rules it loaded at startup and happily indexes files and folders that a newly added/edited.ignoresays to exclude — so the live overlay ends up containing content that a freshtgrep indexover the same tree would never index.Root cause
tgrep-cli/src/serve.rs:handle_fs_eventuses this predicate to decide whether an event should setignore_rules_dirtyand scheduleschedule_ignore_rules_refresh, which is what rebuilds and republishesServerState::gitignore.The predicate covers
.gitignore(at any depth) and root-levelp4ignore.ini, but it omits.ignore, even though.ignoreis a first-class ignore source everywhere else in the codebase:walker::walk_dircollectsignore_filesseparately fromgitignore_files.gitignore::matcher_from_ignore_paths_with_optionsfeeds both into the matcher, and.ignoreeven outranks.gitignore..gitignore,.ignoreis not git-gated — it applies outside a repo too, which is exactly the case covered bytgrep-cli/tests/watcher_dot_ignore.rs.Because the event never matches, the
.ignorewrite also falls through to the normal reindex path whereshould_skip_watcher_pathdrops it (any dot-prefixed segment is skipped), so nothing at all happens.Impact
.ignorewhile the server is up → files under the newly ignored directory keep getting indexed and remain searchable..ignore→ the stale matcher keeps excluding paths that should now be indexed, until the next full stale check or restart.should_skip_watcher_pathexists to prevent..gitignoreand rootp4ignore.inido not have this problem; only.ignoreis missed.Reproduction
tgrep index <root> --index-path <idx>on a tree with no.gitdirectory.tgrep serve --index-path <idx> <root>, wait for the watcher to come up.echo "secret/" > <root>/.ignoremkdir <root>/secret && echo "leak_marker" > <root>/secret/creds.txtleak_marker→ it matches. Expected: no matches, becausesecret/is excluded by.ignore.The existing
tgrep-cli/tests/watcher_dot_ignore.rspasses only because its.ignoreexists before the initial index/walk, so the startup matcher already contains the rule.Expected behavior
A create/modify/remove event for a
.ignorefile should be treated exactly like a.gitignoreevent: markignore_rules_dirtyand schedule the background stale refresh so the matcher is rebuilt and republished.Proposed fix
Extend
is_ignore_rules_fileto recognize.ignorealongside.gitignore(matching by file name at any depth, since nested.ignorefiles are honored by the matcher), keepingp4ignore.iniroot-scoped as it is today. Add unit coverage inserve.rsplus an end-to-end test that writes.ignoreafter the server is live.