Skip to content

Commit 499de12

Browse files
authored
Merge branch 'main' into copilot/fix-259000
2 parents 9290de6 + d1ad717 commit 499de12

File tree

71 files changed

+778
-468
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

71 files changed

+778
-468
lines changed

build/lib/stylelint/vscode-known-variables.json

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -52,17 +52,17 @@
5252
"--vscode-charts-yellow",
5353
"--vscode-chat-avatarBackground",
5454
"--vscode-chat-avatarForeground",
55+
"--vscode-chat-checkpointSeparator",
5556
"--vscode-chat-editedFileForeground",
57+
"--vscode-chat-linesAddedForeground",
58+
"--vscode-chat-linesRemovedForeground",
5659
"--vscode-chat-requestBackground",
5760
"--vscode-chat-requestBorder",
5861
"--vscode-chat-requestBubbleBackground",
5962
"--vscode-chat-requestBubbleHoverBackground",
60-
"--vscode-chat-checkpointSeparator",
6163
"--vscode-chat-requestCodeBorder",
6264
"--vscode-chat-slashCommandBackground",
6365
"--vscode-chat-slashCommandForeground",
64-
"--vscode-chat-linesAddedForeground",
65-
"--vscode-chat-linesRemovedForeground",
6666
"--vscode-checkbox-background",
6767
"--vscode-checkbox-border",
6868
"--vscode-checkbox-disabled-background",
@@ -322,9 +322,9 @@
322322
"--vscode-editorPane-background",
323323
"--vscode-editorRuler-foreground",
324324
"--vscode-editorStickyScroll-background",
325-
"--vscode-editorStickyScrollGutter-background",
326325
"--vscode-editorStickyScroll-border",
327326
"--vscode-editorStickyScroll-shadow",
327+
"--vscode-editorStickyScrollGutter-background",
328328
"--vscode-editorStickyScrollHover-background",
329329
"--vscode-editorSuggestWidget-background",
330330
"--vscode-editorSuggestWidget-border",
@@ -807,6 +807,8 @@
807807
"--vscode-terminalSymbolIcon-methodForeground",
808808
"--vscode-terminalSymbolIcon-optionForeground",
809809
"--vscode-terminalSymbolIcon-optionValueForeground",
810+
"--vscode-terminalSymbolIcon-symbolicLinkFileForeground",
811+
"--vscode-terminalSymbolIcon-symbolicLinkFolderForeground",
810812
"--vscode-testing-coverCountBadgeBackground",
811813
"--vscode-testing-coverCountBadgeForeground",
812814
"--vscode-testing-coveredBackground",
@@ -963,4 +965,4 @@
963965
"--animation-opacity",
964966
"--chat-setup-dialog-glow-angle"
965967
]
966-
}
968+
}

cli/src/commands/serve_web.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,12 @@ pub async fn serve_web(ctx: CommandContext, mut args: ServeWebArgs) -> Result<i3
9191
if args.commit_id.is_none() {
9292
cm.clone()
9393
.start_update_checker(Duration::from_secs(update_check_interval));
94+
} else {
95+
// If a commit was provided, invoke get_latest_release() once to ensure we're using that exact version;
96+
// get_latest_release() will short-circuit to args.commit_id.
97+
if let Err(e) = cm.get_latest_release().await {
98+
warning!(cm.log, "error getting latest version: {}", e);
99+
}
94100
}
95101

96102
let key = get_server_key_half(&ctx.paths);

extensions/git/src/api/git.d.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -442,5 +442,6 @@ export const enum GitErrorCodes {
442442
CherryPickEmpty = 'CherryPickEmpty',
443443
CherryPickConflict = 'CherryPickConflict',
444444
WorktreeContainsChanges = 'WorktreeContainsChanges',
445-
WorktreeAlreadyExists = 'WorktreeAlreadyExists'
445+
WorktreeAlreadyExists = 'WorktreeAlreadyExists',
446+
WorktreeBranchAlreadyUsed = 'WorktreeBranchAlreadyUsed'
446447
}

