Skip to content

Commit ccc29e3

Browse files
committed
Reworks remote parsing
Combines same url into same remote Adds a change event for custom remote providers Adds a repo change event for custom remote providers
1 parent 48814d4 commit ccc29e3

16 files changed

+139
-87
lines changed

src/commands/openBranchInRemote.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
'use strict';
2-
import { Arrays } from '../system';
32
import { commands, TextEditor, Uri, window } from 'vscode';
43
import { ActiveEditorCommand, CommandContext, Commands, getCommandUri, isCommandViewContextWithBranch } from './common';
54
import { GlyphChars } from '../constants';
@@ -52,7 +51,8 @@ export class OpenBranchInRemoteCommand extends ActiveEditorCommand {
5251
if (args.branch === undefined) return undefined;
5352
}
5453

55-
const remotes = Arrays.uniqueBy(await this.git.getRemotes(repoPath), _ => _.url, _ => !!_.provider);
54+
const remotes = (await this.git.getRemotes(repoPath)).filter(r => r.provider !== undefined);
55+
5656
return commands.executeCommand(Commands.OpenInRemote, uri, {
5757
resource: {
5858
type: 'branch',

src/commands/openBranchesInRemote.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
'use strict';
2-
import { Arrays } from '../system';
32
import { commands, TextEditor, Uri, window } from 'vscode';
43
import { ActiveEditorCommand, CommandContext, Commands, getCommandUri, isCommandViewContextWithRemote } from './common';
54
import { GitService, GitUri } from '../gitService';
@@ -34,7 +33,7 @@ export class OpenBranchesInRemoteCommand extends ActiveEditorCommand {
3433
if (!repoPath) return undefined;
3534

3635
try {
37-
const remotes = Arrays.uniqueBy(await this.git.getRemotes(repoPath), r => r.url, r => !!r.provider);
36+
const remotes = (await this.git.getRemotes(repoPath)).filter(r => r.provider !== undefined);
3837

3938
return commands.executeCommand(Commands.OpenInRemote, uri, {
4039
resource: {

src/commands/openCommitInRemote.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
'use strict';
2-
import { Arrays } from '../system';
32
import { commands, TextEditor, Uri, window } from 'vscode';
43
import { ActiveEditorCommand, CommandContext, Commands, getCommandUri, isCommandViewContextWithCommit } from './common';
54
import { GitBlameCommit, GitService, GitUri } from '../gitService';
@@ -53,7 +52,8 @@ export class OpenCommitInRemoteCommand extends ActiveEditorCommand {
5352
args.sha = commit.sha;
5453
}
5554

56-
const remotes = Arrays.uniqueBy(await this.git.getRemotes(gitUri.repoPath), _ => _.url, _ => !!_.provider);
55+
const remotes = (await this.git.getRemotes(gitUri.repoPath)).filter(r => r.provider !== undefined);
56+
5757
return commands.executeCommand(Commands.OpenInRemote, uri, {
5858
resource: {
5959
type: 'commit',

src/commands/openFileInRemote.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
'use strict';
2-
import { Arrays } from '../system';
32
import { commands, Range, TextEditor, Uri, window } from 'vscode';
43
import { ActiveEditorCommand, CommandContext, Commands, getCommandUri, isCommandViewContextWithBranch, isCommandViewContextWithCommit } from './common';
54
import { GitService, GitUri } from '../gitService';
@@ -45,7 +44,7 @@ export class OpenFileInRemoteCommand extends ActiveEditorCommand {
4544
}
4645

4746
try {
48-
const remotes = Arrays.uniqueBy(await this.git.getRemotes(gitUri.repoPath), _ => _.url, _ => !!_.provider);
47+
const remotes = (await this.git.getRemotes(gitUri.repoPath)).filter(r => r.provider !== undefined);
4948
const range = (args.range && editor !== undefined)
5049
? new Range(editor.selection.start.with({ line: editor.selection.start.line + 1 }), editor.selection.end.with({ line: editor.selection.end.line + 1 }))
5150
: undefined;

src/commands/openRepoInRemote.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
'use strict';
2-
import { Arrays } from '../system';
32
import { commands, TextEditor, Uri, window } from 'vscode';
43
import { ActiveEditorCommand, CommandContext, Commands, getCommandUri, isCommandViewContextWithRemote } from './common';
54
import { GitService, GitUri } from '../gitService';
@@ -34,7 +33,7 @@ export class OpenRepoInRemoteCommand extends ActiveEditorCommand {
3433
if (!repoPath) return undefined;
3534

3635
try {
37-
const remotes = Arrays.uniqueBy(await this.git.getRemotes(repoPath), r => r.url, r => !!r.provider);
36+
const remotes = (await this.git.getRemotes(repoPath)).filter(r => r.provider !== undefined);
3837

3938
return commands.executeCommand(Commands.OpenInRemote, uri, {
4039
resource: {

src/git/git.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ export * from './parsers/blameParser';
1414
export * from './parsers/branchParser';
1515
export * from './parsers/diffParser';
1616
export * from './parsers/logParser';
17+
export * from './parsers/remoteParser';
1718
export * from './parsers/stashParser';
1819
export * from './parsers/statusParser';
1920
export * from './remotes/provider';

src/git/gitContextTracker.ts

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
import { Disposable, Event, EventEmitter, TextDocument, TextDocumentChangeEvent, TextEditor, window, workspace } from 'vscode';
33
import { TextDocumentComparer } from '../comparers';
44
import { CommandContext, setCommandContext } from '../constants';
5-
import { GitService, GitUri } from '../gitService';
5+
import { GitService, GitUri, RepoChangedReasons } from '../gitService';
66
import { Logger } from '../logger';
77

88
export interface BlameabilityChangeEvent {
@@ -32,6 +32,7 @@ export class GitContextTracker extends Disposable {
3232
subscriptions.push(workspace.onDidChangeConfiguration(this._onConfigurationChanged, this));
3333
subscriptions.push(workspace.onDidSaveTextDocument(this._onTextDocumentSaved, this));
3434
subscriptions.push(this.git.onDidBlameFail(this._onBlameFailed, this));
35+
subscriptions.push(this.git.onDidChangeRepo(this._onRepoChanged, this));
3536

3637
this._disposable = Disposable.from(...subscriptions);
3738

@@ -54,6 +55,13 @@ export class GitContextTracker extends Disposable {
5455
}
5556
}
5657

58+
async _onRepoChanged(reasons: RepoChangedReasons[]) {
59+
if (!reasons.includes(RepoChangedReasons.Remotes)) return;
60+
61+
const gitUri = this._editor === undefined ? undefined : await GitUri.fromUri(this._editor.document.uri, this.git);
62+
this._updateContextHasRemotes(gitUri);
63+
}
64+
5765
private _onActiveTextEditorChanged(editor: TextEditor | undefined) {
5866
this._editor = editor;
5967
this._updateContext(this._gitEnabled ? editor : undefined);

src/git/models/remote.ts

Lines changed: 2 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -5,23 +5,9 @@ export type GitRemoteType = 'fetch' | 'push';
55

66
export class GitRemote {
77

8-
name: string;
9-
url: string;
10-
type: GitRemoteType;
11-
128
provider?: RemoteProvider;
139

14-
constructor(remote: string) {
15-
remote = remote.trim();
16-
17-
const [name, info] = remote.split('\t');
18-
this.name = name;
19-
20-
const [url, typeInfo] = info.split(' ');
21-
this.url = url;
22-
23-
this.type = typeInfo.substring(1, typeInfo.length - 1) as GitRemoteType;
24-
25-
this.provider = RemoteProviderFactory.getRemoteProvider(this.url);
10+
constructor(public readonly repoPath: string, public readonly name: string, public readonly url: string, public readonly domain: string, public readonly path: string, public readonly types: GitRemoteType[]) {
11+
this.provider = RemoteProviderFactory.getRemoteProvider(this.domain, this.path);
2612
}
2713
}

src/git/parsers/remoteParser.ts

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
'use strict';
2+
import { GitRemote } from './../git';
3+
import { GitRemoteType } from '../models/remote';
4+
5+
const remoteRegex = /^(.*)\t(.*)\s\((.*)\)$/gm;
6+
const urlRegex = /^(?:git:\/\/(.*?)\/|https:\/\/(.*?)\/|http:\/\/(.*?)\/|git@(.*):|ssh:\/\/(?:.*@)?(.*?)(?::.*?)?\/)(.*)$/;
7+
8+
export class GitRemoteParser {
9+
10+
static parse(data: string, repoPath: string): GitRemote[] {
11+
if (!data) return [];
12+
13+
const remotes: GitRemote[] = [];
14+
const groups = Object.create(null);
15+
16+
let match: RegExpExecArray | null = null;
17+
do {
18+
match = remoteRegex.exec(data);
19+
if (match == null) break;
20+
21+
const url = match[2];
22+
23+
const [domain, path] = this.parseGitUrl(url);
24+
25+
let remote: GitRemote | undefined = groups[url];
26+
if (remote === undefined) {
27+
remote = new GitRemote(repoPath, match[1], url, domain, path, [match[3] as GitRemoteType]);
28+
remotes.push(remote);
29+
groups[url] = remote;
30+
}
31+
else {
32+
remote.types.push(match[3] as GitRemoteType);
33+
}
34+
} while (match != null);
35+
36+
if (!remotes.length) return [];
37+
38+
return remotes;
39+
}
40+
41+
static parseGitUrl(url: string): [string, string] {
42+
const match = urlRegex.exec(url);
43+
if (match == null) return ['', ''];
44+
45+
return [
46+
match[1] || match[2] || match[3] || match[4] || match[5],
47+
match[6].replace(/\.git\/?$/, '')
48+
];
49+
}
50+
}

src/git/remotes/factory.ts

Lines changed: 49 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
'use strict';
2-
import { ExtensionContext, workspace } from 'vscode';
2+
import { Objects } from '../../system';
3+
import { Event, EventEmitter, ExtensionContext, workspace } from 'vscode';
34
import { BitbucketService } from './bitbucket';
45
import { BitbucketServerService } from './bitbucket-server';
56
import { CustomRemoteType, IConfig, IRemotesConfig } from '../../configuration';
@@ -9,71 +10,39 @@ import { GitLabService } from './gitlab';
910
import { Logger } from '../../logger';
1011
import { RemoteProvider } from './provider';
1112
import { VisualStudioService } from './visualStudio';
12-
import { Objects } from '../../system';
1313

1414
export { RemoteProvider };
1515

16-
const UrlRegex = /^(?:git:\/\/(.*?)\/|https:\/\/(.*?)\/|http:\/\/(.*?)\/|git@(.*):|ssh:\/\/(?:.*@)?(.*?)(?::.*?)?\/)(.*)$/;
17-
18-
function getCustomProvider(type: CustomRemoteType) {
19-
switch (type) {
20-
case CustomRemoteType.Bitbucket: return (domain: string, path: string) => new BitbucketService(domain, path, true);
21-
case CustomRemoteType.BitbucketServer: return (domain: string, path: string) => new BitbucketServerService(domain, path, true);
22-
case CustomRemoteType.GitHub: return (domain: string, path: string) => new GitHubService(domain, path, true);
23-
case CustomRemoteType.GitLab: return (domain: string, path: string) => new GitLabService(domain, path, true);
24-
}
25-
return undefined;
26-
}
27-
2816
const defaultProviderMap = new Map<string, (domain: string, path: string) => RemoteProvider>([
2917
['bitbucket.org', (domain: string, path: string) => new BitbucketService(domain, path)],
3018
['github.com', (domain: string, path: string) => new GitHubService(domain, path)],
3119
['gitlab.com', (domain: string, path: string) => new GitLabService(domain, path)],
3220
['visualstudio.com', (domain: string, path: string) => new VisualStudioService(domain, path)]
3321
]);
3422

35-
let providerMap: Map<string, (domain: string, path: string) => RemoteProvider>;
36-
let remotesCfg: IRemotesConfig[];
37-
38-
function onConfigurationChanged() {
39-
const cfg = workspace.getConfiguration().get<IConfig>(ExtensionKey);
40-
if (cfg === undefined) return;
41-
42-
if (!Objects.areEquivalent(cfg.remotes, remotesCfg)) {
43-
providerMap = new Map(defaultProviderMap);
23+
export class RemoteProviderFactory {
4424

45-
remotesCfg = cfg.remotes;
46-
if (remotesCfg != null && remotesCfg.length > 0) {
47-
for (const svc of remotesCfg) {
48-
const provider = getCustomProvider(svc.type);
49-
if (provider === undefined) continue;
25+
private static _providerMap: Map<string, (domain: string, path: string) => RemoteProvider>;
26+
private static _remotesCfg: IRemotesConfig[];
5027

51-
providerMap.set(svc.domain.toLowerCase(), provider);
52-
}
53-
}
28+
private static _onDidChange = new EventEmitter<void>();
29+
public static get onDidChange(): Event<void> {
30+
return this._onDidChange.event;
5431
}
55-
}
56-
57-
export class RemoteProviderFactory {
5832

5933
static configure(context: ExtensionContext) {
60-
context.subscriptions.push(workspace.onDidChangeConfiguration(onConfigurationChanged));
61-
onConfigurationChanged();
34+
context.subscriptions.push(workspace.onDidChangeConfiguration(() => this.onConfigurationChanged()));
35+
this.onConfigurationChanged(true);
6236
}
6337

64-
static getRemoteProvider(url: string): RemoteProvider | undefined {
38+
static getRemoteProvider(domain: string, path: string): RemoteProvider | undefined {
6539
try {
66-
const match = UrlRegex.exec(url);
67-
if (match == null) return undefined;
68-
69-
const domain = match[1] || match[2] || match[3] || match[4] || match[5];
70-
const path = match[6].replace(/\.git\/?$/, '');
71-
72-
const key = domain.toLowerCase().endsWith('visualstudio.com')
73-
? 'visualstudio.com'
74-
: domain;
40+
let key = domain.toLowerCase();
41+
if (key.endsWith('visualstudio.com')) {
42+
key = 'visualstudio.com';
43+
}
7544

76-
const creator = providerMap.get(key.toLowerCase());
45+
const creator = this._providerMap.get(key);
7746
if (creator === undefined) return undefined;
7847

7948
return creator(domain, path);
@@ -83,4 +52,37 @@ export class RemoteProviderFactory {
8352
return undefined;
8453
}
8554
}
55+
56+
private static onConfigurationChanged(silent: boolean = false) {
57+
const cfg = workspace.getConfiguration().get<IConfig>(ExtensionKey);
58+
if (cfg === undefined) return;
59+
60+
if (!Objects.areEquivalent(cfg.remotes, this._remotesCfg)) {
61+
this._providerMap = new Map(defaultProviderMap);
62+
63+
this._remotesCfg = cfg.remotes;
64+
if (this._remotesCfg != null && this._remotesCfg.length > 0) {
65+
for (const svc of this._remotesCfg) {
66+
const provider = this.getCustomProvider(svc.type);
67+
if (provider === undefined) continue;
68+
69+
this._providerMap.set(svc.domain.toLowerCase(), provider);
70+
}
71+
72+
if (!silent) {
73+
this._onDidChange.fire();
74+
}
75+
}
76+
}
77+
}
78+
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);
85+
}
86+
return undefined;
87+
}
8688
}

0 commit comments

Comments
 (0)