From 4a0c7fcf30fb395c1afbcc7c97eb495121ef9f5e Mon Sep 17 00:00:00 2001 From: Ladislau Szomoru <3372902+lszomoru@users.noreply.github.com> Date: Wed, 18 Mar 2026 22:58:21 +0000 Subject: [PATCH] Sessions - support last turn's changes when using checkpoints (#303001) * Sessions - support last turn's changes when using checkpoints * Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --------- Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- .../contrib/changes/browser/changesView.ts | 61 +++++++++++++++++-- .../browser/mainThreadGitExtensionService.ts | 10 +++ .../workbench/api/common/extHost.protocol.ts | 1 + .../api/common/extHostGitExtensionService.ts | 19 ++++++ .../contrib/git/browser/gitService.ts | 4 ++ .../contrib/git/common/gitService.ts | 2 + 6 files changed, 92 insertions(+), 5 deletions(-) diff --git a/src/vs/sessions/contrib/changes/browser/changesView.ts b/src/vs/sessions/contrib/changes/browser/changesView.ts index c7fd07a94e7..e4f6e029bcf 100644 --- a/src/vs/sessions/contrib/changes/browser/changesView.ts +++ b/src/vs/sessions/contrib/changes/browser/changesView.ts @@ -586,14 +586,48 @@ export class ChangesViewPane extends ViewPane { return repository?.state.read(reader)?.HEAD?.commit; }); + const lastCheckpointRefObs = derived(reader => { + const sessionResource = activeSessionResource.read(reader); + if (!sessionResource) { + return undefined; + } + + sessionsChangedSignal.read(reader); + const model = this.agentSessionsService.getSession(sessionResource); + + return model?.metadata?.lastCheckpointRef as string | undefined; + }); + + const beforeLastCheckpointRefObs = derived(reader => { + const lastCheckpointRef = lastCheckpointRefObs.read(reader); + if (!lastCheckpointRef) { + return undefined; + } + + const checkpointSegments = lastCheckpointRef.split('/'); + const turnCount = parseInt(checkpointSegments.pop() ?? '-1', 10); + if (!Number.isFinite(turnCount) || turnCount <= 0) { + return undefined; + } + + checkpointSegments.push(`${turnCount - 1}`); + return checkpointSegments.join('/'); + }); + const lastTurnChangesObs = derived(reader => { const repository = this.activeSessionRepositoryObs.read(reader); const headCommit = headCommitObs.read(reader); + if (!repository || !headCommit) { return constObservable(undefined); } - return new ObservablePromise(repository.diffBetweenWithStats(`${headCommit}^`, headCommit)).resolvedValue; + const lastCheckpointRef = lastCheckpointRefObs.read(reader); + const beforeLastCheckpointRef = beforeLastCheckpointRefObs.read(reader); + + return lastCheckpointRef && beforeLastCheckpointRef + ? new ObservablePromise(repository.diffBetweenWithStats2(`${beforeLastCheckpointRef}..${lastCheckpointRef}`)).resolvedValue + : new ObservablePromise(repository.diffBetweenWithStats(`${headCommit}^`, headCommit)).resolvedValue; }); // Combine both entry sources for display @@ -607,13 +641,30 @@ export class ChangesViewPane extends ViewPane { let sourceEntries: IChangesFileItem[]; if (versionMode === ChangesVersionMode.LastTurn) { const diffChanges = lastTurnDiffChanges ?? []; - const parentRef = headCommit ? `${headCommit}^` : ''; + const lastCheckpointRef = lastCheckpointRefObs.read(undefined); + const beforeLastCheckpointRef = beforeLastCheckpointRefObs.read(undefined); + + const ref = lastCheckpointRef + ? lastCheckpointRef + : headCommit; + + const parentRef = beforeLastCheckpointRef + ? beforeLastCheckpointRef + : headCommit ? `${headCommit}^` : undefined; + sourceEntries = diffChanges.map(change => { const isDeletion = change.modifiedUri === undefined; const isAddition = change.originalUri === undefined; - const fileUri = change.modifiedUri ?? change.uri; - const originalUri = isAddition ? change.originalUri - : headCommit ? fileUri.with({ scheme: 'git', query: JSON.stringify({ path: fileUri.fsPath, ref: parentRef }) }) + const uri = change.modifiedUri ?? change.uri; + const fileUri = isDeletion + ? uri + : ref + ? uri.with({ scheme: 'git', query: JSON.stringify({ path: uri.fsPath, ref }) }) + : uri; + const originalUri = isAddition + ? change.originalUri + : parentRef + ? fileUri.with({ scheme: 'git', query: JSON.stringify({ path: fileUri.fsPath, ref: parentRef }) }) : change.originalUri; return { type: 'file', diff --git a/src/vs/workbench/api/browser/mainThreadGitExtensionService.ts b/src/vs/workbench/api/browser/mainThreadGitExtensionService.ts index e9579b0a3ec..15774eafe5f 100644 --- a/src/vs/workbench/api/browser/mainThreadGitExtensionService.ts +++ b/src/vs/workbench/api/browser/mainThreadGitExtensionService.ts @@ -165,6 +165,16 @@ export class MainThreadGitExtensionService extends Disposable implements MainThr return result.map(toGitDiffChange); } + async diffBetweenWithStats2(root: URI, ref: string, path?: string): Promise { + const handle = this._repositoryHandles.get(root); + if (handle === undefined) { + return []; + } + + const result = await this._proxy.$diffBetweenWithStats2(handle, ref, path); + return result.map(toGitDiffChange); + } + async $onDidChangeRepository(handle: number): Promise { const repository = this._repositories.get(handle); if (!repository) { diff --git a/src/vs/workbench/api/common/extHost.protocol.ts b/src/vs/workbench/api/common/extHost.protocol.ts index a7a52b724ba..7972ca1fb01 100644 --- a/src/vs/workbench/api/common/extHost.protocol.ts +++ b/src/vs/workbench/api/common/extHost.protocol.ts @@ -3726,6 +3726,7 @@ export interface ExtHostGitExtensionShape { $getRefs(handle: number, query: GitRefQueryDto, token?: CancellationToken): Promise; $getRepositoryState(handle: number): Promise; $diffBetweenWithStats(handle: number, ref1: string, ref2: string, path?: string): Promise; + $diffBetweenWithStats2(handle: number, ref: string, path?: string): Promise; } // --- proxy identifiers diff --git a/src/vs/workbench/api/common/extHostGitExtensionService.ts b/src/vs/workbench/api/common/extHostGitExtensionService.ts index 4d1dd2e2acb..a0f7ec43df1 100644 --- a/src/vs/workbench/api/common/extHostGitExtensionService.ts +++ b/src/vs/workbench/api/common/extHostGitExtensionService.ts @@ -95,6 +95,7 @@ interface Repository { getBranchBase(name: string): Promise; getRefs(query: GitRefQuery, token?: vscode.CancellationToken): Promise; diffBetweenWithStats(ref1: string, ref2: string, path?: string): Promise; + diffBetweenWithStats2(ref: string, path?: string): Promise; isBranchProtected(branch?: Branch): boolean; } @@ -326,6 +327,24 @@ export class ExtHostGitExtensionService extends Disposable implements IExtHostGi } } + async $diffBetweenWithStats2(handle: number, ref: string, path?: string): Promise { + const repository = this._repositories.get(handle); + if (!repository) { + return []; + } + + try { + const changes = await repository.diffBetweenWithStats2(ref, path); + return changes.map(c => ({ + ...toGitChangeDto(c), + insertions: c.insertions, + deletions: c.deletions, + })); + } catch { + return []; + } + } + private async _ensureGitApi(): Promise { if (this._gitApi) { return this._gitApi; diff --git a/src/vs/workbench/contrib/git/browser/gitService.ts b/src/vs/workbench/contrib/git/browser/gitService.ts index ca34f506015..f9403bd33f1 100644 --- a/src/vs/workbench/contrib/git/browser/gitService.ts +++ b/src/vs/workbench/contrib/git/browser/gitService.ts @@ -85,4 +85,8 @@ export class GitRepository extends Disposable implements IGitRepository { async diffBetweenWithStats(ref1: string, ref2: string, path?: string): Promise { return this.delegate.diffBetweenWithStats(this.rootUri, ref1, ref2, path); } + + async diffBetweenWithStats2(ref: string, path?: string): Promise { + return this.delegate.diffBetweenWithStats2(this.rootUri, ref, path); + } } diff --git a/src/vs/workbench/contrib/git/common/gitService.ts b/src/vs/workbench/contrib/git/common/gitService.ts index ce605402b6f..cbaa386370d 100644 --- a/src/vs/workbench/contrib/git/common/gitService.ts +++ b/src/vs/workbench/contrib/git/common/gitService.ts @@ -74,6 +74,7 @@ export interface IGitRepository { getRefs(query: GitRefQuery, token?: CancellationToken): Promise; diffBetweenWithStats(ref1: string, ref2: string, path?: string): Promise; + diffBetweenWithStats2(ref: string, path?: string): Promise; } export interface IGitExtensionDelegate { @@ -82,6 +83,7 @@ export interface IGitExtensionDelegate { getRefs(root: URI, query?: GitRefQuery, token?: CancellationToken): Promise; diffBetweenWithStats(root: URI, ref1: string, ref2: string, path?: string): Promise; + diffBetweenWithStats2(root: URI, ref: string, path?: string): Promise; } export const IGitService = createDecorator('gitService');