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 }
    }
}