private/Get-WtwMergedBranches.ps1
|
function Get-WtwDefaultBranch { <# .SYNOPSIS Default branch for a checkout: origin/HEAD, then main, then master. #> [CmdletBinding()] param( [Parameter(Mandatory)] [string] $RepoPath ) if (-not (Test-Path $RepoPath)) { return $null } $originHead = git -C $RepoPath symbolic-ref --quiet refs/remotes/origin/HEAD 2>$null if ($LASTEXITCODE -eq 0 -and "$originHead" -match 'refs/remotes/origin/(.+)$') { return $Matches[1] } foreach ($candidate in @('main', 'master')) { git -C $RepoPath show-ref --verify --quiet "refs/heads/$candidate" 2>$null if ($LASTEXITCODE -eq 0) { return $candidate } } $current = git -C $RepoPath branch --show-current 2>$null if ($current) { return $current.Trim() } return $null } function Get-WtwCheckedOutBranches { <# .SYNOPSIS Local branch names currently checked out in any worktree of this repo. #> [CmdletBinding()] param( [Parameter(Mandatory)] [string] $RepoPath ) $names = @() $porcelain = git -C $RepoPath worktree list --porcelain 2>$null foreach ($line in @($porcelain)) { if ($line -match '^branch refs/heads/(.+)$') { $names += $Matches[1] } } return @($names | Select-Object -Unique) } function Get-WtwMergedLocalBranches { <# .SYNOPSIS Local branches fully merged into the repo default branch, minus protected ones. .DESCRIPTION Skips the default branch and any branch still checked out in a worktree. Those need ``wtw remove`` first; ``git branch -d`` cannot delete them. #> [CmdletBinding()] param( [Parameter(Mandatory)] [string] $RepoPath, [Parameter(Mandatory)] [string] $RepoName, [string] $DefaultBranch ) $result = [PSCustomObject]@{ Items = @() Skipped = @() DefaultBranch = $DefaultBranch } if (-not $DefaultBranch) { $DefaultBranch = Get-WtwDefaultBranch -RepoPath $RepoPath $result.DefaultBranch = $DefaultBranch } if (-not $DefaultBranch) { return $result } $checkedOut = @(Get-WtwCheckedOutBranches -RepoPath $RepoPath) $lines = git -C $RepoPath branch --merged $DefaultBranch --format='%(refname:short)' 2>$null if ($LASTEXITCODE -ne 0) { return $result } $items = @() $skipped = @() foreach ($raw in @($lines)) { $name = "$raw".Trim() if (-not $name) { continue } if ($name -eq $DefaultBranch) { continue } if ($name -in $checkedOut) { $skipped += $name continue } $items += [PSCustomObject]@{ Repo = $RepoName Branch = $name Into = $DefaultBranch Status = 'merged' } } $result.Items = @($items) $result.Skipped = @($skipped) return $result } function Resolve-WtwCleanScope { <# .SYNOPSIS Decide whether clean runs worktrees, merged branches, or both. #> [CmdletBinding()] param( [switch] $All, [switch] $Worktrees, [switch] $Branches, [string] $Choice ) if ($All -or ($Worktrees -and $Branches)) { return [PSCustomObject]@{ Worktrees = $true; Branches = $true } } if ($Worktrees) { return [PSCustomObject]@{ Worktrees = $true; Branches = $false } } if ($Branches) { return [PSCustomObject]@{ Worktrees = $false; Branches = $true } } if (-not $PSBoundParameters.ContainsKey('Choice')) { Write-Host ' Clean what?' -ForegroundColor Yellow Write-Host ' worktrees stale AI / detached worktrees' Write-Host ' branches local branches already merged into the default branch' Write-Host ' all both' Write-Host '' $Choice = Read-Host ' Select' } $token = if ($null -eq $Choice) { '' } else { $Choice.Trim().ToLowerInvariant() } switch -Regex ($token) { '^(all|a|3)$' { return [PSCustomObject]@{ Worktrees = $true; Branches = $true } } '^(worktrees?|wt|w|1)$' { return [PSCustomObject]@{ Worktrees = $true; Branches = $false } } '^(branches?|br|b|2)$' { return [PSCustomObject]@{ Worktrees = $false; Branches = $true } } } Write-Host ' Cancelled.' -ForegroundColor DarkGray return $null } function Select-WtwCleanItems { <# .SYNOPSIS Shared all / none / 1,3,5 picker used by worktree and branch clean. #> [CmdletBinding()] param( [Parameter(Mandatory)] [AllowEmptyCollection()] [object[]] $Items, [switch] $Force, [string] $Noun = 'items' ) if ($Force -or @($Items).Count -eq 0) { return @($Items) } Write-Host ' Options:' -ForegroundColor Yellow Write-Host " all - Remove all $Noun" Write-Host ' none - Cancel' Write-Host ' 1,3,5 - Remove specific items (by number)' Write-Host '' $selection = Read-Host ' Select' if ($selection -eq 'none' -or -not $selection) { Write-Host ' Cancelled.' -ForegroundColor DarkGray return $null } if ($selection -eq 'all') { return @($Items) } $indices = $selection -split '[,\s]+' | ForEach-Object { [int]$_ - 1 } return @($indices | ForEach-Object { $Items[$_] } | Where-Object { $_ }) } |