@@ -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
@@ -3397,8 +3407,36 @@ export class CommandCenter {
3397
3407
}
3398
3408
}
3399
3409
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
+
3402
3440
await this . _createWorktree ( repository ) ;
3403
3441
}
3404
3442
@@ -3442,16 +3480,14 @@ export class CommandCenter {
3442
3480
return ;
3443
3481
}
3444
3482
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 ;
3454
3489
}
3490
+
3455
3491
commitish = choice . refName ;
3456
3492
}
3457
3493
@@ -3488,6 +3524,14 @@ export class CommandCenter {
3488
3524
return [ start , value . length ] ;
3489
3525
} ;
3490
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
+
3491
3535
// Default worktree path is based on the last worktree location or a worktree folder for the repository
3492
3536
const defaultWorktreeRoot = this . globalState . get < string > ( `${ CommandCenter . WORKTREE_ROOT_KEY } :${ repository . root } ` ) ;
3493
3537
const defaultWorktreePath = defaultWorktreeRoot
@@ -3502,6 +3546,7 @@ export class CommandCenter {
3502
3546
inputBox . prompt = l10n . t ( 'Please provide a worktree path' ) ;
3503
3547
inputBox . value = defaultWorktreePath ;
3504
3548
inputBox . valueSelection = getValueSelection ( inputBox . value ) ;
3549
+ inputBox . validationMessage = getValidationMessage ( inputBox . value ) ;
3505
3550
inputBox . ignoreFocusOut = true ;
3506
3551
inputBox . buttons = [
3507
3552
{
@@ -3516,6 +3561,9 @@ export class CommandCenter {
3516
3561
const worktreePath = await new Promise < string | undefined > ( ( resolve ) => {
3517
3562
disposables . push ( inputBox . onDidHide ( ( ) => resolve ( undefined ) ) ) ;
3518
3563
disposables . push ( inputBox . onDidAccept ( ( ) => resolve ( inputBox . value ) ) ) ;
3564
+ disposables . push ( inputBox . onDidChangeValue ( value => {
3565
+ inputBox . validationMessage = getValidationMessage ( value ) ;
3566
+ } ) ) ;
3519
3567
disposables . push ( inputBox . onDidTriggerButton ( async ( ) => {
3520
3568
inputBox . value = await getWorktreePath ( ) ?? '' ;
3521
3569
inputBox . valueSelection = getValueSelection ( inputBox . value ) ;
@@ -3537,57 +3585,64 @@ export class CommandCenter {
3537
3585
this . globalState . update ( `${ CommandCenter . WORKTREE_ROOT_KEY } :${ repository . root } ` , worktreeRoot ) ;
3538
3586
}
3539
3587
} 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 {
3541
3593
throw err ;
3542
3594
}
3543
3595
3544
- this . handleWorktreeError ( err ) ;
3545
3596
return ;
3546
3597
}
3547
3598
}
3548
3599
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 ( / 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 ' ( [ ^ ' ] + ) ' / ) ;
3557
3602
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
+ }
3560
3606
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 ) ;
3562
3610
}
3563
3611
3564
- private async handleWorktreeError ( err : any ) : Promise < void > {
3565
- 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
+
3566
3615
if ( ! match ) {
3567
3616
return ;
3568
3617
}
3569
3618
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
+
3571
3627
const worktreeRepository = this . model . getRepository ( path ) ;
3572
3628
3573
3629
if ( ! worktreeRepository ) {
3574
3630
return ;
3575
3631
}
3576
3632
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' ) ;
3580
3635
const choice = await window . showWarningMessage ( message , { modal : true } , openWorktree , openWorktreeInNewWindow ) ;
3581
3636
3582
3637
if ( choice === openWorktree ) {
3583
3638
await this . openWorktreeInCurrentWindow ( worktreeRepository ) ;
3584
3639
} else if ( choice === openWorktreeInNewWindow ) {
3585
3640
await this . openWorktreeInNewWindow ( worktreeRepository ) ;
3586
3641
}
3587
-
3588
3642
return ;
3589
3643
}
3590
3644
3645
+
3591
3646
@command ( 'git.deleteWorktree' , { repository : true , repositoryFilter : [ 'worktree' ] } )
3592
3647
async deleteWorktree ( repository : Repository ) : Promise < void > {
3593
3648
if ( ! repository . dotGit . commonPath ) {
@@ -3640,7 +3695,7 @@ export class CommandCenter {
3640
3695
}
3641
3696
}
3642
3697
3643
- @command ( 'git.openWorktree' , { repository : true , repositoryFilter : [ 'worktree' ] } )
3698
+ @command ( 'git.openWorktree' , { repository : true } )
3644
3699
async openWorktreeInCurrentWindow ( repository : Repository ) : Promise < void > {
3645
3700
if ( ! repository ) {
3646
3701
return ;
@@ -3650,7 +3705,7 @@ export class CommandCenter {
3650
3705
await commands . executeCommand ( 'vscode.openFolder' , uri , { forceReuseWindow : true } ) ;
3651
3706
}
3652
3707
3653
- @command ( 'git.openWorktreeInNewWindow' , { repository : true , repositoryFilter : [ 'worktree' ] } )
3708
+ @command ( 'git.openWorktreeInNewWindow' , { repository : true } )
3654
3709
async openWorktreeInNewWindow ( repository : Repository ) : Promise < void > {
3655
3710
if ( ! repository ) {
3656
3711
return ;
0 commit comments