Skip to content

Commit 6fdb812

Browse files
authored
fix: improve normalizeGitUrl (#2113)
1 parent ad2b174 commit 6fdb812

File tree

3 files changed

+89
-28
lines changed

3 files changed

+89
-28
lines changed

app/composables/useRepositoryUrl.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,9 @@ export function useRepositoryUrl(
1717
}
1818

1919
let url = normalizeGitUrl(repo.url)
20+
if (!url) {
21+
return null
22+
}
2023

2124
// append `repository.directory` for monorepo packages
2225
if (repo.directory) {

shared/utils/git-providers.ts

Lines changed: 10 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -293,33 +293,16 @@ const providers: ProviderConfig[] = [
293293
* Handles: git+https://, git://, git@host:path, ssh://git@host/path
294294
*/
295295
export function normalizeGitUrl(input: string): string | null {
296-
const raw = input.trim()
297-
if (!raw) return null
298-
299-
const normalized = raw.replace(/^git\+/, '')
300-
301-
// Handle ssh:// and git:// URLs by converting to https://
302-
if (/^(?:ssh|git):\/\//i.test(normalized)) {
303-
try {
304-
const url = new URL(normalized)
305-
const path = url.pathname.replace(/^\/*/, '')
306-
return `https://${url.hostname}/${path}`
307-
} catch {
308-
// Fall through to SCP handling
309-
}
310-
}
311-
312-
if (!/^https?:\/\//i.test(normalized)) {
313-
// Handle SCP-style URLs: git@host:path
314-
const scp = normalized.match(/^(?:git@)?([^:/]+):(.+)$/i)
315-
if (scp?.[1] && scp?.[2]) {
316-
const host = scp[1]
317-
const path = scp[2].replace(/^\/*/, '')
318-
return `https://${host}/${path}`
319-
}
320-
}
321-
322-
return normalized
296+
const url = input
297+
.trim()
298+
.replace(/^git\+/, '')
299+
.replace(/\.git$/, '')
300+
.replace(/(^|\/)[^/]+?@/, '$1') // remove "user@" from "ssh://user@host.com:..."
301+
.replace(/(\.[^./]+?):/, '$1/') // change ".com:" to ".com/" from "ssh://user@host.com:..."
302+
.replace(/^git:\/\//, 'https://')
303+
.replace(/^ssh:\/\//, 'https://')
304+
if (!url) return null
305+
return url.includes('://') ? url : `https://${url}`
323306
}
324307

325308
export function parseRepoUrl(input: string): RepoRef | null {

test/unit/shared/utils/git-providers.spec.ts

Lines changed: 76 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,80 @@
11
import { describe, expect, it } from 'vitest'
2-
import { parseRepositoryInfo, type RepositoryInfo } from '#shared/utils/git-providers'
2+
import {
3+
normalizeGitUrl,
4+
parseRepositoryInfo,
5+
type RepositoryInfo,
6+
} from '#shared/utils/git-providers'
7+
8+
describe('normalizeGitUrl', () => {
9+
it('should return null for empty input', () => {
10+
expect.soft(normalizeGitUrl('')).toBeNull()
11+
})
12+
13+
it('should leave plain HTTPS URLs unchanged', () => {
14+
expect
15+
.soft(normalizeGitUrl('https://github.com/user/repo'))
16+
.toBe('https://github.com/user/repo')
17+
})
18+
19+
it('should remove git+ prefix', () => {
20+
expect
21+
.soft(normalizeGitUrl('git+https://github.com/user/repo'))
22+
.toBe('https://github.com/user/repo')
23+
expect
24+
.soft(normalizeGitUrl('git+https://github.com/user/repo.git'))
25+
.toBe('https://github.com/user/repo')
26+
expect
27+
.soft(normalizeGitUrl('git+ssh://git@github.com/user/repo.git'))
28+
.toBe('https://github.com/user/repo')
29+
})
30+
31+
it('should remove .git suffix', () => {
32+
expect
33+
.soft(normalizeGitUrl('https://github.com/user/repo.git'))
34+
.toBe('https://github.com/user/repo')
35+
expect
36+
.soft(normalizeGitUrl('https://gitlab.com/user/repo.git'))
37+
.toBe('https://gitlab.com/user/repo')
38+
expect
39+
.soft(normalizeGitUrl('https://bitbucket.org/user/repo.git'))
40+
.toBe('https://bitbucket.org/user/repo')
41+
})
42+
43+
it('should convert git:// protocol to https://', () => {
44+
expect.soft(normalizeGitUrl('git://github.com/user/repo')).toBe('https://github.com/user/repo')
45+
expect
46+
.soft(normalizeGitUrl('git://github.com/user/repo.git'))
47+
.toBe('https://github.com/user/repo')
48+
})
49+
50+
it('should convert ssh:// protocol to https://', () => {
51+
expect
52+
.soft(normalizeGitUrl('ssh://git@github.com/user/repo'))
53+
.toBe('https://github.com/user/repo')
54+
expect
55+
.soft(normalizeGitUrl('ssh://git@github.com/user/repo.git'))
56+
.toBe('https://github.com/user/repo')
57+
})
58+
59+
it('should convert SSH format to https://', () => {
60+
expect.soft(normalizeGitUrl('git@github.com:user/repo')).toBe('https://github.com/user/repo')
61+
expect
62+
.soft(normalizeGitUrl('git@github.com:user/repo.git'))
63+
.toBe('https://github.com/user/repo')
64+
expect
65+
.soft(normalizeGitUrl('git@github.com/user/repo:123'))
66+
.toBe('https://github.com/user/repo:123')
67+
})
68+
69+
it('should handle combined permutations', () => {
70+
expect
71+
.soft(normalizeGitUrl('git+git://github.com/user/repo.git'))
72+
.toBe('https://github.com/user/repo')
73+
expect
74+
.soft(normalizeGitUrl('git+ssh://git@gitlab.com/user/repo.git'))
75+
.toBe('https://gitlab.com/user/repo')
76+
})
77+
})
378

479
describe('parseRepositoryInfo', () => {
580
it('returns undefined for undefined input', () => {

0 commit comments

Comments
 (0)