diff --git a/packages/source-control-github/src/server/gitHubRepositoryResolution.test.ts b/packages/source-control-github/src/server/gitHubRepositoryResolution.test.ts index 3447c3a1be2ab..ddd4be97462e6 100644 --- a/packages/source-control-github/src/server/gitHubRepositoryResolution.test.ts +++ b/packages/source-control-github/src/server/gitHubRepositoryResolution.test.ts @@ -33,6 +33,18 @@ describe("selectGitHubBaseRepository", () => { select({ remotes: fork, resolved: "remote.origin.gh-resolved acme/other" }), { owner: "acme", name: "other" }, ); + assert.deepStrictEqual( + select({ + remotes: remotes( + ["origin", "github.com:me/web.git"], + ["upstream", "https://github.com/acme/web.git"], + ["local", "/tmp/local.git"], + ["file", "file:///tmp/local.git"], + ), + resolved: "remote.origin.gh-resolved base", + }), + { owner: "me", name: "web" }, + ); const partialClone = fork.replaceAll("(fetch)", "(fetch) [blob:none]"); assert.deepStrictEqual( select({ remotes: partialClone, resolved: "remote.origin.gh-resolved base" }), @@ -119,6 +131,15 @@ describe("selectGitHubBaseRepository", () => { ["mirror", "git@gitlab.com:me/web.git"], ), }, + { + remotes: remotes( + ["origin", "git@github.com:me/web.git"], + ["mirror", "gitlab.com:me/web.git"], + ), + }, + { + remotes: remotes(["origin", "git@github.com:me/web.git"], ["bad", "https://github.com/me"]), + }, { remotes: remotes(["origin", "git@work:me/web.git"]) }, { remotes: remotes( diff --git a/packages/source-control-github/src/server/gitHubRepositoryResolution.ts b/packages/source-control-github/src/server/gitHubRepositoryResolution.ts index e42e428d9d997..7cf739123b6cd 100644 --- a/packages/source-control-github/src/server/gitHubRepositoryResolution.ts +++ b/packages/source-control-github/src/server/gitHubRepositoryResolution.ts @@ -1,4 +1,4 @@ -import { normalizeGitRemoteUrl } from "@t3tools/shared/git"; +import { normalizeGitRemoteUrl, parseGitRemoteRepository } from "@t3tools/shared/git"; import { isSshRemoteUrl } from "@t3tools/shared/sourceControl"; import { parseGitRemoteHost } from "@t3tools/source-control-core/shared/definition"; import { definition } from "../shared/definition.ts"; @@ -21,7 +21,10 @@ export function parseFetchRemotes( .map((match) => ({ name: match[1]!, url: match[2]! })); } -/** gh priority: marked default, upstream/github/origin, sole remote. Null if ambiguous. */ +/** + * gh priority: marked default, upstream/github/origin, sole remote. Null if ambiguous. + * Hostless remotes, such as local paths, are skipped as gh skips them. + */ export function selectGitHubBaseRepository(input: { readonly remotes: string; readonly resolved: string; @@ -30,6 +33,10 @@ export function selectGitHubBaseRepository(input: { const host = input.host.toLowerCase(); const repositories = new Map(); for (const remote of parseFetchRemotes(input.remotes)) { + // A userless SCP remote (`github.com:me/web`) has no host for parseGitRemoteHost, but gh reads one. + if (parseGitRemoteRepository(remote.url) === null && parseGitRemoteHost(remote.url) === null) { + continue; + } const [normalizedHost, owner, name, ...rest] = normalizeGitRemoteUrl(remote.url).split("/"); const remoteHost = normalizedHost && isSshRemoteUrl(remote.url)