git-pr-release 0.6.0 → 0.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: f42f2fbcbe9b84d8350c7000d2b0ca020cb6dd1cea5e1052589d496c6a508ed8
4
- data.tar.gz: 3870591b45c379d9d313eb3dd5b753b299e1b794700b84b0ba5be37ba99e45d9
3
+ metadata.gz: 9a844daa490c47830e91fa7246c0b156c125c88015267325270a38591714efd3
4
+ data.tar.gz: c186c70a276036feb41445a96c1902f75978e22a41733ecc48e82debd8b57c18
5
5
  SHA512:
6
- metadata.gz: c12cef27a2b08b16578c136bcd64b65c3f432df57f812decd735e38af71256d7034fcd0846345221e7edd0a336b3b06dd31b7427b74526294aa59ab24848f092
7
- data.tar.gz: 1d47e89c6b8cb2186708c9925f02c0fddae0c101076a62861ccdccce85140e32bc38524b831380fb47d851d65634581e05d754648293e4f549e9f81a3cfabffb
6
+ metadata.gz: c3b8706c8becc5547e7e7ad628aec694a94e912958f3acb717c9228d62e84132cc4382c5a0e90e4984cb1074fc64ac33495605a48b1f3b227efb048678ccd9f7
7
+ data.tar.gz: 330ddac17a6514d06a9ddf522f50be4f267f5fce53a32fb0f20e7cfadfa30eb64dad647386e265c3541d5c8118795c207aad1337c950ae62e8678031b5b4a7c2
data/.gitignore CHANGED
@@ -1,3 +1,4 @@
1
1
  /.bundle/
2
2
  *.gem
3
3
  Gemfile.lock
4
+ /.rspec_status
data/.rspec ADDED
@@ -0,0 +1 @@
1
+ --require spec_helper
data/bin/git-pr-release CHANGED
@@ -153,7 +153,16 @@ def merge_pr_body(old_body, new_body)
153
153
  # Try to take over checklist statuses
154
154
  pr_body_lines = []
155
155
 
156
- Diff::LCS.traverse_balanced(old_body.split(/\r?\n/), new_body.split(/\r?\n/)) do |event|
156
+ check_status = {}
157
+ old_body.split(/\r?\n/).each { |line|
158
+ line.match(/^- \[(?<check_value>[ x])\] #(?<issue_number>\d+)/) { |m|
159
+ say "Found pull-request checkbox \##{m[:issue_number]} is #{m[:check_value]}.", :trace
160
+ check_status[m[:issue_number]] = m[:check_value]
161
+ }
162
+ }
163
+ old_body_unchecked = old_body.gsub /^- \[[ x]\] \#(\d+)/, '- [ ] #\1'
164
+
165
+ Diff::LCS.traverse_balanced(old_body_unchecked.split(/\r?\n/), new_body.split(/\r?\n/)) do |event|
157
166
  say "diff: #{event.inspect}", :trace
158
167
  action, old, new = *event
159
168
  old_nr, old_line = *old
@@ -167,7 +176,7 @@ def merge_pr_body(old_body, new_body)
167
176
  say "Use old line: #{old_line}", :trace
168
177
  pr_body_lines << old_line
169
178
  when '!'
170
- if [ old_line, new_line ].all? { |line| /^- \[[ x]\]/ === line }
179
+ if [ old_line, new_line ].all? { |line| /^- \[ \]/ === line }
171
180
  say "Found checklist diff; use old one: #{old_line}", :trace
172
181
  pr_body_lines << old_line
173
182
  else
@@ -183,7 +192,13 @@ def merge_pr_body(old_body, new_body)
183
192
  end
184
193
  end
185
194
 
186
- pr_body_lines.join("\n")
195
+ merged_body = pr_body_lines.join("\n")
196
+ check_status.each { |issue_number, check_value|
197
+ say "Update pull-request checkbox \##{issue_number} to #{check_value}.", :trace
198
+ merged_body.gsub! /^- \[ \] \##{issue_number}/, "- [#{check_value}] \##{issue_number}"
199
+ }
200
+
201
+ merged_body
187
202
  end
