From 21e0963600a4a26e3a7c193d904a06885f41efed Mon Sep 17 00:00:00 2001 From: Eric Amodio Date: Mon, 11 Sep 2017 19:18:29 -0400 Subject: [PATCH] Adds bitbucket server support --- CHANGELOG.md | 1 + README.md | 3 ++- package.json | 3 ++- src/configuration.ts | 1 + src/git/remotes/bitbucket-server.ts | 47 +++++++++++++++++++++++++++++++++++++ src/git/remotes/bitbucket.ts | 4 ++-- src/git/remotes/factory.ts | 16 +++++++------ src/git/remotes/github.ts | 4 ++-- src/git/remotes/gitlab.ts | 4 ++-- src/git/remotes/provider.ts | 11 ++++++++- 10 files changed, 78 insertions(+), 16 deletions(-) create mode 100644 src/git/remotes/bitbucket-server.ts 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;