diff --git a/src/shared/plugins/catalog/plugin-marketplace.test.ts b/src/shared/plugins/catalog/plugin-marketplace.test.ts index 458f7309ec9..e407c8fe677 100644 --- a/src/shared/plugins/catalog/plugin-marketplace.test.ts +++ b/src/shared/plugins/catalog/plugin-marketplace.test.ts @@ -232,6 +232,13 @@ describe('isSameGitRepository', () => { 'git@gitlab.com:Acme/Team-A/Tool' ) ).toBe(true) + // A self-hosted server may serve these as two repositories, so an approval must not carry. + expect( + isSameGitRepository( + 'https://git.example.com/team/Plugin.git', + 'https://git.example.com/team/plugin.git' + ) + ).toBe(false) expect( isSameGitRepository( 'https://gitlab.com/acme/team-a/tool', diff --git a/src/shared/plugins/catalog/plugin-marketplace.ts b/src/shared/plugins/catalog/plugin-marketplace.ts index f56e1d19e95..6e145d56c66 100644 --- a/src/shared/plugins/catalog/plugin-marketplace.ts +++ b/src/shared/plugins/catalog/plugin-marketplace.ts @@ -270,6 +270,9 @@ type GitRepositoryIdentity = { path: string } +// Why only these: a self-hosted server may serve `team/Plugin` and `team/plugin` as two repositories. +const CASE_INSENSITIVE_GIT_HOSTS = new Set(['github.com', 'gitlab.com', 'bitbucket.org']) + /** Extracts the host and first owner segment from the HTTPS/SSH Git forms the * installer accepts. It is intentionally only a provenance parser. */ export function parseGitRepositoryIdentity(url: string): GitRepositoryIdentity | null { @@ -302,17 +305,21 @@ function repositoryIdentity(host: string, repositoryPath: string): GitRepository if (!repository) { return null } - const path = [...segments.slice(0, -1), repository].join('/').toLowerCase() - return { host: host.toLowerCase(), owner: segments[0]!, repository, path } + const lowerHost = host.toLowerCase() + const exactPath = [...segments.slice(0, -1), repository].join('/') + const path = CASE_INSENSITIVE_GIT_HOSTS.has(lowerHost) ? exactPath.toLowerCase() : exactPath + return { host: lowerHost, owner: segments[0]!, repository, path } } /** `host/owner` of a repository, with every GitLab subgroup (`gitlab.com/acme/tools`). */ export function gitRepositoryOwnerLabel(url: string): string | null { const identity = parseGitRepositoryIdentity(url) - return identity ? [identity.host, ...identity.path.split('/').slice(0, -1)].join('/') : null + return identity + ? [identity.host, ...identity.path.split('/').slice(0, -1)].join('/').toLowerCase() + : null } -/** Same repository regardless of URL spelling (https vs scp, `.git`, case). */ +/** Same repository regardless of URL spelling (https vs scp, `.git`, case where the host ignores it). */ export function isSameGitRepository(left: string, right: string): boolean { const a = parseGitRepositoryIdentity(left) const b = parseGitRepositoryIdentity(right)