geet 0.26.0 → 0.27.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.
@@ -1,32 +1,61 @@
1
1
  # frozen_string_literal: true
2
+ # typed: strict
2
3
 
3
4
  module Geet
4
5
  module Gitlab
5
6
  class Label
6
- attr_reader :name, :color
7
+ extend T::Sig
7
8
 
9
+ sig { returns(String) }
10
+ attr_reader :name
11
+
12
+ sig { returns(String) }
13
+ attr_reader :color
14
+
15
+ sig {
16
+ params(
17
+ name: String,
18
+ color: String
19
+ ).void
20
+ }
8
21
  def initialize(name, color)
9
22
  @name = name
10
23
  @color = color
11
24
  end
12
25
 
13
- # Returns a flat list of names in string form.
14
- def self.list(api_interface, **)
26
+ sig {
27
+ params(
28
+ api_interface: ApiInterface
29
+ ).returns(T::Array[Label])
30
+ }
31
+ def self.list(api_interface)
15
32
  api_path = "projects/#{api_interface.path_with_namespace(encoded: true)}/labels"
16
- response = api_interface.send_request(api_path, multipage: true)
33
+ response = T.cast(
34
+ api_interface.send_request(api_path, multipage: true),
35
+ T::Array[T::Hash[String, T.untyped]]
36
+ )
17
37
 
18
38
  response.map do |label_entry|
19
- name = label_entry.fetch('name')
20
- color = label_entry.fetch('color').sub('#', '') # normalize
39
+ name = T.cast(label_entry.fetch('name'), String)
40
+ color = T.cast(label_entry.fetch('color'), String)
41
+
42
+ color = color.sub('#', '') # normalize
21
43
 
22
44
  new(name, color)
23
45
  end
24
46
  end
25
47
 
26
48
  # See https://docs.gitlab.com/ee/api/labels.html#create-a-new-label
27
- def self.create(name, color, api_interface, **)
49
+ sig {
50
+ params(
51
+ name: String,
52
+ color: String,
53
+ api_interface: ApiInterface
54
+ ).returns(Label)
55
+ }
56
+ def self.create(name, color, api_interface)
28
57
  api_path = "projects/#{api_interface.path_with_namespace(encoded: true)}/labels"
29
- request_data = { name: name, color: "##{color}" }
58
+ request_data = { name:, color: "##{color}" }
30
59
 
31
60
  api_interface.send_request(api_path, data: request_data)
32
61
 
@@ -1,12 +1,30 @@
1
1
  # frozen_string_literal: true
2
+ # typed: strict
2
3
 
3
4
  require 'date'
4
5
 
5
6
  module Geet
6
7
  module Gitlab
7
8
  class Milestone
8
- attr_reader :number, :title, :due_on
9
+ extend T::Sig
9
10
 
11
+ sig { returns(Integer) }
12
+ attr_reader :number
13
+
14
+ sig { returns(String) }
15
+ attr_reader :title
16
+
17
+ sig { returns(T.nilable(Date)) }
18
+ attr_reader :due_on
19
+
20
+ sig {
21
+ params(
22
+ number: Integer,
23
+ title: String,
24
+ due_on: T.nilable(Date),
25
+ api_interface: ApiInterface
26
+ ).void
27
+ }
10
28
  def initialize(number, title, due_on, api_interface)
11
29
  @number = number
12
30
  @title = title
@@ -17,23 +35,40 @@ module Geet
17
35
 
18
36
  # See https://docs.gitlab.com/ee/api/milestones.html#list-project-milestones
19
37
  #
20
- def self.list(api_interface, **)
38
+ sig {
39
+ params(
40
+ api_interface: ApiInterface
41
+ ).returns(T::Array[Milestone])
42
+ }
43
+ def self.list(api_interface)
21
44
  api_path = "projects/#{api_interface.path_with_namespace(encoded: true)}/milestones"
22
45
 
23
- response = api_interface.send_request(api_path, multipage: true)
46
+ response = T.cast(
47
+ api_interface.send_request(api_path, multipage: true),
48
+ T::Array[T::Hash[String, T.untyped]]
49
+ )
24
50
 
25
51
  response.map do |milestone_data|
