* Creates a new git worktree for the given slug, or resumes it if it already exists. * Named worktrees reuse the same path across invocations, so the existence check * prevents unconditionally running `git fetch` (which can hang waiting for credentials) * on every resume.
(
repoRoot: string,
slug: string,
options?: { prNumber?: number },
)
| 233 | * on every resume. |
| 234 | */ |
| 235 | async function getOrCreateWorktree( |
| 236 | repoRoot: string, |
| 237 | slug: string, |
| 238 | options?: { prNumber?: number }, |
| 239 | ): Promise<WorktreeCreateResult> { |
| 240 | const worktreePath = worktreePathFor(repoRoot, slug) |
| 241 | const worktreeBranch = worktreeBranchName(slug) |
| 242 | |
| 243 | // Fast resume path: if the worktree already exists skip fetch and creation. |
| 244 | // Read the .git pointer file directly (no subprocess, no upward walk) — a |
| 245 | // subprocess `rev-parse HEAD` burns ~15ms on spawn overhead even for a 2ms |
| 246 | // task, and the await yield lets background spawnSyncs pile on (seen at 55ms). |
| 247 | const existingHead = await readWorktreeHeadSha(worktreePath) |
| 248 | if (existingHead) { |
| 249 | return { |
| 250 | worktreePath, |
| 251 | worktreeBranch, |
| 252 | headCommit: existingHead, |
| 253 | existed: true, |
| 254 | } |
| 255 | } |
| 256 | |
| 257 | // New worktree: fetch base branch then add |
| 258 | await mkdir(worktreesDir(repoRoot), { recursive: true }) |
| 259 | |
| 260 | const fetchEnv = { ...process.env, ...GIT_NO_PROMPT_ENV } |
| 261 | |
| 262 | let baseBranch: string |
| 263 | let baseSha: string | null = null |
| 264 | if (options?.prNumber) { |
| 265 | const { code: prFetchCode, stderr: prFetchStderr } = |
| 266 | await execFileNoThrowWithCwd( |
| 267 | gitExe(), |
| 268 | ['fetch', 'origin', `pull/${options.prNumber}/head`], |
| 269 | { cwd: repoRoot, stdin: 'ignore', env: fetchEnv }, |
| 270 | ) |
| 271 | if (prFetchCode !== 0) { |
| 272 | throw new Error( |
| 273 | `Failed to fetch PR #${options.prNumber}: ${prFetchStderr.trim() || 'PR may not exist or the repository may not have a remote named "origin"'}`, |
| 274 | ) |
| 275 | } |
| 276 | baseBranch = 'FETCH_HEAD' |
| 277 | } else { |
| 278 | // If origin/<branch> already exists locally, skip fetch. In large repos |
| 279 | // (210k files, 16M objects) fetch burns ~6-8s on a local commit-graph |
| 280 | // scan before even hitting the network. A slightly stale base is fine — |
| 281 | // the user can pull in the worktree if they want latest. |
| 282 | // resolveRef reads the loose/packed ref directly; when it succeeds we |
| 283 | // already have the SHA, so the later rev-parse is skipped entirely. |
| 284 | const [defaultBranch, gitDir] = await Promise.all([ |
| 285 | getDefaultBranch(), |
| 286 | resolveGitDir(repoRoot), |
| 287 | ]) |
| 288 | const originRef = `origin/${defaultBranch}` |
| 289 | const originSha = gitDir |
| 290 | ? await resolveRef(gitDir, `refs/remotes/origin/${defaultBranch}`) |
| 291 | : null |
| 292 | if (originSha) { |
no test coverage detected