mirror of
https://github.com/stablyai/orca.git
synced 2026-10-30 16:47:34 +00:00
fix(bitbucket): canonicalize ssh.bitbucket.org remotes in review links and Gitea detection (#30808)
Fold every forge SSH host alias (ssh.github.com, altssh.gitlab.com, ssh.bitbucket.org) through one shared normalizeForgeWebHost so main-process provider detection and renderer manual review links agree. Remotes on ssh.bitbucket.org now open bitbucket.org pull request forms instead of ssh.bitbucket.org URLs, and Gitea no longer claims the host. Co-authored-by: Eric Junker <4758+ejunker@users.noreply.github.com> Co-authored-by: Neil <4138956+nwparker@users.noreply.github.com>
This commit is contained in:
co-authored by
Eric Junker
Neil
parent
19ecde24a0
commit
780ab3d71d
@@ -1,3 +1,4 @@
|
||||
import { normalizeBitbucketRemoteHost } from '../../shared/git-remote-host-alias'
|
||||
import { createRemoteRefProbeCache } from '../git/remote-ref-probe-cache'
|
||||
|
||||
export type BitbucketRepoRef = {
|
||||
@@ -58,8 +59,7 @@ export function parseBitbucketRepoRef(remoteUrl: string): BitbucketRepoRef | nul
|
||||
|
||||
try {
|
||||
const url = new URL(trimmed)
|
||||
const host = url.hostname.toLowerCase()
|
||||
if (host !== 'bitbucket.org' && host !== 'ssh.bitbucket.org') {
|
||||
if (normalizeBitbucketRemoteHost(url.hostname) !== 'bitbucket.org') {
|
||||
return null
|
||||
}
|
||||
return parseBitbucketPath(url.pathname)
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { normalizeGitLabRemoteHost } from '../../shared/git-remote-host-alias'
|
||||
import { normalizeForgeWebHost } from '../../shared/git-remote-host-alias'
|
||||
|
||||
// Scoped local fork of the hosted-git-info@9.0.3 behavior Orca used.
|
||||
// Keep only the provider parsing and file/commit URL construction needed by source links.
|
||||
@@ -22,15 +22,14 @@ const shorthandHosts: Record<string, { host: string; provider: HostedRemoteProvi
|
||||
}
|
||||
|
||||
function providerForHost(host: string): HostedRemoteHost | null {
|
||||
const normalized = normalizeGitLabRemoteHost(host)
|
||||
if (normalized === 'github.com' || normalized === 'ssh.github.com') {
|
||||
// SSH-over-HTTPS uses ssh.github.com:443, but browser/account identity remains github.com.
|
||||
const normalized = normalizeForgeWebHost(host)
|
||||
if (normalized === 'github.com') {
|
||||
return { host: 'github.com', provider: 'github' }
|
||||
}
|
||||
if (normalized === 'gitlab.com') {
|
||||
return { host: 'gitlab.com', provider: 'gitlab' }
|
||||
}
|
||||
if (normalized === 'bitbucket.org' || normalized === 'ssh.bitbucket.org') {
|
||||
if (normalized === 'bitbucket.org') {
|
||||
return { host: 'bitbucket.org', provider: 'bitbucket' }
|
||||
}
|
||||
return null
|
||||
|
||||
@@ -93,6 +93,7 @@ describe('Gitea repository ref parsing', () => {
|
||||
expect(parseGiteaRepoRef('git@github.com:team/project.git')).toBeNull()
|
||||
expect(parseGiteaRepoRef('https://gitlab.com/team/project.git')).toBeNull()
|
||||
expect(parseGiteaRepoRef('https://bitbucket.org/team/project.git')).toBeNull()
|
||||
expect(parseGiteaRepoRef('git@ssh.bitbucket.org:team/project.git')).toBeNull()
|
||||
expect(parseGiteaRepoRef('https://dev.azure.com/team/project/_git/repo')).toBeNull()
|
||||
expect(parseGiteaRepoRef('https://team.visualstudio.com/project/_git/repo')).toBeNull()
|
||||
})
|
||||
|
||||
@@ -16,6 +16,7 @@ const KNOWN_NON_GITEA_HOSTS = new Set([
|
||||
'github.com',
|
||||
'gitlab.com',
|
||||
'bitbucket.org',
|
||||
'ssh.bitbucket.org',
|
||||
'dev.azure.com',
|
||||
'ssh.dev.azure.com'
|
||||
])
|
||||
|
||||
@@ -106,6 +106,41 @@ describe('buildSourceControlManualReviewUrl', () => {
|
||||
}
|
||||
})
|
||||
|
||||
it.each([
|
||||
'ssh://git@ssh.bitbucket.org/team/project.git',
|
||||
'git@ssh.bitbucket.org:team/project.git',
|
||||
'git@SSH.BITBUCKET.ORG:team/project.git'
|
||||
])('canonicalizes the Bitbucket SSH host web identity: %s', (remote) => {
|
||||
expect(parseRemoteRepo(remote)).toEqual({
|
||||
provider: 'bitbucket',
|
||||
path: 'team/project',
|
||||
webBaseUrl: 'https://bitbucket.org/team/project'
|
||||
})
|
||||
for (const provider of [undefined, 'bitbucket'] as const) {
|
||||
expect(
|
||||
buildSourceControlManualReviewUrl({
|
||||
baseRef: 'main',
|
||||
branchName: 'feature/x',
|
||||
repoRemoteName: 'origin',
|
||||
repoRemoteUrl: remote,
|
||||
provider,
|
||||
upstreamName: 'origin/feature/x'
|
||||
})
|
||||
).toBe('https://bitbucket.org/team/project/pull-requests/new?source=feature%2Fx&dest=main')
|
||||
}
|
||||
})
|
||||
|
||||
it.each(['ssh://git@ssh.github.com:443/team/project.git', 'git@ssh.github.com:team/project.git'])(
|
||||
'canonicalizes the GitHub SSH-over-443 web identity: %s',
|
||||
(remote) => {
|
||||
expect(parseRemoteRepo(remote)).toEqual({
|
||||
provider: 'github',
|
||||
path: 'team/project',
|
||||
webBaseUrl: 'https://github.com/team/project'
|
||||
})
|
||||
}
|
||||
)
|
||||
|
||||
it('does not infer GitLab or rewrite the web origin of an alias lookalike', () => {
|
||||
const remote = 'ssh://git@altssh.gitlab.com.example.com:443/team/project.git'
|
||||
expect(parseRemoteRepo(remote)).toEqual({
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import type { HostedReviewProvider } from '../../../../../../shared/hosted-review'
|
||||
import { normalizeGitLabRemoteHost } from '../../../../../../shared/git-remote-host-alias'
|
||||
import { normalizeForgeWebHost } from '../../../../../../shared/git-remote-host-alias'
|
||||
|
||||
export type ManualReviewProvider = Exclude<HostedReviewProvider, 'unsupported'>
|
||||
|
||||
@@ -48,20 +48,19 @@ function cleanPath(path: string): string | null {
|
||||
}
|
||||
|
||||
function providerForHost(host: string): ManualReviewProvider | null {
|
||||
const normalized = normalizeGitLabRemoteHost(host)
|
||||
if (normalized === 'github.com' || normalized === 'ssh.github.com') {
|
||||
if (host === 'github.com') {
|
||||
return 'github'
|
||||
}
|
||||
if (normalized === 'gitlab.com') {
|
||||
if (host === 'gitlab.com') {
|
||||
return 'gitlab'
|
||||
}
|
||||
if (normalized === 'bitbucket.org') {
|
||||
if (host === 'bitbucket.org') {
|
||||
return 'bitbucket'
|
||||
}
|
||||
if (
|
||||
normalized === 'dev.azure.com' ||
|
||||
normalized === 'ssh.dev.azure.com' ||
|
||||
normalized.endsWith('.visualstudio.com')
|
||||
host === 'dev.azure.com' ||
|
||||
host === 'ssh.dev.azure.com' ||
|
||||
host.endsWith('.visualstudio.com')
|
||||
) {
|
||||
return 'azure-devops'
|
||||
}
|
||||
@@ -154,7 +153,7 @@ export function parseRemoteRepo(
|
||||
? trimmed.match(/^(?:[^@/:]+@)?([^:\s/]+):([^\s]+?)(?:\.git)?$/)
|
||||
: null
|
||||
if (scpLike) {
|
||||
const host = normalizeGitLabRemoteHost(scpLike[1].toLowerCase())
|
||||
const host = normalizeForgeWebHost(scpLike[1])
|
||||
const path = cleanPath(scpLike[2])
|
||||
if (!path) {
|
||||
return null
|
||||
@@ -178,18 +177,14 @@ export function parseRemoteRepo(
|
||||
return null
|
||||
}
|
||||
const remoteHost = url.hostname.toLowerCase()
|
||||
const host = normalizeGitLabRemoteHost(remoteHost)
|
||||
const host = normalizeForgeWebHost(remoteHost)
|
||||
const hintedProvider = normalizeProvider(providerHint)
|
||||
const inferredProvider = providerForHost(host)
|
||||
const provider = hintedProvider ?? inferredProvider
|
||||
let webOrigin: string
|
||||
if (host === 'ssh.github.com') {
|
||||
webOrigin = 'https://github.com'
|
||||
} else if (remoteHost === 'altssh.gitlab.com') {
|
||||
webOrigin = 'https://gitlab.com'
|
||||
} else {
|
||||
webOrigin = buildWebOrigin(protocol, url.host, url.hostname).replace(/\/+$/, '')
|
||||
}
|
||||
const webOrigin =
|
||||
host === remoteHost
|
||||
? buildWebOrigin(protocol, url.host, url.hostname).replace(/\/+$/, '')
|
||||
: `https://${host}`
|
||||
return {
|
||||
provider,
|
||||
path,
|
||||
|
||||
@@ -21,6 +21,16 @@ export function normalizeGitLabRemoteHost(host: string): string {
|
||||
return normalizedHost === 'altssh.gitlab.com' ? 'gitlab.com' : normalizedHost
|
||||
}
|
||||
|
||||
export function normalizeBitbucketRemoteHost(host: string): string {
|
||||
const normalizedHost = normalizeRemoteHost(host)
|
||||
// Why: Bitbucket Cloud serves SSH for bitbucket.org repos from ssh.bitbucket.org.
|
||||
return normalizedHost === 'ssh.bitbucket.org' ? 'bitbucket.org' : normalizedHost
|
||||
}
|
||||
|
||||
export function normalizeForgeWebHost(host: string): string {
|
||||
return normalizeBitbucketRemoteHost(normalizeGitLabRemoteHost(normalizeGitHubRemoteHost(host)))
|
||||
}
|
||||
|
||||
/** Comparison-only host fold: the forge alias plus `www.`, for matching a remote to a pasted URL. */
|
||||
export function foldComparableGitHubHost(host: string): string {
|
||||
return normalizeGitHubRemoteHost(foldWwwHostAlias(host))
|
||||
|
||||
Reference in New Issue
Block a user