gitlab 4.10.0 → 4.19.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 (49) hide show
  1. checksums.yaml +4 -4
  2. data/LICENSE.txt +1 -1
  3. data/README.md +26 -18
  4. data/lib/gitlab/api.rb +2 -0
  5. data/lib/gitlab/cli.rb +6 -8
  6. data/lib/gitlab/cli_helpers.rb +20 -25
  7. data/lib/gitlab/client/application_settings.rb +172 -0
  8. data/lib/gitlab/client/build_variables.rb +17 -12
  9. data/lib/gitlab/client/commits.rb +42 -5
  10. data/lib/gitlab/client/container_registry.rb +85 -0
  11. data/lib/gitlab/client/epic_issues.rb +23 -0
  12. data/lib/gitlab/client/epics.rb +73 -0
  13. data/lib/gitlab/client/group_badges.rb +88 -0
  14. data/lib/gitlab/client/group_labels.rb +1 -1
  15. data/lib/gitlab/client/groups.rb +153 -2
  16. data/lib/gitlab/client/issue_links.rb +48 -0
  17. data/lib/gitlab/client/issues.rb +1 -1
  18. data/lib/gitlab/client/jobs.rb +91 -8
  19. data/lib/gitlab/client/keys.rb +11 -0
  20. data/lib/gitlab/client/labels.rb +1 -1
  21. data/lib/gitlab/client/lint.rb +19 -0
  22. data/lib/gitlab/client/markdown.rb +23 -0
  23. data/lib/gitlab/client/merge_request_approvals.rb +160 -7
  24. data/lib/gitlab/client/merge_requests.rb +64 -4
  25. data/lib/gitlab/client/notes.rb +28 -1
  26. data/lib/gitlab/client/packages.rb +95 -0
  27. data/lib/gitlab/client/pipeline_schedules.rb +16 -4
  28. data/lib/gitlab/client/pipelines.rb +12 -0
  29. data/lib/gitlab/client/projects.rb +142 -8
  30. data/lib/gitlab/client/remote_mirrors.rb +51 -0
  31. data/lib/gitlab/client/repositories.rb +59 -3
  32. data/lib/gitlab/client/repository_files.rb +16 -0
  33. data/lib/gitlab/client/resource_state_events.rb +57 -0
  34. data/lib/gitlab/client/runners.rb +99 -14
  35. data/lib/gitlab/client/search.rb +5 -1
  36. data/lib/gitlab/client/user_snippets.rb +114 -0
  37. data/lib/gitlab/client/users.rb +142 -11
  38. data/lib/gitlab/client.rb +18 -2
  39. data/lib/gitlab/configuration.rb +1 -1
  40. data/lib/gitlab/error.rb +54 -0
  41. data/lib/gitlab/help.rb +8 -8
  42. data/lib/gitlab/objectified_hash.rb +23 -7
  43. data/lib/gitlab/page_links.rb +1 -1
  44. data/lib/gitlab/paginated_response.rb +23 -20
  45. data/lib/gitlab/request.rb +34 -33
  46. data/lib/gitlab/shell_history.rb +6 -10
  47. data/lib/gitlab/version.rb +1 -1
  48. data/lib/gitlab.rb +14 -6
  49. metadata +24 -47
