git-pr-release 2.4.0 → 2.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: 9e376b7fffae49f44aebc877ed835158b11fdcc74cb6ef59f9226b638c9e4d31
4
- data.tar.gz: cb97fbbffc5be83f438384a02902ba05c6c12327d787d9df93d93f6b5c11c61c
3
+ metadata.gz: 10a02af243f4da16852984b6f131d360f2f815f1706ee3f1cc46a568827f86f1
4
+ data.tar.gz: 42df34c42101d8f15fedeeafe9b06e27adedfa90d88033b72b5644748d163dca
5
5
  SHA512:
6
- metadata.gz: bd00f29dcbb0690b1a607ab34a110d51e8b44f25c0f51e834a99a8034062147277fe07d50ee12248494a37e10c1eb2196c3960a3bf7253072be988f3f9cce49b
7
- data.tar.gz: b18a60610527e86b6ef83bb744d197460bd5e90f2ddc7e35ad8d6502ae633834b2f21a66b41f2f5f174d1ff27401c70743500bcfca715d3024631a8affe69637
6
+ metadata.gz: ab9372594976f0953e8dc7520453c8108cb304d5a0570b2d20e6c7c0a5614bb34db41e5ce25a975ae58ea18b5f2d6be04aa933c0a21d630ae0e4348119661d3a
7
+ data.tar.gz: 7b57ebdd687de251b86d9e496e5f2f860ec99b6375c0167154e62dde26624f7932612c18b9da39f990f18b845fa0f96f496e7f338dd4e2d818d2bdb046a9dc68
@@ -0,0 +1,15 @@
1
+ version: 2
2
+ updates:
3
+ - package-ecosystem: "github-actions"
4
+ directory: "/"
5
+ schedule:
6
+ interval: "weekly"
7
+ cooldown:
8
+ default-days: 3
9
+
10
+ - package-ecosystem: "bundler"
11
+ directory: "/"
12
+ schedule:
13
+ interval: "weekly"
14
+ cooldown:
15
+ default-days: 3
@@ -12,13 +12,13 @@ jobs:
12
12
  id-token: write
13
13
 
14
14
  steps:
15
- - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
15
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
16
16
  with:
17
17
  persist-credentials: false
18
18
 
19
- - uses: ruby/setup-ruby@ac793fdd38cc468a4dd57246fa9d0e868aba9085 # v1.270.0
19
+ - uses: ruby/setup-ruby@95ef2b042f9d7a56d8268cba8559e2842e2ad01b # v1.321.0
20
20
  with:
21
21
  bundler-cache: true
22
22
  ruby-version: ruby
23
23
 
24
- - uses: rubygems/release-gem@1c162a739e8b4cb21a676e97b087e8268d8fc40b # v1.1.2
24
+ - uses: rubygems/release-gem@7f9650160c1a4e7989fdc9855807bdbd421d8b6b # v1.4.1
@@ -15,34 +15,25 @@ jobs:
15
15
  fail-fast: false
16
16
  matrix:
17
17
  ruby:
18
+ - "3.0"
18
19
  - "3.1"
19
20
  - "3.2"
20
21
  - "3.3"
21
22
  - "3.4"
23
+ - "4.0"
22
24
  - head
23
25
 
24
26
  steps:
25
- - uses: actions/checkout@v4
26
- - uses: ruby/setup-ruby@v1
27
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
28
+ with:
29
+ persist-credentials: false
30
+ - uses: ruby/setup-ruby@95ef2b042f9d7a56d8268cba8559e2842e2ad01b # v1.321.0
27
31
  with:
28
32
  ruby-version: ${{ matrix.ruby }}
29
33
  bundler-cache: true
30
- - name: Cache vendor/bundle
31
- uses: actions/cache@v4
32
- with:
33
- path: vendor/bundle
34
- key: v1-gem-${{ runner.os }}-${{ matrix.ruby }}-${{ github.sha }}
35
- restore-keys: |
36
- v1-gem-${{ runner.os }}-${{ matrix.ruby }}-
37
- continue-on-error: ${{ matrix.allow_failures == 'true' }}
38
34
  - name: bundle update
