From f7decab8175ab8e9f395fbe89cb802dd6485fd3c Mon Sep 17 00:00:00 2001 From: bluwy Date: Tue, 17 Mar 2026 09:50:13 +0800 Subject: [PATCH 1/7] fix: improve normalizeGitUrl --- app/composables/useRepositoryUrl.ts | 3 +++ shared/utils/git-providers.ts | 36 ++++++++--------------------- 2 files changed, 12 insertions(+), 27 deletions(-) diff --git a/app/composables/useRepositoryUrl.ts b/app/composables/useRepositoryUrl.ts index 6e3b4ee718..0e65aa0ee6 100644 --- a/app/composables/useRepositoryUrl.ts +++ b/app/composables/useRepositoryUrl.ts @@ -17,6 +17,9 @@ export function useRepositoryUrl( } let url = normalizeGitUrl(repo.url) + if (!url) { + return null + } // append `repository.directory` for monorepo packages if (repo.directory) { diff --git a/shared/utils/git-providers.ts b/shared/utils/git-providers.ts index e14e82f5f7..dec3330c9c 100644 --- a/shared/utils/git-providers.ts +++ b/shared/utils/git-providers.ts @@ -293,33 +293,15 @@ const providers: ProviderConfig[] = [ * Handles: git+https://, git://, git@host:path, ssh://git@host/path */ export function normalizeGitUrl(input: string): string | null { - const raw = input.trim() - if (!raw) return null - - const normalized = raw.replace(/^git\+/, '') - - // Handle ssh:// and git:// URLs by converting to https:// - if (/^(?:ssh|git):\/\//i.test(normalized)) { - try { - const url = new URL(normalized) - const path = url.pathname.replace(/^\/*/, '') - return `https://${url.hostname}/${path}` - } catch { - // Fall through to SCP handling - } - } - - if (!/^https?:\/\//i.test(normalized)) { - // Handle SCP-style URLs: git@host:path - const scp = normalized.match(/^(?:git@)?([^:/]+):(.+)$/i) - if (scp?.[1] && scp?.[2]) { - const host = scp[1] - const path = scp[2].replace(/^\/*/, '') - return `https://${host}/${path}` - } - } - - return normalized + const normalized = input + .trim() + .replace(/^git\+/, '') + .replace(/\.git$/, '') + .replace(/(^|\/)[^/]+?@/, '$1') // remove "user@" from "ssh://user@host.com:..." + .replace(/(\.[^.]+?):/, '$1/') // change ".com:" to ".com/" from "ssh://user@host.com:..." + .replace(/^git:\/\//, 'https://') + .replace(/^ssh:\/\//, 'https://') + return normalized || null } export function parseRepoUrl(input: string): RepoRef | null { From 3624c79a49b6c016369de1f8d144f8d77b22a2ae Mon Sep 17 00:00:00 2001 From: bluwy Date: Tue, 17 Mar 2026 10:06:21 +0800 Subject: [PATCH 2/7] chore: fix bug --- shared/utils/git-providers.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/shared/utils/git-providers.ts b/shared/utils/git-providers.ts index dec3330c9c..2c51dfa81d 100644 --- a/shared/utils/git-providers.ts +++ b/shared/utils/git-providers.ts @@ -298,7 +298,7 @@ export function normalizeGitUrl(input: string): string | null { .replace(/^git\+/, '') .replace(/\.git$/, '') .replace(/(^|\/)[^/]+?@/, '$1') // remove "user@" from "ssh://user@host.com:..." - .replace(/(\.[^.]+?):/, '$1/') // change ".com:" to ".com/" from "ssh://user@host.com:..." + .replace(/(\.[^./]+?):/, '$1/') // change ".com:" to ".com/" from "ssh://user@host.com:..." .replace(/^git:\/\//, 'https://') .replace(/^ssh:\/\//, 'https://') return normalized || null From f443ecdc8b7c17aa12d70c8fc3d818465f1c0216 Mon Sep 17 00:00:00 2001 From: bluwy Date: Tue, 17 Mar 2026 10:12:44 +0800 Subject: [PATCH 3/7] chore: improve --- shared/utils/git-providers.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/shared/utils/git-providers.ts b/shared/utils/git-providers.ts index 2c51dfa81d..2505ffe7db 100644 --- a/shared/utils/git-providers.ts +++ b/shared/utils/git-providers.ts @@ -293,7 +293,7 @@ const providers: ProviderConfig[] = [ * Handles: git+https://, git://, git@host:path, ssh://git@host/path */ export function normalizeGitUrl(input: string): string | null { - const normalized = input + let url = input .trim() .replace(/^git\+/, '') .replace(/\.git$/, '') @@ -301,7 +301,8 @@ export function normalizeGitUrl(input: string): string | null { .replace(/(\.[^./]+?):/, '$1/') // change ".com:" to ".com/" from "ssh://user@host.com:..." .replace(/^git:\/\//, 'https://') .replace(/^ssh:\/\//, 'https://') - return normalized || null + url = url.includes('://') ? url : `https://${url}` + return url || null } export function parseRepoUrl(input: string): RepoRef | null { From cb2fecf4b05f2c2951fff590652b6b4b87147056 Mon Sep 17 00:00:00 2001 From: bluwy Date: Tue, 17 Mar 2026 10:26:18 +0800 Subject: [PATCH 4/7] chore: fix --- shared/utils/git-providers.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/shared/utils/git-providers.ts b/shared/utils/git-providers.ts index 2505ffe7db..7a4e9b81f0 100644 --- a/shared/utils/git-providers.ts +++ b/shared/utils/git-providers.ts @@ -301,8 +301,8 @@ export function normalizeGitUrl(input: string): string | null { .replace(/(\.[^./]+?):/, '$1/') // change ".com:" to ".com/" from "ssh://user@host.com:..." .replace(/^git:\/\//, 'https://') .replace(/^ssh:\/\//, 'https://') - url = url.includes('://') ? url : `https://${url}` - return url || null + if (!url) return null + return url.includes('://') ? url : `https://${url}` } export function parseRepoUrl(input: string): RepoRef | null { From 48fb4e84f79f2171dd3f90da07dbaad2b00128a5 Mon Sep 17 00:00:00 2001 From: bluwy Date: Tue, 17 Mar 2026 10:26:40 +0800 Subject: [PATCH 5/7] chore: typo --- shared/utils/git-providers.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/shared/utils/git-providers.ts b/shared/utils/git-providers.ts index 7a4e9b81f0..cf1277fa53 100644 --- a/shared/utils/git-providers.ts +++ b/shared/utils/git-providers.ts @@ -293,7 +293,7 @@ const providers: ProviderConfig[] = [ * Handles: git+https://, git://, git@host:path, ssh://git@host/path */ export function normalizeGitUrl(input: string): string | null { - let url = input + const url = input .trim() .replace(/^git\+/, '') .replace(/\.git$/, '') From bf3e34c6a8098d0372a17b686afe9c05b9516960 Mon Sep 17 00:00:00 2001 From: bluwy Date: Wed, 18 Mar 2026 09:13:00 +0800 Subject: [PATCH 6/7] chore: add test --- test/unit/shared/utils/git-providers.spec.ts | 73 +++++++++++++++++++- 1 file changed, 72 insertions(+), 1 deletion(-) diff --git a/test/unit/shared/utils/git-providers.spec.ts b/test/unit/shared/utils/git-providers.spec.ts index 242f8b1026..3124513d07 100644 --- a/test/unit/shared/utils/git-providers.spec.ts +++ b/test/unit/shared/utils/git-providers.spec.ts @@ -1,5 +1,76 @@ import { describe, expect, it } from 'vitest' -import { parseRepositoryInfo, type RepositoryInfo } from '#shared/utils/git-providers' +import { + normalizeGitUrl, + parseRepositoryInfo, + type RepositoryInfo, +} from '#shared/utils/git-providers' + +describe('normalizeGitUrl', () => { + it('should leave plain HTTPS URLs unchanged', () => { + expect + .soft(normalizeGitUrl('https://github.com/user/repo')) + .toBe('https://github.com/user/repo') + }) + + it('should remove git+ prefix', () => { + expect + .soft(normalizeGitUrl('git+https://github.com/user/repo')) + .toBe('https://github.com/user/repo') + expect + .soft(normalizeGitUrl('git+https://github.com/user/repo.git')) + .toBe('https://github.com/user/repo') + expect + .soft(normalizeGitUrl('git+ssh://git@github.com/user/repo.git')) + .toBe('https://github.com/user/repo') + }) + + it('should remove .git suffix', () => { + expect + .soft(normalizeGitUrl('https://github.com/user/repo.git')) + .toBe('https://github.com/user/repo') + expect + .soft(normalizeGitUrl('https://gitlab.com/user/repo.git')) + .toBe('https://gitlab.com/user/repo') + expect + .soft(normalizeGitUrl('https://bitbucket.org/user/repo.git')) + .toBe('https://bitbucket.org/user/repo') + }) + + it('should convert git:// protocol to https://', () => { + expect.soft(normalizeGitUrl('git://github.com/user/repo')).toBe('https://github.com/user/repo') + expect + .soft(normalizeGitUrl('git://github.com/user/repo.git')) + .toBe('https://github.com/user/repo') + }) + + it('should convert ssh:// protocol to https://', () => { + expect + .soft(normalizeGitUrl('ssh://git@github.com/user/repo')) + .toBe('https://github.com/user/repo') + expect + .soft(normalizeGitUrl('ssh://git@github.com/user/repo.git')) + .toBe('https://github.com/user/repo') + }) + + it('should convert git@github.com SSH format', () => { + expect.soft(normalizeGitUrl('git@github.com:user/repo')).toBe('https://github.com/user/repo') + expect + .soft(normalizeGitUrl('git@github.com:user/repo.git')) + .toBe('https://github.com/user/repo') + expect + .soft(normalizeGitUrl('git@github.com/user/repo:123')) + .toBe('https://github.com/user/repo:123') + }) + + it('should handle combined permutations', () => { + expect + .soft(normalizeGitUrl('git+git://github.com/user/repo.git')) + .toBe('https://github.com/user/repo') + expect + .soft(normalizeGitUrl('git+ssh://git@gitlab.com/user/repo.git')) + .toBe('https://gitlab.com/user/repo') + }) +}) describe('parseRepositoryInfo', () => { it('returns undefined for undefined input', () => { From 96a24a4bdad81e1572771a469ccf20add7f25868 Mon Sep 17 00:00:00 2001 From: bluwy Date: Wed, 18 Mar 2026 09:23:22 +0800 Subject: [PATCH 7/7] test: handle reviews --- test/unit/shared/utils/git-providers.spec.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/test/unit/shared/utils/git-providers.spec.ts b/test/unit/shared/utils/git-providers.spec.ts index 3124513d07..4b6b5dbad3 100644 --- a/test/unit/shared/utils/git-providers.spec.ts +++ b/test/unit/shared/utils/git-providers.spec.ts @@ -6,6 +6,10 @@ import { } from '#shared/utils/git-providers' describe('normalizeGitUrl', () => { + it('should return null for empty input', () => { + expect.soft(normalizeGitUrl('')).toBeNull() + }) + it('should leave plain HTTPS URLs unchanged', () => { expect .soft(normalizeGitUrl('https://github.com/user/repo')) @@ -52,7 +56,7 @@ describe('normalizeGitUrl', () => { .toBe('https://github.com/user/repo') }) - it('should convert git@github.com SSH format', () => { + it('should convert SSH format to https://', () => { expect.soft(normalizeGitUrl('git@github.com:user/repo')).toBe('https://github.com/user/repo') expect .soft(normalizeGitUrl('git@github.com:user/repo.git'))