Replace git check-ignore with pure-Go gitignore matcher#597
Replace git check-ignore with pure-Go gitignore matcher#597
Conversation
db024b9 to
1a0dc93
Compare
…w session fix Amp-Thread-ID: https://ampcode.com/threads/T-019d730c-8f25-702a-bee1-caedc872c97d Co-authored-by: Amp <amp@ampcode.com>
1a0dc93 to
a1bd47b
Compare
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: a1bd47bd88
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| saveTimer = time.AfterFunc(5*time.Second, func() { | ||
| saveMu.Lock() | ||
| defer saveMu.Unlock() | ||
| if err := act.Save(activityPath); err != nil { |
There was a problem hiding this comment.
Serialize debounced activity saves
The debounce AfterFunc now calls act.Save without taking saveMu, while shutdown still performs a locked save, so two saves can run concurrently against the same activityPath. Because Tracker.Save writes to a fixed temp file (activity.json.tmp) before rename, concurrent writers can race and produce rename errors (no such file or directory), which can drop the final persisted activity state during shutdown or heavy event bursts. Please reintroduce save serialization for the timer callback (or otherwise make saves mutually exclusive).
Useful? React with 👍 / 👎.
| if m, ok := c.matcherCache[projectPath]; ok { | ||
| c.mu.RUnlock() | ||
| return m |
There was a problem hiding this comment.
Invalidate cached matcher when ignore rules change
This cache path reuses a single parsed gitignore.Matcher for all subsequent UpsertFile events, but the matcher is only rebuilt on RescanWith; edits to .gitignore/.git/info/exclude during normal runtime are not observed. As a result, incremental updates can keep including files that were newly ignored (or excluding files that were unignored) until a full rescan/restart, which is a behavioral regression from the previous per-call git check-ignore checks.
Useful? React with 👍 / 👎.
Problem
Penpal spawns
git check-ignoresubprocesses on hot paths:scanProjectSources): a persistentgit check-ignore --stdinsubprocess per project scanResolveFileInfo): N one-shotgit check-ignore -qspawns per ancestor directory — called fromUpsertFileon every.mdfile-watcher eventFor a file 5 levels deep, that's 5 process spawns per file change.
Solution
New
internal/gitignorepackage — a pure-Go gitignore matcher that parses.gitignorefiles,.git/info/exclude, and the global gitignore once, then answersIsIgnoredDir()queries in-process with caching.Subprocess cost: from 1 persistent + N per-file → 0 (except a single
sync.Once-cachedgit config core.excludesFilefor global gitignore path).Changes
internal/gitignore/*,**,?,[abc], negation, anchored, directory-only, escaped trailing spaces), hierarchical.gitignoreloading with precedence, directory result caching.internal/cache/cache.gogitIgnoreCheckerandisAncestorDirGitIgnoredwithgitignore.Matcher. AddedmatcherCacheonCachesoResolveFileInforeuses matchers across file events.cmd/penpal-server/main.gosaveMulock from debounceAfterFunccallback —Record()no longer blocks on activity disk I/O.frontend/src-tauri/src/lib.rsDestroyedhandler saves session before removing window (was only saving on last window, losing multi-window sessions). Extractedensure_geo_entry()helper.frontend/src/hooks/useTabs.tsloadPersistedTabscalled once, shared by bothuseStateinitializers.frontend/src/hooks/useTabs.test.tsinternal/gitignore/gitignore_test.go**, nested.gitignore, anchored,.git/info/exclude, caching, no-trailing-slash, escaped spaces.internal/cache/cache_test.goTestResolveFileInfo_SkipsGitignored. Updated existing tests for new signature.ERD.md,TESTING.mdTesting
go test ./...— all 14 Go packages passnpx vitest run— all frontend tests passanchors check .— 0 warnings, 0 errors