extensions/git/src/commands.ts

Lines changed: 59 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,16 @@ class RefItem implements QuickPickItem {
110110
return undefined;
111111
}
112112

113+
get refId(): string {
114+
switch (this.ref.type) {
115+
case RefType.Head:
116+
return `refs/heads/${this.ref.name}`;
117+
case RefType.RemoteHead:
118+
return `refs/remotes/${this.ref.remote}/${this.ref.name}`;
119+
case RefType.Tag:
120+
return `refs/tags/${this.ref.name}`;
121+
}
122+
}
113123
get refName(): string | undefined { return this.ref.name; }
114124
get refRemote(): string | undefined { return this.ref.remote; }
115125
get shortCommit(): string { return (this.ref.commit || '').substring(0, this.shortCommitLength); }
@@ -2923,12 +2933,12 @@ export class CommandCenter {
29232933
try {
29242934
await item.run(repository, opts);
29252935
} catch (err) {
2926-
if (err.gitErrorCode !== GitErrorCodes.DirtyWorkTree && err.gitErrorCode !== GitErrorCodes.WorktreeAlreadyExists) {
2936+
if (err.gitErrorCode !== GitErrorCodes.DirtyWorkTree && err.gitErrorCode !== GitErrorCodes.WorktreeBranchAlreadyUsed) {
29272937
throw err;
29282938
}
29292939

2930-
if (err.gitErrorCode === GitErrorCodes.WorktreeAlreadyExists) {
2931-
this.handleWorktreeError(err);
2940+
if (err.gitErrorCode === GitErrorCodes.WorktreeBranchAlreadyUsed) {
2941+
this.handleWorktreeBranchAlreadyUsed(err);
29322942
return false;
29332943
}
29342944

@@ -3470,16 +3480,14 @@ export class CommandCenter {
34703480
return;
34713481
}
34723482

3473-
// If the worktree is locked, we prompt to create a new branch
3474-
// otherwise we can use the existing selected branch or tag
3475-
const isWorktreeLocked = await this.isWorktreeLocked(repository, choice);
3476-
if (isWorktreeLocked) {
3477-
branch = await this.promptForBranchName(repository);
3478-
3479-
if (!branch) {
3480-
return;
3481-
}
3483+
// Check whether the selected branch is checked out in an existing worktree
3484+
const worktree = repository.worktrees.find(worktree => worktree.ref === choice.refId);
3485+
if (worktree) {
3486+
const message = l10n.t('Branch "{0}" is already checked out in the worktree at "{1}".', choice.refName, worktree.path);
3487+
await this.handleWorktreeConflict(worktree.path, message);
3488+
return;
34823489
}
3490+
34833491
commitish = choice.refName;
34843492
}
34853493

@@ -3516,6 +3524,14 @@ export class CommandCenter {
35163524
return [start, value.length];
35173525
};
35183526

3527+
const getValidationMessage = (value: string): InputBoxValidationMessage | undefined => {
3528+
const worktree = repository.worktrees.find(worktree => pathEquals(path.normalize(worktree.path), path.normalize(value)));
3529+
return worktree ? {
3530+
message: l10n.t('A worktree already exists at "{0}".', value),
3531+
severity: InputBoxValidationSeverity.Warning
3532+
} : undefined;
3533+
};
3534+
35193535
// Default worktree path is based on the last worktree location or a worktree folder for the repository
35203536
const defaultWorktreeRoot = this.globalState.get<string>(`${CommandCenter.WORKTREE_ROOT_KEY}:${repository.root}`);
35213537
const defaultWorktreePath = defaultWorktreeRoot
@@ -3530,6 +3546,7 @@ export class CommandCenter {
35303546
inputBox.prompt = l10n.t('Please provide a worktree path');
35313547
inputBox.value = defaultWorktreePath;
35323548
inputBox.valueSelection = getValueSelection(inputBox.value);
3549+
inputBox.validationMessage = getValidationMessage(inputBox.value);
35333550
inputBox.ignoreFocusOut = true;
35343551
inputBox.buttons = [
35353552
{
@@ -3544,6 +3561,9 @@ export class CommandCenter {
35443561
const worktreePath = await new Promise<string | undefined>((resolve) => {
35453562
disposables.push(inputBox.onDidHide(() => resolve(undefined)));
35463563
disposables.push(inputBox.onDidAccept(() => resolve(inputBox.value)));
3564+
disposables.push(inputBox.onDidChangeValue(value => {
3565+
inputBox.validationMessage = getValidationMessage(value);
3566+
}));
35473567
disposables.push(inputBox.onDidTriggerButton(async () => {
35483568
inputBox.value = await getWorktreePath() ?? '';
35493569
inputBox.valueSelection = getValueSelection(inputBox.value);
@@ -3565,57 +3585,64 @@ export class CommandCenter {
35653585
this.globalState.update(`${CommandCenter.WORKTREE_ROOT_KEY}:${repository.root}`, worktreeRoot);
35663586
}
35673587
} catch (err) {
3568-
if (err.gitErrorCode !== GitErrorCodes.WorktreeAlreadyExists) {
3588+
if (err.gitErrorCode === GitErrorCodes.WorktreeAlreadyExists) {
3589+
await this.handleWorktreeAlreadyExists(err);
3590+
} else if (err.gitErrorCode === GitErrorCodes.WorktreeBranchAlreadyUsed) {
3591+
await this.handleWorktreeBranchAlreadyUsed(err);
3592+
} else {
35693593
throw err;
35703594
}
35713595

3572-
this.handleWorktreeError(err);
35733596
return;
35743597
}
35753598
}
35763599

3577-
// If the user picks a branch that is present in any of the worktrees or the current branch, return true
3578-
// Otherwise, return false.
3579-
private async isWorktreeLocked(repository: Repository, choice: RefItem): Promise<boolean> {
3580-
if (!choice.refName) {
3581-
return false;
3582-
}
3583-
3584-
const worktrees = await repository.getWorktrees();
3600+
private async handleWorktreeBranchAlreadyUsed(err: any): Promise<void> {
3601+
const match = err.stderr.match(/fatal: '([^']+)' is already used by worktree at '([^']+)'/);
35853602

3586-
const isInWorktree = worktrees.some(worktree => worktree.ref === `refs/heads/${choice.refName}`);
3587-
const isCurrentBranch = repository.HEAD?.name === choice.refName;
3603+
if (!match) {
3604+
return;
3605+
}
35883606

3589-
return isInWorktree || isCurrentBranch;
3607+
const [, branch, path] = match;
3608+
const message = l10n.t('Branch "{0}" is already checked out in the worktree at "{1}".', branch, path);
3609+
await this.handleWorktreeConflict(path, message);
35903610
}
35913611

3592-
private async handleWorktreeError(err: any): Promise<void> {
3593-
const match = err.stderr.match(/^fatal: '([^']+)' is already used by worktree at '([^']+)'/);
3612+
private async handleWorktreeAlreadyExists(err: any): Promise<void> {
3613+
const match = err.stderr.match(/fatal: '([^']+)'/);
3614+
35943615
if (!match) {
35953616
return;
35963617
}
35973618

3598-
const [, branch, path] = match;
3619+
const [, path] = match;
3620+
const message = l10n.t('A worktree already exists at "{0}".', path);
3621+
await this.handleWorktreeConflict(path, message);
3622+
}
3623+
3624+
private async handleWorktreeConflict(path: string, message: string): Promise<void> {
3625+
await this.model.openRepository(path, true);
3626+
35993627
const worktreeRepository = this.model.getRepository(path);
36003628

36013629
if (!worktreeRepository) {
36023630
return;
36033631
}
36043632

3605-
const openWorktree = l10n.t('Open in Current Window');
3606-
const openWorktreeInNewWindow = l10n.t('Open in New Window');
3607-
const message = l10n.t('Branch \'{0}\' is already checked out in the worktree at \'{1}\'.', branch, path);
3633+
const openWorktree = l10n.t('Open Worktree in Current Window');
3634+
const openWorktreeInNewWindow = l10n.t('Open Worktree in New Window');
36083635
const choice = await window.showWarningMessage(message, { modal: true }, openWorktree, openWorktreeInNewWindow);
36093636

36103637
if (choice === openWorktree) {
36113638
await this.openWorktreeInCurrentWindow(worktreeRepository);
36123639
} else if (choice === openWorktreeInNewWindow) {
36133640
await this.openWorktreeInNewWindow(worktreeRepository);
36143641
}
3615-
36163642
return;
36173643
}
36183644

3645+
36193646
@command('git.deleteWorktree', { repository: true, repositoryFilter: ['worktree'] })
36203647
async deleteWorktree(repository: Repository): Promise<void> {
36213648
if (!repository.dotGit.commonPath) {

extensions/git/src/git.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -351,10 +351,11 @@ function getGitErrorCode(stderr: string): string | undefined {
351351
return GitErrorCodes.NotASafeGitRepository;
352352
} else if (/contains modified or untracked files|use --force to delete it/.test(stderr)) {
353353
return GitErrorCodes.WorktreeContainsChanges;
354-
} else if (/is already used by worktree at|already exists/.test(stderr)) {
354+
} else if (/fatal: '[^']+' already exists/.test(stderr)) {
355355
return GitErrorCodes.WorktreeAlreadyExists;
356+
} else if (/is already used by worktree at/.test(stderr)) {
357+
return GitErrorCodes.WorktreeBranchAlreadyUsed;
356358
}
357-
358359
return undefined;
359360
}
360361

extensions/git/src/model.ts

Lines changed: 0 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -647,19 +647,6 @@ export class Model implements IRepositoryResolver, IBranchProtectionProviderRegi
647647
// Open repository
648648
const [dotGit, repositoryRootRealPath] = await Promise.all([this.git.getRepositoryDotGit(repositoryRoot), this.getRepositoryRootRealPath(repositoryRoot)]);
649649
const gitRepository = this.git.open(repositoryRoot, repositoryRootRealPath, dotGit, this.logger);
650-
651-
// Check if the repository is a submodule/worktree and if they should be detected
652-
const detectSubmodules = config.get<boolean>('detectSubmodules', true) === true;
653-
const detectWorktrees = config.get<boolean>('detectWorktrees', true) === true;
654-
if ((gitRepository.kind === 'submodule' && !detectSubmodules) ||
655-
(gitRepository.kind === 'worktree' && !detectWorktrees)) {
656-
this.logger.info(`[Model][openRepository] Skip opening repository (path): ${repositoryRoot}`);
657-
this.logger.info(`[Model][openRepository] Skip opening repository (real path): ${repositoryRootRealPath ?? repositoryRoot}`);
658-
this.logger.info(`[Model][openRepository] Skip opening repository (kind): ${gitRepository.kind}`);
659-
660-
return;
661-
}
662-
663650
const repository = new Repository(gitRepository, this, this, this, this, this, this, this.globalState, this.logger, this.telemetryReporter);
664651

665652
this.open(repository);

extensions/package-lock.json

Lines changed: 4 additions & 4 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

extensions/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
"license": "MIT",
55
"description": "Dependencies shared by all extensions",
66
"dependencies": {
7-
"typescript": "^5.9.1-rc"
7+
"typescript": "^5.9.2"
88
},
99
"scripts": {
1010
"postinstall": "node ./postinstall.mjs"

0 commit comments

Comments
 (0)