mirror of
https://github.com/stablyai/orca.git
synced 2026-10-20 16:03:20 +00:00
Display SSH worktrees immediately using persisted metadata
Users can now see known worktrees for SSH hosts without waiting for the provider connection to establish. Worktrees are fetched from local metadata and displayed as non-authoritative, then merged without replacing richer live data once the provider becomes available.
This commit is contained in:
@@ -2589,6 +2589,84 @@ describe('registerWorktreeHandlers', () => {
|
||||
})
|
||||
})
|
||||
|
||||
it('lists every persisted SSH worktree without accessing the live provider', async () => {
|
||||
const sshHostId = toSshExecutionHostId('target-a')
|
||||
const sshRepo = {
|
||||
id: 'repo-1',
|
||||
path: '/remote/repo',
|
||||
displayName: 'repo',
|
||||
badgeColor: '#000',
|
||||
addedAt: 0,
|
||||
connectionId: 'target-a'
|
||||
}
|
||||
const metaById = {
|
||||
'repo-1::/remote/repo': makeWorktreeMeta({
|
||||
displayName: 'main',
|
||||
hostId: sshHostId
|
||||
}),
|
||||
'repo-1::/remote/queued': makeWorktreeMeta({
|
||||
displayName: 'queued',
|
||||
hostId: sshHostId
|
||||
}),
|
||||
'repo-1::/remote/other-host': makeWorktreeMeta({
|
||||
displayName: 'other host',
|
||||
hostId: toSshExecutionHostId('target-b')
|
||||
})
|
||||
}
|
||||
store.getRepos.mockReturnValue([sshRepo])
|
||||
store.getProjectHostSetups.mockReturnValue([])
|
||||
store.getAllWorktreeMeta.mockReturnValue(metaById)
|
||||
store.getWorktreeMeta.mockImplementation((worktreeId: string) => metaById[worktreeId])
|
||||
|
||||
const result = await handlers['worktrees:listKnownForExecutionHost'](null, {
|
||||
repoId: sshRepo.id,
|
||||
executionHostId: sshHostId
|
||||
})
|
||||
|
||||
expect(result).toMatchObject({
|
||||
status: 'complete',
|
||||
repoId: sshRepo.id,
|
||||
executionHostId: sshHostId,
|
||||
result: {
|
||||
repoId: sshRepo.id,
|
||||
authoritative: false,
|
||||
source: 'metadata-fallback',
|
||||
worktrees: [
|
||||
expect.objectContaining({ path: '/remote/repo', isMainWorktree: true }),
|
||||
expect.objectContaining({ path: '/remote/queued', isMainWorktree: false })
|
||||
]
|
||||
}
|
||||
})
|
||||
expect(getSshGitProviderMock).not.toHaveBeenCalled()
|
||||
expect(listWorktreesMock).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('rejects ambiguous metadata-only SSH owners', async () => {
|
||||
const sshHostId = toSshExecutionHostId('target-a')
|
||||
const sshRepo = {
|
||||
id: 'repo-1',
|
||||
path: '/remote/repo-a',
|
||||
displayName: 'repo',
|
||||
badgeColor: '#000',
|
||||
addedAt: 0,
|
||||
connectionId: 'target-a'
|
||||
}
|
||||
store.getRepos.mockReturnValue([sshRepo, { ...sshRepo, path: '/remote/repo-b' }])
|
||||
|
||||
const result = await handlers['worktrees:listKnownForExecutionHost'](null, {
|
||||
repoId: sshRepo.id,
|
||||
executionHostId: sshHostId
|
||||
})
|
||||
|
||||
expect(result).toEqual({
|
||||
status: 'rejected',
|
||||
repoId: sshRepo.id,
|
||||
executionHostId: sshHostId
|
||||
})
|
||||
expect(store.getAllWorktreeMeta).not.toHaveBeenCalled()
|
||||
expect(getSshGitProviderMock).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('fails closed for duplicate exact owners and ambiguous legacy repo IDs', async () => {
|
||||
const sshRepo = {
|
||||
id: 'shared-repo',
|
||||
|
||||
@@ -49,7 +49,9 @@ import {
|
||||
import {
|
||||
PROVIDER_REQUEST_ID_MAX_UTF8_BYTES,
|
||||
type DirectSshDetectedWorktreeRequest,
|
||||
type HostQualifiedKnownWorktreeResult,
|
||||
type HostQualifiedDetectedWorktreeResult,
|
||||
type ListKnownWorktreesForExecutionHostArgs,
|
||||
type ListDetectedWorktreesArgs,
|
||||
type ProviderRequestId
|
||||
} from '../../shared/detected-worktree-provider-contract'
|
||||
@@ -1826,6 +1828,7 @@ export function registerWorktreeHandlers(
|
||||
ipcMain.removeHandler('worktrees:listAll')
|
||||
ipcMain.removeHandler('worktrees:list')
|
||||
ipcMain.removeHandler('worktrees:listDetected')
|
||||
ipcMain.removeHandler('worktrees:listKnownForExecutionHost')
|
||||
ipcMain.removeHandler('worktrees:cancelListDetected')
|
||||
ipcMain.removeHandler('worktrees:create')
|
||||
ipcMain.removeHandler('worktrees:prefetchCreateBase')
|
||||
@@ -1970,6 +1973,44 @@ export function registerWorktreeHandlers(
|
||||
}
|
||||
})
|
||||
|
||||
ipcMain.handle(
|
||||
'worktrees:listKnownForExecutionHost',
|
||||
(_event, args: ListKnownWorktreesForExecutionHostArgs): HostQualifiedKnownWorktreeResult => {
|
||||
const rejected = (): HostQualifiedKnownWorktreeResult => ({
|
||||
status: 'rejected',
|
||||
repoId: args.repoId,
|
||||
executionHostId: args.executionHostId
|
||||
})
|
||||
const parsedHost = parseExecutionHostId(args.executionHostId)
|
||||
if (parsedHost?.kind !== 'ssh') {
|
||||
return rejected()
|
||||
}
|
||||
const candidates = store.getRepos().filter((candidate) => candidate.id === args.repoId)
|
||||
if (
|
||||
candidates.some((candidate) => resolveRepoOwnershipEvidence(candidate).status !== 'owned')
|
||||
) {
|
||||
return rejected()
|
||||
}
|
||||
const repo = findExactRepoOwner(store, args.repoId, args.executionHostId)
|
||||
if (!repo || repo.connectionId !== parsedHost.targetId) {
|
||||
return rejected()
|
||||
}
|
||||
const metaIndex = createSshWorktreeMetaIndex(Object.entries(store.getAllWorktreeMeta()))
|
||||
const worktrees = listDisconnectedSshWorktrees(store, repo, metaIndex)
|
||||
return {
|
||||
status: 'complete',
|
||||
repoId: repo.id,
|
||||
executionHostId: args.executionHostId,
|
||||
result: {
|
||||
repoId: repo.id,
|
||||
authoritative: false,
|
||||
source: 'metadata-fallback',
|
||||
worktrees: buildDisconnectedDetectedWorktrees(store, repo, worktrees)
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
ipcMain.handle(
|
||||
'worktrees:listDetected',
|
||||
async (
|
||||
|
||||
@@ -30,8 +30,10 @@ import type { ReadClipboardTextOptions } from '../shared/clipboard-text'
|
||||
import type { AppIdentity } from '../shared/app-identity'
|
||||
import type { ReleaseChannel } from '../shared/release-channel'
|
||||
import type {
|
||||
HostQualifiedKnownWorktreeResult,
|
||||
HostQualifiedDetectedWorktreeResult,
|
||||
LegacyDetectedWorktreeRequest,
|
||||
ListKnownWorktreesForExecutionHostArgs,
|
||||
ListDetectedWorktreesArgs,
|
||||
ProviderRequestId
|
||||
} from '../shared/detected-worktree-provider-contract'
|
||||
@@ -1396,6 +1398,9 @@ export type PreloadApi = {
|
||||
): Promise<HostQualifiedDetectedWorktreeResult | DetectedWorktreeListResult>
|
||||
(args: LegacyDetectedWorktreeRequest): Promise<DetectedWorktreeListResult>
|
||||
}
|
||||
listKnownForExecutionHost?: (
|
||||
args: ListKnownWorktreesForExecutionHostArgs
|
||||
) => Promise<HostQualifiedKnownWorktreeResult>
|
||||
cancelListDetected?: (args: { providerRequestId: ProviderRequestId }) => Promise<void>
|
||||
listAll: () => Promise<Worktree[]>
|
||||
create: (args: CreateWorktreeArgs) => Promise<CreateWorktreeResult>
|
||||
|
||||
@@ -764,6 +764,9 @@ const api = {
|
||||
|
||||
listDetected: (args) => ipcRenderer.invoke('worktrees:listDetected', args),
|
||||
|
||||
listKnownForExecutionHost: (args) =>
|
||||
ipcRenderer.invoke('worktrees:listKnownForExecutionHost', args),
|
||||
|
||||
cancelListDetected: (args) => ipcRenderer.invoke('worktrees:cancelListDetected', args),
|
||||
|
||||
listAll: () => ipcRenderer.invoke('worktrees:listAll'),
|
||||
|
||||
@@ -68,6 +68,16 @@ describe('native preload SSH authority forwarding', () => {
|
||||
>()
|
||||
})
|
||||
|
||||
it('forwards host-qualified metadata-only worktree reads', async () => {
|
||||
await import('./index')
|
||||
const api = exposeInMainWorld.mock.calls.find(([name]) => name === 'api')?.[1] as PreloadApi
|
||||
const args = { repoId: 'repo-1', executionHostId: 'ssh:ssh-1' as const }
|
||||
|
||||
await api.worktrees.listKnownForExecutionHost?.(args)
|
||||
|
||||
expect(invoke).toHaveBeenCalledWith('worktrees:listKnownForExecutionHost', args)
|
||||
})
|
||||
|
||||
it('forwards full-pair get and push states without cloning away authority', async () => {
|
||||
const state: SshConnectionState = {
|
||||
targetId: 'ssh-1',
|
||||
|
||||
@@ -21,6 +21,8 @@ import { clearRuntimeCompatibilityCacheForTests } from '../../runtime/runtime-rp
|
||||
import { LOCAL_EXECUTION_HOST_ID } from '../../../../shared/execution-host'
|
||||
import type {
|
||||
HostQualifiedDetectedWorktreeResult,
|
||||
HostQualifiedKnownWorktreeResult,
|
||||
ListKnownWorktreesForExecutionHostArgs,
|
||||
ListDetectedWorktreesArgs
|
||||
} from '../../../../shared/detected-worktree-provider-contract'
|
||||
import type { DirectSshAuthority, SshProviderEpoch } from '../../../../shared/ssh-types'
|
||||
@@ -105,12 +107,17 @@ const listDetectedMock = vi.fn<
|
||||
return qualifyDetectedResult(args, result)
|
||||
})
|
||||
|
||||
const listKnownForExecutionHostMock = vi.fn<
|
||||
(args: ListKnownWorktreesForExecutionHostArgs) => Promise<HostQualifiedKnownWorktreeResult>
|
||||
>(async (args) => ({ status: 'rejected', ...args }))
|
||||
|
||||
const mockApi = {
|
||||
worktrees: {
|
||||
create: vi.fn(),
|
||||
prefetchCreateBase: vi.fn().mockResolvedValue(undefined),
|
||||
list: worktreeListMock,
|
||||
listDetected: listDetectedMock,
|
||||
listKnownForExecutionHost: listKnownForExecutionHostMock,
|
||||
cancelListDetected: vi.fn().mockResolvedValue(undefined),
|
||||
listLineage: vi.fn().mockResolvedValue({}),
|
||||
remove: vi.fn().mockResolvedValue(undefined),
|
||||
@@ -1350,6 +1357,107 @@ describe('fetchWorktrees', () => {
|
||||
expect(store.getState().detectedWorktreesByRepo).toBe(detectedWorktreesByRepo)
|
||||
})
|
||||
|
||||
it('shows persisted secondary worktrees while SSH is connecting', async () => {
|
||||
const store = createTestStore()
|
||||
const sshRepo = {
|
||||
id: 'repo-ssh',
|
||||
path: '/home/orca/repo',
|
||||
displayName: 'SSH Repo',
|
||||
badgeColor: '#000',
|
||||
addedAt: 0,
|
||||
connectionId: 'ssh-1'
|
||||
}
|
||||
const queued = makeWorktree({
|
||||
id: 'repo-ssh::/home/orca/queued',
|
||||
repoId: 'repo-ssh',
|
||||
path: '/home/orca/queued',
|
||||
displayName: 'queued'
|
||||
})
|
||||
const detected = makeDetectedResult('repo-ssh', [queued], {
|
||||
authoritative: false,
|
||||
source: 'metadata-fallback'
|
||||
})
|
||||
listKnownForExecutionHostMock.mockResolvedValueOnce({
|
||||
status: 'complete',
|
||||
repoId: sshRepo.id,
|
||||
executionHostId: 'ssh:ssh-1',
|
||||
result: detected
|
||||
})
|
||||
store.setState({
|
||||
repos: [sshRepo],
|
||||
sshConnectionStates: new Map([
|
||||
[
|
||||
'ssh-1',
|
||||
{
|
||||
targetId: 'ssh-1',
|
||||
status: 'connecting',
|
||||
error: null,
|
||||
reconnectAttempt: 0,
|
||||
providerEpoch: null
|
||||
}
|
||||
]
|
||||
])
|
||||
} as Partial<AppState>)
|
||||
|
||||
await expect(store.getState().fetchWorktrees(sshRepo.id)).resolves.toBe(false)
|
||||
|
||||
expect(store.getState().worktreesByRepo[sshRepo.id]).toEqual([
|
||||
{ ...queued, hostId: 'ssh:ssh-1' }
|
||||
])
|
||||
expect(listKnownForExecutionHostMock).toHaveBeenCalledWith({
|
||||
repoId: sshRepo.id,
|
||||
executionHostId: 'ssh:ssh-1'
|
||||
})
|
||||
expect(mockApi.worktrees.listDetected).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('adds metadata rows without replacing richer cached SSH worktrees', async () => {
|
||||
const store = createTestStore()
|
||||
const sshRepo = {
|
||||
id: 'repo-ssh',
|
||||
path: '/home/orca/repo',
|
||||
displayName: 'SSH Repo',
|
||||
badgeColor: '#000',
|
||||
addedAt: 0,
|
||||
connectionId: 'ssh-1'
|
||||
}
|
||||
const existing = makeWorktree({
|
||||
id: 'repo-ssh::/home/orca/existing',
|
||||
repoId: 'repo-ssh',
|
||||
path: '/home/orca/existing',
|
||||
hostId: 'ssh:ssh-1',
|
||||
head: 'live-head',
|
||||
branch: 'refs/heads/live-branch'
|
||||
})
|
||||
const metadataExisting = { ...existing, head: '', branch: '' }
|
||||
const queued = makeWorktree({
|
||||
id: 'repo-ssh::/home/orca/queued',
|
||||
repoId: 'repo-ssh',
|
||||
path: '/home/orca/queued'
|
||||
})
|
||||
listKnownForExecutionHostMock.mockResolvedValueOnce({
|
||||
status: 'complete',
|
||||
repoId: sshRepo.id,
|
||||
executionHostId: 'ssh:ssh-1',
|
||||
result: makeDetectedResult('repo-ssh', [metadataExisting, queued], {
|
||||
authoritative: false,
|
||||
source: 'metadata-fallback'
|
||||
})
|
||||
})
|
||||
store.setState({
|
||||
repos: [sshRepo],
|
||||
sshConnectionStates: new Map(),
|
||||
worktreesByRepo: { [sshRepo.id]: [existing] }
|
||||
} as Partial<AppState>)
|
||||
|
||||
await store.getState().fetchWorktrees(sshRepo.id)
|
||||
|
||||
expect(store.getState().worktreesByRepo[sshRepo.id]).toEqual([
|
||||
existing,
|
||||
{ ...queued, hostId: 'ssh:ssh-1' }
|
||||
])
|
||||
})
|
||||
|
||||
it('keeps worktree maps byte-identical for stale and malformed direct results', async () => {
|
||||
const store = createTestStore()
|
||||
const existing = makeWorktree({
|
||||
@@ -7536,6 +7644,63 @@ describe('fetchAllWorktrees hydration-time purge (design §4.4)', () => {
|
||||
addedAt: 0
|
||||
}
|
||||
|
||||
it.each([false, true])(
|
||||
'hydrates connecting SSH worktrees with hydration purge completed=%s',
|
||||
async (hasHydratedWorktreePurge) => {
|
||||
const store = createTestStore()
|
||||
const sshRepo = {
|
||||
id: 'repo-ssh',
|
||||
path: '/home/orca/repo',
|
||||
displayName: 'SSH Repo',
|
||||
badgeColor: '#000',
|
||||
addedAt: 0,
|
||||
connectionId: 'ssh-1'
|
||||
}
|
||||
const queued = makeWorktree({
|
||||
id: 'repo-ssh::/home/orca/queued',
|
||||
repoId: 'repo-ssh',
|
||||
path: '/home/orca/queued',
|
||||
displayName: 'queued'
|
||||
})
|
||||
listKnownForExecutionHostMock.mockResolvedValueOnce({
|
||||
status: 'complete',
|
||||
repoId: sshRepo.id,
|
||||
executionHostId: 'ssh:ssh-1',
|
||||
result: makeDetectedResult(sshRepo.id, [queued], {
|
||||
authoritative: false,
|
||||
source: 'metadata-fallback'
|
||||
})
|
||||
})
|
||||
store.setState({
|
||||
repos: [sshRepo],
|
||||
hasHydratedWorktreePurge,
|
||||
sshConnectionStates: new Map([
|
||||
[
|
||||
'ssh-1',
|
||||
{
|
||||
targetId: 'ssh-1',
|
||||
status: 'connecting',
|
||||
error: null,
|
||||
reconnectAttempt: 0,
|
||||
providerEpoch: null
|
||||
}
|
||||
]
|
||||
])
|
||||
} as Partial<AppState>)
|
||||
|
||||
await store.getState().fetchAllWorktrees()
|
||||
|
||||
expect(store.getState().worktreesByRepo[sshRepo.id]).toEqual([
|
||||
{ ...queued, hostId: 'ssh:ssh-1' }
|
||||
])
|
||||
expect(listKnownForExecutionHostMock).toHaveBeenCalledWith({
|
||||
repoId: sshRepo.id,
|
||||
executionHostId: 'ssh:ssh-1'
|
||||
})
|
||||
expect(mockApi.worktrees.listDetected).not.toHaveBeenCalled()
|
||||
}
|
||||
)
|
||||
|
||||
it('preserves resolved inline legacy lineage when side-map hydration is absent', async () => {
|
||||
const store = createTestStore()
|
||||
const parent = makeWorktree({
|
||||
|
||||
@@ -121,6 +121,7 @@ import {
|
||||
import { getTerminalActivationSpawnSuppression } from './terminal-activation-spawn-suppression'
|
||||
import type {
|
||||
HostQualifiedDetectedWorktreeResult,
|
||||
HostQualifiedKnownWorktreeResult,
|
||||
ListDetectedWorktreesArgs,
|
||||
ProviderRequestId,
|
||||
SshExecutionHostId
|
||||
@@ -2980,6 +2981,100 @@ function mergeFetchedWorktrees(
|
||||
return admitted
|
||||
}
|
||||
|
||||
function appendMissingWorktreesForHost<
|
||||
T extends { id: string; hostId?: ExecutionHostId; runtimeOwnerEnvironmentId?: string }
|
||||
>(
|
||||
current: readonly T[] | undefined,
|
||||
incoming: readonly T[],
|
||||
hostId: ExecutionHostId,
|
||||
options: WorktreeHostMatchOptions
|
||||
): T[] {
|
||||
const existing = current ?? []
|
||||
const existingHostIds = new Set(
|
||||
existing
|
||||
.filter((worktree) => worktreeMatchesHost(worktree, hostId, options))
|
||||
.map(({ id }) => id)
|
||||
)
|
||||
return [...existing, ...incoming.filter((worktree) => !existingHostIds.has(worktree.id))]
|
||||
}
|
||||
|
||||
function isAdmittedKnownSshWorktreeResult(
|
||||
result: HostQualifiedKnownWorktreeResult,
|
||||
repoId: string,
|
||||
executionHostId: SshExecutionHostId
|
||||
): result is Extract<HostQualifiedKnownWorktreeResult, { status: 'complete' }> {
|
||||
return (
|
||||
result.status === 'complete' &&
|
||||
result.repoId === repoId &&
|
||||
result.executionHostId === executionHostId &&
|
||||
isDetectedWorktreeListResult(result.result) &&
|
||||
result.result.repoId === repoId &&
|
||||
result.result.authoritative === false
|
||||
)
|
||||
}
|
||||
|
||||
async function fetchKnownSshWorktreesForRepo(
|
||||
set: Parameters<StateCreator<AppState, [], [], WorktreeSlice>>[0],
|
||||
repoId: string,
|
||||
executionHostId: SshExecutionHostId
|
||||
): Promise<DetectedWorktreeListResult | null> {
|
||||
const listKnown = window.api.worktrees.listKnownForExecutionHost
|
||||
if (typeof listKnown !== 'function') {
|
||||
return null
|
||||
}
|
||||
const result = await listKnown({ repoId, executionHostId })
|
||||
if (!isAdmittedKnownSshWorktreeResult(result, repoId, executionHostId)) {
|
||||
return null
|
||||
}
|
||||
let admitted = false
|
||||
set((state) => {
|
||||
if (!repoHasExactlyOneExecutionHostOwner(state, repoId, executionHostId, false)) {
|
||||
return state
|
||||
}
|
||||
admitted = true
|
||||
const setup = getProjectHostSetupForRepoHost(state, repoId, executionHostId)
|
||||
const matchOptions = worktreeHostMatchOptions(state, repoId, executionHostId)
|
||||
const incomingDetected = result.result.worktrees.map((worktree) =>
|
||||
withRepoHostOwnership(worktree, executionHostId, setup)
|
||||
)
|
||||
const detected = {
|
||||
...result.result,
|
||||
worktrees: appendMissingWorktreesForHost(
|
||||
state.detectedWorktreesByRepo[repoId]?.worktrees,
|
||||
incomingDetected,
|
||||
executionHostId,
|
||||
matchOptions
|
||||
)
|
||||
}
|
||||
const worktrees = appendMissingWorktreesForHost(
|
||||
state.worktreesByRepo[repoId],
|
||||
toVisibleWorktrees(result.result, executionHostId, setup),
|
||||
executionHostId,
|
||||
matchOptions
|
||||
)
|
||||
const worktreesChanged = !areWorktreesEqual(state.worktreesByRepo[repoId], worktrees)
|
||||
const detectedChanged = !areDetectedWorktreeResultsEqual(
|
||||
state.detectedWorktreesByRepo[repoId],
|
||||
detected
|
||||
)
|
||||
if (!worktreesChanged && !detectedChanged) {
|
||||
return state
|
||||
}
|
||||
return {
|
||||
...(worktreesChanged
|
||||
? {
|
||||
worktreesByRepo: { ...state.worktreesByRepo, [repoId]: worktrees },
|
||||
sortEpoch: state.sortEpoch + 1
|
||||
}
|
||||
: {}),
|
||||
...(detectedChanged
|
||||
? { detectedWorktreesByRepo: { ...state.detectedWorktreesByRepo, [repoId]: detected } }
|
||||
: {})
|
||||
}
|
||||
})
|
||||
return admitted ? result.result : null
|
||||
}
|
||||
|
||||
export type DirectSshDetectedWorktreeRefresh = {
|
||||
waiterLeaseId: DetectedWorktreeRefreshLease['waiterLeaseId']
|
||||
providerRequestId: ProviderRequestId
|
||||
@@ -3110,7 +3205,7 @@ export const createWorktreeSlice: StateCreator<AppState, [], [], WorktreeSlice>
|
||||
? (getCurrentDirectSshAuthority(ownerState, hostId) ?? undefined)
|
||||
: undefined
|
||||
if (parsedHost?.kind === 'ssh' && !directSshAuthority) {
|
||||
return null
|
||||
return fetchKnownSshWorktreesForRepo(set, repoId, parsedHost.id)
|
||||
}
|
||||
const refresh = await listDetectedWorktreesForRepoCoalesced(
|
||||
settingsForRepoOwner(ownerState, repoId, hostId),
|
||||
@@ -3208,6 +3303,7 @@ export const createWorktreeSlice: StateCreator<AppState, [], [], WorktreeSlice>
|
||||
? (directCallerAuthority ?? getCurrentDirectSshAuthority(ownerState, hostId) ?? undefined)
|
||||
: undefined
|
||||
if (parsedHost?.kind === 'ssh' && !directSshAuthority) {
|
||||
await fetchKnownSshWorktreesForRepo(set, repoId, parsedHost.id)
|
||||
return false
|
||||
}
|
||||
const refresh = await listDetectedWorktreesForRepoCoalesced(settings, repoId, {
|
||||
@@ -3272,6 +3368,7 @@ export const createWorktreeSlice: StateCreator<AppState, [], [], WorktreeSlice>
|
||||
? (getCurrentDirectSshAuthority(requestStartedState, hostId) ?? undefined)
|
||||
: undefined
|
||||
if (parsedHost?.kind === 'ssh' && !directSshAuthority) {
|
||||
await fetchKnownSshWorktreesForRepo(set, r.id, parsedHost.id)
|
||||
return
|
||||
}
|
||||
const refresh = await listDetectedWorktreesForRepoCoalesced(settings, r.id, {
|
||||
@@ -3323,6 +3420,7 @@ export const createWorktreeSlice: StateCreator<AppState, [], [], WorktreeSlice>
|
||||
? (getCurrentDirectSshAuthority(requestStartedState, hostId) ?? undefined)
|
||||
: undefined
|
||||
if (parsedHost?.kind === 'ssh' && !directSshAuthority) {
|
||||
await fetchKnownSshWorktreesForRepo(set, r.id, parsedHost.id)
|
||||
return { repoId: r.id, ok: false as const }
|
||||
}
|
||||
const refresh = await listDetectedWorktreesForRepoCoalesced(
|
||||
|
||||
@@ -23,6 +23,24 @@ export type ListDetectedWorktreesArgs =
|
||||
| LocalDetectedWorktreeRequest
|
||||
| DirectSshDetectedWorktreeRequest
|
||||
|
||||
export type ListKnownWorktreesForExecutionHostArgs = {
|
||||
repoId: string
|
||||
executionHostId: SshExecutionHostId
|
||||
}
|
||||
|
||||
export type HostQualifiedKnownWorktreeResult =
|
||||
| {
|
||||
status: 'complete'
|
||||
repoId: string
|
||||
executionHostId: SshExecutionHostId
|
||||
result: DetectedWorktreeListResult
|
||||
}
|
||||
| {
|
||||
status: 'rejected'
|
||||
repoId: string
|
||||
executionHostId: SshExecutionHostId
|
||||
}
|
||||
|
||||
export type AuthoritativeDetectedWorktreeHost =
|
||||
| {
|
||||
kind: 'local'
|
||||
|
||||
Reference in New Issue
Block a user