diff --git a/src/main/github/github-api-repository-probe.ts b/src/main/github/github-api-repository-probe.ts new file mode 100644 index 00000000000..04a140faefe --- /dev/null +++ b/src/main/github/github-api-repository-probe.ts @@ -0,0 +1,21 @@ +import type { LocalGitExecOptions } from './github-repository-identity' + +export function githubApiRepositoryProbeCacheKey( + repoPath: string, + remoteName: string, + connectionId: string | null | undefined, + localGitOptions: LocalGitExecOptions, + requireVerifiedSshProbe: boolean +): string { + return `${connectionId ?? 'local'}\0${localGitOptions.wslDistro ?? ''}\0${repoPath}\0${remoteName}\0${requireVerifiedSshProbe ? 'verified' : 'tolerant'}` +} + +export function resolveGitHubApiRepositoryProbe( + value: T | undefined, + requireVerifiedSshProbe: boolean +): T | null { + if (value === undefined && requireVerifiedSshProbe) { + throw new Error('GitHub repository identity is unverifiable.') + } + return value ?? null +} diff --git a/src/main/github/github-api-repository.test.ts b/src/main/github/github-api-repository.test.ts index 7aa89984cbe..b7fdb6c16da 100644 --- a/src/main/github/github-api-repository.test.ts +++ b/src/main/github/github-api-repository.test.ts @@ -183,6 +183,22 @@ describe('resolveGitHubRepoExecution', () => { }) describe('origin repository cache', () => { + it('keeps an indeterminate auth inventory unverifiable during candidate discovery', async () => { + getEnterpriseGitHubRepoSlugMock.mockResolvedValue(undefined) + + await expect( + getGitHubApiRepositoryForRemote( + '/remote/repo', + 'origin', + 'ssh-1', + {}, + { + requireVerifiedSshProbe: true + } + ) + ).rejects.toThrow('GitHub repository identity is unverifiable.') + }) + it('does not reuse a tolerant SSH miss for verified candidate discovery', async () => { const enterprise = { owner: 'acme', diff --git a/src/main/github/github-api-repository.ts b/src/main/github/github-api-repository.ts index ba3c23f551b..7e394789714 100644 --- a/src/main/github/github-api-repository.ts +++ b/src/main/github/github-api-repository.ts @@ -21,6 +21,10 @@ import { isValidGitHubApiRepository, type GitHubApiRepositoryResolution } from './github-api-repository-validation' +import { + githubApiRepositoryProbeCacheKey, + resolveGitHubApiRepositoryProbe +} from './github-api-repository-probe' export { githubHostExecOptions, @@ -40,16 +44,6 @@ const ORIGIN_REPO_CACHE_MAX_ENTRIES = 512 const originRepoCache = new Map() const originRepoInFlight = new Map>() -function originRepoCacheKey( - repoPath: string, - remoteName: string, - connectionId?: string | null, - localGitOptions: LocalGitExecOptions = {}, - requireVerifiedSshProbe = false -): string { - return `${connectionId ?? 'local'}\0${localGitOptions.wslDistro ?? ''}\0${repoPath}\0${remoteName}\0${requireVerifiedSshProbe ? 'verified' : 'tolerant'}` -} - /** @internal - exposed for tests only */ export function _resetOriginGitHubApiRepositoryCache(): void { originRepoCache.clear() @@ -97,7 +91,7 @@ export async function getGitHubApiRepositoryForRemote( if (ownerRepo) { return { ...ownerRepo, host: 'github.com' } } - const cacheKey = originRepoCacheKey( + const cacheKey = githubApiRepositoryProbeCacheKey( repoPath, remoteName, connectionId, @@ -142,7 +136,7 @@ export async function getGitHubApiRepositoryForRemote( }) pruneOriginRepoCache(Date.now()) } - return slug ?? null + return resolveGitHubApiRepositoryProbe(slug, requireVerifiedSshProbe) })() originRepoInFlight.set(cacheKey, probe) try {