diff --git a/CHANGELOG.md b/CHANGELOG.md index 6abc105c1aac2..648c633f54770 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,7 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/) and this p - Adds the ability to change a branch's merge target in Home view. ([#4224](https://github.com/gitkraken/vscode-gitlens/issues/4224)) - Adds Google Gemini 2.5 Flash (Preview) model, and OpenAI GPT-4.1, GPT-4.1 mini, GPT-4.1 nano, o4 mini, and o3 models for GitLens' AI features ([#4235](https://github.com/gitkraken/vscode-gitlens/issues/4235)) - Adds _Open File at Revision from Remote_ command to open the specific file revision from a remote file URL +- Adds a new `version` option to `gitlens.remotes` to support some providers where the version affects the URL format, such as GitLab. ### Changed diff --git a/package.json b/package.json index 8d9e6a5ecf98b..84bf4bc125c6e 100644 --- a/package.json +++ b/package.json @@ -3889,6 +3889,10 @@ "default": false, "description": "Specifies whether to ignore invalid SSL certificate errors when connecting to the remote service" }, + "version": { + "type": "string", + "description": "Specifies the version of the remote service. Only used by some services such as GitLab." + }, "urls": { "type": "object", "required": [ diff --git a/src/config.ts b/src/config.ts index 30046291ccb80..067cacc45b9e8 100644 --- a/src/config.ts +++ b/src/config.ts @@ -648,6 +648,7 @@ export type RemotesConfig = readonly type: CustomRemoteType; readonly urls?: RemotesUrlsConfig; readonly ignoreSSLErrors?: boolean | 'force'; + readonly version?: string; } | { readonly domain: null; @@ -657,6 +658,7 @@ export type RemotesConfig = readonly type: CustomRemoteType; readonly urls?: RemotesUrlsConfig; readonly ignoreSSLErrors?: boolean | 'force'; + readonly version?: string; }; export interface RemotesUrlsConfig { diff --git a/src/git/remotes/gitlab.ts b/src/git/remotes/gitlab.ts index f24118d259e07..fd70f8ced9394 100644 --- a/src/git/remotes/gitlab.ts +++ b/src/git/remotes/gitlab.ts @@ -44,6 +44,7 @@ export class GitLabRemote extends RemoteProvider { protocol?: string, name?: string, custom: boolean = false, + public readonly version?: string, ) { super(domain, path, protocol, name, custom); } @@ -52,8 +53,16 @@ export class GitLabRemote extends RemoteProvider { return this.custom ? `${this.protocol}://${this.domain}/api` : `https://${this.domain}/api`; } + get urlPrefix(): string { + // before gitlab 12, the /-/ URL syntax did not exist, see + // - https://gitlab.com/gitlab-org/gitlab/-/issues/29572 + // - https://github.com/gitkraken/vscode-gitlens/pull/2022 + const legacyUrlFormat = this.version && !Number.isNaN(Number(this.version)) && Number(this.version) < 12; + return legacyUrlFormat ? '' : '/-'; + } + protected override get issueLinkPattern(): string { - return `${this.baseUrl}/-/issues/`; + return `${this.baseUrl}${this.urlPrefix}/issues/`; } private _autolinks: (AutolinkReference | DynamicAutolinkReference)[] | undefined; @@ -73,7 +82,7 @@ export class GitLabRemote extends RemoteProvider { }, { prefix: '!', - url: `${this.baseUrl}/-/merge_requests/`, + url: `${this.baseUrl}${this.urlPrefix}/merge_requests/`, alphanumeric: false, ignoreCase: false, title: `Open Merge Request ! on ${this.name}`, @@ -94,7 +103,9 @@ export class GitLabRemote extends RemoteProvider { ? text : text.replace(autolinkFullIssuesRegex, (linkText: string, repo: string, num: string) => { const url = encodeUrl( - `${this.protocol}://${this.domain}/${unescapeMarkdown(repo)}/-/issues/${num}`, + `${this.protocol}://${this.domain}/${unescapeMarkdown(repo)}${ + this.urlPrefix + }/issues/${num}`, ); const title = ` "Open Issue #${num} from ${repo} on ${this.name}"`; @@ -161,7 +172,7 @@ export class GitLabRemote extends RemoteProvider { provider: this, id: num, prefix: `${ownerAndRepo}#`, - url: `${this.protocol}://${this.domain}/${ownerAndRepo}/-/issues/${num}`, + url: `${this.protocol}://${this.domain}/${ownerAndRepo}${this.urlPrefix}/issues/${num}`, alphanumeric: false, ignoreCase: true, title: `Open Issue # from ${ownerAndRepo} on ${this.name}`, @@ -192,7 +203,7 @@ export class GitLabRemote extends RemoteProvider { autolinkFullMergeRequestsRegex, (linkText: string, repo: string, num: string) => { const url = encodeUrl( - `${this.protocol}://${this.domain}/${repo}/-/merge_requests/${num}`, + `${this.protocol}://${this.domain}/${repo}${this.urlPrefix}/merge_requests/${num}`, ); const title = ` "Open Merge Request !${num} from ${repo} on ${this.name}"`; @@ -263,7 +274,7 @@ export class GitLabRemote extends RemoteProvider { provider: this, id: num, prefix: `${ownerAndRepo}!`, - url: `${this.protocol}://${this.domain}/${ownerAndRepo}/-/merge_requests/${num}`, + url: `${this.protocol}://${this.domain}/${ownerAndRepo}${this.urlPrefix}/merge_requests/${num}`, alphanumeric: false, ignoreCase: true, title: `Open Merge Request ! from ${ownerAndRepo} on ${this.name}`, @@ -395,19 +406,19 @@ export class GitLabRemote extends RemoteProvider { } protected getUrlForBranches(): string { - return this.encodeUrl(`${this.baseUrl}/-/branches`); + return this.encodeUrl(`${this.baseUrl}${this.urlPrefix}/branches`); } protected getUrlForBranch(branch: string): string { - return this.encodeUrl(`${this.baseUrl}/-/tree/${branch}`); + return this.encodeUrl(`${this.baseUrl}${this.urlPrefix}/tree/${branch}`); } protected getUrlForCommit(sha: string): string { - return this.encodeUrl(`${this.baseUrl}/-/commit/${sha}`); + return this.encodeUrl(`${this.baseUrl}${this.urlPrefix}/commit/${sha}`); } protected override getUrlForComparison(base: string, head: string, notation: GitRevisionRangeNotation): string { - return this.encodeUrl(`${this.baseUrl}/-/compare/${base}${notation}${head}`); + return this.encodeUrl(`${this.baseUrl}${this.urlPrefix}/compare/${base}${notation}${head}`); } override async isReadyForForCrossForkPullRequestUrls(): Promise { @@ -461,7 +472,9 @@ export class GitLabRemote extends RemoteProvider { query.set('merge_request[description]', details.description); } - return `${this.encodeUrl(`${this.getRepoBaseUrl(head.remote.path)}/-/merge_requests/new`)}?${query.toString()}`; + return `${this.encodeUrl( + `${this.getRepoBaseUrl(head.remote.path)}${this.urlPrefix}/merge_requests/new`, + )}?${query.toString()}`; } protected getUrlForFile(fileName: string, branch?: string, sha?: string, range?: Range): string { @@ -476,8 +489,8 @@ export class GitLabRemote extends RemoteProvider { line = ''; } - if (sha) return `${this.encodeUrl(`${this.baseUrl}/-/blob/${sha}/${fileName}`)}${line}`; - if (branch) return `${this.encodeUrl(`${this.baseUrl}/-/blob/${branch}/${fileName}`)}${line}`; + if (sha) return `${this.encodeUrl(`${this.baseUrl}${this.urlPrefix}/blob/${sha}/${fileName}`)}${line}`; + if (branch) return `${this.encodeUrl(`${this.baseUrl}${this.urlPrefix}/blob/${branch}/${fileName}`)}${line}`; return `${this.encodeUrl(`${this.baseUrl}?path=${fileName}`)}${line}`; } } diff --git a/src/git/remotes/remoteProviders.ts b/src/git/remotes/remoteProviders.ts index 3f8410a071ac6..8ded11a562368 100644 --- a/src/git/remotes/remoteProviders.ts +++ b/src/git/remotes/remoteProviders.ts @@ -182,7 +182,7 @@ function getCustomProviderCreator(cfg: RemotesConfig) { new GitHubRemote(container, domain, path, cfg.protocol, cfg.name, true); case 'GitLab': return (container: Container, domain: string, path: string) => - new GitLabRemote(container, domain, path, cfg.protocol, cfg.name, true); + new GitLabRemote(container, domain, path, cfg.protocol, cfg.name, true, cfg.version); default: return undefined; }