Skip to content

Commit 05e342c

Browse files
lramos15Copilot
andcommitted
Merge main and fix unified workspace setting references
Use the Sessions-owned workspace picker setting in the provider and its tests to fix CI compilation and platform test failures. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2 parents 47f01bb + 42b37b7 commit 05e342c

34 files changed

Lines changed: 2022 additions & 324 deletions

build/azure-pipelines/product-build.yml

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -44,9 +44,9 @@ parameters:
4444
- Product
4545
- CI
4646
- name: VSCODE_BUILD_TITLE_PREFIX
47-
displayName: "Build title prefix"
47+
displayName: "Build Title Prefix"
4848
type: string
49-
default: ''
49+
default: none
5050
- name: NPM_REGISTRY
5151
displayName: "Custom NPM Registry"
5252
type: string
@@ -139,6 +139,11 @@ parameters:
139139
default: auto
140140

141141
variables:
142+
- name: VSCODE_BUILD_TITLE_PREFIX
143+
${{ if eq(parameters.VSCODE_BUILD_TITLE_PREFIX, 'none') }}:
144+
value: ""
145+
${{ else }}:
146+
value: ${{ parameters.VSCODE_BUILD_TITLE_PREFIX }}
142147
- name: VSCODE_PRIVATE_BUILD
143148
value: ${{ ne(variables['Build.Repository.Uri'], 'https://github.com/microsoft/vscode.git') }}
144149
- name: NPM_REGISTRY
@@ -221,7 +226,7 @@ variables:
221226
- name: ARTIFACT_PREFIX
222227
value: ''
223228

224-
name: "${{ parameters.VSCODE_BUILD_TITLE_PREFIX }}$(Date:yyyyMMdd).$(Rev:r) (${{ parameters.VSCODE_QUALITY }})"
229+
name: "$(VSCODE_BUILD_TITLE_PREFIX)$(Date:yyyyMMdd).$(Rev:r) (${{ parameters.VSCODE_QUALITY }})"
225230

226231
resources:
227232
repositories:

extensions/copilot/src/extension/chatSessions/vscode-node/copilotCloudSessionsProvider.ts

Lines changed: 29 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ import { IVSCodeExtensionContext } from '../../../platform/extContext/common/ext
1414
import { IFileSystemService } from '../../../platform/filesystem/common/fileSystemService';
1515
import { FileType } from '../../../platform/filesystem/common/fileTypes';
1616
import { IGitExtensionService } from '../../../platform/git/common/gitExtensionService';
17-
import { GithubRepoId, IGitService } from '../../../platform/git/common/gitService';
17+
import { getGithubRepoIdFromFetchUrl, GithubRepoId, IGitService, toGithubNwo } from '../../../platform/git/common/gitService';
1818
import { derivePullRequestState, PullRequestSearchItem } from '../../../platform/github/common/githubAPI';
1919
import { CCAEnabledResult, IGithubRepositoryService, IOctoKitService } from '../../../platform/github/common/githubService';
2020
import { getModelCapabilitiesDescription, normalizeTokenPrices } from '../../conversation/common/languageModelAccess';
@@ -247,6 +247,30 @@ export function parseGitHubContextUrl(value: string, kind: 'issue' | 'pullReques
247247
};
248248
}
249249

