Skip to content

Commit fbd7565

Browse files
authored
Merge branch 'main' into copilot/fix-258895
2 parents ed44f1b + d1ad717 commit fbd7565

File tree

79 files changed

+1018
-523
lines changed

Some content is hidden

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

79 files changed

+1018
-523
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: 91 additions & 36 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

@@ -3397,8 +3407,36 @@ export class CommandCenter {
33973407
}
33983408
}
33993409

3400-
@command('git.createWorktree', { repository: true, repositoryFilter: ['repository', 'submodule'] })
3401-
async createWorktree(repository: Repository): Promise<void> {
3410+
@command('git.createWorktree')
3411+
async createWorktree(repository: any): Promise<void> {
3412+
repository = this.model.getRepository(repository);
3413+
3414+
if (!repository) {
3415+
// Single repository/submodule/worktree
3416+
if (this.model.repositories.length === 1) {
3417+
repository = this.model.repositories[0];
3418+
}
3419+
}
3420+
3421+
if (!repository) {
3422+
// Single repository/submodule
3423+
const repositories = this.model.repositories
3424+
.filter(r => r.kind === 'repository' || r.kind === 'submodule');
3425+
3426+
if (repositories.length === 1) {
3427+
repository = repositories[0];
3428+
}
3429+
}
3430+
3431+
if (!repository) {
3432+
// Multiple repositories/submodules
3433+
repository = await this.model.pickRepository(['repository', 'submodule']);
3434+
}
3435+
3436+
if (!repository) {
3437+
return;
3438+
}
3439+
34023440
await this._createWorktree(repository);
34033441
}
34043442

@@ -3442,16 +3480,14 @@ export class CommandCenter {
34423480
return;
34433481
}
34443482

3445-
// If the worktree is locked, we prompt to create a new branch
3446-
// otherwise we can use the existing selected branch or tag
3447-
const isWorktreeLocked = await this.isWorktreeLocked(repository, choice);
3448-
if (isWorktreeLocked) {
3449-
branch = await this.promptForBranchName(repository);
3450-
3451-
if (!branch) {
3452-
return;
3453-
}
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;
34543489
}
3490+
34553491
commitish = choice.refName;
34563492
}
34573493

