diff --git a/packages/server/src/server/worktree-core.posix.test.ts b/packages/server/src/server/worktree-core.posix.test.ts index 0a412938c..58778b3f6 100644 --- a/packages/server/src/server/worktree-core.posix.test.ts +++ b/packages/server/src/server/worktree-core.posix.test.ts @@ -85,6 +85,15 @@ function createGitRepoWithDevBranch(): { tempDir: string; repoDir: string; paseo return { tempDir, repoDir, paseoHome }; } +function createGitRepoWithOriginMain(): { tempDir: string; repoDir: string; paseoHome: string } { + const { tempDir, repoDir, paseoHome } = createGitRepo(); + const remoteDir = path.join(tempDir, "origin.git"); + execFileSync("git", ["clone", "--bare", repoDir, remoteDir], { stdio: "pipe" }); + execFileSync("git", ["remote", "add", "origin", remoteDir], { cwd: repoDir, stdio: "pipe" }); + execFileSync("git", ["fetch", "origin"], { cwd: repoDir, stdio: "pipe" }); + return { tempDir, repoDir, paseoHome }; +} + function createGitHubPrRemoteRepo(): { tempDir: string; repoDir: string; paseoHome: string } { const { tempDir, repoDir, paseoHome } = createGitRepo(); const featureBranch = "feature/review-pr"; @@ -165,6 +174,14 @@ function createForkGitHubPrRemoteRepo(): { return { tempDir, repoDir, headRemoteDir, paseoHome }; } +function getBranchUpstream(cwd: string): string | null { + const result = spawnSync("git", ["rev-parse", "--abbrev-ref", "--symbolic-full-name", "@{u}"], { + cwd, + encoding: "utf8", + }); + return result.status === 0 ? result.stdout.trim() : null; +} + describe.skipIf(isPlatform("win32"))("worktree-core POSIX-only", () => { describe("createWorktreeCore", () => { const cleanupPaths: string[] = []; @@ -199,6 +216,30 @@ describe.skipIf(isPlatform("win32"))("worktree-core POSIX-only", () => { expect(existsSync(result.worktree.worktreePath)).toBe(true); }); + test("creates branch-off worktrees from origin main without tracking origin main", async () => { + const { tempDir, repoDir, paseoHome } = createGitRepoWithOriginMain(); + cleanupPaths.push(tempDir); + + const result = await createCoreWorktree( + { + cwd: repoDir, + worktreeSlug: "no-upstream-feature", + action: "branch-off", + refName: "main", + paseoHome, + runSetup: false, + }, + createCoreDeps(), + ); + + expect(result.intent).toEqual({ + kind: "branch-off", + baseBranch: "main", + branchName: "no-upstream-feature", + }); + expect(getBranchUpstream(result.worktree.worktreePath)).toBeNull(); + }); + test("creates a branch-off worktree with a mnemonic slug when no slug is supplied", async () => { const { tempDir, repoDir, paseoHome } = createGitRepo(); cleanupPaths.push(tempDir); diff --git a/packages/server/src/utils/checkout-git.test.ts b/packages/server/src/utils/checkout-git.test.ts index 086e7a7d3..02ff020c3 100644 --- a/packages/server/src/utils/checkout-git.test.ts +++ b/packages/server/src/utils/checkout-git.test.ts @@ -1269,6 +1269,14 @@ const x = 1; await pushCurrentBranch(repoDir); execFileSync("git", ["--git-dir", remoteDir, "show-ref", "--verify", "refs/heads/feature"]); + const upstream = execFileSync( + "git", + ["rev-parse", "--abbrev-ref", "--symbolic-full-name", "@{u}"], + { cwd: repoDir }, + ) + .toString() + .trim(); + expect(upstream).toBe("origin/feature"); }); it("lists merged local and remote branch suggestions with provenance", async () => { diff --git a/packages/server/src/utils/worktree.ts b/packages/server/src/utils/worktree.ts index 71c6ab618..80aefb2d0 100644 --- a/packages/server/src/utils/worktree.ts +++ b/packages/server/src/utils/worktree.ts @@ -1277,7 +1277,7 @@ async function resolveWorktreeSourcePlan({ return { branchName: newBranchName, metadataBaseRefName: normalizedBaseBranch, - addArguments: ["-b", newBranchName, base], + addArguments: ["-b", newBranchName, "--no-track", base], }; } case "checkout-branch": {