Browse Source

Removes RemoteProviderFactory static class wrapper

Renames Authentication to be clearer about remote provider connections
main
Eric Amodio 2 years ago
parent
commit
436ca959f9
10 changed files with 208 additions and 176 deletions
  1. +2
    -2
      src/annotations/lineAnnotationController.ts
  2. +3
    -3
      src/env/node/git/localGitProvider.ts
  3. +2
    -2
      src/git/gitProviderService.ts
  4. +3
    -5
      src/git/models/repository.ts
  5. +99
    -89
      src/git/remotes/factory.ts
  6. +21
    -15
      src/git/remotes/github.ts
  7. +21
    -15
      src/git/remotes/gitlab.ts
  8. +24
    -42
      src/git/remotes/provider.ts
  9. +30
    -0
      src/git/remotes/remoteProviderConnections.ts
  10. +3
    -3
      src/plus/github/githubGitProvider.ts

+ 2
- 2
src/annotations/lineAnnotationController.ts View File

@ -12,7 +12,7 @@ import type { Container } from '../container';
import { CommitFormatter } from '../git/formatters/commitFormatter';
import type { GitCommit } from '../git/models/commit';
import type { PullRequest } from '../git/models/pullRequest';
import { Authentication } from '../git/remotes/provider';
import { RichRemoteProviders } from '../git/remotes/remoteProviderConnections';
import type { LogScope } from '../logger';
import { Logger } from '../logger';
import { debug, getLogScope, log } from '../system/decorators/log';
@ -44,7 +44,7 @@ export class LineAnnotationController implements Disposable {
once(container.onReady)(this.onReady, this),
configuration.onDidChange(this.onConfigurationChanged, this),
container.fileAnnotations.onDidToggleAnnotations(this.onFileAnnotationsToggled, this),
Authentication.onDidChange(() => void this.refresh(window.activeTextEditor)),
RichRemoteProviders.onDidChangeConnectionState(() => void this.refresh(window.activeTextEditor)),
);
}

+ 3
- 3
src/env/node/git/localGitProvider.ts View File

