Public/New-GitWorktree.ps1
|
<#
.SYNOPSIS Creates a new git worktree with a prefixed branch name. .DESCRIPTION Creates a worktree adjacent to the bare repo directory. Branch naming and worktree path rules: develop/{author}/{slug}[-{desc}] -- stored at ./develop/{slug} feature/[{author}/]{slug}[-{desc}] -- stored at ./feature/{slug} bug/[{author}/]{slug}[-{desc}] -- stored at ./bug/{slug} temp/[{author}/]{slug}[-{desc}] -- stored at ./temp/{slug} hotfix/{slug} -- stored at ./hotfix/{slug} release/{slug} -- stored at ./release/{slug} Worktree paths never include the author segment. The author segment is derived from $env:GIT_USER_SHORTNAME, then `git config user.name`, then $env:USERNAME (first non-empty wins). Use -NoAuthor to suppress the author on feature/bug/temp branches. The default source branch is resolved from the repo's HEAD, then init.defaultBranch config, preferring 'master' over 'main'. .EXAMPLE New-GitWorktree -Feature "user-auth" -Description "Add OAuth login" # branch: feature/snkemp/user-auth-add-oauth-login # worktree: ..\feature\user-auth-add-oauth-login .EXAMPLE New-GitWorktree -Develop "payments" # branch: develop/snkemp/payments (author always present) # worktree: ..\develop\payments .EXAMPLE New-GitWorktree -Hotfix "2.1.1" -SourceBranch master # branch: hotfix/2.1.1 (no author — production path) # worktree: ..\hotfix\2.1.1 .EXAMPLE New-GitWorktree -Bug "null-ref" -NoAuthor # branch: bug/null-ref # worktree: ..\bug\null-ref .EXAMPLE New-GitWorktree -Feature "login" -OpenTerminal .EXAMPLE New-GitWorktree -Feature "login" -NoSolution #> function New-GitWorktree { [CmdletBinding(SupportsShouldProcess, DefaultParameterSetName = 'Feature')] param( [Parameter(Mandatory, ParameterSetName = 'Feature', Position = 0)] [string] $Feature, [Parameter(Mandatory, ParameterSetName = 'Develop', Position = 0)] [string] $Develop, [Parameter(Mandatory, ParameterSetName = 'Hotfix', Position = 0)] [string] $Hotfix, [Parameter(Mandatory, ParameterSetName = 'Release', Position = 0)] [string] $Release, [Parameter(Mandatory, ParameterSetName = 'Bug', Position = 0)] [string] $Bug, [Parameter(Mandatory, ParameterSetName = 'Temp', Position = 0)] [string] $Temp, [Parameter()] [string] $Description, [Parameter()] [string] $SourceBranch, # Suppress author segment on feature/bug/temp branches. Has no effect on develop. [Parameter()] [switch] $NoAuthor, # Override the author slug used in the branch name (bypasses GIT_USER_SHORTNAME / git config) [Parameter()] [string] $Author, # Open a new terminal window in the created worktree directory [Parameter()] [switch] $OpenTerminal, # Suppress auto-opening any .sln/.slnx file found in the worktree [Parameter()] [switch] $NoSolution, [Parameter()] [string] $RepoPath = (Get-Location).Path ) $root = Get-GitRoot -Path $RepoPath $isProdPath = $false $isDevelopPath = $false switch ($PSCmdlet.ParameterSetName) { 'Feature' { $prefix = 'feature'; $rawName = $Feature } 'Develop' { $prefix = 'develop'; $rawName = $Develop; $isDevelopPath = $true } 'Hotfix' { $prefix = 'hotfix'; $rawName = $Hotfix; $isProdPath = $true } 'Release' { $prefix = 'release'; $rawName = $Release; $isProdPath = $true } 'Bug' { $prefix = 'bug'; $rawName = $Bug } 'Temp' { $prefix = 'temp'; $rawName = $Temp } } $slug = ConvertTo-SafeBranchName -Name $rawName if ($Description) { $descSlug = ConvertTo-SafeBranchName -Name $Description $slug = "$slug-$descSlug" } # Branch name: develop always includes author; feature/bug/temp include author unless -NoAuthor; # hotfix/release never include author. if ($isDevelopPath -or (-not $isProdPath -and -not $NoAuthor)) { $authorSlug = if ($Author) { ConvertTo-SafeBranchName -Name $Author } else { Get-GitAuthorSlug -RepoPath $root } $branchName = "$prefix/$authorSlug/$slug" } else { $branchName = "$prefix/$slug" } # Worktree path never includes the author segment. $wtDir = Join-Path (Split-Path $root -Parent) "$prefix/$slug" # Resolve source branch — HEAD symbolic ref, then configured/preferred default. if (-not $SourceBranch) { $SourceBranch = git -C $root symbolic-ref --short HEAD 2>$null if ($LASTEXITCODE -ne 0 -or -not $SourceBranch) { $SourceBranch = Get-DefaultBranch -RepoPath $root $exists = git -C $root branch --list $SourceBranch 2>$null if (-not $exists) { foreach ($candidate in @('master', 'main')) { $exists = git -C $root branch --list $candidate 2>$null if ($exists) { $SourceBranch = $candidate; break } } } } if (-not $SourceBranch) { throw "Could not determine a default source branch. Use -SourceBranch." } } if ($PSCmdlet.ShouldProcess($wtDir, "Create worktree for branch '$branchName' off '$SourceBranch'")) { $wtParent = Split-Path $wtDir -Parent if (-not (Test-Path $wtParent)) { New-Item -ItemType Directory -Path $wtParent -Force | Out-Null } Write-Verbose "Creating branch '$branchName' from '$SourceBranch'" git -C $root worktree add -b $branchName $wtDir $SourceBranch if ($LASTEXITCODE -ne 0) { throw "Failed to create worktree at '$wtDir' for branch '$branchName'" } git config --global --add safe.directory $wtDir 2>$null Write-Host "Worktree created : $wtDir" -ForegroundColor Green Write-Host "Branch : $branchName" -ForegroundColor Cyan if (-not $NoSolution) { Open-WorktreeSolution -WorktreePath $wtDir } if ($OpenTerminal) { Open-WorktreeTerminal -WorktreePath $wtDir } return [PSCustomObject]@{ Path = $wtDir; Branch = $branchName; SourceBranch = $SourceBranch } } } |