g-unstack.ps1
|
[CmdletBinding()] param( [switch]$Force, [switch]$DryRun, [switch]$Quiet ) . (Join-Path $PSScriptRoot 'g-registry.ps1') $repo = Get-Location $branch = git -C $repo branch --show-current 2>$null if (-not $branch) { Write-Host "not a git repo"; exit 1 } $repoName = ((git -C $repo remote get-url origin 2>$null) -replace ".*github\.com[:/]", "") -replace "\.git$", "" $baseBranch = (Get-GitboxConfig -RepoPath $repo).BaseBranch $allPRs = gh pr list --repo $repoName --state open --json number,headRefName,baseRefName,title,statusCheckRollup,isDraft 2>$null | ConvertFrom-Json if (-not $allPRs) { $allPRs = @() } $headToBase = @{} $headToPR = @{} foreach ($pr in $allPRs) { $headToBase[$pr.headRefName] = $pr.baseRefName $headToPR[$pr.headRefName] = $pr } # Walk from current branch toward base to find the bottom of the stack $chain = [System.Collections.Generic.List[string]]::new() $cur = $branch $visited = [System.Collections.Generic.HashSet[string]]::new() while ($cur -and $headToBase.ContainsKey($cur) -and $visited.Add($cur)) { $chain.Insert(0, $cur) $cur = $headToBase[$cur] } # Walk down from the deepest ancestor to collect all children in topological order function Get-StackOrder { param([string]$Head, [System.Collections.Generic.List[string]]$Out) $Out.Add($Head) $kids = @($allPRs | Where-Object { $_.baseRefName -eq $Head }) foreach ($k in $kids) { Get-StackOrder -Head $k.headRefName -Out $Out } } $ordered = [System.Collections.Generic.List[string]]::new() if ($chain.Count -gt 0) { Get-StackOrder -Head $chain[0] -Out $ordered } else { # Current branch may be a stack root (has children, no parent PR) $children = @($allPRs | Where-Object { $_.baseRefName -eq $branch }) if ($children.Count -eq 0) { Write-Host "unstack: branch '$branch' is not part of a stacked PR chain" exit 0 } Get-StackOrder -Head $branch -Out $ordered } if ($ordered.Count -eq 0) { Write-Host "unstack: no stacked PRs found" exit 1 } if ($ordered[0] -ne $branch) { Write-Host "unstack: repositioning to bottom of stack: $($ordered[0])" $coOut = git -C $repo checkout $ordered[0] 2>&1 if ($LASTEXITCODE -ne 0) { Write-Host " checkout failed: $($coOut -join ' ')"; exit 1 } $branch = $ordered[0] } if (-not $Quiet) { & (Join-Path $PSScriptRoot 'g-stack.ps1') Write-Host "" } $n = $ordered.Count $labels = ($ordered | ForEach-Object { "#$($headToPR[$_].number) ($_)" }) -join ' → ' Write-Host "unstack: will merge $n PR(s) in order: $labels" if ($DryRun) { Write-Host "unstack: dry run — would merge $n PR(s) in order:" foreach ($b in $ordered) { $pr = $headToPR[$b] Write-Host " #$($pr.number) $b -> $($headToBase[$b])" } exit 0 } if (-not $Force) { $isInteractive = [Environment]::UserInteractive -and -not [Console]::IsInputRedirected if (-not $isInteractive) { Write-Host "unstack: non-interactive session — pass -Force to proceed" exit 1 } try { $answer = Read-Host "Proceed? [y/N]" } catch { Write-Host "unstack: non-interactive session — pass -Force to proceed" exit 1 } if ($answer -notmatch '^[yY]$') { Write-Host "unstack: aborted" exit 0 } } $i = 0 foreach ($b in $ordered) { $i++ $pr = $headToPR[$b] if (-not $pr) { Write-Host "unstack: $i/$n — '$b' has no open PR; halting" exit 1 } if (-not $Quiet) { Write-Host "unstack: $i/$n — checking out $b ..." } $coOut = git -C $repo checkout $b 2>&1 if ($LASTEXITCODE -ne 0) { Write-Host "checkout failed: $b" if ($VerbosePreference -ne 'SilentlyContinue') { $coOut | ForEach-Object { Write-Host " $_" } } exit 1 } if ($pr.isDraft) { Write-Host "unstack: $i/$n — PR #$($pr.number) ($b) is a draft — run: gh pr ready $($pr.number)" exit 1 } if (-not $Quiet) { Write-Host "unstack: $i/$n — checking CI for #$($pr.number) ..." } & (Join-Path $PSScriptRoot 'g-pr.ps1') -Action checks if ($LASTEXITCODE -ne 0) { Write-Host "unstack: $i/$n — CI failed for #$($pr.number) ($b); halting" exit 1 } if (-not $Quiet) { Write-Host "unstack: $i/$n — merging #$($pr.number) ($b) ..." } & (Join-Path $PSScriptRoot 'g-merge-rotate.ps1') -SuppressWipWarning if ($LASTEXITCODE -ne 0) { Write-Host "unstack: $i/$n — merge failed for #$($pr.number) ($b); halting" exit 1 } Write-Host "unstack: $i/$n merged #$($pr.number)" } Write-Host "unstack: done — $n PR(s) merged" exit 0 |