@@ -0,0 +1,114 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Gitlab::Client
4
+ # Defines methods related to user snippets.
5
+ # @see https://docs.gitlab.com/ce/api/snippets.html
6
+ module UserSnippets
7
+ # Get a list of the snippets of the current user.
8
+ #
9
+ # @example
10
+ # Gitlab.user_snippets
11
+ #
12
+ # @return [Array<Gitlab::ObjectifiedHash>] List of snippets of current user
13
+ def user_snippets
14
+ get('/snippets')
15
+ end
16
+
17
+ # Get a single snippet.
18
+ #
19
+ # @example
20
+ # Gitlab.user_snippet(1)
21
+ #
22
+ # @param [Integer] id ID of snippet to retrieve.
23
+ # @return [Gitlab::ObjectifiedHash] Information about the user snippet
24
+ def user_snippet(id)
25
+ get("/snippets/#{id}")
26
+ end
27
+
28
+ # Get raw contents of a single snippet.
29
+ #
30
+ # @example
31
+ # Gitlab.user_snippet_raw(1)
32
+ #
33
+ # @param [Integer] id ID of snippet to retrieve.
34
+ # @return [String] User snippet text
35
+ def user_snippet_raw(id)
36
+ get("/snippets/#{id}/raw",
37
+ format: nil,
38
+ headers: { Accept: 'text/plain' },
39
+ parser: ::Gitlab::Request::Parser)
40
+ end
41
+
42
+ # Create a new snippet.
43
+ #
44
+ # @example
45
+ # Gitlab.create_user_snippet({ title: 'REST', file_name: 'api.rb', content: 'some code', description: 'Hello World snippet', visibility: 'public'})
46
+ #
47
+ # @param [Hash] options A customizable set of options.
48
+ # @option options [String] :title (required) Title of a snippet.
49
+ # @option options [String] :file_name (required) Name of a snippet file.
50
+ # @option options [String] :content (required) Content of a snippet.
51
+ # @option options [String] :description (optional) Description of a snippet.
52
+ # @option options [String] :visibility (optional) visibility of a snippet.
53
+ # @return [Gitlab::ObjectifiedHash] Information about created snippet.
54
+ def create_user_snippet(options = {})
55
+ post('/snippets', body: options)
56
+ end
57
+
58
+ # Update an existing snippet.
59
+ #
60
+ # @example
61
+ # Gitlab.edit_user_snippet(34, { file_name: 'README.txt' })
62
+ # Gitlab.edit_user_snippet(34, { file_name: 'README.txt', title: 'New title' })
63
+ #
64
+ # @param [Integer] id ID of snippet to update.
65
+ # @param [Hash] options A customizable set of options.
66
+ # @option options [String] :title (optional) Title of a snippet.
67
+ # @option options [String] :file_name (optional) Name of a snippet file.
68
+ # @option options [String] :content (optional) Content of a snippet.
69
+ # @option options [String] :description (optional) Description of a snippet.
70
+ # @option options [String] :visibility (optional) visibility of a snippet.
71
+ # @return [Gitlab::ObjectifiedHash] Information about updated snippet.
72
+ def edit_user_snippet(id, options = {})
73
+ put("/snippets/#{id}", body: options)
74
+ end
75
+
76
+ # Delete an existing snippet.
77
+ #
78
+ # @example
79
+ # Gitlab.delete_user_snippet(14)
80
+ #
81
+ # @param [Integer] id ID of snippet to delete.
82
+ # @return [void] This API call returns an empty response body.
83
+ def delete_user_snippet(id)
84
+ delete("/snippets/#{id}")
85
+ end
86
+
87
+ # List all public snippets.
88
+ #
89
+ # @example
90
+ # Gitlab.public_snippets
91
+ # Gitlab.public_snippets(per_page: 2, page: 1)
92
+ #
93
+ # @param [Hash] options A customizable set of options.
94
+ # @option options [String] :per_page (optional) Number of snippets to return per page.
95
+ # @option options [String] :page (optional) Page to retrieve.
96
+ #
97
+ # @return [Array<Gitlab::ObjectifiedHash>] List of all public snippets
98
+ def public_snippets(options = {})
99
+ get('/snippets/public', query: options)
100
+ end
101
+
102
+ # Get user agent details for a snippet.
103
+ #
104
+ # @example
105
+ # Gitlab.snippet_user_agent_details(1)
106
+ #
107
+ # @param [Integer] id ID of snippet to delete.
108
+ #
109
+ # @return [Array<Gitlab::ObjectifiedHash>] Details of the user agent
110
+ def snippet_user_agent_details(id)
111
+ get("/snippets/#{id}/user_agent_detail")
112
+ end
113
+ end
114
+ end
@@ -37,11 +37,11 @@ class Gitlab::Client
37
37
  # @example
38
38
  # Gitlab.create_user('joe@foo.org', 'secret', 'joe', { name: 'Joe Smith' })
39
39
  # or
40
- # Gitlab.create_user('joe@foo.org', 'secret')
40
+ # Gitlab.create_user('joe@foo.org', 'secret', 'joe')
41
41
  #
42
- # @param [String] email The email of a user.
43
- # @param [String] password The password of a user.
44
- # @param [String] username The username of a user.
42
+ # @param [String] email(required) The email of a user.
43
+ # @param [String] password(required) The password of a user.
44
+ # @param [String] username(required) The username of a user.
45
45
  # @param [Hash] options A customizable set of options.
46
46
  # @option options [String] :name The name of a user. Defaults to email.
47
47
  # @option options [String] :skype The skype of a user.
