diff --git a/src/main/github/gh-utils.ts b/src/main/github/gh-utils.ts index ffeb2ca22c7..7f4c4e05830 100644 --- a/src/main/github/gh-utils.ts +++ b/src/main/github/gh-utils.ts @@ -26,6 +26,7 @@ export { } from './github-repository-identity' export type { GitHubRemoteIdentity, + GitHubRemoteIdentityProbeOptions, GitHubRepoContext, LocalGitExecOptions, OwnerRepo diff --git a/src/main/github/github-api-repository-validation.ts b/src/main/github/github-api-repository-validation.ts index a7dc26f4fdf..e70197288cd 100644 --- a/src/main/github/github-api-repository-validation.ts +++ b/src/main/github/github-api-repository-validation.ts @@ -1,5 +1,11 @@ import type { GitHubOwnerRepo } from '../../shared/github/pull-request-types' +export type GitHubApiRepositoryResolution = + | GitHubOwnerRepo + | null + | undefined + | (() => Promise) + // Why: renderer/RPC overrides reach authenticated REST paths. const OWNER_SLUG_RE = /^[A-Za-z0-9][A-Za-z0-9-]*$/ const REPOSITORY_SLUG_RE = /^[A-Za-z0-9._-]+$/ diff --git a/src/main/github/github-api-repository.ts b/src/main/github/github-api-repository.ts index 864d32f97b1..ba3c23f551b 100644 --- a/src/main/github/github-api-repository.ts +++ b/src/main/github/github-api-repository.ts @@ -8,6 +8,7 @@ import { getOwnerRepoForRemote, ghRepoExecOptions, githubRepoContext, + type GitHubRemoteIdentityProbeOptions, type LocalGitExecOptions } from './gh-utils' import { @@ -16,14 +17,16 @@ import { isGitHubHostAuthenticated } from './github-enterprise-repository' import { githubHostExecOptions } from './github-repository-host' -import { isValidGitHubApiRepository } from './github-api-repository-validation' +import { + isValidGitHubApiRepository, + type GitHubApiRepositoryResolution +} from './github-api-repository-validation' export { githubHostExecOptions, githubRepositorySlugArg, githubRepositoryWebHost } from './github-repository-host' - export type GitHubApiRepository = GitHubOwnerRepo export type GitHubRepoExecOptions = ReturnType & { host?: string } export type GitHubRepoExecution = { @@ -31,19 +34,7 @@ export type GitHubRepoExecution = { ghOptions: GitHubRepoExecOptions } -type GitHubApiRepositoryProbeOptions = { - requireVerifiedSshProbe?: boolean -} - -type GitHubApiRepositoryResolution = - | GitHubApiRepository - | null - | undefined - | (() => Promise) - -// Why: the enterprise branch spawns an uncached `git remote get-url` (an SSH -// round trip on connection-backed repos) — hot paths like per-file contents -// and viewed-state toggles resolve per call, so cache like ownerRepoCache does. +// Why: cache the uncached Enterprise remote probe used by hot paths. const ORIGIN_REPO_CACHE_TTL_MS = 30_000 const ORIGIN_REPO_CACHE_MAX_ENTRIES = 512 const originRepoCache = new Map() @@ -90,16 +81,18 @@ export async function getGitHubApiRepositoryForRemote( remoteName: string, connectionId?: string | null, localGitOptions: LocalGitExecOptions = {}, - probeOptions: GitHubApiRepositoryProbeOptions = {} + probeOptions: GitHubRemoteIdentityProbeOptions = {} ): Promise { // Why: generic PR resolution prefers upstream, but this API represents the // caller-selected remote exactly (#7331). + const requireVerifiedSshProbe = probeOptions.requireVerifiedSshProbe === true + const verifiedIdentityArgs = requireVerifiedSshProbe ? ([probeOptions] as const) : [] const ownerRepo = await getOwnerRepoForRemote( repoPath, remoteName, connectionId, localGitOptions, - probeOptions + ...verifiedIdentityArgs ) if (ownerRepo) { return { ...ownerRepo, host: 'github.com' } @@ -109,7 +102,7 @@ export async function getGitHubApiRepositoryForRemote( remoteName, connectionId, localGitOptions, - probeOptions.requireVerifiedSshProbe === true + requireVerifiedSshProbe ) const now = Date.now() pruneOriginRepoCache(now) @@ -124,20 +117,21 @@ export async function getGitHubApiRepositoryForRemote( 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, - probeOptions.requireVerifiedSshProbe === true + ...verifiedEnterpriseArgs ) : await getEnterpriseGitHubRepoSlugForRemote( repoPath, remoteName, connectionId, enterpriseOptions, - probeOptions.requireVerifiedSshProbe === true + ...verifiedEnterpriseArgs ) // Why: undefined means the gh auth inventory could not be read. Caching it // as a negative would turn a transient spawn failure into a 30-second miss.