188
203
 
189
204
  def obtain_token!
@@ -246,148 +261,152 @@ def pull_request_files(client, pull_request)
246
261
  return client.pull_request_files repository, pull_request.number
247
262
  end
248
263
 
249
- host, repository, scheme = host_and_repository_and_scheme
264
+ def main
265
+ host, repository, scheme = host_and_repository_and_scheme
250
266
 
251
- if host
252
- # GitHub:Enterprise
253
- OpenSSL::SSL.const_set :VERIFY_PEER, OpenSSL::SSL::VERIFY_NONE # XXX
267
+ if host
268
+ # GitHub:Enterprise
269
+ OpenSSL::SSL.const_set :VERIFY_PEER, OpenSSL::SSL::VERIFY_NONE # XXX
254
270
 
255
- Octokit.configure do |c|
256
- c.api_endpoint = "#{scheme}://#{host}/api/v3"
257
- c.web_endpoint = "#{scheme}://#{host}/"
271
+ Octokit.configure do |c|
272
+ c.api_endpoint = "#{scheme}://#{host}/api/v3"
273
+ c.web_endpoint = "#{scheme}://#{host}/"
274
+ end
258
275
  end
259
- end
260
276
 
261
- OptionParser.new do |opts|
262
- opts.on('-n', '--dry-run', 'Do not create/update a PR. Just prints out') do |v|
263
- @dry_run = v
264
- end
265
- opts.on('--json', 'Show data of target PRs in JSON format') do |v|
266
- @json = v
267
- end
268
- opts.on('--no-fetch', 'Do not fetch from remote repo before determining target PRs (CI friendly)') do |v|
269
- @no_fetch = v
270
- end
271
- end.parse!
277
+ OptionParser.new do |opts|
278
+ opts.on('-n', '--dry-run', 'Do not create/update a PR. Just prints out') do |v|
279
+ @dry_run = v
280
+ end
281
+ opts.on('--json', 'Show data of target PRs in JSON format') do |v|
282
+ @json = v
283
+ end
284
+ opts.on('--no-fetch', 'Do not fetch from remote repo before determining target PRs (CI friendly)') do |v|
285
+ @no_fetch = v
286
+ end
287
+ end.parse!
272
288
 
273
- ### Set up configuration
289
+ ### Set up configuration
274
290
 
275
- production_branch = ENV.fetch('GIT_PR_RELEASE_BRANCH_PRODUCTION') { git_config('branch.production') } || 'master'
276
- staging_branch = ENV.fetch('GIT_PR_RELEASE_BRANCH_STAGING') { git_config('branch.staging') } || 'staging'
291
+ production_branch = ENV.fetch('GIT_PR_RELEASE_BRANCH_PRODUCTION') { git_config('branch.production') } || 'master'
292
+ staging_branch = ENV.fetch('GIT_PR_RELEASE_BRANCH_STAGING') { git_config('branch.staging') } || 'staging'
277
293
 
278
- say "Repository: #{repository}", :debug
279
- say "Production branch: #{production_branch}", :debug
280
- say "Staging branch: #{staging_branch}", :debug
294
+ say "Repository: #{repository}", :debug
295
+ say "Production branch: #{production_branch}", :debug
296
+ say "Staging branch: #{staging_branch}", :debug
281
297
 
282
- client = Octokit::Client.new :access_token => obtain_token!
298
+ client = Octokit::Client.new :access_token => obtain_token!
283
299
 
284
- git :remote, 'update', 'origin' unless @no_fetch
300
+ git :remote, 'update', 'origin' unless @no_fetch
285
301
 
286
- ### Fetch merged PRs
302
+ ### Fetch merged PRs
287
303
 
288
- merged_feature_head_sha1s = git(
289
- :log, '--merges', '--pretty=format:%P', "origin/#{production_branch}..origin/#{staging_branch}"
290
- ).map do |line|
291
- main_sha1, feature_sha1 = line.chomp.split /\s+/
292
- feature_sha1
293
- end
304
+ merged_feature_head_sha1s = git(
305
+ :log, '--merges', '--pretty=format:%P', "origin/#{production_branch}..origin/#{staging_branch}"
306
+ ).map do |line|
307
+ main_sha1, feature_sha1 = line.chomp.split /\s+/
308
+ feature_sha1
309
+ end
294
310
 
295
- merged_pull_request_numbers = git('ls-remote', 'origin', 'refs/pull/*/head').map do |line|
296
- sha1, ref = line.chomp.split /\s+/
311
+ merged_pull_request_numbers = git('ls-remote', 'origin', 'refs/pull/*/head').map do |line|
312
+ sha1, ref = line.chomp.split /\s+/
297
313
 
298
- if merged_feature_head_sha1s.include? sha1
299
- if %r<^refs/pull/(\d+)/head$>.match ref
300
- pr_number = $1.to_i
314
+ if merged_feature_head_sha1s.include? sha1
315
+ if %r<^refs/pull/(\d+)/head$>.match ref
316
+ pr_number = $1.to_i
301
317
 
302
- if git('merge-base', sha1, "origin/#{production_branch}").first.chomp == sha1
303
- say "##{pr_number} (#{sha1}) is already merged into #{production_branch}", :debug
318
+ if git('merge-base', sha1, "origin/#{production_branch}").first.chomp == sha1
319
+ say "##{pr_number} (#{sha1}) is already merged into #{production_branch}", :debug
320
+ else
321
+ pr_number
322
+ end
304
323
  else
305
- pr_number
324
+ say "Bad pull request head ref format: #{ref}", :warn
325
+ nil
306
326
  end
307
- else
308
- say "Bad pull request head ref format: #{ref}", :warn
309
- nil
310
327
  end
311
- end
312
- end.compact
328
+ end.compact
313
329
 
314
- if merged_pull_request_numbers.empty?
315
- say 'No pull requests to be released', :error
316
- exit 1
317
- end
330
+ if merged_pull_request_numbers.empty?
331
+ say 'No pull requests to be released', :error
332
+ exit 1
333
+ end
318
334
 
319
- merged_prs = merged_pull_request_numbers.map do |nr|
320
- pr = client.pull_request repository, nr
321
- say "To be released: ##{pr.number} #{pr.title}", :notice
322
- pr
323
- end
335
+ merged_prs = merged_pull_request_numbers.map do |nr|
336
+ pr = client.pull_request repository, nr
337
+ say "To be released: ##{pr.number} #{pr.title}", :notice
338
+ pr
339
+ end
324
340
 
325
- ### Create a release PR
341
+ ### Create a release PR
326
342
 
327
- say 'Searching for existing release pull requests...', :info
328
- found_release_pr = client.pull_requests(repository).find do |pr|
329
- pr.head.ref == staging_branch && pr.base.ref == production_branch
330
- end
331
- create_mode = found_release_pr.nil?
343
+ say 'Searching for existing release pull requests...', :info
344
+ found_release_pr = client.pull_requests(repository).find do |pr|
345
+ pr.head.ref == staging_branch && pr.base.ref == production_branch
346
+ end
347
+ create_mode = found_release_pr.nil?
332
348
 
333
- # Fetch changed files of a release PR
334
- changed_files = pull_request_files(client, found_release_pr)
349
+ # Fetch changed files of a release PR
350
+ changed_files = pull_request_files(client, found_release_pr)
335
351
 
336
- if @dry_run
337
- pr_title, new_body = build_pr_title_and_body found_release_pr, merged_prs, changed_files
338
- pr_body = create_mode ? new_body : merge_pr_body(found_release_pr.body, new_body)
352
+ if @dry_run
353
+ pr_title, new_body = build_pr_title_and_body found_release_pr, merged_prs, changed_files
354
+ pr_body = create_mode ? new_body : merge_pr_body(found_release_pr.body, new_body)
339
355
 
