Skip to content

Commit fdc35ee

Browse files
committed
Optionally apply a release label to release PRs
1 parent c122e7f commit fdc35ee

File tree

12 files changed

+310
-5
lines changed

12 files changed

+310
-5
lines changed

lib/create_github_release/assertions.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,5 +21,6 @@ module Assertions; end
2121
require_relative 'assertions/no_staged_changes'
2222
require_relative 'assertions/no_uncommitted_changes'
2323
require_relative 'assertions/on_default_branch'
24+
require_relative 'assertions/release_pr_label_exists'
2425
require_relative 'assertions/remote_release_branch_does_not_exist'
2526
require_relative 'assertions/remote_release_tag_does_not_exist'
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
# frozen_string_literal: true
2+
3+
require 'English'
4+
require 'create_github_release/assertion_base'
5+
6+
module CreateGithubRelease
7+
module Assertions
8+
# Assert that the release tag does not exist in the local repository
9+
#
10+
# @api public
11+
#
12+
class ReleasePrLabelExists < AssertionBase
13+
# Assert that the release pr label is defined in GitHub
14+
#
15+
# @example
16+
# require 'create_github_release'
17+
#
18+
# options = CreateGithubRelease::Options.new { |o| o.release_type = 'major', o.release_pr_label = 'release' }
19+
# assertion = CreateGithubRelease::Assertions::LastReleaseTagExists.new(options)
20+
# begin
21+
# assertion.assert
22+
# puts 'Assertion passed'
23+
# rescue SystemExit
24+
# puts 'Assertion failed'
25+
# end
26+
#
27+
# @return [void]
28+
#
29+
# @raise [SystemExit] if the assertion fails
30+
#
31+
def assert
32+
return if project.release_pr_label.nil?
33+
34+
print "Checking that release pr label '#{project.release_pr_label}' exists..."
35+
36+
if labels.include?(project.release_pr_label)
37+
puts 'OK'
38+
else
39+
error "Release pr label '#{project.release_pr_label}' does not exist"
40+
end
41+
end
42+
43+
private
44+
45+
# Get the list of labels for the current repository from GitHub
46+
# @return [Array<String>] The list of labels for the current repository
47+
# @api private
48+
def labels
49+
output = `gh label list`
50+
error 'Could not list pr labels' unless $CHILD_STATUS.success?
51+
52+
output.lines.map(&:chomp).map { |line| line.split("\t").first }
53+
end
54+
end
55+
end
56+
end

lib/create_github_release/command_line.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ module CommandLine
3232
# @return [Array<Symbol>]
3333
ALLOWED_OPTIONS = %i[
3434
release_type pre pre_type default_branch release_branch remote last_release_version
35-
next_release_version changelog_path quiet verbose
35+
next_release_version release_pr_label changelog_path quiet verbose
3636
].freeze
3737
end
3838
end

lib/create_github_release/command_line/options.rb

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,13 @@ class Options
6363
# @return [String]
6464
# @api public
6565

66+
# @!attribute release_pr_label [rw] the label to apply to the release pull request
67+
# @example
68+
# options = CreateGithubRelease::CommandLine::Options.new(release_pr_label: 'release')
69+
# options.release_pr_label #=> 'release'
70+
# @return [String]
71+
# @api public
72+
6673
# @attribute changelog_path [rw] the path to the changelog file
6774
# @example
6875
# options = CreateGithubRelease::CommandLine::Options.new(changelog_path: 'CHANGELOG.md')

lib/create_github_release/command_line/parser.rb

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -140,10 +140,20 @@ def define_options
140140
%i[
141141
define_help_option define_default_branch_option define_release_branch_option define_pre_option
142142
define_pre_type_option define_remote_option define_last_release_version_option define_version_option
143-
define_next_release_version_option define_changelog_path_option define_quiet_option define_verbose_option
143+
define_next_release_version_option define_release_pr_label_option define_changelog_path_option
144+
define_quiet_option define_verbose_option
144145
].each { |m| send(m) }
145146
end
146147

148+
# Define the release_pr_label option which requires a value
149+
# @return [void]
150+
# @api private
151+
def define_release_pr_label_option
152+
option_parser.on('--release-pr-label=LABEL', 'The label to apply to the release pull request') do |label|
153+
options.release_pr_label = label
154+
end
155+
end
156+
147157
# Define the pre option
148158
# @return [void]
149159
# @api private

lib/create_github_release/project.rb

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,8 @@ def initialize(options)
6969
:release_type, :pre, :pre_type, :release_url, :remote, :remote_base_url,
7070
:remote_repository, :remote_url, :changelog_path, :changes,
7171
:next_release_description, :last_release_changelog, :next_release_changelog,
72-
:first_commit, :verbose, :quiet, :release_pr_number, :release_pr_url
72+
:first_commit, :verbose, :quiet, :release_pr_number, :release_pr_url,
73+
:release_pr_label
7374

