mirror of
https://github.com/stablyai/orca.git
synced 2026-10-18 00:03:20 +00:00
Fix worktree host ownership consistency
This commit is contained in:
@@ -215,6 +215,7 @@ import {
|
||||
ALL_EXECUTION_HOSTS_SCOPE,
|
||||
getRepoExecutionHostId,
|
||||
getSettingsFocusedExecutionHostId,
|
||||
getWorktreeExecutionHostId,
|
||||
type ExecutionHostId
|
||||
} from '../../../../shared/execution-host'
|
||||
import { getRepoHeaderCreateState } from './repo-header-create-state'
|
||||
@@ -2115,10 +2116,7 @@ const VirtualizedWorktreeViewport = React.memo(function VirtualizedWorktreeViewp
|
||||
const targetWorktree = worktrees.find((w) => w.id === pendingRevealWorktree.worktreeId)
|
||||
const targetRepo = targetWorktree ? repoMap.get(targetWorktree.repoId) : undefined
|
||||
if (targetWorktree) {
|
||||
const hostId =
|
||||
targetRepo && (targetRepo.connectionId || targetRepo.executionHostId)
|
||||
? getRepoExecutionHostId(targetRepo)
|
||||
: defaultHostId
|
||||
const hostId = getWorktreeExecutionHostId(targetWorktree, targetRepo, defaultHostId)
|
||||
const hostGroupKey = `host:${hostId}`
|
||||
if (collapsedGroups.has(hostGroupKey)) {
|
||||
toggleGroup(hostGroupKey)
|
||||
|
||||
@@ -3,6 +3,7 @@ import {
|
||||
LOCAL_EXECUTION_HOST_ID,
|
||||
getLocalExecutionHostLabel,
|
||||
getRepoExecutionHostId,
|
||||
getWorktreeExecutionHostId,
|
||||
type ExecutionHostId,
|
||||
type ExecutionHostKind,
|
||||
type ExecutionHostScope
|
||||
@@ -69,9 +70,7 @@ function getFolderWorkspaceHostId(
|
||||
function getRowHostId(row: Row, defaultHostId: ExecutionHostId): ExecutionHostId | null {
|
||||
switch (row.type) {
|
||||
case 'item':
|
||||
// Why: runtime and SSH snapshots can carry a more precise owner than the
|
||||
// repo fallback; header localization already uses this worktree identity.
|
||||
return row.worktree.hostId ?? getRepoHostId(row.repo, defaultHostId)
|
||||
return getWorktreeExecutionHostId(row.worktree, row.repo, defaultHostId)
|
||||
case 'pending-creation':
|
||||
case 'imported-worktrees-card':
|
||||
case 'new-external-worktrees-inbox':
|
||||
|
||||
@@ -329,6 +329,25 @@ describe('computeVisibleWorktreeIds', () => {
|
||||
expect(result).toEqual([local.id])
|
||||
})
|
||||
|
||||
it('uses explicit worktree ownership when it differs from the repo host', () => {
|
||||
const runtime = makeWorktree('runtime', 'repo1')
|
||||
runtime.hostId = 'runtime:env-1'
|
||||
|
||||
const runtimeResult = computeVisibleWorktreeIds(
|
||||
{ repo1: [runtime] },
|
||||
[runtime.id],
|
||||
visibleOptions({ workspaceHostScope: 'runtime:env-1' })
|
||||
)
|
||||
const localResult = computeVisibleWorktreeIds(
|
||||
{ repo1: [runtime] },
|
||||
[runtime.id],
|
||||
visibleOptions({ workspaceHostScope: 'local' })
|
||||
)
|
||||
|
||||
expect(runtimeResult).toEqual([runtime.id])
|
||||
expect(localResult).toEqual([])
|
||||
})
|
||||
|
||||
it('keeps every host visible when workspace host scope is all', () => {
|
||||
const local = makeWorktree('local', 'repo1')
|
||||
const remote = makeWorktree('remote', 'repo2')
|
||||
|
||||
@@ -6,8 +6,8 @@ import { getAllWorktreesFromState, getRepoMapFromState } from '@/store/selectors
|
||||
import { DEFAULT_SHOW_SLEEPING_WORKSPACES } from '../../../../shared/constants'
|
||||
import {
|
||||
ALL_EXECUTION_HOSTS_SCOPE,
|
||||
getRepoExecutionHostId,
|
||||
getSettingsFocusedExecutionHostId,
|
||||
getWorktreeExecutionHostId,
|
||||
type ExecutionHostId,
|
||||
type ExecutionHostScope
|
||||
} from '../../../../shared/execution-host'
|
||||
@@ -153,10 +153,7 @@ export function computeVisibleWorktreeIds(
|
||||
if (!repo) {
|
||||
return false
|
||||
}
|
||||
const hostId =
|
||||
repo.connectionId || repo.executionHostId
|
||||
? getRepoExecutionHostId(repo)
|
||||
: opts.defaultHostId
|
||||
const hostId = getWorktreeExecutionHostId(w, repo, opts.defaultHostId)
|
||||
return visibleHostIdSet.has(hostId)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -38,6 +38,7 @@ import {
|
||||
getExecutionHostLabel,
|
||||
LOCAL_EXECUTION_HOST_ID,
|
||||
getRepoExecutionHostId,
|
||||
getWorktreeExecutionHostId,
|
||||
type ExecutionHostId
|
||||
} from '../../../../shared/execution-host'
|
||||
import { parseWslUncPath } from '../../../../shared/wsl-paths'
|
||||
@@ -476,7 +477,7 @@ function emitPinnedGroup(
|
||||
const pinnedRepoOrder: string[] = []
|
||||
const seenPinnedRepoIds = new Set<string>()
|
||||
for (const worktree of pinned) {
|
||||
const hostId = getWorktreeHostId(worktree, repoMap, defaultHostId)
|
||||
const hostId = getWorktreeExecutionHostId(worktree, repoMap.get(worktree.repoId), defaultHostId)
|
||||
hostWorktreeCounts.set(hostId, (hostWorktreeCounts.get(hostId) ?? 0) + 1)
|
||||
const hostIds = hostWorktreeIds.get(hostId) ?? []
|
||||
hostIds.push(worktree.id)
|
||||
@@ -730,20 +731,6 @@ function getMixedHostContextLabels(
|
||||
return uniqueLabels.size > 1 ? labelsByRepoId : undefined
|
||||
}
|
||||
|
||||
function getWorktreeHostId(
|
||||
worktree: Worktree,
|
||||
repoMap: Map<string, Repo>,
|
||||
defaultHostId: ExecutionHostId
|
||||
): ExecutionHostId {
|
||||
if (worktree.hostId) {
|
||||
return worktree.hostId
|
||||
}
|
||||
const repo = repoMap.get(worktree.repoId)
|
||||
return repo && (repo.connectionId || repo.executionHostId)
|
||||
? getRepoExecutionHostId(repo)
|
||||
: defaultHostId
|
||||
}
|
||||
|
||||
function getHostWorktreeCounts(
|
||||
worktrees: readonly Worktree[],
|
||||
repoMap: Map<string, Repo>,
|
||||
@@ -759,7 +746,7 @@ function getHostWorktreeCounts(
|
||||
continue
|
||||
}
|
||||
seenWorktreeIds.add(worktree.id)
|
||||
const hostId = getWorktreeHostId(worktree, repoMap, defaultHostId)
|
||||
const hostId = getWorktreeExecutionHostId(worktree, repoMap.get(worktree.repoId), defaultHostId)
|
||||
counts.set(hostId, (counts.get(hostId) ?? 0) + 1)
|
||||
}
|
||||
return counts
|
||||
@@ -780,7 +767,7 @@ function getHostWorktreeIds(
|
||||
continue
|
||||
}
|
||||
seenWorktreeIds.add(worktree.id)
|
||||
const hostId = getWorktreeHostId(worktree, repoMap, defaultHostId)
|
||||
const hostId = getWorktreeExecutionHostId(worktree, repoMap.get(worktree.repoId), defaultHostId)
|
||||
const ids = idsByHost.get(hostId) ?? []
|
||||
ids.push(worktree.id)
|
||||
idsByHost.set(hostId, ids)
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { getRepoExecutionHostId } from '../../../../shared/execution-host'
|
||||
import { getWorktreeExecutionHostId } from '../../../../shared/execution-host'
|
||||
import type { Repo, Worktree, WorktreeLineage } from '../../../../shared/types'
|
||||
import { canAssignWorktreeParent } from './worktree-parent-eligibility'
|
||||
|
||||
@@ -15,7 +15,7 @@ function getWorktreeOwnerHostId(
|
||||
repoMap: Map<string, Pick<Repo, 'connectionId' | 'executionHostId'>>
|
||||
): string | null {
|
||||
const repo = repoMap.get(worktree.repoId)
|
||||
return worktree.hostId ?? (repo ? getRepoExecutionHostId(repo) : null)
|
||||
return repo ? getWorktreeExecutionHostId(worktree, repo) : (worktree.hostId ?? null)
|
||||
}
|
||||
|
||||
export function getEligibleWorktreeParents({
|
||||
|
||||
@@ -5,6 +5,7 @@ import {
|
||||
getLocalExecutionHostLabel,
|
||||
getRepoExecutionHostId,
|
||||
getSettingsFocusedExecutionHostId,
|
||||
getWorktreeExecutionHostId,
|
||||
normalizeExecutionHostOrder,
|
||||
normalizeExecutionHostScope,
|
||||
normalizeVisibleExecutionHostIds,
|
||||
@@ -87,6 +88,20 @@ describe('execution host identity', () => {
|
||||
expect(getRepoExecutionHostId({ connectionId: 'ssh-target-1' })).toBe('ssh:ssh-target-1')
|
||||
})
|
||||
|
||||
it('prefers explicit worktree ownership before repo and focused-host fallbacks', () => {
|
||||
expect(
|
||||
getWorktreeExecutionHostId(
|
||||
{ hostId: 'runtime:workspace-owner' },
|
||||
{ connectionId: 'repo-owner' },
|
||||
'runtime:focused-host'
|
||||
)
|
||||
).toBe('runtime:workspace-owner')
|
||||
expect(
|
||||
getWorktreeExecutionHostId({}, { connectionId: 'repo-owner' }, 'runtime:focused-host')
|
||||
).toBe('ssh:repo-owner')
|
||||
expect(getWorktreeExecutionHostId({}, {}, 'runtime:focused-host')).toBe('runtime:focused-host')
|
||||
})
|
||||
|
||||
it('derives focused host compatibility from active runtime settings', () => {
|
||||
expect(getSettingsFocusedExecutionHostId(null)).toBe(LOCAL_EXECUTION_HOST_ID)
|
||||
expect(getSettingsFocusedExecutionHostId({ activeRuntimeEnvironmentId: 'runtime-1' })).toBe(
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { GlobalSettings, Repo } from './types'
|
||||
import type { GlobalSettings, Repo, Worktree } from './types'
|
||||
|
||||
export const LOCAL_EXECUTION_HOST_ID = 'local'
|
||||
export const ALL_EXECUTION_HOSTS_SCOPE = 'all'
|
||||
@@ -150,6 +150,19 @@ export function getRepoExecutionHostId(
|
||||
return connectionId ? toSshExecutionHostId(connectionId) : LOCAL_EXECUTION_HOST_ID
|
||||
}
|
||||
|
||||
export function getWorktreeExecutionHostId(
|
||||
worktree: Pick<Worktree, 'hostId'>,
|
||||
repo: Pick<Repo, 'connectionId' | 'executionHostId'> | undefined,
|
||||
defaultHostId: ExecutionHostId = LOCAL_EXECUTION_HOST_ID
|
||||
): ExecutionHostId {
|
||||
// Why: runtime and SSH snapshots can identify a more precise owner than
|
||||
// the repo fallback; every sidebar host decision must use the same precedence.
|
||||
return (
|
||||
worktree.hostId ??
|
||||
(repo?.connectionId || repo?.executionHostId ? getRepoExecutionHostId(repo) : defaultHostId)
|
||||
)
|
||||
}
|
||||
|
||||
export function getSettingsFocusedExecutionHostId(
|
||||
settings: Pick<GlobalSettings, 'activeRuntimeEnvironmentId'> | null | undefined
|
||||
): ExecutionHostId {
|
||||
|
||||
Reference in New Issue
Block a user