Skip to content

fix: match git status paths literally instead of as pathspecs (#921) - #922

Open
gustav-fff wants to merge 1 commit into
mainfrom
triage-bot/issue-921
Open

gustav-fff wants to merge 1 commit into
mainfrom
triage-bot/issue-921

Conversation

@gustav-fff

@gustav-fff gustav-fff commented Oct 14, 2026 •

Copy link
Copy Markdown
Collaborator

Closes #921

Root cause

GitStatusCache::git_status_for_paths (crates/fff-core/src/git.rs:141) adds every changed file as a libgit2 wildmatch pathspec. libgit2 then calls git_pathspec__match for every index/workdir entry against every pathspec, which is O(entries × paths). The trigger: FSEvents sends Rescan for the base dir during an ignored-only write burst (pnpm install). The full rescan is throttled, so the base dir lands in new_dirs_to_watch (crates/fff-core/src/watcher/background_watcher.rs:492). index_new_directory re-walks the tree and enqueues all 80k files (:899), and the status pass then runs 80k × 80k wildmatches.

Fix

status_options.disable_pathspec_match(true). Callers always pass exact file paths, so libgit2 can use its sorted pathlist instead (bsearch and merge walk in the iterators, with directory pruning). Side effect: filenames containing glob characters (app/[id]/page.tsx) no longer match unrelated siblings (app/i/page.tsx).

Not changed: a throttled base-dir Rescan still re-walks the whole tree via index_new_directory (fall-through added in #751). That cost is now bounded (~0.25 s walk + ~0.6 s status per event) and stops when events stop. @dmtrKovalenko, your call whether a base-dir Rescan should skip the fall-through.

Steps to reproduce

macOS (FSEvents). Run on pre-fix main:

cargo build --profile prof -p fff-mcp
mkdir -p /tmp/r/main && cd /tmp/r/main && git init -q && printf 'node_modules\n' > .gitignore
python3 -c "import os
for p in range(400):
  for d in range(10):
    b=f'packages/pkg-{p}/src/mod{d}'; os.makedirs(b,exist_ok=True)
    [open(f'{b}/F{f}.ts','w').write('x\n') for f in range(20)]"
git add -A && git commit -qm seed && git worktree add -q -b probe /tmp/r/wt HEAD
(sleep 3600 | $FFF/target/prof/fff-mcp /tmp/r/wt --log-level debug --log-file /tmp/r/fff.log --no-update-check) &
sleep 5
python3 -c "import os
for i in range(600):
  b=f'/tmp/r/wt/node_modules/.pnpm/d{i}/lib'; os.makedirs(b,exist_ok=True)
  [open(f'{b}/f{f}.js','w').write('1\n') for f in range(150)]"
PID=$(pgrep -f 'fff-mcp /tmp/r/wt'); for i in 1 2 3 4 5; do ps -o %cpu= -p $PID; sleep 3; done
grep -a 'Indexed new' /tmp/r/fff.log
sample $PID 3

Expected: CPU returns to ~0 shortly after the burst.
Actual (pre-fix): Indexed new 80401 files from new directory /private/tmp/r/wt/. CPU stays at 99.6-99.8% and sample shows fff-git-status with 1843/1843 samples in update_git_status_for_paths -> read_status -> git_status_list_new -> git_diff__from_iterators -> git_pathspec__match -> dowild. The git_status_for_paths{paths_count=80401} span had not closed when I killed the process ~35 s later.

How verified

  • Same repro with the fix: git_status_for_paths{paths_count=80401}:read_status close time.busy=539ms..622ms. CPU went 128 -> 149 -> 141 -> 38 -> 0.0% and stayed at 0.0%.
  • New test git::tests::status_for_paths_treats_paths_literally covers a literal [id] path, WT_DELETED, and WT_NEW in an untracked nested dir. It fails without the fix (app/i/page.tsx glob-matched) and passes with it.
  • make test-rust: all pass (fff-search lib 217 passed). make lint-rust: clean. cargo fmt --check: clean.

Automated triage via Gustav. Honk-Honk 🪿

Summary by CodeRabbit

  • Bug Fixes
    • Path lookups now treat wildcard-like characters as literal parts of the requested path, preventing similarly named files from being included unexpectedly. This improves accuracy when checking multiple paths, including paths containing brackets, and helps ensure deleted or untracked files are reported correctly.

git_status_for_paths fed every changed file to libgit2 as a wildmatch
pathspec, making status O(entries * paths). A throttled FSEvents Rescan
on the base dir re-indexes the whole tree and enqueues all files, which
pinned fff-git-status for minutes on an 80k-file repo. Disabling
pathspec matching switches libgit2 to its sorted pathlist filter.

Closes #921

@greptile-apps greptile-apps Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Your trial has ended. Reactivate Greptile to resume code reviews.

@coderabbitai

coderabbitai Bot commented Oct 14, 2026 •

Copy link
Copy Markdown

Review in Change Stack →

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration
  • Configuration used: Repository UI
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: cb022f0c-fb8c-48d1-aad6-3867051e82f4
📥 Commits

Reviewing files that changed from the base of the PR and between 4b17d9b and bc45b9b.

📒 Files selected for processing: 1
  • crates/fff-core/src/git.rs

Included review availability: This review used your included allowance. Your plan provides up to 10 included reviews per hour; 9 remain after this review.

📝 Walkthrough

Multi-path GitStatusCache::git_status_for_paths queries now treat paths literally instead of as wildcard patterns. A test covers bracketed, deleted, and untracked paths, and checks that a similar path is not included.

Changes: Git status path matching

Layer / File(s) Summary
Literal path matching and validation
crates/fff-core/src/git.rs
Multi-path queries disable pathspec pattern matching. A test checks the requested paths’ statuses, confirms a similar path is absent, and verifies the cache has three entries.

Priority: ➖ Normal

Estimated code review effort: 2 (Simple) | ~10 minutes

Change: Bug fix

Suggested reviewers: dmtrkovalenko

Merge Risk: ⚪ Minimal · up to bc45b

The literal-path change is ready to merge after normal checks; no actionable risk remains.

Pre-merge checks: Passed 5
Passed Passed checks: 5 passed
Check name Status Explanation
Linked Issues check Passed The change satisfies the coding requirement in #921 to bound the status work. git_status_for_paths now calls disable_pathspec_match(true), so exact paths use libgit2 path-list processing instead o…
Out of Scope Changes check Passed The changes stay within #921. They modify the affected Git status query and add a focused regression test for literal path behavior. No unrelated production behavior or unrelated files are changed.
Docstring Coverage Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 3 functions across 1 files.
Description Check Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check Passed The title clearly and concisely describes the main change: literal Git status path matching instead of pathspec matching.
✨ Finishing Touches
📝 Generate docstrings
  • Commit to this branch
  • Create a new PR
🧪 Generate unit tests (beta)
  • Commit to this branch
  • Create a new PR
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

fff-git-status pins one core for minutes after a large install in a linked git worktree (macOS, v0.11.0)

1 participant