geet 0.27.7 → 0.28.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.
Files changed (44) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/ci.yml +0 -1
  3. data/README.md +8 -17
  4. data/geet.gemspec +1 -1
  5. data/lib/geet/commandline/configuration.rb +1 -1
  6. data/lib/geet/git/repository.rb +36 -70
  7. data/lib/geet/helpers/services_workflow_helper.rb +1 -1
  8. data/lib/geet/services/add_upstream_repo.rb +1 -1
  9. data/lib/geet/services/close_milestones.rb +1 -1
  10. data/lib/geet/services/comment_pr.rb +1 -1
  11. data/lib/geet/services/create_issue.rb +27 -28
  12. data/lib/geet/services/create_label.rb +2 -2
  13. data/lib/geet/services/create_milestone.rb +2 -2
  14. data/lib/geet/services/create_pr.rb +20 -27
  15. data/lib/geet/services/list_issues.rb +3 -3
  16. data/lib/geet/services/list_labels.rb +1 -1
  17. data/lib/geet/services/list_milestones.rb +7 -7
  18. data/lib/geet/services/list_prs.rb +1 -1
  19. data/lib/geet/services/merge_pr.rb +4 -3
  20. data/lib/geet/services/open_pr.rb +1 -1
  21. data/lib/geet/utils/git_client.rb +0 -11
  22. data/lib/geet/version.rb +1 -1
  23. data/spec/integration/create_label_spec.rb +0 -21
  24. data/spec/integration/create_pr_spec.rb +0 -1
  25. data/spec/integration/list_issues_spec.rb +0 -46
  26. data/spec/integration/list_labels_spec.rb +0 -27
  27. data/spec/integration/list_milestones_spec.rb +0 -27
  28. data/spec/integration/merge_pr_spec.rb +1 -31
  29. data/spec/spec_helper.rb +0 -3
  30. metadata +3 -17
  31. data/lib/geet/github/remote_repository.rb +0 -53
  32. data/lib/geet/gitlab/api_interface.rb +0 -212
  33. data/lib/geet/gitlab/issue.rb +0 -62
  34. data/lib/geet/gitlab/label.rb +0 -66
  35. data/lib/geet/gitlab/milestone.rb +0 -78
  36. data/lib/geet/gitlab/pr.rb +0 -114
  37. data/lib/geet/gitlab/user.rb +0 -52
  38. data/lib/geet/services/abstract_create_issue.rb +0 -21
  39. data/spec/vcr_cassettes/gitlab_com/create_label.yml +0 -64
  40. data/spec/vcr_cassettes/gitlab_com/list_issues.yml +0 -84
  41. data/spec/vcr_cassettes/gitlab_com/list_issues_with_assignee.yml +0 -162
  42. data/spec/vcr_cassettes/gitlab_com/list_labels.yml +0 -80
  43. data/spec/vcr_cassettes/gitlab_com/list_milestones.yml +0 -397
  44. data/spec/vcr_cassettes/gitlab_com/merge_pr.yml +0 -144
@@ -23,7 +23,7 @@ module Geet
23
23
  params(
24
24
  assignee: T.nilable(String)
25
25
  )
26
- .returns(T.any(T::Array[Github::AbstractIssue], T::Array[Gitlab::Issue]))
26
+ .returns(T::Array[Github::AbstractIssue])
27
27
  }
28
28
  def execute(assignee: nil)
29
29
  selected_assignee = find_and_select_attributes(assignee) if assignee
@@ -40,14 +40,14 @@ module Geet
40
40
  sig {
41
41
  params(
42
42
  assignee: String
43
- ).returns(T.any(Github::User, Gitlab::User))
43
+ ).returns(Github::User)
44
44
  }
45
45
  def find_and_select_attributes(assignee)
46
46
  selection_manager = Geet::Utils::AttributesSelectionManager.new(@repository, out: @out)
47
47
 
48
48
  selection_manager.add_attribute(:collaborators, "assignee", assignee, SELECTION_SINGLE, name_method: :username)
49
49
 
50
- T.cast(selection_manager.select_attributes[0], T.any(Github::User, Gitlab::User))
50
+ T.cast(selection_manager.select_attributes[0], Github::User)
51
51
  end
52
52
  end
53
53
  end
