From 73e290bb5771322ef5fc43e5afca80a4bbf99e46 Mon Sep 17 00:00:00 2001 From: Christoph Leiter Date: Fri, 24 Jul 2026 15:17:55 +0200 Subject: [PATCH] fix(server): name both refs in the base ref mismatch error (#2161) The base ref mismatch error printed the same value on both sides, so it always read "expected master, got master". All four throw sites computed `baseRef = compare.baseRef ?? resolvedBaseRef`, but the guard only fires when the caller passed a ref, so `baseRef` and the "got" value were the same string by construction. The value that actually differs -- the stored `baseRefName` from the worktree metadata -- was never shown, which left the error undiagnosable from the UI alone. Build the message in one place and have the four guards throw it, naming the refs `stored` and `requested`. The wording deliberately avoids "expected": a caller's ref can be stale, but the stored ref can equally be the wrong one, so the message states both facts and leaves the diagnosis open. The guards stay where they are. Only the message moves, so the condition that fires each throw is still visible at the call site, and both refs are narrowed to plain strings by the time the message is built. Throw conditions are unchanged; only the message text differs. Co-authored-by: Claude Opus 4.8 --- packages/server/src/utils/checkout-git.test.ts | 14 ++++++++++++++ packages/server/src/utils/checkout-git.ts | 14 ++++++++++---- 2 files changed, 24 insertions(+), 4 deletions(-) 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