26
- number = milestone_data.fetch('iid')
27
- title = milestone_data.fetch('title')
28
- due_on = parse_due_date(milestone_data.fetch('due_date'))
52
+ number = T.cast(milestone_data.fetch('iid'), Integer)
53
+ title = T.cast(milestone_data.fetch('title'), String)
54
+ due_on = parse_due_date(
55
+ T.cast(milestone_data.fetch('due_date'), T.nilable(String))
56
+ )
29
57
 
30
58
  new(number, title, due_on, api_interface)
31
59
  end
32
60
  end
33
61
 
34
62
  class << self
63
+ extend T::Sig
64
+
35
65
  private
36
66
 
67
+ sig {
68
+ params(
69
+ raw_due_date: T.nilable(String)
70
+ ).returns(T.nilable(Date))
71
+ }
37
72
  def parse_due_date(raw_due_date)
38
73
  Date.parse(raw_due_date) if raw_due_date
39
74
  end
@@ -1,10 +1,28 @@
1
1
  # frozen_string_literal: true
2
+ # typed: strict
2
3
 
3
4
  module Geet
4
5
  module Gitlab
5
6
  class PR
6
- attr_reader :number, :title, :link
7
+ extend T::Sig
7
8
 
9
+ sig { returns(Integer) }
10
+ attr_reader :number
11
+
12
+ sig { returns(String) }
13
+ attr_reader :title
14
+
15
+ sig { returns(String) }
16
+ attr_reader :link
17
+
18
+ sig {
19
+ params(
20
+ number: Integer,
21
+ api_interface: ApiInterface,
22
+ title: String,
23
+ link: String
24
+ ).void
25
+ }
8
26
  def initialize(number, api_interface, title, link)
9
27
  @number = number
10
28
  @api_interface = api_interface
@@ -12,11 +30,19 @@ module Geet
12
30
  @link = link
13
31
  end
14
32
 
15
- # owner: required only for API compatibility. it's not required; if passed, it's only checked
16
- # against the API path to make sure it's correct.
17
- #
18
33
  # See https://docs.gitlab.com/ee/api/merge_requests.html#list-merge-requests
19
34
  #
35
+ sig {
36
+ params(
37
+ api_interface: ApiInterface,
38
+ milestone: T.nilable(Milestone),
39
+ assignee: T.nilable(User),
40
+ # Required only for API compatibility. it's not required; if passed, it's only checked
41
+ # against the API path to make sure it's correct.
42
+ owner: T.nilable(String),
43
+ head: T.nilable(String)
44
+ ).returns(T::Array[PR])
45
+ }
20
46
  def self.list(api_interface, milestone: nil, assignee: nil, owner: nil, head: nil)
21
47
  api_path = "projects/#{api_interface.path_with_namespace(encoded: true)}/merge_requests"
22
48
 
@@ -27,19 +53,37 @@ module Geet
27
53
  request_params[:milestone] = milestone.title if milestone
28
54
  request_params[:source_branch] = head if head
29
55
 
30
- response = api_interface.send_request(api_path, params: request_params, multipage: true)
56
+ response = T.cast(
57
+ api_interface.send_request(api_path, params: request_params, multipage: true),
58
+ T::Array[T::Hash[String, T.untyped]]
59
+ )
31
60
 
32
61
  response.map do |issue_data, result|
33
- number = issue_data.fetch('iid')
34
- title = issue_data.fetch('title')
35
- link = issue_data.fetch('web_url')
62
+ number = T.cast(issue_data.fetch('iid'), Integer)
63
+ title = T.cast(issue_data.fetch('title'), String)
64
+ link = T.cast(issue_data.fetch('web_url'), String)
36
65
 
37
66
  new(number, api_interface, title, link)
38
67
  end
39
68
  end
40
69
 
70
+ # See https://docs.gitlab.com/ee/api/notes.html#create-new-merge-request-note
71
+ #
72
+ sig { params(comment: String).void }
73
+ def comment(comment)
74
+ api_path = "projects/#{@api_interface.path_with_namespace(encoded: true)}/merge_requests/#{number}/notes"
75
+ request_data = { body: comment }
76
+
77
+ @api_interface.send_request(api_path, data: request_data)
78
+ end
79
+
41
80
  # See https://docs.gitlab.com/ee/api/merge_requests.html#accept-mr
