git-pr-release 1.1.0 → 1.6.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 0ff787e6c979a6958c8be9e429eb7c089095a55fd20ac3ac497592564c9a9355
4
- data.tar.gz: 4db4523c50aa12ba8b7f51450197b6e00dc3560d40356cdfac035fa0a036d871
3
+ metadata.gz: 54aeb2f71e8311efc9d48ea98c695496ddf9a9755d23aa822078484384b83329
4
+ data.tar.gz: ee8358c06dc99f8d15983c81f9725c50111af8697253fcc39c66bf2eef3099d7
5
5
  SHA512:
6
- metadata.gz: ac75e33bbf1c2c8808ee0f22ebf397678401f12e384eae7042a82a5ec350ab3039f55a72d5d093da5242a5ee4a7d8bd892b717feca97f9deb58cc818039e98da
7
- data.tar.gz: f0e1566526389611d3e94da79fb74dd3df6495a2d5242866a69e796fe02ca3053cb1a70ffe226f34540d7b684c2f2d4ee25bd1b55cd10591e86d7315c68af16c
6
+ metadata.gz: 594733c2d859ec97b8573df6bfb805df8ad83ffde7e1f80afde7bdc889c1f73b1641f54d047657d58b9cea54c3a24375e56cac6088326fe24f28843be756bfda
7
+ data.tar.gz: '09f9266b8d49b75a8fc2981b524df7a8bfb3bffd100c258ac4d660a1fa6cf3906af51f4ad913ae7ba86a3c13f6b1ec27fb9ce7c59e4e7fde49e9f204544604c8'
data/CHANGELOG.md CHANGED
@@ -1,14 +1,51 @@
1
1
  # git-pr-release
2
2
 
