Files
orca/src/main/git/saved-clone-target.ts
T
OrcaWinandOrcaWin 4687e90a16 [comment audit] Continue main-process comment cleanup (#32946)
* [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>
2026-10-23 16:27:37 -07:00

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.`
)
}