340
- say 'Dry-run. Not updating PR', :info
341
- say pr_title, :notice
342
- say pr_body, :notice
343
- dump_result_as_json( found_release_pr, merged_prs, changed_files ) if @json
344
- exit 0
345
- end
356
+ say 'Dry-run. Not updating PR', :info
357
+ say pr_title, :notice
358
+ say pr_body, :notice
359
+ dump_result_as_json( found_release_pr, merged_prs, changed_files ) if @json
360
+ exit 0
361
+ end
346
362
 
347
- pr_title, pr_body = nil, nil
348
- release_pr = nil
363
+ pr_title, pr_body = nil, nil
364
+ release_pr = nil
349
365
 
350
- if create_mode
351
- created_pr = client.create_pull_request(
352
- repository, production_branch, staging_branch, 'Preparing release pull request...', ''
353
- )
354
- unless created_pr
355
- say 'Failed to create a new pull request', :error
356
- exit 2
366
+ if create_mode
367
+ created_pr = client.create_pull_request(
368
+ repository, production_branch, staging_branch, 'Preparing release pull request...', ''
369
+ )
370
+ unless created_pr
371
+ say 'Failed to create a new pull request', :error
372
+ exit 2
373
+ end
374
+ changed_files = pull_request_files(client, created_pr) # Refetch changed files from created_pr
375
+ pr_title, pr_body = build_pr_title_and_body created_pr, merged_prs, changed_files
376
+ release_pr = created_pr
377
+ else
378
+ pr_title, new_body = build_pr_title_and_body found_release_pr, merged_prs, changed_files
379
+ pr_body = merge_pr_body(found_release_pr.body, new_body)
380
+ release_pr = found_release_pr
357
381
  end
358
- changed_files = pull_request_files(client, created_pr) # Refetch changed files from created_pr
359
- pr_title, pr_body = build_pr_title_and_body created_pr, merged_prs, changed_files
360
- release_pr = created_pr
361
- else
362
- pr_title, new_body = build_pr_title_and_body found_release_pr, merged_prs, changed_files
363
- pr_body = merge_pr_body(found_release_pr.body, new_body)
364
- release_pr = found_release_pr
365
- end
366
382
 
367
- say 'Pull request body:', :debug
368
- say pr_body, :debug
383
+ say 'Pull request body:', :debug
384
+ say pr_body, :debug
369
385
 
370
- updated_pull_request = client.update_pull_request(
371
- repository, release_pr.number, :title => pr_title, :body => pr_body
372
- )
386
+ updated_pull_request = client.update_pull_request(
387
+ repository, release_pr.number, :title => pr_title, :body => pr_body
388
+ )
373
389
 
374
- unless updated_pull_request
375
- say 'Failed to update a pull request', :error
376
- exit 3
377
- end
390
+ unless updated_pull_request
391
+ say 'Failed to update a pull request', :error
392
+ exit 3
393
+ end
378
394
 
379
- labels = ENV.fetch('GIT_PR_RELEASE_LABELS') { git_config('labels') }
380
- if not labels.nil? and not labels.empty?
381
- labels = labels.split(/\s*,\s*/)
382
- labeled_pull_request = client.add_labels_to_an_issue(
383
- repository, release_pr.number, labels
384
- )
395
+ labels = ENV.fetch('GIT_PR_RELEASE_LABELS') { git_config('labels') }
396
+ if not labels.nil? and not labels.empty?
397
+ labels = labels.split(/\s*,\s*/)
398
+ labeled_pull_request = client.add_labels_to_an_issue(
399
+ repository, release_pr.number, labels
400
+ )
385
401
 
386
- unless labeled_pull_request
387
- say 'Failed to add labels to a pull request', :error
388
- exit 4
402
+ unless labeled_pull_request
403
+ say 'Failed to add labels to a pull request', :error
404
+ exit 4
405
+ end
389
406
  end
407
+
408
+ say "#{create_mode ? 'Created' : 'Updated'} pull request: #{updated_pull_request.rels[:html].href}", :notice
409
+ dump_result_as_json( release_pr, merged_prs, changed_files ) if @json
390
410
  end
