Skip to content

Commit 27dedd6

Browse files
Marius StorhaugCopilot
authored andcommitted
fix: honour the merged PR version label on a default-branch dispatch
A workflow_dispatch on the default branch is the documented recovery route for a failed or cancelled release run. Pull request association was gated on $isPush, so a dispatch resolved no pull request, saw no version label, and silently applied the AutoPatching patch fallback. Widen the gate to cover a manual dispatch on the default branch, so the same association and selection path runs for the same merge commit. Also fail loudly when a merged default-branch pull request is associated with the commit but does not match it, instead of falling back to a patch bump. A wrong version published to the PowerShell Gallery cannot be reclaimed. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
1 parent 68696b6 commit 27dedd6

2 files changed

Lines changed: 132 additions & 2 deletions

File tree

.github/actions/Get-PSModuleSettings/src/Get-PSModuleSettings.Helpers.psm1

Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,103 @@ function Select-PullRequestForPush {
109109
Select-Object -First 1
110110
}
111111

112+
function Test-ShouldResolveAssociatedPullRequest {
113+
<#
114+
.SYNOPSIS
115+
Decides whether a commit's associated pull request must be resolved from the GitHub API.
116+
117+
.DESCRIPTION
118+
A push to any branch resolves its associated pull request so a default-branch release
119+
honours the merged pull request's version label. A manual dispatch on the default branch is
120+
the documented recovery route for a failed or cancelled release run and targets the same
121+
merge commit, so it must resolve the same pull request. Excluding it left the pull request
122+
null and silently downgraded a labelled Major or Minor release to a Patch bump.
123+
124+
.OUTPUTS
125+
Boolean. True when the association lookup must run.
126+
127+
.EXAMPLE
128+
Test-ShouldResolveAssociatedPullRequest -IsPush $false -IsManualDispatchToDefaultBranch $true -CommitSha 'abc123'
129+
130+
Returns $true, because a default-branch dispatch releases the same commit a push would.
131+
#>
132+
[CmdletBinding()]
133+
[OutputType([bool])]
134+
param(
135+
# Whether the workflow was triggered by a push event.
136+
[Parameter()]
137+
[bool] $IsPush,
138+
139+
# Whether the workflow was manually dispatched against the default branch.
140+
[Parameter()]
141+
[bool] $IsManualDispatchToDefaultBranch,
142+
143+
# The commit the workflow is resolving a release for.
144+
[Parameter()]
145+
[string] $CommitSha
146+
)
147+
148+
($IsPush -or $IsManualDispatchToDefaultBranch) -and -not [string]::IsNullOrWhiteSpace($CommitSha)
149+
}
150+
151+
function Get-DiscardedReleasePullRequest {
152+
<#
153+
.SYNOPSIS
154+
Reports merged default-branch pull requests whose version label would be discarded.
155+
156+
.DESCRIPTION
157+
Select-PullRequestForPush returns nothing both when a commit has no associated pull request
158+
and when every associated pull request fails its criteria. Only the first case is safe: a
159+
commit pushed directly to the default branch has no label to honour, so the direct-release
160+
path applies the default patch bump.
161+
162+
The unsafe case is a pull request that was merged into the default branch, and therefore
163+
carries the version label that was meant to drive the release, but was not selected because
164+
its merge commit does not match the commit being released. Falling back to a patch bump
165+
there publishes a version nobody asked for, and a PowerShell Gallery version cannot be
166+
reclaimed, so the caller must fail instead.
167+
168+
Pull requests that are not merged are ignored. The GitHub commit association endpoint also
169+
returns open pull requests whose branch contains the commit, which is expected and carries
170+
no release intent.
171+
172+
.OUTPUTS
173+
String, one description per merged pull request that was rejected. Nothing when the
174+
associated pull requests carry no release intent.
175+
176+
.EXAMPLE
177+
Get-DiscardedReleasePullRequest -PullRequest $associated -DefaultBranch main -CommitSha $sha
178+
179+
Returns '#412 was merged into [main] with merge commit [abc123]'.
180+
#>
181+
[CmdletBinding()]
182+
[OutputType([string])]
183+
param(
184+
# The pull requests the GitHub API associated with the commit.
185+
[Parameter()]
186+
[object[]] $PullRequest,
187+
188+
# The repository default branch a release must target.
189+
[Parameter(Mandatory)]
190+
[string] $DefaultBranch,
191+
192+
# The commit the workflow is resolving a release for.
193+
[Parameter(Mandatory)]
194+
[string] $CommitSha
195+
)
196+
197+
foreach ($candidate in ($PullRequest | Where-Object { $null -ne $_ })) {
198+
$isMergedToDefaultBranch = (
199+
$candidate.Base.Ref -eq $DefaultBranch -and
200+
-not [string]::IsNullOrWhiteSpace($candidate.merged_at)
201+
)
202+
if (-not $isMergedToDefaultBranch) { continue }
203+
if ($candidate.merge_commit_sha -eq $CommitSha) { continue }
204+
205+
"#$($candidate.Number) was merged into [$DefaultBranch] with merge commit [$($candidate.merge_commit_sha)]"
206+
}
207+
}
208+
112209
function Get-FilesFromGitTree {
113210
<#
114211
.SYNOPSIS

.github/actions/Get-PSModuleSettings/src/main.ps1

Lines changed: 35 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -242,19 +242,52 @@ LogGroup 'Calculate Job Run Conditions:' {
242242
$isManualDispatchToDefaultBranch = $isManualDispatch -and $workflowRef -eq $defaultBranch
243243
$pullRequest = $eventData.PullRequest
244244

245-
if ($isPush -and $commitSha) {
245+
# A manual dispatch on the default branch is the documented recovery route for a failed or
246+
# cancelled release run. It targets the same merge commit as the push it replaces, so it must
247+
# resolve the same pull request and honour the same version label. Gating this lookup on
248+
# $isPush alone left $pullRequest null for a dispatch, which silently downgraded a labelled
249+
# Major or Minor release to a Patch bump through the AutoPatching fallback.
250+
$shouldResolvePullRequest = Test-ShouldResolveAssociatedPullRequest -IsPush $isPush `
251+
-IsManualDispatchToDefaultBranch $isManualDispatchToDefaultBranch `
252+
-CommitSha $commitSha
253+
if ($shouldResolvePullRequest) {
246254
LogGroup "Resolve pull request for commit [$commitSha]" {
247255
$owner = $env:GITHUB_REPOSITORY_OWNER
248256
$repo = $env:GITHUB_REPOSITORY_NAME
249257
$response = Invoke-GitHubAPI -ApiEndpoint "/repos/$owner/$repo/commits/$commitSha/pulls" -Method GET
250-
$associatedPullRequests = @($response.Response)
258+
$associatedPullRequests = @($response.Response | Where-Object { $null -ne $_ })
251259
$pullRequest = Select-PullRequestForPush -PullRequest $associatedPullRequests `
252260
-DefaultBranch $defaultBranch `
253261
-CommitSha $commitSha
254262

255263
if ($pullRequest) {
256264
Write-Host "Resolved pull request #$($pullRequest.Number) from commit [$commitSha]."
257265
} else {
266+
# Distinguish 'no pull request carries release intent' from 'a merged default-branch
267+
# pull request exists but was not selected'. The first is a legitimate direct push to
268+
# the default branch, which the direct-release path handles with the default patch
269+
# bump. The second means the commit carries a version label that would be discarded,
270+
# and a wrong version published to the PowerShell Gallery cannot be reclaimed, so
271+
# fail loudly instead. Only release-bearing events are checked; a push to a feature
272+
# branch has no release to get wrong.
273+
$isReleaseEvent = $isPushToDefaultBranch -or $isManualDispatchToDefaultBranch
274+
$discardedPullRequests = if ($isReleaseEvent) {
275+
@(Get-DiscardedReleasePullRequest -PullRequest $associatedPullRequests `
276+
-DefaultBranch $defaultBranch `
277+
-CommitSha $commitSha)
278+
} else {
279+
@()
280+
}
281+
if ($discardedPullRequests.Count -gt 0) {
282+
throw (
283+
"Commit [$commitSha] cannot be released because its version label cannot be determined. " +
284+
'The following merged pull request(s) are associated with it but none matches the commit ' +
285+
"being released: $($discardedPullRequests -join '; '). " +
286+
'Refusing to fall back to a patch bump, because a wrong version published to the ' +
287+
'PowerShell Gallery cannot be reclaimed. Re-run the workflow against the merge commit ' +
288+
'of the pull request you intend to release.'
289+
)
290+
}
258291
Write-Host "::notice::No pull request is associated with commit [$commitSha]."
259292
}
260293
}

0 commit comments

Comments
 (0)