diff --git a/src/git/parsers/remoteParser.ts b/src/git/parsers/remoteParser.ts index 65a2ccf..ad8e87e 100644 --- a/src/git/parsers/remoteParser.ts +++ b/src/git/parsers/remoteParser.ts @@ -68,15 +68,17 @@ export class GitRemoteParser { const uniqueness = `${domain}/${path}`; let remote: GitRemote | undefined = groups[uniqueness]; if (remote === undefined) { + const provider = providerFactory(domain, path); + remote = new GitRemote( repoPath, uniqueness, // Stops excessive memory usage -- https://bugs.chromium.org/p/v8/issues/detail?id=2869 (' ' + match[1]).substr(1), scheme, - domain, - path, - providerFactory(domain, path), + provider !== undefined ? provider.domain : domain, + provider !== undefined ? provider.path : path, + provider, // Stops excessive memory usage -- https://bugs.chromium.org/p/v8/issues/detail?id=2869 [{ url: url, type: (' ' + match[3]).substr(1) as GitRemoteType }] ); diff --git a/src/git/remotes/azure-devops.ts b/src/git/remotes/azure-devops.ts index 5be6727..45d8b10 100644 --- a/src/git/remotes/azure-devops.ts +++ b/src/git/remotes/azure-devops.ts @@ -3,22 +3,36 @@ import { Range } from 'vscode'; import { RemoteProvider } from './provider'; const issueEnricherRegex = /(^|\s)(#([0-9]+))\b/gi; -const stripGitRegex = /\/_git\/?/i; -const sshDomainRegex = /^ssh\./i; +const gitRegex = /\/_git\/?/i; +const legacyDefaultCollectionRegex = /^DefaultCollection\//i; +const orgAndProjectRegex = /^(.*?)\/(.*?)\/(.*)/; +const sshDomainRegex = /^(ssh|vs-ssh)\./i; const sshPathRegex = /^\/?v\d\//i; export class AzureDevOpsRemote extends RemoteProvider { - constructor(domain: string, path: string, protocol?: string, name?: string) { - domain = domain.replace(sshDomainRegex, ''); - path = path.replace(sshPathRegex, '').replace(stripGitRegex, '/'); + constructor(domain: string, path: string, protocol?: string, name?: string, legacy: boolean = false) { + if (sshDomainRegex.test(domain)) { + path = path.replace(sshPathRegex, ''); + domain = domain.replace(sshDomainRegex, ''); - super(domain, path, protocol, name); - } + // Add in /_git/ into ssh urls + const match = orgAndProjectRegex.exec(path); + if (match != null) { + const [, org, project, rest] = match; + + // Handle legacy vsts urls + if (legacy) { + domain = `${org}.${domain}`; + path = `${project}/_git/${rest}`; + } + else { + path = `${org}/${project}/_git/${rest}`; + } + } + } - get baseUrl() { - const [orgAndProject, repo] = this.splitPath(); - return `https://${this.domain}/${orgAndProject}/_git/${repo}`; + super(domain, path, protocol, name); } get icon() { @@ -29,9 +43,17 @@ export class AzureDevOpsRemote extends RemoteProvider { return 'Azure DevOps'; } + private _displayPath: string | undefined; + get displayPath(): string { + if (this._displayPath === undefined) { + this._displayPath = this.path.replace(gitRegex, '/').replace(legacyDefaultCollectionRegex, ''); + } + return this._displayPath; + } + enrichMessage(message: string): string { // Strip off any `_git` part from the repo url - const baseUrl = this.baseUrl.replace(stripGitRegex, '/'); + const baseUrl = this.baseUrl.replace(gitRegex, '/'); // Matches #123 return message.replace(issueEnricherRegex, `$1[$2](${baseUrl}/_workitems/edit/$3 "Open Work Item $2")`); } @@ -63,10 +85,4 @@ export class AzureDevOpsRemote extends RemoteProvider { if (branch) return `${this.baseUrl}/?path=%2F${fileName}&version=GB${branch}&_a=contents${line}`; return `${this.baseUrl}?path=%2F${fileName}${line}`; } - - protected splitPath(): [string, string] { - const index = this.path.lastIndexOf('/'); - return [this.path.substring(0, index), this.path.substring(index + 1)]; - } - } diff --git a/src/git/remotes/factory.ts b/src/git/remotes/factory.ts index 4dbcbdd..0cdde22 100644 --- a/src/git/remotes/factory.ts +++ b/src/git/remotes/factory.ts @@ -19,7 +19,10 @@ const defaultProviders: RemoteProviders = [ [/\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)] + [ + /\bvisualstudio\.com$/i, + (domain: string, path: string) => new AzureDevOpsRemote(domain, path, undefined, undefined, true) + ] ]; export class RemoteProviderFactory { diff --git a/src/git/remotes/provider.ts b/src/git/remotes/provider.ts index 7a82440..a916114 100644 --- a/src/git/remotes/provider.ts +++ b/src/git/remotes/provider.ts @@ -80,6 +80,10 @@ export abstract class RemoteProvider { return 'remote'; } + get displayPath(): string { + return this.path; + } + abstract get name(): string; protected get baseUrl() { diff --git a/src/views/nodes/remoteNode.ts b/src/views/nodes/remoteNode.ts index 0cc4ac2..57ce7b6 100644 --- a/src/views/nodes/remoteNode.ts +++ b/src/views/nodes/remoteNode.ts @@ -90,7 +90,9 @@ export class RemoteNode extends ViewNode { ); item.description = `${separator}${GlyphChars.Space} ${ this.remote.provider !== undefined ? this.remote.provider.name : this.remote.domain - } ${GlyphChars.Space}${GlyphChars.Dot}${GlyphChars.Space} ${this.remote.path}`; + } ${GlyphChars.Space}${GlyphChars.Dot}${GlyphChars.Space} ${ + this.remote.provider !== undefined ? this.remote.provider.displayPath : this.remote.path + }`; item.contextValue = ResourceType.Remote; if (this.remote.default) { item.contextValue += '+default'; @@ -110,9 +112,13 @@ export class RemoteNode extends ViewNode { } item.id = this.id; - item.tooltip = `${this.remote.name}\n${this.remote.path} (${ + item.tooltip = `${this.remote.name} (${ this.remote.provider !== undefined ? this.remote.provider.name : this.remote.domain - })`; + })\n${this.remote.provider !== undefined ? this.remote.provider.displayPath : this.remote.path}\n`; + + for (const type of this.remote.types) { + item.tooltip += `\n${type.url} (${type.type})`; + } return item; }