diff --git a/src/codelens/codeLensProvider.ts b/src/codelens/codeLensProvider.ts index 79340b9..1bc28a0 100644 --- a/src/codelens/codeLensProvider.ts +++ b/src/codelens/codeLensProvider.ts @@ -25,7 +25,7 @@ import type { Container } from '../container'; import type { GitUri } from '../git/gitUri'; import type { GitBlame, GitBlameLines } from '../git/models/blame'; import type { GitCommit } from '../git/models/commit'; -import { RemoteResourceType } from '../git/remotes/provider'; +import { RemoteResourceType } from '../git/models/remoteResource'; import { Logger } from '../logger'; import { asCommand, executeCoreCommand } from '../system/command'; import { is, once } from '../system/function'; diff --git a/src/commands/createPullRequestOnRemote.ts b/src/commands/createPullRequestOnRemote.ts index 638b3e5..eb4341b 100644 --- a/src/commands/createPullRequestOnRemote.ts +++ b/src/commands/createPullRequestOnRemote.ts @@ -1,8 +1,9 @@ import { Commands } from '../constants'; import type { Container } from '../container'; import type { GitRemote } from '../git/models/remote'; -import type { RemoteProvider, RemoteResource } from '../git/remotes/provider'; -import { RemoteResourceType } from '../git/remotes/provider'; +import type { RemoteResource } from '../git/models/remoteResource'; +import { RemoteResourceType } from '../git/models/remoteResource'; +import type { RemoteProvider } from '../git/remotes/remoteProvider'; import { command, executeCommand } from '../system/command'; import { Command } from './base'; import type { OpenOnRemoteCommandArgs } from './openOnRemote'; diff --git a/src/commands/openBranchOnRemote.ts b/src/commands/openBranchOnRemote.ts index 0c48f71..cc1c1a2 100644 --- a/src/commands/openBranchOnRemote.ts +++ b/src/commands/openBranchOnRemote.ts @@ -3,7 +3,7 @@ import { window } from 'vscode'; import { Commands } from '../constants'; import type { Container } from '../container'; import { GitUri } from '../git/gitUri'; -import { RemoteResourceType } from '../git/remotes/provider'; +import { RemoteResourceType } from '../git/models/remoteResource'; import { Logger } from '../logger'; import { CommandQuickPickItem } from '../quickpicks/items/common'; import { ReferencePicker, ReferencesQuickPickIncludes } from '../quickpicks/referencePicker'; diff --git a/src/commands/openBranchesOnRemote.ts b/src/commands/openBranchesOnRemote.ts index 0cfaf5a..8fe4513 100644 --- a/src/commands/openBranchesOnRemote.ts +++ b/src/commands/openBranchesOnRemote.ts @@ -3,7 +3,7 @@ import { window } from 'vscode'; import { Commands } from '../constants'; import type { Container } from '../container'; import { GitUri } from '../git/gitUri'; -import { RemoteResourceType } from '../git/remotes/provider'; +import { RemoteResourceType } from '../git/models/remoteResource'; import { Logger } from '../logger'; import { RepositoryPicker } from '../quickpicks/repositoryPicker'; import { command, executeCommand } from '../system/command'; diff --git a/src/commands/openCommitOnRemote.ts b/src/commands/openCommitOnRemote.ts index e6aa0cc..aa134a8 100644 --- a/src/commands/openCommitOnRemote.ts +++ b/src/commands/openCommitOnRemote.ts @@ -4,7 +4,7 @@ import { Commands } from '../constants'; import type { Container } from '../container'; import { GitUri } from '../git/gitUri'; import { GitRevision } from '../git/models/reference'; -import { RemoteResourceType } from '../git/remotes/provider'; +import { RemoteResourceType } from '../git/models/remoteResource'; import { Logger } from '../logger'; import { showFileNotUnderSourceControlWarningMessage } from '../messages'; import { command, executeCommand } from '../system/command'; diff --git a/src/commands/openComparisonOnRemote.ts b/src/commands/openComparisonOnRemote.ts index d3bed37..2fcff53 100644 --- a/src/commands/openComparisonOnRemote.ts +++ b/src/commands/openComparisonOnRemote.ts @@ -1,7 +1,7 @@ import { window } from 'vscode'; import { Commands } from '../constants'; import type { Container } from '../container'; -import { RemoteResourceType } from '../git/remotes/provider'; +import { RemoteResourceType } from '../git/models/remoteResource'; import { Logger } from '../logger'; import { command, executeCommand } from '../system/command'; import { ResultsCommitsNode } from '../views/nodes/resultsCommitsNode'; diff --git a/src/commands/openCurrentBranchOnRemote.ts b/src/commands/openCurrentBranchOnRemote.ts index b7aeeb4..cb44c7f 100644 --- a/src/commands/openCurrentBranchOnRemote.ts +++ b/src/commands/openCurrentBranchOnRemote.ts @@ -3,7 +3,7 @@ import { window } from 'vscode'; import { Commands } from '../constants'; import type { Container } from '../container'; import { GitUri } from '../git/gitUri'; -import { RemoteResourceType } from '../git/remotes/provider'; +import { RemoteResourceType } from '../git/models/remoteResource'; import { Logger } from '../logger'; import { RepositoryPicker } from '../quickpicks/repositoryPicker'; import { command, executeCommand } from '../system/command'; diff --git a/src/commands/openFileOnRemote.ts b/src/commands/openFileOnRemote.ts index ed540c8..8029c46 100644 --- a/src/commands/openFileOnRemote.ts +++ b/src/commands/openFileOnRemote.ts @@ -7,7 +7,7 @@ import type { Container } from '../container'; import { GitUri } from '../git/gitUri'; import { getBranchNameWithoutRemote, getRemoteNameFromBranchName } from '../git/models/branch'; import { GitRevision } from '../git/models/reference'; -import { RemoteResourceType } from '../git/remotes/provider'; +import { RemoteResourceType } from '../git/models/remoteResource'; import { Logger } from '../logger'; import { ReferencePicker } from '../quickpicks/referencePicker'; import { command, executeCommand } from '../system/command'; diff --git a/src/commands/openOnRemote.ts b/src/commands/openOnRemote.ts index 81a0162..7cf0a02 100644 --- a/src/commands/openOnRemote.ts +++ b/src/commands/openOnRemote.ts @@ -2,8 +2,9 @@ import { Commands, GlyphChars } from '../constants'; import type { Container } from '../container'; import { GitRevision } from '../git/models/reference'; import { GitRemote } from '../git/models/remote'; -import type { RemoteProvider, RemoteResource } from '../git/remotes/provider'; -import { RemoteResourceType } from '../git/remotes/provider'; +import type { RemoteResource } from '../git/models/remoteResource'; +import { RemoteResourceType } from '../git/models/remoteResource'; +import type { RemoteProvider } from '../git/remotes/remoteProvider'; import { Logger } from '../logger'; import { showGenericErrorMessage } from '../messages'; import { RemoteProviderPicker } from '../quickpicks/remoteProviderPicker'; diff --git a/src/commands/openRepoOnRemote.ts b/src/commands/openRepoOnRemote.ts index 5ba06f5..23daaaf 100644 --- a/src/commands/openRepoOnRemote.ts +++ b/src/commands/openRepoOnRemote.ts @@ -3,7 +3,7 @@ import { window } from 'vscode'; import { Commands } from '../constants'; import type { Container } from '../container'; import { GitUri } from '../git/gitUri'; -import { RemoteResourceType } from '../git/remotes/provider'; +import { RemoteResourceType } from '../git/models/remoteResource'; import { Logger } from '../logger'; import { RepositoryPicker } from '../quickpicks/repositoryPicker'; import { command, executeCommand } from '../system/command'; diff --git a/src/commands/quickCommand.steps.ts b/src/commands/quickCommand.steps.ts index d5a361b..6067033 100644 --- a/src/commands/quickCommand.steps.ts +++ b/src/commands/quickCommand.steps.ts @@ -13,13 +13,13 @@ import type { GitLog } from '../git/models/log'; import type { GitBranchReference, GitRevisionReference, GitTagReference } from '../git/models/reference'; import { GitReference, GitRevision } from '../git/models/reference'; import { GitRemote } from '../git/models/remote'; +import { RemoteResourceType } from '../git/models/remoteResource'; import { Repository } from '../git/models/repository'; import type { GitStash } from '../git/models/stash'; import type { GitStatus } from '../git/models/status'; import type { GitTag, TagSortOptions } from '../git/models/tag'; import { sortTags } from '../git/models/tag'; import type { GitWorktree } from '../git/models/worktree'; -import { RemoteResourceType } from '../git/remotes/provider'; import { CommitApplyFileChangesCommandQuickPickItem, CommitBrowseRepositoryFromHereCommandQuickPickItem, diff --git a/src/commands/remoteProviders.ts b/src/commands/remoteProviders.ts index bcde509..f2d2be8 100644 --- a/src/commands/remoteProviders.ts +++ b/src/commands/remoteProviders.ts @@ -3,7 +3,7 @@ import type { Container } from '../container'; import type { GitCommit } from '../git/models/commit'; import { GitRemote } from '../git/models/remote'; import type { Repository } from '../git/models/repository'; -import type { RichRemoteProvider } from '../git/remotes/provider'; +import type { RichRemoteProvider } from '../git/remotes/richRemoteProvider'; import { RepositoryPicker } from '../quickpicks/repositoryPicker'; import { command } from '../system/command'; import { first } from '../system/iterable'; diff --git a/src/env/node/git/localGitProvider.ts b/src/env/node/git/localGitProvider.ts index 4511525..4df9ca4 100644 --- a/src/env/node/git/localGitProvider.ts +++ b/src/env/node/git/localGitProvider.ts @@ -71,6 +71,7 @@ import type { GitBranchReference } from '../../../git/models/reference'; import { GitReference, GitRevision } from '../../../git/models/reference'; import type { GitReflog } from '../../../git/models/reflog'; import { getRemoteIconUri, GitRemote } from '../../../git/models/remote'; +import { RemoteResourceType } from '../../../git/models/remoteResource'; import type { RepositoryChangeEvent } from '../../../git/models/repository'; import { Repository, RepositoryChange, RepositoryChangeComparisonMode } from '../../../git/models/repository'; import type { GitStash } from '../../../git/models/stash'; @@ -92,10 +93,10 @@ import { GitStatusParser } from '../../../git/parsers/statusParser'; 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 { getRemoteProviderMatcher, loadRemoteProviders } from '../../../git/remotes/factory'; -import type { RemoteProvider, RichRemoteProvider } from '../../../git/remotes/provider'; -import { RemoteResourceType } from '../../../git/remotes/provider'; +import type { RemoteProvider } from '../../../git/remotes/remoteProvider'; +import type { RemoteProviders } from '../../../git/remotes/remoteProviders'; +import { getRemoteProviderMatcher, loadRemoteProviders } from '../../../git/remotes/remoteProviders'; +import type { RichRemoteProvider } from '../../../git/remotes/richRemoteProvider'; import { SearchPattern } from '../../../git/search'; import { Logger } from '../../../logger'; import type { LogScope } from '../../../logger'; diff --git a/src/git/formatters/commitFormatter.ts b/src/git/formatters/commitFormatter.ts index fbee34d..706c9ec 100644 --- a/src/git/formatters/commitFormatter.ts +++ b/src/git/formatters/commitFormatter.ts @@ -31,7 +31,7 @@ import type { IssueOrPullRequest } from '../models/issue'; import { PullRequest } from '../models/pullRequest'; import { GitRevision } from '../models/reference'; import { GitRemote } from '../models/remote'; -import type { RemoteProvider } from '../remotes/provider'; +import type { RemoteProvider } from '../remotes/remoteProvider'; import type { FormatOptions } from './formatter'; import { Formatter } from './formatter'; diff --git a/src/git/gitProvider.ts b/src/git/gitProvider.ts index 549a01d..7cac3ae 100644 --- a/src/git/gitProvider.ts +++ b/src/git/gitProvider.ts @@ -23,8 +23,9 @@ import type { GitTag, TagSortOptions } from './models/tag'; import type { GitTreeEntry } from './models/tree'; 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 type { RemoteProvider } from './remotes/remoteProvider'; +import type { RemoteProviders } from './remotes/remoteProviders'; +import type { RichRemoteProvider } from './remotes/richRemoteProvider'; import type { SearchPattern } from './search'; export const enum GitProviderId { diff --git a/src/git/gitProviderService.ts b/src/git/gitProviderService.ts index 0dc3830..8abc52a 100644 --- a/src/git/gitProviderService.ts +++ b/src/git/gitProviderService.ts @@ -18,6 +18,7 @@ import { setContext } from '../context'; import { AccessDeniedError, ProviderNotFoundError } from '../errors'; import type { FeatureAccess, Features } from '../features'; import { PlusFeatures } from '../features'; +import type { RemoteProvider } from '../git/remotes/remoteProvider'; import { Logger } from '../logger'; import type { SubscriptionChangeEvent } from '../plus/subscription/subscriptionService'; import type { RepoComparisonKey } from '../repositories'; @@ -66,9 +67,9 @@ import type { GitTag, TagSortOptions } from './models/tag'; import type { GitTreeEntry } from './models/tree'; 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 { RichRemoteProviders } from './remotes/remoteProviderConnections'; +import type { RemoteProviders } from './remotes/remoteProviders'; +import type { RichRemoteProvider } from './remotes/richRemoteProvider'; import type { SearchPattern } from './search'; const maxDefaultBranchWeight = 100; @@ -1590,7 +1591,7 @@ export class GitProviderService implements Disposable { let provider; if (GitRemote.is(remoteOrProvider)) { ({ provider } = remoteOrProvider); - if (!provider?.hasRichApi()) return undefined; + if (!provider?.hasRichIntegration()) return undefined; } else { provider = remoteOrProvider; } @@ -1645,7 +1646,7 @@ export class GitProviderService implements Disposable { let provider; if (GitRemote.is(remoteOrProvider)) { ({ provider } = remoteOrProvider); - if (!provider?.hasRichApi()) return undefined; + if (!provider?.hasRichIntegration()) return undefined; } else { provider = remoteOrProvider; } @@ -1880,7 +1881,11 @@ export class GitProviderService implements Disposable { ? repository.getRemotes(options) : this.getRemotes(repoPath, options)); - return remotes.filter(r => r.provider != null) as GitRemote[]; + return remotes.filter( + ( + r: GitRemote, + ): r is GitRemote => r.provider != null, + ); } getBestRepository(): Repository | undefined; diff --git a/src/git/models/branch.ts b/src/git/models/branch.ts index e0f5204..11d8a1e 100644 --- a/src/git/models/branch.ts +++ b/src/git/models/branch.ts @@ -5,7 +5,8 @@ import { debug } from '../../system/decorators/log'; import { memoize } from '../../system/decorators/memoize'; import { cancellable } from '../../system/promise'; import { sortCompare } from '../../system/string'; -import type { RemoteProvider, RichRemoteProvider } from '../remotes/provider'; +import type { RemoteProvider } from '../remotes/remoteProvider'; +import type { RichRemoteProvider } from '../remotes/richRemoteProvider'; import type { PullRequest, PullRequestState } from './pullRequest'; import type { GitBranchReference, GitReference } from './reference'; import { GitRevision } from './reference'; diff --git a/src/git/models/commit.ts b/src/git/models/commit.ts index f8cb0db..535b6c6 100644 --- a/src/git/models/commit.ts +++ b/src/git/models/commit.ts @@ -11,7 +11,7 @@ import { cancellable } from '../../system/promise'; import { pad, pluralize } from '../../system/string'; import type { PreviousLineComparisonUrisResult } from '../gitProvider'; import { GitUri } from '../gitUri'; -import type { RichRemoteProvider } from '../remotes/provider'; +import type { RichRemoteProvider } from '../remotes/richRemoteProvider'; import type { GitFile } from './file'; import { GitFileChange, GitFileWorkingTreeStatus } from './file'; import type { PullRequest } from './pullRequest'; diff --git a/src/git/models/remote.ts b/src/git/models/remote.ts index 8811f88..a40adbf 100644 --- a/src/git/models/remote.ts +++ b/src/git/models/remote.ts @@ -3,8 +3,8 @@ import { Uri, window } from 'vscode'; import { Container } from '../../container'; import { sortCompare } from '../../system/string'; import { isLightTheme } from '../../system/utils'; -import type { RemoteProvider } from '../remotes/provider'; -import { RichRemoteProvider } from '../remotes/provider'; +import type { RemoteProvider } from '../remotes/remoteProvider'; +import type { RichRemoteProvider } from '../remotes/richRemoteProvider'; export const enum GitRemoteType { Fetch = 'fetch', @@ -82,7 +82,7 @@ export class GitRemote { - return RichRemoteProvider.is(this.provider); + return this.provider?.hasRichIntegration() ?? false; } async setAsDefault(value: boolean = true) { diff --git a/src/git/models/remoteResource.ts b/src/git/models/remoteResource.ts new file mode 100644 index 0000000..c3149d1 --- /dev/null +++ b/src/git/models/remoteResource.ts @@ -0,0 +1,83 @@ +import type { Range } from 'vscode'; +import type { GitCommit } from './commit'; + +export const enum RemoteResourceType { + Branch = 'branch', + Branches = 'branches', + Commit = 'commit', + Comparison = 'comparison', + CreatePullRequest = 'createPullRequest', + File = 'file', + Repo = 'repo', + Revision = 'revision', +} + +export type RemoteResource = + | { + type: RemoteResourceType.Branch; + branch: string; + } + | { + type: RemoteResourceType.Branches; + } + | { + type: RemoteResourceType.Commit; + sha: string; + } + | { + type: RemoteResourceType.Comparison; + base: string; + compare: string; + notation?: '..' | '...'; + } + | { + type: RemoteResourceType.CreatePullRequest; + base: { + branch?: string; + remote: { path: string; url: string }; + }; + compare: { + branch: string; + remote: { path: string; url: string }; + }; + } + | { + type: RemoteResourceType.File; + branchOrTag?: string; + fileName: string; + range?: Range; + } + | { + type: RemoteResourceType.Repo; + } + | { + type: RemoteResourceType.Revision; + branchOrTag?: string; + commit?: GitCommit; + fileName: string; + range?: Range; + sha?: string; + }; + +export function getNameFromRemoteResource(resource: RemoteResource) { + switch (resource.type) { + case RemoteResourceType.Branch: + return 'Branch'; + case RemoteResourceType.Branches: + return 'Branches'; + case RemoteResourceType.Commit: + return 'Commit'; + case RemoteResourceType.Comparison: + return 'Comparison'; + case RemoteResourceType.CreatePullRequest: + return 'Create Pull Request'; + case RemoteResourceType.File: + return 'File'; + case RemoteResourceType.Repo: + return 'Repository'; + case RemoteResourceType.Revision: + return 'File'; + default: + return ''; + } +} diff --git a/src/git/models/repository.ts b/src/git/models/repository.ts index a87220b..b40eb66 100644 --- a/src/git/models/repository.ts +++ b/src/git/models/repository.ts @@ -20,9 +20,9 @@ import { updateRecordValue } from '../../system/object'; import { basename, normalizePath } from '../../system/path'; import { runGitCommandInTerminal } from '../../terminal'; import type { GitProviderDescriptor } from '../gitProvider'; -import type { RemoteProviders } from '../remotes/factory'; -import { loadRemoteProviders } from '../remotes/factory'; -import { RichRemoteProvider } from '../remotes/provider'; +import { loadRemoteProviders } from '../remotes/remoteProviders'; +import type { RemoteProviders } from '../remotes/remoteProviders'; +import type { RichRemoteProvider } from '../remotes/richRemoteProvider'; import type { SearchPattern } from '../search'; import type { BranchSortOptions, GitBranch } from './branch'; import { getBranchNameWithoutRemote, getRemoteNameFromBranchName } from './branch'; @@ -598,7 +598,7 @@ export class Repository implements Disposable { this._remotesDisposable = Disposable.from( ...filterMap(await remotes, r => { - if (!RichRemoteProvider.is(r.provider)) return undefined; + if (!r.provider?.hasRichIntegration()) return undefined; return r.provider.onDidChange(() => this.fireChange(RepositoryChange.RemoteProviders)); }), diff --git a/src/git/parsers/remoteParser.ts b/src/git/parsers/remoteParser.ts index d80e063..789ba79 100644 --- a/src/git/parsers/remoteParser.ts +++ b/src/git/parsers/remoteParser.ts @@ -1,7 +1,7 @@ import { debug } from '../../system/decorators/log'; import type { GitRemoteType } from '../models/remote'; import { GitRemote } from '../models/remote'; -import type { RemoteProvider } from '../remotes/provider'; +import type { getRemoteProviderMatcher } from '../remotes/remoteProviders'; const emptyStr = ''; @@ -53,7 +53,7 @@ export class GitRemoteParser { static parse( data: string, repoPath: string, - providerFactory: (url: string, domain: string, path: string) => RemoteProvider | undefined, + remoteProviderMatcher: ReturnType, ): GitRemote[] | undefined { if (!data) return undefined; @@ -86,7 +86,7 @@ export class GitRemoteParser { uniqueness = `${domain ? `${domain}/` : ''}${path}`; remote = groups[uniqueness]; if (remote === undefined) { - const provider = providerFactory(url, domain, path); + const provider = remoteProviderMatcher(url, domain, path); remote = new GitRemote( repoPath, diff --git a/src/git/remotes/azure-devops.ts b/src/git/remotes/azure-devops.ts index 6ee17ed..3861e9e 100644 --- a/src/git/remotes/azure-devops.ts +++ b/src/git/remotes/azure-devops.ts @@ -3,7 +3,7 @@ import type { DynamicAutolinkReference } from '../../annotations/autolinks'; import type { AutolinkReference } from '../../config'; import { AutolinkType } from '../../config'; import type { Repository } from '../models/repository'; -import { RemoteProvider } from './provider'; +import { RemoteProvider } from './remoteProvider'; const gitRegex = /\/_git\/?/i; const legacyDefaultCollectionRegex = /^DefaultCollection\//i; diff --git a/src/git/remotes/bitbucket-server.ts b/src/git/remotes/bitbucket-server.ts index 2f79ca3..cf634ea 100644 --- a/src/git/remotes/bitbucket-server.ts +++ b/src/git/remotes/bitbucket-server.ts @@ -4,7 +4,7 @@ import type { AutolinkReference } from '../../config'; import { AutolinkType } from '../../config'; import { GitRevision } from '../models/reference'; import type { Repository } from '../models/repository'; -import { RemoteProvider } from './provider'; +import { RemoteProvider } from './remoteProvider'; const fileRegex = /^\/([^/]+)\/([^/]+?)\/src(.+)$/i; const rangeRegex = /^lines-(\d+)(?::(\d+))?$/; diff --git a/src/git/remotes/bitbucket.ts b/src/git/remotes/bitbucket.ts index 76be155..e0ade3c 100644 --- a/src/git/remotes/bitbucket.ts +++ b/src/git/remotes/bitbucket.ts @@ -4,7 +4,7 @@ import type { AutolinkReference } from '../../config'; import { AutolinkType } from '../../config'; import { GitRevision } from '../models/reference'; import type { Repository } from '../models/repository'; -import { RemoteProvider } from './provider'; +import { RemoteProvider } from './remoteProvider'; const fileRegex = /^\/([^/]+)\/([^/]+?)\/src(.+)$/i; const rangeRegex = /^lines-(\d+)(?::(\d+))?$/; diff --git a/src/git/remotes/custom.ts b/src/git/remotes/custom.ts index aa97dab..15c20fe 100644 --- a/src/git/remotes/custom.ts +++ b/src/git/remotes/custom.ts @@ -2,7 +2,7 @@ import type { Range, Uri } from 'vscode'; import type { RemotesUrlsConfig } from '../../configuration'; import { interpolate } from '../../system/string'; import type { Repository } from '../models/repository'; -import { RemoteProvider } from './provider'; +import { RemoteProvider } from './remoteProvider'; export class CustomRemote extends RemoteProvider { private readonly urls: RemotesUrlsConfig; diff --git a/src/git/remotes/gerrit.ts b/src/git/remotes/gerrit.ts index 771a57e..e567cfd 100644 --- a/src/git/remotes/gerrit.ts +++ b/src/git/remotes/gerrit.ts @@ -3,7 +3,7 @@ import type { DynamicAutolinkReference } from '../../annotations/autolinks'; import type { AutolinkReference } from '../../config'; import { GitRevision } from '../models/reference'; import type { Repository } from '../models/repository'; -import { RemoteProvider } from './provider'; +import { RemoteProvider } from './remoteProvider'; const fileRegex = /^\/([^/]+)\/\+(.+)$/i; const rangeRegex = /^(\d+)$/; diff --git a/src/git/remotes/gitea.ts b/src/git/remotes/gitea.ts index 1f2d813..def06f8 100644 --- a/src/git/remotes/gitea.ts +++ b/src/git/remotes/gitea.ts @@ -4,7 +4,7 @@ import type { AutolinkReference } from '../../config'; import { AutolinkType } from '../../config'; import { GitRevision } from '../models/reference'; import type { Repository } from '../models/repository'; -import { RemoteProvider } from './provider'; +import { RemoteProvider } from './remoteProvider'; const fileRegex = /^\/([^/]+)\/([^/]+?)\/src(.+)$/i; const rangeRegex = /^L(\d+)(?:-L(\d+))?$/; diff --git a/src/git/remotes/github.ts b/src/git/remotes/github.ts index a4b2b61..a331642 100644 --- a/src/git/remotes/github.ts +++ b/src/git/remotes/github.ts @@ -13,7 +13,7 @@ import type { IssueOrPullRequest } from '../models/issue'; import type { PullRequest, PullRequestState } from '../models/pullRequest'; import { GitRevision } from '../models/reference'; import type { Repository } from '../models/repository'; -import { RichRemoteProvider } from './provider'; +import { RichRemoteProvider } from './richRemoteProvider'; const autolinkFullIssuesRegex = /\b(?[^/\s]+\/[^/\s]+)#(?[0-9]+)\b(?!]\()/g; const fileRegex = /^\/([^/]+)\/([^/]+?)\/blob(.+)$/i; diff --git a/src/git/remotes/gitlab.ts b/src/git/remotes/gitlab.ts index 743906d..3bdc59d 100644 --- a/src/git/remotes/gitlab.ts +++ b/src/git/remotes/gitlab.ts @@ -17,7 +17,7 @@ import type { IssueOrPullRequest } from '../models/issue'; import type { PullRequest, PullRequestState } from '../models/pullRequest'; import { GitRevision } from '../models/reference'; import type { Repository } from '../models/repository'; -import { RichRemoteProvider } from './provider'; +import { RichRemoteProvider } from './richRemoteProvider'; const autolinkFullIssuesRegex = /\b(?[^/\s]+\/[^/\s]+)#(?[0-9]+)\b(?!]\()/g; const autolinkFullMergeRequestsRegex = /\b(?[^/\s]+\/[^/\s]+)!(?[0-9]+)\b(?!]\()/g; diff --git a/src/git/remotes/remoteProvider.ts b/src/git/remotes/remoteProvider.ts new file mode 100644 index 0000000..c1416dd --- /dev/null +++ b/src/git/remotes/remoteProvider.ts @@ -0,0 +1,157 @@ +import { env, Uri } from 'vscode'; +import type { Range } from 'vscode'; +import type { DynamicAutolinkReference } from '../../annotations/autolinks'; +import type { AutolinkReference } from '../../config'; +import { encodeUrl } from '../../system/encoding'; +import type { RemoteProviderReference } from '../models/remoteProvider'; +import type { RemoteResource } from '../models/remoteResource'; +import { RemoteResourceType } from '../models/remoteResource'; +import type { Repository } from '../models/repository'; +import type { RichRemoteProvider } from './richRemoteProvider'; + +export abstract class RemoteProvider implements RemoteProviderReference { + readonly type: 'simple' | 'rich' = 'simple'; + protected readonly _name: string | undefined; + + constructor( + public readonly domain: string, + public readonly path: string, + public readonly protocol: string = 'https', + name?: string, + public readonly custom: boolean = false, + ) { + this._name = name; + } + + get autolinks(): (AutolinkReference | DynamicAutolinkReference)[] { + return []; + } + + get avatarUri(): Uri | undefined { + return undefined; + } + + get displayPath(): string { + return this.path; + } + + get icon(): string { + return 'remote'; + } + + abstract get id(): string; + abstract get name(): string; + + async copy(resource: RemoteResource): Promise { + const url = this.url(resource); + if (url == null) { + return; + } + + await env.clipboard.writeText(url); + } + + hasRichIntegration(): this is RichRemoteProvider { + return this.type === 'rich'; + } + + abstract getLocalInfoFromRemoteUri( + repository: Repository, + uri: Uri, + options?: { validate?: boolean }, + ): Promise<{ uri: Uri; startLine?: number; endLine?: number } | undefined>; + + open(resource: RemoteResource): Promise { + return this.openUrl(this.url(resource)); + } + + url(resource: RemoteResource): string | undefined { + switch (resource.type) { + case RemoteResourceType.Branch: + return this.getUrlForBranch(resource.branch); + case RemoteResourceType.Branches: + return this.getUrlForBranches(); + case RemoteResourceType.Commit: + return this.getUrlForCommit(resource.sha); + case RemoteResourceType.Comparison: { + return this.getUrlForComparison?.(resource.base, resource.compare, resource.notation ?? '...'); + } + case RemoteResourceType.CreatePullRequest: { + return this.getUrlForCreatePullRequest?.(resource.base, resource.compare); + } + case RemoteResourceType.File: + return this.getUrlForFile( + resource.fileName, + resource.branchOrTag != null ? resource.branchOrTag : undefined, + undefined, + resource.range, + ); + case RemoteResourceType.Repo: + return this.getUrlForRepository(); + case RemoteResourceType.Revision: + return this.getUrlForFile( + resource.fileName, + resource.branchOrTag != null ? resource.branchOrTag : undefined, + resource.sha != null ? resource.sha : undefined, + resource.range, + ); + default: + return undefined; + } + } + + protected get baseUrl(): string { + return `${this.protocol}://${this.domain}/${this.path}`; + } + + protected formatName(name: string) { + if (this._name != null) { + return this._name; + } + 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 getUrlForBranch(branch: string): string; + + protected abstract getUrlForBranches(): string; + + protected abstract getUrlForCommit(sha: string): string; + + protected getUrlForComparison?(base: string, compare: string, notation: '..' | '...'): string | undefined; + + protected getUrlForCreatePullRequest?( + base: { branch?: string; remote: { path: string; url: string } }, + compare: { branch: string; remote: { path: string; url: string } }, + ): string | undefined; + + protected abstract getUrlForFile(fileName: string, branch?: string, sha?: string, range?: Range): string; + + protected getUrlForRepository(): string { + return this.baseUrl; + } + + private async openUrl(url?: string): Promise { + if (url == null) { + return undefined; + } + + const uri = Uri.parse(url); + // Pass a string to openExternal to avoid double encoding issues: https://github.com/microsoft/vscode/issues/85930 + if (uri.path.includes('#')) { + // .d.ts currently says it only supports a Uri, but it actually accepts a string too + return (env.openExternal as unknown as (target: string) => Thenable)(uri.toString()); + } + return env.openExternal(uri); + } + + protected encodeUrl(url: string): string; + protected encodeUrl(url: string | undefined): string | undefined; + protected encodeUrl(url: string | undefined): string | undefined { + return encodeUrl(url)?.replace(/#/g, '%23'); + } +} diff --git a/src/git/remotes/factory.ts b/src/git/remotes/remoteProviders.ts similarity index 99% rename from src/git/remotes/factory.ts rename to src/git/remotes/remoteProviders.ts index 76cedac..10565ab 100644 --- a/src/git/remotes/factory.ts +++ b/src/git/remotes/remoteProviders.ts @@ -11,7 +11,7 @@ import { GiteaRemote } from './gitea'; import { GitHubRemote } from './github'; import { GitLabRemote } from './gitlab'; import { GoogleSourceRemote } from './google-source'; -import type { RemoteProvider } from './provider'; +import type { RemoteProvider } from './remoteProvider'; export type RemoteProviders = { custom: boolean; diff --git a/src/git/remotes/provider.ts b/src/git/remotes/richRemoteProvider.ts similarity index 66% rename from src/git/remotes/provider.ts rename to src/git/remotes/richRemoteProvider.ts index 88abf2b..97c9535 100644 --- a/src/git/remotes/provider.ts +++ b/src/git/remotes/richRemoteProvider.ts @@ -1,9 +1,7 @@ -import type { AuthenticationSession, AuthenticationSessionsChangeEvent, Event, MessageItem, Range } from 'vscode'; -import { authentication, env, EventEmitter, Uri, window } from 'vscode'; +import { authentication, EventEmitter, window } from 'vscode'; +import type { AuthenticationSession, AuthenticationSessionsChangeEvent, Event, MessageItem } from 'vscode'; import { wrapForForcedInsecureSSL } from '@env/fetch'; import { isWeb } from '@env/platform'; -import type { DynamicAutolinkReference } from '../../annotations/autolinks'; -import type { AutolinkReference } from '../../config'; import { configuration } from '../../configuration'; import type { Container } from '../../container'; import { AuthenticationError, ProviderRequestClientError } from '../../errors'; @@ -12,248 +10,19 @@ import { showIntegrationDisconnectedTooManyFailedRequestsWarningMessage } from ' import type { IntegrationAuthenticationSessionDescriptor } from '../../plus/integrationAuthentication'; import { gate } from '../../system/decorators/gate'; import { debug, getLogScope, log } from '../../system/decorators/log'; -import { encodeUrl } from '../../system/encoding'; import { isPromise } from '../../system/promise'; import type { Account } from '../models/author'; -import type { GitCommit } from '../models/commit'; import type { DefaultBranch } from '../models/defaultBranch'; 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 { RemoteProvider } from './remoteProvider'; import { RichRemoteProviders } from './remoteProviderConnections'; -export const enum RemoteResourceType { - Branch = 'branch', - Branches = 'branches', - Commit = 'commit', - Comparison = 'comparison', - CreatePullRequest = 'createPullRequest', - File = 'file', - Repo = 'repo', - Revision = 'revision', -} - -export type RemoteResource = - | { - type: RemoteResourceType.Branch; - branch: string; - } - | { - type: RemoteResourceType.Branches; - } - | { - type: RemoteResourceType.Commit; - sha: string; - } - | { - type: RemoteResourceType.Comparison; - base: string; - compare: string; - notation?: '..' | '...'; - } - | { - type: RemoteResourceType.CreatePullRequest; - base: { - branch?: string; - remote: { path: string; url: string }; - }; - compare: { - branch: string; - remote: { path: string; url: string }; - }; - } - | { - type: RemoteResourceType.File; - branchOrTag?: string; - fileName: string; - range?: Range; - } - | { - type: RemoteResourceType.Repo; - } - | { - type: RemoteResourceType.Revision; - branchOrTag?: string; - commit?: GitCommit; - fileName: string; - range?: Range; - sha?: string; - }; - -export function getNameFromRemoteResource(resource: RemoteResource) { - switch (resource.type) { - case RemoteResourceType.Branch: - return 'Branch'; - case RemoteResourceType.Branches: - return 'Branches'; - case RemoteResourceType.Commit: - return 'Commit'; - case RemoteResourceType.Comparison: - return 'Comparison'; - case RemoteResourceType.CreatePullRequest: - return 'Create Pull Request'; - case RemoteResourceType.File: - return 'File'; - case RemoteResourceType.Repo: - return 'Repository'; - case RemoteResourceType.Revision: - return 'File'; - default: - return ''; - } -} - -export abstract class RemoteProvider implements RemoteProviderReference { - readonly type: 'simple' | 'rich' = 'simple'; - protected readonly _name: string | undefined; - - constructor( - public readonly domain: string, - public readonly path: string, - public readonly protocol: string = 'https', - name?: string, - public readonly custom: boolean = false, - ) { - this._name = name; - } - - get autolinks(): (AutolinkReference | DynamicAutolinkReference)[] { - return []; - } - - get avatarUri(): Uri | undefined { - return undefined; - } - - get displayPath(): string { - return this.path; - } - - get icon(): string { - return 'remote'; - } - - abstract get id(): string; - abstract get name(): string; - - async copy(resource: RemoteResource): Promise { - const url = this.url(resource); - if (url == null) return; - - await env.clipboard.writeText(url); - } - - hasRichApi(): this is RichRemoteProvider { - return RichRemoteProvider.is(this); - } - - abstract getLocalInfoFromRemoteUri( - repository: Repository, - uri: Uri, - options?: { validate?: boolean }, - ): Promise<{ uri: Uri; startLine?: number; endLine?: number } | undefined>; - - open(resource: RemoteResource): Promise { - return this.openUrl(this.url(resource)); - } - - url(resource: RemoteResource): string | undefined { - switch (resource.type) { - case RemoteResourceType.Branch: - return this.getUrlForBranch(resource.branch); - case RemoteResourceType.Branches: - return this.getUrlForBranches(); - case RemoteResourceType.Commit: - return this.getUrlForCommit(resource.sha); - case RemoteResourceType.Comparison: { - return this.getUrlForComparison?.(resource.base, resource.compare, resource.notation ?? '...'); - } - case RemoteResourceType.CreatePullRequest: { - return this.getUrlForCreatePullRequest?.(resource.base, resource.compare); - } - case RemoteResourceType.File: - return this.getUrlForFile( - resource.fileName, - resource.branchOrTag != null ? resource.branchOrTag : undefined, - undefined, - resource.range, - ); - case RemoteResourceType.Repo: - return this.getUrlForRepository(); - case RemoteResourceType.Revision: - return this.getUrlForFile( - resource.fileName, - resource.branchOrTag != null ? resource.branchOrTag : undefined, - resource.sha != null ? resource.sha : undefined, - resource.range, - ); - default: - return undefined; - } - } - - protected get baseUrl(): string { - return `${this.protocol}://${this.domain}/${this.path}`; - } - - protected formatName(name: string) { - if (this._name != null) return this._name; - 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 getUrlForBranch(branch: string): string; - - protected abstract getUrlForBranches(): string; - - protected abstract getUrlForCommit(sha: string): string; - - protected getUrlForComparison?(base: string, compare: string, notation: '..' | '...'): string | undefined; - - protected getUrlForCreatePullRequest?( - base: { branch?: string; remote: { path: string; url: string } }, - compare: { branch: string; remote: { path: string; url: string } }, - ): string | undefined; - - protected abstract getUrlForFile(fileName: string, branch?: string, sha?: string, range?: Range): string; - - protected getUrlForRepository(): string { - return this.baseUrl; - } - - private async openUrl(url?: string): Promise { - if (url == null) return undefined; - - const uri = Uri.parse(url); - // Pass a string to openExternal to avoid double encoding issues: https://github.com/microsoft/vscode/issues/85930 - if (uri.path.includes('#')) { - // .d.ts currently says it only supports a Uri, but it actually accepts a string too - return (env.openExternal as unknown as (target: string) => Thenable)(uri.toString()); - } - return env.openExternal(uri); - } - - protected encodeUrl(url: string): string; - protected encodeUrl(url: string | undefined): string | undefined; - protected encodeUrl(url: string | undefined): string | undefined { - return encodeUrl(url)?.replace(/#/g, '%23'); - } -} - // TODO@eamodio revisit how once authenticated, all remotes are always connected, even after a restart export abstract class RichRemoteProvider extends RemoteProvider { override readonly type: 'simple' | 'rich' = 'rich'; - static is(provider: RemoteProvider | undefined): provider is RichRemoteProvider { - return provider?.type === 'rich'; - } - private readonly _onDidChange = new EventEmitter(); get onDidChange(): Event { return this._onDidChange.event; @@ -269,7 +38,7 @@ export abstract class RichRemoteProvider extends RemoteProvider { ) { super(domain, path, protocol, name, custom); - this.container.context.subscriptions.push( + container.context.subscriptions.push( configuration.onDidChange(e => { if (configuration.changed(e, 'remotes')) { this._ignoreSSLErrors.clear(); @@ -304,9 +73,7 @@ export abstract class RichRemoteProvider extends RemoteProvider { } get maybeConnected(): boolean | undefined { - if (this._session === undefined) return undefined; - - return this._session !== null; + return this._session === undefined ? undefined : this._session !== null; } protected _session: AuthenticationSession | null | undefined; diff --git a/src/plus/github/github.ts b/src/plus/github/github.ts index f5a9e2d..02231c0 100644 --- a/src/plus/github/github.ts +++ b/src/plus/github/github.ts @@ -25,7 +25,7 @@ import type { PullRequest } from '../../git/models/pullRequest'; import { GitRevision } from '../../git/models/reference'; import type { GitUser } from '../../git/models/user'; import { getGitHubNoReplyAddressParts } from '../../git/remotes/github'; -import type { RichRemoteProvider } from '../../git/remotes/provider'; +import type { RichRemoteProvider } from '../../git/remotes/richRemoteProvider'; import type { LogScope } from '../../logger'; import { Logger, LogLevel } from '../../logger'; import { diff --git a/src/plus/github/githubGitProvider.ts b/src/plus/github/githubGitProvider.ts index 464f7cd..a62e034 100644 --- a/src/plus/github/githubGitProvider.ts +++ b/src/plus/github/githubGitProvider.ts @@ -60,9 +60,10 @@ import { GitTag, sortTags } from '../../git/models/tag'; 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 { getRemoteProviderMatcher, loadRemoteProviders } from '../../git/remotes/factory'; -import type { RemoteProvider, RichRemoteProvider } from '../../git/remotes/provider'; +import type { RemoteProvider } from '../../git/remotes/remoteProvider'; +import type { RemoteProviders } from '../../git/remotes/remoteProviders'; +import { getRemoteProviderMatcher, loadRemoteProviders } from '../../git/remotes/remoteProviders'; +import type { RichRemoteProvider } from '../../git/remotes/richRemoteProvider'; import { SearchPattern } from '../../git/search'; import type { LogScope } from '../../logger'; import { Logger } from '../../logger'; diff --git a/src/plus/github/models.ts b/src/plus/github/models.ts index 1c2f3b2..fd32e3d 100644 --- a/src/plus/github/models.ts +++ b/src/plus/github/models.ts @@ -2,7 +2,7 @@ import type { Endpoints } from '@octokit/types'; import { GitFileIndexStatus } from '../../git/models/file'; import type { IssueOrPullRequestType } from '../../git/models/issue'; import { PullRequest, PullRequestState } from '../../git/models/pullRequest'; -import type { RichRemoteProvider } from '../../git/remotes/provider'; +import type { RichRemoteProvider } from '../../git/remotes/richRemoteProvider'; export interface GitHubBlame { ranges: GitHubBlameRange[]; diff --git a/src/plus/gitlab/gitlab.ts b/src/plus/gitlab/gitlab.ts index 9eca3ba..1dfbef6 100644 --- a/src/plus/gitlab/gitlab.ts +++ b/src/plus/gitlab/gitlab.ts @@ -18,7 +18,7 @@ import type { DefaultBranch } from '../../git/models/defaultBranch'; import type { IssueOrPullRequest } from '../../git/models/issue'; import { IssueOrPullRequestType } from '../../git/models/issue'; import { PullRequest } from '../../git/models/pullRequest'; -import type { RichRemoteProvider } from '../../git/remotes/provider'; +import type { RichRemoteProvider } from '../../git/remotes/richRemoteProvider'; import type { LogScope } from '../../logger'; import { Logger, LogLevel } from '../../logger'; import { diff --git a/src/plus/gitlab/models.ts b/src/plus/gitlab/models.ts index ed1a43a..1f170b8 100644 --- a/src/plus/gitlab/models.ts +++ b/src/plus/gitlab/models.ts @@ -1,5 +1,5 @@ import { PullRequest, PullRequestState } from '../../git/models/pullRequest'; -import type { RichRemoteProvider } from '../../git/remotes/provider'; +import type { RichRemoteProvider } from '../../git/remotes/richRemoteProvider'; export interface GitLabUser { id: number; diff --git a/src/quickpicks/remoteProviderPicker.ts b/src/quickpicks/remoteProviderPicker.ts index 3687a5d..5c2830b 100644 --- a/src/quickpicks/remoteProviderPicker.ts +++ b/src/quickpicks/remoteProviderPicker.ts @@ -5,8 +5,9 @@ import { Commands, GlyphChars } from '../constants'; import { Container } from '../container'; import { getBranchNameWithoutRemote, getRemoteNameFromBranchName } from '../git/models/branch'; import { GitRemote } from '../git/models/remote'; -import type { RemoteProvider, RemoteResource } from '../git/remotes/provider'; -import { getNameFromRemoteResource, RemoteResourceType } from '../git/remotes/provider'; +import type { RemoteResource } from '../git/models/remoteResource'; +import { getNameFromRemoteResource, RemoteResourceType } from '../git/models/remoteResource'; +import type { RemoteProvider } from '../git/remotes/remoteProvider'; import type { Keys } from '../keyboard'; import { CommandQuickPickItem } from '../quickpicks/items/common'; import { getSettledValue } from '../system/promise'; diff --git a/src/views/nodes/commitNode.ts b/src/views/nodes/commitNode.ts index f522468..62e370b 100644 --- a/src/views/nodes/commitNode.ts +++ b/src/views/nodes/commitNode.ts @@ -9,7 +9,7 @@ import type { GitCommit } from '../../git/models/commit'; import type { PullRequest } from '../../git/models/pullRequest'; import type { GitRevisionReference } from '../../git/models/reference'; import type { GitRemote } from '../../git/models/remote'; -import type { RichRemoteProvider } from '../../git/remotes/provider'; +import type { RichRemoteProvider } from '../../git/remotes/richRemoteProvider'; import { makeHierarchical } from '../../system/array'; import { gate } from '../../system/decorators/gate'; import { joinPaths, normalizePath } from '../../system/path'; diff --git a/src/views/nodes/remoteNode.ts b/src/views/nodes/remoteNode.ts index 6e3af80..6db6e02 100644 --- a/src/views/nodes/remoteNode.ts +++ b/src/views/nodes/remoteNode.ts @@ -125,7 +125,7 @@ export class RemoteNode extends ViewNode { light: this.view.container.context.asAbsolutePath(`images/light/icon-${provider.icon}.svg`), }; - if (provider.hasRichApi()) { + if (provider.hasRichIntegration()) { const connected = provider.maybeConnected ?? (await provider.isConnected()); item.contextValue = `${ContextValues.Remote}${connected ? '+connected' : '+disconnected'}`;