mirror of
https://github.com/stablyai/orca.git
synced 2026-09-29 16:02:50 +00:00
`RuntimeGitTarget` carried `connectionId?: string` and no host id, so `undefined` spelled three different answers at once — "runtime: host", "unresolved", and "genuinely local". Its sole resolver read `store.getRepo(worktree.repoId)?.connectionId` and never looked at `worktree.hostId`, which outranks every repo row, so one arbitrarily chosen row decided the execution host for 36 downstream dispatches. The target now carries `executionHostId: ExecutionHostId` (never null, never optional), resolved through the shared rule that landed with #17909/#17919 and dispatched through the host-keyed routes from #18296. Dispatch sites call `requireRuntimeGitProvider`, where `null` means exactly one thing: the host is `local` and the command runs here as free functions. Four answers that used to collapse into one: - `ssh:x` with a rival row on `ssh:y` — routes to x. Previously the first row won, which is the reproduced cross-host leak. - `local` with a surviving `connectionId` — a row contradicting itself; no SSH connection is handed out. - `runtime:<env>` — throws `ExecutionHostNotDispatchableError`. Its repo row's connection names a target in the *server's* namespace; dialling it here reaches a same-named target on this client. - rival rows disagreeing with no worktree host — `worktree_execution_host_unresolved`, matching the launch path rather than guessing a row. An unreachable SSH host still throws `SSH_GIT_PROVIDER_UNAVAILABLE_MESSAGE`; loss of contact is never evidence of locality (docs/reference/ssh-execution-boundary.md). `resolveWorktreeLaunchHost` keeps its exact signature and now delegates to `resolveWorktreeHostRouting`, the same resolution answering "which host is this on" rather than "what may this client dial" — the git target needs the first question because `local` and `runtime:` are two different non-SSH answers. No wire change: `RuntimeGitTarget` is main-process internal, and the SSH and local model-discovery host keys are byte-identical to before. `RuntimeFileTarget` has the same defect in ~30 filesystem dispatches and is deliberately left for a follow-up.
198 lines
6.6 KiB
TypeScript
198 lines
6.6 KiB
TypeScript
import type {
|
|
GitBranchCompareResult,
|
|
GitCommitCompareResult,
|
|
GitDiffResult
|
|
} from '../../shared/git-diff-compare-types'
|
|
import { assertGitDiffWithinTransportBudget } from '../../shared/git-diff-transport-budget'
|
|
import { getRemoteCommitUrl, getRemoteFileUrl } from '../git/repo'
|
|
import {
|
|
getBranchCompare,
|
|
getBranchDiff,
|
|
getCommitCompare,
|
|
getCommitDiff,
|
|
getDiff
|
|
} from '../git/status'
|
|
import { awaitWindowsHostGitEnvironmentReady } from '../git/runner'
|
|
import type { GitAdmissionTier } from '../git/command-runner/git-exec-options'
|
|
import { normalizeRuntimeRelativePath } from './runtime-relative-paths'
|
|
import {
|
|
localGitOptionsForTarget,
|
|
normalizeRuntimeGitRelativePath,
|
|
requireRuntimeGitProvider,
|
|
type RuntimeGitCommandHost
|
|
} from './runtime-git-command-target'
|
|
|
|
export class RuntimeGitDiffCommands {
|
|
constructor(private readonly host: RuntimeGitCommandHost) {}
|
|
|
|
// Why: cap both local and forwarded SSH payloads after execution-host dispatch.
|
|
async getRuntimeGitDiff(
|
|
worktreeSelector: string,
|
|
filePath: string,
|
|
staged: boolean,
|
|
compareAgainstHead?: boolean,
|
|
maxContentBytes?: number
|
|
): Promise<GitDiffResult> {
|
|
const target = await this.host.resolveRuntimeGitTarget(worktreeSelector)
|
|
const relativePath = normalizeRuntimeGitRelativePath(filePath)
|
|
const provider = requireRuntimeGitProvider(target)
|
|
if (provider) {
|
|
return assertGitDiffWithinTransportBudget(
|
|
await provider.getDiff(target.worktree.path, relativePath, staged, compareAgainstHead),
|
|
maxContentBytes
|
|
)
|
|
}
|
|
return assertGitDiffWithinTransportBudget(
|
|
await getDiff(target.worktree.path, relativePath, staged, compareAgainstHead, {
|
|
...localGitOptionsForTarget(target),
|
|
admissionTier: 'interactive'
|
|
}),
|
|
maxContentBytes
|
|
)
|
|
}
|
|
|
|
async getRuntimeGitBranchCompare(
|
|
worktreeSelector: string,
|
|
baseRef: string,
|
|
admissionTier: GitAdmissionTier = 'interactive'
|
|
): Promise<GitBranchCompareResult> {
|
|
const target = await this.host.resolveRuntimeGitTarget(worktreeSelector)
|
|
const provider = requireRuntimeGitProvider(target)
|
|
if (provider) {
|
|
return provider.getBranchCompare(target.worktree.path, baseRef, { admissionTier })
|
|
}
|
|
return getBranchCompare(target.worktree.path, baseRef, {
|
|
...localGitOptionsForTarget(target),
|
|
admissionTier
|
|
})
|
|
}
|
|
|
|
async getRuntimeGitCommitCompare(
|
|
worktreeSelector: string,
|
|
commitId: string
|
|
): Promise<GitCommitCompareResult> {
|
|
const target = await this.host.resolveRuntimeGitTarget(worktreeSelector)
|
|
const provider = requireRuntimeGitProvider(target)
|
|
if (provider) {
|
|
return provider.getCommitCompare(target.worktree.path, commitId)
|
|
}
|
|
return getCommitCompare(target.worktree.path, commitId, {
|
|
...localGitOptionsForTarget(target),
|
|
admissionTier: 'interactive'
|
|
})
|
|
}
|
|
|
|
async getRuntimeGitBranchDiff(
|
|
worktreeSelector: string,
|
|
compare: { mergeBase: string; headOid: string },
|
|
filePath: string,
|
|
oldPath?: string,
|
|
maxContentBytes?: number
|
|
): Promise<GitDiffResult> {
|
|
const target = await this.host.resolveRuntimeGitTarget(worktreeSelector)
|
|
const relativePath = normalizeRuntimeGitRelativePath(filePath)
|
|
const oldRelativePath = oldPath ? normalizeRuntimeGitRelativePath(oldPath) : undefined
|
|
const provider = requireRuntimeGitProvider(target)
|
|
if (provider) {
|
|
const results = await provider.getBranchDiff(target.worktree.path, compare.mergeBase, {
|
|
includePatch: true,
|
|
headOid: compare.headOid,
|
|
filePath: relativePath,
|
|
oldPath: oldRelativePath
|
|
})
|
|
return assertGitDiffWithinTransportBudget(
|
|
results[0] ?? {
|
|
kind: 'text',
|
|
originalContent: '',
|
|
modifiedContent: '',
|
|
originalIsBinary: false,
|
|
modifiedIsBinary: false
|
|
},
|
|
maxContentBytes
|
|
)
|
|
}
|
|
return assertGitDiffWithinTransportBudget(
|
|
await getBranchDiff(
|
|
target.worktree.path,
|
|
{
|
|
mergeBase: compare.mergeBase,
|
|
headOid: compare.headOid,
|
|
filePath: relativePath,
|
|
oldPath: oldRelativePath
|
|
},
|
|
{
|
|
...localGitOptionsForTarget(target),
|
|
admissionTier: 'interactive'
|
|
}
|
|
),
|
|
maxContentBytes
|
|
)
|
|
}
|
|
|
|
async getRuntimeGitCommitDiff(
|
|
worktreeSelector: string,
|
|
args: { commitOid: string; parentOid?: string | null; filePath: string; oldPath?: string },
|
|
maxContentBytes?: number
|
|
): Promise<GitDiffResult> {
|
|
const target = await this.host.resolveRuntimeGitTarget(worktreeSelector)
|
|
const relativePath = normalizeRuntimeRelativePath(args.filePath)
|
|
const oldRelativePath = args.oldPath ? normalizeRuntimeRelativePath(args.oldPath) : undefined
|
|
const provider = requireRuntimeGitProvider(target)
|
|
if (provider) {
|
|
return assertGitDiffWithinTransportBudget(
|
|
await provider.getCommitDiff(target.worktree.path, {
|
|
commitOid: args.commitOid,
|
|
parentOid: args.parentOid,
|
|
filePath: relativePath,
|
|
oldPath: oldRelativePath
|
|
}),
|
|
maxContentBytes
|
|
)
|
|
}
|
|
return assertGitDiffWithinTransportBudget(
|
|
await getCommitDiff(
|
|
target.worktree.path,
|
|
{
|
|
commitOid: args.commitOid,
|
|
parentOid: args.parentOid,
|
|
filePath: relativePath,
|
|
oldPath: oldRelativePath
|
|
},
|
|
{
|
|
...localGitOptionsForTarget(target),
|
|
admissionTier: 'interactive'
|
|
}
|
|
),
|
|
maxContentBytes
|
|
)
|
|
}
|
|
|
|
async getRuntimeGitRemoteFileUrl(
|
|
worktreeSelector: string,
|
|
relativePath: string,
|
|
line: number
|
|
): Promise<string | null> {
|
|
const target = await this.host.resolveRuntimeGitTarget(worktreeSelector)
|
|
const normalizedRelativePath = normalizeRuntimeGitRelativePath(relativePath)
|
|
const provider = requireRuntimeGitProvider(target)
|
|
if (provider) {
|
|
return provider.getRemoteFileUrl(target.worktree.path, normalizedRelativePath, line)
|
|
}
|
|
await awaitWindowsHostGitEnvironmentReady({ cwd: target.worktree.path })
|
|
return getRemoteFileUrl(target.worktree.path, normalizedRelativePath, line)
|
|
}
|
|
|
|
async getRuntimeGitRemoteCommitUrl(
|
|
worktreeSelector: string,
|
|
sha: string
|
|
): Promise<string | null> {
|
|
const target = await this.host.resolveRuntimeGitTarget(worktreeSelector)
|
|
const provider = requireRuntimeGitProvider(target)
|
|
if (provider) {
|
|
return provider.getRemoteCommitUrl(target.worktree.path, sha)
|
|
}
|
|
await awaitWindowsHostGitEnvironmentReady({ cwd: target.worktree.path })
|
|
return getRemoteCommitUrl(target.worktree.path, sha)
|
|
}
|
|
}
|