@@ -51,11 +51,9 @@ class Gitlab::Client
51
51
  # @return [Gitlab::ObjectifiedHash] Information about created user.
52
52
  def create_user(*args)
53
53
  options = args.last.is_a?(Hash) ? args.pop : {}
54
- body = if args[2]
55
- { email: args[0], password: args[1], username: args[2] }
56
- else
57
- { email: args[0], password: args[1], name: args[0] }
58
- end
54
+ raise ArgumentError, 'Missing required parameters' unless args[2]
55
+
56
+ body = { email: args[0], password: args[1], username: args[2], name: args[0] }
59
57
  body.merge!(options)
60
58
  post('/users', body: body)
61
59
  end
@@ -112,6 +110,17 @@ class Gitlab::Client
112
110
  post("/users/#{user_id}/unblock")
113
111
  end
114
112
 
113
+ # Approves the specified user. Available only for admin.
114
+ #
115
+ # @example
116
+ # Gitlab.approve_user(15)
117
+ #
118
+ # @param [Integer] user_id The Id of user
119
+ # @return [Boolean] success or not
120
+ def approve_user(user_id)
121
+ post("/users/#{user_id}/approve")
122
+ end
123
+
115
124
  # Creates a new user session.
116
125
  #
117
126
  # @example
@@ -125,6 +134,20 @@ class Gitlab::Client
125
134
  post('/session', body: { email: email, password: password }, unauthenticated: true)
126
135
  end
127
136
 
137
+ # Gets a list of user activities (for admin access only).
138
+ #
139
+ # @example
140
+ # Gitlab.activities
141
+ #
142
+ # @param [Hash] options A customizable set of options.
143
+ # @option options [Integer] :page The page number.
144
+ # @option options [Integer] :per_page The number of results per page.
145
+ # @option options [String] :from The start date for paginated results.
146
+ # @return [Array<Gitlab::ObjectifiedHash>]
147
+ def activities(options = {})
148
+ get('/user/activities', query: options)
149
+ end
150
+
128
151
  # Gets a list of user's SSH keys.
129
152
  #
130
153
  # @example
@@ -227,10 +250,15 @@ class Gitlab::Client
227
250
  #
228
251
  # @param [String] email Email address
229
252
  # @param [Integer] user_id The ID of a user.
253
+ # @param [Boolean] skip_confirmation Skip confirmation and assume e-mail is verified
230
254
  # @return [Gitlab::ObjectifiedHash]
231
- def add_email(email, user_id = nil)
255
+ def add_email(email, user_id = nil, skip_confirmation = nil)
232
256
  url = user_id.to_i.zero? ? '/user/emails' : "/users/#{user_id}/emails"
233
- post(url, body: { email: email })
257
+ if skip_confirmation.nil?
258
+ post(url, body: { email: email })
259
+ else
260
+ post(url, body: { email: email, skip_confirmation: skip_confirmation })
261
+ end
234
262
  end
235
263
 
236
264
  # Delete email
@@ -262,5 +290,108 @@ class Gitlab::Client
262
290
  options[:search] = search
263
291
  get('/users', query: options)
264
292
  end
