Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(git): normalize repo url without legacy url.parse #5100

Merged
merged 2 commits into from Nov 30, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
25 changes: 25 additions & 0 deletions .yarn/versions/c2cb7806.yml
@@ -0,0 +1,25 @@
releases:
"@yarnpkg/cli": patch
"@yarnpkg/plugin-git": patch

declined:
- "@yarnpkg/plugin-compat"
- "@yarnpkg/plugin-constraints"
- "@yarnpkg/plugin-dlx"
- "@yarnpkg/plugin-essentials"
- "@yarnpkg/plugin-github"
- "@yarnpkg/plugin-init"
- "@yarnpkg/plugin-interactive-tools"
- "@yarnpkg/plugin-nm"
- "@yarnpkg/plugin-npm-cli"
- "@yarnpkg/plugin-pack"
- "@yarnpkg/plugin-patch"
- "@yarnpkg/plugin-pnp"
- "@yarnpkg/plugin-pnpm"
- "@yarnpkg/plugin-stage"
- "@yarnpkg/plugin-typescript"
- "@yarnpkg/plugin-version"
- "@yarnpkg/plugin-workspace-tools"
- "@yarnpkg/builder"
- "@yarnpkg/core"
- "@yarnpkg/doctor"
29 changes: 8 additions & 21 deletions packages/plugin-git/sources/gitUtils.ts
Expand Up @@ -5,7 +5,8 @@ import GitUrlParse
import capitalize from 'lodash/capitalize';
import querystring from 'querystring';
import semver from 'semver';
import urlLib from 'url';

import {tryParseGitURL} from './hosted-git-info-parse';

function makeGitEnvironment() {
return {
Expand Down Expand Up @@ -126,27 +127,13 @@ export function normalizeRepoUrl(url: string, {git = false}: {git?: boolean} = {
url = url.replace(/^https:\/\/github\.com\/(?!\.{1,2}\/)([a-zA-Z0-9._-]+)\/(?!\.{1,2}(?:#|$))([a-zA-Z0-9._-]+?)\/tarball\/(.+)?$/, `https://github.com/$1/$2.git#$3`);

if (git) {
// The `git+` prefix doesn't mean anything at all for Git
url = url.replace(/^git\+([^:]+):/, `$1:`);

// The `ssh://` prefix should be removed because so URLs won't work in Git:
// ssh://git@github.com:yarnpkg/berry.git
// git@github.com/yarnpkg/berry.git
// Git only allows:
// git@github.com:yarnpkg/berry.git (no ssh)
// ssh://git@github.com/yarnpkg/berry.git (no colon)
// So we should cut `ssh://`, but only in URLs that contain colon after the hostname

let parsedUrl: urlLib.UrlWithStringQuery | null;
try {
parsedUrl = urlLib.parse(url);
} catch {
parsedUrl = null;
}
// Try to normalize the URL in a way that git accepts.
const parsedUrl = tryParseGitURL(url);
if (parsedUrl)
url = parsedUrl.href;

if (parsedUrl && parsedUrl.protocol === `ssh:` && parsedUrl.path?.startsWith(`/:`)) {
url = url.replace(/^ssh:\/\//, ``);
}
// The `git+` prefix doesn't mean anything at all for Git.
url = url.replace(/^git\+([^:]+):/, `$1:`);
}

return url;
Expand Down
67 changes: 67 additions & 0 deletions packages/plugin-git/sources/hosted-git-info-parse.ts
@@ -0,0 +1,67 @@
// Based on https://github.com/npm/hosted-git-info/blob/cf8115d6fa056fbfb0d63d4d13bde6116b2a02e0/lib/parse-url.js

/**
@license
Copyright (c) 2015, Rebecca Turner

Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.

THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
PERFORMANCE OF THIS SOFTWARE.
*/

function lastIndexOfBefore(str: string, char: string, beforeChar: string) {
const startPosition = str.indexOf(beforeChar);
return str.lastIndexOf(char, startPosition > -1 ? startPosition : Infinity);
}

function safeURL(url: string) {
try {
return new URL(url);
} catch {
return undefined;
}
}

// 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 = `ssh://${gitURL}`;

return gitURL;
}

export function tryParseGitURL(gitURL: string) {
return safeURL(gitURL) || safeURL(correctURL(gitURL));
}
4 changes: 2 additions & 2 deletions packages/plugin-git/tests/__snapshots__/gitUtils.test.ts.snap
Expand Up @@ -46,7 +46,7 @@ exports[`gitUtils should properly normalize git+ssh://git@github.com/yarnpkg/uti

exports[`gitUtils should properly normalize git+ssh://git@github.com:yarnpkg/berry.git#v2.1.1 ({ git: false }) 1`] = `"git+ssh://git@github.com:yarnpkg/berry.git#v2.1.1"`;

exports[`gitUtils should properly normalize git+ssh://git@github.com:yarnpkg/berry.git#v2.1.1 ({ git: true }) 1`] = `"git@github.com:yarnpkg/berry.git#v2.1.1"`;
exports[`gitUtils should properly normalize git+ssh://git@github.com:yarnpkg/berry.git#v2.1.1 ({ git: true }) 1`] = `"ssh://git@github.com/yarnpkg/berry.git#v2.1.1"`;

exports[`gitUtils should properly normalize git://github.com/yarnpkg/util-deprecate.git#v1.0.1 ({ git: false }) 1`] = `"git://github.com/yarnpkg/util-deprecate.git#v1.0.1"`;

Expand Down Expand Up @@ -86,7 +86,7 @@ exports[`gitUtils should properly normalize ssh://git@github.com/yarnpkg/util-de

exports[`gitUtils should properly normalize ssh://git@github.com:yarnpkg/berry.git#v2.1.1 ({ git: false }) 1`] = `"ssh://git@github.com:yarnpkg/berry.git#v2.1.1"`;

exports[`gitUtils should properly normalize ssh://git@github.com:yarnpkg/berry.git#v2.1.1 ({ git: true }) 1`] = `"git@github.com:yarnpkg/berry.git#v2.1.1"`;
exports[`gitUtils should properly normalize ssh://git@github.com:yarnpkg/berry.git#v2.1.1 ({ git: true }) 1`] = `"ssh://git@github.com/yarnpkg/berry.git#v2.1.1"`;

exports[`gitUtils should properly split GitHubOrg/foo-bar.js 1`] = `
{
Expand Down