42
81
  #
82
+ sig {
83
+ params(
84
+ merge_method: T.nilable(String)
85
+ ).void
86
+ }
43
87
  def merge(merge_method: nil)
44
88
  raise ArgumentError, "GitLab does not support the merge_method parameter" if merge_method
45
89
 
@@ -49,8 +93,16 @@ module Geet
49
93
  end
50
94
 
51
95
  class << self
96
+ extend T::Sig
97
+
52
98
  private
53
99
 
100
+ sig {
101
+ params(
102
+ api_interface: ApiInterface,
103
+ owner: String
104
+ ).void
105
+ }
54
106
  def check_list_owner!(api_interface, owner)
55
107
  if !api_interface.path_with_namespace.start_with?("#{owner}/")
56
108
  raise "Mismatch owner/API path!: #{owner}<>#{api_interface.path_with_namespace}"
@@ -1,10 +1,24 @@
1
1
  # frozen_string_literal: true
2
+ # typed: strict
2
3
 
3
4
  module Geet
4
5
  module Gitlab
5
6
  class User
6
- attr_reader :id, :username
7
+ extend T::Sig
7
8
 
9
+ sig { returns(Integer) }
10
+ attr_reader :id
11
+
12
+ sig { returns(String) }
13
+ attr_reader :username
14
+
15
+ sig {
16
+ params(
17
+ id: Integer,
18
+ username: String,
19
+ api_interface: ApiInterface
20
+ ).void
21
+ }
8
22
  def initialize(id, username, api_interface)
9
23
  @id = id
10
24
  @username = username
@@ -13,14 +27,22 @@ module Geet
13
27
 
14
28
  # Returns an array of User instances
15
29
  #
16
- def self.list_collaborators(api_interface, **)
30
+ sig {
31
+ params(
32
+ api_interface: ApiInterface
33
+ ).returns(T::Array[User])
34
+ }
35
+ def self.list_collaborators(api_interface)
17
36
  api_path = "projects/#{api_interface.path_with_namespace(encoded: true)}/members"
18
37
 
19
- response = api_interface.send_request(api_path, multipage: true)
38
+ response = T.cast(
39
+ api_interface.send_request(api_path, multipage: true),
40
+ T::Array[T::Hash[String, T.untyped]]
41
+ )
20
42
 
21
43
  response.map do |user_entry|
22
- id = user_entry.fetch('id')
23
- username = user_entry.fetch('username')
44
+ id = T.cast(user_entry.fetch('id'), Integer)
45
+ username = T.cast(user_entry.fetch('username'), String)
24
46
 
25
47
  new(id, username, api_interface)
26
48
  end
@@ -36,7 +36,7 @@ module Geet
36
36
 
37
37
  @out.puts "Finding PR with head (#{owner}:#{head})..."
38
38
 
39
- prs = @repository.prs(owner: owner, head: head)
39
+ prs = @repository.prs(owner:, head:)
40
40
 
41
41
  raise "Expected to find only one PR for the current branch; found: #{prs.size}" if prs.size != 1
42
42
 
@@ -16,7 +16,7 @@ module Geet
16
16
  @git_client = git_client
17
17
  end
18
18
 
19
- def execute(comment, open_browser: false, **)
19
+ def execute(comment, open_browser: false)
20
20
  pr = checked_find_branch_pr
21
21
  pr.comment(comment)
22
22
  open_file_with_default_application(pr.link) if open_browser
@@ -27,7 +27,7 @@ module Geet
27
27
  @out.puts "Creating a #{gist_access} gist..."
28
28
 
29
29
  filename = File.basename(full_filename)
30
- gist = Geet::Github::Gist.create(filename, content, @api_interface, description: description, publik: publik)
30
+ gist = Geet::Github::Gist.create(filename, content, @api_interface, description:, publik:)
31
31
 
32
32
  if open_browser
33
33
  open_file_with_default_application(gist.link)
@@ -1,21 +1,27 @@
1
1
  # frozen_string_literal: true
2
+ # typed: strict
2
3
 
