fix(git): derive clone folder from scp-style remote path (#30763)

This commit is contained in:
zzr
2026-10-19 10:58:05 -07:00
committed by GitHub
parent 53191250e8
commit 95f544aedf
2 changed files with 46 additions and 2 deletions
+41 -1
View File
@@ -2,9 +2,49 @@ import { describe, expect, it } from 'vitest'
import { mkdtemp, rm } from 'node:fs/promises'
import { tmpdir } from 'node:os'
import { join } from 'node:path'
import { deriveValidatedClonePath, getClonePathComparisonKey } from './repo-clone-path'
import {
deriveCloneRepoNameFromUrl,
deriveValidatedClonePath,
getClonePathComparisonKey
} from './repo-clone-path'
describe('repo clone path helpers', () => {
it.each([
'git@example.com:project.git',
'example.com:project.git',
'git@[2001:db8::1]:project.git',
'[2001:db8::1]:project.git',
'git@example.com:team/project.git',
'git@example.com:/srv/project.git'
])('derives the repository directory from the path in %s', (url) => {
expect(deriveCloneRepoNameFromUrl(url)).toBe('project')
expect(deriveValidatedClonePath({ url, destination: process.cwd() })).toBe(
join(process.cwd(), 'project')
)
})
it.each([
'https://example.com/team/project.git',
'ssh://git@example.com/team/project.git',
'/srv/project.git',
'C:\\repos\\project.git',
'C:/repos/project.git',
'\\\\server\\share\\project.git'
])('preserves repository names for URL and local path inputs: %s', (url) => {
expect(deriveCloneRepoNameFromUrl(url)).toBe('project')
})
it('preserves a colon in an explicitly local relative path', () => {
expect(deriveCloneRepoNameFromUrl('./local:project.git')).toBe('local:project')
})
it.each(['git@example.com:.', 'git@example.com:..'])(
'rejects an unsafe repository basename in %s',
(url) => {
expect(() => deriveCloneRepoNameFromUrl(url)).toThrow('Invalid repository name')
}
)
it('allows safe repository names that start with two dots', async () => {
const destination = await mkdtemp(join(tmpdir(), 'orca-clone-path-'))
try {
+5 -1
View File
@@ -19,7 +19,11 @@ export function deriveCloneRepoNameFromUrl(url: string): string {
// be "." or ".."; rejecting them prevents parent/destination deletion.
const source = url.replace(/\.git\/?$/, '')
const isWindowsLocalSource = /^[A-Za-z]:[\\/]/.test(source) || source.startsWith('\\\\')
const repoName = isWindowsLocalSource ? win32.basename(source) : posix.basename(source)
const scpPath =
!isWindowsLocalSource && !source.includes('://')
? source.match(/^(?:[^@/:]+@)?(?:\[[^\]]+\]|[^:/\s]+):(.+)$/)?.[1]
: undefined
const repoName = isWindowsLocalSource ? win32.basename(source) : posix.basename(scpPath ?? source)
if (!repoName || repoName === '.' || repoName === '..') {
throw new Error('Invalid repository name derived from URL')
}