@ -93,7 +93,7 @@ import { GitTagParser } from '../../../git/parsers/tagParser';
import { GitTreeParser } from '../../../git/parsers/treeParser';
import { GitWorktreeParser } from '../../../git/parsers/worktreeParser';
import type { RemoteProviders } from '../../../git/remotes/factory';
import { RemoteProviderFactory } from '../../../git/remotes/factory';
import { getRemoteProviderMatcher, loadRemoteProviders } from '../../../git/remotes/factory';
import type { RemoteProvider, RichRemoteProvider } from '../../../git/remotes/provider';
import { RemoteResourceType } from '../../../git/remotes/provider';
import { SearchPattern } from '../../../git/search';
@ -3634,11 +3634,11 @@ export class LocalGitProvider implements GitProvider, Disposable {
): Promise<GitRemote<RemoteProvider | RichRemoteProvider | undefined>[]> {
if (repoPath == null) return [];
const providers = options?.providers ?? RemoteProviderFactory.loadProviders(configuration.get('remotes', null));
const providers = options?.providers ?? loadRemoteProviders(configuration.get('remotes', null));
try {
const data = await this.git.remote(repoPath);
const remotes = GitRemoteParser.parse(data, repoPath, RemoteProviderFactory.factory(providers));
const remotes = GitRemoteParser.parse(data, repoPath, getRemoteProviderMatcher(this.container, providers));
if (remotes == null) return [];
if (options?.sort) {

+ 2
- 2
src/git/gitProviderService.ts View File

@ -68,7 +68,7 @@ import type { GitUser } from './models/user';
import type { GitWorktree } from './models/worktree';
import type { RemoteProviders } from './remotes/factory';
import type { RemoteProvider, RichRemoteProvider } from './remotes/provider';
import { Authentication } from './remotes/provider';
import { RichRemoteProviders } from './remotes/remoteProviderConnections';
import type { SearchPattern } from './search';
const maxDefaultBranchWeight = 100;
@ -154,7 +154,7 @@ export class GitProviderService implements Disposable {
window.onDidChangeWindowState(this.onWindowStateChanged, this),
workspace.onDidChangeWorkspaceFolders(this.onWorkspaceFoldersChanged, this),
configuration.onDidChange(this.onConfigurationChanged, this),
Authentication.onDidChange(e => {
RichRemoteProviders.onDidChangeConnectionState(e => {
if (e.reason === 'connected') {
resetAvatarCache('failed');
}

+ 3
- 5
src/git/models/repository.ts View File

@ -21,7 +21,7 @@ import { basename, normalizePath } from '../../system/path';
import { runGitCommandInTerminal } from '../../terminal';
import type { GitProviderDescriptor } from '../gitProvider';
import type { RemoteProviders } from '../remotes/factory';
import { RemoteProviderFactory } from '../remotes/factory';
import { loadRemoteProviders } from '../remotes/factory';
import { RichRemoteProvider } from '../remotes/provider';
import type { SearchPattern } from '../search';
import type { BranchSortOptions, GitBranch } from './branch';
@ -284,9 +284,7 @@ export class Repository implements Disposable {
private onConfigurationChanged(e?: ConfigurationChangeEvent) {
if (configuration.changed(e, 'remotes', this.folder?.uri)) {
this._providers = RemoteProviderFactory.loadProviders(
configuration.get('remotes', this.folder?.uri ?? null),
);
this._providers = loadRemoteProviders(configuration.get('remotes', this.folder?.uri ?? null));
if (e != null) {
this.resetCaches('remotes');
@ -577,7 +575,7 @@ export class Repository implements Disposable {
if (this._remotes == null) {
if (this._providers == null) {
const remotesCfg = configuration.get('remotes', this.folder?.uri ?? null);
this._providers = RemoteProviderFactory.loadProviders(remotesCfg);
this._providers = loadRemoteProviders(remotesCfg);
}
// Since we are caching the results, always sort

+ 99
- 89
src/git/remotes/factory.ts View File

@ -1,5 +1,6 @@
import type { RemotesConfig } from '../../configuration';
import { CustomRemoteType } from '../../configuration';
import type { Container } from '../../container';
import { Logger } from '../../logger';
import { AzureDevOpsRemote } from './azure-devops';
import { BitbucketRemote } from './bitbucket';
@ -12,156 +13,165 @@ import { GitLabRemote } from './gitlab';
import { GoogleSourceRemote } from './google-source';
import type { RemoteProvider } from './provider';
// export { RemoteProvider, RichRemoteProvider };
export type RemoteProviders = {
custom: boolean;
matcher: string | RegExp;
creator: (domain: string, path: string) => RemoteProvider;
creator: (container: Container, domain: string, path: string) => RemoteProvider;
}[];
const builtInProviders: RemoteProviders = [
{
custom: false,
matcher: 'bitbucket.org',
creator: (domain: string, path: string) => new BitbucketRemote(domain, path),
creator: (_container: Container, domain: string, path: string) => new BitbucketRemote(domain, path),
},
{
custom: false,
matcher: 'github.com',
creator: (domain: string, path: string) => new GitHubRemote(domain, path),
creator: (container: Container, domain: string, path: string) => new GitHubRemote(container, domain, path),
},
{
custom: false,
matcher: 'gitlab.com',
creator: (domain: string, path: string) => new GitLabRemote(domain, path),
creator: (container: Container, domain: string, path: string) => new GitLabRemote(container, domain, path),
},
{
custom: false,
matcher: /\bdev\.azure\.com$/i,
creator: (domain: string, path: string) => new AzureDevOpsRemote(domain, path),
creator: (_container: Container, domain: string, path: string) => new AzureDevOpsRemote(domain, path),
},
{
custom: true,
matcher: /^(.+\/(?:bitbucket|stash))\/scm\/(.+)$/i,
creator: (domain: string, path: string) => new BitbucketServerRemote(domain, path),
creator: (_container: Container, domain: string, path: string) => new BitbucketServerRemote(domain, path),
},
{
custom: false,
matcher: /\bgitlab\b/i,
creator: (domain: string, path: string) => new GitLabRemote(domain, path),
creator: (container: Container, domain: string, path: string) => new GitLabRemote(container, domain, path),
},
{
custom: false,
matcher: /\bvisualstudio\.com$/i,
creator: (domain: string, path: string) => new AzureDevOpsRemote(domain, path, undefined, undefined, true),
creator: (_container: Container, domain: string, path: string) =>
new AzureDevOpsRemote(domain, path, undefined, undefined, true),
},
{
custom: false,
matcher: /\bgitea\b/i,
creator: (domain: string, path: string) => new GiteaRemote(domain, path),
creator: (_container: Container, domain: string, path: string) => new GiteaRemote(domain, path),
},
{
custom: false,
matcher: /\bgerrithub\.io$/i,
creator: (domain: string, path: string) => new GerritRemote(domain, path),
creator: (_container: Container, domain: string, path: string) => new GerritRemote(domain, path),
},
{
custom: false,
matcher: /\bgooglesource\.com$/i,
creator: (domain: string, path: string) => new GoogleSourceRemote(domain, path),
creator: (_container: Container, domain: string, path: string) => new GoogleSourceRemote(domain, path),
},
];
// eslint-disable-next-line @typescript-eslint/no-extraneous-class
export class RemoteProviderFactory {
static factory(
providers: RemoteProviders,
): (url: string, domain: string, path: string) => RemoteProvider | undefined {
return (url: string, domain: string, path: string) => this.create(providers, url, domain, path);
}
static create(providers: RemoteProviders, url: string, domain: string, path: string): RemoteProvider | undefined {
try {
const key = domain.toLowerCase();
for (const { custom, matcher, creator } of providers) {
if (typeof matcher === 'string') {
if (matcher === key) return creator(domain, path);
continue;
}
export function loadRemoteProviders(cfg: RemotesConfig[] | null | undefined): RemoteProviders {
const providers: RemoteProviders = [];
if (matcher.test(key)) return creator(domain, path);
if (!custom) continue;
if (cfg?.length) {
for (const rc of cfg) {
const providerCreator = getCustomProviderCreator(rc);
if (providerCreator == null) continue;
const match = matcher.exec(url);
if (match != null) {
return creator(match[1], match[2]);
}
let matcher: string | RegExp | undefined;
try {
matcher = rc.regex ? new RegExp(rc.regex, 'i') : rc.domain?.toLowerCase();
if (matcher == null) throw new Error('No matcher found');
} catch (ex) {
Logger.error(ex, `Loading remote provider '${rc.name ?? ''}' failed`);
}
return undefined;
} catch (ex) {
Logger.error(ex, 'RemoteProviderFactory');
return undefined;
providers.push({
custom: true,
matcher: matcher!,
creator: providerCreator,
});
}
}
static loadProviders(cfg: RemotesConfig[] | null | undefined): RemoteProviders {
const providers: RemoteProviders = [];
providers.push(...builtInProviders);
if (cfg != null && cfg.length > 0) {
for (const rc of cfg) {
const provider = this.getCustomProvider(rc);
if (provider == null) continue;
return providers;
}
let matcher: string | RegExp | undefined;
try {
matcher = rc.regex ? new RegExp(rc.regex, 'i') : rc.domain?.toLowerCase();
if (matcher == null) throw new Error('No matcher found');
} catch (ex) {
Logger.error(ex, `Loading remote provider '${rc.name ?? ''}' failed`);
}
function getCustomProviderCreator(cfg: RemotesConfig) {
switch (cfg.type) {
case CustomRemoteType.AzureDevOps:
return (_container: Container, domain: string, path: string) =>
new AzureDevOpsRemote(domain, path, cfg.protocol, cfg.name, true);
case CustomRemoteType.Bitbucket:
return (_container: Container, domain: string, path: string) =>
new BitbucketRemote(domain, path, cfg.protocol, cfg.name, true);
case CustomRemoteType.BitbucketServer:
return (_container: Container, domain: string, path: string) =>
new BitbucketServerRemote(domain, path, cfg.protocol, cfg.name, true);
case CustomRemoteType.Custom:
return (_container: Container, domain: string, path: string) =>
new CustomRemote(domain, path, cfg.urls!, cfg.protocol, cfg.name);
case CustomRemoteType.Gerrit:
return (_container: Container, domain: string, path: string) =>
new GerritRemote(domain, path, cfg.protocol, cfg.name, true);
case CustomRemoteType.GoogleSource:
return (_container: Container, domain: string, path: string) =>
new GoogleSourceRemote(domain, path, cfg.protocol, cfg.name, true);
case CustomRemoteType.Gitea:
return (_container: Container, domain: string, path: string) =>
new GiteaRemote(domain, path, cfg.protocol, cfg.name, true);
case CustomRemoteType.GitHub:
return (container: Container, domain: string, path: string) =>
new GitHubRemote(container, domain, path, cfg.protocol, cfg.name, true);
case CustomRemoteType.GitLab:
return (container: Container, domain: string, path: string) =>
new GitLabRemote(container, domain, path, cfg.protocol, cfg.name, true);
default:
return undefined;
}
}
providers.push({
custom: true,
matcher: matcher!,
creator: provider,
});
}
}
export function getRemoteProviderMatcher(
container: Container,
providers: RemoteProviders,
): (url: string, domain: string, path: string) => RemoteProvider | undefined {
return (url: string, domain: string, path: string) =>;
createBestRemoteProvider(container, providers, url, domain, path);
}
providers.push(...builtInProviders);
function createBestRemoteProvider(
container: Container,
providers: RemoteProviders,
url: string,
domain: string,
path: string,
): RemoteProvider | undefined {
try {
const key = domain.toLowerCase();
for (const { custom, matcher, creator } of providers) {
if (typeof matcher === 'string') {
if (matcher === key) return creator(container, domain, path);
return providers;
}
continue;
}
if (matcher.test(key)) return creator(container, domain, path);
if (!custom) continue;
private static getCustomProvider(cfg: RemotesConfig) {
switch (cfg.type) {
case CustomRemoteType.AzureDevOps:
return (domain: string, path: string) =>
new AzureDevOpsRemote(domain, path, cfg.protocol, cfg.name, true);
case CustomRemoteType.Bitbucket:
return (domain: string, path: string) =>
new BitbucketRemote(domain, path, cfg.protocol, cfg.name, true);
case CustomRemoteType.BitbucketServer:
return (domain: string, path: string) =>
new BitbucketServerRemote(domain, path, cfg.protocol, cfg.name, true);
case CustomRemoteType.Custom:
return (domain: string, path: string) =>
new CustomRemote(domain, path, cfg.urls!, cfg.protocol, cfg.name);
case CustomRemoteType.Gerrit:
return (domain: string, path: string) => new GerritRemote(domain, path, cfg.protocol, cfg.name, true);
case CustomRemoteType.GoogleSource:
return (domain: string, path: string) =>
new GoogleSourceRemote(domain, path, cfg.protocol, cfg.name, true);
case CustomRemoteType.Gitea:
return (domain: string, path: string) => new GiteaRemote(domain, path, cfg.protocol, cfg.name, true);
case CustomRemoteType.GitHub:
return (domain: string, path: string) => new GitHubRemote(domain, path, cfg.protocol, cfg.name, true);
case CustomRemoteType.GitLab:
return (domain: string, path: string) => new GitLabRemote(domain, path, cfg.protocol, cfg.name, true);
default:
return undefined;
const match = matcher.exec(url);
if (match != null) {
return creator(container, match[1], match[2]);
}
}
return undefined;
} catch (ex) {
Logger.error(ex, 'createRemoteProvider');
return undefined;
}
}

+ 21
- 15
src/git/remotes/github.ts View File

@ -2,7 +2,7 @@ import type { AuthenticationSession, Range } from 'vscode';
import { Uri, window } from 'vscode';
import type { Autolink, DynamicAutolinkReference } from '../../annotations/autolinks';
import type { AutolinkReference } from '../../config';
import { Container } from '../../container';
import type { Container } from '../../container';
import { isSubscriptionPaidPlan, isSubscriptionPreviewTrialExpired } from '../../subscription';
import { log } from '../../system/decorators/log';
import { memoize } from '../../system/decorators/memoize';
@ -28,8 +28,15 @@ export class GitHubRemote extends RichRemoteProvider {
return equalsIgnoreCase(this.domain, 'github.com') ? authProvider : enterpriseAuthProvider;
}
constructor(domain: string, path: string, protocol?: string, name?: string, custom: boolean = false) {
super(domain, path, protocol, name, custom);
constructor(
container: Container,
domain: string,
path: string,
protocol?: string,
name?: string,
custom: boolean = false,
) {
super(container, domain, path, protocol, name, custom);
}
get apiBaseUrl() {
@ -109,12 +116,11 @@ export class GitHubRemote extends RichRemoteProvider {
@log()
override async connect(): Promise<boolean> {
if (!equalsIgnoreCase(this.domain, 'github.com')) {
const container = Container.instance;
const title =
'Connecting to a GitHub Enterprise instance for rich integration features requires a paid GitLens+ account.';
while (true) {
const subscription = await container.subscription.getSubscription();
const subscription = await this.container.subscription.getSubscription();
if (subscription.account?.verified === false) {
const resend = { title: 'Resend Verification' };
const cancel = { title: 'Cancel', isCloseAffordance: true };
@ -126,7 +132,7 @@ export class GitHubRemote extends RichRemoteProvider {
);
if (result === resend) {
if (await container.subscription.resendVerification()) {
if (await this.container.subscription.resendVerification()) {
continue;
}
}
@ -149,7 +155,7 @@ export class GitHubRemote extends RichRemoteProvider {
if (result !== startTrial) return false;
void container.subscription.startPreviewTrial();
void this.container.subscription.startPreviewTrial();
break;
} else if (subscription.account == null) {
const signIn = { title: 'Sign In to GitLens+' };
@ -162,7 +168,7 @@ export class GitHubRemote extends RichRemoteProvider {
);
if (result === signIn) {
if (await container.subscription.loginOrSignUp()) {
if (await this.container.subscription.loginOrSignUp()) {
continue;
}
}
@ -177,7 +183,7 @@ export class GitHubRemote extends RichRemoteProvider {
);
if (result === upgrade) {
void container.subscription.purchase();
void this.container.subscription.purchase();
}
}
@ -306,7 +312,7 @@ export class GitHubRemote extends RichRemoteProvider {
},
): Promise<Account | undefined> {
const [owner, repo] = this.splitPath();
return (await Container.instance.github)?.getAccountForCommit(this, accessToken, owner, repo, ref, {
return (await this.container.github)?.getAccountForCommit(this, accessToken, owner, repo, ref, {
...options,
baseUrl: this.apiBaseUrl,
});
@ -320,7 +326,7 @@ export class GitHubRemote extends RichRemoteProvider {
},
): Promise<Account | undefined> {
const [owner, repo] = this.splitPath();
return (await Container.instance.github)?.getAccountForEmail(this, accessToken, owner, repo, email, {
return (await this.container.github)?.getAccountForEmail(this, accessToken, owner, repo, email, {
...options,
baseUrl: this.apiBaseUrl,
});
@ -330,7 +336,7 @@ export class GitHubRemote extends RichRemoteProvider {
accessToken,
}: AuthenticationSession): Promise<DefaultBranch | undefined> {
const [owner, repo] = this.splitPath();
return (await Container.instance.github)?.getDefaultBranch(this, accessToken, owner, repo, {
return (await this.container.github)?.getDefaultBranch(this, accessToken, owner, repo, {
baseUrl: this.apiBaseUrl,
});
}
@ -340,7 +346,7 @@ export class GitHubRemote extends RichRemoteProvider {
id: string,
): Promise<IssueOrPullRequest | undefined> {
const [owner, repo] = this.splitPath();
return (await Container.instance.github)?.getIssueOrPullRequest(this, accessToken, owner, repo, Number(id), {
return (await this.container.github)?.getIssueOrPullRequest(this, accessToken, owner, repo, Number(id), {
baseUrl: this.apiBaseUrl,
});
}
@ -358,7 +364,7 @@ export class GitHubRemote extends RichRemoteProvider {
const GitHubPullRequest = (await import(/* webpackChunkName: "github" */ '../../plus/github/models'))
.GitHubPullRequest;
return (await Container.instance.github)?.getPullRequestForBranch(this, accessToken, owner, repo, branch, {
return (await this.container.github)?.getPullRequestForBranch(this, accessToken, owner, repo, branch, {
...opts,
include: include?.map(s => GitHubPullRequest.toState(s)),
baseUrl: this.apiBaseUrl,
@ -370,7 +376,7 @@ export class GitHubRemote extends RichRemoteProvider {
ref: string,
): Promise<PullRequest | undefined> {
const [owner, repo] = this.splitPath();
return (await Container.instance.github)?.getPullRequestForCommit(this, accessToken, owner, repo, ref, {
return (await this.container.github)?.getPullRequestForCommit(this, accessToken, owner, repo, ref, {
baseUrl: this.apiBaseUrl,
});
}

+ 21
- 15
src/git/remotes/gitlab.ts View File

@ -3,7 +3,7 @@ import { env, ThemeIcon, Uri, window } from 'vscode';
import type { Autolink, DynamicAutolinkReference } from '../../annotations/autolinks';
import type { AutolinkReference } from '../../config';
import { AutolinkType } from '../../config';
import { Container } from '../../container';
import type { Container } from '../../container';
import type {
IntegrationAuthenticationProvider,
IntegrationAuthenticationSessionDescriptor,
@ -31,8 +31,15 @@ export class GitLabRemote extends RichRemoteProvider {
return authProvider;
}
constructor(domain: string, path: string, protocol?: string, name?: string, custom: boolean = false) {
super(domain, path, protocol, name, custom);
constructor(
container: Container,
domain: string,
path: string,
protocol?: string,
name?: string,
custom: boolean = false,
) {
super(container, domain, path, protocol, name, custom);
}
get apiBaseUrl() {
@ -139,12 +146,11 @@ export class GitLabRemote extends RichRemoteProvider {
@log()
override async connect(): Promise<boolean> {
if (!equalsIgnoreCase(this.domain, 'gitlab.com')) {
const container = Container.instance;
const title =
'Connecting to a GitLab self-managed instance for rich integration features requires a paid GitLens+ account.';
while (true) {
const subscription = await container.subscription.getSubscription();
const subscription = await this.container.subscription.getSubscription();
if (subscription.account?.verified === false) {
const resend = { title: 'Resend Verification' };
const cancel = { title: 'Cancel', isCloseAffordance: true };
@ -156,7 +162,7 @@ export class GitLabRemote extends RichRemoteProvider {
);
if (result === resend) {
if (await container.subscription.resendVerification()) {
if (await this.container.subscription.resendVerification()) {
continue;
}
}
@ -179,7 +185,7 @@ export class GitLabRemote extends RichRemoteProvider {
if (result !== startTrial) return false;
void container.subscription.startPreviewTrial();
void this.container.subscription.startPreviewTrial();
break;
} else if (subscription.account == null) {
const signIn = { title: 'Sign In to GitLens+' };
@ -192,7 +198,7 @@ export class GitLabRemote extends RichRemoteProvider {
);
if (result === signIn) {
if (await container.subscription.loginOrSignUp()) {
if (await this.container.subscription.loginOrSignUp()) {
continue;
}
}
@ -207,7 +213,7 @@ export class GitLabRemote extends RichRemoteProvider {
);
if (result === upgrade) {
void container.subscription.purchase();
void this.container.subscription.purchase();
}
}
@ -324,7 +330,7 @@ export class GitLabRemote extends RichRemoteProvider {
},
): Promise<Account | undefined> {
const [owner, repo] = this.splitPath();
return (await Container.instance.gitlab)?.getAccountForCommit(this, accessToken, owner, repo, ref, {
return (await this.container.gitlab)?.getAccountForCommit(this, accessToken, owner, repo, ref, {
...options,
baseUrl: this.apiBaseUrl,
});
@ -338,7 +344,7 @@ export class GitLabRemote extends RichRemoteProvider {
},
): Promise<Account | undefined> {
const [owner, repo] = this.splitPath();
return (await Container.instance.gitlab)?.getAccountForEmail(this, accessToken, owner, repo, email, {
return (await this.container.gitlab)?.getAccountForEmail(this, accessToken, owner, repo, email, {
...options,
baseUrl: this.apiBaseUrl,
});
@ -348,7 +354,7 @@ export class GitLabRemote extends RichRemoteProvider {
accessToken,
}: AuthenticationSession): Promise<DefaultBranch | undefined> {
const [owner, repo] = this.splitPath();
return (await Container.instance.gitlab)?.getDefaultBranch(this, accessToken, owner, repo, {
return (await this.container.gitlab)?.getDefaultBranch(this, accessToken, owner, repo, {
baseUrl: this.apiBaseUrl,
});
}
@ -358,7 +364,7 @@ export class GitLabRemote extends RichRemoteProvider {
id: string,
): Promise<IssueOrPullRequest | undefined> {
const [owner, repo] = this.splitPath();
return (await Container.instance.gitlab)?.getIssueOrPullRequest(this, accessToken, owner, repo, Number(id), {
return (await this.container.gitlab)?.getIssueOrPullRequest(this, accessToken, owner, repo, Number(id), {
baseUrl: this.apiBaseUrl,
});
}
@ -376,7 +382,7 @@ export class GitLabRemote extends RichRemoteProvider {
const GitLabMergeRequest = (await import(/* webpackChunkName: "gitlab" */ '../../plus/gitlab/models'))
.GitLabMergeRequest;
return (await Container.instance.gitlab)?.getPullRequestForBranch(this, accessToken, owner, repo, branch, {
return (await this.container.gitlab)?.getPullRequestForBranch(this, accessToken, owner, repo, branch, {
...opts,
include: include?.map(s => GitLabMergeRequest.toState(s)),
baseUrl: this.apiBaseUrl,
@ -388,7 +394,7 @@ export class GitLabRemote extends RichRemoteProvider {
ref: string,
): Promise<PullRequest | undefined> {
const [owner, repo] = this.splitPath();
return (await Container.instance.gitlab)?.getPullRequestForCommit(this, accessToken, owner, repo, ref, {
return (await this.container.gitlab)?.getPullRequestForCommit(this, accessToken, owner, repo, ref, {
baseUrl: this.apiBaseUrl,
});
}

+ 24
- 42
src/git/remotes/provider.ts View File

@ -5,7 +5,7 @@ import { isWeb } from '@env/platform';
import type { DynamicAutolinkReference } from '../../annotations/autolinks';
import type { AutolinkReference } from '../../config';
import { configuration } from '../../configuration';
import { Container } from '../../container';
import type { Container } from '../../container';
import { AuthenticationError, ProviderRequestClientError } from '../../errors';
import { Logger } from '../../logger';
import { showIntegrationDisconnectedTooManyFailedRequestsWarningMessage } from '../../messages';
@ -21,6 +21,7 @@ import type { IssueOrPullRequest } from '../models/issue';
import type { PullRequest, PullRequestState } from '../models/pullRequest';
import type { RemoteProviderReference } from '../models/remoteProvider';
import type { Repository } from '../models/repository';
import { RichRemoteProviders } from './remoteProviderConnections';
export const enum RemoteResourceType {
Branch = 'branch',
@ -244,28 +245,6 @@ export abstract class RemoteProvider implements RemoteProviderReference {
}
}
const _connectedCache = new Set<string>();
const _onDidChangeAuthentication = new EventEmitter<{ reason: 'connected' | 'disconnected'; key: string }>();
function fireAuthenticationChanged(key: string, reason: 'connected' | 'disconnected') {
// Only fire events if the key is being connected for the first time (we could probably do the same for disconnected, but better safe on those imo)
if (_connectedCache.has(key)) {
if (reason === 'connected') return;
_connectedCache.delete(key);
} else if (reason === 'connected') {
_connectedCache.add(key);
}
_onDidChangeAuthentication.fire({ key: key, reason: reason });
}
// eslint-disable-next-line @typescript-eslint/no-extraneous-class
export class Authentication {
static get onDidChange(): Event<{ reason: 'connected' | 'disconnected'; key: string }> {
return _onDidChangeAuthentication.event;
}
}
// TODO@eamodio revisit how once authenticated, all remotes are always connected, even after a restart
export abstract class RichRemoteProvider extends RemoteProvider {
@ -280,17 +259,24 @@ export abstract class RichRemoteProvider extends RemoteProvider {
return this._onDidChange.event;
}
constructor(domain: string, path: string, protocol?: string, name?: string, custom?: boolean) {
constructor(
protected readonly container: Container,
domain: string,
path: string,
protocol?: string,
name?: string,
custom?: boolean,
) {
super(domain, path, protocol, name, custom);
Container.instance.context.subscriptions.push(
this.container.context.subscriptions.push(
configuration.onDidChange(e => {
if (configuration.changed(e, 'remotes')) {
this._ignoreSSLErrors.clear();
}
}),
// TODO@eamodio revisit how connections are linked or not
Authentication.onDidChange(e => {
RichRemoteProviders.onDidChangeConnectionState(e => {
if (e.key !== this.key) return;
if (e.reason === 'disconnected') {
@ -354,8 +340,6 @@ export abstract class RichRemoteProvider extends RemoteProvider {
const connected = this._session != null;
const container = Container.instance;
if (connected && !options?.silent) {
if (options?.currentSessionOnly) {
void showIntegrationDisconnectedTooManyFailedRequestsWarningMessage(this.name);
@ -365,7 +349,7 @@ export abstract class RichRemoteProvider extends RemoteProvider {
const cancel = { title: 'Cancel', isCloseAffordance: true };
let result: MessageItem | undefined;
if (container.integrationAuthentication.hasProvider(this.authProvider.id)) {
if (this.container.integrationAuthentication.hasProvider(this.authProvider.id)) {
result = await window.showWarningMessage(
`Are you sure you want to disable the rich integration with ${this.name}?\n\nNote: signing out clears the saved authentication.`,
{ modal: true },
@ -384,7 +368,7 @@ export abstract class RichRemoteProvider extends RemoteProvider {
if (result == null || result === cancel) return;
if (result === signout) {
void container.integrationAuthentication.deleteSession(this.id, this.authProviderDescriptor);
void this.container.integrationAuthentication.deleteSession(this.id, this.authProviderDescriptor);
}
}
}
@ -396,12 +380,12 @@ export abstract class RichRemoteProvider extends RemoteProvider {
if (connected) {
// Don't store the disconnected flag if this only for this current VS Code session (will be re-connected on next restart)
if (!options?.currentSessionOnly) {
void container.storage.storeWorkspace(this.connectedKey, false);
void this.container.storage.storeWorkspace(this.connectedKey, false);
}
this._onDidChange.fire();
if (!options?.silent && !options?.currentSessionOnly) {
fireAuthenticationChanged(this.key, 'disconnected');
RichRemoteProviders.disconnected(this.key);
}
}
}
@ -663,18 +647,16 @@ export abstract class RichRemoteProvider extends RemoteProvider {
if (this._session != null) return this._session;
if (!configuration.get('integrations.enabled')) return undefined;
const { instance: container } = Container;
if (createIfNeeded) {
await container.storage.deleteWorkspace(this.connectedKey);
} else if (container.storage.getWorkspace(this.connectedKey) === false) {
await this.container.storage.deleteWorkspace(this.connectedKey);
} else if (this.container.storage.getWorkspace(this.connectedKey) === false) {
return undefined;
}
let session: AuthenticationSession | undefined | null;
try {
if (container.integrationAuthentication.hasProvider(this.authProvider.id)) {
session = await container.integrationAuthentication.getSession(
if (this.container.integrationAuthentication.hasProvider(this.authProvider.id)) {
session = await this.container.integrationAuthentication.getSession(
this.authProvider.id,
this.authProviderDescriptor,
{ createIfNeeded: createIfNeeded, forceNewSession: forceNewSession },
@ -689,7 +671,7 @@ export abstract class RichRemoteProvider extends RemoteProvider {
);
}
} catch (ex) {
await container.storage.deleteWorkspace(this.connectedKey);
await this.container.storage.deleteWorkspace(this.connectedKey);
if (ex instanceof Error && ex.message.includes('User did not consent')) {
return undefined;
@ -699,18 +681,18 @@ export abstract class RichRemoteProvider extends RemoteProvider {
}
if (session === undefined && !createIfNeeded) {
await container.storage.deleteWorkspace(this.connectedKey);
await this.container.storage.deleteWorkspace(this.connectedKey);
}
this._session = session ?? null;
this.resetRequestExceptionCount();
if (session != null) {
await container.storage.storeWorkspace(this.connectedKey, true);
await this.container.storage.storeWorkspace(this.connectedKey, true);
queueMicrotask(() => {
this._onDidChange.fire();
fireAuthenticationChanged(this.key, 'connected');
RichRemoteProviders.connected(this.key);
});
}

+ 30
- 0
src/git/remotes/remoteProviderConnections.ts View File

@ -0,0 +1,30 @@
import { EventEmitter } from 'vscode';
import type { Event } from 'vscode';
export interface ConnectionStateChangeEvent {
key: string;
reason: 'connected' | 'disconnected';
}
export namespace RichRemoteProviders {
const _connectedCache = new Set<string>();
export const _onDidChangeConnectionState = new EventEmitter<ConnectionStateChangeEvent>();
export const onDidChangeConnectionState: Event<ConnectionStateChangeEvent> = _onDidChangeConnectionState.event;
export function connected(key: string): void {
// Only fire events if the key is being connected for the first time
if (_connectedCache.has(key)) return;
_connectedCache.add(key);
_onDidChangeConnectionState.fire({ key: key, reason: 'connected' });
}
export function disconnected(key: string): void {
// Probably shouldn't bother to fire the event if we don't already think we are connected, but better to be safe
// if (!_connectedCache.has(key)) return;
_connectedCache.delete(key);
_onDidChangeConnectionState.fire({ key: key, reason: 'disconnected' });
}
}

+ 3
- 3
src/plus/github/githubGitProvider.ts View File

@ -61,7 +61,7 @@ import type { GitTreeEntry } from '../../git/models/tree';
import type { GitUser } from '../../git/models/user';
import { isUserMatch } from '../../git/models/user';
import type { RemoteProviders } from '../../git/remotes/factory';
import { RemoteProviderFactory } from '../../git/remotes/factory';
import { getRemoteProviderMatcher, loadRemoteProviders } from '../../git/remotes/factory';
import type { RemoteProvider, RichRemoteProvider } from '../../git/remotes/provider';
import { SearchPattern } from '../../git/search';
import type { LogScope } from '../../logger';
@ -2303,7 +2303,7 @@ export class GitHubGitProvider implements GitProvider, Disposable {
): Promise<GitRemote<RemoteProvider | RichRemoteProvider | undefined>[]> {
if (repoPath == null) return [];
const providers = options?.providers ?? RemoteProviderFactory.loadProviders(configuration.get('remotes', null));
const providers = options?.providers ?? loadRemoteProviders(configuration.get('remotes', null));
const uri = Uri.parse(repoPath, true);
const [, owner, repo] = uri.path.split('/', 3);
@ -2320,7 +2320,7 @@ export class GitHubGitProvider implements GitProvider, Disposable {
'https',
domain,
path,
RemoteProviderFactory.factory(providers)(url, domain, path),
getRemoteProviderMatcher(this.container, providers)(url, domain, path),
[
{ type: GitRemoteType.Fetch, url: url },
{ type: GitRemoteType.Push, url: url },

Loading…
Cancel
Save