250+
export async function resolveGitHubContextRepository(gitService: IGitService, repository: string | vscode.Uri | undefined): Promise<string | undefined> {
251+
if (!repository || typeof repository === 'string') {
252+
return repository;
253+
}
254+
255+
const repositoryInfo = await gitService.getRepositoryFetchUrls(repository);
256+
for (const remoteUrl of repositoryInfo?.remoteFetchUrls ?? []) {
257+
const repositoryId = remoteUrl && getGithubRepoIdFromFetchUrl(remoteUrl);
258+
if (repositoryId) {
259+
return toGithubNwo(repositoryId);
260+
}
261+
}
262+
return undefined;
263+
}
264+
265+
export async function resolveOrPickGitHubContextRepository(
266+
gitService: IGitService,
267+
repository: string | vscode.Uri | undefined,
268+
pickRepository: () => Promise<string | undefined>,
269+
): Promise<string | undefined> {
270+
const repositoryId = await resolveGitHubContextRepository(gitService, repository);
271+
return repository && !repositoryId ? pickRepository() : repositoryId;
272+
}
273+
250274
/** Context key gating the chat-input "Create pull request" toolbar action: true while the viewed cloud task is settled and has no PR yet. */
251275
const CAN_CREATE_PULL_REQUEST_CONTEXT_KEY = 'github.copilot.chat.cloudTaskCanCreatePullRequest';
252276
/** Context key gating the chat-input "Open pull request" toolbar action: true once the viewed cloud task has a pull request. */
@@ -767,8 +791,10 @@ export class CopilotCloudSessionsProvider extends Disposable implements vscode.C
767791
}));
768792
});
769793
};
770-
this._register(vscode.commands.registerCommand(OPEN_ISSUE_COMMAND_ID, (repoId?: string) => openGitHubContext('issue', repoId)));
771-
this._register(vscode.commands.registerCommand(OPEN_PULL_REQUEST_COMMAND_ID, (repoId?: string) => openGitHubContext('pullRequest', repoId)));
794+
this._register(vscode.commands.registerCommand(OPEN_ISSUE_COMMAND_ID, async (repository?: string | vscode.Uri) =>
795+
openGitHubContext('issue', await resolveOrPickGitHubContextRepository(this._gitService, repository, openRepositoryCommand))));
796+
this._register(vscode.commands.registerCommand(OPEN_PULL_REQUEST_COMMAND_ID, async (repository?: string | vscode.Uri) =>
797+
openGitHubContext('pullRequest', await resolveOrPickGitHubContextRepository(this._gitService, repository, openRepositoryCommand))));
772798