3
4
  module Geet
4
5
  module Services
5
6
  class CreateIssue < AbstractCreateIssue
7
+ extend T::Sig
8
+
6
9
  include Geet::Shared::RepoPermissions
7
10
  include Geet::Shared::Selection
8
11
 
9
- # options:
10
- # :labels
11
- # :milestone: number or description pattern.
12
- # :assignees
13
- # :open_browser
14
- #
12
+ sig {
13
+ params(
14
+ title: String,
15
+ description: String,
16
+ labels: T.nilable(String),
17
+ milestone: T.nilable(String), # Number or description pattern
18
+ assignees: T.nilable(String),
19
+ open_browser: T::Boolean
20
+ ).returns(T.any(Github::Issue, Gitlab::Issue))
21
+ }
15
22
  def execute(
16
23
  title, description,
17
- labels: nil, milestone: nil, assignees: nil, open_browser: false,
18
- **
24
+ labels: nil, milestone: nil, assignees: nil, open_browser: false
19
25
  )
20
26
  # Inefficient (in worst case, triples the pre issue creation waiting time: #is_collaborator?,
21
27
  # #has_permissions?, and the attributes batch), but not trivial to speed up. Not difficult
@@ -48,6 +54,17 @@ module Geet
48
54
 
49
55
  # Internal actions
50
56
 
57
+ sig {
58
+ params(
59
+ labels: T.nilable(String),
60
+ milestone: T.nilable(String),
61
+ assignees: T.nilable(String)
62
+ ).returns([
63
+ T.nilable(T::Array[T.any(Github::Label, Gitlab::Label)]),
64
+ T.nilable(T.any(Github::Milestone, Gitlab::Milestone)),
65
+ T.nilable(T::Array[T.any(Github::User, Gitlab::User)])
66
+ ])
67
+ }
51
68
  def find_and_select_attributes(labels, milestone, assignees)
52
69
  selection_manager = Geet::Utils::AttributesSelectionManager.new(@repository, out: @out)
53
70
 
@@ -58,12 +75,26 @@ module Geet
58
75
  selection_manager.select_attributes
59
76
  end
60
77
 
78
+ sig {
79
+ params(
80
+ title: String,
81
+ description: String
82
+ ).returns(T.any(Github::Issue, Gitlab::Issue))
83
+ }
61
84
  def create_issue(title, description)
62
85
  @out.puts 'Creating the issue...'
63
86
 
64
87
  issue = @repository.create_issue(title, description)
65
88
  end
66
89
 
90
+ sig {
91
+ params(
92
+ issue: T.any(Github::Issue, Gitlab::Issue),
93
+ labels: T.nilable(T::Array[T.any(Github::Label, Gitlab::Label)]),
94
+ milestone: T.nilable(T.any(Github::Milestone, Gitlab::Milestone)),
95
+ assignees: T.nilable(T::Array[T.any(Github::User, Gitlab::User)])
96
+ ).void
97
+ }
67
98
  def edit_issue(issue, labels, milestone, assignees)
68
99
  # labels can be nil (parameter not passed) or empty array (parameter passed, but nothing
69
100
  # selected)
@@ -82,7 +113,15 @@ module Geet
82
113
  assign_users_thread&.join
83
114
  end
84
115
 
116
+ sig {
117
+ params(
118
+ issue: T.any(Github::Issue, Gitlab::Issue),
119
+ selected_labels: T::Array[T.any(Github::Label, Gitlab::Label)]
120
+ ).returns(Thread)
121
+ }
85
122
  def add_labels(issue, selected_labels)
123
+ raise "Functionality unsupported on GitLab!" if issue.is_a?(Gitlab::Issue)
124
+
86
125
  labels_list = selected_labels.map(&:name).join(', ')
87
126
 
88
127
  @out.puts "Adding labels #{labels_list}..."
@@ -92,7 +131,15 @@ module Geet
92
131
  end
93
132
  end
94
133
 
134
+ sig {
135
+ params(
136
+ issue: T.any(Github::Issue, Gitlab::Issue),
137
+ milestone: T.any(Github::Milestone, Gitlab::Milestone)
138
+ ).returns(Thread)
139
+ }
95
140
  def set_milestone(issue, milestone)
