gitlab 3.5.0 → 3.6.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +7 -1
- data/README.md +17 -0
- data/Rakefile +1 -1
- data/exe/gitlab +1 -1
- data/lib/gitlab.rb +5 -3
- data/lib/gitlab/cli.rb +1 -1
- data/lib/gitlab/cli_helpers.rb +24 -26
- data/lib/gitlab/client.rb +1 -1
- data/lib/gitlab/client/branches.rb +2 -5
- data/lib/gitlab/client/commits.rb +5 -5
- data/lib/gitlab/client/groups.rb +8 -9
- data/lib/gitlab/client/issues.rb +7 -7
- data/lib/gitlab/client/labels.rb +3 -3
- data/lib/gitlab/client/merge_requests.rb +7 -7
- data/lib/gitlab/client/milestones.rb +5 -5
- data/lib/gitlab/client/namespaces.rb +1 -1
- data/lib/gitlab/client/notes.rb +7 -7
- data/lib/gitlab/client/projects.rb +22 -21
- data/lib/gitlab/client/repositories.rb +7 -7
- data/lib/gitlab/client/repository_files.rb +10 -10
- data/lib/gitlab/client/snippets.rb +6 -6
- data/lib/gitlab/client/system_hooks.rb +1 -1
- data/lib/gitlab/client/users.rb +5 -6
- data/lib/gitlab/configuration.rb +1 -1
- data/lib/gitlab/help.rb +19 -15
- data/lib/gitlab/objectified_hash.rb +2 -2
- data/lib/gitlab/page_links.rb +33 -0
- data/lib/gitlab/paginated_response.rb +97 -0
- data/lib/gitlab/request.rb +24 -26
- data/lib/gitlab/shell.rb +4 -5
- data/lib/gitlab/shell_history.rb +3 -5
- data/lib/gitlab/version.rb +1 -1
- data/spec/gitlab/cli_helpers_spec.rb +8 -9
- data/spec/gitlab/cli_spec.rb +0 -2
- data/spec/gitlab/client/branches_spec.rb +2 -2
- data/spec/gitlab/client/commits_spec.rb +16 -16
- data/spec/gitlab/client/groups_spec.rb +11 -14
- data/spec/gitlab/client/issues_spec.rb +9 -9
- data/spec/gitlab/client/labels_spec.rb +6 -6
- data/spec/gitlab/client/merge_requests_spec.rb +23 -23
- data/spec/gitlab/client/milestones_spec.rb +9 -9
- data/spec/gitlab/client/namespaces_spec.rb +2 -2
- data/spec/gitlab/client/notes_spec.rb +10 -10
- data/spec/gitlab/client/projects_spec.rb +29 -28
- data/spec/gitlab/client/repositories_spec.rb +7 -7
- data/spec/gitlab/client/snippets_spec.rb +7 -7
- data/spec/gitlab/client/system_hooks_spec.rb +2 -2
- data/spec/gitlab/client/users_spec.rb +20 -21
- data/spec/gitlab/help_spec.rb +1 -4
- data/spec/gitlab/objectified_hash_spec.rb +2 -2
- data/spec/gitlab/page_links_spec.rb +16 -0
- data/spec/gitlab/paginated_response_spec.rb +60 -0
- data/spec/gitlab/request_spec.rb +12 -13
- data/spec/gitlab/shell_history_spec.rb +1 -1
- data/spec/gitlab/shell_spec.rb +6 -6
- data/spec/spec_helper.rb +12 -12
- metadata +8 -2
data/lib/gitlab/help.rb
CHANGED
@@ -5,7 +5,6 @@ module Gitlab::Help
|
|
5
5
|
extend Gitlab::CLI::Helpers
|
6
6
|
|
7
7
|
class << self
|
8
|
-
|
9
8
|
# Returns the (modified) help from the 'ri' command or returns an error.
|
10
9
|
#
|
11
10
|
# @return [String]
|
@@ -15,7 +14,7 @@ module Gitlab::Help
|
|
15
14
|
if cmd_namespace
|
16
15
|
ri_output = `#{ri_cmd} -T #{cmd_namespace} 2>&1`.chomp
|
17
16
|
|
18
|
-
if
|
17
|
+
if $CHILD_STATUS == 0
|
19
18
|
change_help_output! cmd, ri_output
|
20
19
|
yield ri_output if block_given?
|
21
20
|
|
@@ -34,7 +33,7 @@ module Gitlab::Help
|
|
34
33
|
def ri_cmd
|
35
34
|
which_ri = `which ri`.chomp
|
36
35
|
if which_ri.empty?
|
37
|
-
|
36
|
+
fail "'ri' tool not found in $PATH. Please install it to use the help."
|
38
37
|
end
|
39
38
|
|
40
39
|
which_ri
|
@@ -47,19 +46,19 @@ module Gitlab::Help
|
|
47
46
|
# @return [Hash<Array>]
|
48
47
|
def help_map
|
49
48
|
@help_map ||= begin
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
49
|
+
actions.each_with_object({}) do |action, hsh|
|
50
|
+
key = client.method(action).
|
51
|
+
owner.to_s.gsub(/Gitlab::(?:Client::)?/, '')
|
52
|
+
hsh[key] ||= []
|
53
|
+
hsh[key] << action.to_s
|
54
|
+
end
|
56
55
|
end
|
57
56
|
end
|
58
57
|
|
59
58
|
# Table with available commands.
|
60
59
|
#
|
61
60
|
# @return [Terminal::Table]
|
62
|
-
def actions_table(topic
|
61
|
+
def actions_table(topic=nil)
|
63
62
|
rows = topic ? help_map[topic] : help_map.keys
|
64
63
|
table do |t|
|
65
64
|
t.title = topic || "Help Topics"
|
@@ -75,16 +74,21 @@ module Gitlab::Help
|
|
75
74
|
# Returns full namespace of a command (e.g. Gitlab::Client::Branches.cmd)
|
76
75
|
def namespace(cmd)
|
77
76
|
method_owners.select { |method| method[:name] === cmd }.
|
78
|
-
|
79
|
-
|
77
|
+
map { |method| method[:owner] + '.' + method[:name] }.
|
78
|
+
shift
|
80
79
|
end
|
81
80
|
|
82
81
|
# Massage output from 'ri'.
|
83
82
|
def change_help_output!(cmd, output_str)
|
84
|
-
output_str.gsub!(/#{cmd}\((.*?)\)/m, cmd+' \1')
|
83
|
+
output_str.gsub!(/#{cmd}\((.*?)\)/m, cmd + ' \1')
|
85
84
|
output_str.gsub!(/\,[\s]*/, ' ')
|
85
|
+
|
86
|
+
# Ensure @option descriptions are on a single line
|
87
|
+
output_str.gsub!(/\n\[/, " \[")
|
88
|
+
output_str.gsub!(/\s(@)/, "\n@")
|
89
|
+
output_str.gsub!(/(\])\n(\:)/, '\1 \2')
|
90
|
+
output_str.gsub!(/(\:.*)(\n)(.*\.)/, '\1 \3')
|
91
|
+
|
86
92
|
end
|
87
|
-
|
88
93
|
end # class << self
|
89
94
|
end
|
90
|
-
|
@@ -4,7 +4,7 @@ module Gitlab
|
|
4
4
|
# Creates a new ObjectifiedHash object.
|
5
5
|
def initialize(hash)
|
6
6
|
@hash = hash
|
7
|
-
@data = hash.inject({}) do |data, (key,value)|
|
7
|
+
@data = hash.inject({}) do |data, (key, value)|
|
8
8
|
value = ObjectifiedHash.new(value) if value.is_a? Hash
|
9
9
|
data[key.to_s] = value
|
10
10
|
data
|
@@ -27,7 +27,7 @@ module Gitlab
|
|
27
27
|
@data.key?(key.to_s) ? @data[key.to_s] : nil
|
28
28
|
end
|
29
29
|
|
30
|
-
def respond_to?(method_name, include_private
|
30
|
+
def respond_to?(method_name, include_private=false)
|
31
31
|
@hash.keys.map(&:to_sym).include?(method_name.to_sym) || super
|
32
32
|
end
|
33
33
|
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
module Gitlab
|
2
|
+
# Parses link header.
|
3
|
+
#
|
4
|
+
# @private
|
5
|
+
class PageLinks
|
6
|
+
HEADER_LINK = 'Link'.freeze
|
7
|
+
DELIM_LINKS = ','.freeze
|
8
|
+
LINK_REGEX = /<([^>]+)>; rel=\"([^\"]+)\"/
|
9
|
+
METAS = %w(last next first prev)
|
10
|
+
|
11
|
+
attr_accessor(*METAS)
|
12
|
+
|
13
|
+
def initialize(headers)
|
14
|
+
link_header = headers[HEADER_LINK]
|
15
|
+
|
16
|
+
if link_header && link_header =~ /(next|first|last|prev)/
|
17
|
+
extract_links(link_header)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
private
|
22
|
+
|
23
|
+
def extract_links(header)
|
24
|
+
header.split(DELIM_LINKS).each do |link|
|
25
|
+
LINK_REGEX.match(link.strip) do |match|
|
26
|
+
url, meta = match[1], match[2]
|
27
|
+
next if !url || !meta || METAS.index(meta).nil?
|
28
|
+
self.send("#{meta}=", url)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,97 @@
|
|
1
|
+
module Gitlab
|
2
|
+
# Wrapper class of paginated response.
|
3
|
+
class PaginatedResponse
|
4
|
+
attr_accessor :client
|
5
|
+
|
6
|
+
def initialize(array)
|
7
|
+
@array = array
|
8
|
+
end
|
9
|
+
|
10
|
+
def ==(other)
|
11
|
+
@array == other
|
12
|
+
end
|
13
|
+
|
14
|
+
def inspect
|
15
|
+
@array.inspect
|
16
|
+
end
|
17
|
+
|
18
|
+
def method_missing(name, *args, &block)
|
19
|
+
if @array.respond_to?(name)
|
20
|
+
@array.send(name, *args, &block)
|
21
|
+
else
|
22
|
+
super
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def respond_to?(method, include_all=false)
|
27
|
+
super || @array.respond_to?(method, include_all)
|
28
|
+
end
|
29
|
+
|
30
|
+
def parse_headers!(headers)
|
31
|
+
@links = PageLinks.new headers
|
32
|
+
end
|
33
|
+
|
34
|
+
def each_page
|
35
|
+
current = self
|
36
|
+
yield current
|
37
|
+
while current.has_next_page?
|
38
|
+
current = current.next_page
|
39
|
+
yield current
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
def auto_paginate
|
44
|
+
response = block_given? ? nil : []
|
45
|
+
each_page do |page|
|
46
|
+
if block_given?
|
47
|
+
page.each do |item|
|
48
|
+
yield item
|
49
|
+
end
|
50
|
+
else
|
51
|
+
response += page
|
52
|
+
end
|
53
|
+
end
|
54
|
+
response
|
55
|
+
end
|
56
|
+
|
57
|
+
def has_last_page?
|
58
|
+
!(@links.nil? || @links.last.nil?)
|
59
|
+
end
|
60
|
+
|
61
|
+
def last_page
|
62
|
+
return nil if @client.nil? || !has_last_page?
|
63
|
+
path = @links.last.sub(/#{@client.endpoint}/, '')
|
64
|
+
@client.get(path)
|
65
|
+
end
|
66
|
+
|
67
|
+
def has_first_page?
|
68
|
+
!(@links.nil? || @links.first.nil?)
|
69
|
+
end
|
70
|
+
|
71
|
+
def first_page
|
72
|
+
return nil if @client.nil? || !has_first_page?
|
73
|
+
path = @links.first.sub(/#{@client.endpoint}/, '')
|
74
|
+
@client.get(path)
|
75
|
+
end
|
76
|
+
|
77
|
+
def has_next_page?
|
78
|
+
!(@links.nil? || @links.next.nil?)
|
79
|
+
end
|
80
|
+
|
81
|
+
def next_page
|
82
|
+
return nil if @client.nil? || !has_next_page?
|
83
|
+
path = @links.next.sub(/#{@client.endpoint}/, '')
|
84
|
+
@client.get(path)
|
85
|
+
end
|
86
|
+
|
87
|
+
def has_prev_page?
|
88
|
+
!(@links.nil? || @links.prev.nil?)
|
89
|
+
end
|
90
|
+
|
91
|
+
def prev_page
|
92
|
+
return nil if @client.nil? || !has_prev_page?
|
93
|
+
path = @links.prev.sub(/#{@client.endpoint}/, '')
|
94
|
+
@client.get(path)
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
data/lib/gitlab/request.rb
CHANGED
@@ -7,7 +7,7 @@ module Gitlab
|
|
7
7
|
include HTTParty
|
8
8
|
format :json
|
9
9
|
headers 'Accept' => 'application/json'
|
10
|
-
parser
|
10
|
+
parser proc { |body, _| parse(body) }
|
11
11
|
|
12
12
|
attr_accessor :private_token, :endpoint
|
13
13
|
|
@@ -18,7 +18,7 @@ module Gitlab
|
|
18
18
|
if body.is_a? Hash
|
19
19
|
ObjectifiedHash.new body
|
20
20
|
elsif body.is_a? Array
|
21
|
-
body.collect! { |e| ObjectifiedHash.new(e) }
|
21
|
+
PaginatedResponse.new(body.collect! { |e| ObjectifiedHash.new(e) })
|
22
22
|
elsif body
|
23
23
|
true
|
24
24
|
elsif !body
|
@@ -32,11 +32,9 @@ module Gitlab
|
|
32
32
|
|
33
33
|
# Decodes a JSON response into Ruby object.
|
34
34
|
def self.decode(response)
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
raise Error::Parsing.new "The response is not a valid JSON"
|
39
|
-
end
|
35
|
+
JSON.load response
|
36
|
+
rescue JSON::ParserError
|
37
|
+
raise Error::Parsing.new "The response is not a valid JSON"
|
40
38
|
end
|
41
39
|
|
42
40
|
def get(path, options={})
|
@@ -67,26 +65,29 @@ module Gitlab
|
|
67
65
|
# Returns parsed response for successful requests.
|
68
66
|
def validate(response)
|
69
67
|
case response.code
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
68
|
+
when 400 then fail Error::BadRequest.new error_message(response)
|
69
|
+
when 401 then fail Error::Unauthorized.new error_message(response)
|
70
|
+
when 403 then fail Error::Forbidden.new error_message(response)
|
71
|
+
when 404 then fail Error::NotFound.new error_message(response)
|
72
|
+
when 405 then fail Error::MethodNotAllowed.new error_message(response)
|
73
|
+
when 409 then fail Error::Conflict.new error_message(response)
|
74
|
+
when 422 then fail Error::Unprocessable.new error_message(response)
|
75
|
+
when 500 then fail Error::InternalServerError.new error_message(response)
|
76
|
+
when 502 then fail Error::BadGateway.new error_message(response)
|
77
|
+
when 503 then fail Error::ServiceUnavailable.new error_message(response)
|
80
78
|
end
|
81
79
|
|
82
|
-
response.parsed_response
|
80
|
+
parsed = response.parsed_response
|
81
|
+
parsed.client = self if parsed.respond_to?(:client=)
|
82
|
+
parsed.parse_headers!(response.headers) if parsed.respond_to?(:parse_headers!)
|
83
|
+
parsed
|
83
84
|
end
|
84
85
|
|
85
86
|
# Sets a base_uri and default_params for requests.
|
86
87
|
# @raise [Error::MissingCredentials] if endpoint not set.
|
87
88
|
def set_request_defaults(sudo=nil)
|
89
|
+
self.class.default_params sudo: sudo
|
88
90
|
raise Error::MissingCredentials.new("Please set an endpoint to API") unless @endpoint
|
89
|
-
self.class.default_params :sudo => sudo
|
90
91
|
self.class.default_params.delete(:sudo) if sudo.nil?
|
91
92
|
end
|
92
93
|
|
@@ -98,9 +99,9 @@ module Gitlab
|
|
98
99
|
unless path == '/session'
|
99
100
|
raise Error::MissingCredentials.new("Please provide a private_token or auth_token for user") unless @private_token
|
100
101
|
if @private_token.length <= 20
|
101
|
-
options[:headers] = {'PRIVATE-TOKEN' => @private_token}
|
102
|
+
options[:headers] = { 'PRIVATE-TOKEN' => @private_token }
|
102
103
|
else
|
103
|
-
options[:headers] = {'Authorization' => "Bearer #{@private_token}"}
|
104
|
+
options[:headers] = { 'Authorization' => "Bearer #{@private_token}" }
|
104
105
|
end
|
105
106
|
end
|
106
107
|
end
|
@@ -108,9 +109,7 @@ module Gitlab
|
|
108
109
|
# Set HTTParty configuration
|
109
110
|
# @see https://github.com/jnunemaker/httparty
|
110
111
|
def set_httparty_config(options)
|
111
|
-
if
|
112
|
-
options.merge!(self.httparty)
|
113
|
-
end
|
112
|
+
options.merge!(httparty) if httparty
|
114
113
|
end
|
115
114
|
|
116
115
|
def error_message(response)
|
@@ -127,7 +126,7 @@ module Gitlab
|
|
127
126
|
case message
|
128
127
|
when Gitlab::ObjectifiedHash
|
129
128
|
message.to_h.sort.map do |key, val|
|
130
|
-
"'#{key}' #{(val.is_a?(Hash) ? val.sort.map { |k,v| "(#{k}: #{v.join(' ')})"} : val).join(' ')}"
|
129
|
+
"'#{key}' #{(val.is_a?(Hash) ? val.sort.map { |k, v| "(#{k}: #{v.join(' ')})" } : val).join(' ')}"
|
131
130
|
end.join(', ')
|
132
131
|
when Array
|
133
132
|
message.join(' ')
|
@@ -135,6 +134,5 @@ module Gitlab
|
|
135
134
|
message
|
136
135
|
end
|
137
136
|
end
|
138
|
-
|
139
137
|
end
|
140
138
|
end
|
data/lib/gitlab/shell.rb
CHANGED
@@ -62,13 +62,13 @@ class Gitlab::Shell
|
|
62
62
|
end
|
63
63
|
|
64
64
|
# Execute a given command with arguements
|
65
|
-
def execute(cmd
|
65
|
+
def execute(cmd=command, args=arguments)
|
66
66
|
if actions.include?(cmd.to_sym)
|
67
67
|
confirm_command(cmd)
|
68
68
|
gitlab_helper(cmd, args)
|
69
69
|
else
|
70
|
-
|
71
|
-
|
70
|
+
fail "Unknown command: #{cmd}. " \
|
71
|
+
"See the 'help' for a list of valid commands."
|
72
72
|
end
|
73
73
|
end
|
74
74
|
|
@@ -80,6 +80,5 @@ class Gitlab::Shell
|
|
80
80
|
def history
|
81
81
|
@history ||= History.new
|
82
82
|
end
|
83
|
-
|
84
|
-
end # class << self
|
83
|
+
end # class << self
|
85
84
|
end
|
data/lib/gitlab/shell_history.rb
CHANGED
@@ -3,7 +3,7 @@ class Gitlab::Shell
|
|
3
3
|
DEFAULT_HISTFILESIZE = 200
|
4
4
|
DEFAULT_FILE_PATH = File.join(Dir.home, '.gitlab_shell_history')
|
5
5
|
|
6
|
-
def initialize(options
|
6
|
+
def initialize(options={})
|
7
7
|
@file_path = options[:file_path] || DEFAULT_FILE_PATH
|
8
8
|
Readline::HISTORY.clear
|
9
9
|
end
|
@@ -47,15 +47,13 @@ class Gitlab::Shell
|
|
47
47
|
def read_from_file
|
48
48
|
path = history_file_path
|
49
49
|
|
50
|
-
if File.exist?(path)
|
51
|
-
File.foreach(path) { |line| yield(line) }
|
52
|
-
end
|
50
|
+
File.foreach(path) { |line| yield(line) } if File.exist?(path)
|
53
51
|
rescue => error
|
54
52
|
warn "History file not loaded: #{error.message}"
|
55
53
|
end
|
56
54
|
|
57
55
|
def max_lines
|
58
|
-
(ENV['GITLAB_HISTFILESIZE']|| DEFAULT_HISTFILESIZE).to_i
|
56
|
+
(ENV['GITLAB_HISTFILESIZE'] || DEFAULT_HISTFILESIZE).to_i
|
59
57
|
end
|
60
58
|
end
|
61
59
|
end
|
data/lib/gitlab/version.rb
CHANGED
@@ -7,8 +7,8 @@ describe Gitlab::CLI::Helpers do
|
|
7
7
|
end
|
8
8
|
it "should return Array of Hashes containing method names and owners" do
|
9
9
|
expect(@methods).to be_a Array
|
10
|
-
expect(@methods.all? { |m| m.is_a? Hash}
|
11
|
-
expect(@methods.all? { |m| m.keys.sort === [:name, :owner]}
|
10
|
+
expect(@methods.all? { |m| m.is_a? Hash }).to be true
|
11
|
+
expect(@methods.all? { |m| m.keys.sort === [:name, :owner] }).to be true
|
12
12
|
end
|
13
13
|
end
|
14
14
|
|
@@ -24,15 +24,15 @@ describe Gitlab::CLI::Helpers do
|
|
24
24
|
describe ".symbolize_keys" do
|
25
25
|
context "when input is a Hash" do
|
26
26
|
it "should return a Hash with symbols for keys" do
|
27
|
-
hash = {'key1' => 'val1', 'key2' => 'val2'}
|
27
|
+
hash = { 'key1' => 'val1', 'key2' => 'val2' }
|
28
28
|
symbolized_hash = Gitlab::CLI::Helpers.symbolize_keys(hash)
|
29
|
-
expect(symbolized_hash).to eq(
|
29
|
+
expect(symbolized_hash).to eq(key1: 'val1', key2: 'val2')
|
30
30
|
end
|
31
31
|
end
|
32
32
|
context "when input is NOT a Hash" do
|
33
33
|
it "should return input untouched" do
|
34
34
|
array = [1, 2, 3]
|
35
|
-
new_array = Gitlab::CLI::Helpers.symbolize_keys(array)
|
35
|
+
new_array = Gitlab::CLI::Helpers.symbolize_keys(array)
|
36
36
|
expect(new_array).to eq([1, 2, 3])
|
37
37
|
end
|
38
38
|
end
|
@@ -41,18 +41,17 @@ describe Gitlab::CLI::Helpers do
|
|
41
41
|
describe ".yaml_load" do
|
42
42
|
context "when argument is a YAML string" do
|
43
43
|
it "should return Ruby objects" do
|
44
|
-
argument = "{foo: bar, sna: fu}"
|
44
|
+
argument = "{foo: bar, sna: fu}"
|
45
45
|
output = Gitlab::CLI::Helpers.yaml_load argument
|
46
|
-
expect(output).to eq(
|
46
|
+
expect(output).to eq('foo' => 'bar', 'sna' => 'fu')
|
47
47
|
end
|
48
48
|
end
|
49
49
|
|
50
50
|
context "when input is NOT valid YAML" do
|
51
51
|
it "should raise" do
|
52
52
|
ruby_array = [1, 2, 3, 4]
|
53
|
-
expect { Gitlab::CLI::Helpers.yaml_load ruby_array}.to raise_error TypeError
|
53
|
+
expect { Gitlab::CLI::Helpers.yaml_load ruby_array }.to raise_error TypeError
|
54
54
|
end
|
55
55
|
end
|
56
56
|
end
|
57
|
-
|
58
57
|
end
|