diff --git a/extensions/git/package.json b/extensions/git/package.json index 05dddd0d2a7..da96c49e3a7 100644 --- a/extensions/git/package.json +++ b/extensions/git/package.json @@ -2215,6 +2215,12 @@ "scope": "resource", "default": 10000, "description": "%config.statusLimit%" + }, + "git.repositoryScanMaxDepth": { + "type": "number", + "scope": "resource", + "default": 1, + "markdownDescription": "%config.repositoryScanMaxDepth%" } } }, diff --git a/extensions/git/package.nls.json b/extensions/git/package.nls.json index 7b3af1f5ca9..481b1cb09c0 100644 --- a/extensions/git/package.nls.json +++ b/extensions/git/package.nls.json @@ -193,6 +193,7 @@ "config.showUnpublishedCommitsButton.whenEmpty": "Only shows the action button if there are no other changes and there are unpublished commits.", "config.showUnpublishedCommitsButton.never": "Never shows the action button.", "config.statusLimit": "Controls how to limit the number of changes that can be parsed from Git status command. Can be set to 0 for no limit.", + "config.repositoryScanMaxDepth": "Controls the depth used when scanning workspace folders for Git repositories when `#git.autoRepositoryDetection#` is set to `true` or `subFolders`. Can be set to `-1` for no limit.", "submenu.explorer": "Git", "submenu.commit": "Commit", "submenu.commit.amend": "Amend", diff --git a/extensions/git/src/model.ts b/extensions/git/src/model.ts index 60a231321b1..a9a7b0e5f31 100644 --- a/extensions/git/src/model.ts +++ b/extensions/git/src/model.ts @@ -17,6 +17,7 @@ import { Askpass } from './askpass'; import { IPushErrorHandlerRegistry } from './pushError'; import { ApiRepository } from './api/api1'; import { IRemoteSourcePublisherRegistry } from './remotePublisher'; +import { Log, LogLevel } from './log'; const localize = nls.loadMessageBundle(); @@ -133,12 +134,20 @@ export class Model implements IRemoteSourcePublisherRegistry, IPushErrorHandlerR } /** - * Scans the first level of each workspace folder, looking - * for git repositories. + * Scans each workspace folder, looking for git repositories. By + * default it scans one level deep but that can be changed using + * the git.repositoryScanMaxDepth setting. */ private async scanWorkspaceFolders(): Promise { const config = workspace.getConfiguration('git'); const autoRepositoryDetection = config.get('autoRepositoryDetection'); + const repositoryScanMaxDepth = config.get('repositoryScanMaxDepth', 1); + + // Log repository scan settings + if (Log.logLevel <= LogLevel.Trace) { + this.outputChannel.appendLine(`${logTimestamp()} Trace: autoRepositoryDetection="${autoRepositoryDetection}"`); + this.outputChannel.appendLine(`${logTimestamp()} Trace: repositoryScanMaxDepth=${repositoryScanMaxDepth}`); + } if (autoRepositoryDetection !== true && autoRepositoryDetection !== 'subFolders') { return; @@ -146,9 +155,11 @@ export class Model implements IRemoteSourcePublisherRegistry, IPushErrorHandlerR await Promise.all((workspace.workspaceFolders || []).map(async folder => { const root = folder.uri.fsPath; - const children = (await fs.promises.readdir(root, { withFileTypes: true })).filter(dirent => dirent.isDirectory()).map(dirent => dirent.name); - const subfolders = new Set(children.filter(child => child !== '.git').map(child => path.join(root, child))); + // Workspace folder children + const subfolders = new Set(await this.traverseWorkspaceFolder(root, repositoryScanMaxDepth)); + + // Repository scan folders const scanPaths = (workspace.isTrusted ? workspace.getConfiguration('git', folder.uri) : config).get('scanRepositories') || []; for (const scanPath of scanPaths) { if (scanPath === '.git') { @@ -167,6 +178,29 @@ export class Model implements IRemoteSourcePublisherRegistry, IPushErrorHandlerR })); } + private async traverseWorkspaceFolder(workspaceFolder: string, maxDepth: number): Promise { + const result: string[] = []; + const foldersToTravers = [{ path: workspaceFolder, depth: 0 }]; + + while (foldersToTravers.length > 0) { + const currentFolder = foldersToTravers.shift()!; + + if (currentFolder.depth < maxDepth || maxDepth === -1) { + const children = await fs.promises.readdir(currentFolder.path, { withFileTypes: true }); + const childrenFolders = children + .filter(dirent => dirent.isDirectory() && dirent.name !== '.git') + .map(dirent => path.join(currentFolder.path, dirent.name)); + + result.push(...childrenFolders); + foldersToTravers.push(...childrenFolders.map(folder => { + return { path: folder, depth: currentFolder.depth + 1 }; + })); + } + } + + return result; + } + private onPossibleGitRepositoryChange(uri: Uri): void { const config = workspace.getConfiguration('git'); const autoRepositoryDetection = config.get('autoRepositoryDetection'); @@ -303,7 +337,9 @@ export class Model implements IRemoteSourcePublisherRegistry, IPushErrorHandlerR repository.status(); // do not await this, we want SCM to know about the repo asap } catch (ex) { // noop - this.outputChannel.appendLine(`${logTimestamp()} Opening repository for path='${repoPath}' failed; ex=${ex}`); + if (Log.logLevel <= LogLevel.Trace) { + this.outputChannel.appendLine(`${logTimestamp()} Trace: Opening repository for path='${repoPath}' failed; ex=${ex}`); + } } }