@@ -110,6 +110,16 @@ class RefItem implements QuickPickItem {
110
110
return undefined ;
111
111
}
112
112
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
+ }
113
123
get refName ( ) : string | undefined { return this . ref . name ; }
114
124
get refRemote ( ) : string | undefined { return this . ref . remote ; }
115
125
get shortCommit ( ) : string { return ( this . ref . commit || '' ) . substring ( 0 , this . shortCommitLength ) ; }
@@ -2923,12 +2933,12 @@ export class CommandCenter {
2923
2933
try {
2924
2934
await item . run ( repository , opts ) ;
2925
2935
} catch ( err ) {
2926
- if ( err . gitErrorCode !== GitErrorCodes . DirtyWorkTree && err . gitErrorCode !== GitErrorCodes . WorktreeAlreadyExists ) {
2936
+ if ( err . gitErrorCode !== GitErrorCodes . DirtyWorkTree && err . gitErrorCode !== GitErrorCodes . WorktreeBranchAlreadyUsed ) {
2927
2937
throw err ;
2928
2938
}
2929
2939
2930
- if ( err . gitErrorCode === GitErrorCodes . WorktreeAlreadyExists ) {
2931
- this . handleWorktreeError ( err ) ;
2940
+ if ( err . gitErrorCode === GitErrorCodes . WorktreeBranchAlreadyUsed ) {
2941
+ this . handleWorktreeBranchAlreadyUsed ( err ) ;
2932
2942
return false ;
2933
2943
}
2934
2944
@@ -3470,16 +3480,14 @@ export class CommandCenter {
3470
3480
return ;
3471
3481
}
3472
3482
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 ;
3482
3489
}
3490
+
3483
3491
commitish = choice . refName ;
3484
3492
}
3485
3493
@@ -3516,6 +3524,14 @@ export class CommandCenter {
3516
3524
return [ start , value . length ] ;
3517
3525
} ;
3518
3526
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
+
3519
3535
// Default worktree path is based on the last worktree location or a worktree folder for the repository
3520
3536
const defaultWorktreeRoot = this . globalState . get < string > ( `${ CommandCenter . WORKTREE_ROOT_KEY } :${ repository . root } ` ) ;
3521
3537
const defaultWorktreePath = defaultWorktreeRoot
@@ -3530,6 +3546,7 @@ export class CommandCenter {
3530
3546
inputBox . prompt = l10n . t ( 'Please provide a worktree path' ) ;
3531
3547
inputBox . value = defaultWorktreePath ;
3532
3548
inputBox . valueSelection = getValueSelection ( inputBox . value ) ;
3549
+ inputBox . validationMessage = getValidationMessage ( inputBox . value ) ;
3533
3550
inputBox . ignoreFocusOut = true ;
3534
3551
inputBox . buttons = [
3535
3552
{
@@ -3544,6 +3561,9 @@ export class CommandCenter {
3544
3561
const worktreePath = await new Promise < string | undefined > ( ( resolve ) => {
3545
3562
disposables . push ( inputBox . onDidHide ( ( ) => resolve ( undefined ) ) ) ;
3546
3563
disposables . push ( inputBox . onDidAccept ( ( ) => resolve ( inputBox . value ) ) ) ;
3564
+ disposables . push ( inputBox . onDidChangeValue ( value => {
3565
+ inputBox . validationMessage = getValidationMessage ( value ) ;
3566
+ } ) ) ;
3547
3567
disposables . push ( inputBox . onDidTriggerButton ( async ( ) => {
3548
3568
inputBox . value = await getWorktreePath ( ) ?? '' ;
3549
3569
inputBox . valueSelection = getValueSelection ( inputBox . value ) ;
@@ -3565,57 +3585,62 @@ export class CommandCenter {
3565
3585
this . globalState . update ( `${ CommandCenter . WORKTREE_ROOT_KEY } :${ repository . root } ` , worktreeRoot ) ;
3566
3586
}
3567
3587
} 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 {
3569
3593
throw err ;
3570
3594
}
3571
3595
3572
- this . handleWorktreeError ( err ) ;
3573
3596
return ;
3574
3597
}
3575
3598
}
3576
3599
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 ( / f a t a l : ' ( [ ^ ' ] + ) ' i s a l r e a d y u s e d b y w o r k t r e e a t ' ( [ ^ ' ] + ) ' / ) ;
3585
3602
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
+ }
3588
3606
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 ) ;
3590
3610
}
3591
3611
3592
- private async handleWorktreeError ( err : any ) : Promise < void > {
3593
- const match = err . stderr . match ( / ^ f a t a l : ' ( [ ^ ' ] + ) ' i s a l r e a d y u s e d b y w o r k t r e e a t ' ( [ ^ ' ] + ) ' / ) ;
3612
+ private async handleWorktreeAlreadyExists ( err : any ) : Promise < void > {
3613
+ const match = err . stderr . match ( / f a t a l : ' ( [ ^ ' ] + ) ' / ) ;
3614
+
3594
3615
if ( ! match ) {
3595
3616
return ;
3596
3617
}
3597
3618
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 > {
3599
3625
const worktreeRepository = this . model . getRepository ( path ) ;
3600
3626
3601
3627
if ( ! worktreeRepository ) {
3602
3628
return ;
3603
3629
}
3604
3630
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 ) ;
3631
+ const openWorktree = l10n . t ( 'Open Worktree in Current Window' ) ;
3632
+ const openWorktreeInNewWindow = l10n . t ( 'Open Worktree in New Window' ) ;
3608
3633
const choice = await window . showWarningMessage ( message , { modal : true } , openWorktree , openWorktreeInNewWindow ) ;
3609
3634
3610
3635
if ( choice === openWorktree ) {
3611
3636
await this . openWorktreeInCurrentWindow ( worktreeRepository ) ;
3612
3637
} else if ( choice === openWorktreeInNewWindow ) {
3613
3638
await this . openWorktreeInNewWindow ( worktreeRepository ) ;
3614
3639
}
3615
-
3616
3640
return ;
3617
3641
}
3618
3642
3643
+
3619
3644
@command ( 'git.deleteWorktree' , { repository : true , repositoryFilter : [ 'worktree' ] } )
3620
3645
async deleteWorktree ( repository : Repository ) : Promise < void > {
3621
3646
if ( ! repository . dotGit . commonPath ) {
0 commit comments