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);
 	}