293
+
294
+ # Gets user custom_attributes.
295
+ #
296
+ # @example
297
+ # Gitlab.user_custom_attributes(2)
298
+ #
299
+ # @param [Integer] user_id The ID of a user.
300
+ # @return [Gitlab::ObjectifiedHash]
301
+ def user_custom_attributes(user_id)
302
+ get("/users/#{user_id}/custom_attributes")
303
+ end
304
+
305
+ # Gets single user custom_attribute.
306
+ #
307
+ # @example
308
+ # Gitlab.user_custom_attribute(key, 2)
309
+ #
310
+ # @param [String] key The custom_attributes key
311
+ # @param [Integer] user_id The ID of a user.
312
+ # @return [Gitlab::ObjectifiedHash]
313
+ def user_custom_attribute(key, user_id)
314
+ get("/users/#{user_id}/custom_attributes/#{key}")
315
+ end
316
+
317
+ # Creates a new custom_attribute
318
+ #
319
+ # @example
320
+ # Gitlab.add_custom_attribute('some_new_key', 'some_new_value', 2)
321
+ #
322
+ # @param [String] key The custom_attributes key
323
+ # @param [String] value The custom_attributes value
324
+ # @param [Integer] user_id The ID of a user.
325
+ # @return [Gitlab::ObjectifiedHash]
326
+ def add_user_custom_attribute(key, value, user_id)
327
+ url = "/users/#{user_id}/custom_attributes/#{key}"
328
+ put(url, body: { value: value })
329
+ end
330
+
331
+ # Delete custom_attribute
332
+ # Will delete a custom_attribute
333
+ #
334
+ # @example
335
+ # Gitlab.delete_user_custom_attribute('somekey', 2)
336
+ #
337
+ # @param [String] key The custom_attribute key to delete
338
+ # @param [Integer] user_id The ID of a user.
339
+ # @return [Boolean]
340
+ def delete_user_custom_attribute(key, user_id)
341
+ delete("/users/#{user_id}/custom_attributes/#{key}")
342
+ end
343
+
344
+ # Get all impersonation tokens for a user
345
+ #
346
+ # @example
347
+ # Gitlab.user_impersonation_tokens(1)
348
+ #
349
+ # @param [Integer] user_id The ID of the user.
350
+ # @param [String] state Filter impersonation tokens by state {}
351
+ # @return [Array<Gitlab::ObjectifiedHash>]
352
+ def user_impersonation_tokens(user_id)
353
+ get("/users/#{user_id}/impersonation_tokens")
354
+ end
355
+
356
+ # Get impersonation token information
357
+ #
358
+ # @example
359
+ # Gitlab.user_impersonation_token(1, 1)
360
+ #
361
+ # @param [Integer] user_id The ID of the user.
362
+ # @param [Integer] impersonation_token_id ID of the impersonation token.
363
+ # @return [Gitlab::ObjectifiedHash]
364
+ def user_impersonation_token(user_id, impersonation_token_id)
365
+ get("/users/#{user_id}/impersonation_tokens/#{impersonation_token_id}")
366
+ end
367
+
368
+ # Create impersonation token
369
+ #
370
+ # @example
371
+ # Gitlab.create_user_impersonation_token(2, "token", ["api", "read_user"])
372
+ # Gitlab.create_user_impersonation_token(2, "token", ["api", "read_user"], "1970-01-01")
373
+ #
374
+ # @param [Integer] user_id The ID of the user.
375
+ # @param [String] name Name for impersonation token.
376
+ # @param [Array<String>] scopes Array of scopes for the impersonation token
377
+ # @param [String] expires_at Date for impersonation token expiration in ISO format.
378
+ # @return [Gitlab::ObjectifiedHash]
379
+ def create_user_impersonation_token(user_id, name, scopes, expires_at = nil)
380
+ body = { name: name, scopes: scopes }
381
+ body[:expires_at] = expires_at if expires_at
382
+ post("/users/#{user_id}/impersonation_tokens", body: body)
383
+ end
384
+
385
+ # Revoke an impersonation token
386
+ #
387
+ # @example
388
+ # Gitlab.revoke_user_impersonation_token(1, 1)
389
+ #
390
+ # @param [Integer] user_id The ID of the user.
391
+ # @param [Integer] impersonation_token_id ID of the impersonation token.
392
+ # @return [Gitlab::ObjectifiedHash]
393
+ def revoke_user_impersonation_token(user_id, impersonation_token_id)
394
+ delete("/users/#{user_id}/impersonation_tokens/#{impersonation_token_id}")
395
+ end
265
396
  end
266
397
  end
data/lib/gitlab/client.rb CHANGED
@@ -7,6 +7,7 @@ module Gitlab
7
7
 
8
8
  # Please keep in alphabetical order
9
9
  include AccessRequests
10
+ include ApplicationSettings
10
11
  include Avatar
11
12
  include AwardEmojis
12
13
  include Boards
@@ -15,23 +16,31 @@ module Gitlab
15
16
  include BuildVariables
16
17
  include Builds
17
18
  include Commits
19
+ include ContainerRegistry
18
20
  include Deployments
19
21
  include Environments
22
+ include EpicIssues
23
+ include Epics
20
24
  include Events
21
25
  include Features
26
+ include GroupBadges
22
27
  include GroupBoards
23
28
  include GroupLabels
24
29
  include GroupMilestones
25
30
  include Groups
31
+ include IssueLinks
26
32
  include Issues
27
33
  include Jobs
28
34
  include Keys
29
35
  include Labels
36
+ include Lint
37
+ include Markdown
30
38
  include MergeRequestApprovals
31
39
  include MergeRequests
