diff --git a/CHANGELOG.md b/CHANGELOG.md
index 0240cbd..1ea5bb1 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -63,6 +63,7 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/) and this p
- Clicking on `Changes` will run the `Compare File Revisions` command (`gitlens.diffWith`)
- Clicking the current and previous commit ids will run the `Show Commit Details` command (`gitlens.showQuickCommitDetails`)
- Adds support for custom remote services - see [#120](https://github.com/eamodio/vscode-gitlens/issues/120)
+- Adds support for Bitbucket Server (previously called Stash) remote services - see [#120](https://github.com/eamodio/vscode-gitlens/issues/120)
- Adds `Compare File Revisions` command (`gitlens.diffWith`) - compares the specified file revisions
- Adds `Open Branches in Remote` command (`gitlens.openBranchesInRemote`) - opens the branches in the supported remote service
- Adds `Stash Changes` command (`gitlens.stashSave`) to the source control group context menu -- can now stash a group of files
diff --git a/README.md b/README.md
index 2593e41..b0a793e 100644
--- a/README.md
+++ b/README.md
@@ -172,6 +172,7 @@ GitLens provides an unobtrusive blame annotation at the end of the current line,
- Adds a `Search Commits` command (`gitlens.showCommitSearch`) with a shortcut of `alt+/` to search for commits by message, author, file(s), or commit id
- 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
+ - Also supports [custom](#custom-remote-settings) remote services, such as **BitBucket, Bitbucket Server (previously called Stash), GitHub, GitLab**
- `Open Branches in Remote` command (`gitlens.openBranchesInRemote`) — opens the branches in the supported remote service
- `Open Branch in Remote` command (`gitlens.openBranchInRemote`) — opens the current branch commits in the supported remote service
- `Open Commit in Remote` command (`gitlens.openCommitInRemote`) — opens the commit revision of the active line in the supported remote service
@@ -350,7 +351,7 @@ GitLens is highly customizable and provides many configuration settings to allow
|`gitlens.gitExplorer.stashFormat`|Specifies the format of stashed changes in the `GitLens` custom view
Available tokens
${id} - commit id
${author} - commit author
${message} - commit message
${ago} - relative commit date (e.g. 1 day ago)
${date} - formatted commit date (format specified by `gitlens.statusBar.dateFormat`)
${authorAgo} - commit author, relative commit date
See https://github.com/eamodio/vscode-gitlens/wiki/Advanced-Formatting for advanced formatting
|`gitlens.gitExplorer.stashFileFormat`|Specifies the format of a stashed file in the `GitLens` custom view
Available tokens
${file} - file name
${filePath} - file name and path
${path} - file path
-### GitLens Custom Remotes Settings
+### Custom Remotes Settings
|Name | Description
|-----|------------
diff --git a/package.json b/package.json
index db626b1..d18fcbe 100644
--- a/package.json
+++ b/package.json
@@ -461,10 +461,11 @@
"type": "string",
"enum": [
"Bitbucket",
+ "BitbucketServer",
"GitHub",
"GitLab"
],
- "description": "Specifies the type of the custom remote service\n `Bitbucket`, `GitHub`, or `GitLab`"
+ "description": "Specifies the type of the custom remote service\n `Bitbucket`, `BitbucketServer`, `GitHub`, or `GitLab`"
},
"domain": {
"type": "string",
diff --git a/src/configuration.ts b/src/configuration.ts
index d53e2d9..667e741 100644
--- a/src/configuration.ts
+++ b/src/configuration.ts
@@ -48,6 +48,7 @@ export type CustomRemoteType =
'GitLab';
export const CustomRemoteType = {
Bitbucket: 'Bitbucket' as CustomRemoteType,
+ BitbucketServer: 'BitbucketServer' as CustomRemoteType,
GitHub: 'GitHub' as CustomRemoteType,
GitLab: 'GitLab' as CustomRemoteType
};
diff --git a/src/git/remotes/bitbucket-server.ts b/src/git/remotes/bitbucket-server.ts
new file mode 100644
index 0000000..b808be2
--- /dev/null
+++ b/src/git/remotes/bitbucket-server.ts
@@ -0,0 +1,47 @@
+'use strict';
+import { Range } from 'vscode';
+import { RemoteProvider } from './provider';
+
+export class BitbucketServerService extends RemoteProvider {
+
+ constructor(public domain: string, public path: string, public custom: boolean = false) {
+ super(domain, path);
+ }
+
+ get name() {
+ return this.formatName('Bitbucket Server');
+ }
+
+ protected get baseUrl() {
+ const [project, repo] = super.splitPath();
+ return `https://${this.domain}/projects/${project}/repos/${repo}`;
+ }
+
+ protected getUrlForBranches(): string {
+ return `${this.baseUrl}/branches`;
+ }
+
+ protected getUrlForBranch(branch: string): string {
+ return `${this.baseUrl}/commits?until=${branch}`;
+ }
+
+ protected getUrlForCommit(sha: string): string {
+ return `${this.baseUrl}/commits/${sha}`;
+ }
+
+ protected getUrlForFile(fileName: string, branch?: string, sha?: string, range?: Range): string {
+ let line = '';
+ if (range) {
+ if (range.start.line === range.end.line) {
+ line = `#${range.start.line}`;
+ }
+ else {
+ line = `#${range.start.line}-${range.end.line}`;
+ }
+ }
+
+ if (sha) return `${this.baseUrl}/browse/${fileName}?at=${sha}${line}`;
+ if (branch) return `${this.baseUrl}/browse/${fileName}?at=${branch}${line}`;
+ return `${this.baseUrl}/browse/${fileName}${line}`;
+ }
+}
\ No newline at end of file
diff --git a/src/git/remotes/bitbucket.ts b/src/git/remotes/bitbucket.ts
index 28d2951..9cf8f6e 100644
--- a/src/git/remotes/bitbucket.ts
+++ b/src/git/remotes/bitbucket.ts
@@ -4,12 +4,12 @@ import { RemoteProvider } from './provider';
export class BitbucketService extends RemoteProvider {
- constructor(public domain: string, public path: string) {
+ constructor(public domain: string, public path: string, public custom: boolean = false) {
super(domain, path);
}
get name() {
- return 'Bitbucket';
+ return this.formatName('Bitbucket');
}
protected getUrlForBranches(): string {
diff --git a/src/git/remotes/factory.ts b/src/git/remotes/factory.ts
index 48c260a..931b398 100644
--- a/src/git/remotes/factory.ts
+++ b/src/git/remotes/factory.ts
@@ -1,6 +1,7 @@
'use strict';
import { ExtensionContext, workspace } from 'vscode';
import { BitbucketService } from './bitbucket';
+import { BitbucketServerService } from './bitbucket-server';
import { CustomRemoteType, IConfig, IRemotesConfig } from '../../configuration';
import { ExtensionKey } from '../../constants';
import { GitHubService } from './github';
@@ -14,11 +15,12 @@ export { RemoteProvider };
const UrlRegex = /^(?:git:\/\/(.*?)\/|https:\/\/(.*?)\/|http:\/\/(.*?)\/|git@(.*):|ssh:\/\/(?:.*@)?(.*?)(?::.*?)?\/)(.*)$/;
-function getProviderKey(type: CustomRemoteType) {
+function getCustomProvider(type: CustomRemoteType) {
switch (type) {
- case CustomRemoteType.Bitbucket: return 'bitbucket.org';
- case CustomRemoteType.GitHub: return 'github.com';
- case CustomRemoteType.GitLab: return 'gitlab.com';
+ case CustomRemoteType.Bitbucket: return (domain: string, path: string) => new BitbucketService(domain, path, true);
+ case CustomRemoteType.BitbucketServer: return (domain: string, path: string) => new BitbucketServerService(domain, path, true);
+ case CustomRemoteType.GitHub: return (domain: string, path: string) => new GitHubService(domain, path, true);
+ case CustomRemoteType.GitLab: return (domain: string, path: string) => new GitLabService(domain, path, true);
}
return undefined;
}
@@ -43,10 +45,10 @@ function onConfigurationChanged() {
remotesCfg = cfg.remotes;
if (remotesCfg != null && remotesCfg.length > 0) {
for (const svc of remotesCfg) {
- const key = getProviderKey(svc.type);
- if (key === undefined) continue;
+ const provider = getCustomProvider(svc.type);
+ if (provider === undefined) continue;
- providerMap.set(svc.domain.toLowerCase(), providerMap.get(key)!);
+ providerMap.set(svc.domain.toLowerCase(), provider);
}
}
}
diff --git a/src/git/remotes/github.ts b/src/git/remotes/github.ts
index 6f6c88c..7d8b8ef 100644
--- a/src/git/remotes/github.ts
+++ b/src/git/remotes/github.ts
@@ -4,12 +4,12 @@ import { RemoteProvider } from './provider';
export class GitHubService extends RemoteProvider {
- constructor(public domain: string, public path: string) {
+ constructor(public domain: string, public path: string, public custom: boolean = false) {
super(domain, path);
}
get name() {
- return 'GitHub';
+ return this.formatName('GitHub');
}
protected getUrlForBranches(): string {
diff --git a/src/git/remotes/gitlab.ts b/src/git/remotes/gitlab.ts
index dc8d5ab..60e99d0 100644
--- a/src/git/remotes/gitlab.ts
+++ b/src/git/remotes/gitlab.ts
@@ -3,11 +3,11 @@ import { GitHubService } from './github';
export class GitLabService extends GitHubService {
- constructor(public domain: string, public path: string) {
+ constructor(public domain: string, public path: string, public custom: boolean = false) {
super(domain, path);
}
get name() {
- return 'GitLab';
+ return this.formatName('GitLab');
}
}
\ No newline at end of file
diff --git a/src/git/remotes/provider.ts b/src/git/remotes/provider.ts
index 1c0933c..852e015 100644
--- a/src/git/remotes/provider.ts
+++ b/src/git/remotes/provider.ts
@@ -26,7 +26,7 @@ export function getNameFromRemoteResource(resource: RemoteResource) {
export abstract class RemoteProvider {
- constructor(public domain: string, public path: string) { }
+ constructor(public domain: string, public path: string, public custom: boolean = false) { }
abstract get name(): string;
@@ -34,6 +34,15 @@ export abstract class RemoteProvider {
return `https://${this.domain}/${this.path}`;
}
+ protected formatName(name: string) {
+ return `${name}${this.custom ? ` (${this.domain})` : ''}`;
+ }
+
+ protected splitPath(): [string, string] {
+ const index = this.path.indexOf('/');
+ return [ this.path.substring(0, index), this.path.substring(index + 1) ];
+ }
+
protected abstract getUrlForBranches(): string;
protected abstract getUrlForBranch(branch: string): string;
protected abstract getUrlForCommit(sha: string): string;