gitlab 3.3.0 → 3.4.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/.travis.yml +2 -1
  3. data/CHANGELOG.md +157 -0
  4. data/LICENSE.txt +1 -1
  5. data/README.md +16 -10
  6. data/lib/gitlab.rb +1 -1
  7. data/lib/gitlab/api.rb +5 -3
  8. data/lib/gitlab/cli.rb +21 -3
  9. data/lib/gitlab/cli_helpers.rb +46 -79
  10. data/lib/gitlab/client.rb +2 -1
  11. data/lib/gitlab/client/branches.rb +16 -1
  12. data/lib/gitlab/client/groups.rb +1 -0
  13. data/lib/gitlab/client/issues.rb +1 -0
  14. data/lib/gitlab/client/labels.rb +2 -0
  15. data/lib/gitlab/client/merge_requests.rb +12 -10
  16. data/lib/gitlab/client/milestones.rb +15 -0
  17. data/lib/gitlab/client/notes.rb +22 -6
  18. data/lib/gitlab/client/projects.rb +18 -0
  19. data/lib/gitlab/client/repositories.rb +4 -1
  20. data/lib/gitlab/client/repository_files.rb +72 -0
  21. data/lib/gitlab/client/snippets.rb +1 -11
  22. data/lib/gitlab/client/system_hooks.rb +1 -0
  23. data/lib/gitlab/client/users.rb +2 -0
  24. data/lib/gitlab/configuration.rb +3 -1
  25. data/lib/gitlab/error.rb +0 -3
  26. data/lib/gitlab/help.rb +77 -31
  27. data/lib/gitlab/objectified_hash.rb +6 -0
  28. data/lib/gitlab/request.rb +31 -15
  29. data/lib/gitlab/shell.rb +57 -58
  30. data/lib/gitlab/version.rb +1 -1
  31. data/spec/fixtures/branch_delete.json +3 -0
  32. data/spec/fixtures/merge_request_changes.json +1 -0
  33. data/spec/fixtures/milestone_issues.json +1 -0
  34. data/spec/fixtures/project_search.json +1 -0
  35. data/spec/fixtures/repository_file.json +1 -0
  36. data/spec/gitlab/cli_helpers_spec.rb +58 -0
  37. data/spec/gitlab/cli_spec.rb +1 -2
  38. data/spec/gitlab/client/branches_spec.rb +15 -0
  39. data/spec/gitlab/client/merge_requests_spec.rb +20 -12
  40. data/spec/gitlab/client/milestones_spec.rb +16 -0
  41. data/spec/gitlab/client/notes_spec.rb +17 -0
  42. data/spec/gitlab/client/projects_spec.rb +45 -8
  43. data/spec/gitlab/client/repository_files_spec.rb +45 -0
  44. data/spec/gitlab/help_spec.rb +44 -0
  45. data/spec/gitlab/objectified_hash_spec.rb +7 -0
  46. data/spec/gitlab/request_spec.rb +45 -6
  47. data/spec/gitlab/shell_spec.rb +80 -0
  48. data/spec/gitlab_spec.rb +12 -0
  49. metadata +23 -3
@@ -11,11 +11,17 @@ module Gitlab
11
11
  end
12
12
  end
13
13
 
14
+ # @return [Hash] The original hash.
14
15
  def to_hash
15
16
  @hash
16
17
  end
17
18
  alias_method :to_h, :to_hash
18
19
 
20
+ # @return [String] Formatted string with the class name, object id and original hash.
21
+ def inspect
22
+ "#<#{self.class}:#{object_id} {hash: #{@hash.inspect}}"
23
+ end
24
+
19
25
  # Delegate to ObjectifiedHash.
20
26
  def method_missing(key)
21
27
  @data.key?(key.to_s) ? @data[key.to_s] : nil
@@ -19,6 +19,8 @@ module Gitlab
19
19
  ObjectifiedHash.new body
20
20
  elsif body.is_a? Array
21
21
  body.collect! { |e| ObjectifiedHash.new(e) }
22
+ elsif body.nil?
23
+ false
22
24
  else
23
25
  raise Error::Parsing.new "Couldn't parse a response body"
24
26
  end
@@ -35,25 +37,25 @@ module Gitlab
35
37
 