32
40
  include Milestones
33
41
  include Namespaces
34
42
  include Notes
43
+ include Packages
35
44
  include PipelineSchedules
36
45
  include PipelineTriggers
37
46
  include Pipelines
@@ -41,10 +50,12 @@ module Gitlab
41
50
  include ProjectReleases
42
51
  include Projects
43
52
  include ProtectedTags
53
+ include RemoteMirrors
44
54
  include Repositories
45
55
  include RepositoryFiles
46
56
  include RepositorySubmodules
47
57
  include ResourceLabelEvents
58
+ include ResourceStateEvents
48
59
  include Runners
49
60
  include Search
50
61
  include Services
@@ -55,6 +66,7 @@ module Gitlab
55
66
  include Templates
56
67
  include Todos
57
68
  include Users
69
+ include UserSnippets
58
70
  include Versions
59
71
  include Wikis
60
72
 
@@ -67,14 +79,18 @@ module Gitlab
67
79
  inspected
68
80
  end
69
81
 
82
+ # Utility method for URL encoding of a string.
83
+ # Copied from https://ruby-doc.org/stdlib-2.7.0/libdoc/erb/rdoc/ERB/Util.html
84
+ #
85
+ # @return [String]
70
86
  def url_encode(url)
71
- URI.encode(url.to_s, /\W/)
87
+ url.to_s.b.gsub(/[^a-zA-Z0-9_\-.~]/n) { |m| sprintf('%%%02X', m.unpack1('C')) } # rubocop:disable Style/FormatString
72
88
  end
73
89
 
74
90
  private
75
91
 
76
92
  def only_show_last_four_chars(token)
77
- "#{'*' * (token.size - 4)}#{token[-4..-1]}"
93
+ "#{'*' * (token.size - 4)}#{token[-4..]}"
78
94
  end
79
95
  end
80
96
  end
@@ -35,7 +35,7 @@ module Gitlab
35
35
 
36
36
  # Resets all configuration options to the defaults.
37
37
  def reset
38
- self.endpoint = ENV['GITLAB_API_ENDPOINT']
38
+ self.endpoint = ENV['GITLAB_API_ENDPOINT'] || ENV['CI_API_V4_URL']
39
39
  self.private_token = ENV['GITLAB_API_PRIVATE_TOKEN'] || ENV['GITLAB_API_AUTH_TOKEN']
40
40
  self.httparty = get_httparty_config(ENV['GITLAB_API_HTTPARTY_OPTIONS'])
41
41
  self.sudo = nil
data/lib/gitlab/error.rb CHANGED
@@ -34,6 +34,17 @@ module Gitlab
34
34
  @response.parsed_response.message
35
35
  end
36
36
 
37
+ # Additional error context returned by some API endpoints
38
+ #
39
+ # @return [String]
40
+ def error_code
41
+ if @response.respond_to?(:error_code)
42
+ @response.error_code
43
+ else
44
+ ''
45
+ end
46
+ end
47
+
37
48
  private
38
49
 
39
50
  # Human friendly message.
@@ -63,6 +74,14 @@ module Gitlab
63
74
  else
64
75
  @response.parsed_response
65
76
  end
77
+ rescue Gitlab::Error::Parsing
78
+ # Return stringified response when receiving a
79
+ # parsing error to avoid obfuscation of the
80
+ # api error.
81
+ #
82
+ # note: The Gitlab API does not always return valid
83
+ # JSON when there are errors.
84
+ @response.to_s
66
85
  end
67
86
 
68
87
  # Handle error response message in case of nested hashes
@@ -95,6 +114,9 @@ module Gitlab
95
114
  # Raised when API endpoint returns the HTTP status code 405.
96
115
  class MethodNotAllowed < ResponseError; end
97
116
 
117
+ # Raised when API endpoint returns the HTTP status code 406.
118
+ class NotAcceptable < ResponseError; end
119
+
98
120
  # Raised when API endpoint returns the HTTP status code 409.
99
121
  class Conflict < ResponseError; end
100
122
 
@@ -112,5 +134,37 @@ module Gitlab
112
134
 
113
135
  # Raised when API endpoint returns the HTTP status code 503.
114
136
  class ServiceUnavailable < ResponseError; end