7475
# attr_writer :first_release
7576

@@ -292,6 +293,34 @@ def release_branch
292293
@release_branch ||= options.release_branch || "release-#{next_release_tag}"
293294
end
294295

296+
# @!attribute [rw] release_pr_label
297+
#
298+
# The name of the label to apply to the release pull request
299+
#
300+
# @example By default, `release_pr_label` is nil indicating no label should be applied
301+
# options = CreateGithubRelease::CommandLine::Options.new(release_type: 'major')
302+
# project = CreateGithubRelease::Project.new(options)
303+
# project.release_pr_label #=> nil
304+
#
305+
# @example Set to 'release'
306+
# options = CreateGithubRelease::CommandLine::Options.new(release_type: 'major', release_pr_label: 'release')
307+
# project = CreateGithubRelease::Project.new(options)
308+
# project.release_pr_label #=> 'release'
309+
#
310+
# @example It can also be set explicitly
311+
# options = CreateGithubRelease::CommandLine::Options.new(release_type: 'major')
312+
# project = CreateGithubRelease::Project.new(options)
313+
# project.release_pr_label = 'release' # could be set to nil to remove the label
314+
# project.release_pr_label #=> 'release'
315+
#
316+
# @return [String, nil]
317+
#
318+
# @api public
319+
#
320+
def release_pr_label
321+
@release_pr_label ||= options.release_pr_label || nil
322+
end
323+
295324
# @!attribute [rw] release_log_url
296325
#
297326
# The URL of the page containing a list of the changes in the release

lib/create_github_release/release_assertions.rb

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,8 @@ def initialize(options)
6161
CreateGithubRelease::Assertions::LocalReleaseBranchDoesNotExist,
6262
CreateGithubRelease::Assertions::RemoteReleaseBranchDoesNotExist,
6363
CreateGithubRelease::Assertions::GhCommandExists,
64-
CreateGithubRelease::Assertions::GhAuthenticated
64+
CreateGithubRelease::Assertions::GhAuthenticated,
65+
CreateGithubRelease::Assertions::ReleasePrLabelExists
6566
].freeze
6667

6768
# Run all assertions

lib/create_github_release/tasks/create_release_pull_request.rb

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,14 +48,31 @@ def create_release_pr(path)
4848
print 'Creating GitHub pull request...'
4949
tag = project.next_release_tag
5050
default_branch = project.default_branch
51-
`gh pr create --title 'Release #{tag}' --body-file '#{path}' --base '#{default_branch}'`
51+
`#{command(tag, path, default_branch)}`
5252
if $CHILD_STATUS.success?
5353
puts 'OK'
5454
else
5555
error 'Could not create release pull request'
5656
end
5757
end
5858

