git-pr-release 1.1.0 → 1.6.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: 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