@@ -13,7 +13,7 @@ module Geet
13
13
  end
14
14
 
15
15
  sig {
16
- returns(T::Array[T.any(Github::Label, Gitlab::Label)])
16
+ returns(T::Array[Github::Label])
17
17
  }
18
18
  def execute
19
19
  labels = @repository.labels
@@ -18,7 +18,7 @@ module Geet
18
18
  end
19
19
 
20
20
  sig {
21
- returns(T::Array[T.any(Github::Milestone, Gitlab::Milestone)])
21
+ returns(T::Array[Github::Milestone])
22
22
  }
23
23
  def execute
24
24
  milestones = find_milestones
@@ -47,7 +47,7 @@ module Geet
47
47
  # are considered formatters, conceptually external to the class.
48
48
  sig {
49
49
  params(
50
- milestone: T.any(Github::Milestone, Gitlab::Milestone)
50
+ milestone: Github::Milestone
51
51
  ).returns(String)
52
52
  }
53
53
  def milestone_description(milestone)
@@ -57,7 +57,7 @@ module Geet
57
57
  end
58
58
 
59
59
  sig {
60
- returns(T::Array[T.any(Github::Milestone, Gitlab::Milestone)])
60
+ returns(T::Array[Github::Milestone])
61
61
  }
62
62
  def find_milestones
63
63
  @out.puts "Finding milestones..."
@@ -67,13 +67,13 @@ module Geet
67
67
 
68
68
  sig {
69
69
  params(
70
- milestones: T::Array[T.any(Github::Milestone, Gitlab::Milestone)]
70
+ milestones: T::Array[Github::Milestone]
71
71
  ).returns(
72
72
  T::Hash[
73
- T.any(Github::Milestone, Gitlab::Milestone),
73
+ Github::Milestone,
74
74
  {
75
- issues: T::Array[T.any(Github::Issue, Gitlab::Issue)],
76
- prs: T::Array[T.any(Github::PR, Gitlab::PR)],
75
+ issues: T::Array[Github::Issue],
76
+ prs: T::Array[Github::PR],
77
77
  }
78
78
  ]
79
79
  )
@@ -13,7 +13,7 @@ module Geet
13
13
  end
14
14
 
15
15
  sig {
16
- returns(T::Array[T.any(Github::PR, Gitlab::PR)])
16
+ returns(T::Array[Github::PR])
17
17
  }
18
18
  def execute
19
19
  prs = @repository.prs
@@ -29,7 +29,7 @@ module Geet
29
29
  delete_branch: T::Boolean,
30
30
  squash: T::Boolean
31
31
  )
32
- .returns(T.any(Github::PR, Gitlab::PR))
32
+ .returns(Github::PR)
33
33
  }
34
34
  def execute(delete_branch: false, squash: false)
35
35
  merge_method = "squash" if squash
@@ -75,12 +75,13 @@ module Geet
75
75
 
76
76
  sig { void }
77
77
  def check_no_missing_upstream_commits
78
- missing_upstream_commits = @git_client.cherry("HEAD", head: :main_branch)
78
+ remote_main_branch = "#{Utils::GitClient::ORIGIN_NAME}/#{@git_client.main_branch}"
79
+ missing_upstream_commits = @git_client.cherry("HEAD", head: remote_main_branch)
79
80
 
80
81
  raise "Found #{missing_upstream_commits.size} missing upstream commits!" if missing_upstream_commits.any?
81
82
  end
82
83
 
83
- sig { params(pr: T.any(Github::PR, Gitlab::PR), merge_method: T.nilable(String)).void }
84
+ sig { params(pr: Github::PR, merge_method: T.nilable(String)).void }
84
85
  def merge_pr(pr, merge_method: nil)
85
86
  @out.puts "Merging PR ##{pr.number}..."
86
87
 
@@ -32,7 +32,7 @@ module Geet
32
32
  params(
33
33
  delete_branch: T::Boolean,
34
34
  _: T.untyped,
35
- ).returns(T.any(Github::PR, Gitlab::PR))
35
+ ).returns(Github::PR)
36
36
  }
37
37
  def execute(delete_branch: false, **_)
38
38
  pr = checked_find_branch_pr
@@ -209,17 +209,6 @@ module Geet
209
209
  T.must(path.split("/")[0])