137
+
138
+ # Raised when API endpoint returns the HTTP status code 522.
139
+ class ConnectionTimedOut < ResponseError; end
140
+
141
+ # HTTP status codes mapped to error classes.
142
+ STATUS_MAPPINGS = {
143
+ 400 => BadRequest,
144
+ 401 => Unauthorized,
145
+ 403 => Forbidden,
146
+ 404 => NotFound,
147
+ 405 => MethodNotAllowed,
148
+ 406 => NotAcceptable,
149
+ 409 => Conflict,
150
+ 422 => Unprocessable,
151
+ 429 => TooManyRequests,
152
+ 500 => InternalServerError,
153
+ 502 => BadGateway,
154
+ 503 => ServiceUnavailable,
155
+ 522 => ConnectionTimedOut
156
+ }.freeze
157
+
158
+ # Returns error class that should be raised for this response. Returns nil
159
+ # if the response status code is not 4xx or 5xx.
160
+ #
161
+ # @param [HTTParty::Response] response The response object.
162
+ # @return [Class<Error::ResponseError>, nil]
163
+ def self.klass(response)
164
+ error_klass = STATUS_MAPPINGS[response.code]
165
+ return error_klass if error_klass
166
+
167
+ ResponseError if response.server_error? || response.client_error?
168
+ end
115
169
  end
116
170
  end
data/lib/gitlab/help.rb CHANGED
@@ -45,14 +45,13 @@ module Gitlab::Help
45
45
  #
46
46
  # @return [Hash<Array>]
47
47
  def help_map
48
- @help_map ||= begin
48
+ @help_map ||=
49
49
  actions.each_with_object({}) do |action, hsh|
50
50
  key = client.method(action)
51
51
  .owner.to_s.gsub(/Gitlab::(?:Client::)?/, '')
52
52
  hsh[key] ||= []
53
53
  hsh[key] << action.to_s
54
54
  end
55
- end
56
55
  end
57
56
 
58
57
  # Table with available commands.
@@ -74,21 +73,22 @@ module Gitlab::Help
74
73
  # Returns full namespace of a command (e.g. Gitlab::Client::Branches.cmd)
75
74
  def namespace(cmd)
76
75
  method_owners.select { |method| method[:name] == cmd }
77
- .map { |method| method[:owner] + '.' + method[:name] }
76
+ .map { |method| "#{method[:owner]}.#{method[:name]}" }
78
77
  .shift
79
78
  end
80
79
 
81
80
  # Massage output from 'ri'.
82
81
  def change_help_output!(cmd, output_str)
83
- output_str.gsub!(/#{cmd}\((.*?)\)/m, cmd + ' \1')
84
- output_str.gsub!(/\,[\s]*/, ' ')
82
+ output_str = +output_str
83
+ output_str.gsub!(/#{cmd}(\(.*?\))/m, "#{cmd}\\1")
84
+ output_str.gsub!(/,\s*/, ', ')
85
85
 
86
86
  # Ensure @option descriptions are on a single line
87
87
  output_str.gsub!(/\n\[/, " \[")
88
88
  output_str.gsub!(/\s(@)/, "\n@")
89
- output_str.gsub!(/(\])\n(\:)/, '\1 \2')
90
- output_str.gsub!(/(\:.*)(\n)(.*\.)/, '\1 \3')
91
- output_str.gsub!(/\{(.+)\}/, '"{\1}"')
89
+ output_str.gsub!(/(\])\n(:)/, '\\1 \\2')
90
+ output_str.gsub!(/(:.*)(\n)(.*\.)/, '\\1 \\3')
91
+ output_str.gsub!(/\{(.+)\}/, '"{\\1}"')
92
92
  end
93
93
  end
94
94
  end
@@ -7,29 +7,45 @@ module Gitlab
7
7
  def initialize(hash)
8
8
  @hash = hash
9
9
  @data = hash.each_with_object({}) do |(key, value), data|
10
- value = ObjectifiedHash.new(value) if value.is_a? Hash
10
+ value = self.class.new(value) if value.is_a? Hash
11
+ value = value.map { |v| v.is_a?(Hash) ? self.class.new(v) : v } if value.is_a? Array
11
12
  data[key.to_s] = value
12
13
  end
13
14
  end
14
15
 
15
16
  # @return [Hash] The original hash.
16
17
  def to_hash
17
- @hash
18
+ hash
18
19
  end
19
20
  alias to_h to_hash
20
21
 
21
22
  # @return [String] Formatted string with the class name, object id and original hash.
22
23
  def inspect
23
- "#<#{self.class}:#{object_id} {hash: #{@hash.inspect}}"
24
+ "#<#{self.class}:#{object_id} {hash: #{hash.inspect}}"
24
25
  end
25
26
 
26
- # Delegate to ObjectifiedHash.
27
- def method_missing(key)
28
- @data.key?(key.to_s) ? @data[key.to_s] : super
27
+ def [](key)
28
+ data[key]
29
+ end
30
+
31
+ private
32
+
33
+ attr_reader :hash, :data
34
+
35
+ # Respond to messages for which `self.data` has a key
36
+ def method_missing(method_name, *args, &block)
37
+ if data.key?(method_name.to_s)
38
+ data[method_name.to_s]
39
+ elsif data.respond_to?(method_name)
40
+ warn 'WARNING: Please convert ObjectifiedHash object to hash before calling Hash methods on it.'
41
+ data.send(method_name, *args, &block)
42
+ else
43
+ super
44
+ end
29
45
  end
30
46
 
31
47
  def respond_to_missing?(method_name, include_private = false)
32
- @hash.keys.map(&:to_sym).include?(method_name.to_sym) || super
48
+ hash.keys.map(&:to_sym).include?(method_name.to_sym) || super
33
49
  end
34
50
  end
35
51
  end
@@ -7,7 +7,7 @@ module Gitlab
7
7
  class PageLinks
8
8
  HEADER_LINK = 'Link'
9
9
  DELIM_LINKS = ','
10
- LINK_REGEX = /<([^>]+)>; rel=\"([^\"]+)\"/.freeze
10
+ LINK_REGEX = /<([^>]+)>; rel="([^"]+)"/.freeze
11
11
  METAS = %w[last next first prev].freeze
12
12
 
13
13
  attr_accessor(*METAS)
@@ -42,18 +42,20 @@ module Gitlab
42
42
  end
43
43
  end
44
44
 
45
- def auto_paginate
46
- response = block_given? ? nil : []
47
- each_page do |page|
48
- if block_given?
49
- page.each do |item|
50
- yield item
51
- end
52
- else
53
- response += page
54
- end
55
- end
56
- response
45
+ def lazy_paginate
46
+ to_enum(:each_page).lazy.flat_map(&:to_ary)
47
+ end
48
+
49
+ def auto_paginate(&block)
50
+ return lazy_paginate.to_a unless block
51
+
52
+ lazy_paginate.each(&block)
53
+ end
54
+
55
+ def paginate_with_limit(limit, &block)
56
+ return lazy_paginate.take(limit).to_a unless block
57
+
58
+ lazy_paginate.take(limit).each(&block)
57
59
  end
58
60
 
59
61
  def last_page?
@@ -64,8 +66,7 @@ module Gitlab
64
66
  def last_page
65
67
  return nil if @client.nil? || !has_last_page?
66
68
 
67
- path = @links.last.sub(/#{@client.endpoint}/, '')
68
- @client.get(path)
69
+ @client.get(client_relative_path(@links.last))
69
70
  end
70
71
 
71
72
  def first_page?
@@ -76,8 +77,7 @@ module Gitlab
76
77
  def first_page
77
78
  return nil if @client.nil? || !has_first_page?
78
79
 
79
- path = @links.first.sub(/#{@client.endpoint}/, '')
80
- @client.get(path)
80
+ @client.get(client_relative_path(@links.first))
81
81
  end
82
82
 
83
83
  def next_page?
@@ -88,8 +88,7 @@ module Gitlab
88
88
  def next_page
89
89
  return nil if @client.nil? || !has_next_page?
90
90
 
91
- path = @links.next.sub(/#{@client.endpoint}/, '')
92
- @client.get(path)
91
+ @client.get(client_relative_path(@links.next))
93
92
  end
94
93
 
95
94
  def prev_page?
@@ -100,8 +99,12 @@ module Gitlab
100
99
  def prev_page
101
100
  return nil if @client.nil? || !has_prev_page?
102
101
 
103
- path = @links.prev.sub(/#{@client.endpoint}/, '')
104
- @client.get(path)
102
+ @client.get(client_relative_path(@links.prev))
103
+ end
104
+
105
+ def client_relative_path(link)
106
+ client_endpoint_path = URI.parse(@client.endpoint).request_uri # api/v4
107
+ URI.parse(link).request_uri.sub(client_endpoint_path, '')
105
108
  end
106
109
  end
107
110
  end