diff --git a/src/renderer/src/components/right-sidebar/SourceControl.compare-summary.test.ts b/src/renderer/src/components/right-sidebar/SourceControl.compare-summary.test.ts index 044291e1347..0994c5a3218 100644 --- a/src/renderer/src/components/right-sidebar/SourceControl.compare-summary.test.ts +++ b/src/renderer/src/components/right-sidebar/SourceControl.compare-summary.test.ts @@ -2,6 +2,7 @@ import { describe, expect, it, vi } from 'vitest' import { CompareSummary, CompareSummaryToolbarButton, + resolveSourceControlBaseRef, shouldShowCompareSummary } from './SourceControl' import type { GitBranchCompareSummary } from '../../../../shared/types' @@ -75,6 +76,33 @@ const readySummary: GitBranchCompareSummary = { } describe('SourceControl compare summary', () => { + it('prefers the worktree creation base for branch compare', () => { + expect( + resolveSourceControlBaseRef({ + worktreeBaseRef: 'refs/remotes/origin/main', + repoBaseRef: 'main', + defaultBaseRef: 'origin/main' + }) + ).toBe('refs/remotes/origin/main') + }) + + it('falls back to repo and default base refs when worktree metadata is absent', () => { + expect( + resolveSourceControlBaseRef({ + worktreeBaseRef: ' ', + repoBaseRef: ' origin/release ', + defaultBaseRef: 'origin/main' + }) + ).toBe('origin/release') + + expect( + resolveSourceControlBaseRef({ + repoBaseRef: null, + defaultBaseRef: 'origin/main' + }) + ).toBe('origin/main') + }) + it('wires toolbar actions without rendering the dead view-mode toggle', () => { const onChangeBaseRef = vi.fn() const onRetry = vi.fn() diff --git a/src/renderer/src/components/right-sidebar/SourceControl.tsx b/src/renderer/src/components/right-sidebar/SourceControl.tsx index 573ac9c1afe..35e9e983aa1 100644 --- a/src/renderer/src/components/right-sidebar/SourceControl.tsx +++ b/src/renderer/src/components/right-sidebar/SourceControl.tsx @@ -221,6 +221,19 @@ export type SourceControlActionError = { message: string } +export function resolveSourceControlBaseRef(input: { + worktreeBaseRef?: string | null + repoBaseRef?: string | null + defaultBaseRef?: string | null +}): string | null { + return ( + input.worktreeBaseRef?.trim() || + input.repoBaseRef?.trim() || + input.defaultBaseRef?.trim() || + null + ) +} + const EMPTY_GIT_STATUS_ENTRIES: GitStatusEntry[] = [] const EMPTY_BRANCH_CHANGE_ENTRIES: GitBranchChangeEntry[] = [] @@ -1164,7 +1177,15 @@ function SourceControlInner(): React.JSX.Element { } }, [activeRepo, activeRepoSettings, isBranchVisible, isFolder]) - const effectiveBaseRef = activeRepo?.worktreeBaseRef ?? defaultBaseRef + const normalizedWorktreeBaseRef = activeWorktree?.baseRef?.trim() || null + const normalizedRepoBaseRef = activeRepo?.worktreeBaseRef?.trim() || null + const effectiveBaseRef = resolveSourceControlBaseRef({ + worktreeBaseRef: normalizedWorktreeBaseRef, + repoBaseRef: normalizedRepoBaseRef, + defaultBaseRef + }) + const baseRefOwnedByWorktree = normalizedWorktreeBaseRef !== null + const pinnedBaseRef = normalizedWorktreeBaseRef ?? normalizedRepoBaseRef const hasUncommittedEntries = entries.length > 0 const hostedReviewCreation = @@ -4604,14 +4625,22 @@ function SourceControlInner(): React.JSX.Element { { - void updateRepo(activeRepo.id, { worktreeBaseRef: ref }) + if (baseRefOwnedByWorktree && activeWorktreeId) { + void updateWorktreeMeta(activeWorktreeId, { baseRef: ref }) + } else { + void updateRepo(activeRepo.id, { worktreeBaseRef: ref }) + } setBaseRefDialogOpen(false) window.setTimeout(() => void refreshBranchCompare(), 0) }} onUsePrimary={() => { - void updateRepo(activeRepo.id, { worktreeBaseRef: undefined }) + if (baseRefOwnedByWorktree && activeWorktreeId) { + void updateWorktreeMeta(activeWorktreeId, { baseRef: undefined }) + } else { + void updateRepo(activeRepo.id, { worktreeBaseRef: undefined }) + } setBaseRefDialogOpen(false) window.setTimeout(() => void refreshBranchCompare(), 0) }} diff --git a/src/renderer/src/store/slices/worktrees.test.ts b/src/renderer/src/store/slices/worktrees.test.ts index 3616acb77a4..51b766254f7 100644 --- a/src/renderer/src/store/slices/worktrees.test.ts +++ b/src/renderer/src/store/slices/worktrees.test.ts @@ -1291,6 +1291,31 @@ describe('createWorktree base status merge', () => { }) }) + it('merges create result metadata into a worktree inserted by the watcher race', async () => { + const store = createTestStore() + const watcherWorktree = makeWorktree({ + id: 'repo1::/path/wt1', + repoId: 'repo1', + path: '/path/wt1' + }) + const createdWorktree = makeWorktree({ + ...watcherWorktree, + baseRef: 'refs/remotes/origin/main' + }) + store.setState({ + worktreesByRepo: { repo1: [watcherWorktree] } + } as Partial) + mockApi.worktrees.create.mockResolvedValue({ worktree: createdWorktree }) + + await store.getState().createWorktree('repo1', 'feature', 'origin/main') + + expect(store.getState().worktreesByRepo.repo1).toHaveLength(1) + expect(store.getState().worktreesByRepo.repo1[0]).toMatchObject({ + id: watcherWorktree.id, + baseRef: 'refs/remotes/origin/main' + }) + }) + it.each([ { status: 'skipped_dirty_worktree', diff --git a/src/renderer/src/store/slices/worktrees.ts b/src/renderer/src/store/slices/worktrees.ts index 9bb484fb095..af4dd617102 100644 --- a/src/renderer/src/store/slices/worktrees.ts +++ b/src/renderer/src/store/slices/worktrees.ts @@ -1233,10 +1233,17 @@ export const createWorktreeSlice: StateCreator set((s) => { const current = s.worktreesByRepo[repoId] ?? [] const alreadyPresent = current.some((w) => w.id === result.worktree.id) + const nextWorktrees = alreadyPresent + ? current.map((worktree) => + worktree.id === result.worktree.id + ? { ...worktree, ...result.worktree } + : worktree + ) + : [...current, result.worktree] return { worktreesByRepo: { ...s.worktreesByRepo, - [repoId]: alreadyPresent ? current : [...current, result.worktree] + [repoId]: nextWorktrees }, ...(result.initialBaseStatus ? {