39
- run: |
40
- set -xe
41
- bundle config path vendor/bundle
42
- bundle update --jobs $(nproc) --retry 3
35
+ run: bundle update --jobs $(nproc) --retry 3
43
36
  continue-on-error: ${{ matrix.allow_failures == 'true' }}
44
- - name: Run ${{ matrix.test_framework }}
45
- run: |
46
- set -xe
47
- bundle exec rspec
37
+ - name: Run RSpec
38
+ run: bundle exec rspec
48
39
  continue-on-error: ${{ matrix.allow_failures == 'true' }}
data/CHANGELOG.md CHANGED
@@ -1,5 +1,17 @@
1
1
  # git-pr-release
2
2
 
3
+ ## v2.6.0 (2026-09-09)
4
+
5
+ [full changelog](https://github.com/x-motemen/git-pr-release/compare/v2.5.0...v2.6.0)
6
+
7
+ * (#123) Retry GitHub API requests failing with a server error (@exoego)
8
+
9
+ ## v2.5.0 (2025-12-16)
10
+
11
+ [full changelog](https://github.com/x-motemen/git-pr-release/compare/v2.4.0...v2.5.0)
12
+
13
+ * (#116) Make it backward compatible for Ruby 3.0 (@moznion)
14
+
3
15
  ## v2.4.0 (2025-12-16)
4
16
 
5
17
  [full changelog](https://github.com/x-motemen/git-pr-release/compare/v2.3.0...v2.4.0)
@@ -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 = '2.4.0'
7
+ spec.version = '2.6.0'
8
8
  spec.authors = ["motemen"]
9
9
  spec.email = ["motemen@gmail.com"]
10
10
  spec.summary = 'Creates a release pull request'
@@ -17,7 +17,10 @@ Gem::Specification.new do |spec|
17
17
  spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
18
18
  spec.require_paths = ["lib"]
19
19
 
20
+ spec.required_ruby_version = '>= 3.0'
21
+
20
22
  spec.add_dependency 'octokit'
23
+ spec.add_dependency 'faraday-retry'
21
24
  spec.add_dependency 'highline'
22
25
  spec.add_dependency 'colorize'
23
26
  spec.add_dependency 'diff-lcs'
@@ -1,3 +1,4 @@
1
+ require 'faraday/retry'
1
2
  require 'octokit'
2
3
  require 'optparse'
3
4
 
@@ -54,8 +55,23 @@ module Git
54
55
  return 0
55
56
  end
56
57
 
58
+ # POST is left out: retrying it could create a duplicated release pull request.
59
+ RETRY_OPTIONS = {
60
+ max: 5,
61
+ interval: 1,
62
+ interval_randomness: 0.5,
63
+ backoff_factor: 2,
64
+ retry_statuses: [500, 502, 503, 504],
65
+ methods: %i[delete get head options patch put]
66
+ }.freeze
67
+
57
68
  def client
58
- @client ||= Octokit::Client.new :access_token => obtain_token!
69
+ @client ||= Octokit::Client.new(:access_token => obtain_token!).tap do |client|
70
+ # Inside RaiseError so retry_statuses sees the response, not Octokit::ServerError.
71
+ client.middleware = client.middleware.dup.tap do |builder|
72
+ builder.insert_after Octokit::Response::RaiseError, Faraday::Retry::Middleware, RETRY_OPTIONS
73
+ end
74
+ end
59
75
  end
60
76
 
61
77
  def configure
@@ -175,9 +191,13 @@ module Git
175
191
  # This is difficult to read from the current documentation, but that is the current
176
192
  # behavior and GitHub support has responded that this is the spec.
177
193
  shas.each do |sha|
178
- # Longer than 256 characters are not supported in the query.
194
+ # GitHub documents a 256-character limit for search queries, but in practice
195
+ # the API enforces a stricter limit, likely due to URL-encoded length counting.
196
+ # Multiple users have reported errors at 220-240 characters, with 200 being stable.
179
197
  # ref. https://docs.github.com/en/rest/reference/search#limitations-on-query-length
180
- if query.length + 1 + sha.length >= 256
198
+ # ref. https://github.com/x-motemen/git-pr-release/issues/103
199
+ # ref. https://github.com/orgs/community/discussions/186957
200
+ if query.length + 1 + sha.length >= 200
181
201
  pr_nums.concat(search_issue_numbers(query))
182
202
  query = query_base
183
203
  end
@@ -273,7 +293,7 @@ module Git
273
293
  reviewers = merged_prs.map { |pr| PullRequest.new(pr).target_user_login_names }.flatten.compact.uniq
274
294
  unless reviewers.empty?
275
295
  client.request_pull_request_review(
276
- repository, release_pr.number, reviewers:
296
+ repository, release_pr.number, reviewers: reviewers
277
297
  )
278
298
  end
279
299
  end
@@ -313,6 +313,64 @@ RSpec.describe Git::Pr::Release::CLI do
313
313
  end
314
314
  end
315
315
 
316
+ describe "#client" do
317
+ subject { @cli.client }
318
+
319
+ let(:pull_request_url) { "https://api.github.com/repos/motemen/git-pr-release/pulls/1023" }
320
+
321
+ before {
322
+ @cli = configured_cli
323
+
324
+ allow(@cli).to receive(:obtain_token!) { "dummy token" }
325
+ }
326
+
327
+ context "When the GitHub API responds a server error to an idempotent request" do
328
+ before {
329
+ stub_request(:patch, pull_request_url).to_return(
330
+ { status: 502, body: "", headers: {} },
331
+ { status: 200, body: %({"number":1023}), headers: { "Content-Type" => "application/json" } },
332
+ )
333
+ }
334
+
335
+ it "retries the request" do
336
+ expect(subject.update_pull_request("motemen/git-pr-release", 1023, title: "PR Title").number).to eq 1023
337
+ expect(a_request(:patch, pull_request_url)).to have_been_made.twice
338
+ end
339
+ end
340
+
341
+ context "When the GitHub API responds a server error to a request creating a pull request" do
342
+ let(:pull_requests_url) { "https://api.github.com/repos/motemen/git-pr-release/pulls" }
343
+
344
+ before {
345
+ stub_request(:post, pull_requests_url).to_return(status: 502, body: "", headers: {})
346
+ }
347
+
348
+ it "does not retry the request" do
349
+ expect {
350
+ subject.create_pull_request("motemen/git-pr-release", "master", "staging", "PR Title", "")
351
+ }.to raise_error Octokit::BadGateway
352
+ expect(a_request(:post, pull_requests_url)).to have_been_made.once
353
+ end
354
+ end
355
+
356
+ context "When the GitHub API responds a client error" do
357
+ before {
358
+ stub_request(:patch, pull_request_url).to_return(status: 404, body: "", headers: {})
359
+ }
360
+
361
+ it "does not retry the request" do
362
+ expect {
363
+ subject.update_pull_request("motemen/git-pr-release", 1023, title: "PR Title")
364
+ }.to raise_error Octokit::NotFound
365
+ expect(a_request(:patch, pull_request_url)).to have_been_made.once
366
+ end
367
+ end
368
+
369
+ it "does not modify the middleware shared by every Octokit client" do
370
+ expect { subject }.not_to change { Octokit::Default::MIDDLEWARE.handlers }
371
+ end
372
+ end
373
+
316
374
  describe "#fetch_merged_prs" do
317
375
  subject { @cli.fetch_merged_prs }
318
376
 
@@ -707,4 +765,64 @@ RSpec.describe Git::Pr::Release::CLI do
707
765
  expect(@client).to have_received(:auto_paginate=).with(false)
708
766
  }
709
767
  end
768
+
769
+ describe "#fetch_squash_merged_pr_numbers_from_github" do
770
+ subject { @cli.send(:fetch_squash_merged_pr_numbers_from_github) }
771
+
772
+ before {
773
+ @cli = configured_cli
774
+
775
+ allow(@cli).to receive(:git).with(:log, '--pretty=format:%h', "--abbrev=7", "--no-merges", "--first-parent",
776
+ "origin/master..origin/staging") { shas.map { |sha| "#{sha}\n" } }
777
+ allow(@cli).to receive(:search_issue_numbers) { |query| [123] }
778
+ }
779
+
780
+ context "When SHAs fit within query length limit" do
781
+ let(:shas) { ["abc1234", "def5678"] }
782
+
783
+ it "makes a single search request" do
784
+ subject
785
+
786
+ expect(@cli).to have_received(:search_issue_numbers).once
787
+ end
788
+ end
789
+
790
+ context "When SHAs exceed query length limit" do
791
+ # query_base = "repo:motemen/git-pr-release is:pr is:closed" (44 characters)
792
+ # Each SHA is 7 characters + 1 space = 8 characters
793
+ # Available space for SHAs: 200 - 44 = 156 characters
794
+ # To exceed the limit: 156 / 8 = ~19.5, so 20 SHAs will trigger a split
795
+ let(:shas) { (1..25).map { |i| sprintf("%07d", i) } }
796
+
797
+ it "splits into multiple search requests" do
798
+ subject
799
+
800
+ expect(@cli).to have_received(:search_issue_numbers).at_least(2).times
801
+ end
802
+
803
+ it "keeps total query length under 200 characters" do
804
+ call_count = 0
805
+
806
+ allow(@cli).to receive(:search_issue_numbers) do |query|
807
+ call_count += 1
808
+ expect(query.length).to be < 200
809
+ [123]
810
+ end
811
+
812
+ subject
813
+
814
+ expect(call_count).to be > 1
815
+ end
816
+ end
817
+
818
+ context "When query_base is empty after processing all SHAs" do
819
+ let(:shas) { ["abc1234"] }
820
+
821
+ it "makes exactly one search request" do
822
+ subject
823
+
824
+ expect(@cli).to have_received(:search_issue_numbers).once
825
+ end
826
+ end
827
+ end
710
828
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: git-pr-release
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.4.0
4
+ version: 2.6.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - motemen
@@ -23,6 +23,20 @@ dependencies:
23
23
  - - ">="
24
24
  - !ruby/object:Gem::Version
25
25
  version: '0'
26
+ - !ruby/object:Gem::Dependency
27
+ name: faraday-retry
28
+ requirement: !ruby/object:Gem::Requirement
29
+ requirements:
30
+ - - ">="
31
+ - !ruby/object:Gem::Version
32
+ version: '0'
33
+ type: :runtime
34
+ prerelease: false
35
+ version_requirements: !ruby/object:Gem::Requirement
36
+ requirements:
37
+ - - ">="
38
+ - !ruby/object:Gem::Version
39
+ version: '0'
26
40
  - !ruby/object:Gem::Dependency
27
41
  name: highline
28
42
  requirement: !ruby/object:Gem::Requirement
@@ -130,6 +144,7 @@ executables:
130
144
  extensions: []
131
145
  extra_rdoc_files: []
132
146
  files:
147
+ - ".github/dependabot.yml"
133
148
  - ".github/workflows/release.yml"
134
149
  - ".github/workflows/test.yml"
135
150
  - ".gitignore"
@@ -171,14 +186,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
171
186
  requirements:
172
187
  - - ">="
173
188
  - !ruby/object:Gem::Version
174
- version: '0'
189
+ version: '3.0'
175
190
  required_rubygems_version: !ruby/object:Gem::Requirement
176
191
  requirements:
177
192
  - - ">="
178
193
  - !ruby/object:Gem::Version
179
194
  version: '0'
180
195
  requirements: []
181
- rubygems_version: 3.6.9
196
+ rubygems_version: 4.0.16
182
197
  specification_version: 4
183
198
  summary: Creates a release pull request
184
199
  test_files: