From 89e13d82be3ef4a0bea9cd59981ce8e8f7097dde Mon Sep 17 00:00:00 2001 From: Iitian001 Date: Thu, 23 Jul 2026 18:40:20 +0530 Subject: [PATCH 1/3] git: remove repo from SCM when workspace folder is removed When a workspace folder is removed from a multi-root workspace, the corresponding git repository was not being removed from the SCM panel. The original code used getOpenRepository(folder.uri) to find repos to dispose, which internally relies on isDescendant(repoRoot, resourcePath). This check fails when the repo root IS the workspace folder (i.e. the most common case), because a path is not a descendant of itself. Fix by iterating all open repositories and checking whether their root is equal to or a descendant of any removed workspace folder, rather than relying on the indirect URI-based lookup. Additionally, prune stale closedRepositories workspace state entries for any removed workspace folders. Without this, repos that were manually closed before the folder was removed would ghost back into the SCM panel the next time the workspace is opened. Fixes #108757 --- extensions/git/src/model.ts | 26 +++++++++++++++++++++----- 1 file changed, 21 insertions(+), 5 deletions(-) diff --git a/extensions/git/src/model.ts b/extensions/git/src/model.ts index dafcea34b336ae..9dab0fd159d4c9 100644 --- a/extensions/git/src/model.ts +++ b/extensions/git/src/model.ts @@ -477,13 +477,28 @@ export class Model implements IRepositoryResolver, IBranchProtectionProviderRegi .filter(repository => !!repository) as Repository[]; const activeRepositories = new Set(activeRepositoriesList); - const openRepositoriesToDispose = removed - .map(folder => this.getOpenRepository(folder.uri)) - .filter(r => !!r) - .filter(r => !activeRepositories.has(r!.repository)) - .filter(r => !(workspace.workspaceFolders || []).some(f => isDescendant(f.uri.fsPath, r!.repository.root))) as OpenRepository[]; + + // Find all open repositories whose root is at or inside a removed workspace folder. + // The original check only used getOpenRepository(folder.uri) which relies on isDescendant + // and misses the common case where the repo root IS the workspace folder itself. + const openRepositoriesToDispose = this.openRepositories + .filter(r => removed.some(folder => isDescendant(folder.uri.fsPath, r.repository.root) || r.repository.root === folder.uri.fsPath)) + .filter(r => !activeRepositories.has(r.repository)) + .filter(r => !(workspace.workspaceFolders || []).some(f => isDescendant(f.uri.fsPath, r.repository.root))); openRepositoriesToDispose.forEach(r => r.dispose()); + + // Prune stale closedRepositories entries for the removed workspace folders so that + // manually-closed repos don't ghost back the next time the workspace is opened. + for (const folder of removed) { + for (const closedRepo of this._closedRepositoriesManager.repositories) { + if (isDescendant(folder.uri.fsPath, closedRepo) || closedRepo === folder.uri.fsPath) { + this._closedRepositoriesManager.deleteRepository(closedRepo); + this.logger.trace(`[Model][onDidChangeWorkspaceFolders] Pruned stale closed repository: ${closedRepo}`); + } + } + } + this.logger.trace(`[Model][onDidChangeWorkspaceFolders] Workspace folders: [${possibleRepositoryFolders.map(p => p.uri.fsPath).join(', ')}]`); await Promise.all(possibleRepositoryFolders.map(p => this.openRepository(p.uri.fsPath))); } @@ -492,6 +507,7 @@ export class Model implements IRepositoryResolver, IBranchProtectionProviderRegi } } + private async onDidChangeWorkspaceTrustedFolders(): Promise { try { const openRepositoriesToDispose: OpenRepository[] = []; From 95dd2b1933ff89775ba4bd4409262d0379091078 Mon Sep 17 00:00:00 2001 From: Shreyash <160615932+Iitian001@users.noreply.github.com> Date: Fri, 16 Oct 2026 15:16:10 +0530 Subject: [PATCH 2/3] fix(git): preserve parent repositories and manual close state on folder removal --- extensions/git/src/model.ts | 28 +++++++++++++--------------- 1 file changed, 13 insertions(+), 15 deletions(-) diff --git a/extensions/git/src/model.ts b/extensions/git/src/model.ts index 9dab0fd159d4c9..8179729fc3f23c 100644 --- a/extensions/git/src/model.ts +++ b/extensions/git/src/model.ts @@ -478,26 +478,24 @@ export class Model implements IRepositoryResolver, IBranchProtectionProviderRegi const activeRepositories = new Set(activeRepositoriesList); - // Find all open repositories whose root is at or inside a removed workspace folder. - // The original check only used getOpenRepository(folder.uri) which relies on isDescendant - // and misses the common case where the repo root IS the workspace folder itself. + // A repository may be rooted at, inside, or ABOVE a workspace folder + // (for example, when git.openRepositoryInParentFolders is enabled). + const overlapsFolder = (folderPath: string, repositoryRoot: string): boolean => + pathEquals(folderPath, repositoryRoot) || + isDescendant(folderPath, repositoryRoot) || + isDescendant(repositoryRoot, folderPath); + + const remainingFolders = workspace.workspaceFolders || []; const openRepositoriesToDispose = this.openRepositories - .filter(r => removed.some(folder => isDescendant(folder.uri.fsPath, r.repository.root) || r.repository.root === folder.uri.fsPath)) + .filter(r => removed.some(folder => overlapsFolder(folder.uri.fsPath, r.repository.root))) .filter(r => !activeRepositories.has(r.repository)) - .filter(r => !(workspace.workspaceFolders || []).some(f => isDescendant(f.uri.fsPath, r.repository.root))); + // Keep a parent repo open while any remaining child workspace folder uses it. + .filter(r => !remainingFolders.some(folder => overlapsFolder(folder.uri.fsPath, r.repository.root))); openRepositoriesToDispose.forEach(r => r.dispose()); - // Prune stale closedRepositories entries for the removed workspace folders so that - // manually-closed repos don't ghost back the next time the workspace is opened. - for (const folder of removed) { - for (const closedRepo of this._closedRepositoriesManager.repositories) { - if (isDescendant(folder.uri.fsPath, closedRepo) || closedRepo === folder.uri.fsPath) { - this._closedRepositoriesManager.deleteRepository(closedRepo); - this.logger.trace(`[Model][onDidChangeWorkspaceFolders] Pruned stale closed repository: ${closedRepo}`); - } - } - } + // Preserve manually closed repository markers. Removing them here would + // allow the next repository scan to reopen a repository the user closed. this.logger.trace(`[Model][onDidChangeWorkspaceFolders] Workspace folders: [${possibleRepositoryFolders.map(p => p.uri.fsPath).join(', ')}]`); await Promise.all(possibleRepositoryFolders.map(p => this.openRepository(p.uri.fsPath))); From f065182ffba5cd582e8ef5beb1d3fd17f7ddf524 Mon Sep 17 00:00:00 2001 From: Iitian001 <160615932+Iitian001@users.noreply.github.com> Date: Sun, 15 Nov 2026 17:56:10 +0530 Subject: [PATCH 3/3] git: tidy workspace folder removal comments Co-Authored-By: Claude Opus 5.5 --- extensions/git/src/model.ts | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/extensions/git/src/model.ts b/extensions/git/src/model.ts index 8179729fc3f23c..8ac6936191a3be 100644 --- a/extensions/git/src/model.ts +++ b/extensions/git/src/model.ts @@ -492,11 +492,9 @@ export class Model implements IRepositoryResolver, IBranchProtectionProviderRegi // Keep a parent repo open while any remaining child workspace folder uses it. .filter(r => !remainingFolders.some(folder => overlapsFolder(folder.uri.fsPath, r.repository.root))); + // Manually closed repository markers are intentionally kept so that the + // repository scan below does not reopen a repository the user closed. openRepositoriesToDispose.forEach(r => r.dispose()); - - // Preserve manually closed repository markers. Removing them here would - // allow the next repository scan to reopen a repository the user closed. - this.logger.trace(`[Model][onDidChangeWorkspaceFolders] Workspace folders: [${possibleRepositoryFolders.map(p => p.uri.fsPath).join(', ')}]`); await Promise.all(possibleRepositoryFolders.map(p => this.openRepository(p.uri.fsPath))); } @@ -505,7 +503,6 @@ export class Model implements IRepositoryResolver, IBranchProtectionProviderRegi } } - private async onDidChangeWorkspaceTrustedFolders(): Promise { try { const openRepositoriesToDispose: OpenRepository[] = [];