Skip to content

Commit

Permalink
handle git+ssh with semver
Browse files Browse the repository at this point in the history
  • Loading branch information
andreineculau committed Mar 18, 2023
1 parent eceaa8b commit 1b7c82a
Showing 1 changed file with 39 additions and 28 deletions.
67 changes: 39 additions & 28 deletions resolving/git-resolver/src/parsePref.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,15 +38,8 @@ export async function parsePref (pref: string): Promise<HostedPackageSpec | null
if (colonsPos === -1) return null
const protocol = pref.slice(0, colonsPos)
if (protocol && gitProtocols.has(protocol.toLocaleLowerCase())) {
const urlparse = new URL(escapeColon(pref))
const urlparse = new URL(correctUrl(pref))
if (!urlparse?.protocol) return null
const match = urlparse.protocol === 'git+ssh:' && matchGitScp(pref)
if (match) {
return {
...match,
normalizedPref: pref,
}
}

const committish = (urlparse.hash?.length > 1) ? decodeURIComponent(urlparse.hash.slice(1)) : null
return {
Expand All @@ -58,13 +51,6 @@ export async function parsePref (pref: string): Promise<HostedPackageSpec | null
return null
}

function escapeColon (url: string) {
if (!url.includes('@')) return url
const [front, ...backs] = url.split('@')
const escapedBacks = backs.map(e => e.replace(/:([^/\d]|\d+[^:/\d])/, ':/$1'))
return [front, ...escapedBacks].join('@')
}

function urlToFetchSpec (urlparse: URL) {
urlparse.hash = ''
const fetchSpec = url.format(urlparse)
Expand Down Expand Up @@ -151,18 +137,43 @@ function setGitCommittish (committish: string | null) {
return { gitCommittish: committish }
}

function matchGitScp (spec: string) {
// git ssh specifiers are overloaded to also use scp-style git
// specifiers, so we have to parse those out and treat them special.
// They are NOT true URIs, so we can't hand them to `url.parse`.
//
// This regex looks for things that look like:
// git+ssh://git@my.custom.git.com:username/project.git#deadbeef
//
// ...and various combinations. The username in the beginning is *required*.
const matched = spec.match(/^git\+ssh:\/\/([^:]+:[^#]+(?:\.git)?)(?:#(.*))$/i)
return (matched != null) && (matched[1].match(/:[0-9]+\/?.*$/i) == null) && {
fetchSpec: matched[1],
gitCommittish: matched[2],
// see https://github.com/npm/hosted-git-info/blob/f03bfbd3022c8f6283a991ff879ed97704ac35fa/lib/parse-url.js#L3
function lastIndexOfBefore (str: string, char: string, beforeChar: string) {
const startPosition = str.indexOf(beforeChar)
return str.lastIndexOf(char, startPosition > -1 ? startPosition : Infinity)
}

// see https://github.com/npm/hosted-git-info/blob/f03bfbd3022c8f6283a991ff879ed97704ac35fa/lib/parse-url.js#L41
// attempt to correct an scp style url so that it will parse with `new URL()`
function correctUrl (giturl: string) {
// ignore @ that come after the first hash since the denotes the start
// of a committish which can contain @ characters
const firstAt = lastIndexOfBefore(giturl, '@', '#')
// ignore colons that come after the hash since that could include colons such as:
// git@github.com:user/package-2#semver:^1.0.0
const lastColonBeforeHash = lastIndexOfBefore(giturl, ':', '#')

if (lastColonBeforeHash > firstAt) {
// the last : comes after the first @ (or there is no @)
// like it would in:
// proto://hostname.com:user/repo
// username@hostname.com:user/repo
// :password@hostname.com:user/repo
// username:password@hostname.com:user/repo
// proto://username@hostname.com:user/repo
// proto://:password@hostname.com:user/repo
// proto://username:password@hostname.com:user/repo
// then we replace the last : with a / to create a valid path
giturl = giturl.slice(0, lastColonBeforeHash) + '/' + giturl.slice(lastColonBeforeHash + 1)
}

if (lastIndexOfBefore(giturl, ':', '#') === -1 && giturl.indexOf('//') === -1) {
// we have no : at all
// as it would be in:
// username@hostname.com/user/repo
// then we prepend a protocol
giturl = `git+ssh://${giturl}`
}

return giturl
}

0 comments on commit 1b7c82a

Please sign in to comment.