diff --git a/extensions/git/src/api/api1.ts b/extensions/git/src/api/api1.ts index 55dbdf19d34..818dfc536e3 100644 --- a/extensions/git/src/api/api1.ts +++ b/extensions/git/src/api/api1.ts @@ -395,6 +395,11 @@ export class ApiImpl implements API { } } + async getRepositoryWorkspace(uri: Uri): Promise { + const workspaces = this.#model.repositoryCache.get(uri.toString()); + return workspaces ? workspaces.map(r => Uri.file(r.workspacePath)) : null; + } + async init(root: Uri, options?: InitOptions): Promise { const path = root.fsPath; await this.#model.git.init(path, options); @@ -404,7 +409,7 @@ export class ApiImpl implements API { async clone(uri: Uri, options?: CloneOptions): Promise { const parentPath = options?.parentPath?.fsPath; - const result = await this.#cloneManager.clone(uri.toString(), { parentPath, recursive: options?.recursive, ref: options?.ref, postCloneAction: options?.postCloneAction, skipCache: options?.skipCache }); + const result = await this.#cloneManager.clone(uri.toString(), { parentPath, recursive: options?.recursive, ref: options?.ref, postCloneAction: options?.postCloneAction }); return result ? Uri.file(result) : null; } diff --git a/extensions/git/src/api/git.d.ts b/extensions/git/src/api/git.d.ts index f7813f13e8c..c59c2f90658 100644 --- a/extensions/git/src/api/git.d.ts +++ b/extensions/git/src/api/git.d.ts @@ -193,8 +193,7 @@ export interface CloneOptions { /** * If no postCloneAction is provided, then the users setting for git.openAfterClone is used. */ - postCloneAction?: 'none' | 'open' | 'prompt'; - skipCache?: boolean; + postCloneAction?: 'none'; } export interface RefQuery { @@ -379,9 +378,12 @@ export interface API { toGitUri(uri: Uri, ref: string): Uri; getRepository(uri: Uri): Repository | null; getRepositoryRoot(uri: Uri): Promise; + getRepositoryWorkspace(uri: Uri): Promise; init(root: Uri, options?: InitOptions): Promise; /** - * @returns The URI of either the cloned repository, or the workspace file or folder which contains the cloned repository. + * Checks the cache of known cloned repositories, and clones if the repository is not found. + * Make sure to pass `postCloneAction` 'none' if you want to have the uri where you can find the repository returned. + * @returns The URI of a folder or workspace file which, when opened, will open the cloned repository. */ clone(uri: Uri, options?: CloneOptions): Promise; openRepository(root: Uri): Promise; diff --git a/extensions/git/src/cloneManager.ts b/extensions/git/src/cloneManager.ts index bea8aa64ad3..e25ee8d0df7 100644 --- a/extensions/git/src/cloneManager.ts +++ b/extensions/git/src/cloneManager.ts @@ -12,14 +12,14 @@ import { RepositoryCache, RepositoryCacheInfo } from './repositoryCache'; import TelemetryReporter from '@vscode/extension-telemetry'; import { Model } from './model'; -type PostCloneAction = 'none' | 'open' | 'prompt'; +type ApiPostCloneAction = 'none'; +enum PostCloneAction { Open, OpenNewWindow, AddToWorkspace, None } export interface CloneOptions { parentPath?: string; ref?: string; recursive?: boolean; - postCloneAction?: PostCloneAction; - skipCache?: boolean; + postCloneAction?: ApiPostCloneAction; } export class CloneManager { @@ -29,13 +29,13 @@ export class CloneManager { clone(url?: string, options: CloneOptions = {}) { const cachedRepository = url ? this.repositoryCache.get(url) : undefined; - if (url && !options.skipCache && cachedRepository && (cachedRepository.length > 0)) { + if (url && cachedRepository && (cachedRepository.length > 0)) { return this.tryOpenExistingRepository(cachedRepository, url, options.postCloneAction, options.parentPath, options.ref); } return this.cloneRepository(url, options.parentPath, options); } - private async cloneRepository(url?: string, parentPath?: string, options: { recursive?: boolean; ref?: string; postCloneAction?: PostCloneAction } = {}): Promise { + private async cloneRepository(url?: string, parentPath?: string, options: { recursive?: boolean; ref?: string; postCloneAction?: ApiPostCloneAction } = {}): Promise { if (!url || typeof url !== 'string') { url = await pickRemoteSource({ providerLabel: provider => l10n.t('Clone from {0}', provider.name), @@ -97,65 +97,7 @@ export class CloneManager { (progress, token) => this.model.git.clone(url!, { parentPath: parentPath!, progress, recursive: options.recursive, ref: options.ref }, token) ); - const config = workspace.getConfiguration('git'); - const openAfterClone = config.get<'always' | 'alwaysNewWindow' | 'whenNoFolderOpen' | 'prompt'>('openAfterClone'); - - enum PostCloneAction { Open, OpenNewWindow, AddToWorkspace, None } - let action: PostCloneAction | undefined = undefined; - - if (options.postCloneAction) { - if (options.postCloneAction === 'open') { - action = PostCloneAction.Open; - } else if (options.postCloneAction === 'none') { - action = PostCloneAction.None; - } - } else { - if (openAfterClone === 'always') { - action = PostCloneAction.Open; - } else if (openAfterClone === 'alwaysNewWindow') { - action = PostCloneAction.OpenNewWindow; - } else if (openAfterClone === 'whenNoFolderOpen' && !workspace.workspaceFolders) { - action = PostCloneAction.Open; - } - } - - if (action === undefined) { - let message = l10n.t('Would you like to open the cloned repository?'); - const open = l10n.t('Open'); - const openNewWindow = l10n.t('Open in New Window'); - const choices = [open, openNewWindow]; - - const addToWorkspace = l10n.t('Add to Workspace'); - if (workspace.workspaceFolders) { - message = l10n.t('Would you like to open the cloned repository, or add it to the current workspace?'); - choices.push(addToWorkspace); - } - - const result = await window.showInformationMessage(message, { modal: true }, ...choices); - - action = result === open ? PostCloneAction.Open - : result === openNewWindow ? PostCloneAction.OpenNewWindow - : result === addToWorkspace ? PostCloneAction.AddToWorkspace : undefined; - } - - /* __GDPR__ - "clone" : { - "owner": "lszomoru", - "outcome" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "comment": "The outcome of the git operation" }, - "openFolder": { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth", "isMeasurement": true, "comment": "Indicates whether the folder is opened following the clone operation" } - } - */ - this.telemetryReporter.sendTelemetryEvent('clone', { outcome: 'success' }, { openFolder: action === PostCloneAction.Open || action === PostCloneAction.OpenNewWindow ? 1 : 0 }); - - const uri = Uri.file(repositoryPath); - - if (action === PostCloneAction.Open) { - commands.executeCommand('vscode.openFolder', uri, { forceReuseWindow: true }); - } else if (action === PostCloneAction.AddToWorkspace) { - workspace.updateWorkspaceFolders(workspace.workspaceFolders!.length, 0, { uri }); - } else if (action === PostCloneAction.OpenNewWindow) { - commands.executeCommand('vscode.openFolder', uri, { forceNewWindow: true }); - } + await this.doPostCloneAction(repositoryPath, options.postCloneAction); return repositoryPath; } catch (err) { @@ -183,14 +125,64 @@ export class CloneManager { } } - private async doPostCloneAction(target: string, postCloneAction?: PostCloneAction): Promise { - const forceReuseWindow = ((workspace.workspaceFile === undefined) && (workspace.workspaceFolders === undefined)); - if (postCloneAction === 'open') { - await commands.executeCommand('vscode.openFolder', Uri.file(target), { forceReuseWindow }); + private async doPostCloneAction(target: string, postCloneAction?: ApiPostCloneAction): Promise { + const config = workspace.getConfiguration('git'); + const openAfterClone = config.get<'always' | 'alwaysNewWindow' | 'whenNoFolderOpen' | 'prompt'>('openAfterClone'); + + let action: PostCloneAction | undefined = undefined; + + if (postCloneAction && postCloneAction === 'none') { + action = PostCloneAction.None; + } else { + if (openAfterClone === 'always') { + action = PostCloneAction.Open; + } else if (openAfterClone === 'alwaysNewWindow') { + action = PostCloneAction.OpenNewWindow; + } else if (openAfterClone === 'whenNoFolderOpen' && !workspace.workspaceFolders) { + action = PostCloneAction.Open; + } + } + + if (action === undefined) { + let message = l10n.t('Would you like to open the cloned repository?'); + const open = l10n.t('Open'); + const openNewWindow = l10n.t('Open in New Window'); + const choices = [open, openNewWindow]; + + const addToWorkspace = l10n.t('Add to Workspace'); + if (workspace.workspaceFolders) { + message = l10n.t('Would you like to open the cloned repository, or add it to the current workspace?'); + choices.push(addToWorkspace); + } + + const result = await window.showInformationMessage(message, { modal: true }, ...choices); + + action = result === open ? PostCloneAction.Open + : result === openNewWindow ? PostCloneAction.OpenNewWindow + : result === addToWorkspace ? PostCloneAction.AddToWorkspace : undefined; + } + + /* __GDPR__ + "clone" : { + "owner": "lszomoru", + "outcome" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "comment": "The outcome of the git operation" }, + "openFolder": { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth", "isMeasurement": true, "comment": "Indicates whether the folder is opened following the clone operation" } + } + */ + this.telemetryReporter.sendTelemetryEvent('clone', { outcome: 'success' }, { openFolder: action === PostCloneAction.Open || action === PostCloneAction.OpenNewWindow ? 1 : 0 }); + + const uri = Uri.file(target); + + if (action === PostCloneAction.Open) { + commands.executeCommand('vscode.openFolder', uri, { forceReuseWindow: true }); + } else if (action === PostCloneAction.AddToWorkspace) { + workspace.updateWorkspaceFolders(workspace.workspaceFolders!.length, 0, { uri }); + } else if (action === PostCloneAction.OpenNewWindow) { + commands.executeCommand('vscode.openFolder', uri, { forceNewWindow: true }); } } - private async chooseExistingRepository(url: string, existingCachedRepositories: RepositoryCacheInfo[], ref: string | undefined, parentPath?: string, postCloneAction?: PostCloneAction): Promise { + private async chooseExistingRepository(url: string, existingCachedRepositories: RepositoryCacheInfo[], ref: string | undefined, parentPath?: string, postCloneAction?: ApiPostCloneAction): Promise { try { const items: { label: string; description?: string; item?: RepositoryCacheInfo }[] = existingCachedRepositories.map(knownFolder => { const isWorkspace = knownFolder.workspacePath.endsWith('.code-workspace'); @@ -213,7 +205,7 @@ export class CloneManager { } } - private async tryOpenExistingRepository(cachedRepository: RepositoryCacheInfo[], url: string, postCloneAction?: PostCloneAction, parentPath?: string, ref?: string): Promise { + private async tryOpenExistingRepository(cachedRepository: RepositoryCacheInfo[], url: string, postCloneAction?: ApiPostCloneAction, parentPath?: string, ref?: string): Promise { // Gather existing folders/workspace files (ignore ones that no longer exist) const existingCachedRepositories: RepositoryCacheInfo[] = (await Promise.all(cachedRepository.map(async folder => { const stat = await fs.promises.stat(folder.workspacePath).catch(() => undefined); @@ -243,8 +235,9 @@ export class CloneManager { repoForWorkspace = await this.chooseExistingRepository(url, existingCachedRepositories, ref, parentPath, postCloneAction); } if (repoForWorkspace) { - return this.doPostCloneAction(repoForWorkspace, postCloneAction); + await this.doPostCloneAction(repoForWorkspace, postCloneAction); + return repoForWorkspace; } - return undefined; + return; } }