@@ -3488,6 +3524,14 @@ export class CommandCenter {
34883524
return [start, value.length];
34893525
};
34903526

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+
34913535
// Default worktree path is based on the last worktree location or a worktree folder for the repository
34923536
const defaultWorktreeRoot = this.globalState.get<string>(`${CommandCenter.WORKTREE_ROOT_KEY}:${repository.root}`);
34933537
const defaultWorktreePath = defaultWorktreeRoot
@@ -3502,6 +3546,7 @@ export class CommandCenter {
35023546
inputBox.prompt = l10n.t('Please provide a worktree path');
35033547
inputBox.value = defaultWorktreePath;
35043548
inputBox.valueSelection = getValueSelection(inputBox.value);
3549+
inputBox.validationMessage = getValidationMessage(inputBox.value);
35053550
inputBox.ignoreFocusOut = true;
35063551
inputBox.buttons = [
35073552
{
@@ -3516,6 +3561,9 @@ export class CommandCenter {
35163561
const worktreePath = await new Promise<string | undefined>((resolve) => {
35173562
disposables.push(inputBox.onDidHide(() => resolve(undefined)));
35183563
disposables.push(inputBox.onDidAccept(() => resolve(inputBox.value)));
3564+
disposables.push(inputBox.onDidChangeValue(value => {
3565+
inputBox.validationMessage = getValidationMessage(value);
3566+
}));
35193567
disposables.push(inputBox.onDidTriggerButton(async () => {
35203568
inputBox.value = await getWorktreePath() ?? '';
35213569
inputBox.valueSelection = getValueSelection(inputBox.value);
@@ -3537,57 +3585,64 @@ export class CommandCenter {
35373585
this.globalState.update(`${CommandCenter.WORKTREE_ROOT_KEY}:${repository.root}`, worktreeRoot);
35383586
}
35393587
} catch (err) {
3540-
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 {
35413593
throw err;
35423594
}
35433595

3544-
this.handleWorktreeError(err);
35453596
return;
35463597
}
35473598
}
35483599

3549-
// If the user picks a branch that is present in any of the worktrees or the current branch, return true
3550-
// Otherwise, return false.
3551-
private async isWorktreeLocked(repository: Repository, choice: RefItem): Promise<boolean> {
3552-
if (!choice.refName) {
3553-
return false;
3554-
}
3555-
3556-
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 '([^']+)'/);
35573602

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

3561-
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);
35623610
}
35633611

3564-
private async handleWorktreeError(err: any): Promise<void> {
3565-
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+
35663615
if (!match) {
35673616
return;
35683617
}
35693618

3570-
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+
35713627
const worktreeRepository = this.model.getRepository(path);
35723628

35733629
if (!worktreeRepository) {
35743630
return;
35753631
}
35763632

3577-
const openWorktree = l10n.t('Open in Current Window');
3578-
const openWorktreeInNewWindow = l10n.t('Open in New Window');
3579-
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');
35803635
const choice = await window.showWarningMessage(message, { modal: true }, openWorktree, openWorktreeInNewWindow);
35813636

35823637
if (choice === openWorktree) {
35833638
await this.openWorktreeInCurrentWindow(worktreeRepository);
35843639
} else if (choice === openWorktreeInNewWindow) {
35853640
await this.openWorktreeInNewWindow(worktreeRepository);
35863641
}
3587-
35883642
return;
35893643
}
35903644

3645+
35913646
@command('git.deleteWorktree', { repository: true, repositoryFilter: ['worktree'] })
35923647
async deleteWorktree(repository: Repository): Promise<void> {
35933648
if (!repository.dotGit.commonPath) {
@@ -3640,7 +3695,7 @@ export class CommandCenter {
36403695
}
36413696
}
36423697

3643-
@command('git.openWorktree', { repository: true, repositoryFilter: ['worktree'] })
3698+
@command('git.openWorktree', { repository: true })
36443699
async openWorktreeInCurrentWindow(repository: Repository): Promise<void> {
36453700
if (!repository) {
36463701
return;
@@ -3650,7 +3705,7 @@ export class CommandCenter {
36503705
await commands.executeCommand('vscode.openFolder', uri, { forceReuseWindow: true });
36513706
}
36523707

3653-
@command('git.openWorktreeInNewWindow', { repository: true, repositoryFilter: ['worktree'] })
3708+
@command('git.openWorktreeInNewWindow', { repository: true })
36543709
async openWorktreeInNewWindow(repository: Repository): Promise<void> {
36553710
if (!repository) {
36563711
return;

extensions/git/src/git.ts

Lines changed: 5 additions & 9 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

@@ -2786,14 +2787,9 @@ export class Repository {
27862787
return [];
27872788
}
27882789

2789-
if (this.kind !== 'repository') {
2790-
this.logger.info('[Git][getWorktreesFS] Either a submodule or a worktree, skipping worktree detection');
2791-
return [];
2792-
}
2793-
27942790
try {
27952791
// List all worktree folder names
2796-
const worktreesPath = path.join(this.repositoryRoot, '.git', 'worktrees');
2792+
const worktreesPath = path.join(this.dotGit.commonPath ?? this.dotGit.path, 'worktrees');
27972793
const dirents = await fs.readdir(worktreesPath, { withFileTypes: true });
27982794
const result: Worktree[] = [];
27992795

@@ -2812,7 +2808,7 @@ export class Repository {
28122808
result.push({
28132809
name: dirent.name,
28142810
// Remove '/.git' suffix
2815-
path: gitdirContent.replace(/\.git.*$/, ''),
2811+
path: gitdirContent.replace(/\/.git.*$/, ''),
28162812
// Remove 'ref: ' prefix
28172813
ref: headContent.replace(/^ref: /, ''),
28182814
});

extensions/git/src/model.ts

Lines changed: 5 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);
@@ -783,6 +770,11 @@ export class Model implements IRepositoryResolver, IBranchProtectionProviderRegi
783770
return;
784771
}
785772

773+
if (repository.kind === 'worktree') {
774+
this.logger.trace('[Model][open] Automatic detection of git worktrees is not skipped.');
775+
return;
776+
}
777+
786778
if (repository.worktrees.length > worktreesLimit) {
787779
window.showWarningMessage(l10n.t('The "{0}" repository has {1} worktrees which won\'t be opened automatically. You can still open each one individually by opening a file within.', path.basename(repository.root), repository.worktrees.length));
788780
statusListener.dispose();

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.

0 commit comments

Comments
 (0)