Skip to content

Commit e70dd6d

Browse files
committed
Closes #148 - Adds custom provider support
1 parent d111fb0 commit e70dd6d

File tree

11 files changed

+160
-25
lines changed

11 files changed

+160
-25
lines changed

README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -178,7 +178,7 @@ GitLens provides an unobtrusive blame annotation at the end of the current line,
178178

179179
- Adds a `Search Commits` command (`gitlens.showCommitSearch`) with a shortcut of `alt+/` to search for commits by message, author, file(s), or commit id
180180

181-
- Adds commands to open files, commits, branches, and the repository in the supported remote services, currently **BitBucket, GitHub, GitLab, and Visual Studio Team Services** — only available if a Git upstream service is configured in the repository
181+
- Adds commands to open files, commits, branches, and the repository in the supported remote services, **BitBucket, GitHub, GitLab, and Visual Studio Team Services** or a [**user-defined** remote services](#custom-remotes-settings) — only available if a Git upstream service is configured in the repository
182182
- Also supports [remote services with custom domains](#custom-remotes-settings), such as **BitBucket, Bitbucket Server (previously called Stash), GitHub, GitHub Enterprise, GitLab**
183183
- `Open Branches in Remote` command (`gitlens.openBranchesInRemote`) — opens the branches in the supported remote service
184184
- `Open Branch in Remote` command (`gitlens.openBranchInRemote`) — opens the current branch commits in the supported remote service
@@ -371,7 +371,7 @@ GitLens is highly customizable and provides many configuration settings to allow
371371

372372
|Name | Description
373373
|-----|------------
374-
|`gitlens.remotes`|Specifies any custom domains for remote (code-hosting) services<br />Example: ```"gitlens.remotes": [{ "domain": "git.corporate-url.com", "type": "GitHub" }]```
374+
|`gitlens.remotes`|Specifies user-defined remote (code-hosting) services or custom domains for built-in remote services<br /><br />Example:<br />```"gitlens.remotes": [{ "domain": "git.corporate-url.com", "type": "GitHub" }]```<br /><br />Example:<br />```"gitlens.remotes": [{ "domain": "git.corporate-url.com", "type": "Custom", "name": "My Company", "urls": { "repository": "https://git.corporate-url.com/${repo}", "branches": "https://git.corporate-url.com/${repo}/branches", "branch": "https://git.corporate-url.com/${repo}/commits/${branch}", "commit": "https://git.corporate-url.com/${repo}/commit/${id}", "file": "https://git.corporate-url.com/${repo}?path=${file}${line}", "fileInBranch": "https://git.corporate-url.com/${repo}/blob/${branch}/${file}${line}", "fileInCommit": "https://git.corporate-url.com/${repo}/blob/${id}/${file}${line}", "fileLine": "#L${line}", "fileRange": "#L${start}-L${end}" } }```
375375

376376
### Status Bar Settings
377377

package.json

Lines changed: 60 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -498,6 +498,7 @@
498498
"enum": [
499499
"Bitbucket",
500500
"BitbucketServer",
501+
"Custom",
501502
"GitHub",
502503
"GitLab"
503504
],
@@ -506,11 +507,68 @@
506507
"domain": {
507508
"type": "string",
508509
"description": "Specifies the domain name of the custom remote service"
509-
}
510+
},
511+
"name": {
512+
"type": "string",
513+
"description": "Specifies an optional friendly name for the custom remote service"
514+
},
515+
"urls": {
516+
"type": "object",
517+
"required": [
518+
"repository",
519+
"branches",
520+
"branch",
521+
"commit",
522+
"file",
523+
"fileInCommit",
524+
"fileInBranch",
525+
"fileLine",
526+
"fileRange"
527+
],
528+
"properties": {
529+
"repository": {
530+
"type": "string",
531+
"description": "Specifies the format of a respository url for the custom remote service\nAvailable tokens\n ${repo} - repository path"
532+
},
533+
"branches": {
534+
"type": "string",
535+
"description": "Specifies the format of a branches url for the custom remote service\nAvailable tokens\n ${repo} - repository path\n ${branch} - branch"
536+
},
537+
"branch": {
538+
"type": "string",
539+
"description": "Specifies the format of a branch url for the custom remote service\nAvailable tokens\n ${repo} - repository path\n ${branch} - branch"
540+
},
541+
"commit": {
542+
"type": "string",
543+
"description": "Specifies the format of a commit url for the custom remote service\nAvailable tokens\n ${repo} - repository path\n ${id} - commit id"
544+
},
545+
"file": {
546+
"type": "string",
547+
"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"
548+
},
549+
"fileInBranch": {
550+
"type": "string",
551+
"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"
552+
},
553+
"fileInCommit": {
554+
"type": "string",
555+
"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 id\n ${line} - formatted line information"
556+
},
557+
"fileLine": {
558+
"type": "string",
559+
"description": "Specifies the format of a line in a file url for the custom remote service\nAvailable tokens\n ${line} - line"
560+
},
561+
"fileRange": {
562+
"type": "string",
563+
"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"
564+
}
565+
}
566+
},
567+
"description": "Specifies the url formats of the custom remote service"
510568
}
511569
},
512570
"uniqueItems": true,
513-
"description": "Specifies any custom domains for remote (code-hosting) services"
571+
"description": "Specifies user-defined remote (code-hosting) services or custom domains for built-in remote services"
514572
},
515573
"gitlens.statusBar.enabled": {
516574
"type": "boolean",

src/configuration.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,11 +44,13 @@ export const LineHighlightLocations = {
4444

4545
export type CustomRemoteType =
4646
'Bitbucket' |
47+
'Custom' |
4748
'GitHub' |
4849
'GitLab';
4950
export const CustomRemoteType = {
5051
Bitbucket: 'Bitbucket' as CustomRemoteType,
5152
BitbucketServer: 'BitbucketServer' as CustomRemoteType,
53+
Custom: 'Custom' as CustomRemoteType,
5254
GitHub: 'GitHub' as CustomRemoteType,
5355
GitLab: 'GitLab' as CustomRemoteType
5456
};
@@ -163,6 +165,18 @@ export interface IGitExplorerConfig {
163165
export interface IRemotesConfig {
164166
type: CustomRemoteType;
165167
domain: string;
168+
name?: string;
169+
urls?: {
170+
repository: string;
171+
branches: string;
172+
branch: string;
173+
commit: string;
174+
file: string;
175+
fileInBranch: string;
176+
fileInCommit: string;
177+
fileLine: string;
178+
fileRange: string;
179+
};
166180
}
167181

168182
export interface IThemeConfig {

src/git/remotes/bitbucket-server.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,8 @@ import { RemoteProvider } from './provider';
44

55
export class BitbucketServerService extends RemoteProvider {
66

7-
constructor(public domain: string, public path: string, public custom: boolean = false) {
8-
super(domain, path);
7+
constructor(domain: string, path: string, name?: string, custom: boolean = false) {
8+
super(domain, path, name, custom);
99
}
1010

1111
get name() {

src/git/remotes/bitbucket.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,8 @@ import { RemoteProvider } from './provider';
44

55
export class BitbucketService extends RemoteProvider {
66

7-
constructor(public domain: string, public path: string, public custom: boolean = false) {
8-
super(domain, path);
7+
constructor(domain: string, path: string, name?: string, custom: boolean = false) {
8+
super(domain, path, name, custom);
99
}
1010

1111
get name() {

src/git/remotes/custom.ts

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
'use strict';
2+
import { Strings } from '../../system';
3+
import { Range } from 'vscode';
4+
import { IRemotesConfig } from '../../configuration';
5+
import { RemoteProvider } from './provider';
6+
7+
export class CustomService extends RemoteProvider {
8+
9+
constructor(domain: string, path: string, private readonly config: IRemotesConfig) {
10+
super(domain, path, config.name, true);
11+
}
12+
13+
get name() {
14+
return this.formatName('Custom');
15+
}
16+
17+
protected getUrlForRepository(): string {
18+
return Strings.interpolate(this.config.custom!.repository, { repo: this.path });
19+
}
20+
21+
protected getUrlForBranches(): string {
22+
return Strings.interpolate(this.config.custom!.branches, { repo: this.path });
23+
}
24+
25+
protected getUrlForBranch(branch: string): string {
26+
return Strings.interpolate(this.config.custom!.branch, { repo: this.path, branch: branch });
27+
}
28+
29+
protected getUrlForCommit(sha: string): string {
30+
return Strings.interpolate(this.config.custom!.commit, { repo: this.path, id: sha });
31+
}
32+
33+
protected getUrlForFile(fileName: string, branch?: string, sha?: string, range?: Range): string {
34+
let line = '';
35+
if (range) {
36+
if (range.start.line === range.end.line) {
37+
line = Strings.interpolate(this.config.custom!.fileLine, { line: range.start.line });
38+
}
39+
else {
40+
line = Strings.interpolate(this.config.custom!.fileRange, { start: range.start.line, end: range.end.line });
41+
}
42+
}
43+
44+
if (sha) return Strings.interpolate(this.config.custom!.fileInCommit, { repo: this.path, id: sha, file: fileName, line: line });
45+
if (branch) return Strings.interpolate(this.config.custom!.fileInBranch, { repo: this.path, branch: branch, file: fileName, line: line });
46+
return Strings.interpolate(this.config.custom!.file, { repo: this.path, file: fileName, line: line });
47+
}
48+
}

src/git/remotes/factory.ts

Lines changed: 11 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import { BitbucketService } from './bitbucket';
55
import { BitbucketServerService } from './bitbucket-server';
66
import { CustomRemoteType, IConfig, IRemotesConfig } from '../../configuration';
77
import { ExtensionKey } from '../../constants';
8+
import { CustomService } from './custom';
89
import { GitHubService } from './github';
910
import { GitLabService } from './gitlab';
1011
import { Logger } from '../../logger';
@@ -62,11 +63,11 @@ export class RemoteProviderFactory {
6263

6364
this._remotesCfg = cfg.remotes;
6465
if (this._remotesCfg != null && this._remotesCfg.length > 0) {
65-
for (const svc of this._remotesCfg) {
66-
const provider = this.getCustomProvider(svc.type);
66+
for (const remoteCfg of this._remotesCfg) {
67+
const provider = this.getCustomProvider(remoteCfg);
6768
if (provider === undefined) continue;
6869

69-
this._providerMap.set(svc.domain.toLowerCase(), provider);
70+
this._providerMap.set(remoteCfg.domain.toLowerCase(), provider);
7071
}
7172

7273
if (!silent) {
@@ -76,12 +77,13 @@ export class RemoteProviderFactory {
7677
}
7778
}
7879

79-
private static getCustomProvider(type: CustomRemoteType) {
80-
switch (type) {
81-
case CustomRemoteType.Bitbucket: return (domain: string, path: string) => new BitbucketService(domain, path, true);
82-
case CustomRemoteType.BitbucketServer: return (domain: string, path: string) => new BitbucketServerService(domain, path, true);
83-
case CustomRemoteType.GitHub: return (domain: string, path: string) => new GitHubService(domain, path, true);
84-
case CustomRemoteType.GitLab: return (domain: string, path: string) => new GitLabService(domain, path, true);
80+
private static getCustomProvider(cfg: IRemotesConfig) {
81+
switch (cfg.type) {
82+
case CustomRemoteType.Bitbucket: return (domain: string, path: string) => new BitbucketService(domain, path, cfg.name, true);
83+
case CustomRemoteType.BitbucketServer: return (domain: string, path: string) => new BitbucketServerService(domain, path, cfg.name, true);
84+
case CustomRemoteType.Custom: return (domain: string, path: string) => new CustomService(domain, path, cfg);
85+
case CustomRemoteType.GitHub: return (domain: string, path: string) => new GitHubService(domain, path, cfg.name, true);
86+
case CustomRemoteType.GitLab: return (domain: string, path: string) => new GitLabService(domain, path, cfg.name, true);
8587
}
8688
return undefined;
8789
}

src/git/remotes/github.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,8 @@ import { RemoteProvider } from './provider';
44

55
export class GitHubService extends RemoteProvider {
66

7-
constructor(public domain: string, public path: string, public custom: boolean = false) {
8-
super(domain, path);
7+
constructor(domain: string, path: string, name?: string, custom: boolean = false) {
8+
super(domain, path, name, custom);
99
}
1010

1111
get name() {

src/git/remotes/gitlab.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,8 @@ import { RemoteProvider } from './provider';
44

55
export class GitLabService extends RemoteProvider {
66

7-
constructor(public domain: string, public path: string, public custom: boolean = false) {
8-
super(domain, path);
7+
constructor(domain: string, path: string, name?: string, custom: boolean = false) {
8+
super(domain, path, name, custom);
99
}
1010

1111
get name() {

src/git/remotes/provider.ts

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,16 @@ export function getNameFromRemoteResource(resource: RemoteResource) {
2626

2727
export abstract class RemoteProvider {
2828

29-
constructor(public domain: string, public path: string, public custom: boolean = false) { }
29+
private _name: string | undefined;
30+
31+
constructor(
32+
public readonly domain: string,
33+
public readonly path: string,
34+
name?: string,
35+
public readonly custom: boolean = false
36+
) {
37+
this._name = name;
38+
}
3039

3140
abstract get name(): string;
3241

@@ -35,6 +44,7 @@ export abstract class RemoteProvider {
3544
}
3645

3746
protected formatName(name: string) {
47+
if (this._name !== undefined) return this._name;
3848
return `${name}${this.custom ? ` (${this.domain})` : ''}`;
3949
}
4050

@@ -43,6 +53,9 @@ export abstract class RemoteProvider {
4353
return [ this.path.substring(0, index), this.path.substring(index + 1) ];
4454
}
4555

56+
protected getUrlForRepository(): string {
57+
return this.baseUrl;
58+
}
4659
protected abstract getUrlForBranches(): string;
4760
protected abstract getUrlForBranch(branch: string): string;
4861
protected abstract getUrlForCommit(sha: string): string;
@@ -66,7 +79,7 @@ export abstract class RemoteProvider {
6679
}
6780

6881
openRepo() {
69-
return this._openUrl(this.baseUrl);
82+
return this._openUrl(this.getUrlForRepository());
7083
}
7184

7285
openBranches() {

0 commit comments

Comments
 (0)