diff --git a/extensions/git/src/artifactProvider.ts b/extensions/git/src/artifactProvider.ts index b48711fcec3..f99e262b9c4 100644 --- a/extensions/git/src/artifactProvider.ts +++ b/extensions/git/src/artifactProvider.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { LogOutputChannel, SourceControlArtifactProvider, SourceControlArtifactGroup, SourceControlArtifact, Event, EventEmitter, ThemeIcon, l10n, workspace, Uri, Disposable, Command } from 'vscode'; -import { coalesce, dispose, filterEvent, IDisposable } from './util'; +import { coalesce, dispose, filterEvent, IDisposable, isCopilotWorktree } from './util'; import { Repository } from './repository'; import { Commit, Ref, RefType } from './api/git'; import { OperationKind } from './operation'; @@ -172,7 +172,7 @@ export class GitArtifactProvider implements SourceControlArtifactProvider, IDisp w.commitDetails?.hash.substring(0, shortCommitLength), w.commitDetails?.message.split('\n')[0] ]).join(' \u2022 '), - icon: w.name.startsWith('copilot-worktree') + icon: isCopilotWorktree(w.path) ? new ThemeIcon('chat-sparkle') : new ThemeIcon('worktree'), timestamp: w.commitDetails?.commitDate?.getTime(), diff --git a/extensions/git/src/repository.ts b/extensions/git/src/repository.ts index 81ef23040f1..f610095a92f 100644 --- a/extensions/git/src/repository.ts +++ b/extensions/git/src/repository.ts @@ -22,7 +22,7 @@ import { IPushErrorHandlerRegistry } from './pushError'; import { IRemoteSourcePublisherRegistry } from './remotePublisher'; import { StatusBarCommands } from './statusbar'; import { toGitUri } from './uri'; -import { anyEvent, combinedDisposable, debounceEvent, dispose, EmptyDisposable, eventToPromise, filterEvent, find, getCommitShortHash, IDisposable, isDescendant, isLinuxSnap, isRemote, isWindows, Limiter, onceEvent, pathEquals, relativePath } from './util'; +import { anyEvent, combinedDisposable, debounceEvent, dispose, EmptyDisposable, eventToPromise, filterEvent, find, getCommitShortHash, IDisposable, isCopilotWorktree, isDescendant, isLinuxSnap, isRemote, isWindows, Limiter, onceEvent, pathEquals, relativePath } from './util'; import { IFileWatcher, watch } from './watch'; import { ISourceControlHistoryItemDetailsProviderRegistry } from './historyItemDetailsProvider'; import { GitArtifactProvider } from './artifactProvider'; @@ -947,11 +947,20 @@ export class Repository implements Disposable { const icon = repository.kind === 'submodule' ? new ThemeIcon('archive') : repository.kind === 'worktree' - ? new ThemeIcon('worktree') + ? isCopilotWorktree(repository.root) + ? new ThemeIcon('chat-sparkle') + : new ThemeIcon('worktree') : new ThemeIcon('repo'); + // Hidden + // This is a temporary solution to hide worktrees created by Copilot + // when the main repository is opened. Users can still manually open + // the worktree from the Repositories view. + const hidden = repository.kind === 'worktree' && + isCopilotWorktree(repository.root) && parent !== undefined; + const root = Uri.file(repository.root); - this._sourceControl = scm.createSourceControl('git', 'Git', root, icon, parent); + this._sourceControl = scm.createSourceControl('git', 'Git', root, icon, hidden, parent); this._sourceControl.contextValue = repository.kind; this._sourceControl.quickDiffProvider = this; diff --git a/extensions/git/src/util.ts b/extensions/git/src/util.ts index d7b0a07eeb4..c6ec6ece45c 100644 --- a/extensions/git/src/util.ts +++ b/extensions/git/src/util.ts @@ -866,3 +866,11 @@ export function getStashDescription(stash: Stash): string | undefined { return descriptionSegments.join(' \u2022 '); } + +export function isCopilotWorktree(path: string): boolean { + const lastSepIndex = path.lastIndexOf(sep); + + return lastSepIndex !== -1 + ? path.substring(lastSepIndex + 1).startsWith('copilot-worktree-') + : path.startsWith('copilot-worktree-'); +} diff --git a/src/vs/workbench/api/browser/mainThreadSCM.ts b/src/vs/workbench/api/browser/mainThreadSCM.ts index 983e9fa0f7f..97eb0456cda 100644 --- a/src/vs/workbench/api/browser/mainThreadSCM.ts +++ b/src/vs/workbench/api/browser/mainThreadSCM.ts @@ -314,6 +314,7 @@ class MainThreadSCMProvider implements ISCMProvider { get label(): string { return this._label; } get rootUri(): URI | undefined { return this._rootUri; } get iconPath(): URI | { light: URI; dark: URI } | ThemeIcon | undefined { return this._iconPath; } + get isHidden(): boolean | undefined { return this._isHidden; } get inputBoxTextModel(): ITextModel { return this._inputBoxTextModel; } private readonly _contextValue = observableValue(this, undefined); @@ -353,6 +354,7 @@ class MainThreadSCMProvider implements ISCMProvider { private readonly _label: string, private readonly _rootUri: URI | undefined, private readonly _iconPath: URI | { light: URI; dark: URI } | ThemeIcon | undefined, + private readonly _isHidden: boolean | undefined, private readonly _inputBoxTextModel: ITextModel, private readonly _quickDiffService: IQuickDiffService, private readonly _uriIdentService: IUriIdentityService, @@ -633,11 +635,11 @@ export class MainThreadSCM implements MainThreadSCMShape { this._disposables.dispose(); } - async $registerSourceControl(handle: number, parentHandle: number | undefined, id: string, label: string, rootUri: UriComponents | undefined, iconPath: UriComponents | { light: UriComponents; dark: UriComponents } | ThemeIcon | undefined, inputBoxDocumentUri: UriComponents): Promise { + async $registerSourceControl(handle: number, parentHandle: number | undefined, id: string, label: string, rootUri: UriComponents | undefined, iconPath: UriComponents | { light: UriComponents; dark: UriComponents } | ThemeIcon | undefined, isHidden: boolean | undefined, inputBoxDocumentUri: UriComponents): Promise { this._repositoryBarriers.set(handle, new Barrier()); const inputBoxTextModelRef = await this.textModelService.createModelReference(URI.revive(inputBoxDocumentUri)); - const provider = new MainThreadSCMProvider(this._proxy, handle, parentHandle, id, label, rootUri ? URI.revive(rootUri) : undefined, getIconFromIconDto(iconPath), inputBoxTextModelRef.object.textEditorModel, this.quickDiffService, this._uriIdentService, this.workspaceContextService); + const provider = new MainThreadSCMProvider(this._proxy, handle, parentHandle, id, label, rootUri ? URI.revive(rootUri) : undefined, getIconFromIconDto(iconPath), isHidden, inputBoxTextModelRef.object.textEditorModel, this.quickDiffService, this._uriIdentService, this.workspaceContextService); const repository = this.scmService.registerSCMProvider(provider); this._repositories.set(handle, repository); diff --git a/src/vs/workbench/api/common/extHost.api.impl.ts b/src/vs/workbench/api/common/extHost.api.impl.ts index 105a583456a..8fc90fe1cb9 100644 --- a/src/vs/workbench/api/common/extHost.api.impl.ts +++ b/src/vs/workbench/api/common/extHost.api.impl.ts @@ -1293,11 +1293,11 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I return extHostSCM.getLastInputBox(extension)!; // Strict null override - Deprecated api }, - createSourceControl(id: string, label: string, rootUri?: vscode.Uri, iconPath?: vscode.IconPath, parent?: vscode.SourceControl): vscode.SourceControl { - if (iconPath || parent) { + createSourceControl(id: string, label: string, rootUri?: vscode.Uri, iconPath?: vscode.IconPath, isHidden?: boolean, parent?: vscode.SourceControl): vscode.SourceControl { + if (iconPath || isHidden || parent) { checkProposedApiEnabled(extension, 'scmProviderOptions'); } - return extHostSCM.createSourceControl(extension, id, label, rootUri, iconPath, parent); + return extHostSCM.createSourceControl(extension, id, label, rootUri, iconPath, isHidden, parent); } }; diff --git a/src/vs/workbench/api/common/extHost.protocol.ts b/src/vs/workbench/api/common/extHost.protocol.ts index d1367994bb8..d24a2d7b932 100644 --- a/src/vs/workbench/api/common/extHost.protocol.ts +++ b/src/vs/workbench/api/common/extHost.protocol.ts @@ -1771,7 +1771,7 @@ export interface SCMArtifactDto { } export interface MainThreadSCMShape extends IDisposable { - $registerSourceControl(handle: number, parentHandle: number | undefined, id: string, label: string, rootUri: UriComponents | undefined, iconPath: IconPathDto | undefined, inputBoxDocumentUri: UriComponents): Promise; + $registerSourceControl(handle: number, parentHandle: number | undefined, id: string, label: string, rootUri: UriComponents | undefined, iconPath: IconPathDto | undefined, isHidden: boolean | undefined, inputBoxDocumentUri: UriComponents): Promise; $updateSourceControl(handle: number, features: SCMProviderFeatures): Promise; $unregisterSourceControl(handle: number): Promise; diff --git a/src/vs/workbench/api/common/extHostSCM.ts b/src/vs/workbench/api/common/extHostSCM.ts index 6fdc95a8c83..57a6ef48497 100644 --- a/src/vs/workbench/api/common/extHostSCM.ts +++ b/src/vs/workbench/api/common/extHostSCM.ts @@ -819,6 +819,7 @@ class ExtHostSourceControl implements vscode.SourceControl { private _label: string, private _rootUri?: vscode.Uri, _iconPath?: vscode.IconPath, + _isHidden?: boolean, _parent?: ExtHostSourceControl ) { this.#proxy = proxy; @@ -830,7 +831,7 @@ class ExtHostSourceControl implements vscode.SourceControl { }); this._inputBox = new ExtHostSCMInputBox(_extension, _extHostDocuments, this.#proxy, this.handle, inputBoxDocumentUri); - this.#proxy.$registerSourceControl(this.handle, _parent?.handle, _id, _label, _rootUri, getHistoryItemIconDto(_iconPath), inputBoxDocumentUri); + this.#proxy.$registerSourceControl(this.handle, _parent?.handle, _id, _label, _rootUri, getHistoryItemIconDto(_iconPath), _isHidden, inputBoxDocumentUri); this.onDidDisposeParent = _parent ? _parent.onDidDispose : Event.None; } @@ -1003,7 +1004,7 @@ export class ExtHostSCM implements ExtHostSCMShape { }); } - createSourceControl(extension: IExtensionDescription, id: string, label: string, rootUri: vscode.Uri | undefined, iconPath: vscode.IconPath | undefined, parent: vscode.SourceControl | undefined): vscode.SourceControl { + createSourceControl(extension: IExtensionDescription, id: string, label: string, rootUri: vscode.Uri | undefined, iconPath: vscode.IconPath | undefined, isHidden: boolean | undefined, parent: vscode.SourceControl | undefined): vscode.SourceControl { this.logService.trace('ExtHostSCM#createSourceControl', extension.identifier.value, id, label, rootUri); type TEvent = { extensionId: string }; @@ -1017,7 +1018,7 @@ export class ExtHostSCM implements ExtHostSCMShape { }); const parentSourceControl = parent ? Iterable.find(this._sourceControls.values(), s => s === parent) : undefined; - const sourceControl = new ExtHostSourceControl(extension, this._extHostDocuments, this._proxy, this._commands, id, label, rootUri, iconPath, parentSourceControl); + const sourceControl = new ExtHostSourceControl(extension, this._extHostDocuments, this._proxy, this._commands, id, label, rootUri, iconPath, isHidden, parentSourceControl); this._sourceControls.set(sourceControl.handle, sourceControl); const sourceControls = this._sourceControlsByExtension.get(extension.identifier) || []; diff --git a/src/vs/workbench/contrib/scm/browser/scmViewService.ts b/src/vs/workbench/contrib/scm/browser/scmViewService.ts index 4951923b73e..f9b2be053c3 100644 --- a/src/vs/workbench/contrib/scm/browser/scmViewService.ts +++ b/src/vs/workbench/contrib/scm/browser/scmViewService.ts @@ -123,7 +123,9 @@ export class SCMViewService implements ISCMViewService { private _repositories: ISCMRepositoryView[] = []; get repositories(): ISCMRepository[] { - return this._repositories.map(r => r.repository); + return this._repositories + .filter(r => r.repository.provider.isHidden !== true) + .map(r => r.repository); } readonly didFinishLoadingRepositories = observableValue(this, false); diff --git a/src/vs/workbench/contrib/scm/common/scm.ts b/src/vs/workbench/contrib/scm/common/scm.ts index 57ec30eb46a..63a864b9faa 100644 --- a/src/vs/workbench/contrib/scm/common/scm.ts +++ b/src/vs/workbench/contrib/scm/common/scm.ts @@ -82,6 +82,7 @@ export interface ISCMProvider extends IDisposable { readonly rootUri?: URI; readonly iconPath?: URI | { light: URI; dark: URI } | ThemeIcon; + readonly isHidden?: boolean; readonly inputBoxTextModel: ITextModel; readonly contextValue: IObservable; readonly count: IObservable; diff --git a/src/vscode-dts/vscode.proposed.scmProviderOptions.d.ts b/src/vscode-dts/vscode.proposed.scmProviderOptions.d.ts index b7869f61da8..8ef67d72d20 100644 --- a/src/vscode-dts/vscode.proposed.scmProviderOptions.d.ts +++ b/src/vscode-dts/vscode.proposed.scmProviderOptions.d.ts @@ -34,6 +34,6 @@ declare module 'vscode' { } export namespace scm { - export function createSourceControl(id: string, label: string, rootUri?: Uri, iconPath?: IconPath, parent?: SourceControl): SourceControl; + export function createSourceControl(id: string, label: string, rootUri?: Uri, iconPath?: IconPath, isHidden?: boolean, parent?: SourceControl): SourceControl; } }