豆豆友情提示:这是一个非官方 GitHub 代理镜像,主要用于网络测试或访问加速。请勿在此进行登录、注册或处理任何敏感信息。进行这些操作请务必访问官方网站 github.com。 Raw 内容也通过此代理提供。
Skip to content

Commit 3f17034

Browse files
Copilot CLI: refactor how uncommitted changes are determined before creating a new CLI session (#4756)
* Copilot CLI: refactor how uncommitted changes are determined before creating a new CLI session * Update src/extension/chatSessions/vscode-node/folderRepositoryManagerImpl.ts Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Updates * Address review comments --------- Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
1 parent 4e872ea commit 3f17034

File tree

5 files changed

+24
-234
lines changed

5 files changed

+24
-234
lines changed

src/extension/chatSessions/vscode-node/chatSessionWorkspaceFolderServiceImpl.ts

Lines changed: 1 addition & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -6,17 +6,14 @@
66
import { promises as fs } from 'fs';
77
import * as vscode from 'vscode';
88
import { IVSCodeExtensionContext } from '../../../platform/extContext/common/extensionContext';
9-
import { IFileSystemService } from '../../../platform/filesystem/common/fileSystemService';
10-
import { RelativePattern } from '../../../platform/filesystem/common/fileTypes';
119
import { IGitService, RepoContext } from '../../../platform/git/common/gitService';
1210
import { parseGitChangesRaw } from '../../../platform/git/vscode-node/utils';
1311
import { DiffChange } from '../../../platform/git/vscode/git';
1412
import { ILogService } from '../../../platform/log/common/logService';
1513
import { coalesce } from '../../../util/vs/base/common/arrays';
1614
import { SequencerByKey } from '../../../util/vs/base/common/async';
17-
import { Disposable, DisposableResourceMap, DisposableStore } from '../../../util/vs/base/common/lifecycle';
15+
import { Disposable } from '../../../util/vs/base/common/lifecycle';
1816
import { ResourceMap, ResourceSet } from '../../../util/vs/base/common/map';
19-
import { autorun } from '../../../util/vs/base/common/observableInternal';
2017
import * as path from '../../../util/vs/base/common/path';
2118
import { isEqual } from '../../../util/vs/base/common/resources';
2219
import { generateUuid } from '../../../util/vs/base/common/uuid';
@@ -37,14 +34,12 @@ export class ChatSessionWorkspaceFolderService extends Disposable implements ICh
3734
private recentFolders: { folder: vscode.Uri; lastAccessTime: number }[] = [];
3835
private readonly deletedFolders = new ResourceSet();
3936
private readonly workspaceChangesSequencer = new SequencerByKey<string>();
40-
private readonly repoStateSubscriptions = this._register(new DisposableResourceMap());
4137

4238
constructor(
4339
@IGitService private readonly gitService: IGitService,
4440
@ILogService private readonly logService: ILogService,
4541
@IChatSessionMetadataStore private readonly metadataStore: IChatSessionMetadataStore,
4642
@IVSCodeExtensionContext private readonly extensionContext: IVSCodeExtensionContext,
47-
@IFileSystemService private readonly fileSystemService: IFileSystemService,
4843
@IAgentSessionsWorkspace private readonly agentSessionsWorkspace: IAgentSessionsWorkspace,
4944
) {
5045
super();
@@ -118,9 +113,6 @@ export class ChatSessionWorkspaceFolderService extends Disposable implements ICh
118113
return [];
119114
}
120115

121-
// Subscribe to repository state changes to invalidate the cache
122-
this.subscribeToRepoStateChanges(workspaceFolderUri, repository);
123-
124116
// Workspace changes are handled differently in the Sessions app,
125117
// where all changes remain in the working tree and are not being
126118
// staged.
@@ -222,30 +214,4 @@ export class ChatSessionWorkspaceFolderService extends Disposable implements ICh
222214
clearWorkspaceChanges(workspaceFolderUri: vscode.Uri): void {
223215
this.workspaceFolderChanges.delete(workspaceFolderUri);
224216
}
225-
226-
private subscribeToRepoStateChanges(workspaceFolderUri: vscode.Uri, repository: RepoContext): void {
227-
if (this.repoStateSubscriptions.has(workspaceFolderUri)) {
228-
return;
229-
}
230-
231-
const disposables = new DisposableStore();
232-
233-
// Invalidate cache when the HEAD commit changes (e.g. after a commit, checkout, merge, rebase)
234-
disposables.add(autorun(reader => {
235-
repository.headCommitHashObs.read(reader);
236-
this.workspaceFolderChanges.delete(workspaceFolderUri);
237-
}));
238-
239-
// Invalidate cache when files change in the workspace folder
240-
const watcher = this.fileSystemService.createFileSystemWatcher(new RelativePattern(workspaceFolderUri, '**'));
241-
disposables.add(watcher);
242-
const onFileChange = () => {
243-
this.workspaceFolderChanges.delete(workspaceFolderUri);
244-
};
245-
disposables.add(watcher.onDidChange(onFileChange));
246-
disposables.add(watcher.onDidCreate(onFileChange));
247-
disposables.add(watcher.onDidDelete(onFileChange));
248-
249-
this.repoStateSubscriptions.set(workspaceFolderUri, disposables);
250-
}
251217
}

