refactor(repo-host): one helper for the hosts registering a repo id (#31634)

Four twin checks (sort stamps, sidebar unread, sort-order notify, worktree-meta owner)
each rebuilt new Set(repos.filter(id).map(getRepoExecutionHostId)). Name it once as
getRepoRegistrationHostIds in shared/repo-host-identity.
This commit is contained in:
Neil
2026-10-21 13:31:46 -07:00
committed by GitHub
parent 7511300aa7
commit 87ca5dad43
6 changed files with 37 additions and 29 deletions
@@ -1,3 +1,4 @@
import { getRepoRegistrationHostIds } from '../../shared/repo-host-identity'
import { getRepoExecutionHostId, type ExecutionHostId } from '../../shared/execution-host'
import type { Repo } from '../../shared/repo-types'
import type { WorktreeMeta } from '../../shared/worktree/meta-types'
@@ -86,7 +87,7 @@ export function resolveWorktreeMetaOwner(
const repoId = getRepoIdFromWorktreeId(worktreeId)
const owners = store.getRepos().filter((repo) => repo.id === repoId)
// Why: the raw-id row's stamp names whichever twin wrote first, not the twin the caller means.
if (new Set(owners.map(getRepoExecutionHostId)).size > 1) {
if (getRepoRegistrationHostIds(owners, repoId).size > 1) {
return undefined
}
const stamped = store.getWorktreeMeta(worktreeId)?.hostId
@@ -1,8 +1,8 @@
import { getRepoRegistrationHostIds } from '../../shared/repo-host-identity'
import type { GitPushTarget, Worktree } from '../../shared/worktree/types'
import type { WorktreeMeta } from '../../shared/worktree/meta-types'
import { worktreeWorkspaceKey } from '../../shared/workspace-scope'
import { splitWorktreeId } from '../../shared/worktree/id'
import { getRepoExecutionHostId } from '../../shared/execution-host'
import type { WorktreeSortOrderUpdate } from '../../shared/worktree/sort-order-update'
import { stripOrcaProvenanceMetaUpdates } from '../worktree-removal-safety'
import { isRemovedFolderWorkspaceInstance } from '../folder-workspace-meta-presence'
@@ -174,13 +174,8 @@ export function persistRuntimeManagedWorktreeSortOrder(args: {
}
}
for (const [repoId, sortOrders] of updatesByRepo) {
const hosts = new Set(
args.store
.getRepos()
.filter((repo) => repo.id === repoId)
.map(getRepoExecutionHostId)
)
args.notifyChanged(repoId, hosts.size > 1 ? undefined : sortOrders)
const isTwin = getRepoRegistrationHostIds(args.store.getRepos(), repoId).size > 1
args.notifyChanged(repoId, isTwin ? undefined : sortOrders)
}
return { updated: updates.length }
}
@@ -1,3 +1,4 @@
import { getRepoRegistrationHostIds } from '../../../shared/repo-host-identity'
import { isRemoteRuntimePtyId } from '../../../shared/remote-runtime-pty-id'
import { OrcaRuntimeWithDetectedCheckoutsAndLiveContent } from './orca-runtime-detected-checkouts-live-content'
import type { OrcaRuntimeWithAgentTransitions } from '../orca-runtime-agent-transitions'
@@ -5,7 +6,6 @@ import { ORCA_SIDEBAR_INPUTS_IS_MAIN } from '../../../shared/sidebar-inputs-swit
import { DEFAULT_HIDE_SLEEPING_WORKSPACES } from '../../../shared/constants'
import {
getConnectionExecutionHostId,
getRepoExecutionHostId,
isLocalExecutionHost,
LOCAL_EXECUTION_HOST_ID,
parseExecutionHostId,
@@ -202,12 +202,7 @@ export abstract class OrcaRuntimeWithSidebarTemporal extends OrcaRuntimeWithDete
return false
}
const repoId = getRepoIdFromWorktreeId(worktreeId)
const hosts = new Set(
(this.store?.getRepos() ?? [])
.filter((repo) => repo.id === repoId)
.map(getRepoExecutionHostId)
)
return hosts.size > 1
return getRepoRegistrationHostIds(this.store?.getRepos() ?? [], repoId).size > 1
}
private workspaceHostId(worktreeId: string): ExecutionHostId | null {
@@ -1,9 +1,6 @@
import { getRepoRegistrationHostIds } from '../../../shared/repo-host-identity'
import type { AgentPaneWorkspace } from '../../agent-hooks/agent-transition-detector'
import {
getRepoExecutionHostId,
LOCAL_EXECUTION_HOST_ID,
type ExecutionHostId
} from '../../../shared/execution-host'
import { LOCAL_EXECUTION_HOST_ID, type ExecutionHostId } from '../../../shared/execution-host'
import { getRepoIdFromWorktreeId } from '../../../shared/worktree/id'
import type {
SmartStampFields,
@@ -70,13 +67,7 @@ function localTwinHostId(
store: Pick<WorkspaceRecordStore<unknown, never>, 'getRepos'>,
worktreeId: string
): ExecutionHostId | null {
const repoId = getRepoIdFromWorktreeId(worktreeId)
const hosts = new Set(
store
.getRepos()
.filter((repo) => repo.id === repoId)
.map(getRepoExecutionHostId)
)
const hosts = getRepoRegistrationHostIds(store.getRepos(), getRepoIdFromWorktreeId(worktreeId))
return hosts.size > 1 && hosts.has(LOCAL_EXECUTION_HOST_ID) ? LOCAL_EXECUTION_HOST_ID : null
}
+18 -1
View File
@@ -1,6 +1,10 @@
import { describe, expect, expectTypeOf, it } from 'vitest'
import { toRuntimeExecutionHostId, toSshExecutionHostId } from './execution-host'
import { getRepoHostIdentityForParts, type HostScopedRepoKey } from './repo-host-identity'
import {
getRepoHostIdentityForParts,
getRepoRegistrationHostIds,
type HostScopedRepoKey
} from './repo-host-identity'
// Why: these keys index persisted and cross-module repo state, so the bytes are frozen (D25).
describe('repo host identity golden bytes', () => {
@@ -29,3 +33,16 @@ describe('repo host identity golden bytes', () => {
expect(raw).toBe(forged)
})
})
describe('getRepoRegistrationHostIds', () => {
it('collects each host registering the id once, across both SSH spellings', () => {
const repos = [
{ id: 'repo-1' },
{ id: 'repo-1', connectionId: 'box' },
{ id: 'repo-1', executionHostId: 'ssh:box' as const },
{ id: 'repo-2', executionHostId: 'runtime:env' as const }
]
expect(getRepoRegistrationHostIds(repos, 'repo-1')).toEqual(new Set(['local', 'ssh:box']))
expect(getRepoRegistrationHostIds(repos, 'missing').size).toBe(0)
})
})
+9
View File
@@ -1,3 +1,4 @@
import { getRepoExecutionHostId, type ExecutionHostId } from './execution-host'
import type { Repo } from './repo-types'
declare const hostScopedRepoKeyBrand: unique symbol
@@ -14,3 +15,11 @@ export function getRepoHostIdentityForParts(repoId: string, hostId: string): Hos
// oxlint-disable-next-line typescript/consistent-type-assertions -- SAFETY: this is the only constructor of the brand; the bytes are the unbranded composite, frozen by golden tests (D25).
return `${hostId}\0${repoId}` as HostScopedRepoKey
}
/** The hosts registering `repoId`; two or more means same-id twins on different hosts. */
export function getRepoRegistrationHostIds(
repos: readonly RepoHostTarget[],
repoId: string
): Set<ExecutionHostId> {
return new Set(repos.filter((repo) => repo.id === repoId).map(getRepoExecutionHostId))
}