210
210
  end
211
211
 
212
- sig { returns(String) }
213
- def provider_domain
214
- # We assume that it's not possible to have origin and upstream on different providers.
215
-
216
- domain = T.must(remote()[REMOTE_URL_REGEX, 2])
217
-
218
- raise "Can't identify domain in the provider domain string: #{domain}" if domain !~ /\w+\.\w+/
219
-
220
- domain
221
- end
222
-
223
212
  # Returns the URL of the remote with the given name.
224
213
  # Sanity checks are performed.
225
214
  #
data/lib/geet/version.rb CHANGED
@@ -2,5 +2,5 @@
2
2
  # typed: strict
3
3
 
4
4
  module Geet
5
- VERSION = "0.27.7"
5
+ VERSION = "0.28.0"
6
6
  end
@@ -85,25 +85,4 @@ describe Geet::Services::CreateLabel do
85
85
  end
86
86
  end # context 'with github.com'
87
87
 
88
- context "with gitlab.com" do
89
- it "should create a label" do
90
- allow(git_client).to receive(:remote).with(no_args).and_return("git@gitlab.com:donaldduck/testproject")
91
-
92
- expected_output = <<~STR
93
- Creating label...
94
- Created with color #123456
95
- STR
96
-
97
- actual_output = StringIO.new
98
-
99
- actual_created_label = VCR.use_cassette("gitlab_com/create_label") do
100
- described_class.new(repository, out: actual_output).execute("my_label", color: "123456")
101
- end
102
-
103
- expect(actual_output.string).to eql(expected_output)
104
-
105
- expect(actual_created_label.name).to eql("my_label")
106
- expect(actual_created_label.color).to eql("123456")
107
- end
108
- end
109
88
  end
@@ -264,7 +264,6 @@ describe Geet::Services::CreatePr do
264
264
  allow(git_client).to receive(:push)
265
265
  allow(git_client).to receive(:remote).with(no_args).and_return("git@github.com:donaldduck/testrepo_f")
266
266
 
267
- # Mock the repository and PR without enable_automerge method (simulating GitLab)
268
267
  allow(repository).to receive(:authenticated_user).and_return(
269
268
  double(is_collaborator?: true, has_permission?: true)
270
269
  )
@@ -79,50 +79,4 @@ describe Geet::Services::ListIssues do
79
79
  end
80
80
  end
81
81
 
