diff --git a/packages/server/src/utils/checkout-git.test.ts b/packages/server/src/utils/checkout-git.test.ts index 877e2ddde..2720b0d0a 100644 --- a/packages/server/src/utils/checkout-git.test.ts +++ b/packages/server/src/utils/checkout-git.test.ts @@ -3002,6 +3002,20 @@ const x = 1; expect(baseDiff.diff).not.toContain("file.txt"); }); + it("names both refs when a requested base ref does not match the stored one", async () => { + const worktree = await createLegacyWorktreeForTest({ + branchName: "mismatch-feature", + cwd: repoDir, + baseBranch: "main", + worktreeSlug: "mismatch-feature", + paseoHome, + }); + + await expect( + getCheckoutDiff(worktree.worktreePath, { mode: "base", baseRef: "other" }, { paseoHome }), + ).rejects.toThrow("Base ref mismatch: stored main, requested other"); + }); + it("excludes dirty working tree changes from Paseo worktree base diffs", async () => { const worktree = await createLegacyWorktreeForTest({ branchName: "feature", diff --git a/packages/server/src/utils/checkout-git.ts b/packages/server/src/utils/checkout-git.ts index f518adfed..6d09eee0d 100644 --- a/packages/server/src/utils/checkout-git.ts +++ b/packages/server/src/utils/checkout-git.ts @@ -1134,6 +1134,12 @@ async function resolveBaseRefForCwd( }; } +// Names both refs rather than labelling either one correct: a caller's ref can be stale, but the +// stored ref can equally be wrong, so the message states the two facts and leaves the diagnosis open. +function baseRefMismatchError(refs: { stored: string; requested: string }): Error { + return new Error(`Base ref mismatch: stored ${refs.stored}, requested ${refs.requested}`); +} + async function isWorkingTreeDirty(cwd: string, context?: CheckoutContext): Promise { const { stdout } = await runGitCommand(["status", "--porcelain"], { cwd, @@ -2795,7 +2801,7 @@ async function resolveCheckoutDiffRefs( return null; } if (storedBaseRef && compare.baseRef && compare.baseRef !== storedBaseRef) { - throw new Error(`Base ref mismatch: expected ${baseRef}, got ${compare.baseRef}`); + throw baseRefMismatchError({ stored: storedBaseRef, requested: compare.baseRef }); } const bestBaseRef = await resolveBestComparisonBaseRef(cwd, baseRef); return { @@ -3009,7 +3015,7 @@ export async function mergeToBase( throw new Error("Unable to determine base branch for merge"); } if (storedBaseRef && options.baseRef && options.baseRef !== storedBaseRef) { - throw new Error(`Base ref mismatch: expected ${baseRef}, got ${options.baseRef}`); + throw baseRefMismatchError({ stored: storedBaseRef, requested: options.baseRef }); } if (!currentBranch) { throw new Error("Unable to determine current branch for merge"); @@ -3085,7 +3091,7 @@ export async function mergeFromBase( throw new Error("Unable to determine base branch for merge"); } if (storedBaseRef && options.baseRef && options.baseRef !== storedBaseRef) { - throw new Error(`Base ref mismatch: expected ${baseRef}, got ${options.baseRef}`); + throw baseRefMismatchError({ stored: storedBaseRef, requested: options.baseRef }); } const requireCleanTarget = options.requireCleanTarget ?? true; @@ -3423,7 +3429,7 @@ export async function createPullRequest( } const normalizedBase = normalizeLocalBranchRefName(base); if (storedBaseRef && options.base && options.base !== storedBaseRef) { - throw new Error(`Base ref mismatch: expected ${base}, got ${options.base}`); + throw baseRefMismatchError({ stored: storedBaseRef, requested: options.base }); } // The push deliberately happens before the adapter resolves the target