mirror of
https://github.com/stablyai/orca.git
synced 2026-11-02 18:17:36 +00:00
fix(worktrees): a paired host honors the client's Keep Local Main Up to Date (#29469)
The desktop keeps the toggle and its dismissed-suggestion flag in its own profile, but the host read its own copy on create, so local main never fast-forwarded and the suggestion toast kept returning. Send both with worktree.create, like nestWorkspaces; older clients keep the host's value. Fixes STA-6997
This commit is contained in:
@@ -48,6 +48,12 @@ export function buildManagedWorktreeCreateArgs(
|
||||
workspaceStatus: params.workspaceStatus,
|
||||
manualOrder: params.manualOrder,
|
||||
...(params.nestWorkspaces !== undefined ? { nestWorkspaces: params.nestWorkspaces } : {}),
|
||||
...(params.refreshLocalBaseRefOnWorktreeCreate !== undefined
|
||||
? { refreshLocalBaseRefOnWorktreeCreate: params.refreshLocalBaseRefOnWorktreeCreate }
|
||||
: {}),
|
||||
...(params.localBaseRefSuggestionDismissed !== undefined
|
||||
? { localBaseRefSuggestionDismissed: params.localBaseRefSuggestionDismissed }
|
||||
: {}),
|
||||
sparseCheckout: params.sparseCheckout,
|
||||
pushTarget: params.pushTarget,
|
||||
runHooks: params.runHooks === true,
|
||||
|
||||
@@ -200,6 +200,42 @@ describe('runtime nested-workspaces layout', () => {
|
||||
})
|
||||
})
|
||||
|
||||
describe('runtime keep-local-main-up-to-date choice', () => {
|
||||
const addRefreshArg = (): unknown => mocks.add.mock.calls[0]?.[4]
|
||||
const addOptions = (): AddWorktreeOptions | undefined => mocks.add.mock.calls[0]?.[6]
|
||||
|
||||
beforeEach(() => {
|
||||
mocks.consume.mockResolvedValue({ status: 'miss', reason: 'disabled', rearm: mocks.rearm })
|
||||
mocks.remoteBase.mockResolvedValue({
|
||||
remote: 'origin',
|
||||
branch: 'main',
|
||||
ref: 'refs/remotes/origin/main',
|
||||
base: 'origin/main'
|
||||
})
|
||||
})
|
||||
|
||||
it('refreshes local main when the client turned it on, over the host default', async () => {
|
||||
await createWorktree({ refreshLocalBaseRefOnWorktreeCreate: true })
|
||||
expect(mocks.consume).toHaveBeenCalledWith(
|
||||
expect.objectContaining({ refreshLocalBaseRef: true })
|
||||
)
|
||||
expect(addRefreshArg()).toBe(true)
|
||||
expect(addOptions()?.suggestLocalBaseRefUpdate).toBeUndefined()
|
||||
})
|
||||
|
||||
it('suppresses the suggestion the client already dismissed', async () => {
|
||||
await createWorktree({ localBaseRefSuggestionDismissed: true })
|
||||
expect(addRefreshArg()).toBe(false)
|
||||
expect(addOptions()?.suggestLocalBaseRefUpdate).toBeUndefined()
|
||||
})
|
||||
|
||||
it('keeps the host setting and suggestion when the client sends neither', async () => {
|
||||
await createWorktree({})
|
||||
expect(addRefreshArg()).toBe(false)
|
||||
expect(addOptions()?.suggestLocalBaseRefUpdate).toBe(true)
|
||||
})
|
||||
})
|
||||
|
||||
describe('runtime prepared-worktree replenishment', () => {
|
||||
it('keeps a failed claim reserved through the normal-add fallback', async () => {
|
||||
mocks.consume.mockResolvedValue({
|
||||
|
||||
@@ -70,7 +70,13 @@ async function performRuntimeLocalWorktreeCreate<T>(args: RuntimeLocalWorktreeCr
|
||||
// Why: the layout choice must reach name retirement and metadata too, not just the path.
|
||||
const settings = {
|
||||
...hostSettings,
|
||||
nestWorkspaces: request.nestWorkspaces ?? hostSettings.nestWorkspaces
|
||||
nestWorkspaces: request.nestWorkspaces ?? hostSettings.nestWorkspaces,
|
||||
// Why: a paired desktop keeps this toggle in its own profile, never in the host's.
|
||||
refreshLocalBaseRefOnWorktreeCreate:
|
||||
request.refreshLocalBaseRefOnWorktreeCreate ??
|
||||
hostSettings.refreshLocalBaseRefOnWorktreeCreate,
|
||||
localBaseRefSuggestionDismissed:
|
||||
request.localBaseRefSuggestionDismissed ?? hostSettings.localBaseRefSuggestionDismissed
|
||||
}
|
||||
const pathSettings = getWorktreePathSettings(repo, settings, getWorktreeMirrorDistro(store, repo))
|
||||
const gitExecOptions = getLocalProjectGitExecOptions(store, repo)
|
||||
|
||||
@@ -43,6 +43,8 @@ export type RuntimeManagedWorktreeCreateArgs = {
|
||||
workspaceStatus?: string
|
||||
manualOrder?: number
|
||||
nestWorkspaces?: boolean
|
||||
refreshLocalBaseRefOnWorktreeCreate?: boolean
|
||||
localBaseRefSuggestionDismissed?: boolean
|
||||
sparseCheckout?: { directories: string[]; presetId?: string }
|
||||
pushTarget?: GitPushTarget
|
||||
runHooks?: boolean
|
||||
|
||||
@@ -56,7 +56,9 @@ describe('worktree remote runtime mutations', () => {
|
||||
settings: {
|
||||
...store.getState().settings!,
|
||||
activeRuntimeEnvironmentId: 'env-1',
|
||||
nestWorkspaces: false
|
||||
nestWorkspaces: false,
|
||||
refreshLocalBaseRefOnWorktreeCreate: true,
|
||||
localBaseRefSuggestionDismissed: false
|
||||
},
|
||||
worktreesByRepo: { repo1: [] }
|
||||
})
|
||||
@@ -88,6 +90,8 @@ describe('worktree remote runtime mutations', () => {
|
||||
setupDecision: 'skip',
|
||||
sparseCheckout: { directories: ['src'], presetId: 'preset-1' },
|
||||
nestWorkspaces: false,
|
||||
refreshLocalBaseRefOnWorktreeCreate: true,
|
||||
localBaseRefSuggestionDismissed: false,
|
||||
telemetrySource: 'sidebar',
|
||||
displayName: 'Feature title',
|
||||
linkedIssue: 123,
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
import type { GlobalSettings } from '../../../../../../shared/global-settings-types'
|
||||
|
||||
const CLIENT_WORKTREE_CREATE_SETTING_KEYS = [
|
||||
'nestWorkspaces',
|
||||
'refreshLocalBaseRefOnWorktreeCreate',
|
||||
'localBaseRefSuggestionDismissed'
|
||||
] as const
|
||||
|
||||
/** Client-profile settings a remote host applies to this create instead of its own. */
|
||||
export type ClientWorktreeCreateSettings = Partial<
|
||||
Pick<GlobalSettings, (typeof CLIENT_WORKTREE_CREATE_SETTING_KEYS)[number]>
|
||||
>
|
||||
|
||||
export function pickClientWorktreeCreateSettings(
|
||||
settings: GlobalSettings | null | undefined
|
||||
): ClientWorktreeCreateSettings {
|
||||
const picked: ClientWorktreeCreateSettings = {}
|
||||
for (const key of CLIENT_WORKTREE_CREATE_SETTING_KEYS) {
|
||||
const value = settings?.[key]
|
||||
if (typeof value === 'boolean') {
|
||||
picked[key] = value
|
||||
}
|
||||
}
|
||||
return picked
|
||||
}
|
||||
@@ -8,6 +8,10 @@ import { adoptFromEndpoint } from '../../../adopt-from-endpoint'
|
||||
import type { WorktreeSlice } from '../../worktree-helpers'
|
||||
import type { WorktreeSliceGet, WorktreeSliceSet } from '../listing/worktree-slice-types'
|
||||
import type { CreateWorktreeResult } from '../../../../../../shared/worktree/create-types'
|
||||
import {
|
||||
pickClientWorktreeCreateSettings,
|
||||
type ClientWorktreeCreateSettings
|
||||
} from './client-worktree-create-settings'
|
||||
import {
|
||||
CLIENT_WORKTREE_CREATE_MAX_ATTEMPTS,
|
||||
getClientWorktreeCreateCandidate,
|
||||
@@ -51,7 +55,7 @@ async function runCreateAttempt(
|
||||
request: WorktreeCreateRequest,
|
||||
attempt: WorktreeCreateAttempt,
|
||||
target: RuntimeTarget,
|
||||
nestWorkspaces?: boolean
|
||||
clientCreateSettings: ClientWorktreeCreateSettings = {}
|
||||
): Promise<CreateAttemptOutcome> {
|
||||
const provisionedRoot = request.options?.provisionedRoot
|
||||
const create = async (
|
||||
@@ -79,8 +83,8 @@ async function runCreateAttempt(
|
||||
'worktree.create',
|
||||
{
|
||||
...buildRuntimeWorktreeCreateParams(request, { ...attempt, parentWorkspace }),
|
||||
// Why: the host's own store default would otherwise decide the layout over the user's choice.
|
||||
...(nestWorkspaces === undefined ? {} : { nestWorkspaces })
|
||||
// Why: the host's own store defaults would otherwise override the user's choices.
|
||||
...clientCreateSettings
|
||||
},
|
||||
{ timeoutMs: 10 * 60_000 }
|
||||
)
|
||||
@@ -239,7 +243,7 @@ export function createCreateWorktree(
|
||||
manualOrder
|
||||
},
|
||||
target,
|
||||
get().settings?.nestWorkspaces
|
||||
pickClientWorktreeCreateSettings(get().settings)
|
||||
)
|
||||
if (lostRequestedParent(outcome, parent, target)) {
|
||||
warnParentDroppedOnce()
|
||||
|
||||
@@ -60,6 +60,9 @@ export const WorktreeCreate = z
|
||||
manualOrder: OptionalFiniteNumber,
|
||||
// Why: the client's "Nest Workspaces" choice; absent (older clients) keeps the host's own setting.
|
||||
nestWorkspaces: z.boolean().optional(),
|
||||
// Why: the client's Keep Local Main Up to Date choice; absent (older clients) keeps the host's.
|
||||
refreshLocalBaseRefOnWorktreeCreate: z.boolean().optional(),
|
||||
localBaseRefSuggestionDismissed: z.boolean().optional(),
|
||||
sparseCheckout: z
|
||||
.object({
|
||||
directories: z.array(z.string()),
|
||||
|
||||
Reference in New Issue
Block a user