mirror of
https://github.com/stablyai/orca.git
synced 2026-11-15 18:37:24 +00:00
The project catalog keys a repo by its first network remote (upstream, origin, then the rest), but GitHub issue and PR lookups only read the remotes named origin and upstream. A repo whose origin is a local bare repo and whose GitHub remote has another name showed as GitHub-backed yet Tasks said no GitHub source was detected. Origin's role now falls to the same remote the project identity picks. Fixes #42102
143 lines
5.0 KiB
TypeScript
143 lines
5.0 KiB
TypeScript
import {
|
|
withGitHubRepoExecution,
|
|
getOwnerRepoForRemote,
|
|
type GitHubRemoteIdentityProbeOptions,
|
|
type LocalGitExecOptions
|
|
} from './github-repository-identity'
|
|
import type { GitHubRepositoryIdentity as GitHubApiRepository } from '../../../shared/github/repositories/repository-identity'
|
|
import { shouldProbeGitRemote } from '../../git/remotes/remote-name-listing'
|
|
import { resolveOriginWithStandIn } from '../../git/remotes/origin-stand-in-remote'
|
|
|
|
import {
|
|
getEnterpriseGitHubRepoSlug,
|
|
getEnterpriseGitHubRepoSlugForRemote
|
|
} from './github-enterprise-repository'
|
|
import {
|
|
githubApiRepositoryProbeCacheKey,
|
|
resolveGitHubApiRepositoryProbe
|
|
} from './github-api-repository-probe'
|
|
|
|
const ORIGIN_REPO_CACHE_TTL_MS = 30_000
|
|
const ORIGIN_REPO_CACHE_MAX_ENTRIES = 512
|
|
const originRepoCache = new Map<string, { value: GitHubApiRepository | null; expiresAt: number }>()
|
|
const originRepoInFlight = new Map<string, Promise<GitHubApiRepository | null>>()
|
|
|
|
/** @internal - exposed for tests only */
|
|
export function _resetOriginGitHubApiRepositoryCache(): void {
|
|
originRepoCache.clear()
|
|
originRepoInFlight.clear()
|
|
}
|
|
|
|
function pruneOriginRepoCache(now: number): void {
|
|
for (const [key, entry] of originRepoCache) {
|
|
if (entry.expiresAt <= now) {
|
|
originRepoCache.delete(key)
|
|
}
|
|
}
|
|
while (originRepoCache.size > ORIGIN_REPO_CACHE_MAX_ENTRIES) {
|
|
const oldestKey = originRepoCache.keys().next().value
|
|
if (oldestKey === undefined) {
|
|
return
|
|
}
|
|
originRepoCache.delete(oldestKey)
|
|
}
|
|
}
|
|
|
|
/** Resolve this remote exactly; auth-gate Enterprise hosts so other forges cannot route to GitHub. */
|
|
export async function getGitHubApiRepositoryForRemote(
|
|
repoPath: string,
|
|
remoteName: string,
|
|
connectionId?: string | null,
|
|
localGitOptions: LocalGitExecOptions = {},
|
|
probeOptions: GitHubRemoteIdentityProbeOptions = {}
|
|
): Promise<GitHubApiRepository | null> {
|
|
return withGitHubRepoExecution(repoPath, connectionId, localGitOptions, async () => {
|
|
// Why: skip remotes the listing says are absent; origin stays unconditional so its absence is definitive.
|
|
if (
|
|
remoteName !== 'origin' &&
|
|
!(await shouldProbeGitRemote(repoPath, remoteName, connectionId, localGitOptions))
|
|
) {
|
|
return null
|
|
}
|
|
// Generic PR resolution prefers upstream; this entry point must honor the selected remote.
|
|
const requireVerifiedSshProbe = probeOptions.requireVerifiedSshProbe === true
|
|
const verifiedIdentityArgs = requireVerifiedSshProbe ? ([probeOptions] as const) : []
|
|
const ownerRepo = await getOwnerRepoForRemote(
|
|
repoPath,
|
|
remoteName,
|
|
connectionId,
|
|
localGitOptions,
|
|
...verifiedIdentityArgs
|
|
)
|
|
if (ownerRepo) {
|
|
return { ...ownerRepo, host: 'github.com' }
|
|
}
|
|
const cacheKey = githubApiRepositoryProbeCacheKey(
|
|
repoPath,
|
|
remoteName,
|
|
connectionId,
|
|
localGitOptions,
|
|
requireVerifiedSshProbe
|
|
)
|
|
const now = Date.now()
|
|
pruneOriginRepoCache(now)
|
|
const cached = originRepoCache.get(cacheKey)
|
|
if (cached && cached.expiresAt > now) {
|
|
return cached.value
|
|
}
|
|
const inFlight = originRepoInFlight.get(cacheKey)
|
|
if (inFlight) {
|
|
return inFlight
|
|
}
|
|
const probe = (async () => {
|
|
const enterpriseOptions =
|
|
Object.keys(localGitOptions).length > 0 ? { localGitExecOptions: localGitOptions } : {}
|
|
const verifiedEnterpriseArgs = requireVerifiedSshProbe ? ([true] as const) : []
|
|
const slug =
|
|
remoteName === 'origin'
|
|
? await getEnterpriseGitHubRepoSlug(
|
|
repoPath,
|
|
connectionId,
|
|
enterpriseOptions,
|
|
...verifiedEnterpriseArgs
|
|
)
|
|
: await getEnterpriseGitHubRepoSlugForRemote(
|
|
repoPath,
|
|
remoteName,
|
|
connectionId,
|
|
enterpriseOptions,
|
|
...verifiedEnterpriseArgs
|
|
)
|
|
// Undefined means auth inventory was unreadable; do not cache transient probe failures.
|
|
if (slug !== undefined) {
|
|
originRepoCache.set(cacheKey, {
|
|
value: slug,
|
|
expiresAt: Date.now() + ORIGIN_REPO_CACHE_TTL_MS
|
|
})
|
|
pruneOriginRepoCache(Date.now())
|
|
}
|
|
return resolveGitHubApiRepositoryProbe(slug, requireVerifiedSshProbe)
|
|
})()
|
|
originRepoInFlight.set(cacheKey, probe)
|
|
try {
|
|
return await probe
|
|
} finally {
|
|
if (originRepoInFlight.get(cacheKey) === probe) {
|
|
originRepoInFlight.delete(cacheKey)
|
|
}
|
|
}
|
|
})
|
|
}
|
|
|
|
/** The repo origin names; a project whose origin is missing or a local path reads its stand-in remote. */
|
|
export async function getOriginGitHubApiRepository(
|
|
repoPath: string,
|
|
connectionId?: string | null,
|
|
localGitOptions: LocalGitExecOptions = {},
|
|
probeOptions: GitHubRemoteIdentityProbeOptions = {}
|
|
): Promise<GitHubApiRepository | null> {
|
|
return resolveOriginWithStandIn(repoPath, connectionId, localGitOptions, (name) =>
|
|
getGitHubApiRepositoryForRemote(repoPath, name, connectionId, localGitOptions, probeOptions)
|
|
)
|
|
}
|