src/extension/chatSessions/vscode-node/folderRepositoryManagerImpl.ts

Lines changed: 13 additions & 73 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,6 @@ import { Disposable, DisposableStore } from '../../../util/vs/base/common/lifecy
1515
import { ResourceSet } from '../../../util/vs/base/common/map';
1616
import { isEqual } from '../../../util/vs/base/common/resources';
1717
import { createTimeout } from '../../inlineEdits/common/common';
18-
import { ToolName } from '../../tools/common/toolNames';
1918
import { IToolsService } from '../../tools/common/toolsService';
2019
import { IChatSessionWorkspaceFolderService } from '../common/chatSessionWorkspaceFolderService';
2120
import { ChatSessionWorktreeFile, ChatSessionWorktreeProperties, IChatSessionWorktreeService } from '../common/chatSessionWorktreeService';
@@ -254,7 +253,7 @@ export abstract class FolderRepositoryManager extends Disposable implements IFol
254253
// Check for uncommitted changes and prompt user before creating worktree
255254
let uncommittedChangesAction: 'move' | 'copy' | 'skip' | 'cancel' | undefined = undefined;
256255
if ((!sessionId || isUntitledSessionId(sessionId)) && !worktreeProperties) {
257-
uncommittedChangesAction = await this.promptForUncommittedChangesAction(sessionId, toolInvocationToken, token);
256+
uncommittedChangesAction = await this.promptForUncommittedChangesAction(sessionId, repository, branch, toolInvocationToken, token);
258257
if (uncommittedChangesAction === 'cancel') {
259258
return { folder, repository, worktree, worktreeProperties, trusted: true, cancelled: true };
260259
}
@@ -374,18 +373,16 @@ export abstract class FolderRepositoryManager extends Disposable implements IFol
374373
*/
375374
private async promptForUncommittedChangesAction(
376375
sessionId: string | undefined,
376+
repositoryUri: vscode.Uri,
377+
branch: string | undefined,
377378
toolInvocationToken: vscode.ChatParticipantToolToken,
378379
token: vscode.CancellationToken
379380
): Promise<'move' | 'copy' | 'skip' | 'cancel' | undefined> {
380-
const uncommittedChanges = await this.getUncommittedChangesPromptData(sessionId, token);
381+
const uncommittedChanges = await this.getUncommittedChanges(repositoryUri, branch, token);
381382
if (!uncommittedChanges) {
382383
return undefined;
383384
}
384385

385-
if (!this.toolsService.getTool('vscode_get_modified_files_confirmation')) {
386-
return this.promptForUncommittedChangesActionOld(sessionId, toolInvocationToken, token);
387-
}
388-
389386
const isDelegation = !sessionId;
390387
const title = isDelegation
391388
? l10n.t('Delegate to Copilot CLI')
@@ -422,48 +419,6 @@ export abstract class FolderRepositoryManager extends Disposable implements IFol
422419
}
423420
}
424421

