mirror of
https://github.com/stablyai/orca.git
synced 2026-11-02 10:08:13 +00:00
* [comment audit] Shorten daemon, hook, and emulator comments [skip ci] * [comment audit] Shorten VM, conversation, SQLite, and theme-import comments [skip ci] * [comment audit] Tighten Git execution and source-control comment contracts [skip ci] * [comment audit] Clarify repository probes and maintenance comments [skip ci] * [comment audit] Shorten status, diff-cache, and checkout comments [skip ci] * [comment audit] Clarify worktree lifecycle and WSL routing comments [skip ci] * [comment audit] Shorten forge identity and review-refresh comments [skip ci] * [comment audit] Tighten hosted-review client comment contracts [skip ci] * [comment audit] Clarify GitLab compatibility and review comments [skip ci] * [comment audit] Tighten hook ownership and watchdog rationale [skip ci] * [comment audit] Condense host and integration compatibility comments [skip ci] * [comment audit] Clarify IPC authority and remote-routing rationale [skip ci] * [comment audit] Finalize file-import and containment comment cleanup * [comment audit] Fix stale Jira type imports blocking full CI --------- Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>
144 lines
5.2 KiB
TypeScript
144 lines
5.2 KiB
TypeScript
import type { ExecutionHostId } from '../../shared/execution-host'
|
|
import { normalizeGitRemoteUrl } from '../../shared/git-remote-identity'
|
|
import { isShowRefNoMatchError } from '../../shared/git-show-ref-no-match'
|
|
import { isFolderRepo } from '../../shared/repo-kind'
|
|
import type { Repo } from '../../shared/repo-types'
|
|
import { runGitProbeOnHost } from '../repo-git-remote-identity'
|
|
|
|
type GitProbe = (args: string[]) => Promise<string>
|
|
|
|
/** Read failures stay distinct from Git proving an unborn HEAD. */
|
|
function probeIn(repoPath: string, hostId: ExecutionHostId, signal?: AbortSignal): GitProbe {
|
|
return async (args) => {
|
|
if (signal?.aborted) {
|
|
throw new Error('Clone aborted')
|
|
}
|
|
const result = await runGitProbeOnHost(args, repoPath, hostId, { signal })
|
|
if (!result) {
|
|
throw new Error('Clone target host is unavailable')
|
|
}
|
|
return result.stdout
|
|
}
|
|
}
|
|
|
|
/** Empty first line proves top level; subfolders print ../ and bare repos supply no cdup record. */
|
|
function isCheckoutTopLevel(cdupStdout: string | null): boolean {
|
|
return cdupStdout !== null && cdupStdout.split(/\r?\n/)[0] === ''
|
|
}
|
|
|
|
async function isFinishedCheckout(
|
|
probe: GitProbe
|
|
): Promise<{ unbornBranch: string | null } | null> {
|
|
try {
|
|
const settled = await probe(['rev-parse', '--show-cdup', '--verify', '--quiet', 'HEAD'])
|
|
return isCheckoutTopLevel(settled) ? { unbornBranch: null } : null
|
|
} catch (error) {
|
|
// The same quiet missing-ref exit applies to rev-parse; transport faults must stop here.
|
|
if (!isShowRefNoMatchError(error)) {
|
|
throw error
|
|
}
|
|
}
|
|
if (!isCheckoutTopLevel(await probe(['rev-parse', '--show-cdup']))) {
|
|
return null
|
|
}
|
|
const head = (await probe(['symbolic-ref', 'HEAD'])).trim()
|
|
return head.startsWith('refs/heads/') ? { unbornBranch: head.slice('refs/heads/'.length) } : null
|
|
}
|
|
|
|
async function readCloneOrigin(
|
|
probe: GitProbe,
|
|
unbornBranch: string | null
|
|
): Promise<string | null> {
|
|
if (unbornBranch === null) {
|
|
return (await probe(['config', '--get-all', 'remote.origin.url'])).trim()
|
|
}
|
|
const branchKey = `branch.${unbornBranch}`
|
|
const escapedKey = branchKey.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
|
|
const config = await probe([
|
|
'config',
|
|
'--get-regexp',
|
|
`^(remote\\.origin\\.url|${escapedKey}\\.(remote|merge))$`
|
|
])
|
|
const values = new Map<string, string>()
|
|
for (const line of config.trim().split('\n')) {
|
|
const separator = line.indexOf(' ')
|
|
if (separator === -1) {
|
|
return null
|
|
}
|
|
const key = line.slice(0, separator)
|
|
if (values.has(key)) {
|
|
return null
|
|
}
|
|
values.set(key, line.slice(separator + 1))
|
|
}
|
|
// Git 2.25 writes this tracking pair only after an empty clone is known to have finished.
|
|
if (
|
|
values.get(`${branchKey}.remote`) !== 'origin' ||
|
|
values.get(`${branchKey}.merge`) !== `refs/heads/${unbornBranch}`
|
|
) {
|
|
return null
|
|
}
|
|
return values.get('remote.origin.url') ?? null
|
|
}
|
|
|
|
/** Require a finished top-level checkout with exactly one origin URL matching the requested repository. */
|
|
async function isFinishedCloneOf(
|
|
repoPath: string,
|
|
url: string,
|
|
hostId: ExecutionHostId,
|
|
signal?: AbortSignal
|
|
): Promise<boolean> {
|
|
const probe = probeIn(repoPath, hostId, signal)
|
|
try {
|
|
const checkout = await isFinishedCheckout(probe)
|
|
if (!checkout) {
|
|
return false
|
|
}
|
|
const originUrl = await readCloneOrigin(probe, checkout.unbornBranch)
|
|
if (!originUrl || originUrl.includes('\n')) {
|
|
return false
|
|
}
|
|
const requestedKey = normalizeGitRemoteUrl(url)
|
|
return requestedKey ? normalizeGitRemoteUrl(originUrl) === requestedKey : originUrl === url
|
|
} catch {
|
|
return false
|
|
}
|
|
}
|
|
|
|
/** Reuse finished saved clones, allow same-repo reclones, and reject mismatches; findSaved must match host and path. */
|
|
export async function reuseSavedCloneTarget(
|
|
findSaved: () => Repo | undefined,
|
|
url: string,
|
|
hostId: ExecutionHostId,
|
|
signal?: AbortSignal
|
|
): Promise<Repo | null> {
|
|
const saved = findSaved()
|
|
if (!saved || isFolderRepo(saved)) {
|
|
return null
|
|
}
|
|
const isClone = await isFinishedCloneOf(saved.path, url, hostId, signal)
|
|
if (signal?.aborted) {
|
|
throw new Error('Clone aborted')
|
|
}
|
|
if (isClone) {
|
|
const current = findSaved()
|
|
// Recheck identity after Git I/O; clone itself refuses any remaining nonempty folder.
|
|
return current?.id === saved.id ? current : null
|
|
}
|
|
const requestedKey = normalizeGitRemoteUrl(url)
|
|
// Only origin names this project's own repo; upstream metadata and avatars may identify its source fork.
|
|
const storedOrigin =
|
|
saved.gitRemoteIdentity?.remoteName === 'origin' ? saved.gitRemoteIdentity : null
|
|
// Keep settings when the same repository needs its missing folder recreated.
|
|
if (requestedKey && storedOrigin?.canonicalKey === requestedKey) {
|
|
return null
|
|
}
|
|
throw new Error(
|
|
`"${saved.displayName}" is already an Orca project at ${saved.path}, and Orca couldn't confirm that folder is a clone of this URL: ${
|
|
storedOrigin
|
|
? 'Orca has that project recorded as a different repository'
|
|
: 'Orca has no record of which repository that project holds, so it cannot tell whether the folder is missing or holds something else'
|
|
}. Remove the project from Orca or choose another folder.`
|
|
)
|
|
}
|