3
+ ## v1.6.0 (2021-05-15)
4
+
5
+ [full changelog](https://github.com/x-motemen/git-pr-release/compare/v1.5.0...v1.6.0)
6
+
7
+ * (#63) Sort merged_pull_request_numbers numerically by default (@yutailang0119)
8
+
9
+ ## v1.5.0 (2021-04-02)
10
+
11
+ [full changelog](https://github.com/x-motemen/git-pr-release/compare/v1.4.0...v1.5.0)
12
+
13
+ * (#60, #61) Get issue number from GitHub API for squashed PR (@yuuan)
14
+ * (#58) Make stable test (@kachick)
15
+ * (#55) Suppress warning for ERB (@ohbarye)
16
+ * (#50) support `GIT_PR_RELEASE_MENTION` environment variable (@dabutvin)
17
+ * (#49) Transfer repository to x-motemen organization (@onk)
18
+
19
+ ## v1.4.0 (2020-02-22)
20
+
21
+ [full changelog](https://github.com/x-motemen/git-pr-release/compare/v1.3.0...v1.4.0)
22
+
23
+ * (#48) List PR API needs head user or head organization and branch name (@sasasin)
24
+
25
+ ## v1.3.0 (2020-02-19)
26
+
27
+ [full changelog](https://github.com/x-motemen/git-pr-release/compare/v1.2.0...v1.3.0)
28
+
29
+ * (#47) Fix Errno::ENOENT when finding the specified template (@onk)
30
+ * (#45) Fix "warning: instance variable @xxx not initialized" (@onk)
31
+
32
+ ## v1.2.0 (2020-02-07)
33
+
34
+ [full changelog](https://github.com/x-motemen/git-pr-release/compare/v1.1.0...v1.2.0)
35
+
36
+ * (#44) Use API option when detecting existing release PR (@onk)
37
+ * (#41, #42) Refactor (@onk)
38
+ - Some local variables are removed. This will break if you have customized the template ERB.
39
+
3
40
  ## v1.1.0 (2020-01-02)
4
41
 
5
- [full changelog](https://github.com/motemen/git-pr-release/compare/v1.0.1...v1.1.0)
42
+ [full changelog](https://github.com/x-motemen/git-pr-release/compare/v1.0.1...v1.1.0)
6
43
 
7
44
  * (#38) Fetch changed files as many as possible (@shibayu36)
8
45
 
9
46
  ## v1.0.1 (2019-12-17)
10
47
 
11
- [full changelog](https://github.com/motemen/git-pr-release/compare/v1.0.0...v1.0.1)
48
+ [full changelog](https://github.com/x-motemen/git-pr-release/compare/v1.0.0...v1.0.1)
12
49
 
13
50
  * (#37) Fix NameError (@onk)
14
51
 
data/README.md CHANGED
@@ -75,6 +75,15 @@ You can specify this value by `GIT_PR_RELEASE_LABELS` environment variable.
75
75
 
76
76
  If not specified, any labels will not be added for PRs.
77
77
 
78
+ ### `pr-release.mention`
79
+
80
+ The name that is listed next to each PR title.
81
+ Accepted values: `author`
82
+
83
+ You can specify this value by `GIT_PR_RELEASE_MENTION` environment variable.
84
+
85
+ If not specified, the mention will be the PR assignee
86
+
78
87
 
79
88
  Errors and exit statuses
80
89
  ------------------------
@@ -4,12 +4,12 @@ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
4
 
5
5
  Gem::Specification.new do |spec|
6
6
  spec.name = "git-pr-release"
7
- spec.version = '1.1.0'
7
+ spec.version = '1.6.0'
8
8
  spec.authors = ["motemen"]
9
9
  spec.email = ["motemen@gmail.com"]
10
10
  spec.summary = 'Creates a release pull request'
11
11
  spec.description = 'git-pr-release creates a pull request which summarizes feature branches that are to be released into production'
12
- spec.homepage = 'https://github.com/motemen/git-pr-release'
12
+ spec.homepage = 'https://github.com/x-motemen/git-pr-release'
13
13
 
14
14
  spec.files = `git ls-files`.split($/)
15
15
  spec.bindir = "exe"
@@ -24,6 +24,7 @@ Gem::Specification.new do |spec|
24
24
 
25
25
  spec.add_development_dependency 'rspec'
26
26
  spec.add_development_dependency 'timecop'
27
+ spec.add_development_dependency 'webmock'
27
28
 
28
29
  spec.license = 'MIT'
29
30
  end
@@ -6,20 +6,21 @@ module Git
6
6
  module Release
7
7
  class CLI
8
8
  include Git::Pr::Release::Util
9
- extend Git::Pr::Release::Util
10
- def self.start
11
- host, repository, scheme = host_and_repository_and_scheme
9
+ attr_reader :repository, :production_branch, :staging_branch, :template_path, :labels
12
10
 
13
- if host
14
- # GitHub:Enterprise
15
- OpenSSL::SSL.const_set :VERIFY_PEER, OpenSSL::SSL::VERIFY_NONE # XXX
11
+ def self.start
12
+ result = self.new.start
13
+ exit result
14
+ end
16
15
 
17
- Octokit.configure do |c|
18
- c.api_endpoint = "#{scheme}://#{host}/api/v3"
19
- c.web_endpoint = "#{scheme}://#{host}/"
20
- end
21
- end
16
+ def initialize
17
+ @dry_run = false
18
+ @json = false
19
+ @no_fetch = false
20
+ @squashed = false
21
+ end
22
22
 
23
+ def start
23
24
  OptionParser.new do |opts|
24
25
  opts.on('-n', '--dry-run', 'Do not create/update a PR. Just prints out') do |v|
25
26
  @dry_run = v
@@ -30,31 +31,78 @@ module Git
30
31
  opts.on('--no-fetch', 'Do not fetch from remote repo before determining target PRs (CI friendly)') do |v|
31
32
  @no_fetch = v
32
33
  end
34
+ opts.on('--squashed', 'Handle squash merged PRs') do |v|
35
+ @squashed = v
36
+ end
33
37
  end.parse!
34
38
 
35
39
  ### Set up configuration
40
+ configure
41
+
42
+ ### Fetch merged PRs
43
+ merged_prs = fetch_merged_prs
44
+ if merged_prs.empty?
45
+ say 'No pull requests to be released', :error
46
+ return 1
47
+ end
48
+
49
+ ### Create a release PR
50
+ create_release_pr(merged_prs)
51
+ return 0
52
+ end
53
+
54
+ def client
55
+ @client ||= Octokit::Client.new :access_token => obtain_token!
56
+ end
36
57
 
37
- production_branch = ENV.fetch('GIT_PR_RELEASE_BRANCH_PRODUCTION') { git_config('branch.production') } || 'master'
38
- staging_branch = ENV.fetch('GIT_PR_RELEASE_BRANCH_STAGING') { git_config('branch.staging') } || 'staging'
58
+ def configure
59
+ host, @repository, scheme = host_and_repository_and_scheme
60
+
61
+ if host
62
+ # GitHub:Enterprise
63
+ OpenSSL::SSL.const_set :VERIFY_PEER, OpenSSL::SSL::VERIFY_NONE # XXX
64
+
65
+ Octokit.configure do |c|
66
+ c.api_endpoint = "#{scheme}://#{host}/api/v3"
67
+ c.web_endpoint = "#{scheme}://#{host}/"
68
+ end
69
+ end
70
+
71
+ @production_branch = ENV.fetch('GIT_PR_RELEASE_BRANCH_PRODUCTION') { git_config('branch.production') } || 'master'
72
+ @staging_branch = ENV.fetch('GIT_PR_RELEASE_BRANCH_STAGING') { git_config('branch.staging') } || 'staging'
73
+ @template_path = ENV.fetch('GIT_PR_RELEASE_TEMPLATE') { git_config('template') }
74
+
75
+ _labels = ENV.fetch('GIT_PR_RELEASE_LABELS') { git_config('labels') }
76
+ @labels = _labels && _labels.split(/\s*,\s*/) || []
39
77
 
40
78
  say "Repository: #{repository}", :debug
41
79
  say "Production branch: #{production_branch}", :debug
42
80
  say "Staging branch: #{staging_branch}", :debug
81
+ say "Template path: #{template_path}", :debug
82
+ say "Labels #{labels}", :debug
83
+ end
43
84
 
44
- client = Octokit::Client.new :access_token => obtain_token!
45
-
85
+ def fetch_merged_prs
46
86
  git :remote, 'update', 'origin' unless @no_fetch
47
87
 
48
- ### Fetch merged PRs
88
+ merged_pull_request_numbers = @squashed ? fetch_merged_pr_numbers_from_github : fetch_merged_pr_numbers_from_git_remote
89
+
90
+ merged_prs = merged_pull_request_numbers.sort.map do |nr|
91
+ pr = client.pull_request repository, nr
92
+ say "To be released: ##{pr.number} #{pr.title}", :notice
93
+ pr
94
+ end
95
+
96
+ merged_prs
97
+ end
49
98
 
50
- merged_feature_head_sha1s = git(
51
- :log, '--merges', '--pretty=format:%P', "origin/#{production_branch}..origin/#{staging_branch}"
52
- ).map do |line|
99
+ def fetch_merged_pr_numbers_from_git_remote
100
+ merged_feature_head_sha1s = git(:log, '--merges', '--pretty=format:%P', "origin/#{production_branch}..origin/#{staging_branch}").map do |line|
53
101
  main_sha1, feature_sha1 = line.chomp.split /\s+/
54
102
  feature_sha1
55
103
  end
56
104
 
57
- merged_pull_request_numbers = git('ls-remote', 'origin', 'refs/pull/*/head').map do |line|
105
+ git('ls-remote', 'origin', 'refs/pull/*/head').map do |line|
58
106
  sha1, ref = line.chomp.split /\s+/
59
107
 
60
108
  if merged_feature_head_sha1s.include? sha1
@@ -72,87 +120,92 @@ module Git
72
120
  end
73
121
  end
74
122
  end.compact
123
+ end
75
124
 
76
- if merged_pull_request_numbers.empty?
77
- say 'No pull requests to be released', :error
78
- exit 1
79
- end
80
-
81
- merged_prs = merged_pull_request_numbers.map do |nr|
82
- pr = client.pull_request repository, nr
83
- say "To be released: ##{pr.number} #{pr.title}", :notice
84
- pr
85
- end
125
+ def fetch_merged_pr_numbers_from_github
126
+ git(:log, '--pretty=format:%H', "origin/#{production_branch}..origin/#{staging_branch}").map(&:chomp).map do |sha1|
127
+ client.search_issues("repo:#{repository} is:pr is:closed #{sha1}")[:items].map(&:number)
128
+ sleep 1
129
+ end.flatten
130
+ end
86
131
 
87
- ### Create a release PR
132
+ def create_release_pr(merged_prs)
133
+ found_release_pr = detect_existing_release_pr
134
+ create_mode = found_release_pr.nil?
88
135
 
89
- say 'Searching for existing release pull requests...', :info
90
- found_release_pr = client.pull_requests(repository).find do |pr|
91
- pr.head.ref == staging_branch && pr.base.ref == production_branch
136
+ if create_mode
137
+ if @dry_run
138
+ release_pr = nil
139
+ changed_files = []
140
+ else
141
+ release_pr = prepare_release_pr
142
+ changed_files = pull_request_files(release_pr)
143
+ end
144
+ else
145
+ release_pr = found_release_pr
146
+ changed_files = pull_request_files(release_pr)
92
147
  end
93
- create_mode = found_release_pr.nil?
94
148
 
95
- # Fetch changed files of a release PR
96
- changed_files = pull_request_files(client, found_release_pr)
149
+ pr_title, pr_body = build_and_merge_pr_title_and_body(release_pr, merged_prs, changed_files)
97
150
 
98
151
  if @dry_run
99
- pr_title, new_body = build_pr_title_and_body found_release_pr, merged_prs, changed_files
100
- pr_body = create_mode ? new_body : merge_pr_body(found_release_pr.body, new_body)
101
-
102
152
  say 'Dry-run. Not updating PR', :info
103
153
  say pr_title, :notice
104
154
  say pr_body, :notice
105
- dump_result_as_json( found_release_pr, merged_prs, changed_files ) if @json
106
- exit 0
155
+ dump_result_as_json( release_pr, merged_prs, changed_files ) if @json
156
+ return
107
157
  end
108
158
 
109
- pr_title, pr_body = nil, nil
110
- release_pr = nil
159
+ update_release_pr(release_pr, pr_title, pr_body)
111
160
 
112
- if create_mode
113
- created_pr = client.create_pull_request(
114
- repository, production_branch, staging_branch, 'Preparing release pull request...', ''
115
- )
116
- unless created_pr
117
- say 'Failed to create a new pull request', :error
118
- exit 2
119
- end
120
- changed_files = pull_request_files(client, created_pr) # Refetch changed files from created_pr
121
- pr_title, pr_body = build_pr_title_and_body created_pr, merged_prs, changed_files
122
- release_pr = created_pr
123
- else
124
- pr_title, new_body = build_pr_title_and_body found_release_pr, merged_prs, changed_files
125
- pr_body = merge_pr_body(found_release_pr.body, new_body)
126
- release_pr = found_release_pr
127
- end
161
+ say "#{create_mode ? 'Created' : 'Updated'} pull request: #{release_pr.rels[:html].href}", :notice
162
+ dump_result_as_json( release_pr, merged_prs, changed_files ) if @json
163
+ end
164
+
165
+ def detect_existing_release_pr
166
+ say 'Searching for existing release pull requests...', :info
167
+ user=repository.split("/")[0]
168
+ client.pull_requests(repository, head: "#{user}:#{staging_branch}", base: production_branch).first
169
+ end
170
+
171
+ def prepare_release_pr
172
+ client.create_pull_request(
173
+ repository, production_branch, staging_branch, 'Preparing release pull request...', ''
174
+ )
175
+ end
128
176
 
177
+ def build_and_merge_pr_title_and_body(release_pr, merged_prs, changed_files)
178
+ # release_pr is nil when dry_run && create_mode
179
+ old_body = release_pr ? release_pr.body : ""
180
+ pr_title, new_body = build_pr_title_and_body(release_pr, merged_prs, changed_files, template_path)
181
+
182
+ [pr_title, merge_pr_body(old_body, new_body)]
183
+ end
184
+
185
+ def update_release_pr(release_pr, pr_title, pr_body)
129
186
  say 'Pull request body:', :debug
130
187
  say pr_body, :debug
131
188
 
132
- updated_pull_request = client.update_pull_request(
189
+ client.update_pull_request(
133
190
  repository, release_pr.number, :title => pr_title, :body => pr_body
134
191
  )
135
192
 
136
- unless updated_pull_request
137
- say 'Failed to update a pull request', :error
138
- exit 3
139
- end
140
-
141
- labels = ENV.fetch('GIT_PR_RELEASE_LABELS') { git_config('labels') }
142
- if not labels.nil? and not labels.empty?
143
- labels = labels.split(/\s*,\s*/)
144
- labeled_pull_request = client.add_labels_to_an_issue(
193
+ unless labels.empty?
194
+ client.add_labels_to_an_issue(
145
195
  repository, release_pr.number, labels
146
196
  )
147
-
148
- unless labeled_pull_request
149
- say 'Failed to add labels to a pull request', :error
150
- exit 4
151
- end
152
197
  end
198
+ end
153
199
 
154
- say "#{create_mode ? 'Created' : 'Updated'} pull request: #{updated_pull_request.rels[:html].href}", :notice
155
- dump_result_as_json( release_pr, merged_prs, changed_files ) if @json
200
+ # Fetch PR files of specified pull_request
201
+ def pull_request_files(pull_request)
202
+ return [] if pull_request.nil?
203
+
204
+ # Fetch files as many as possible
205
+ client.auto_paginate = true
206
+ files = client.pull_request_files repository, pull_request.number
207
+ client.auto_paginate = false
208
+ return files
156
209
  end
157
210
  end
158
211
  end
@@ -34,7 +34,7 @@ module Git
34
34
  end
35
35
 
36
36
  def self.mention_type
37
- @mention_type ||= (git_config('mention') || 'default')
37
+ @mention_type ||= (ENV.fetch('GIT_PR_RELEASE_MENTION') { git_config('mention') } || 'default')
38
38
  end
39
39
  end
40
40
  end
@@ -83,18 +83,22 @@ Release <%= Time.now %>
83
83
  <% end -%>
84
84
  ERB
85
85
 
86
- def build_pr_title_and_body(release_pr, merged_prs, changed_files)
86
+ def build_pr_title_and_body(release_pr, merged_prs, changed_files, template_path)
87
87
  release_pull_request = target_pull_request = release_pr ? PullRequest.new(release_pr) : DummyPullRequest.new
88
88
  merged_pull_requests = pull_requests = merged_prs.map { |pr| PullRequest.new(pr) }
89
89
 
90
- template = DEFAULT_PR_TEMPLATE
91
-
92
- if path = ENV.fetch('GIT_PR_RELEASE_TEMPLATE') { git_config('template') }
93
- template_path = File.join(git('rev-parse', '--show-toplevel').first.chomp, path)
94
- template = File.read(template_path)
95
- end
96
-
97
- erb = ERB.new template, nil, '-'
90
+ template = if template_path
91
+ template_fullpath = File.join(git('rev-parse', '--show-toplevel').first.chomp, template_path)
92
+ File.read(template_fullpath)
93
+ else
94
+ DEFAULT_PR_TEMPLATE
95
+ end
96
+
97
+ erb = if RUBY_VERSION >= '2.6'
98
+ ERB.new template, trim_mode: '-'
99
+ else
100
+ ERB.new template, nil, '-'
101
+ end
98
102
  content = erb.result binding
99
103
  content.split(/\n/, 2)
100
104
  end
@@ -195,19 +199,6 @@ ERB
195
199
 
196
200
  auth
197
201
  end
198
-
199
- # Fetch PR files of specified pull_request
200
- def pull_request_files(client, pull_request)
201
- return [] if pull_request.nil?
202
-
203
- host, repository, scheme = host_and_repository_and_scheme
204
-
205
- # Fetch files as many as possible
206
- client.auto_paginate = true
207
- files = client.pull_request_files repository, pull_request.number
208
- client.auto_paginate = false
209
- return files
210
- end
211
202
  end
212
203
  end
213
204
  end
@@ -0,0 +1,487 @@
1
+ RSpec.describe Git::Pr::Release::CLI do
2
+ let(:configured_cli) {
3
+ cli = Git::Pr::Release::CLI.new
4
+ allow(cli).to receive(:host_and_repository_and_scheme) {
5
+ [nil, "motemen/git-pr-release", "https"]
6
+ }
7
+ allow(cli).to receive(:git_config).with(anything) { nil }
8
+ cli.configure
9
+ cli
10
+ }
11
+
12
+ describe "#start" do
13
+ subject { @cli.start }
14
+
15
+ before {
16
+ @cli = Git::Pr::Release::CLI.new
17
+
18
+ allow(@cli).to receive(:configure)
19
+ allow(@cli).to receive(:fetch_merged_prs) { merged_prs }
20
+ allow(@cli).to receive(:create_release_pr)
21
+ }
22
+
23
+ context "When merged_prs is empty" do
24
+ let(:merged_prs) { [] }
25
+ it {
26
+ is_expected.to eq 1
27
+ expect(@cli).to have_received(:configure)
28
+ expect(@cli).to have_received(:fetch_merged_prs)
29
+ expect(@cli).not_to have_received(:create_release_pr)
30
+ }
31
+ end
32
+
33
+ context "When merged_prs exists" do
34
+ let(:merged_prs) {
35
+ agent = Sawyer::Agent.new("http://example.com/") do |conn|
36
+ conn.builder.handlers.delete(Faraday::Adapter::NetHttp)
37
+ conn.adapter(:test, Faraday::Adapter::Test::Stubs.new)
38
+ end
39
+ pr_3 = Sawyer::Resource.new(agent, YAML.load_file(file_fixture("pr_3.yml")))
40
+ pr_4 = Sawyer::Resource.new(agent, YAML.load_file(file_fixture("pr_4.yml")))
41
+ [pr_3, pr_4]
42
+ }
43
+ it {
44
+ is_expected.to eq 0
45
+ expect(@cli).to have_received(:configure)
46
+ expect(@cli).to have_received(:fetch_merged_prs)
47
+ expect(@cli).to have_received(:create_release_pr).with(merged_prs)
48
+ }
49
+ end
50
+ end
51
+
52
+ describe "#configure" do
53
+ subject { @cli.configure }
54
+
55
+ before {
56
+ @cli = Git::Pr::Release::CLI.new
57
+
58
+ allow(@cli).to receive(:git_config).with(anything) { nil }
59
+ }
60
+
61
+ context "When default" do
62
+ before {
63
+ allow(@cli).to receive(:host_and_repository_and_scheme) {
64
+ [nil, "motemen/git-pr-release", "https"]
65
+ }
66
+ }
67
+
68
+ it "configured as default" do
69
+ subject
70
+
71
+ expect(Octokit.api_endpoint).to eq "https://api.github.com/"
72
+ expect(Octokit.web_endpoint).to eq "https://github.com/"
73
+
74
+ expect(@cli.repository).to eq "motemen/git-pr-release"
75
+ expect(@cli.production_branch).to eq "master"
76
+ expect(@cli.staging_branch).to eq "staging"
77
+ expect(@cli.template_path).to eq nil
78
+ expect(@cli.labels).to eq []
79
+ end
80
+ end
81
+
82
+ context "When GitHub Enterprise Server" do
83
+ before {
84
+ allow(@cli).to receive(:host_and_repository_and_scheme) {
85
+ ["example.com", "motemen/git-pr-release", "https"]
86
+ }
87
+ }
88
+ after {
89
+ Octokit.reset!
90
+ }
91
+
92
+ it "octokit is configured" do
93
+ subject
94
+
95
+ expect(Octokit.api_endpoint).to eq "https://example.com/api/v3/"
96
+ expect(Octokit.web_endpoint).to eq "https://example.com/"
97
+ end
98
+ end
99
+
100
+ describe "branches" do
101
+ context "When branches are set by ENV" do
102
+ around do |example|
103
+ original = ENV.to_hash
104
+ begin
105
+ ENV["GIT_PR_RELEASE_BRANCH_PRODUCTION"] = "prod"
106
+ ENV["GIT_PR_RELEASE_BRANCH_STAGING"] = "dev"
107
+ example.run
108
+ ensure
109
+ ENV.replace(original)
110
+ end
111
+ end
112
+
113
+ it "branches are configured" do
114
+ subject
115
+
116
+ expect(@cli.production_branch).to eq "prod"
117
+ expect(@cli.staging_branch).to eq "dev"
118
+ end
119
+ end
120
+
121
+ context "When branches are set by git_config" do
122
+ before {
123
+ allow(@cli).to receive(:git_config).with("branch.production") { "production" }
124
+ allow(@cli).to receive(:git_config).with("branch.staging") { "develop" }
125
+ }
126
+
127
+ it "branches are configured" do
128
+ subject
129
+
130
+ expect(@cli.production_branch).to eq "production"
131
+ expect(@cli.staging_branch).to eq "develop"
132
+ end
133
+ end
134
+ end
135
+
136
+ describe "labels" do
137
+ context "With ENV" do
138
+ around do |example|
139
+ original = ENV.to_hash
140
+ begin
141
+ ENV["GIT_PR_RELEASE_LABELS"] = env_labels
142
+ example.run
143
+ ensure
144
+ ENV.replace(original)
145
+ end
146
+ end
147
+
148
+ context "string" do
149
+ let(:env_labels) { "release" }
150
+ it "set labels" do
151
+ subject
152
+ expect(@cli.labels).to eq ["release"]
153
+ end
154
+ end
155
+
156
+ context "comma separated string" do
157
+ let(:env_labels) { "release,release2" }
158
+ it "set labels" do
159
+ subject
160
+ expect(@cli.labels).to eq ["release", "release2"]
161
+ end
162
+ end
163
+
164
+ context "empty string" do
165
+ let(:env_labels) { "" }
166
+ it "set labels as default" do
167
+ subject
168
+ expect(@cli.labels).to eq []
169
+ end
170
+ end
171
+ end
172
+
173
+ context "With git_config" do
174
+ before {
175
+ allow(@cli).to receive(:git_config).with("labels") { "release" }
176
+ }
177
+
178
+ it "set labels" do
179
+ subject
180
+ expect(@cli.labels).to eq ["release"]
181
+ end
182
+ end
183
+ end
184
+ end
185
+
186
+ describe "#fetch_merged_prs" do
187
+ subject { @cli.fetch_merged_prs }
188
+
189
+ before {
190
+ @cli = configured_cli
191
+
192
+ agent = Sawyer::Agent.new("http://example.com/") do |conn|
193
+ conn.builder.handlers.delete(Faraday::Adapter::NetHttp)
194
+ conn.adapter(:test, Faraday::Adapter::Test::Stubs.new)
195
+ end
196
+
197
+ expect(@cli).to receive(:git).with(:remote, "update", "origin") {
198
+ []
199
+ }
200
+
201
+ expect(@cli).to receive(:git).with(:log, "--merges", "--pretty=format:%P", "origin/master..origin/staging") {
202
+ <<~GIT_LOG.each_line
203
+ ad694b9c2b868e8801f9209f0ad5dd5458c49854 42bd43b80c973c8f348df3521745201be05bf194
204
+ b620bead10831d2e4e15be392e0a435d3470a0ad 5c977a1827387ac7b7a85c7b827ee119165f1823
205
+ GIT_LOG
206
+ }
207
+ expect(@cli).to receive(:git).with("ls-remote", "origin", "refs/pull/*/head") {
208
+ <<~GIT_LS_REMOTE.each_line
209
+ bbcd2a04ef394e91be44c24e93e52fdbca944060 refs/pull/1/head
210
+ 5c977a1827387ac7b7a85c7b827ee119165f1823 refs/pull/3/head
211
+ 42bd43b80c973c8f348df3521745201be05bf194 refs/pull/4/head
212
+ GIT_LS_REMOTE
213
+ }
214
+ expect(@cli).to receive(:git).with("merge-base", "5c977a1827387ac7b7a85c7b827ee119165f1823", "origin/master") {
215
+ "b620bead10831d2e4e15be392e0a435d3470a0ad".each_line
216
+ }
217
+ expect(@cli).to receive(:git).with("merge-base", "42bd43b80c973c8f348df3521745201be05bf194", "origin/master") {
218
+ "b620bead10831d2e4e15be392e0a435d3470a0ad".each_line
219
+ }
220
+
221
+ client = double(Octokit::Client)
222
+ @pr_3 = Sawyer::Resource.new(agent, YAML.load_file(file_fixture("pr_3.yml")))
223
+ @pr_4 = Sawyer::Resource.new(agent, YAML.load_file(file_fixture("pr_4.yml")))
224
+ expect(client).to receive(:pull_request).with("motemen/git-pr-release", 3) { @pr_3 }
225
+ expect(client).to receive(:pull_request).with("motemen/git-pr-release", 4) { @pr_4 }
226
+ allow(@cli).to receive(:client).with(no_args) { client }
227
+ }
228
+
229
+ it { is_expected.to eq [@pr_3, @pr_4] }
230
+ end
231
+
232
+ describe "#create_release_pr" do
233
+ subject { @cli.create_release_pr(@merged_prs) }
234
+
235
+ before {
236
+ @cli = configured_cli
237
+
238
+ @agent = Sawyer::Agent.new("http://example.com/") do |conn|
239
+ conn.builder.handlers.delete(Faraday::Adapter::NetHttp)
240
+ conn.adapter(:test, Faraday::Adapter::Test::Stubs.new)
241
+ end
242
+
243
+ @merged_prs = [
244
+ Sawyer::Resource.new(@agent, YAML.load_file(file_fixture("pr_3.yml"))),
245
+ Sawyer::Resource.new(@agent, YAML.load_file(file_fixture("pr_4.yml"))),
246
+ ]
247
+
248
+ allow(@cli).to receive(:detect_existing_release_pr) { existing_release_pr }
249
+ @pr_title = "Release 2020-01-04 16:51:09 +0900"
250
+ @pr_body = <<~BODY.chomp
251
+ - [ ] #3 Provides a creating release pull-request object for template @hakobe
252
+ - [ ] #4 use user who create PR if there is no assignee @motemen
253
+ BODY
254
+ allow(@cli).to receive(:build_and_merge_pr_title_and_body) {
255
+ [@pr_title, @pr_body]
256
+ }
257
+ allow(@cli).to receive(:update_release_pr)
258
+ @changed_files = [double(Sawyer::Resource)]
259
+ allow(@cli).to receive(:pull_request_files) { @changed_files }
260
+ }
261
+
262
+ context "When create_mode" do
263
+ before {
264
+ @created_pr = Sawyer::Resource.new(@agent, YAML.load_file(file_fixture("pr_1.yml")))
265
+ allow(@cli).to receive(:prepare_release_pr) { @created_pr }
266
+ }
267
+
268
+ let(:existing_release_pr) { nil }
269
+
270
+ it {
271
+ subject
272
+
273
+ expect(@cli).to have_received(:detect_existing_release_pr)
274
+ expect(@cli).to have_received(:prepare_release_pr)
275
+ expect(@cli).to have_received(:pull_request_files)
276
+ expect(@cli).to have_received(:build_and_merge_pr_title_and_body).with(@created_pr, @merged_prs, @changed_files)
277
+ expect(@cli).to have_received(:update_release_pr).with(@created_pr, @pr_title, @pr_body)
278
+ }
279
+ end
280
+
281
+ context "When not create_mode" do
282
+ before {
283
+ allow(@cli).to receive(:prepare_release_pr)
284
+ }
285
+
286
+ let(:existing_release_pr) { double(
287
+ number: 1023,
288
+ rels: { html: double(href: "https://github.com/motemen/git-pr-release/pull/1023") },
289
+ )}
290
+
291
+ it {
292
+ subject
293
+
294
+ expect(@cli).to have_received(:detect_existing_release_pr)
295
+ expect(@cli).to have_received(:pull_request_files).with(existing_release_pr)
296
+ expect(@cli).not_to have_received(:prepare_release_pr)
297
+ expect(@cli).to have_received(:build_and_merge_pr_title_and_body).with(existing_release_pr, @merged_prs, @changed_files)
298
+ expect(@cli).to have_received(:update_release_pr).with(existing_release_pr, @pr_title, @pr_body)
299
+ }
300
+ end
301
+
302
+ context "When dry_run with create_mode" do
303
+ before {
304
+ @created_pr = Sawyer::Resource.new(@agent, YAML.load_file(file_fixture("pr_1.yml")))
305
+ allow(@cli).to receive(:prepare_release_pr) { @created_pr }
306
+
307
+ @cli.instance_variable_set(:@dry_run, true)
308
+ }
309
+
310
+ let(:existing_release_pr) { nil }
311
+
312
+ it {
313
+ subject
314
+
315
+ expect(@cli).to have_received(:detect_existing_release_pr)
316
+ expect(@cli).not_to have_received(:prepare_release_pr)
317
+ expect(@cli).not_to have_received(:pull_request_files)
318
+ expect(@cli).to have_received(:build_and_merge_pr_title_and_body).with(nil, @merged_prs, [])
319
+ expect(@cli).not_to have_received(:update_release_pr).with(@created_pr, @pr_title, @pr_body)
320
+ }
321
+ end
322
+ end
323
+
324
+ describe "#prepare_release_pr" do
325
+ subject { @cli.prepare_release_pr }
326
+
327
+ before {
328
+ @cli = configured_cli
329
+
330
+ @client = double(Octokit::Client)
331
+ allow(@client).to receive(:create_pull_request)
332
+ allow(@cli).to receive(:client) { @client }
333
+ }
334
+
335
+ it {
336
+ subject
337
+
338
+ expect(@client).to have_received(:create_pull_request).with(
339
+ "motemen/git-pr-release",
340
+ "master",
341
+ "staging",
342
+ "Preparing release pull request...",
343
+ "", # empby body
344
+ )
345
+ }
346
+ end
347
+
348
+ describe "#build_and_merge_pr_title_and_body" do
349
+ subject { @cli.build_and_merge_pr_title_and_body(release_pr, @merged_prs, changed_files) }
350
+
351
+ before {
352
+ @cli = configured_cli
353
+
354
+ @merged_prs = [double(Sawyer::Resource)]
355
+ allow(@cli).to receive(:build_pr_title_and_body) { ["PR Title", "PR Body"] }
356
+ allow(@cli).to receive(:merge_pr_body) { "Merged Body" }
357
+ }
358
+
359
+ context "When release_pr exists" do
360
+ let(:release_pr) { double(body: "Old Body") }
361
+ let(:changed_files) { [double(Sawyer::Resource)] }
362
+
363
+ it {
364
+ is_expected.to eq ["PR Title", "Merged Body"]
365
+
366
+ expect(@cli).to have_received(:build_pr_title_and_body).with(release_pr, @merged_prs, changed_files, nil)
367
+ expect(@cli).to have_received(:merge_pr_body).with("Old Body", "PR Body")
368
+ }
369
+ end
370
+
371
+ context "When release_pr is nil" do
372
+ # = When dry_run with create_mode
373
+ let(:release_pr) { nil }
374
+ let(:changed_files) { [] }
375
+
376
+ it {
377
+ is_expected.to eq ["PR Title", "Merged Body"]
378
+
379
+ expect(@cli).to have_received(:build_pr_title_and_body).with(release_pr, @merged_prs, changed_files, nil)
380
+ expect(@cli).to have_received(:merge_pr_body).with("", "PR Body")
381
+ }
382
+ end
383
+ end
384
+
385
+ describe "#update_release_pr" do
386
+ subject { @cli.update_release_pr(@release_pr, "PR Title", "PR Body") }
387
+
388
+ before {
389
+ @cli = configured_cli
390
+
391
+ @release_pr = double(number: 1023)
392
+
393
+ @client = double(Octokit::Client)
394
+ allow(@client).to receive(:update_pull_request) { @release_pr }
395
+ allow(@client).to receive(:add_labels_to_an_issue)
396
+ allow(@cli).to receive(:client) { @client }
397
+ }
398
+
399
+ context "Without labels" do
400
+ it {
401
+ subject
402
+
403
+ expect(@client).to have_received(:update_pull_request).with(
404
+ "motemen/git-pr-release",
405
+ 1023,
406
+ {
407
+ title: "PR Title",
408
+ body: "PR Body",
409
+ }
410
+ )
411
+ expect(@client).not_to have_received(:add_labels_to_an_issue)
412
+ }
413
+ end
414
+
415
+ context "With labels" do
416
+ before {
417
+ allow(@cli).to receive(:labels) { ["release"] }
418
+ }
419
+ it {
420
+ subject
421
+
422
+ expect(@client).to have_received(:update_pull_request).with(
423
+ "motemen/git-pr-release",
424
+ 1023,
425
+ {
426
+ title: "PR Title",
427
+ body: "PR Body",
428
+ }
429
+ )
430
+ expect(@client).to have_received(:add_labels_to_an_issue).with(
431
+ "motemen/git-pr-release", 1023, ["release"]
432
+ )
433
+ }
434
+ end
435
+ end
436
+
437
+ describe "#detect_existing_release_pr" do
438
+ subject { @cli.detect_existing_release_pr }
439
+
440
+ before {
441
+ @cli = configured_cli
442
+
443
+ @client = double(Octokit::Client)
444
+ allow(@cli).to receive(:client).with(no_args) { @client }
445
+ }
446
+
447
+ context "When exists" do
448
+ before {
449
+ @release_pr = double(head: double(ref: "staging"), base: double(ref: "master"))
450
+ allow(@client).to receive(:pull_requests) { [@release_pr] }
451
+ }
452
+
453
+ it { is_expected.to eq @release_pr }
454
+ end
455
+
456
+ context "When not exists" do
457
+ before {
458
+ allow(@client).to receive(:pull_requests) { [] }
459
+ }
460
+
461
+ it { is_expected.to be_nil }
462
+ end
463
+ end
464
+
465
+ describe "#pull_request_files" do
466
+ subject { @cli.pull_request_files(@release_pr) }
467
+
468
+ before {
469
+ @cli = configured_cli
470
+
471
+ @release_pr = double(number: 1023)
472
+ @client = double(Octokit::Client)
473
+ @changed_files = [double(Sawyer::Resource)]
474
+ allow(@client).to receive(:pull_request_files) { @changed_files }
475
+ allow(@client).to receive(:auto_paginate=)
476
+ allow(@cli).to receive(:client) { @client }
477
+ }
478
+
479
+ it {
480
+ is_expected.to eq @changed_files
481
+
482
+ expect(@client).to have_received(:auto_paginate=).with(true)
483
+ expect(@client).to have_received(:pull_request_files).with("motemen/git-pr-release", 1023)
484
+ expect(@client).to have_received(:auto_paginate=).with(false)
485
+ }
486
+ end
487
+ end
@@ -19,9 +19,9 @@ RSpec.describe Git::Pr::Release do
19
19
  end
20
20
 
21
21
  describe "#build_pr_title_and_body" do
22
- context "without any options" do
22
+ context "without template_path" do
23
23
  it {
24
- pr_title, new_body = build_pr_title_and_body(@release_pr, @merged_prs, @changed_files)
24
+ pr_title, new_body = build_pr_title_and_body(@release_pr, @merged_prs, @changed_files, nil)
25
25
  expect(pr_title).to eq "Release 2019-02-20 22:58:35 +0900"
26
26
  expect(new_body).to eq <<~MARKDOWN
27
27
  - [ ] #3 Provides a creating release pull-request object for template @hakobe
@@ -30,37 +30,15 @@ RSpec.describe Git::Pr::Release do
30
30
  }
31
31
  end
32
32
 
33
- context "with ENV" do
34
- before {
35
- ENV["GIT_PR_RELEASE_TEMPLATE"] = "spec/fixtures/file/template_1.erb"
36
- }
37
-
38
- after {
39
- ENV["GIT_PR_RELEASE_TEMPLATE"] = nil
40
- }
41
-
33
+ context "with template_path" do
42
34
  it {
43
- pr_title, new_body = build_pr_title_and_body(@release_pr, @merged_prs, @changed_files)
35
+ pr_title, new_body = build_pr_title_and_body(@release_pr, @merged_prs, @changed_files, "spec/fixtures/file/template_1.erb")
44
36
  expect(pr_title).to eq "a"
45
37
  expect(new_body).to eq <<~MARKDOWN
46
38
  b
47
39
  MARKDOWN
48
40
  }
49
41
  end
50
-
51
- context "with git_config template" do
52
- before {
53
- expect(self).to receive(:git_config).with("template") { "spec/fixtures/file/template_2.erb" }
54
- }
55
-
56
- it {
57
- pr_title, new_body = build_pr_title_and_body(@release_pr, @merged_prs, @changed_files)
58
- expect(pr_title).to eq "c"
59
- expect(new_body).to eq <<~MARKDOWN
60
- d
61
- MARKDOWN
62
- }
63
- end
64
42
  end
65
43
 
66
44
  describe "#dump_result_as_json" do
data/spec/spec_helper.rb CHANGED
@@ -1,6 +1,7 @@
1
1
  require "timecop"
2
2
  require "yaml"
3
3
  require "git/pr/release"
4
+ require "webmock/rspec"
4
5
 
5
6
  RSpec.configure do |config|
6
7
  config.expect_with :rspec do |expectations|
@@ -18,6 +19,15 @@ RSpec.configure do |config|
18
19
  config.order = :random
19
20
  Kernel.srand config.seed
20
21
 
22
+ config.around(:each) do |example|
23
+ begin
24
+ ENV['TZ'], old = 'Asia/Tokyo', ENV['TZ']
25
+ example.run
26
+ ensure
27
+ ENV['TZ'] = old
28
+ end
29
+ end
30
+
21
31
  config.after do
22
32
  Timecop.return
23
33
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: git-pr-release
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.1.0
4
+ version: 1.6.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - motemen
8
- autorequire:
8
+ autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2020-01-02 00:00:00.000000000 Z
11
+ date: 2021-05-14 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: octokit
@@ -94,6 +94,20 @@ dependencies:
94
94
  - - ">="
95
95
  - !ruby/object:Gem::Version
96
96
  version: '0'
97
+ - !ruby/object:Gem::Dependency
98
+ name: webmock
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - ">="
102
+ - !ruby/object:Gem::Version
103
+ version: '0'
104
+ type: :development
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - ">="
109
+ - !ruby/object:Gem::Version
110
+ version: '0'
97
111
  description: git-pr-release creates a pull request which summarizes feature branches
98
112
  that are to be released into production
99
113
  email:
@@ -124,15 +138,16 @@ files:
124
138
  - spec/fixtures/file/pr_6.yml
125
139
  - spec/fixtures/file/template_1.erb
126
140
  - spec/fixtures/file/template_2.erb
141
+ - spec/git/pr/release/cli_spec.rb
127
142
  - spec/git/pr/release_spec.rb
128
143
  - spec/spec_helper.rb
129
144
  - spec/support/capture_support.rb
130
145
  - spec/support/file_fixture_support.rb
131
- homepage: https://github.com/motemen/git-pr-release
146
+ homepage: https://github.com/x-motemen/git-pr-release
132
147
  licenses:
133
148
  - MIT
134
149
  metadata: {}
135
- post_install_message:
150
+ post_install_message:
136
151
  rdoc_options: []
137
152
  require_paths:
138
153
  - lib
@@ -147,8 +162,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
147
162
  - !ruby/object:Gem::Version
148
163
  version: '0'
149
164
  requirements: []
150
- rubygems_version: 3.0.3
151
- signing_key:
165
+ rubygems_version: 3.3.0.dev
166
+ signing_key:
152
167
  specification_version: 4
153
168
  summary: Creates a release pull request
154
169
  test_files:
@@ -159,6 +174,7 @@ test_files:
159
174
  - spec/fixtures/file/pr_6.yml
160
175
  - spec/fixtures/file/template_1.erb
161
176
  - spec/fixtures/file/template_2.erb
177
+ - spec/git/pr/release/cli_spec.rb
162
178
  - spec/git/pr/release_spec.rb
163
179
  - spec/spec_helper.rb
164
180
  - spec/support/capture_support.rb