773799
this._register(vscode.commands.registerCommand(CLEAR_CACHES_COMMAND_ID, () => {
774800
this.logService.debug('copilotCloudSessionsProvider#clearCaches: clearing all cloud agent caches');

extensions/copilot/src/extension/chatSessions/vscode-node/test/copilotCloudSessionsProvider.spec.ts

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ import { mock } from '../../../../util/common/test/simpleMock';
1212
import { ChatRequestTurn2, ChatResponseMarkdownPart, ChatResponseTurn2, ChatToolInvocationPart } from '../../../../vscodeTypes';
1313
import { ITaskApiClient, ListTaskEventsOptions, ListTasksOptions } from '../../common/taskApiTypes';
1414
import { ChatSessionContentBuilder, extractTaskErrorDetail, formatTaskStoppedMessage } from '../copilotCloudSessionContentBuilder';
15-
import { formatNewSessionContextReference, getCloudSessionItemMetadata, getCloudSessionResources, normalizeInitialSessionOptions, parseGitHubContextUrl, taskStateToChatSessionStatus } from '../copilotCloudSessionsProvider';
15+
import { formatNewSessionContextReference, getCloudSessionItemMetadata, getCloudSessionResources, normalizeInitialSessionOptions, parseGitHubContextUrl, resolveGitHubContextRepository, resolveOrPickGitHubContextRepository, taskStateToChatSessionStatus } from '../copilotCloudSessionsProvider';
1616
import { TaskApiBackend, parseRepoFromTaskUrl, isCloudCodingAgentTask } from '../taskApiBackend';
1717
import { isActiveTaskState, isFailedTaskState } from '../../vscode/copilotCodingAgentUtils';
1818
import { NullCloudBackendInstrumentation } from '../cloudBackendTelemetry';
@@ -82,6 +82,37 @@ describe('copilotCloudSessionsProvider helpers', () => {
8282
});
8383
});
8484

85+
it('resolves a GitHub context repository from the selected workspace folder', async () => {
86+
const gitService = new TestGitService();
87+
gitService.getRepositoryFetchUrls = vi.fn(async () => ({
88+
rootUri: vscode.Uri.file('/workspace/docs'),
89+
remoteFetchUrls: ['https://github.com/microsoft/vscode-docs.git'],
90+
}));
91+
92+
expect({
93+
folder: await resolveGitHubContextRepository(gitService, vscode.Uri.file('/workspace/docs')),
94+
repository: await resolveGitHubContextRepository(gitService, 'microsoft/vscode'),
95+
}).toEqual({
96+
folder: 'microsoft/vscode-docs',
97+
repository: 'microsoft/vscode',
98+
});
99+
});
100+
101+
it('offers repository selection only when a selected folder cannot be resolved', async () => {
102+
const gitService = new TestGitService();
103+
gitService.getRepositoryFetchUrls = vi.fn(async () => undefined);
104+
const pickRepository = vi.fn(async () => 'microsoft/vscode');
105+
106+
expect({
107+
selectedFolder: await resolveOrPickGitHubContextRepository(gitService, vscode.Uri.file('/workspace/vscode'), pickRepository),
108+
noFolder: await resolveOrPickGitHubContextRepository(gitService, undefined, pickRepository),
109+
}).toEqual({
110+
selectedFolder: 'microsoft/vscode',
111+
noFolder: undefined,
112+
});
113+
expect(pickRepository).toHaveBeenCalledTimes(1);
114+
});
115+
85116
it('coerces object-shaped initialSessionOptions into option entries', () => {
86117
const logService = new RecordingLogService();
87118
const sessionResource = vscode.Uri.parse('copilot-cloud-agent:/1');

extensions/copilot/src/platform/git/vscode-node/gitServiceImpl.ts

Lines changed: 53 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ import { isEqual } from '../../../util/vs/base/common/resources';
2121
import { URI } from '../../../util/vs/base/common/uri';
2222
import { ILogService } from '../../log/common/logService';
2323
import { IGitExtensionService } from '../common/gitExtensionService';
24-
import { IGitService, RepoContext } from '../common/gitService';
24+
import { getOrderedRemoteUrlsFromContext, IGitService, RepoContext } from '../common/gitService';
2525
import { parseGitRemotes } from '../common/utils';
2626
import { API, APIState, Branch, Change, CommitOptions, CommitShortStat, DiffChange, Ref, RefQuery, Repository, RepositoryAccessDetails } from '../vscode/git';
2727

@@ -197,6 +197,30 @@ export class GitServiceImpl extends Disposable implements IGitService {
197197
async getRepositoryFetchUrls(uri: URI): Promise<Pick<RepoContext, 'rootUri' | 'remoteFetchUrls'> | undefined> {
198198
this.logService.trace(`[GitServiceImpl][getRepositoryFetchUrls] URI: ${uri.toString()}`);
199199

200+
if (uri.scheme === 'file') {
201+
try {
202+
const uriStat = await vscode.workspace.fs.stat(uri);
203+
if (uriStat.type === vscode.FileType.Directory) {
204+
const config = await this.readLocalGitConfig(uri);
205+
const parsedRemotes = parseGitRemotes(config);
206+
const origin = parsedRemotes.find(remote => remote.name === 'origin');
207+
const orderedRemotes = origin
208+
? [origin, ...parsedRemotes.filter(remote => remote !== origin)]
209+
: parsedRemotes;
210+
const remotes = {
211+
rootUri: uri,
212+
remoteFetchUrls: orderedRemotes.map(remote => remote.fetchUrl),
213+
};
214+
if (remotes.remoteFetchUrls.length > 0) {
215+
this.logService.trace(`[GitServiceImpl][getRepositoryFetchUrls] Remotes (direct .git/config): ${JSON.stringify(remotes)}`);
216+
return remotes;
217+
}
218+
}
219+
} catch (error) {
220+
this.logService.trace(`[GitServiceImpl][getRepositoryFetchUrls] Could not read remotes directly from .git/config: ${error.message}`);
221+
}
222+
}
223+
200224
// Answering before discovery settles reports the file as belonging to no repository, which
201225
// content exclusion reads as "no repository rules apply to this file".
202226
await this.waitForInitialDiscovery();
@@ -209,11 +233,10 @@ export class GitServiceImpl extends Disposable implements IGitService {
209233
// Query opened repositories
210234
const repository = gitAPI.getRepository(uri);
211235
if (repository) {
212-
await this.waitForRepositoryState(repository);
213-
236+
const repositoryContext = GitServiceImpl.repoToRepoContext(repository);
214237
const remotes = {
215238
rootUri: repository.rootUri,
216-
remoteFetchUrls: repository.state.remotes.map(r => r.fetchUrl),
239+
remoteFetchUrls: repositoryContext ? Array.from(getOrderedRemoteUrlsFromContext(repositoryContext)) : [],
217240
};
218241

219242
this.logService.trace(`[GitServiceImpl][getRepositoryFetchUrls] Remotes (open repository): ${JSON.stringify(remotes)}`);
@@ -254,6 +277,32 @@ export class GitServiceImpl extends Disposable implements IGitService {
254277
}
255278
}
256279

280+
private async readLocalGitConfig(rootUri: URI): Promise<string> {
281+
const dotGitUri = URI.file(path.join(rootUri.fsPath, '.git'));
282+
const dotGitStat = await vscode.workspace.fs.stat(dotGitUri);
283+
let gitDirectory = dotGitUri.fsPath;
284+
285+
if (dotGitStat.type === vscode.FileType.File) {
286+
const dotGit = (await vscode.workspace.fs.readFile(dotGitUri)).toString();
287+
const gitDirectoryMatch = /^gitdir:\s*(?<path>.+)\s*$/m.exec(dotGit);
288+
if (!gitDirectoryMatch?.groups?.path) {
289+
throw new Error(`Invalid Git directory pointer: ${dotGitUri.fsPath}`);
290+
}
291+
gitDirectory = path.resolve(rootUri.fsPath, gitDirectoryMatch.groups.path);
292+
293+
try {
294+
const commonDirectory = (await vscode.workspace.fs.readFile(URI.file(path.join(gitDirectory, 'commondir')))).toString().trim();
295+
if (commonDirectory) {
296+
gitDirectory = path.resolve(gitDirectory, commonDirectory);
297+
}
298+
} catch (error) {
299+
this.logService.trace(`[GitServiceImpl][readLocalGitConfig] No common Git directory for ${gitDirectory}: ${error.message}`);
300+
}
301+
}
302+
303+
return (await vscode.workspace.fs.readFile(URI.file(path.join(gitDirectory, 'config')))).toString();
304+
}
305+
257306
async add(uri: URI, paths: string[]): Promise<void> {
258307
const gitAPI = this.gitExtensionService.getExtensionApi();
259308
const repository = gitAPI?.getRepository(uri);
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
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+
export const OPEN_CUSTOMIZATIONS_COMMAND_ID = 'sessions.customization.overview';

src/vs/sessions/contrib/aiCustomizationTreeView/browser/aiCustomizationTreeView.contribution.ts

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -21,9 +21,13 @@ import { IPromptsService, PromptsStorage } from '../../../../workbench/contrib/c
2121
import { IViewsService } from '../../../../workbench/services/views/common/viewsService.js';
2222
import { KeyCode, KeyMod } from '../../../../base/common/keyCodes.js';
2323
import { KeybindingWeight } from '../../../../platform/keybinding/common/keybindingsRegistry.js';
24-
import { SessionsView, SessionsViewId } from '../../sessions/browser/views/sessionsView.js';
2524
import { IsSessionsWindowContext } from '../../../../workbench/common/contextkeys.js';
2625
import { TerminalContextKeys } from '../../../../workbench/contrib/terminal/common/terminalContextKey.js';
26+
import { OPEN_CUSTOMIZATIONS_COMMAND_ID } from '../../../common/customizations.js';
27+
import { SessionsView, SessionsViewId } from '../../sessions/browser/views/sessionsView.js';
28+
import { IConfigurationService } from '../../../../platform/configuration/common/configuration.js';
29+
import { ChatConfiguration } from '../../../../workbench/contrib/chat/common/constants.js';
30+
import { ChatContextKeys } from '../../../../workbench/contrib/chat/common/actions/chatContextKeys.js';
2731

2832
//#region Utilities
2933

@@ -292,20 +296,23 @@ registerAction2(class extends Action2 {
292296
constructor() {
293297
super({
294298
id: FOCUS_AI_CUSTOMIZATION_VIEW_ID,
295-
title: localize2('focusCustomizations', "Focus Chat Customizations"),
299+
title: localize2('openCustomizations', "Open Chat Customizations"),
296300
category: AI_CUSTOMIZATION_CATEGORY,
297-
precondition: IsSessionsWindowContext,
301+
precondition: ContextKeyExpr.and(IsSessionsWindowContext, ChatContextKeys.enabled),
298302
f1: true,
299303
keybinding: {
300304
weight: KeybindingWeight.WorkbenchContrib,
301305
primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.KeyC,
302-
when: ContextKeyExpr.and(IsSessionsWindowContext, TerminalContextKeys.focus.negate()),
306+
when: ContextKeyExpr.and(IsSessionsWindowContext, ChatContextKeys.enabled, TerminalContextKeys.focus.negate()),
303307
},
304308
});
305309
}
306310
async run(accessor: ServicesAccessor): Promise<void> {
307-
const viewsService = accessor.get(IViewsService);
308-
const sessionsView = await viewsService.openView<SessionsView>(SessionsViewId, false);
311+
if (accessor.get(IConfigurationService).getValue<boolean>(ChatConfiguration.CustomizationEntryPoints)) {
312+
await accessor.get(ICommandService).executeCommand(OPEN_CUSTOMIZATIONS_COMMAND_ID);
313+
return;
314+
}
315+
const sessionsView = await accessor.get(IViewsService).openView<SessionsView>(SessionsViewId, false);
309316
sessionsView?.focusCustomizations();
310317
}
311318
});

src/vs/sessions/contrib/chat/browser/media/chatView.css

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,13 @@
7373
background-image: linear-gradient(var(--vscode-chat-requestBubbleHoverBackground), var(--vscode-chat-requestBubbleHoverBackground));
7474
}
7575

76+
/* The edit input takes the place of the request bubble, so it needs the same opaque base under the translucent bubble tint. */
77+
.agent-sessions-workbench .part.sessionspart.has-chat-background .chat-view .interactive-session .interactive-input-part.editing .chat-input-container,
78+
.agent-sessions-workbench .part.sessionspart.has-chat-background .chat-view .interactive-session .interactive-request.editing .interactive-input-part .chat-input-container {
79+
background-color: var(--session-view-background);
80+
background-image: linear-gradient(var(--vscode-chat-requestBubbleBackground), var(--vscode-chat-requestBubbleBackground));
81+
}
82+
7683
.agent-sessions-workbench .interactive-session {
7784
max-width: none;
7885
}

src/vs/sessions/contrib/chat/browser/media/chatWidget.css

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -307,10 +307,27 @@
307307
font-size: var(--vscode-codiconFontSize-compact);
308308
}
309309

310+
.sessions-workspace-category-picker > .sessions-customize-trigger-slot {
311+
margin-left: auto;
312+
}
313+
310314
.action-widget .sessions-new-chat-picker-list .monaco-list-row.action .codicon {
311315
font-size: var(--vscode-codiconFontSize);
312316
}
313317

318+
.sessions-customize-migration-indicator {
319+
display: none;
320+
flex-shrink: 0;
321+
width: var(--vscode-spacing-size80);
322+
height: var(--vscode-spacing-size80);
323+
border-radius: var(--vscode-cornerRadius-circle);
324+
background-color: var(--vscode-notificationsWarningIcon-foreground);
325+
}
326+
327+
.sessions-customize-trigger.has-migrations .sessions-customize-migration-indicator {
328+
display: block;
329+
}
330+
314331
.sessions-workspace-category-picker .sessions-chat-dropdown-label {
315332
margin-left: 0;
316333
}

0 commit comments

Comments
 (0)