g-unstack.ps1
|
param( [switch]$Force ) . (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 = gh repo view --json nameWithOwner -q .nameWithOwner 2>$null $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 1 } Get-StackOrder -Head $branch -Out $ordered } if ($ordered.Count -eq 0) { Write-Host "unstack: no stacked PRs found" exit 1 } & (Join-Path $PSScriptRoot 'g-stack.ps1') Write-Host "" $n = $ordered.Count $labels = ($ordered | ForEach-Object { $headToPR[$_] | ForEach-Object { "#$($_.number) ($_)" } }) -join ' → ' Write-Host "unstack: will merge $n PR(s) in order: $labels" 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 } Write-Host "unstack: $i/$n — checking out $b ..." $coOut = git -C $repo checkout $b 2>&1 if ($LASTEXITCODE -ne 0) { Write-Host " checkout failed: $($coOut -join ' ')" 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 } Write-Host "unstack: $i/$n — checking CI for #$($pr.number) ..." & (Join-Path $PSScriptRoot 'g-pr-checks.ps1') if ($LASTEXITCODE -ne 0) { Write-Host "unstack: $i/$n — CI failed for #$($pr.number) ($b); halting" exit 1 } Write-Host "unstack: $i/$n — merging #$($pr.number) ($b) ..." & (Join-Path $PSScriptRoot 'g-merge-rotate.ps1') 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 |