From 3015c3ea9a7a7905547457ad6cc3f77552532491 Mon Sep 17 00:00:00 2001 From: Eyejoker Date: Mon, 30 Mar 2026 03:28:09 +0900 Subject: [PATCH] feat: mount parent .git for worktree resolution in reviewer container Git worktrees reference the parent repo's .git directory via absolute path. Without mounting it, all git commands fail inside the container. Now reads the .git file, resolves the parent .git path, and mounts it read-only at the same host path so references resolve naturally. --- src/container-runner.ts | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/src/container-runner.ts b/src/container-runner.ts index 94e8f29..8a95f03 100644 --- a/src/container-runner.ts +++ b/src/container-runner.ts @@ -214,6 +214,37 @@ export function buildReviewerMounts( readonly: true, }); + // Git worktree support: worktree's .git file references the parent repo's + // .git directory via absolute path. Mount the parent .git at the same host + // path so git commands resolve inside the container. + const dotGitPath = path.join(ownerWorkspaceDir, '.git'); + try { + const stat = fs.statSync(dotGitPath); + if (stat.isFile()) { + // Worktree: .git is a file containing "gitdir: /path/to/.git/worktrees/name" + const content = fs.readFileSync(dotGitPath, 'utf-8').trim(); + const match = content.match(/^gitdir:\s*(.+)$/); + if (match) { + const worktreeGitDir = path.resolve(ownerWorkspaceDir, match[1]); + // worktreeGitDir = /parent/.git/worktrees/name → parent .git = ../../ + const parentGitDir = path.resolve(worktreeGitDir, '..', '..'); + if (fs.existsSync(parentGitDir)) { + mounts.push({ + hostPath: parentGitDir, + containerPath: parentGitDir, + readonly: true, + }); + logger.debug( + { parentGitDir, worktreeGitDir }, + 'Mounting parent .git for worktree resolution', + ); + } + } + } + } catch { + // Not a git repo or .git missing — skip + } + // pnpm global store: mount at the same host path so hardlinks resolve. const pnpmStore = detectPnpmStorePath(ownerWorkspaceDir); if (pnpmStore) {