A git worktree nested in a project (e.g. Claude Code's gitignored `.claude/worktrees/<name>/`) was swept into the index as an embedded repo: its `.git` is a FILE pointing into the host repo's `.git/worktrees/`, and embedded- repo discovery treated any `.git` (file or directory) as a distinct repo to index. Each worktree then duplicated the entire graph — one report went from ~1,850 files to 24,533, with search/explore flooded by stale copies. classifyGitDir() now distinguishes: - `.git` directory -> embedded clone, index (#193/#514/#622, unchanged) - `.git` file → worktrees/ -> worktree, skip (#848) - `.git` file → modules/ -> submodule, index (unchanged) Applied at both embedded-repo entry points: findNestedGitRepos discovery (which also covers the sync/change-detection path) and the untracked-subdir recursion in collectGitFiles. Verified: the reproduction drops from 6 files / betaHelper×3 to 3 files / ×1, with a genuine embedded clone and submodules still indexed. Regression test added. Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
64ff7597d0
commit
5cc155ddc4
@@ -108,6 +108,29 @@ describe('multi-repo workspaces (#514)', () => {
|
||||
expect(files).toContain('main.ts');
|
||||
});
|
||||
|
||||
it('skips nested git worktrees instead of indexing them as duplicate embedded repos (#848)', () => {
|
||||
// Claude Code (and others) create worktrees under a gitignored path like
|
||||
// `.claude/worktrees/<name>/`. A worktree's `.git` is a FILE pointing into
|
||||
// the host repo's own `.git/worktrees/`, so it is the SAME repo already
|
||||
// indexed — sweeping it in as an embedded repo multiplies the whole graph.
|
||||
// A genuine embedded clone (a `.git` *directory*) must still be indexed.
|
||||
write(path.join(ws, 'src/app.ts'), 'export function app() { return 1; }\n');
|
||||
write(path.join(ws, '.gitignore'), '.claude/\nvendored/\n');
|
||||
makeRepo(ws);
|
||||
// A real linked worktree under the gitignored .claude/worktrees/.
|
||||
git(ws, 'worktree', 'add', '-q', '.claude/worktrees/feature', '-b', 'feature');
|
||||
// A genuine embedded clone, also gitignored — must STAY indexed (#514).
|
||||
write(path.join(ws, 'vendored/lib.ts'), 'export function vendoredFn() { return 9; }\n');
|
||||
makeRepo(path.join(ws, 'vendored'));
|
||||
|
||||
const files = scanDirectory(ws);
|
||||
expect(files).toContain('src/app.ts');
|
||||
// The worktree is a duplicate working view — never indexed.
|
||||
expect(files.some((f) => f.includes('.claude/worktrees'))).toBe(false);
|
||||
// The genuine embedded clone is still indexed (#514/#622 preserved).
|
||||
expect(files).toContain('vendored/lib.ts');
|
||||
});
|
||||
|
||||
it('non-git workspace: walks children and respects each child own .gitignore', () => {
|
||||
write(path.join(ws, 'proj-a/src/auth.ts'), 'export function login() {}\n');
|
||||
write(path.join(ws, 'proj-a/build/out.ts'), 'export function generated() {}\n');
|
||||
|
||||
Reference in New Issue
Block a user