From cbc57242ed044365549cb1d9da434ff985e0bd07 Mon Sep 17 00:00:00 2001 From: Ladislau Szomoru <3372902+lszomoru@users.noreply.github.com> Date: Fri, 27 Mar 2026 22:04:27 +0000 Subject: [PATCH] Sessions - wire up the discard changes action (#305830) --- extensions/git/src/repository.ts | 115 ++++++++++++------ .../contrib/changes/browser/changesView.ts | 36 ++---- 2 files changed, 90 insertions(+), 61 deletions(-) diff --git a/extensions/git/src/repository.ts b/extensions/git/src/repository.ts index 56c24729257..da3c09215cf 100644 --- a/extensions/git/src/repository.ts +++ b/extensions/git/src/repository.ts @@ -1369,16 +1369,47 @@ export class Repository implements Disposable { await this.run( Operation.Restore(!this.optimisticUpdateEnabled()), async () => { - const resourcePaths = resources.map(r => r.fsPath); - await this.repository.restore(resourcePaths, options); + const toClean: string[] = []; + const toRestore: string[] = []; - if (options?.staged) { - // Index was modified; - this.closeDiffEditors([], resourcePaths); - } else { - // Working tree was modified; - this.closeDiffEditors(resourcePaths, []); + const resourceStates = [ + ...this.indexGroup.resourceStates, + ...this.workingTreeGroup.resourceStates, + ...this.untrackedGroup.resourceStates + ]; + + for (const resource of resources) { + const scmResource = find(resourceStates, r => r.resourceUri.toString() === resource.toString()); + + if (!scmResource) { + toRestore.push(resource.fsPath); + continue; + } + + switch (scmResource.type) { + case Status.UNTRACKED: + case Status.IGNORED: + toClean.push(resource.fsPath); + break; + + default: + toRestore.push(resource.fsPath); + break; + } } + + if (toClean.length > 0) { + await this._clean(toClean); + } + + if (toRestore.length > 0) { + await this.repository.restore(toRestore, options); + } + + this.closeDiffEditors([], [...toClean, ...toRestore]); + + // Clear AI contribution tracking for discarded resources + commands.executeCommand('_aiEdits.clearAiContributions', resources); }); } @@ -1511,9 +1542,6 @@ export class Repository implements Disposable { } async clean(resources: Uri[]): Promise { - const config = workspace.getConfiguration('git'); - const discardUntrackedChangesToTrash = config.get('discardUntrackedChangesToTrash', true) && !isRemote && !isLinuxSnap; - await this.run( Operation.Clean(!this.optimisticUpdateEnabled()), async () => { @@ -1552,33 +1580,7 @@ export class Repository implements Disposable { }); if (toClean.length > 0) { - if (discardUntrackedChangesToTrash) { - try { - // Attempt to move the first resource to the recycle bin/trash to check - // if it is supported. If it fails, we show a confirmation dialog and - // fall back to deletion. - await workspace.fs.delete(Uri.file(toClean[0]), { useTrash: true }); - - const limiter = new Limiter(5); - await Promise.all(toClean.slice(1).map(fsPath => limiter.queue( - async () => await workspace.fs.delete(Uri.file(fsPath), { useTrash: true })))); - } catch { - const message = isWindows - ? l10n.t('Failed to delete using the Recycle Bin. Do you want to permanently delete instead?') - : l10n.t('Failed to delete using the Trash. Do you want to permanently delete instead?'); - const primaryAction = toClean.length === 1 - ? l10n.t('Delete File') - : l10n.t('Delete All {0} Files', resources.length); - - const result = await window.showWarningMessage(message, { modal: true }, primaryAction); - if (result === primaryAction) { - // Delete permanently - await this.repository.clean(toClean); - } - } - } else { - await this.repository.clean(toClean); - } + await this._clean(toClean); } if (toCheckout.length > 0) { @@ -1615,6 +1617,43 @@ export class Repository implements Disposable { }); } + async _clean(resources: string[]): Promise { + const config = workspace.getConfiguration('git'); + const discardUntrackedChangesToTrash = config.get('discardUntrackedChangesToTrash', true) && !isRemote && !isLinuxSnap; + + if (resources.length === 0) { + return; + } + + if (discardUntrackedChangesToTrash) { + try { + // Attempt to move the first resource to the recycle bin/trash to check + // if it is supported. If it fails, we show a confirmation dialog and + // fall back to deletion. + await workspace.fs.delete(Uri.file(resources[0]), { useTrash: true }); + + const limiter = new Limiter(5); + await Promise.all(resources.slice(1).map(fsPath => limiter.queue( + async () => await workspace.fs.delete(Uri.file(fsPath), { useTrash: true })))); + } catch { + const message = isWindows + ? l10n.t('Failed to delete using the Recycle Bin. Do you want to permanently delete instead?') + : l10n.t('Failed to delete using the Trash. Do you want to permanently delete instead?'); + const primaryAction = resources.length === 1 + ? l10n.t('Delete File') + : l10n.t('Delete All {0} Files', resources.length); + + const result = await window.showWarningMessage(message, { modal: true }, primaryAction); + if (result === primaryAction) { + // Delete permanently + await this.repository.clean(resources); + } + } + } else { + await this.repository.clean(resources); + } + } + closeDiffEditors(indexResources: string[] | undefined, workingTreeResources: string[] | undefined, ignoreSetting = false): void { const config = workspace.getConfiguration('git', Uri.file(this.root)); if (!config.get('closeDiffOnOperation', false) && !ignoreSetting) { return; } diff --git a/src/vs/sessions/contrib/changes/browser/changesView.ts b/src/vs/sessions/contrib/changes/browser/changesView.ts index 4cfd91351d2..0e91f0af274 100644 --- a/src/vs/sessions/contrib/changes/browser/changesView.ts +++ b/src/vs/sessions/contrib/changes/browser/changesView.ts @@ -994,7 +994,7 @@ export class ChangesViewPane extends ViewPane { const resourceLabels = this._register(this.instantiationService.createInstance(ResourceLabels, { onDidChangeVisibility: this.onDidChangeBodyVisibility })); const actionRunner = this.renderDisposables.add(new ChangesViewActionRunner( () => this.viewModel.activeSessionResourceObs.get(), - () => this.getSessionRefs(), + () => this.getSessionDiscardRef(), () => this.getTreeSelection(), )); this.tree = this.instantiationService.createInstance( @@ -1197,26 +1197,16 @@ export class ChangesViewPane extends ViewPane { return selection.filter(item => !!item && isChangesFileItem(item)); } - private getSessionRefs(): [string, string] { - const activeSession = this.sessionManagementService.activeSession.get(); - const activeSessionIsolationMode = this.viewModel.activeSessionIsolationModeObs.get(); - const activeSessionRepositoryState = this.viewModel.activeSessionRepositoryObs.get()?.state.get(); + private getSessionDiscardRef(): string { + const versionMode = this.viewModel.versionModeObs.get(); + const firstCheckpointRef = this.viewModel.activeSessionFirstCheckpointRefObs.get(); + const lastCheckpointRef = this.viewModel.activeSessionLastCheckpointRefObs.get(); - let originalRef: string, modifiedRef: string; - if (activeSessionIsolationMode === IsolationMode.Worktree) { - // Worktree - originalRef = activeSession?.workspace.get()?.repositories[0].baseBranchName ?? ''; - modifiedRef = activeSessionRepositoryState?.HEAD?.name ?? ''; - } else { - // Workspace - const upstream = activeSessionRepositoryState?.HEAD?.upstream; - originalRef = upstream - ? `${upstream.remote}/${upstream.name}` - : activeSessionRepositoryState?.HEAD?.name ?? ''; - modifiedRef = activeSessionRepositoryState?.HEAD?.name ?? ''; - } - - return [originalRef, modifiedRef]; + return versionMode === ChangesVersionMode.LastTurn + ? lastCheckpointRef + ? `${lastCheckpointRef}^` + : '' + : firstCheckpointRef ?? ''; } protected override layoutBody(height: number, width: number): void { @@ -1267,7 +1257,7 @@ class ChangesViewActionRunner extends ActionRunner { constructor( private readonly getSessionResource: () => URI | undefined, - private readonly getSessionRefs: () => [originalRef: string, modifiedRef: string], + private readonly getSessionDiscardRef: () => string, private readonly getSelectedFileItems: () => IChangesFileItem[] ) { super(); @@ -1279,11 +1269,11 @@ class ChangesViewActionRunner extends ActionRunner { } const sessionResource = this.getSessionResource(); - const [originalRef, modifiedRef] = this.getSessionRefs(); + const discardRef = this.getSessionDiscardRef(); const selection = this.getSelectedFileItems(); const contextIsSelected = selection.some(s => isEqual(s.uri, context)); const actualContext = contextIsSelected ? selection.map(s => s.uri) : [context]; - await action.run(sessionResource, originalRef, modifiedRef, ...actualContext); + await action.run(sessionResource, discardRef, ...actualContext); } }