Skip to content
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions app/composables/useRepositoryUrl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down
37 changes: 10 additions & 27 deletions shared/utils/git-providers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -293,33 +293,16 @@ 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 url = 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://')
if (!url) return null
return url.includes('://') ? url : `https://${url}`
}

export function parseRepoUrl(input: string): RepoRef | null {
Expand Down
73 changes: 72 additions & 1 deletion test/unit/shared/utils/git-providers.spec.ts
Original file line number Diff line number Diff line change
@@ -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', () => {
Expand Down
Loading