fix(extraction): skip submodule worktrees instead of indexing them as duplicates (#945) (#947)

A worktree of a submodule points its `.git` into
`.git/modules/<module>/worktrees/<name>`, but `classifyGitDir` only matched
the top-level `.git/worktrees/` shape — so submodule worktrees fell through
to "embedded" and every symbol they shared with the real submodule checkout
got indexed twice (one report: ~28% of the index was duplicates, inflating
both query results and the DB). Broaden the worktree detector to allow the
optional `modules/<module>` segment. The submodule's own checkout
(`.git/modules/<module>`, no `worktrees/`) is unaffected and stays indexed as
distinct code.

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Colby Mchenry
2026-06-22 08:44:06 -05:00
committed by GitHub
co-authored by Claude Opus 4.8
parent 03666584ed
commit 2bdc169ce4
3 changed files with 46 additions and 4 deletions
+36
View File
@@ -131,6 +131,42 @@ describe('multi-repo workspaces (#514)', () => {
expect(files).toContain('vendored/lib.ts');
});
it('skips a submodule worktree instead of indexing it as a duplicate (#945)', () => {
// A worktree OF A SUBMODULE points its `.git` into
// `.git/modules/<module>/worktrees/<name>` — not the top-level repo's
// `.git/worktrees/`. The detector used to miss that extra `modules/<name>`
// segment, so the worktree fell through to "embedded" and every symbol it
// shared with the real submodule checkout got indexed twice. The submodule's
// own checkout (`.git/modules/<module>`, no `worktrees/`) is distinct code
// and must stay indexed (#514).
const upstream = fs.mkdtempSync(path.join(os.tmpdir(), 'cg-945-up-'));
try {
// The repo that becomes the submodule's origin.
write(path.join(upstream, 'lib.ts'), 'export function libFn() { return 1; }\n');
makeRepo(upstream);
write(path.join(ws, 'src/app.ts'), 'export function app() { return 1; }\n');
write(path.join(ws, '.gitignore'), '.worktrees/\n');
git(ws, 'init', '-q');
// protocol.file.allow=always: modern git refuses a local-path submodule otherwise.
git(ws, '-c', 'protocol.file.allow=always', 'submodule', 'add', '-q', upstream, 'common');
git(ws, '-c', 'user.email=t@t', '-c', 'user.name=t', 'commit', '-qm', 'add submodule');
// A worktree of the submodule, under the gitignored .worktrees/ — its `.git`
// points into `.git/modules/common/worktrees/<name>`.
git(path.join(ws, 'common'), 'worktree', 'add', '-q', '../.worktrees/common-feature', '-b', 'feature');
const files = scanDirectory(ws);
expect(files).toContain('src/app.ts');
// The real submodule checkout is distinct code — still indexed (#514).
expect(files).toContain('common/lib.ts');
// The submodule worktree is a duplicate working view — never indexed (#945).
expect(files.some((f) => f.includes('.worktrees'))).toBe(false);
} finally {
fs.rmSync(upstream, { recursive: true, force: true });
}
});
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');