391
411
 
392
- say "#{create_mode ? 'Created' : 'Updated'} pull request: #{updated_pull_request.rels[:html].href}", :notice
393
- dump_result_as_json( release_pr, merged_prs, changed_files ) if @json
412
+ main if $0 == __FILE__
@@ -4,7 +4,7 @@ $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 = '0.6.0'
7
+ spec.version = '0.7.0'
8
8
  spec.authors = ["motemen"]
9
9
  spec.email = ["motemen@gmail.com"]
10
10
  spec.summary = 'Creates a release pull request'
@@ -21,5 +21,8 @@ Gem::Specification.new do |spec|
21
21
  spec.add_dependency 'colorize'
22
22
  spec.add_dependency 'diff-lcs'
23
23
 
24
+ spec.add_development_dependency 'rspec'
25
+ spec.add_development_dependency 'timecop'
26
+
24
27
  spec.license = 'MIT'
25
28
  end
data/spec/bin_spec.rb ADDED
@@ -0,0 +1,136 @@
1
+ load File.expand_path("../bin/git-pr-release", __dir__)
2
+
3
+ RSpec.describe "git-pr-release" do
4
+ before do
5
+ Timecop.freeze(Time.parse("2019-02-20 22:58:35"))
6
+
7
+ @stubs = Faraday::Adapter::Test::Stubs.new
8
+ @agent = Sawyer::Agent.new "http://foo.com/a/" do |conn|
9
+ conn.builder.handlers.delete(Faraday::Adapter::NetHttp)
10
+ conn.adapter :test, @stubs
11
+ end
12
+ @release_pr = Sawyer::Resource.new(@agent, YAML.load_file(file_fixture("pr_1.yml")))
13
+ @merged_prs = [
14
+ Sawyer::Resource.new(@agent, YAML.load_file(file_fixture("pr_3.yml"))),
15
+ Sawyer::Resource.new(@agent, YAML.load_file(file_fixture("pr_6.yml"))),
16
+ ]
17
+ @changed_files = [
18
+ Sawyer::Resource.new(@agent, YAML.load_file(file_fixture("pr_1_files.yml"))),
19
+ ]
20
+ end
21
+
22
+ describe "#build_pr_title_and_body" do
23
+ context "without any options" do
24
+ it {
25
+ pr_title, new_body = build_pr_title_and_body(@release_pr, @merged_prs, @changed_files)
26
+ expect(pr_title).to eq "Release 2019-02-20 22:58:35 +0900"
27
+ expect(new_body).to eq <<~MARKDOWN
28
+ - [ ] #3 Provides a creating release pull-request object for template @hakobe
29
+ - [ ] #6 Support two factor auth @ninjinkun
30
+ MARKDOWN
31
+ }
32
+ end
33
+
34
+ context "with ENV" do
35
+ before {
36
+ ENV["GIT_PR_RELEASE_TEMPLATE"] = "spec/fixtures/file/template_1.erb"
37
+ }
38
+
39
+ after {
40
+ ENV["GIT_PR_RELEASE_TEMPLATE"] = nil
41
+ }
42
+
43
+ it {
44
+ pr_title, new_body = build_pr_title_and_body(@release_pr, @merged_prs, @changed_files)
45
+ expect(pr_title).to eq "a"
46
+ expect(new_body).to eq <<~MARKDOWN
47
+ b
48
+ MARKDOWN
49
+ }
50
+ end
51
+
52
+ context "with git_config template" do
53
+ before {
54
+ expect(self).to receive(:git_config).with("template") { "spec/fixtures/file/template_2.erb" }
55
+ }
56
+
57
+ it {
58
+ pr_title, new_body = build_pr_title_and_body(@release_pr, @merged_prs, @changed_files)
59
+ expect(pr_title).to eq "c"
60
+ expect(new_body).to eq <<~MARKDOWN
61
+ d
62
+ MARKDOWN
63
+ }
64
+ end
65
+ end
66
+
67
+ describe "#dump_result_as_json" do
68
+ it {
69
+ output = capture(:stdout) { dump_result_as_json(@release_pr, @merged_prs, @changed_files) }
70
+ parsed_output = JSON.parse(output)
71
+
72
+ expect(parsed_output.keys).to eq %w[release_pull_request merged_pull_requests changed_files]
73
+ expect(parsed_output["release_pull_request"]).to eq({ "data" => JSON.parse(@release_pr.to_hash.to_json) })
74
+ expect(parsed_output["merged_pull_requests"]).to eq @merged_prs.map {|e| JSON.parse(PullRequest.new(e).to_hash.to_json) }
75
+ expect(parsed_output["changed_files"]).to eq @changed_files.map {|e| JSON.parse(e.to_hash.to_json) }
76
+ }
77
+ end
78
+
79
+ describe "#merge_pr_body" do
80
+ context "new pr added" do
81
+ it {
82
+ actual = merge_pr_body(<<~OLD_BODY, <<~NEW_BODY)
83
+ - [x] #3 Provides a creating release pull-request object for template @hakobe
84
+ - [ ] #6 Support two factor auth @ninjinkun
85
+ OLD_BODY
86
+ - [ ] #3 Provides a creating release pull-request object for template @hakobe
87
+ - [ ] #4 use user who create PR if there is no assignee @hakobe
88
+ - [ ] #6 Support two factor auth @ninjinkun
89
+ NEW_BODY
90
+
91
+ expect(actual).to eq <<~MARKDOWN.chomp
92
+ - [x] #3 Provides a creating release pull-request object for template @hakobe
93
+ - [ ] #4 use user who create PR if there is no assignee @hakobe
94
+ - [ ] #6 Support two factor auth @ninjinkun
95
+ MARKDOWN
96
+ }
97
+ end
98
+ context "new pr added and keeping task status" do
99
+ it {
100
+ actual = merge_pr_body(<<~OLD_BODY, <<~NEW_BODY)
101
+ - [x] #4 use user who create PR if there is no assignee @hakobe
102
+ - [x] #6 Support two factor auth @ninjinkun
103
+ OLD_BODY
104
+ - [ ] #3 Provides a creating release pull-request object for template @hakobe
105
+ - [ ] #4 use user who create PR if there is no assignee @hakobe
106
+ - [ ] #6 Support two factor auth @ninjinkun
107
+ NEW_BODY
108
+
109
+ expect(actual).to eq <<~MARKDOWN.chomp
110
+ - [ ] #3 Provides a creating release pull-request object for template @hakobe
111
+ - [x] #4 use user who create PR if there is no assignee @hakobe
112
+ - [x] #6 Support two factor auth @ninjinkun
113
+ MARKDOWN
114
+ }
115
+ end
116
+ end
117
+
118
+ describe "#host_and_repository_and_scheme" do
119
+ it {
120
+ expect(self).to receive(:git).with(:config, "remote.origin.url") { ["https://github.com/motemen/git-pr-release\n"] }
121
+ expect(host_and_repository_and_scheme).to eq [nil, "motemen/git-pr-release", "https"]
122
+ }
123
+ it {
124
+ expect(self).to receive(:git).with(:config, "remote.origin.url") { ["ssh://git@github.com/motemen/git-pr-release.git\n"] }
125
+ expect(host_and_repository_and_scheme).to eq [nil, "motemen/git-pr-release", "https"]
126
+ }
127
+ it {
128
+ expect(self).to receive(:git).with(:config, "remote.origin.url") { ["http://ghe.example.com/motemen/git-pr-release\n"] }
129
+ expect(host_and_repository_and_scheme).to eq ["ghe.example.com", "motemen/git-pr-release", "http"]
130
+ }
131
+ it {
132
+ expect(self).to receive(:git).with(:config, "remote.origin.url") { ["ssh://git@ghe.example.com/motemen/git-pr-release.git\n"] }
133
+ expect(host_and_repository_and_scheme).to eq ["ghe.example.com", "motemen/git-pr-release", "https"]
134
+ }
135
+ end
136
+ end