Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/src/vs/sessions/contrib/changes/browser/changesViewModel.ts
13401 views
1
/*---------------------------------------------------------------------------------------------
2
* Copyright (c) Microsoft Corporation. All rights reserved.
3
* Licensed under the MIT License. See License.txt in the project root for license information.
4
*--------------------------------------------------------------------------------------------*/
5
6
import { Codicon } from '../../../../base/common/codicons.js';
7
import { arrayEqualsC, structuralEquals } from '../../../../base/common/equals.js';
8
import { Iterable } from '../../../../base/common/iterator.js';
9
import { Disposable } from '../../../../base/common/lifecycle.js';
10
import { derived, derivedOpts, IObservable, IObservableWithChange, ISettableObservable, runOnChange, observableValue, observableSignalFromEvent, constObservable, ObservablePromise, derivedObservableWithCache } from '../../../../base/common/observable.js';
11
import { isWeb } from '../../../../base/common/platform.js';
12
import { isEqual } from '../../../../base/common/resources.js';
13
import { URI } from '../../../../base/common/uri.js';
14
import { IStorageService, StorageScope, StorageTarget } from '../../../../platform/storage/common/storage.js';
15
import { IAgentSessionsService } from '../../../../workbench/contrib/chat/browser/agentSessions/agentSessionsService.js';
16
import { IChatSessionFileChange2 } from '../../../../workbench/contrib/chat/common/chatSessionsService.js';
17
import { GitDiffChange, IGitService } from '../../../../workbench/contrib/git/common/gitService.js';
18
import { COPILOT_CLOUD_SESSION_TYPE, ISessionFileChange } from '../../../services/sessions/common/session.js';
19
import { ISessionsManagementService } from '../../../services/sessions/common/sessionsManagement.js';
20
import { IAgentFeedbackService } from '../../agentFeedback/browser/agentFeedbackService.js';
21
import { CodeReviewStateKind, getCodeReviewFilesFromSessionChanges, getCodeReviewVersion, ICodeReviewService, PRReviewStateKind } from '../../codeReview/browser/codeReviewService.js';
22
import { IGitHubService } from '../../github/browser/githubService.js';
23
import { toPRContentUri } from '../../github/common/utils.js';
24
import { ChangesVersionMode, ChangesViewMode, IsolationMode } from '../common/changes.js';
25
26
function toIChatSessionFileChange2(changes: GitDiffChange[], originalRef: string | undefined, modifiedRef: string | undefined): IChatSessionFileChange2[] {
27
return changes.map(change => ({
28
uri: change.uri,
29
originalUri: change.originalUri
30
? originalRef
31
? change.originalUri.with({ scheme: 'git', query: JSON.stringify({ path: change.originalUri.fsPath, ref: originalRef }) })
32
: change.originalUri
33
: undefined,
34
modifiedUri: change.modifiedUri
35
? modifiedRef
36
? change.modifiedUri.with({ scheme: 'git', query: JSON.stringify({ path: change.modifiedUri.fsPath, ref: modifiedRef }) })
37
: change.modifiedUri
38
: undefined,
39
insertions: change.insertions,
40
deletions: change.deletions,
41
} satisfies IChatSessionFileChange2));
42
}
43
44
function sortDateDesc(dateA: Date | undefined, dateB: Date | undefined): number {
45
const chatALastTurnEnd = dateA?.getTime();
46
const chatBLastTurnEnd = dateB?.getTime();
47
48
if (!chatALastTurnEnd && !chatBLastTurnEnd) {
49
return 0;
50
}
51
52
if (!chatALastTurnEnd) {
53
return 1;
54
}
55
56
if (!chatBLastTurnEnd) {
57
return -1;
58
}
59
60
return chatBLastTurnEnd - chatALastTurnEnd;
61
}
62
63
export interface ActiveSessionState {
64
readonly isolationMode: IsolationMode;
65
readonly hasGitRepository: boolean;
66
readonly branchName: string | undefined;
67
readonly baseBranchName: string | undefined;
68
readonly upstreamBranchName: string | undefined;
69
readonly isMergeBaseBranchProtected: boolean | undefined;
70
readonly incomingChanges: number | undefined;
71
readonly outgoingChanges: number | undefined;
72
readonly uncommittedChanges: number | undefined;
73
readonly hasGitHubRemote: boolean | undefined;
74
readonly hasPullRequest: boolean | undefined;
75
readonly hasOpenPullRequest: boolean | undefined;
76
}
77
78
export class ChangesViewModel extends Disposable {
79
readonly activeSessionResourceObs: IObservable<URI | undefined>;
80
readonly activeSessionTypeObs: IObservable<string | undefined>;
81
readonly activeSessionChangesObs: IObservable<readonly ISessionFileChange[]>;
82
readonly activeSessionHasGitRepositoryObs: IObservable<boolean>;
83
readonly activeSessionFirstCheckpointRefObs: IObservable<string | undefined>;
84
readonly activeSessionLastCheckpointRefObs: IObservable<string | undefined>;
85
readonly activeSessionReviewCommentCountByFileObs: IObservable<Map<string, number>>;
86
readonly activeSessionAgentFeedbackCountByFileObs: IObservable<Map<string, number>>;
87
readonly activeSessionStateObs: IObservable<ActiveSessionState | undefined>;
88
readonly activeSessionIsLoadingObs: IObservable<boolean>;
89
90
private _activeSessionMetadataObs!: IObservable<{ readonly [key: string]: unknown } | undefined>;
91
private _activeSessionAllChangesPromiseObs!: IObservableWithChange<IObservable<IChatSessionFileChange2[] | undefined>>;
92
private _activeSessionLastTurnChangesPromiseObs!: IObservableWithChange<IObservable<IChatSessionFileChange2[] | undefined>>;
93
private _activeSessionUncommittedChangesPromiseObs!: IObservableWithChange<IObservable<IChatSessionFileChange2[] | undefined>>;
94
95
readonly versionModeObs: ISettableObservable<ChangesVersionMode>;
96
setVersionMode(mode: ChangesVersionMode): void {
97
if (this.versionModeObs.get() === mode) {
98
return;
99
}
100
this.versionModeObs.set(mode, undefined);
101
}
102
103
readonly viewModeObs: ISettableObservable<ChangesViewMode>;
104
setViewMode(mode: ChangesViewMode): void {
105
if (this.viewModeObs.get() === mode) {
106
return;
107
}
108
this.viewModeObs.set(mode, undefined);
109
this.storageService.store('changesView.viewMode', mode, StorageScope.WORKSPACE, StorageTarget.USER);
110
}
111
112
constructor(
113
@IAgentFeedbackService private readonly agentFeedbackService: IAgentFeedbackService,
114
@IAgentSessionsService private readonly agentSessionsService: IAgentSessionsService,
115
@ICodeReviewService private readonly codeReviewService: ICodeReviewService,
116
@IGitHubService private readonly gitHubService: IGitHubService,
117
@IGitService private readonly gitService: IGitService,
118
@ISessionsManagementService private readonly sessionManagementService: ISessionsManagementService,
119
@IStorageService private readonly storageService: IStorageService,
120
) {
121
super();
122
123
// Active session resource
124
this.activeSessionResourceObs = derivedOpts({ equalsFn: isEqual }, reader => {
125
const activeSession = this.sessionManagementService.activeSession.read(reader);
126
return activeSession?.resource;
127
});
128
129
// Active session type
130
this.activeSessionTypeObs = derived(reader => {
131
const activeSession = this.sessionManagementService.activeSession.read(reader);
132
return activeSession?.sessionType;
133
});
134
135
// Active session metadata
136
this._activeSessionMetadataObs = this._getActiveSessionMetadata();
137
138
// Active session has git repository
139
this.activeSessionHasGitRepositoryObs = derived(reader => {
140
const sessionType = this.activeSessionTypeObs.read(reader);
141
const metadata = this._activeSessionMetadataObs.read(reader);
142
if (sessionType === COPILOT_CLOUD_SESSION_TYPE || metadata?.repositoryPath !== undefined) {
143
return true;
144
}
145
146
// Fall back to reading details from repo on the session management service session
147
const activeSession = this.sessionManagementService.activeSession.read(reader);
148
const workspace = activeSession?.workspace.read(reader);
149
const repository = workspace?.repositories[0];
150
return repository !== undefined && (
151
repository.uncommittedChanges !== undefined ||
152
repository.incomingChanges !== undefined ||
153
repository.outgoingChanges !== undefined ||
154
repository.upstreamBranchName !== undefined
155
);
156
});
157
158
// Active session first checkpoint ref
159
this.activeSessionFirstCheckpointRefObs = derived(reader => {
160
const metadata = this._activeSessionMetadataObs.read(reader);
161
return metadata?.firstCheckpointRef as string | undefined;
162
});
163
164
// Active session last checkpoint ref
165
this.activeSessionLastCheckpointRefObs = derived(reader => {
166
const activeSessionChats = this.sessionManagementService.activeSession.read(reader)?.chats.read(reader);
167
if (!activeSessionChats || activeSessionChats.length === 0) {
168
return undefined;
169
}
170
171
// Session has only one chat
172
if (activeSessionChats.length === 1) {
173
const metadata = this._activeSessionMetadataObs.read(reader);
174
return metadata?.lastCheckpointRef as string | undefined;
175
}
176
177
// Session has multiple chats - find the last chat that completed
178
const chatsSortedByLastTurnEnd = activeSessionChats.toSorted((chatA, chatB) => {
179
const chatALastTurnEnd = chatA.lastTurnEnd.read(reader);
180
const chatBLastTurnEnd = chatB.lastTurnEnd.read(reader);
181
182
return sortDateDesc(chatALastTurnEnd, chatBLastTurnEnd);
183
});
184
185
const model = this.agentSessionsService.getSession(chatsSortedByLastTurnEnd[0].resource);
186
return model?.metadata?.lastCheckpointRef as string | undefined;
187
});
188
189
// Active session state
190
const { isLoading, state } = this._getActiveSessionState();
191
this.activeSessionIsLoadingObs = isLoading;
192
this.activeSessionStateObs = state;
193
194
// Active session changes
195
this.activeSessionChangesObs = this._getActiveSessionChanges();
196
197
// Active session review comment count by file
198
this.activeSessionReviewCommentCountByFileObs = this._getActiveSessionReviewComments();
199
200
// Active session agent feedback count by file
201
this.activeSessionAgentFeedbackCountByFileObs = this._getActiveSessionAgentFeedback();
202
203
// Version mode
204
this.versionModeObs = observableValue<ChangesVersionMode>(this, ChangesVersionMode.BranchChanges);
205
206
this._register(runOnChange(this.activeSessionResourceObs, () => {
207
this.setVersionMode(ChangesVersionMode.BranchChanges);
208
}));
209
210
// View mode
211
const storedMode = this.storageService.get('changesView.viewMode', StorageScope.WORKSPACE);
212
const initialMode = storedMode === ChangesViewMode.Tree ? ChangesViewMode.Tree : ChangesViewMode.List;
213
this.viewModeObs = observableValue<ChangesViewMode>(this, initialMode);
214
}
215
216
private _getActiveSessionMetadata(): IObservable<{ readonly [key: string]: unknown } | undefined> {
217
const sessionsChangedSignal = observableSignalFromEvent(this,
218
this.sessionManagementService.onDidChangeSessions);
219
220
const sessionMetadata = derivedObservableWithCache<{ readonly [key: string]: unknown } | undefined>(this, (reader, lastValue) => {
221
const sessionResource = this.activeSessionResourceObs.read(reader);
222
if (!sessionResource) {
223
return undefined;
224
}
225
226
sessionsChangedSignal.read(reader);
227
const model = this.agentSessionsService.getSession(sessionResource);
228
if (model === undefined) {
229
// This occurs when the untitled session is committed. In order
230
// to avoid flickering of the toolbar, we keep the old metadata
231
// until the new metadata is available.
232
return lastValue;
233
}
234
235
return model.metadata;
236
});
237
238
return derivedOpts<{ readonly [key: string]: unknown } | undefined>({ equalsFn: structuralEquals }, reader => {
239
return sessionMetadata.read(reader);
240
});
241
}
242
243
private _getActiveSessionChanges(): IObservable<readonly ISessionFileChange[]> {
244
// Changes
245
const activeSessionChangesObs = derived(reader => {
246
const activeSession = this.sessionManagementService.activeSession.read(reader);
247
if (!activeSession) {
248
return Iterable.empty();
249
}
250
return activeSession.changes.read(reader);
251
});
252
253
const activeSessionRepositoryPathObs = derived(reader => {
254
const metadata = this._activeSessionMetadataObs.read(reader);
255
const repositoryPath = metadata?.repositoryPath as string | undefined;
256
const worktreePath = metadata?.worktreePath as string | undefined;
257
258
return worktreePath ?? repositoryPath;
259
});
260
261
// Uncommitted changes
262
const activeSessionUncommittedChangesCountObs = derived(reader => {
263
const sessionMetadata = this._activeSessionMetadataObs.read(reader);
264
const uncommittedChanges = sessionMetadata?.uncommittedChanges as number | undefined;
265
266
const activeSession = this.sessionManagementService.activeSession.read(reader);
267
const workspace = activeSession?.workspace.read(reader);
268
const workspaceRepository = workspace?.repositories[0];
269
270
return uncommittedChanges ?? workspaceRepository?.uncommittedChanges;
271
});
272
273
this._activeSessionUncommittedChangesPromiseObs = derived(reader => {
274
const repositoryPath = activeSessionRepositoryPathObs.read(reader);
275
if (!repositoryPath) {
276
return constObservable([]);
277
}
278
279
// Re-run when the number of uncommitted changes changes
280
activeSessionUncommittedChangesCountObs.read(reader);
281
282
const diffPromise = this._getRepositoryChanges(repositoryPath, 'HEAD', undefined);
283
return new ObservablePromise(diffPromise).resolvedValue;
284
});
285
286
// All changes
287
this._activeSessionAllChangesPromiseObs = derived(reader => {
288
const sessionType = this.activeSessionTypeObs.read(reader);
289
290
if (sessionType === COPILOT_CLOUD_SESSION_TYPE) {
291
// Cloud session
292
const metadata = this._activeSessionMetadataObs.read(reader);
293
294
const firstCheckpointRef = metadata?.baseRefOid as string | undefined;
295
const lastCheckpointRef = metadata?.headRefOid as string | undefined;
296
297
if (!firstCheckpointRef || !lastCheckpointRef) {
298
return constObservable([]);
299
}
300
301
const diffPromise = this._getPullRequestChanges(firstCheckpointRef, lastCheckpointRef);
302
return new ObservablePromise(diffPromise).resolvedValue;
303
}
304
305
// Local session
306
const repositoryPath = activeSessionRepositoryPathObs.read(reader);
307
const firstCheckpointRef = this.activeSessionFirstCheckpointRefObs.read(reader);
308
const lastCheckpointRef = this.activeSessionLastCheckpointRefObs.read(reader);
309
310
if (!repositoryPath || !firstCheckpointRef || !lastCheckpointRef) {
311
return constObservable([]);
312
}
313
314
const diffPromise = this._getRepositoryChanges(repositoryPath, firstCheckpointRef, lastCheckpointRef);
315
return new ObservablePromise(diffPromise).resolvedValue;
316
});
317
318
// Last turn changes
319
this._activeSessionLastTurnChangesPromiseObs = derived(reader => {
320
const sessionType = this.activeSessionTypeObs.read(reader);
321
322
if (sessionType === COPILOT_CLOUD_SESSION_TYPE) {
323
// Cloud session
324
const metadata = this._activeSessionMetadataObs.read(reader);
325
const lastCheckpointRef = metadata?.headRefOid as string | undefined;
326
327
if (!lastCheckpointRef) {
328
return constObservable([]);
329
}
330
331
const diffPromise = this._getPullRequestChanges(`${lastCheckpointRef}^`, lastCheckpointRef);
332
return new ObservablePromise(diffPromise).resolvedValue;
333
}
334
335
// Local session
336
const repositoryPath = activeSessionRepositoryPathObs.read(reader);
337
const lastCheckpointRef = this.activeSessionLastCheckpointRefObs.read(reader);
338
339
if (!repositoryPath || !lastCheckpointRef) {
340
return constObservable([]);
341
}
342
343
const diffPromise = this._getRepositoryChanges(repositoryPath, `${lastCheckpointRef}^`, lastCheckpointRef);
344
return new ObservablePromise(diffPromise).resolvedValue;
345
});
346
347
return derivedOpts({
348
equalsFn: arrayEqualsC<ISessionFileChange>()
349
}, reader => {
350
const versionMode = this.versionModeObs.read(reader);
351
352
// BranchChanges reads from the session provider's `changes`
353
// observable directly (e.g. agent-host-tracked diffs), so it
354
// works even for sessions without a git repository.
355
if (versionMode === ChangesVersionMode.BranchChanges) {
356
return activeSessionChangesObs.read(reader);
357
}
358
359
const hasGitRepository = this.activeSessionHasGitRepositoryObs.read(reader);
360
if (!hasGitRepository && !isWeb) {
361
return [];
362
}
363
364
if (versionMode === ChangesVersionMode.UncommittedChanges) {
365
return this._activeSessionUncommittedChangesPromiseObs.read(reader).read(reader) ?? [];
366
} else if (versionMode === ChangesVersionMode.AllChanges) {
367
return this._activeSessionAllChangesPromiseObs.read(reader).read(reader) ?? [];
368
} else if (versionMode === ChangesVersionMode.LastTurn) {
369
return this._activeSessionLastTurnChangesPromiseObs.read(reader).read(reader) ?? [];
370
}
371
372
return [];
373
});
374
}
375
376
private _getActiveSessionState(): { isLoading: IObservable<boolean>; state: IObservable<ActiveSessionState | undefined> } {
377
const isLoadingObs = derived(reader => {
378
// Branch changes
379
const versionMode = this.versionModeObs.read(reader);
380
if (versionMode === ChangesVersionMode.BranchChanges) {
381
return false;
382
}
383
384
// Uncommitted changes
385
if (versionMode === ChangesVersionMode.UncommittedChanges) {
386
const uncommittedChangesResult = this._activeSessionUncommittedChangesPromiseObs.read(reader).read(reader);
387
return uncommittedChangesResult === undefined;
388
}
389
390
// All changes
391
if (versionMode === ChangesVersionMode.AllChanges) {
392
const allChangesResult = this._activeSessionAllChangesPromiseObs.read(reader).read(reader);
393
return allChangesResult === undefined;
394
}
395
396
// Last turn changes
397
if (versionMode === ChangesVersionMode.LastTurn) {
398
const lastTurnChangesResult = this._activeSessionLastTurnChangesPromiseObs.read(reader).read(reader);
399
return lastTurnChangesResult === undefined;
400
}
401
402
return false;
403
});
404
405
const activeSessionStateObs = derivedObservableWithCache<ActiveSessionState | undefined>(this, (reader, lastValue) => {
406
const isLoading = isLoadingObs.read(reader);
407
if (isLoading) {
408
return lastValue;
409
}
410
411
const sessionMetadata = this._activeSessionMetadataObs.read(reader);
412
const activeSession = this.sessionManagementService.activeSession.read(reader);
413
const workspace = activeSession?.workspace.read(reader);
414
415
// Session state
416
const workspaceRepository = workspace?.repositories[0];
417
const hasGitRepository = this.activeSessionHasGitRepositoryObs.read(reader);
418
const branchName = (sessionMetadata?.branchName ?? sessionMetadata?.branch) as string | undefined
419
?? workspaceRepository?.branchName;
420
const baseBranchName = (sessionMetadata?.baseBranchName ?? sessionMetadata?.baseBranch) as string | undefined
421
?? workspaceRepository?.baseBranchName;
422
423
// Fall back to reading details from repo on the session management service session
424
const isMergeBaseBranchProtected = (sessionMetadata?.baseBranchProtected as boolean | undefined)
425
?? workspaceRepository?.baseBranchProtected;
426
const isolationMode = workspaceRepository?.workingDirectory === undefined
427
? IsolationMode.Workspace
428
: IsolationMode.Worktree;
429
430
// Pull request state
431
const gitHubInfo = activeSession?.gitHubInfo.read(reader);
432
const hasPullRequest = gitHubInfo?.pullRequest?.uri !== undefined;
433
const hasOpenPullRequest = hasPullRequest &&
434
(gitHubInfo.pullRequest.icon?.id === Codicon.gitPullRequestDraft.id ||
435
gitHubInfo.pullRequest.icon?.id === Codicon.gitPullRequest.id);
436
437
// Fall back to reading details from repo on the session management service session
438
const hasGitHubRemote = (sessionMetadata?.hasGitHubRemote as boolean | undefined) ?? workspaceRepository?.hasGitHubRemote ?? false;
439
const upstreamBranchName = (sessionMetadata?.upstreamBranchName as string | undefined) ?? workspaceRepository?.upstreamBranchName;
440
const incomingChanges = (sessionMetadata?.incomingChanges as number | undefined) ?? workspaceRepository?.incomingChanges ?? 0;
441
const outgoingChanges = (sessionMetadata?.outgoingChanges as number | undefined) ?? workspaceRepository?.outgoingChanges ?? 0;
442
const uncommittedChanges = (sessionMetadata?.uncommittedChanges as number | undefined) ?? workspaceRepository?.uncommittedChanges ?? 0;
443
444
return {
445
isolationMode,
446
hasGitRepository,
447
branchName,
448
baseBranchName,
449
isMergeBaseBranchProtected,
450
upstreamBranchName,
451
incomingChanges,
452
outgoingChanges,
453
uncommittedChanges,
454
hasGitHubRemote,
455
hasPullRequest,
456
hasOpenPullRequest
457
} satisfies ActiveSessionState;
458
});
459
460
return {
461
isLoading: isLoadingObs,
462
state: derivedOpts({ equalsFn: structuralEquals },
463
reader => activeSessionStateObs.read(reader))
464
};
465
}
466
467
private _getActiveSessionReviewComments(): IObservable<Map<string, number>> {
468
return derived(reader => {
469
const sessionResource = this.activeSessionResourceObs.read(reader);
470
const changes = [...this.activeSessionChangesObs.read(reader)];
471
472
if (!sessionResource) {
473
return new Map<string, number>();
474
}
475
476
const result = new Map<string, number>();
477
const prReviewState = this.codeReviewService.getPRReviewState(sessionResource).read(reader);
478
if (prReviewState.kind === PRReviewStateKind.Loaded) {
479
for (const comment of prReviewState.comments) {
480
const uriKey = comment.uri.fsPath;
481
result.set(uriKey, (result.get(uriKey) ?? 0) + 1);
482
}
483
}
484
485
if (changes.length === 0) {
486
return result;
487
}
488
489
const reviewFiles = getCodeReviewFilesFromSessionChanges(changes);
490
const reviewVersion = getCodeReviewVersion(reviewFiles);
491
const reviewState = this.codeReviewService.getReviewState(sessionResource).read(reader);
492
493
if (reviewState.kind !== CodeReviewStateKind.Result || reviewState.version !== reviewVersion) {
494
return result;
495
}
496
497
for (const comment of reviewState.comments) {
498
const uriKey = comment.uri.fsPath;
499
result.set(uriKey, (result.get(uriKey) ?? 0) + 1);
500
}
501
502
return result;
503
});
504
}
505
506
private _getActiveSessionAgentFeedback(): IObservable<Map<string, number>> {
507
return derived(reader => {
508
const sessionResource = this.activeSessionResourceObs.read(reader);
509
if (!sessionResource) {
510
return new Map<string, number>();
511
}
512
513
observableSignalFromEvent(this, this.agentFeedbackService.onDidChangeFeedback).read(reader);
514
515
const feedbackItems = this.agentFeedbackService.getFeedback(sessionResource);
516
const result = new Map<string, number>();
517
for (const item of feedbackItems) {
518
if (!item.sourcePRReviewCommentId) {
519
const uriKey = item.resourceUri.fsPath;
520
result.set(uriKey, (result.get(uriKey) ?? 0) + 1);
521
}
522
}
523
return result;
524
});
525
}
526
527
private async _getRepositoryChanges(repositoryPath: string, firstCheckpointRef: string, lastCheckpointRef: string | undefined): Promise<IChatSessionFileChange2[] | undefined> {
528
const repository = await this.gitService.openRepository(URI.file(repositoryPath));
529
const ref = lastCheckpointRef
530
? `${firstCheckpointRef}..${lastCheckpointRef}`
531
: firstCheckpointRef;
532
533
const changes = await repository?.diffBetweenWithStats2(ref) ?? [];
534
return toIChatSessionFileChange2(changes, firstCheckpointRef, lastCheckpointRef);
535
}
536
537
private async _getPullRequestChanges(firstCheckpointRef: string, lastCheckpointRef: string): Promise<IChatSessionFileChange2[] | undefined> {
538
const gitHubInfo = this.sessionManagementService.activeSession.get()?.gitHubInfo.get();
539
if (!gitHubInfo?.owner || !gitHubInfo?.repo || !gitHubInfo?.pullRequest?.number) {
540
return [];
541
}
542
543
const params = {
544
owner: gitHubInfo.owner,
545
repo: gitHubInfo.repo,
546
prNumber: gitHubInfo.pullRequest.number,
547
} as const;
548
549
const changes = await this.gitHubService.getChangedFiles(params.owner, params.repo, firstCheckpointRef, lastCheckpointRef);
550
return changes.map(change => {
551
const uri = toPRContentUri(change.filename, {
552
...params,
553
commitSha: lastCheckpointRef,
554
status: change.status,
555
isBase: false
556
});
557
558
const originalUri = change.status !== 'added'
559
? toPRContentUri(change.previous_filename || change.filename, {
560
...params,
561
commitSha: firstCheckpointRef,
562
previousFileName: change.previous_filename,
563
status: change.status,
564
isBase: true
565
})
566
: undefined;
567
568
const modifiedUri = change.status !== 'removed'
569
? uri
570
: undefined;
571
572
return {
573
uri,
574
originalUri,
575
modifiedUri,
576
insertions: change.additions,
577
deletions: change.deletions
578
} satisfies IChatSessionFileChange2;
579
});
580
}
581
}
582
583