gitlab 3.3.0 → 3.4.0

Sign up to get free protection for your applications and to get access to all the features.
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