From 1f0559930ba9f3c8759fb8a19ea4b2a6075792b3 Mon Sep 17 00:00:00 2001 From: Eric Amodio Date: Fri, 29 Jan 2021 01:27:11 -0500 Subject: [PATCH] Closes #1196 - adds regex pattern for remotes --- CHANGELOG.md | 1 + package.json | 48 ++++++++++++++++--------- src/config.ts | 24 +++++++++---- src/git/remotes/factory.ts | 90 +++++++++++++++++++++++++++++++++++----------- 4 files changed, 118 insertions(+), 45 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 47c83de..d4359ae 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -19,6 +19,7 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/) and this p - Adds a `gitlens.advanced.abbreviateShaOnCopy` setting to specify to whether to copy full or abbreviated commit SHAs to the clipboard. Abbreviates to the length of `gitlens.advanced.abbreviatedShaLength` — closes [#1062](https://github.com/eamodio/vscode-gitlens/issues/1062) — thanks to [PR #1316](https://github.com/eamodio/vscode-gitlens/pull/1316) by Brendon Smith ([@br3ndonland](https://github.com/br3ndonland)) - Adds a `gitlens.advanced.externalDiffTool` setting to specify an optional external diff tool to use when comparing files. Must be a configured [Git difftool](https://git-scm.com/docs/git-config#Documentation/git-config.txt-difftool). - Adds a `gitlens.advanced.externalDirectoryDiffTool` setting to specify an optional external diff tool to use when comparing directories. Must be a configured [Git difftool](https://git-scm.com/docs/git-config#Documentation/git-config.txt-difftool). +- Adds a new `regex` option to `gitlens.remotes` to better support custom remote matching — closes [#1196](https://github.com/eamodio/vscode-gitlens/issues/1196) ### Changed diff --git a/package.json b/package.json index 2e973e1..4941f2f 100644 --- a/package.json +++ b/package.json @@ -1430,16 +1430,27 @@ "items": { "type": "object", "required": [ - "type", - "domain" + "type" + ], + "oneOf": [ + { + "required": [ + "domain" + ] + }, + { + "required": [ + "regex" + ] + } ], "properties": { "type": { "type": "string", "enum": [ + "Custom", "Bitbucket", "BitbucketServer", - "Custom", "GitHub", "GitLab" ], @@ -1449,6 +1460,10 @@ "type": "string", "description": "Specifies the domain name of the custom remote service" }, + "regex": { + "type": "string", + "description": "Specifies a regular expression to capture the \"domain name\" and \"path\" of the custom remote service" + }, "name": { "type": "string", "description": "Specifies an optional friendly name for the custom remote service" @@ -1474,45 +1489,44 @@ "properties": { "repository": { "type": "string", - "description": "Specifies the format of a respository url for the custom remote service\nAvailable tokens\n ${repo}` — repository path" + "markdownDescription": "Specifies the format of a respository url for the custom remote service\n\nAvailable tokens\\\n`${repo}` — repository path" }, "branches": { "type": "string", - "description": "Specifies the format of a branches url for the custom remote service\nAvailable tokens\n ${repo}` — repository path\n ${branch}` — branch" + "markdownDescription": "Specifies the format of a branches url for the custom remote service\n\nAvailable tokens\\\n`${repo}` — repository path\\\n`${branch}` — branch" }, "branch": { "type": "string", - "description": "Specifies the format of a branch url for the custom remote service\nAvailable tokens\n ${repo}` — repository path\n ${branch}` — branch" + "markdownDescription": "Specifies the format of a branch url for the custom remote service\n\nAvailable tokens\\\n`${repo}` — repository path\\\n`${branch}` — branch" }, "commit": { "type": "string", - "description": "Specifies the format of a commit url for the custom remote service\nAvailable tokens\n ${repo}` — repository path\n ${id}` — commit SHA" + "markdownDescription": "Specifies the format of a commit url for the custom remote service\n\nAvailable tokens\\\n`${repo}` — repository path\\\n`${id}` — commit SHA" }, "file": { "type": "string", - "description": "Specifies the format of a file url for the custom remote service\nAvailable tokens\n ${repo}` — repository path\n ${file}` — file name\n ${line}` — formatted line information" + "markdownDescription": "Specifies the format of a file url for the custom remote service\n\nAvailable tokens\\\n`${repo}` — repository path\\\n`${file}` — file name\\\n`${line}` — formatted line information" }, "fileInBranch": { "type": "string", - "description": "Specifies the format of a branch file url for the custom remote service\nAvailable tokens\n ${repo}` — repository path\n ${file}` — file name\n ${branch}` — branch\n ${line}` — formatted line information" + "markdownDescription": "Specifies the format of a branch file url for the custom remote service\n\nAvailable tokens\\\n`${repo}` — repository path\\\n`${file}` — file name\\\n`${branch}` — branch\\\n`${line}` — formatted line information" }, "fileInCommit": { "type": "string", - "description": "Specifies the format of a commit file url for the custom remote service\nAvailable tokens\n ${repo}` — repository path\n ${file}` — file name\n ${id}` — commit SHA\n ${line}` — formatted line information" + "markdownDescription": "Specifies the format of a commit file url for the custom remote service\n\nAvailable tokens\\\n`${repo}` — repository path\\\n`${file}` — file name\\\n`${id}` — commit SHA\\\n`${line}` — formatted line information" }, "fileLine": { "type": "string", - "description": "Specifies the format of a line in a file url for the custom remote service\nAvailable tokens\n ${line}` — line" + "markdownDescription": "Specifies the format of a line in a file url for the custom remote service\n\nAvailable tokens\\\n`${line}` — line" }, "fileRange": { "type": "string", - "description": "Specifies the format of a range in a file url for the custom remote service\nAvailable tokens\n ${start}` — starting line\n ${end}` — ending line" + "markdownDescription": "Specifies the format of a range in a file url for the custom remote service\n\nAvailable tokens\\\n`${start}` — starting line\\\n`${end}` — ending line" } - } - }, - "description": "Specifies the url formats of the custom remote service" - }, - "additionalProperties": false + }, + "additionalProperties": false + } + } }, "uniqueItems": true, "markdownDescription": "Specifies user-defined remote (code-hosting) services or custom domains for built-in remote services", diff --git a/src/config.ts b/src/config.ts index 346d682..fcb37a2 100644 --- a/src/config.ts +++ b/src/config.ts @@ -412,13 +412,23 @@ export interface ModeConfig { statusBar?: boolean; } -export interface RemotesConfig { - domain: string; - name?: string; - protocol?: string; - type: CustomRemoteType; - urls?: RemotesUrlsConfig; -} +export type RemotesConfig = + | { + domain: string; + regex: null; + name?: string; + protocol?: string; + type: CustomRemoteType; + urls?: RemotesUrlsConfig; + } + | { + domain: null; + regex: string; + name?: string; + protocol?: string; + type: CustomRemoteType; + urls?: RemotesUrlsConfig; + }; export interface RemotesUrlsConfig { repository: string; diff --git a/src/git/remotes/factory.ts b/src/git/remotes/factory.ts index 3fc6332..6c1fe7e 100644 --- a/src/git/remotes/factory.ts +++ b/src/git/remotes/factory.ts @@ -10,19 +10,48 @@ import { Logger } from '../../logger'; import { RemoteProvider, RichRemoteProvider } from './provider'; export { RemoteProvider, RichRemoteProvider }; -export type RemoteProviders = [string | RegExp, (domain: string, path: string) => RemoteProvider][]; +export type RemoteProviders = { + custom: boolean; + matcher: string | RegExp; + creator: (domain: string, path: string) => RemoteProvider; +}[]; -const defaultProviders: RemoteProviders = [ - ['bitbucket.org', (domain: string, path: string) => new BitbucketRemote(domain, path)], - ['github.com', (domain: string, path: string) => new GitHubRemote(domain, path)], - ['gitlab.com', (domain: string, path: string) => new GitLabRemote(domain, path)], - [/\bdev\.azure\.com$/i, (domain: string, path: string) => new AzureDevOpsRemote(domain, path)], - [/\bbitbucket\b/i, (domain: string, path: string) => new BitbucketServerRemote(domain, path)], - [/\bgitlab\b/i, (domain: string, path: string) => new GitLabRemote(domain, path)], - [ - /\bvisualstudio\.com$/i, - (domain: string, path: string) => new AzureDevOpsRemote(domain, path, undefined, undefined, true), - ], +const builtInProviders: RemoteProviders = [ + { + custom: false, + matcher: 'bitbucket.org', + creator: (domain: string, path: string) => new BitbucketRemote(domain, path), + }, + { + custom: false, + matcher: 'github.com', + creator: (domain: string, path: string) => new GitHubRemote(domain, path), + }, + { + custom: false, + matcher: 'gitlab.com', + creator: (domain: string, path: string) => new GitLabRemote(domain, path), + }, + { + custom: false, + matcher: /\bdev\.azure\.com$/i, + creator: (domain: string, path: string) => new AzureDevOpsRemote(domain, path), + }, + { + custom: false, + matcher: /\bbitbucket\b/i, + creator: (domain: string, path: string) => new BitbucketServerRemote(domain, path), + }, + { + custom: false, + matcher: /\bgitlab\b/i, + creator: (domain: string, path: string) => new GitLabRemote(domain, path), + }, + { + custom: false, + matcher: /\bvisualstudio\.com$/i, + creator: (domain: string, path: string) => new AzureDevOpsRemote(domain, path, undefined, undefined, true), + }, ]; export class RemoteProviderFactory { @@ -33,12 +62,19 @@ export class RemoteProviderFactory { static create(providers: RemoteProviders, domain: string, path: string): RemoteProvider | undefined { try { const key = domain.toLowerCase(); - for (const [matcher, creator] of providers) { - if ( - (typeof matcher === 'string' && matcher === key) || - (typeof matcher !== 'string' && matcher.test(key)) - ) { - return creator(domain, path); + for (const { custom, matcher, creator } of providers) { + if (typeof matcher === 'string') { + if (matcher === key) return creator(domain, path); + + continue; + } + + if (matcher.test(key)) return creator(domain, path); + if (!custom) continue; + + const match = matcher.exec(`${domain}/${path}`); + if (match != null) { + return creator(match[1], match[2]); } } @@ -55,13 +91,25 @@ export class RemoteProviderFactory { if (cfg != null && cfg.length > 0) { for (const rc of cfg) { const provider = this.getCustomProvider(rc); - if (provider === undefined) continue; + if (provider == null) continue; + + let matcher: string | RegExp | undefined; + try { + matcher = rc.regex ? new RegExp(rc.regex, 'i') : rc.domain?.toLowerCase(); + if (matcher == null) throw new Error('No matcher found'); + } catch (ex) { + Logger.error(ex, `Loading remote provider '${rc.name ?? ''}' failed`); + } - providers.push([rc.domain.toLowerCase(), provider]); + providers.push({ + custom: true, + matcher: matcher!, + creator: provider, + }); } } - providers.push(...defaultProviders); + providers.push(...builtInProviders); return providers; }