mirror of
https://github.com/stablyai/orca.git
synced 2026-09-30 00:03:15 +00:00
`ForgeProvider.createReview(repoPath, input, connectionId, options)` and the `connectionId` on `ForgeProviderRepositoryContext` carried the same collapse the five prior migrations closed: `string | null` spells "genuinely local", "runtime host" and "could not resolve" with one value. Because it was decided two layers up -- `repo.connectionId ?? null` at the `hostedReview:*` IPC handlers and in `RuntimeHostedReviewCommands` -- a row naming its owner only as `executionHostId: ssh:<target>` ran the whole review path against this machine's copy of a remote path (#11163): `git rev-parse`, `git status`, the base-on-remote ref probe, the upstream divergence read, and `gh`/`glab` with no host flags. Replace it with a required `ExecutionHostId` threaded from the decision point through the contract, routed by #18296's `resolveGitRouteForHost`. The parameter is removed rather than added beside, so all five implementations -- GitLab, GitHub, Bitbucket, Azure DevOps, Gitea -- and every caller became a compile error. None of these families carries `@ts-nocheck`, so unlike #18325 that guarantee is real here; `orca-runtime-file-commands.ts` does, but it only constructs `RuntimeHostedReviewCommands` with unchanged deps. Also fixed at the sites: - The branch cache scoped entries on `connectionId ?? ''`, so two rows at one path on different hosts shared one cached review, one backoff deadline and one invalidation. Keyed on the resolved host now, as #18377 did for its probe key. - `hostedReview:create` resolved shared symlink paths and normalized worktree paths off the raw field, so an `executionHostId`-only SSH row read `orca.yaml` and `resolve()`d a remote POSIX path on the client. Those ask the file-holder question -- `getRepoSshConnectionId` -- not the dialable one. - An SSH host with no provider now refuses inside the git-state layer instead of reaching the local branch, keeping "remote and unreachable" distinct from "local" (docs/reference/ssh-execution-boundary.md). `runtime:` is a routing mistake inside `hostedReviewSshConnectionId` -- that environment's server runs its own git, and the SSH target on its repo row is nested in that server's namespace, so dialing it here reaches a same-named box of ours. But store-backed callers ask `getRepoHostedReviewExecutionHostId` first, which is "what may this client dial" and answers `local` for a `runtime:` row. That is deliberate and matches #18377: the runtime registration controller only adopts a `runtime:` stamp onto a row with no `connectionId` (`runtimeRepoMatchesExecutionHost` refuses to match an SSH row), so the checkout really is in this process and refusing would regress a runtime server creating reviews for its own rows. No wire change. `connectionId` on `CreateHostedReviewArgs`, `CreateStackedHostedReviewArgs` and `HostedReviewCreationEligibilityArgs` in src/shared/hosted-review.ts is untouched -- every host already ignores it in favor of the repo row, and removing it from the request types would only churn the schema older clients still populate. The main-side eligibility input `Omit`s it so nothing on this side can read the ambiguous field again.
115 lines
3.7 KiB
TypeScript
115 lines
3.7 KiB
TypeScript
import { getRepoHostedReviewExecutionHostId } from '../source-control/hosted-review-execution-host'
|
|
import type { BranchPrefixStrategy } from '../../shared/ui-chrome-types'
|
|
import type { Repo } from '../../shared/repo-types'
|
|
import { getPRForBranch } from '../github/client'
|
|
import { gitExecFileAsync } from '../git/runner'
|
|
import { listWorktrees } from '../git/worktree'
|
|
import { computeValidatedBranchName } from '../ipc/worktree-logic'
|
|
import { getHostedReviewForBranch } from '../source-control/hosted-review'
|
|
import {
|
|
getSelectedReviewBranch,
|
|
getSelectedReviewLookupHints,
|
|
type SelectedReviewBranchInput
|
|
} from './selected-review-branch'
|
|
import { hasLocalGitOptions, normalizeLocalBranchName } from './runtime-worktree-selection'
|
|
import type { HostedReviewExecutionOptions } from '../source-control/hosted-review-git-options'
|
|
|
|
export async function resolveCreateBranchName(
|
|
repoPath: string,
|
|
branchNameOverride: string | undefined,
|
|
sanitizedName: string,
|
|
settings: { branchPrefix: string; branchPrefixCustom?: string },
|
|
username: string | null,
|
|
gitOptions: { wslDistro?: string } = {}
|
|
): Promise<string> {
|
|
if (!branchNameOverride) {
|
|
return computeValidatedBranchName(
|
|
sanitizedName,
|
|
{ ...settings, branchPrefix: settings.branchPrefix as BranchPrefixStrategy },
|
|
username
|
|
)
|
|
}
|
|
if (branchNameOverride.startsWith('-')) {
|
|
throw new Error('Branch name must not start with "-"')
|
|
}
|
|
await gitExecFileAsync(['check-ref-format', '--branch', branchNameOverride], {
|
|
cwd: repoPath,
|
|
...gitOptions
|
|
})
|
|
return branchNameOverride
|
|
}
|
|
|
|
export async function canCheckoutExistingLocalBranch(
|
|
repoPath: string,
|
|
branchName: string,
|
|
baseBranch: string,
|
|
gitOptions: { wslDistro?: string } = {}
|
|
): Promise<boolean> {
|
|
let localHead = ''
|
|
try {
|
|
const { stdout } = await gitExecFileAsync(
|
|
['rev-parse', '--verify', '--quiet', `refs/heads/${branchName}^{commit}`],
|
|
{ cwd: repoPath, ...gitOptions }
|
|
)
|
|
localHead = stdout.trim()
|
|
} catch {
|
|
return false
|
|
}
|
|
if (normalizeLocalBranchName(baseBranch) !== branchName) {
|
|
if (!localHead) {
|
|
return false
|
|
}
|
|
try {
|
|
const { stdout } = await gitExecFileAsync(
|
|
['rev-parse', '--verify', '--quiet', `${baseBranch}^{commit}`],
|
|
{ cwd: repoPath, ...gitOptions }
|
|
)
|
|
if (stdout.trim() !== localHead) {
|
|
return false
|
|
}
|
|
} catch {
|
|
return false
|
|
}
|
|
}
|
|
const worktrees = await listWorktrees(repoPath, gitOptions)
|
|
return !worktrees.some((worktree) => normalizeLocalBranchName(worktree.branch) === branchName)
|
|
}
|
|
|
|
export function getLocalGitHubPrForBranch(
|
|
repoPath: string,
|
|
branchName: string,
|
|
gitOptions: { wslDistro?: string }
|
|
): ReturnType<typeof getPRForBranch> {
|
|
return hasLocalGitOptions(gitOptions)
|
|
? getPRForBranch(repoPath, branchName, null, null, null, {
|
|
localGitExecOptions: gitOptions
|
|
})
|
|
: getPRForBranch(repoPath, branchName)
|
|
}
|
|
|
|
export async function getSelectedHostedReviewForBranch(
|
|
repo: Pick<Repo, 'path' | 'connectionId' | 'executionHostId'>,
|
|
branchName: string,
|
|
args: SelectedReviewBranchInput,
|
|
executionOptions: HostedReviewExecutionOptions = {}
|
|
): Promise<{ matchesSelected: boolean; number: number } | null> {
|
|
const selectedReview = getSelectedReviewBranch(args)
|
|
if (!selectedReview) {
|
|
return null
|
|
}
|
|
const review = await getHostedReviewForBranch({
|
|
repoPath: repo.path,
|
|
executionHostId: getRepoHostedReviewExecutionHostId(repo),
|
|
branch: branchName,
|
|
...executionOptions,
|
|
...getSelectedReviewLookupHints(args)
|
|
})
|
|
return review
|
|
? {
|
|
matchesSelected:
|
|
review.provider === selectedReview.provider && review.number === selectedReview.number,
|
|
number: review.number
|
|
}
|
|
: null
|
|
}
|