From d1dc492472ca3cdd29638f1921647afa4b33409e Mon Sep 17 00:00:00 2001 From: benshi <807629978@qq.com> Date: Tue, 25 Aug 2026 03:02:32 +0000 Subject: [PATCH] fix(watcher): stop endless refresh inside a linked worktree MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Narrow the shared-gitdir watcher from `worktrees/**` to `worktrees/*/{HEAD,gitdir,locked}`: the broad glob also matched every worktree's private runtime state, and for a linked worktree that state IS our own gitdir. Every refresh runs `git status`, which takes `index.lock`, which classified as 'unknown' and forced another full refresh, which ran `git status` again — observed on a large monorepo worktree as a lock create/delete every ~1.5s (debounce + cooldown) that never stopped. Since any non-'status' change invalidates the history search, an in-flight search restarted from scratch each round and never returned a result. The three entries `git worktree list` actually reports are the only ones worth watching; from a main repo this also stops a sibling worktree's routine git activity from forcing a full refresh here. - Keep the pattern deliberately "complex": per the VS Code API contract a RelativePattern containing path segments still creates a recursive watcher, so this only tightens the filter — it does not downgrade to a non-recursive watcher that would miss `worktrees//HEAD` entirely. - Pin the registered patterns in a test: the failure mode is silent (no error, just a UI that never settles), so a regression would otherwise only surface as a bug report. Signed-off-by: benshi <807629978@qq.com> --- src/services/__tests__/file-watcher.test.ts | 11 +++++++++++ src/services/file-watcher.ts | 10 +++++++++- 2 files changed, 20 insertions(+), 1 deletion(-) diff --git a/src/services/__tests__/file-watcher.test.ts b/src/services/__tests__/file-watcher.test.ts index 10e8679..ac7fa02 100644 --- a/src/services/__tests__/file-watcher.test.ts +++ b/src/services/__tests__/file-watcher.test.ts @@ -91,6 +91,17 @@ describe('FileWatcher debounce / cooldown / suppress state machine', () => { expect(onChange).not.toHaveBeenCalled(); }); + it('watches the worktree registry, not each worktree\'s private state', () => { + // `worktrees/**` also matched every worktree's runtime files (index.lock, + // FETCH_HEAD, COMMIT_EDITMSG, logs/**). In a linked worktree that set is + // our OWN gitdir, so the panel's git commands fed its own watcher — an + // endless refresh that restarted any in-flight history search. Only the + // registry entries `worktree list` actually reports are worth watching. + const patterns = h.watchers.map(w => w.pattern.pattern); + expect(patterns).toContain('worktrees/*/{HEAD,gitdir,locked}'); + expect(patterns).not.toContain('worktrees/**'); + }); + it('does not fire when disabled', () => { fw.enabled = false; fireOn('**', `${REPO}/src/a.ts`); diff --git a/src/services/file-watcher.ts b/src/services/file-watcher.ts index 329e0b5..341ab3a 100644 --- a/src/services/file-watcher.ts +++ b/src/services/file-watcher.ts @@ -36,7 +36,15 @@ export class FileWatcher implements vscode.Disposable { this.addWatcher(new vscode.RelativePattern(this.commonDir, 'refs/stash')); this.addWatcher(new vscode.RelativePattern(this.commonDir, 'packed-refs')); this.addWatcher(new vscode.RelativePattern(this.commonDir, 'config')); - this.addWatcher(new vscode.RelativePattern(this.commonDir, 'worktrees/**')); + // Only the registry entries `git worktree list` reports: the admin dir + // appearing/disappearing (gitdir), where each worktree points (HEAD), and + // its lock state. NOT `worktrees/**` — that also matched every worktree's + // private runtime state (index, index.lock, FETCH_HEAD, COMMIT_EDITMSG, + // logs/**). For a linked worktree that state IS our own gitdir, so the + // panel's own `git status` retriggered its own watcher: a refresh loop + // that also restarted any in-flight history search. For a main repo it + // meant every git command in a sibling worktree forced a full refresh. + this.addWatcher(new vscode.RelativePattern(this.commonDir, 'worktrees/*/{HEAD,gitdir,locked}')); // Watch working tree for file changes (exclude heavy dirs via specific patterns) // Using {src,lib,app,...}/** would be too restrictive, so we watch ** but filter