141
+ raise "Functionality unsupported on GitLab!" if issue.is_a?(Gitlab::Issue)
142
+
96
143
  @out.puts "Setting milestone #{milestone.title}..."
97
144
 
98
145
  Thread.new do
@@ -100,7 +147,15 @@ module Geet
100
147
  end
101
148
  end
102
149
 
150
+ sig {
151
+ params(
152
+ issue: T.any(Github::Issue, Gitlab::Issue),
153
+ users: T::Array[T.any(Github::User, Gitlab::User)]
154
+ ).returns(Thread)
155
+ }
103
156
  def assign_users(issue, users)
157
+ raise "Functionality unsupported on GitLab!" if issue.is_a?(Gitlab::Issue)
158
+
104
159
  usernames = users.map(&:username)
105
160
 
106
161
  @out.puts "Assigning users #{usernames.join(', ')}..."
@@ -110,7 +165,14 @@ module Geet
110
165
  end
111
166
  end
112
167
 
168
+ sig {
169
+ params(
170
+ issue: T.any(Github::Issue, Gitlab::Issue)
171
+ ).returns(Thread)
172
+ }
113
173
  def assign_authenticated_user(issue)
174
+ raise "Functionality unsupported on GitLab!" if issue.is_a?(Gitlab::Issue)
175
+
114
176
  @out.puts 'Assigning authenticated user...'
115
177
 
116
178
  Thread.new do
@@ -25,7 +25,7 @@ module Geet
25
25
  #
26
26
  def execute(
27
27
  title, description, labels: nil, milestone: nil, reviewers: nil,
28
- base: nil, draft: false, open_browser: false, automerge: false, **
28
+ base: nil, draft: false, open_browser: false, automerge: false
29
29
  )
30
30
  ensure_clean_tree
31
31
 
@@ -45,7 +45,7 @@ module Geet
45
45
 
46
46
  sync_with_remote_branch
47
47
 
48
- pr = create_pr(title, description, base: base, draft: draft)
48
+ pr = create_pr(title, description, base:, draft:)
49
49
 
50
50
  if user_has_write_permissions
51
51
  edit_pr(pr, selected_labels, selected_milestone, selected_reviewers)
@@ -164,6 +164,8 @@ module Geet
164
164
  end
165
165
 
166
166
  def add_labels(pr, selected_labels)
167
+ raise "Functionality unsupported on GitLab!" if pr.is_a?(Gitlab::PR)
168
+
167
169
  labels_list = selected_labels.map(&:name).join(', ')
168
170
 
169
171
  @out.puts "Adding labels #{labels_list}..."
@@ -174,6 +176,8 @@ module Geet
174
176
  end
175
177
 
176
178
  def set_milestone(pr, milestone)
179
+ raise "Functionality unsupported on GitLab!" if pr.is_a?(Gitlab::PR)
180
+
177
181
  @out.puts "Setting milestone #{milestone.title}..."
178
182
 
179
183
  Thread.new do
@@ -182,6 +186,8 @@ module Geet
182
186
  end
183
187
 
184
188
  def request_review(pr, reviewers)
189
+ raise "Functionality unsupported on GitLab!" if pr.is_a?(Gitlab::PR)
190
+
185
191
  reviewer_usernames = reviewers.map(&:username)
186
192
 
187
193
  @out.puts "Requesting review from #{reviewer_usernames.join(', ')}..."
@@ -192,18 +198,21 @@ module Geet
192
198
  end
193
199
 
194
200
  def enable_automerge(pr)
201
+ raise "Functionality unsupported on GitLab!" if pr.is_a?(Gitlab::PR)
202
+
195
203
  if !pr.respond_to?(:enable_automerge)
196
204
  raise "Automerge is not supported for this repository provider"
197
205
  elsif !pr.respond_to?(:node_id) || pr.node_id.nil?
198
206
  raise "Automerge requires node_id from the API (not available in the response)"
199
207
  end
200
208
 
201
- @out.puts "Enabling automerge..."
209
+ @out.print "Enabling automerge... "
202
210
 
203
211
  begin
