Path: blob/main/extensions/copilot/src/extension/chatSessions/vscode-node/chatSessionRepositoryTracker.ts
13399 views
/*---------------------------------------------------------------------------------------------1* Copyright (c) Microsoft Corporation. All rights reserved.2* Licensed under the MIT License. See License.txt in the project root for license information.3*--------------------------------------------------------------------------------------------*/45import * as vscode from 'vscode';6import { ILogService } from '../../../platform/log/common/logService';7import { Disposable, DisposableResourceMap, DisposableStore } from '../../../util/vs/base/common/lifecycle';8import { IChatSessionWorkspaceFolderService } from '../common/chatSessionWorkspaceFolderService';9import { IChatSessionWorktreeService } from '../common/chatSessionWorktreeService';10import { ICopilotCLIChatSessionItemProvider } from './copilotCLIChatSessions';11import { IGitService } from '../../../platform/git/common/gitService';12import { IChatSessionMetadataStore } from '../common/chatSessionMetadataStore';1314export class ChatSessionRepositoryTracker extends Disposable {15private readonly repositories = new DisposableResourceMap();1617constructor(18// This is only required in non-controller code paths.19private readonly sessionItemProvider: ICopilotCLIChatSessionItemProvider | undefined,20@IChatSessionWorktreeService private readonly worktreeService: IChatSessionWorktreeService,21@IChatSessionWorkspaceFolderService private readonly workspaceFolderService: IChatSessionWorkspaceFolderService,22@IGitService private readonly gitService: IGitService,23@ILogService private readonly logService: ILogService,24@IChatSessionMetadataStore private readonly metadataStore: IChatSessionMetadataStore25) {26super();2728// Only track repository changes in the sessions app29if (vscode.workspace.isAgentSessionsWorkspace) {30this.logService.trace('[ChatSessionRepositoryTracker][constructor] Initializing workspace folder event handler');31this._register(vscode.workspace.onDidChangeWorkspaceFolders(e => this.onDidChangeWorkspaceFolders(e)));32this.onDidChangeWorkspaceFolders({ added: vscode.workspace.workspaceFolders ?? [], removed: [] });33}34}3536private async onDidChangeWorkspaceFolders(e: vscode.WorkspaceFoldersChangeEvent): Promise<void> {37this.logService.trace(`[ChatSessionRepositoryTracker][onDidChangeWorkspaceFolders] Workspace folders changed. Added: ${e.added.map(f => f.uri.fsPath).join(', ')}, Removed: ${e.removed.map(f => f.uri.fsPath).join(', ')}`);3839// Add watchers40for (const added of e.added) {41await this.createRepositoryWatcher(added.uri);42}4344// Dispose watchers45for (const removed of e.removed) {46this.disposeRepositoryWatcher(removed.uri);47}48}4950private async createRepositoryWatcher(uri: vscode.Uri): Promise<void> {51if (this.repositories.has(uri)) {52this.logService.trace(`[ChatSessionRepositoryTracker][createRepositoryWatcher] Already tracking repository changes for ${uri.toString()}.`);53return;54}5556const repository = await this.gitService.openRepository(uri);57if (!repository) {58this.logService.trace(`[ChatSessionRepositoryTracker][createRepositoryWatcher] No repository found at ${uri.toString()}.`);59return;60}6162const disposables = new DisposableStore();63disposables.add(repository.state.onDidChange(() => this.onDidChangeRepositoryState(uri)));64this.repositories.set(uri, disposables);6566// Trigger an initial update to set the session67// properties based on the current repository state68void this.onDidChangeRepositoryState(uri);69}7071private async onDidChangeRepositoryState(uri: vscode.Uri): Promise<void> {72await clearChangesCacheForAffectedSessions(uri, [], this.logService, this.metadataStore, this.workspaceFolderService, this.worktreeService, this.sessionItemProvider);73}7475private disposeRepositoryWatcher(uri: vscode.Uri): void {76if (!this.repositories.has(uri)) {77return;78}7980this.logService.trace(`[ChatSessionRepositoryTracker][disposeRepositoryWatcher] Disposing repository watcher for ${uri.toString()}.`);81this.repositories.deleteAndDispose(uri);82}8384override dispose(): void {85this.repositories.dispose();86super.dispose();87}88}8990/**91* Invalidates the cache for sessions affected by a repository change, and triggers a refresh of those sessions.92* You can optionally provide a list of sessions that should not be refreshed.93* E.g. if you know that those sessions are not affected or are already up to date, you can exclude them from the refresh to avoid unnecessary work.94*/95export async function clearChangesCacheForAffectedSessions(folder: vscode.Uri, sessionsToIgnore: string[], logService: ILogService, metadataStore: IChatSessionMetadataStore, workspaceFolderService: IChatSessionWorkspaceFolderService, worktreeService: IChatSessionWorktreeService, sessionItemProvider?: ICopilotCLIChatSessionItemProvider): Promise<void> {96logService.trace(`[ChatSessionRepositoryTracker][onDidChangeRepositoryState] Repository state changed for ${folder.toString()}. Updating session properties.`);9798const sessionIds = metadataStore.getSessionIdsForFolder(folder).filter(id => !sessionsToIgnore.includes(id));99const workspaceSessionIds = workspaceFolderService.clearWorkspaceChanges(folder).filter(id => !sessionsToIgnore.includes(id));100sessionIds.forEach(id => workspaceFolderService.clearWorkspaceChanges(id));101sessionIds.push(...workspaceSessionIds);102await Promise.all(Array.from(new Set(sessionIds)).map(async sessionId => {103// Worktree104const worktreeProperties = await worktreeService.getWorktreeProperties(sessionId);105if (worktreeProperties) {106await worktreeService.setWorktreeProperties(sessionId, {107...worktreeProperties,108changes: undefined109});110}111}));112// Will be passed in non-controller code paths.113if (sessionItemProvider) {114await sessionItemProvider.refreshSession({ reason: 'update', sessionIds });115}116logService.trace(`[ChatSessionRepositoryTracker][onDidChangeRepositoryState] Updated session properties for worktree ${folder.toString()}.`);117}118119120