82
- context "with gitlab.com" do
83
- it "should list the issues" do
84
- allow(git_client).to receive(:remote).with(no_args).and_return("git@gitlab.com:donaldduck/testproject")
85
-
86
- expected_output = <<~STR
87
- 2. I like more pizza (https://gitlab.com/donaldduck/testproject/issues/2)
88
- 1. I like pizza (https://gitlab.com/donaldduck/testproject/issues/1)
89
- STR
90
- expected_issue_numbers = [2, 1]
91
-
92
- actual_output = StringIO.new
93
-
94
- service_result = VCR.use_cassette("gitlab_com/list_issues") do
95
- described_class.new(repository, out: actual_output).execute
96
- end
97
-
98
- actual_issue_numbers = service_result.map(&:number)
99
-
100
- expect(actual_output.string).to eql(expected_output)
101
- expect(actual_issue_numbers).to eql(expected_issue_numbers)
102
- end
103
-
104
- context "with assignee filtering" do
105
- it "should list the issues" do
106
- allow(git_client).to receive(:remote).with(no_args).and_return("git@gitlab.com:donaldduck/testproject")
107
-
108
- expected_output = <<~STR
109
- Finding collaborators...
110
- 3. This is a test issue (https://gitlab.com/donaldduck/testproject/issues/3)
111
- STR
112
- expected_issue_numbers = [3]
113
-
114
- actual_output = StringIO.new
115
-
116
- service_result = VCR.use_cassette("gitlab_com/list_issues_with_assignee") do
117
- described_class.new(repository, out: actual_output).execute(assignee: "donald-fr")
118
- end
119
-
120
- actual_issue_numbers = service_result.map(&:number)
121
-
122
- expect(actual_output.string).to eql(expected_output)
123
- expect(actual_issue_numbers).to eql(expected_issue_numbers)
124
- end
125
- end
126
-
127
- end # with gitlab.com
128
82
  end
@@ -55,31 +55,4 @@ describe Geet::Services::ListLabels do
55
55
  end
56
56
  end
57
57
 
58
- context "with gitlab.com" do
59
- it "should list the labels" do
60
- allow(git_client).to receive(:remote).with(no_args).and_return("git@gitlab.com:donaldduck/testproject")
61
-
62
- expected_output = <<~STR
63
- - bug (#d9534f)
64
- - confirmed (#d9534f)
65
- - critical (#d9534f)
66
- - discussion (#428bca)
67
- - documentation (#f0ad4e)
68
- - enhancement (#5cb85c)
69
- - suggestion (#428bca)
70
- - support (#f0ad4e)
71
- STR
72
- expected_label_names = %w[bug confirmed critical discussion documentation enhancement suggestion support]
73
-
74
- actual_output = StringIO.new
75
- actual_labels = VCR.use_cassette("gitlab.com/list_labels") do
76
- described_class.new(repository, out: actual_output).execute
77
- end
78
-
79
- actual_label_names = actual_labels.map(&:name)
80
-
81
- expect(actual_output.string).to eql(expected_output)
82
- expect(actual_label_names).to eql(expected_label_names)
83
- end
84
- end
85
58
  end
@@ -78,31 +78,4 @@ describe Geet::Services::ListMilestones do
78
78
  end
79
79
  end # context 'with github.com'
80
80
 
81
- context "with gitlab.com" do
82
- it "should list the milestones" do
83
- allow(git_client).to receive(:remote).with(no_args).and_return("git@gitlab.com:donaldduck/testproject")
84
-
85
- expected_output = <<~STR
86
- Finding milestones...
87
- Finding issues and PRs...
88
-
89
- 2. Milestone 2
90
- 1. Milestone 1
91
- 3. This is a test issue (https://gitlab.com/donaldduck/testproject/issues/3)
92
- 1. Merge request 1 (https://gitlab.com/donaldduck/testproject/merge_requests/1)
93
- STR
94
- expected_milestone_numbers = [2, 1]
95
-
96
- actual_output = StringIO.new
97
-
98
- service_result = VCR.use_cassette("gitlab_com/list_milestones") do
99
- described_class.new(repository, out: actual_output).execute
100
- end
101
-
102
- actual_milestone_numbers = service_result.map(&:number)
103
-
104
- expect(actual_output.string).to eql(expected_output)
105
- expect(actual_milestone_numbers).to eql(expected_milestone_numbers)
106
- end
107
- end
108
81
  end
@@ -20,7 +20,7 @@ describe Geet::Services::MergePr do
20
20
  before :each do
21
21
  expect(git_client).to receive(:fetch).twice
22
22
  expect(git_client).to receive(:push)
23
- expect(git_client).to receive(:cherry).with("HEAD", head: :main_branch).and_return([])
23
+ expect(git_client).to receive(:cherry).with("HEAD", head: "origin/#{main_branch}").and_return([])
24
24
  expect(git_client).to receive(:remote_branch_gone?).and_return(true)
25
25
  expect(git_client).to receive(:checkout).with(main_branch)
26
26
  expect(git_client).to receive(:rebase)
@@ -86,34 +86,4 @@ describe Geet::Services::MergePr do
86
86
  end
87
87
  end # context 'with github.com'
88
88
 
89
- context "with gitlab.com" do
90
- let(:repository_name) { "testproject" }
91
-
92
- it "should merge the PR for the current branch" do
93
- allow(git_client).to receive(:current_branch).and_return(branch)
94
- allow(git_client).to receive(:main_branch).and_return(main_branch)
95
- allow(git_client).to receive(:remote).with(no_args).and_return("git@gitlab.com:#{owner}/#{repository_name}")
96
-
97
- expected_pr_number = 4
98
- expected_output = <<~STR
99
- Finding PR with head (#{owner}:#{branch})...
100
- Merging PR ##{expected_pr_number}...
101
- Fetching repository...
102
- Checking out #{main_branch}...
103
- Rebasing...
104
- Deleting local branch mybranch...
105
- STR
106
-
107
- actual_output = StringIO.new
108
-
109
- service_result = VCR.use_cassette("gitlab_com/merge_pr") do
110
- described_class.new(repository, out: actual_output, git_client: git_client).execute
111
- end
112
-
113
- actual_pr_number = service_result.number
114
-
115
- expect(actual_output.string).to eql(expected_output)
116
- expect(actual_pr_number).to eql(expected_pr_number)
117
- end
118
- end # context 'with gitlab.com'
119
89
  end
data/spec/spec_helper.rb CHANGED
@@ -23,9 +23,6 @@ VCR.configure do |config|
23
23
  Base64.strict_encode64("#{user}:#{api_token}")
24
24
  end
25
25
 
26
- config.filter_sensitive_data("<GITLAB_CREDENTIALS>") do
27
- ENV.fetch("GITLAB_API_TOKEN")
28
- end
29
26
  end
30
27
 
31
28
  Geet::Utils::AttributesSelectionManager.serialize_requests = true
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: geet
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.27.7
4
+ version: 0.28.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Saverio Miroddi
8
8
  bindir: bin
9
9
  cert_chain: []
10
- date: 2026-02-08 00:00:00.000000000 Z
10
+ date: 2026-02-21 00:00:00.000000000 Z
11
11
  dependencies:
12
12
  - !ruby/object:Gem::Dependency
13
13
  name: base64
@@ -145,20 +145,12 @@ files:
145
145
  - lib/geet/github/label.rb
146
146
  - lib/geet/github/milestone.rb
147
147
  - lib/geet/github/pr.rb
148
- - lib/geet/github/remote_repository.rb
149
148
  - lib/geet/github/user.rb
150
- - lib/geet/gitlab/api_interface.rb
151
- - lib/geet/gitlab/issue.rb
152
- - lib/geet/gitlab/label.rb
153
- - lib/geet/gitlab/milestone.rb
154
- - lib/geet/gitlab/pr.rb
155
- - lib/geet/gitlab/user.rb
156
149
  - lib/geet/helpers/json_helper.rb
157
150
  - lib/geet/helpers/os_helper.rb
158
151
  - lib/geet/helpers/services_workflow_helper.rb
159
152
  - lib/geet/helpers/summary_helper.rb
160
153
  - lib/geet/resources/templates/edit_summary.md
161
- - lib/geet/services/abstract_create_issue.rb
162
154
  - lib/geet/services/add_upstream_repo.rb
163
155
  - lib/geet/services/close_milestones.rb
164
156
  - lib/geet/services/comment_pr.rb
@@ -283,12 +275,6 @@ files:
283
275
  - spec/vcr_cassettes/github_com/merge_pr.yml
284
276
  - spec/vcr_cassettes/github_com/merge_pr_with_branch_deletion.yml
285
277
  - spec/vcr_cassettes/github_com/open_pr.yml
286
- - spec/vcr_cassettes/gitlab_com/create_label.yml
287
- - spec/vcr_cassettes/gitlab_com/list_issues.yml
288
- - spec/vcr_cassettes/gitlab_com/list_issues_with_assignee.yml
289
- - spec/vcr_cassettes/gitlab_com/list_labels.yml
290
- - spec/vcr_cassettes/gitlab_com/list_milestones.yml
291
- - spec/vcr_cassettes/gitlab_com/merge_pr.yml
292
278
  - spec/vcr_cassettes/list_milestones_upstream.yml
293
279
  - spec/vcr_cassettes/list_prs.yml
294
280
  - spec/vcr_cassettes/list_prs_upstream.yml
@@ -310,7 +296,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
310
296
  - !ruby/object:Gem::Version
311
297
  version: '0'
312
298
  requirements: []
313
- rubygems_version: 4.0.6
299
+ rubygems_version: 3.6.9
314
300
  specification_version: 4
315
301
  summary: Commandline interface for performing SCM host operations, eg. create a PR
316
302
  on GitHub
@@ -1,53 +0,0 @@
1
- # frozen_string_literal: true
2
- # typed: strict
3
-
4
- module Geet
5
- module Github
6
- # A remote repository. Currently only provides the parent path.
7
- #
8
- # It's a difficult choice whether to independently use the repository path, or relying on the one
9
- # stored in the ApiInterface.
10
- # The former design is conceptually cleaner, but it practically (as of the current design) introduces
11
- # duplication. All in all, for simplicity, the latter design is chosen, but is subject to redesign.
12
- #
13
- class RemoteRepository
14
- extend T::Sig
15
-
16
- # Nil if the repository is not a fork.
17
- #
18
- sig { returns(T.nilable(String)) }
19
- attr_reader :parent_path
20
-
21
- sig {
22
- params(
23
- api_interface: Geet::Github::ApiInterface,
24
- parent_path: T.nilable(String)
25
- ).void
26
- }
27
- def initialize(api_interface, parent_path: nil)
28
- @api_interface = api_interface
29
- @parent_path = parent_path
30
- end
31
-
32
- # Get the repository parent path.
33
- #
34
- # https://docs.github.com/en/rest/reference/repos#get-a-repository
35
- #
36
- sig {
37
- params(
38
- api_interface: Geet::Github::ApiInterface
39
- ).returns(Geet::Github::RemoteRepository)
40
- }
41
- def self.find(api_interface)
42
- api_path = "/repos/#{api_interface.repository_path}"
43
-
44
- response = T.cast(api_interface.send_request(api_path), T::Hash[String, T.untyped])
45
-
46
- parent_hash = T.cast(response["parent"], T.nilable(T::Hash[String, T.untyped]))
47
- parent_path = T.cast(parent_hash&.fetch("full_name"), T.nilable(String))
48
-
49
- new(api_interface, parent_path:)
50
- end
51
- end # module RemoteRepository
52
- end # module GitHub
53
- end # module Geet
@@ -1,212 +0,0 @@
1
- # frozen_string_literal: true
2
- # typed: strict
3
-
4
- require "cgi"
5
- require "uri"
6
- require "net/http"
7
- require "json"
8
-
9
- module Geet
10
- module Gitlab
11
- class ApiInterface
12
- extend T::Sig
13
-
14
- API_BASE_URL = "https://gitlab.com/api/v4"
15
-
16
- sig { returns(T.nilable(String)) }
17
- attr_reader :repository_path
18
-
19
- # repo_path: "path/namespace"; required for the current GitLab operations.
20
- # upstream: boolean; required for the current GitLab operations.
21
- #
22
- sig {
23
- params(
24
- api_token: String,
25
- repo_path: String,
26
- upstream: T::Boolean
27
- ).void
28
- }
29
- def initialize(api_token, repo_path:, upstream:)
30
- @api_token = api_token
31
- @path_with_namespace = repo_path
32
- @upstream = upstream
33
- @repository_path = T.let(nil, T.nilable(String))
34
- end
35
-
36
- sig { returns(T::Boolean) }
37
- def upstream?
38
- @upstream
39
- end
40
-
41
- sig {
42
- params(
43
- encoded: T::Boolean
44
- ).returns(String)
45
- }
46
- def path_with_namespace(encoded: false)
47
- encoded ? CGI.escape(@path_with_namespace) : @path_with_namespace
48
- end
49
-
50
- # Send a request.
51
- #
52
- sig {
53
- params(
54
- # Appended to the API URL.
55
- # for root path, prepend a `/`:
56
- # - use `/gists` for `https://api.github.com/gists`
57
- # when owner/project/repos is included, don't prepend `/`:
58
- # - use `issues` for `https://api.github.com/myowner/myproject/repos/issues`
59
- api_path: String,
60
- params: T.nilable(T::Hash[Symbol, T.untyped]),
61
- # If present, will generate a POST request, otherwise, a GET
62
- data: T.nilable(T.any(T::Hash[Symbol, T.untyped], T::Array[T.untyped])),
63
- # Set true for paged Github responses (eg. issues); it will make the method
64
- multipage: T::Boolean,
65
- # Method (:get, :patch, :post, :put and :delete)
66
- # :get and :post are automatically inferred by the presence of :data; the other cases must be specified.
67
- http_method: T.nilable(Symbol)
68
- # Returns the parsed response, or an Array, in case of multipage.
69
- # Where no body is present in the response, nil is returned.
70
- ).returns(T.nilable(T.any(T::Hash[String, T.untyped], T::Array[T::Hash[String, T.untyped]])))
71
- }
72
- def send_request(api_path, params: nil, data: nil, multipage: false, http_method: nil)
73
- address = T.let(api_url(api_path), T.nilable(String))
74
- # filled only on :multipage
75
- parsed_responses = []
76
-
77
- loop do
78
- response = send_http_request(T.must(address), params:, data:, http_method:)
79
-
80
- if response_body = response.body
81
- parsed_response = JSON.parse(response_body)
82
- end
83
-
84
- if error?(response)
85
- formatted_error = decode_and_format_error(T.cast(parsed_response, T::Hash[String, T.untyped]))
86
- raise(formatted_error)
87
- end
88
-
89
- return parsed_response if !multipage
90
-
91
- parsed_responses.concat(T.cast(parsed_response, T::Array[T::Hash[String, T.untyped]]))
92
-
93
- address = link_next_page(response.to_hash)
94
-
95
- return parsed_responses if address.nil?
96
-
97
- # Gitlab's next link address already includes all the params, so we remove
98
- # the passed ones (if there's any).
99
- params = nil
100
- end
101
- end
102
-
103
- private
104
-
105
- sig {
106
- params(
107
- api_path: String
108
- ).returns(String)
109
- }
110
- def api_url(api_path)
111
- "#{API_BASE_URL}/#{api_path}"
112
- end
113
-
114
- sig {
115
- params(
116
- address: String,
117
- params: T.nilable(T::Hash[Symbol, T.untyped]),
118
- data: T.nilable(T.any(T::Hash[Symbol, T.untyped], T::Array[T.untyped])),
119
- http_method: T.nilable(Symbol)
120
- ).returns(Net::HTTPResponse)
121
- }
122
- def send_http_request(address, params: nil, data: nil, http_method: nil)
123
- uri = encode_uri(address, params)
124
- http_class = find_http_class(http_method, data)
125
-
126
- Net::HTTP.start(uri.host, use_ssl: true) do |http|
127
- request = http_class.new(uri)
128
-
129
- request["Private-Token"] = @api_token
130
- request.body = URI.encode_www_form(data) if data
131
-
132
- http.request(request)
133
- end
134
- end
135
-
136
- sig {
137
- params(
138
- address: String,
139
- params: T.nilable(T::Hash[Symbol, T.untyped])
140
- ).returns(URI::Generic)
141
- }
142
- def encode_uri(address, params)
143
- address += "?" + URI.encode_www_form(params) if params
144
-
145
- URI(address)
146
- end
147
-
148
- sig {
149
- params(
150
- response: Net::HTTPResponse
151
- ).returns(T::Boolean)
152
- }
153
- def error?(response)
154
- !response.code.start_with?("2")
155
- end
156
-
157
- sig {
158
- params(
159
- parsed_response: T::Hash[String, T.untyped]
160
- ).returns(String)
161
- }
162
- def decode_and_format_error(parsed_response)
163
- if parsed_response.key?("error")
164
- parsed_response.fetch("error")
165
- elsif parsed_response.key?("message")
166
- parsed_response.fetch("message")
167
- else
168
- "Unrecognized response: #{parsed_response}"
169
- end
170
- end
171
-
172
- sig {
173
- params(
174
- response_headers: T::Hash[String, T.untyped]
175
- ).returns(T.nilable(String))
176
- }
177
- def link_next_page(response_headers)
178
- # An array (or nil) is returned.
179
- link_header = Array(response_headers["link"])
180
-
181
- return nil if link_header.empty?
182
-
183
- link_header[0][/<(\S+)>; rel="next"/, 1]
184
- end
185
-
186
- sig {
187
- params(
188
- http_method: T.nilable(Symbol),
189
- data: T.nilable(Object)
190
- ).returns(T.class_of(Net::HTTPRequest))
191
- }
192
- def find_http_class(http_method, data)
193
- http_method ||= data ? :post : :get
194
-
195
- case http_method
196
- when :get
197
- Net::HTTP::Get
198
- when :delete
199
- Net::HTTP::Delete
200
- when :patch
201
- Net::HTTP::Patch
202
- when :post
203
- Net::HTTP::Post
204
- when :put
205
- Net::HTTP::Put
206
- else
207
- raise "Unsupported HTTP method: #{http_method.inspect}"
208
- end
209
- end
210
- end
211
- end
212
- end