204
- pr.enable_automerge
212
+ merge_method = pr.enable_automerge
213
+ @out.puts merge_method
205
214
  rescue Geet::Shared::HttpError => e
206
- @out.puts "Warning: Could not enable automerge: #{e.message}"
215
+ @out.puts "", "WARNING: Could not enable automerge: #{e.message}"
207
216
  end
208
217
  end
209
218
  end
@@ -1,16 +1,30 @@
1
1
  # frozen_string_literal: true
2
+ # typed: strict
2
3
 
3
4
  module Geet
4
5
  module Services
5
6
  class ListIssues
7
+ extend T::Sig
8
+
6
9
  include Geet::Shared::Selection
7
10
 
11
+ sig {
12
+ params(
13
+ repository: Git::Repository,
14
+ out: StringIO
15
+ ).void
16
+ }
8
17
  def initialize(repository, out: $stdout)
9
18
  @repository = repository
10
19
  @out = out
11
20
  end
12
21
 
13
- def execute(assignee: nil, **)
22
+ sig {
23
+ params(
24
+ assignee: T.nilable(String)
25
+ ).returns(T::Array[T.any(Github::Issue, Gitlab::Issue)])
26
+ }
27
+ def execute(assignee: nil)
14
28
  selected_assignee = find_and_select_attributes(assignee) if assignee
15
29
 
16
30
  issues = @repository.issues(assignee: selected_assignee)
@@ -22,6 +36,11 @@ module Geet
22
36
 
23
37
  private
24
38
 
39
+ sig {
40
+ params(
41
+ assignee: String
42
+ ).returns(T.any(Github::User, Gitlab::User))
43
+ }
25
44
  def find_and_select_attributes(assignee)
26
45
  selection_manager = Geet::Utils::AttributesSelectionManager.new(@repository, out: @out)
27
46
 
@@ -20,7 +20,7 @@ module Geet
20
20
  @git_client = git_client
21
21
  end
22
22
 
23
- def execute(delete_branch: false, squash: false, **)
23
+ def execute(delete_branch: false, squash: false)
24
24
  merge_method = 'squash' if squash
25
25
 
26
26
  @git_client.fetch
@@ -1,22 +1,37 @@
1
1
  # frozen_string_literal: true
2
+ # typed: strict
2
3
 
3
4
  module Geet
4
5
  module Services
5
6
  # Open in the browser the PR for the current branch.
6
7
  #
7
8
  class OpenPr
9
+ extend T::Sig
10
+
8
11
  include Geet::Helpers::OsHelper
9
12
  include Geet::Helpers::ServicesWorkflowHelper
10
13
 
11
14
  DEFAULT_GIT_CLIENT = Geet::Utils::GitClient.new
12
15
 
16
+ sig {
17
+ params(
18
+ repository: Git::Repository,
19
+ out: StringIO,
20
+ git_client: Utils::GitClient
21
+ ).void
22
+ }
13
23
  def initialize(repository, out: $stdout, git_client: DEFAULT_GIT_CLIENT)
14
24
  @repository = repository
15
25
  @out = out
16
26
  @git_client = git_client
17
27
  end
18
28
 
19
- def execute(delete_branch: false, **)
29
+ sig {
30
+ params(
31
+ delete_branch: T::Boolean
32
+ ).returns(T.any(Github::PR, Gitlab::PR))
33
+ }
34
+ def execute(delete_branch: false)
20
35
  pr = checked_find_branch_pr
21
36
  open_file_with_default_application(pr.link)
22
37
  pr
data/lib/geet/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Geet
4
- VERSION = '0.26.0'
4
+ VERSION = '0.27.0'
5
5
  end
@@ -57,8 +57,7 @@ describe Geet::Services::CreateIssue do
57
57
  actual_output = StringIO.new
58
58
 
59
59
  actual_created_issue = VCR.use_cassette('create_issue_upstream') do
60
- create_options = { out: actual_output }
61
- described_class.new(upstream_repository, out: actual_output).execute('Title', 'Description', **create_options)
60
+ described_class.new(upstream_repository, out: actual_output).execute('Title', 'Description')
62
61
  end
63
62
 
64
63
  expect(actual_output.string).to eql(expected_output)