59+
# The command to create the release pull request
60+
# @param tag [String] The tag for the release
61+
# @param path [String] The path to the file with the PR body
62+
# @param default_branch [String] The default branch for the repository
63+
# @return [String] The command to create the release pull request
64+
# @api private
65+
def command(tag, path, default_branch)
66+
command = [
67+
'gh', 'pr', 'create',
68+
'--title', "'Release #{tag}'",
69+
'--body-file', "'#{path}'",
70+
'--base', "'#{default_branch}'"
71+
]
72+
command += ['--label', "'#{project.release_pr_label}'"] if project.release_pr_label
73+
command.join(' ')
74+
end
75+
5976
# Write the changelog to a new temporary file
6077
# @return [String] the path to the temporary file
6178
# @raise [SystemExit] if the temp could not be created
Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
1+
# frozen_string_literal: true
2+
3+
RSpec.describe CreateGithubRelease::Assertions::ReleasePrLabelExists do
4+
let(:assertion) { described_class.new(project) }
5+
let(:options) { CreateGithubRelease::CommandLine::Options.new { |o| o.release_type = 'major' } }
6+
let(:project) { CreateGithubRelease::Project.new(options) { |p| p.release_pr_label = release_pr_label } }
7+
let(:release_pr_label) { nil }
8+
9+
before do
10+
allow(assertion).to receive(:`).with(String) { |command| execute_mocked_command(mocked_commands, command) }
11+
end
12+
13+
describe '#assert' do
14+
subject do
15+
@stdout, @stderr, exception = capture_output { assertion.assert }
16+
raise exception if exception
17+
end
18+
let(:stdout) { @stdout }
19+
let(:stderr) { @stderr }
20+
21+
context 'when release pr label is nil' do
22+
let(:mocked_commands) { [] }
23+
it 'should not raise an error' do
24+
expect { assertion.assert }.not_to raise_error
25+
end
26+
end
27+
28+
context 'when release pr label is "release"' do
29+
let(:release_pr_label) { 'release' }
30+
31+
context 'when the label exists' do
32+
let(:mocked_commands) do
33+
[
34+
MockedCommand.new('gh label list', stdout: <<~LABELS)
35+
bug\tSomething isn't working\t#d73a4a
36+
release\tThe PR represents a release\t#0075ca
37+
duplicate\tThis issue or pull request already exists\t#cfd3d7
38+
LABELS
39+
]
40+
end
41+
42+
it 'should not raise an error' do
43+
expect { assertion.assert }.not_to raise_error
44+
end
45+
end
46+
47+
context 'when the label does not exist' do
48+
let(:mocked_commands) do
49+
[
50+
MockedCommand.new('gh label list', stdout: <<~LABELS)
51+
bug\tSomething isn't working\t#d73a4a
52+
duplicate\tThis issue or pull request already exists\t#cfd3d7
53+
LABELS
54+
]
55+
end
56+
57+
it 'should fail' do
58+
expect { subject }.to raise_error(SystemExit)
59+
expect(stderr).to start_with("ERROR: Release pr label 'release' does not exist\n")
60+
end
61+
end
62+
end
63+
64+
context 'when the gh command fails' do
65+
let(:release_pr_label) { 'release' }
66+
67+
let(:mocked_commands) do
68+
[
69+
MockedCommand.new('gh label list', exitstatus: 1)
70+
]
71+
end
72+
73+
it 'should fail' do
74+
expect { subject }.to raise_error(SystemExit)
75+
expect(stderr).to start_with('ERROR: Could not list pr labels')
76+
end
77+
end
78+
end
79+
end
80+
81+
# context 'when this is the first release' do
82+
# let(:release_type) { 'first' }
83+
# let(:mocked_commands) { [] }
84+
# it 'should not raise an error' do
85+
# expect { assertion.assert }.not_to raise_error
86+
# end
87+
# end
88+
89+
# context 'when this is NOT the first release' do
90+
# context 'when the last release tag does not exist' do
91+
# let(:mocked_commands) do
92+
# [
93+
# MockedCommand.new('git tag --list "v0.0.0"', stdout: "\n")
94+
# ]
95+
# end
96+
97+
# it 'should fail' do
98+
# expect { subject }.to raise_error(SystemExit)
99+
# expect(stderr).to start_with("ERROR: Last release tag 'v0.0.0' does not exist")
100+
# end
101+
# end
102+
103+
# context 'when the last release tag exists' do
104+
# let(:mocked_commands) do
105+
# [
106+
# MockedCommand.new('git tag --list "v0.0.0"', stdout: "v0.0.0\n")
107+
# ]
108+
# end
109+
110+
# it 'should succeed' do
111+
# expect { subject }.not_to raise_error
112+
# end
113+
# end
114+
115+
# context 'when the git command fails' do
116+
# let(:mocked_commands) do
117+
# [
118+
# MockedCommand.new('git tag --list "v0.0.0"', exitstatus: 1)
119+
# ]
120+
# end
121+
122+
# it 'should fail' do
123+
# expect { subject }.to raise_error(SystemExit)
124+
# expect(stderr).to start_with('ERROR: Could not list tags')
125+
# end
126+
# end
127+
# end
128+
# end
129+
# end

spec/create_github_release/command_line/parser_spec.rb

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@
3838
remote: nil,
3939
last_release_version: nil,
4040
next_release_version: nil,
41+
release_pr_label: nil,
4142
changelog_path: nil,
4243
quiet: false,
4344
verbose: false
@@ -83,6 +84,7 @@
8384
remote: nil,
8485
last_release_version: nil,
8586
next_release_version: nil,
87+
release_pr_label: nil,
8688
changelog_path: nil,
8789
quiet: false,
8890
verbose: false
@@ -128,6 +130,7 @@
128130
remote: nil,
129131
last_release_version: nil,
130132
next_release_version: nil,
133+
release_pr_label: nil,
131134
changelog_path: nil,
132135
quiet: false,
133136
verbose: false
@@ -140,6 +143,11 @@
140143
end
141144
end
142145

146+
context 'when the --release-pr-label option is given' do
147+
let(:args) { ['patch', '--release-pr-label=release'] }
148+
it { is_expected.to have_attributes(release_type: 'patch', release_pr_label: 'release') }
149+
end
150+
143151
context 'when the --quiet option is given' do
144152
let(:args) { ['patch', '--quiet'] }
145153
it { is_expected.to have_attributes(release_type: 'patch', quiet: true) }

0 commit comments

Comments
 (0)