From ebf5d045ac23116ea6d3533017921866d25a6222 Mon Sep 17 00:00:00 2001 From: Eric Amodio <eamodio@gmail.com> Date: Sat, 19 Feb 2022 00:18:38 -0500 Subject: [PATCH] Blocks worktree access (if needed) from repos view Ensures supports works for worktrees and stashes Fixes create worktree from repositories view --- src/env/node/git/localGitProvider.ts | 12 +++++++++--- src/git/gitProvider.ts | 1 + src/git/models/repository.ts | 13 ++++++++++++- src/premium/github/githubGitProvider.ts | 20 ++++---------------- src/views/nodes/repositoryNode.ts | 5 +++-- src/views/nodes/worktreesNode.ts | 17 +++++++++++++++-- src/views/viewCommands.ts | 8 ++++++-- 7 files changed, 50 insertions(+), 26 deletions(-) diff --git a/src/env/node/git/localGitProvider.ts b/src/env/node/git/localGitProvider.ts index 95ee1be..2b9239a 100644 --- a/src/env/node/git/localGitProvider.ts +++ b/src/env/node/git/localGitProvider.ts @@ -426,12 +426,18 @@ export class LocalGitProvider implements GitProvider, Disposable { } private _supportedFeatures = new Map<Features, boolean>(); - // eslint-disable-next-line @typescript-eslint/require-await async supports(feature: Features): Promise<boolean> { - const supported = this._supportedFeatures.get(feature); + let supported = this._supportedFeatures.get(feature); if (supported != null) return supported; - return false; + switch (feature) { + case Features.Worktrees: + supported = await this.git.isAtLeastVersion('2.17.0'); + this._supportedFeatures.set(feature, supported); + return supported; + default: + return true; + } } async visibility(repoPath: string): Promise<RepositoryVisibility> { diff --git a/src/git/gitProvider.ts b/src/git/gitProvider.ts index 642b4a9..0aa859b 100644 --- a/src/git/gitProvider.ts +++ b/src/git/gitProvider.ts @@ -91,6 +91,7 @@ export interface RepositoryOpenEvent { } export const enum Features { + Stashes = 'stashes', Worktrees = 'worktrees', } diff --git a/src/git/models/repository.ts b/src/git/models/repository.ts index d097311..0f8226a 100644 --- a/src/git/models/repository.ts +++ b/src/git/models/repository.ts @@ -27,7 +27,8 @@ import { debounce } from '../../system/function'; import { filter, join, some } from '../../system/iterable'; import { basename, normalizePath } from '../../system/path'; import { runGitCommandInTerminal } from '../../terminal'; -import { GitProviderDescriptor } from '../gitProvider'; +import { Features, GitProviderDescriptor, PremiumFeatures } from '../gitProvider'; +import { FeatureAccess } from '../gitProviderService'; import { RemoteProviderFactory, RemoteProviders } from '../remotes/factory'; import { RichRemoteProvider } from '../remotes/provider'; import { SearchPattern } from '../search'; @@ -394,6 +395,16 @@ export class Repository implements Disposable { } @log() + access(feature?: PremiumFeatures): Promise<FeatureAccess> { + return this.container.git.access(feature, this.uri); + } + + @log() + supports(feature: Features): Promise<boolean> { + return this.container.git.supports(this.uri, feature); + } + + @log() branch(...args: string[]) { this.runTerminalCommand('branch', ...args); } diff --git a/src/premium/github/githubGitProvider.ts b/src/premium/github/githubGitProvider.ts index 71140e2..9c52d4c 100644 --- a/src/premium/github/githubGitProvider.ts +++ b/src/premium/github/githubGitProvider.ts @@ -212,10 +212,10 @@ export class GitHubGitProvider implements GitProvider, Disposable { return allowed; } - private _supportedFeatures = new Map<Features, boolean>(); - async supports(feature: Features): Promise<boolean> { - const supported = this._supportedFeatures.get(feature); - if (supported != null) return supported; + // private _supportedFeatures = new Map<Features, boolean>(); + async supports(_feature: Features): Promise<boolean> { + // const supported = this._supportedFeatures.get(feature); + // if (supported != null) return supported; return false; } @@ -229,18 +229,6 @@ export class GitHubGitProvider implements GitProvider, Disposable { return this.getRemoteVisibility(origin); } - const upstream = remotes.find(r => r.name === 'upstream'); - if (upstream != null) { - return this.getRemoteVisibility(upstream); - } - - const results = await Promise.allSettled(remotes.map(r => this.getRemoteVisibility(r))); - for (const result of results) { - if (result.status !== 'fulfilled') continue; - - if (result.value === RepositoryVisibility.Public) return RepositoryVisibility.Public; - } - return RepositoryVisibility.Private; } diff --git a/src/views/nodes/repositoryNode.ts b/src/views/nodes/repositoryNode.ts index 256a279..c6aeda6 100644 --- a/src/views/nodes/repositoryNode.ts +++ b/src/views/nodes/repositoryNode.ts @@ -1,5 +1,6 @@ import { Disposable, MarkdownString, TreeItem, TreeItemCollapsibleState } from 'vscode'; import { GlyphChars } from '../../constants'; +import { Features } from '../../git/gitProvider'; import { GitUri } from '../../git/gitUri'; import { GitBranch, @@ -152,7 +153,7 @@ export class RepositoryNode extends SubscribeableViewNode<RepositoriesView> { children.push(new RemotesNode(this.uri, this.view, this, this.repo)); } - if (this.view.config.showStashes) { + if (this.view.config.showStashes && (await this.repo.supports(Features.Stashes))) { children.push(new StashesNode(this.uri, this.view, this, this.repo)); } @@ -160,7 +161,7 @@ export class RepositoryNode extends SubscribeableViewNode<RepositoriesView> { children.push(new TagsNode(this.uri, this.view, this, this.repo)); } - if (this.view.config.showWorktrees) { + if (this.view.config.showWorktrees && (await this.repo.supports(Features.Worktrees))) { children.push(new WorktreesNode(this.uri, this.view, this, this.repo)); } diff --git a/src/views/nodes/worktreesNode.ts b/src/views/nodes/worktreesNode.ts index 0e3bd5a..93a4864 100644 --- a/src/views/nodes/worktreesNode.ts +++ b/src/views/nodes/worktreesNode.ts @@ -1,4 +1,6 @@ import { ThemeIcon, TreeItem, TreeItemCollapsibleState } from 'vscode'; +import { GlyphChars } from '../../constants'; +import { PremiumFeatures } from '../../git/gitProvider'; import { GitUri } from '../../git/gitUri'; import { Repository } from '../../git/models'; import { gate } from '../../system/decorators/gate'; @@ -37,6 +39,9 @@ export class WorktreesNode extends ViewNode<WorktreesView | RepositoriesView> { async getChildren(): Promise<ViewNode[]> { if (this._children == null) { + const access = await this.repo.access(PremiumFeatures.Worktrees); + if (!access.allowed) return []; + const worktrees = await this.repo.getWorktrees(); if (worktrees.length === 0) return [new MessageNode(this.view, this, 'No worktrees could be found.')]; @@ -46,10 +51,18 @@ export class WorktreesNode extends ViewNode<WorktreesView | RepositoriesView> { return this._children; } - getTreeItem(): TreeItem { - const item = new TreeItem('Worktrees', TreeItemCollapsibleState.Collapsed); + async getTreeItem(): Promise<TreeItem> { + const access = await this.repo.access(PremiumFeatures.Worktrees); + + const item = new TreeItem( + 'Worktrees', + access.allowed ? TreeItemCollapsibleState.Collapsed : TreeItemCollapsibleState.None, + ); item.id = this.id; item.contextValue = ContextValues.Worktrees; + item.description = access.allowed + ? undefined + : ` ${GlyphChars.Warning} Premium feature which requires an account`; // TODO@eamodio `folder` icon won't work here for some reason item.iconPath = new ThemeIcon('folder-opened'); return item; diff --git a/src/views/viewCommands.ts b/src/views/viewCommands.ts index 1340aaf..552efd1 100644 --- a/src/views/viewCommands.ts +++ b/src/views/viewCommands.ts @@ -58,6 +58,7 @@ import { ViewRefFileNode, ViewRefNode, WorktreeNode, + WorktreesNode, } from './nodes'; interface CompareSelectedInfo { @@ -316,8 +317,11 @@ export class ViewCommands { } @debug() - private async createWorktree(node?: BranchNode) { - if (node !== undefined && !(node instanceof BranchNode)) return undefined; + private async createWorktree(node?: BranchNode | WorktreesNode) { + if (node instanceof WorktreesNode) { + node = undefined; + } + if (node != null && !(node instanceof BranchNode)) return undefined; return GitActions.Worktree.create(node?.repoPath, undefined, node?.ref); }