36
38
  def get(path, options={})
37
39
  set_httparty_config(options)
38
- set_private_token_header(options)
40
+ set_authorization_header(options)
39
41
  validate self.class.get(@endpoint + path, options)
40
42
  end
41
43
 
42
44
  def post(path, options={})
43
45
  set_httparty_config(options)
44
- set_private_token_header(options, path)
46
+ set_authorization_header(options, path)
45
47
  validate self.class.post(@endpoint + path, options)
46
48
  end
47
49
 
48
50
  def put(path, options={})
49
51
  set_httparty_config(options)
50
- set_private_token_header(options)
52
+ set_authorization_header(options)
51
53
  validate self.class.put(@endpoint + path, options)
52
54
  end
53
55
 
54
56
  def delete(path, options={})
55
57
  set_httparty_config(options)
56
- set_private_token_header(options)
58
+ set_authorization_header(options)
57
59
  validate self.class.delete(@endpoint + path, options)
58
60
  end
59
61
 
@@ -77,23 +79,24 @@ module Gitlab
77
79
 
78
80
  # Sets a base_uri and default_params for requests.
79
81
  # @raise [Error::MissingCredentials] if endpoint not set.
80
- def set_request_defaults(endpoint, private_token, sudo=nil)
81
- raise Error::MissingCredentials.new("Please set an endpoint to API") unless endpoint
82
- @private_token = private_token
83
- @endpoint = endpoint
84
-
82
+ def set_request_defaults(sudo=nil)
83
+ raise Error::MissingCredentials.new("Please set an endpoint to API") unless @endpoint
85
84
  self.class.default_params :sudo => sudo
86
85
  self.class.default_params.delete(:sudo) if sudo.nil?
87
86
  end
88
87
 
89
88
  private
90
89
 
91
- # Sets a PRIVATE-TOKEN header for requests.
92
- # @raise [Error::MissingCredentials] if private_token not set.
93
- def set_private_token_header(options, path=nil)
90
+ # Sets a PRIVATE-TOKEN or Authorization header for requests.
91
+ # @raise [Error::MissingCredentials] if private_token and auth_token are not set.
92
+ def set_authorization_header(options, path=nil)
94
93
  unless path == '/session'
95
- raise Error::MissingCredentials.new("Please set a private_token for user") unless @private_token
96
- options[:headers] = {'PRIVATE-TOKEN' => @private_token}
94
+ raise Error::MissingCredentials.new("Please provide a private_token or auth_token for user") unless @private_token
95
+ if @private_token.length <= 20
96
+ options[:headers] = {'PRIVATE-TOKEN' => @private_token}
97
+ else
98
+ options[:headers] = {'Authorization' => "Bearer #{@private_token}"}
99
+ end
97
100
  end
98
101
  end
99
102
 
@@ -106,8 +109,21 @@ module Gitlab
106
109
  end
107
110
 
108
111
  def error_message(response)
109
- "Server responded with code #{response.code}, message: #{response.parsed_response.message}. " \
112
+ "Server responded with code #{response.code}, message: " \
113
+ "#{handle_error(response.parsed_response.message)}. " \
110
114
  "Request URI: #{response.request.base_uri}#{response.request.path}"
111
115
  end
