Skip to content

Commit 09d579f

Browse files
authored
Merge pull request #9640 from ruby/claude/eloquent-williamson-5483b9
Stop silently dropping eligible PRs from patch release preparation
2 parents 32a1265 + ea96503 commit 09d579f

1 file changed

Lines changed: 47 additions & 7 deletions

File tree

tool/release.rb

Lines changed: 47 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -154,6 +154,10 @@ def initialize(version)
154154
"v#{@stable_branch}.0"
155155
end
156156

157+
# The most recent release on this line. For patch releases it bounds the
158+
# search for backport PRs merged straight onto the stable branch since then.
159+
@last_release_tag = @level == :patch ? "v#{@stable_branch}.#{segments[2] - 1}" : @previous_release_tag
160+
157161
rubygems_version = segments.join(".").gsub(/([a-z])\.(\d)/i, '\1\2')
158162
@rubygems = Rubygems.new(rubygems_version, @stable_branch)
159163

@@ -174,19 +178,33 @@ def set_rubygems_as_current_library
174178
def prepare!
175179
initial_branch = `git rev-parse --abbrev-ref HEAD`.strip
176180

181+
# Refresh the upstream refs first so the release is cut from the latest
182+
# origin state. A stale local `master` or stable branch would otherwise
183+
# silently drop PRs merged after the last local fetch.
184+
system("git", "fetch", "--prune", "origin", exception: true)
185+
177186
check_git_state!
178187

179188
unless @prerelease
180-
create_if_not_exist_and_switch_to(@stable_branch, from: "master")
189+
create_if_not_exist_and_switch_to(@stable_branch, from: "origin/master")
181190
system("git", "push", "origin", @stable_branch, exception: true) if @level == :minor_or_major && !ENV["DRYRUN"]
182191
end
183192

184-
from_branch = if @level == :minor_or_major && @prerelease
193+
base_branch = if @level == :minor_or_major && @prerelease
185194
"master"
186195
else
187196
@stable_branch
188197
end
189-
create_if_not_exist_and_switch_to(@release_branch, from: from_branch)
198+
199+
# The ref the release branch is cut from. Patch releases and prereleases
200+
# branch straight off the upstream ref so a stale local copy can't leave
201+
# commits behind; a new stable branch was just created locally above.
202+
release_base = if @level == :minor_or_major
203+
@prerelease ? "origin/master" : @stable_branch
204+
else
205+
"origin/#{@stable_branch}"
206+
end
207+
create_if_not_exist_and_switch_to(@release_branch, from: release_base)
190208

191209
begin
192210
@bundler.set_relevant_pull_requests_from(unreleased_pull_requests)
@@ -200,14 +218,14 @@ def prepare!
200218

201219
gh_client.create_pull_request(
202220
"ruby/rubygems",
203-
from_branch,
221+
base_branch,
204222
@release_branch,
205223
"Prepare RubyGems #{@rubygems.version} and Bundler #{@bundler.version}",
206224
release_pull_request_body
207225
) unless ENV["DRYRUN"]
208226

209227
unless @prerelease
210-
create_if_not_exist_and_switch_to("cherry_pick_changelogs", from: "master")
228+
create_if_not_exist_and_switch_to("cherry_pick_changelogs", from: "origin/master")
211229

212230
begin
213231
system("git", "cherry-pick", bundler_changelog, rubygems_changelog, exception: true)
@@ -271,6 +289,11 @@ def cherry_pick_pull_requests
271289
prs = relevant_unreleased_pull_requests
272290
raise "No unreleased PRs were found. Make sure to tag them with appropriate labels so that they are selected for backport." unless prs.any?
273291

292+
# Dedicated backport PRs target the stable branch directly, so they are
293+
# already on the release branch and only need a changelog entry, not
294+
# another cherry-pick.
295+
prs = prs.reject {|pr| already_on_stable_branch?(pr) }
296+
274297
puts "The following unreleased prs were found:\n#{prs.map {|pr| "* #{pr.url}" }.join("\n")}"
275298

276299
prs.each do |pr|
@@ -375,6 +398,22 @@ def unreleased_pull_requests
375398
@unreleased_pull_requests ||= scan_unreleased_pull_requests(unreleased_pr_ids)
376399
end
377400

401+
# True when the PR's merged commit is already reachable from the release
402+
# branch, e.g. a backport PR merged straight onto the stable branch rather
403+
# than cherry-picked from master.
404+
def already_on_stable_branch?(pr)
405+
system("git", "merge-base", "--is-ancestor", pr.merge_commit_sha, "HEAD", out: IO::NULL, err: IO::NULL)
406+
end
407+
408+
# Commits merged directly onto the stable branch since the last release, such
409+
# as dedicated backport PRs that target the stable branch instead of being
410+
# cherry-picked from master. They never land on master, so the master scan in
411+
# `unreleased_pr_ids` cannot see them and their changelog entries would
412+
# otherwise be dropped from the release.
413+
def stable_branch_backport_commits
414+
`git log --format=%H #{@last_release_tag}..origin/#{@stable_branch}`.split("\n").reject(&:empty?)
415+
end
416+
378417
# Source SHAs already cherry-picked onto the stable branch, derived from the
379418
# `(cherry picked from commit X)` footer that `git cherry-pick -x` records.
380419
# When the footer references a merge commit (PRs merged with "Create a merge
@@ -383,7 +422,7 @@ def unreleased_pull_requests
383422
# through those commits left on master.
384423
def released_commit_shas
385424
@released_commit_shas ||= begin
386-
log = `git log --format=%B #{@previous_release_tag}..#{@stable_branch}`
425+
log = `git log --format=%B #{@previous_release_tag}..origin/#{@stable_branch}`
387426
shas = Set.new
388427
log.scan(/cherry picked from commit ([0-9a-f]+)/).flatten.each do |sha|
389428
shas << sha
@@ -410,9 +449,10 @@ def scan_unreleased_pull_requests(ids)
410449
end
411450

412451
def unreleased_pr_ids
413-
head = @level == :minor_or_major ? "HEAD" : "master"
452+
head = @level == :minor_or_major ? "HEAD" : "origin/master"
414453
commits = `git log --format=%H #{@previous_release_tag}..#{head}`.split("\n")
415454
commits.reject! {|sha| released_commit_shas.include?(sha) } if @level == :patch
455+
commits.concat(stable_branch_backport_commits) if @level == :patch
416456

417457
# GitHub search API has a rate limit of 30 requests per minute for authenticated users
418458
rate_limit = 28

0 commit comments

Comments
 (0)