425-
private async promptForUncommittedChangesActionOld(
426-
sessionId: string | undefined,
427-
toolInvocationToken: vscode.ChatParticipantToolToken,
428-
token: vscode.CancellationToken
429-
): Promise<'move' | 'copy' | 'skip' | 'cancel' | undefined> {
430-
const isDelegation = !sessionId;
431-
const title = isDelegation
432-
? l10n.t('Delegate to Copilot CLI')
433-
: l10n.t('Uncommitted Changes');
434-
const message = isDelegation
435-
? l10n.t('Copilot CLI will work in an isolated worktree to implement your requested changes.')
436-
+ '\n\n'
437-
+ l10n.t('The selected repository has uncommitted changes. Should these changes be included in the new worktree?')
438-
: l10n.t('The selected repository has uncommitted changes. Should these changes be included in the new worktree?');
439-
440-
const copyChanges = l10n.t('Copy Changes');
441-
const moveChanges = l10n.t('Move Changes');
442-
const skipChanges = l10n.t('Skip Changes');
443-
const cancel = l10n.t('Cancel');
444-
const buttons = [copyChanges, moveChanges, skipChanges, cancel];
445-
const input = {
446-
title,
447-
message,
448-
buttons
449-
};
450-
const result = await this.toolsService.invokeTool(ToolName.CoreConfirmationToolWithOptions, { input, toolInvocationToken }, token);
451-
452-
const firstResultPart = result.content.at(0);
453-
const selection = firstResultPart instanceof LanguageModelTextPart ? firstResultPart.value : undefined;
454-
455-
switch (selection?.toUpperCase()) {
456-
case moveChanges.toUpperCase():
457-
return 'move';
458-
case copyChanges.toUpperCase():
459-
return 'copy';
460-
case skipChanges.toUpperCase():
461-
return 'skip';
462-
default:
463-
return 'cancel';
464-
}
465-
}
466-
467422
private getSelectedUncommittedChangesAction(
468423
result: vscode.LanguageModelToolResult,
469424
options: readonly string[]
@@ -482,15 +437,21 @@ export abstract class FolderRepositoryManager extends Disposable implements IFol
482437
return undefined;
483438
}
484439

485-
private async getUncommittedChangesPromptData(
486-
sessionId: string | undefined,
440+
private async getUncommittedChanges(
441+
folderPath: vscode.Uri,
442+
branch: string | undefined,
487443
token: vscode.CancellationToken
488444
): Promise<{ repository: vscode.Uri; modifiedFiles: Array<{ uri: vscode.Uri; originalUri?: vscode.Uri; insertions?: number; deletions?: number }> } | undefined> {
489-
const repository = await this.getRepositoryForUncommittedChanges(sessionId);
445+
const repository = await this.gitService.getRepository(folderPath);
490446
if (!repository) {
491447
return undefined;
492448
}
493449

450+
// If the current branch is not the same as the requested branch, we cannot reliably determine the uncommitted changes, so skip the confirmation.
451+
if (branch && repository.headBranchName !== branch) {
452+
return undefined;
453+
}
454+
494455
const modifiedFiles = await this.getModifiedFilesForConfirmation(repository.rootUri, repository, token);
495456
if (modifiedFiles.length === 0) {
496457
return undefined;
@@ -502,27 +463,6 @@ export abstract class FolderRepositoryManager extends Disposable implements IFol
502463
};
503464
}
504465

505-
private async getRepositoryForUncommittedChanges(sessionId: string | undefined): Promise<ReturnType<IGitService['activeRepository']['get']> | undefined> {
506-
if (sessionId && isUntitledSessionId(sessionId)) {
507-
const folder = this._newSessionFolders.get(sessionId)?.uri
508-
?? await this.workspaceFolderService.getSessionWorkspaceFolder(sessionId);
509-
if (folder) {
510-
return await this.gitService.getRepository(folder, false);
511-
}
512-
// No folder selected, fall through to the active repository check.
513-
}
514-
515-
if (sessionId && !isUntitledSessionId(sessionId)) {
516-
return undefined;
517-
}
518-
519-
if (!isWelcomeView(this.workspaceService) && this.workspaceService.getWorkspaceFolders().length === 1) {
520-
return this.gitService.activeRepository.get();
521-
}
522-
523-
return undefined;
524-
}
525-
526466
private async getModifiedFilesForConfirmation(
527467
repositoryUri: vscode.Uri,
528468
repository: NonNullable<ReturnType<IGitService['activeRepository']['get']>>,

0 commit comments

Comments
 (0)