@@ -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 ( / 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 ' ( [ ^ ' ] + ) ' / ) ;
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 ( / ^ 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+
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 ;
0 commit comments