116
+
117
+ # Handle error response message in case of nested hashes
118
+ def handle_error(message)
119
+ if message.is_a? Gitlab::ObjectifiedHash
120
+ message.to_h.sort.map do |key, val|
121
+ "'#{key}' #{(val.is_a?(Hash) ? val.sort.map { |k,v| "(#{k}: #{v.join(' ')})"} : val).join(' ')}"
122
+ end.join(', ')
123
+ else
124
+ message
125
+ end
126
+ end
127
+
112
128
  end
113
129
  end
@@ -8,80 +8,79 @@ require 'shellwords'
8
8
  class Gitlab::Shell
9
9
  extend Gitlab::CLI::Helpers
10
10
 
11
- # Start gitlab shell and run infinite loop waiting for user input
12
- def self.start
13
- history.load
14
- actions = Gitlab.actions
11
+ class << self
12
+ attr_reader :arguments, :command
15
13
 
16
- comp = proc { |s| actions.map(&:to_s).grep(/^#{Regexp.escape(s)}/) }
17
-
18
- Readline.completion_proc = comp
19
- Readline.completion_append_character = ' '
20
-
21
- client = Gitlab::Client.new(endpoint: '')
22
-
23
- while buf = Readline.readline('gitlab> ')
14
+ def start
24
15
  trap('INT') { quit_shell } # capture ctrl-c
25
-
26
- next if buf.nil? || buf.empty?
27
- quit_shell if buf == 'exit'
28
-
29
- history << buf
30
-
31
- begin
32
- buf = Shellwords.shellwords(buf)
33
- rescue ArgumentError => e
34
- puts e.message
35
- next
16
+ setup
17
+
18
+ while buffer = Readline.readline('gitlab> ')
19
+ begin
20
+ parse_input buffer
21
+
22
+ yaml_load_arguments! @arguments
23
+ @arguments.map! { |arg| symbolize_keys arg }
24
+
25
+ case buffer
26
+ when nil, ''
27
+ next
28
+ when 'exit'
29
+ quit_shell
30
+ when /^\bhelp\b+/
31
+ puts help(arguments[0]) { |out| out.gsub!(/Gitlab\./, 'gitlab> ') }
32
+ else
33
+ history << buffer
34
+
35
+ data = execute command, arguments
36
+ output_table command, arguments, data
37
+ end
38
+ rescue => e
39
+ puts e.message
40
+ end
36
41
  end
37
42
 
38
- cmd = buf.shift
39
- args = buf.count > 0 ? buf : []
43
+ quit_shell # save history if user presses ctrl-d
44
+ end
40
45
 
41
- if cmd == 'help'
42
- methods = []
46
+ def parse_input(buffer)
47
+ buf = Shellwords.shellwords(buffer)
43
48
 
44
- actions.each do |action|
45
- methods << {
46
- name: action.to_s,
47
- owner: client.method(action).owner.to_s
48
- }
49
- end
50
-
51
- args[0].nil? ? Gitlab::Help.get_help(methods) :
52
- Gitlab::Help.get_help(methods, args[0])
53
- next
54
- end
49
+ @command = buf.shift
50
+ @arguments = buf.count > 0 ? buf : []
51
+ end
55
52
 
56
- syntax_errors = false
53
+ def setup
54
+ history.load
57
55
 
58
- begin
59
- yaml_load_and_symbolize_hash!(args)
60
- rescue
61
- syntax_errors = true
62
- end
56
+ Readline.completion_proc = completion
57
+ Readline.completion_append_character = ' '
58
+ end
63
59
 
64
- # errors have been displayed, return to the prompt
65
- next if syntax_errors
60
+ # Gets called when user hits TAB key to do completion
61
+ def completion
62
+ proc { |str| actions.map(&:to_s).grep(/^#{Regexp.escape(str)}/) }
63
+ end
66
64
 
67
- data = if actions.include?(cmd.to_sym)
65
+ # Execute a given command with arguements
66
+ def execute(cmd = command, args = arguments)
67
+ if actions.include?(cmd.to_sym)
68
68
  confirm_command(cmd)
69
69
  gitlab_helper(cmd, args)
70
70
  else
71
- "'#{cmd}' is not a valid command. " +
72
- "See the 'help' for a list of valid commands."
71
+ raise "Unknown command: #{cmd}. " +
72
+ "See the 'help' for a list of valid commands."
73
73
  end
74
+ end
74
75
 
75
- output_table(cmd, args, data)
76
+ def quit_shell
77
+ history.save
78
+ exit
76
79
  end
77
- end
78
80
 
79
- def self.quit_shell
80
- history.save
81
- exit
82
- end
81
+ def history
82
+ @history ||= History.new
83
+ end
83
84
 
84
- def self.history
85
- @history ||= History.new
86
- end
85
+ end # class << self
87
86
  end
@@ -1,3 +1,3 @@
1
1
  module Gitlab
2
- VERSION = "3.3.0"
2
+ VERSION = "3.4.0"
3
3
  end
@@ -0,0 +1,3 @@
1
+ {
2
+ "branch_name": "api"
3
+ }
@@ -0,0 +1 @@
1
+ {"id":2,"iid":5,"project_id":3,"title":"Uncovered","description":"","state":"opened","created_at":"2015-03-15T23:15:13.292+01:00","updated_at":"2015-03-15T23:15:14.132+01:00","target_branch":"master","source_branch":"uncovered","upvotes":0,"downvotes":0,"author":{"name":"Dominik Sander","username":"dsander","id":1,"state":"active","avatar_url":"https://secure.gravatar.com/avatar/7f01734389dd6730d076ddee04838bd3?s=40\u0026d=identicon"},"assignee":null,"source_project_id":6,"target_project_id":6,"labels":[],"milestone":null,"changes":[{"old_path":"lib/omniauth/builder.rb","new_path":"lib/omniauth/builder.rb","a_mode":"100644","b_mode":"100644","diff":"--- a/lib/omniauth/builder.rb\n+++ b/lib/omniauth/builder.rb\n@@ -7,11 +7,13 @@ module OmniAuth\n else\n @app = app\n super(\u0026block)\n+ two = 1 # woah! uncovered\n @ins \u003c\u003c @app\n end\n end\n \n def rack14?\n+ one = 2 # thats oke!\n Rack.release.split('.')[1].to_i \u003e= 4\n end\n ","new_file":false,"renamed_file":false,"deleted_file":false}]}
@@ -0,0 +1 @@
1
+ [{"id":1,"project_id":3,"title":"Culpa eius recusandae suscipit autem distinctio dolorum.","description":null,"labels":[],"milestone":{"id": 1},"assignee":{"id":1,"email":"john@example.com","name":"John Smith","blocked":false,"created_at":"2012-09-17T09:41:56Z"},"author":{"id":1,"email":"john@example.com","name":"John Smith","blocked":false,"created_at":"2012-09-17T09:41:56Z"},"closed":false,"updated_at":"2012-09-17T09:42:20Z","created_at":"2012-09-17T09:42:20Z"},{"id":6,"project_id":3,"title":"Ut in dolorum omnis sed sit aliquam.","description":null,"labels":[],"milestone":{"id": 1},"assignee":{"id":1,"email":"john@example.com","name":"John Smith","blocked":false,"created_at":"2012-09-17T09:41:56Z"},"author":{"id":1,"email":"john@example.com","name":"John Smith","blocked":false,"created_at":"2012-09-17T09:41:56Z"},"closed":false,"updated_at":"2012-09-17T09:42:20Z","created_at":"2012-09-17T09:42:20Z"},{"id":12,"project_id":3,"title":"Veniam et tempore quidem eum reprehenderit cupiditate non aut velit eaque.","description":null,"labels":[],"milestone":{"id": 1},"assignee":{"id":1,"email":"john@example.com","name":"John Smith","blocked":false,"created_at":"2012-09-17T09:41:56Z"},"author":{"id":1,"email":"john@example.com","name":"John Smith","blocked":false,"created_at":"2012-09-17T09:41:56Z"},"closed":false,"updated_at":"2012-09-17T09:42:20Z","created_at":"2012-09-17T09:42:20Z"},{"id":21,"project_id":3,"title":"Vitae ea aliquam et quo eligendi sapiente voluptatum labore hic nihil culpa.","description":null,"labels":[],"milestone":{"id": 1},"assignee":{"id":1,"email":"john@example.com","name":"John Smith","blocked":false,"created_at":"2012-09-17T09:41:56Z"},"author":{"id":1,"email":"john@example.com","name":"John Smith","blocked":false,"created_at":"2012-09-17T09:41:56Z"},"closed":false,"updated_at":"2012-09-17T09:42:20Z","created_at":"2012-09-17T09:42:20Z"},{"id":26,"project_id":3,"title":"Quo enim est nihil atque placeat voluptas neque eos voluptas.","description":null,"labels":[],"milestone":{"id": 1},"assignee":{"id":1,"email":"john@example.com","name":"John Smith","blocked":false,"created_at":"2012-09-17T09:41:56Z"},"author":{"id":1,"email":"john@example.com","name":"John Smith","blocked":false,"created_at":"2012-09-17T09:41:56Z"},"closed":false,"updated_at":"2012-09-17T09:42:20Z","created_at":"2012-09-17T09:42:20Z"},{"id":32,"project_id":3,"title":"Deserunt tenetur impedit est beatae voluptas voluptas quaerat quisquam.","description":null,"labels":[],"milestone":{"id": 1},"assignee":{"id":1,"email":"john@example.com","name":"John Smith","blocked":false,"created_at":"2012-09-17T09:41:56Z"},"author":{"id":1,"email":"john@example.com","name":"John Smith","blocked":false,"created_at":"2012-09-17T09:41:56Z"},"closed":false,"updated_at":"2012-09-17T09:42:20Z","created_at":"2012-09-17T09:42:20Z"}]
@@ -0,0 +1 @@
1
+ [{"id":3,"code":"gitlab","name":"Gitlab","description":null,"path":"gitlab","default_branch":null,"owner":{"id":1,"email":"john@example.com","name":"John Smith","blocked":false,"created_at":"2012-09-17T09:41:56Z"},"private":true,"issues_enabled":true,"merge_requests_enabled":true,"wall_enabled":true,"wiki_enabled":true,"created_at":"2012-09-17T09:41:58Z"}]
@@ -0,0 +1 @@
1
+ {"file_path":"path","branch_name":"branch","encoding":"base64","content":"Y29udGVudA==","commit_message":"commit message"}
@@ -0,0 +1,58 @@
1
+ require 'spec_helper'
2
+
3
+ describe Gitlab::CLI::Helpers do
4
+ describe ".method_owners" do
5
+ before do
6
+ @methods = Gitlab::CLI::Helpers.method_owners
7
+ end
8
+ it "should return Array of Hashes containing method names and owners" do
9
+ expect(@methods).to be_a Array
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
+ end
13
+ end
14
+
15
+ describe ".valid_command?" do
16
+ it "should return true when command is valid" do
17
+ expect(Gitlab::CLI::Helpers.valid_command? 'merge_requests').to be_truthy
18
+ end
19
+ it "should return false when command is NOT valid" do
20
+ expect(Gitlab::CLI::Helpers.valid_command? 'mmmmmerge_requests').to be_falsy
21
+ end
22
+ end
23
+
24
+ describe ".symbolize_keys" do
25
+ context "when input is a Hash" do
26
+ it "should return a Hash with symbols for keys" do
27
+ hash = {'key1' => 'val1', 'key2' => 'val2'}
28
+ symbolized_hash = Gitlab::CLI::Helpers.symbolize_keys(hash)
29
+ expect(symbolized_hash).to eq({key1: 'val1', key2: 'val2'})
30
+ end
31
+ end
32
+ context "when input is NOT a Hash" do
33
+ it "should return input untouched" do
34
+ array = [1, 2, 3]
35
+ new_array = Gitlab::CLI::Helpers.symbolize_keys(array)
36
+ expect(new_array).to eq([1, 2, 3])
37
+ end
38
+ end
39
+ end
40
+
41
+ describe ".yaml_load_arguments!" do
42
+ context "when arguments are YAML" do
43
+ it "should return Ruby objects" do
44
+ arguments = ["{foo: bar, sna: fu}"]
45
+ Gitlab::CLI::Helpers.yaml_load_arguments! arguments
46
+ expect(arguments).to eq([{'foo' => 'bar', 'sna' => 'fu'}])
47
+ end
48
+ end
49
+
50
+ context "when input is NOT valid YAML" do
51
+ it "should raise" do
52
+ ruby_array = [1, 2, 3, 4]
53
+ expect { Gitlab::CLI::Helpers.yaml_load_arguments! ruby_array}.to raise_exception
54
+ end
55
+ end
56
+ end
57
+
58
+ end
@@ -22,9 +22,8 @@ describe Gitlab::CLI do
22
22
  context "when command is help" do
23
23
  it "should show available actions" do
24
24
  output = capture_output { Gitlab::CLI.run('help') }
25
- expect(output).to include('Available commands')
25
+ expect(output).to include('Help Topics')
26
26
  expect(output).to include('MergeRequests')
27
- expect(output).to include('team_members')
28
27
  end
29
28
  end
30
29
 
@@ -81,4 +81,19 @@ describe Gitlab::Client do
81
81
  expect(@branch.name).to eq("api")
82
82
  end
83
83
  end
84
+
85
+ describe ".delete_branch" do
86
+ before do
87
+ stub_delete("/projects/3/repository/branches/api", "branch_delete")
88
+ @branch = Gitlab.delete_branch(3, "api")
89
+ end
90
+
91
+ it "should get the correct resource" do
92
+ expect(a_delete("/projects/3/repository/branches/api")).to have_been_made
93
+ end
94
+
95
+ it "should return information about the deleted repository branch" do
96
+ expect(@branch.branch_name).to eq("api")
97
+ end
98
+ end
84
99
  end
@@ -38,18 +38,6 @@ describe Gitlab::Client do
38
38
  stub_post("/projects/3/merge_requests", "merge_request")
39
39
  end
40
40
 
41
- it "should fail if it doesn't have a source_branch" do
42
- expect {
43
- Gitlab.create_merge_request(3, 'New merge request', :target_branch => 'master')
44
- }.to raise_error Gitlab::Error::MissingAttributes
45
- end
46
-
47
- it "should fail if it doesn't have a target_branch" do
48
- expect {
49
- Gitlab.create_merge_request(3, 'New merge request', :source_branch => 'dev')
50
- }.to raise_error Gitlab::Error::MissingAttributes
51
- end
52
-
53
41
  it "should return information about a merge request" do
54
42
  @merge_request = Gitlab.create_merge_request(3, 'New feature',
55
43
  :source_branch => 'api',
@@ -143,4 +131,24 @@ describe Gitlab::Client do
143
131
  expect(@merge_request.author.id).to eq(1)
144
132
  end
145
133
  end
134
+
135
+ describe ".merge_request_changes" do
136
+ before do
137
+ stub_get("/projects/3/merge_request/2/changes", "merge_request_changes")
138
+ @mr_changes = Gitlab.merge_request_changes(3, 2)
139
+ end
140
+
141
+ it "should get the correct resource" do
142
+ expect(a_get("/projects/3/merge_request/2/changes")).to have_been_made
143
+ end
144
+
145
+ it "should return the merge request changes" do
146
+ expect(@mr_changes.changes).to be_a Array
147
+ expect(@mr_changes.changes.first['old_path']).to eq('lib/omniauth/builder.rb')
148
+ expect(@mr_changes.id).to eq(2)
149
+ expect(@mr_changes.project_id).to eq(3)
150
+ expect(@mr_changes.source_branch).to eq('uncovered')
151
+ expect(@mr_changes.target_branch).to eq('master')
152
+ end
153
+ end
146
154
  end
@@ -31,6 +31,22 @@ describe Gitlab::Client do
31
31
  expect(@milestone.project_id).to eq(3)
32
32
  end
33
33
  end
34
+
35
+ describe ".milestone_issues" do
36
+ before do
37
+ stub_get("/projects/3/milestones/1/issues", "milestone_issues")
38
+ @milestone_issues = Gitlab.milestone_issues(3, 1)
39
+ end
40
+
41
+ it "should get the correct resource" do
42
+ expect(a_get("/projects/3/milestones/1/issues")).to have_been_made
43
+ end
44
+
45
+ it "should return an array of milestone's issues" do
46
+ expect(@milestone_issues).to be_an Array
47
+ expect(@milestone_issues.first.milestone.id).to eq(1)
48
+ end
49
+ end
34
50
 
35
51
  describe ".create_milestone" do
36
52
  before do
@@ -152,5 +152,22 @@ describe Gitlab::Client do
152
152
  expect(@note.author.name).to eq("John Smith")
153
153
  end
154
154
  end
155
+
156
+ context "when merge_request note" do
157
+ before do
158
+ stub_post("/projects/3/merge_requests/7/notes", "note")
159
+ @note = Gitlab.create_merge_request_note(3, 7, "The solution is rather tricky")
160
+ end
161
+
162
+ it "should get the correct resource" do
163
+ expect(a_post("/projects/3/merge_requests/7/notes").
164
+ with(:body => {:body => 'The solution is rather tricky'})).to have_been_made
165
+ end
166
+
167
+ it "should return information about a created note" do
168
+ expect(@note.body).to eq("The solution is rather tricky")
169
+ expect(@note.author.name).to eq("John Smith")
170
+ end
171
+ end
155
172
  end
156
173
  end