gerrit 0.1.0 → 0.2.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.
- checksums.yaml +4 -4
- data/lib/gerrit/client.rb +25 -3
- data/lib/gerrit/command/base.rb +3 -1
- data/lib/gerrit/command/checkout.rb +13 -4
- data/lib/gerrit/command/members.rb +7 -1
- data/lib/gerrit/command/push.rb +133 -0
- data/lib/gerrit/command/setup.rb +69 -0
- data/lib/gerrit/configuration.rb +12 -2
- data/lib/gerrit/error_handler.rb +1 -0
- data/lib/gerrit/errors.rb +3 -0
- data/lib/gerrit/repo.rb +44 -2
- data/lib/gerrit/ui.rb +18 -2
- data/lib/gerrit/utils.rb +20 -0
- data/lib/gerrit/version.rb +1 -1
- metadata +17 -1
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 87240c9921a5ea4b76b05a57b7e6a0f68c003c88
|
4
|
+
data.tar.gz: cd7197693ac6dcc24945697c450e7b8f0712c3ee
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 5467e171e12670ab484430cb9b0ed93f0a70bc42d42fb2fabe96c31bb0d1f087d3b01fcbc47115fd8d5cd7226472448fe76c1991f4145c5a557b78479a01f7ee
|
7
|
+
data.tar.gz: 04f16de2f0662152a187694d0ac06ed32f82f89b01c4465a74653e9633cada93b760d4c5fcade14d42c6dadb73ad556a220cfa54bd1028f869f0defa27914404
|
data/lib/gerrit/client.rb
CHANGED
@@ -43,11 +43,17 @@ module Gerrit
|
|
43
43
|
#
|
44
44
|
# @param group [String] full name of the group
|
45
45
|
# @param recursive [Boolean] whether to include members of sub-groups.
|
46
|
-
# @return [Array<
|
47
|
-
def
|
46
|
+
# @return [Array<Hash>]
|
47
|
+
def group_members(group, recursive: true)
|
48
48
|
flags = []
|
49
49
|
flags << '--recursive' if recursive
|
50
|
-
|
50
|
+
|
51
|
+
rows = execute(%w[ls-members] + ["'#{group}'"] + flags).split("\n")[1..-1]
|
52
|
+
|
53
|
+
rows.map do |row|
|
54
|
+
id, username, full_name, email = row.split("\t")
|
55
|
+
{ id: id, username: username, full_name: full_name, email: email }
|
56
|
+
end
|
51
57
|
end
|
52
58
|
|
53
59
|
# Returns basic information about a change.
|
@@ -63,5 +69,21 @@ module Gerrit
|
|
63
69
|
JSON.parse(rows.first)
|
64
70
|
end
|
65
71
|
end
|
72
|
+
|
73
|
+
# Returns a list of all users to include in the default search scope.
|
74
|
+
#
|
75
|
+
# Gerrit doesn't actually have an endpoint to return all visible users, so
|
76
|
+
# we do the next best thing which is to get users for all groups the user is
|
77
|
+
# a part of, which for all practical purposes is probably good enough.
|
78
|
+
#
|
79
|
+
# Set the `user_search_groups` configuration option to speed this up,
|
80
|
+
# ideally to just one group so we don't have to make parallel calls.
|
81
|
+
def users
|
82
|
+
search_groups = Array(@config.fetch(:user_search_groups, groups))
|
83
|
+
|
84
|
+
Utils.map_in_parallel(search_groups) do |group|
|
85
|
+
group_members(group).map{ |user| user[:username] }
|
86
|
+
end.flatten.uniq
|
87
|
+
end
|
66
88
|
end
|
67
89
|
end
|
data/lib/gerrit/command/base.rb
CHANGED
@@ -5,6 +5,8 @@ module Gerrit::Command
|
|
5
5
|
#
|
6
6
|
# @abstract
|
7
7
|
class Base
|
8
|
+
include Gerrit::Utils
|
9
|
+
|
8
10
|
# @param config [Gerrit::Configuration]
|
9
11
|
# @param ui [Gerrit::UI]
|
10
12
|
# @param arguments [Array<String>]
|
@@ -56,7 +58,7 @@ module Gerrit::Command
|
|
56
58
|
# @param args [Array<String>]
|
57
59
|
# @return [#status, #stdout, #stderr]
|
58
60
|
def spawn(args)
|
59
|
-
Subprocess.spawn(args)
|
61
|
+
Gerrit::Subprocess.spawn(args)
|
60
62
|
end
|
61
63
|
end
|
62
64
|
end
|
@@ -2,10 +2,17 @@ module Gerrit::Command
|
|
2
2
|
# Check out a patchset locally.
|
3
3
|
class Checkout < Base
|
4
4
|
def execute
|
5
|
-
|
6
|
-
|
7
|
-
|
5
|
+
refspec = change_refspec
|
6
|
+
|
7
|
+
ui.spinner('Fetching patchset...') do
|
8
|
+
result = spawn(%W[git fetch #{repo.remote_url} #{refspec}])
|
9
|
+
if result.success?
|
10
|
+
spawn(%w[git checkout FETCH_HEAD])
|
11
|
+
end
|
8
12
|
end
|
13
|
+
|
14
|
+
ui.newline
|
15
|
+
ui.success "You have checked out #{refspec}"
|
9
16
|
end
|
10
17
|
|
11
18
|
private
|
@@ -31,7 +38,9 @@ module Gerrit::Command
|
|
31
38
|
ui.ask('Enter change number or Change-ID').argument(:required).read_string
|
32
39
|
end
|
33
40
|
|
34
|
-
|
41
|
+
ui.spinner('Finding latest patchset...') do
|
42
|
+
client.change(change_num_or_id)
|
43
|
+
end
|
35
44
|
end
|
36
45
|
end
|
37
46
|
end
|
@@ -4,7 +4,13 @@ module Gerrit::Command
|
|
4
4
|
# This allows you to list the members of a group by regex.
|
5
5
|
class Members < Base
|
6
6
|
def execute
|
7
|
-
|
7
|
+
users = client.group_members(find_group)
|
8
|
+
|
9
|
+
ui.table(header: %w[ID Username Name Email]) do |t|
|
10
|
+
users.each do |user|
|
11
|
+
t << [user[:id], user[:username], user[:full_name], user[:email]]
|
12
|
+
end
|
13
|
+
end
|
8
14
|
end
|
9
15
|
|
10
16
|
private
|
@@ -0,0 +1,133 @@
|
|
1
|
+
module Gerrit::Command
|
2
|
+
# Push one or more commits for review.
|
3
|
+
class Push < Base
|
4
|
+
def execute
|
5
|
+
# Sanity check: does this repository have a valid remote_url?
|
6
|
+
# (this will raise an exception if that's not the case)
|
7
|
+
remote_url = repo.remote_url
|
8
|
+
|
9
|
+
# If an explicit ref is given, skip a bunch of the questions
|
10
|
+
if commit_hash?(arguments[1]) || arguments[1] == 'HEAD'
|
11
|
+
ref = arguments[1]
|
12
|
+
reviewer_args = arguments[2..-1] || []
|
13
|
+
target_branch = 'master'
|
14
|
+
type = 'publish'
|
15
|
+
topic = nil
|
16
|
+
else
|
17
|
+
ref = 'HEAD'
|
18
|
+
reviewer_args = arguments[1..-1] || []
|
19
|
+
target_branch = ask_target_branch
|
20
|
+
type = ask_review_type
|
21
|
+
topic = ask_topic
|
22
|
+
end
|
23
|
+
|
24
|
+
reviewers = extract_reviewers(reviewer_args)
|
25
|
+
|
26
|
+
push_changes(remote_url, ref, reviewers, target_branch, type, topic)
|
27
|
+
end
|
28
|
+
|
29
|
+
private
|
30
|
+
|
31
|
+
def push_changes(remote_url, ref, reviewers, target_branch, type, topic)
|
32
|
+
command = %W[git push #{remote_url}]
|
33
|
+
|
34
|
+
if reviewers.any?
|
35
|
+
reviewer_flags = reviewers.map { |reviewer| "--reviewer=#{reviewer}" }
|
36
|
+
command += ['--receive-pack', "git receive-pack #{reviewer_flags.join(' ')}"]
|
37
|
+
end
|
38
|
+
|
39
|
+
destination_ref = "refs/#{type}/#{target_branch}"
|
40
|
+
destination_ref += "/#{topic}" if topic
|
41
|
+
command += ["#{ref}:#{destination_ref}"]
|
42
|
+
|
43
|
+
result =
|
44
|
+
ui.spinner('Pushing changes...') do
|
45
|
+
spawn(command)
|
46
|
+
end
|
47
|
+
|
48
|
+
if result.success?
|
49
|
+
ui.success result.stdout
|
50
|
+
ui.success result.stderr
|
51
|
+
else
|
52
|
+
ui.error result.stdout
|
53
|
+
ui.error result.stderr
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
def extract_reviewers(reviewer_args)
|
58
|
+
if reviewer_args.empty?
|
59
|
+
reviewer_args = ui.ask('Enter users/groups you would like to review your changes')
|
60
|
+
.argument(:required)
|
61
|
+
.read_string
|
62
|
+
.split(/\s*,\s*/)
|
63
|
+
end
|
64
|
+
|
65
|
+
return [] if reviewer_args.empty?
|
66
|
+
|
67
|
+
ui.spinner('Finding matching users/groups...') do
|
68
|
+
extract_users(reviewer_args)
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
def extract_users(reviewer_args)
|
73
|
+
usernames = []
|
74
|
+
groups = client.groups
|
75
|
+
users = client.users
|
76
|
+
|
77
|
+
reviewer_args.each do |arg|
|
78
|
+
users_or_groups = arg.split(/\s*,\s*|\s+/)
|
79
|
+
|
80
|
+
users_or_groups.each do |user_or_group|
|
81
|
+
usernames += users_from_pattern(users, groups, user_or_group)
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
usernames.uniq.sort
|
86
|
+
end
|
87
|
+
|
88
|
+
def users_from_pattern(users, groups, pattern)
|
89
|
+
group_users = users_from_group(groups, pattern)
|
90
|
+
|
91
|
+
# Don't scan users since we already matched a group
|
92
|
+
return group_users if group_users.any?
|
93
|
+
|
94
|
+
users.grep(/#{pattern}/i)
|
95
|
+
end
|
96
|
+
|
97
|
+
def users_from_group(groups, group)
|
98
|
+
matching_groups = groups.grep(/#{group}/i)
|
99
|
+
|
100
|
+
map_in_parallel(matching_groups) do |match|
|
101
|
+
client.group_members(match).map { |user| user[:username] }
|
102
|
+
end.flatten.uniq
|
103
|
+
end
|
104
|
+
|
105
|
+
def ask_target_branch
|
106
|
+
target = ui.ask('Target branch (default master)')
|
107
|
+
.modify(:trim)
|
108
|
+
.read_string
|
109
|
+
|
110
|
+
target.empty? ? 'master' : target
|
111
|
+
end
|
112
|
+
|
113
|
+
def ask_review_type
|
114
|
+
draft = ui.ask('Are you pushing this as a draft? (y/n) [n]')
|
115
|
+
.argument(:required)
|
116
|
+
.default('n')
|
117
|
+
.modify(:downcase)
|
118
|
+
.read_string
|
119
|
+
|
120
|
+
draft == 'y' ? 'draft' : 'publish'
|
121
|
+
end
|
122
|
+
|
123
|
+
def ask_topic
|
124
|
+
topic = ui.ask('Topic name (optional; enter * to autofill with your current branch:')
|
125
|
+
.argument(:optional)
|
126
|
+
.read_string
|
127
|
+
|
128
|
+
topic = repo.branch('HEAD') if topic == '*'
|
129
|
+
topic.strip.empty? ? nil : topic
|
130
|
+
end
|
131
|
+
|
132
|
+
end
|
133
|
+
end
|
@@ -0,0 +1,69 @@
|
|
1
|
+
module Gerrit::Command
|
2
|
+
# Sets up the remotes for this repository to push/pull to/from Gerrit.
|
3
|
+
class Setup < Base
|
4
|
+
def execute
|
5
|
+
remotes_to_add = config[:remotes]
|
6
|
+
existing_remotes = repo.remotes.keys & remotes_to_add.keys
|
7
|
+
|
8
|
+
if existing_remotes.any?
|
9
|
+
return unless can_replace?(existing_remotes)
|
10
|
+
end
|
11
|
+
|
12
|
+
add_remotes(remotes_to_add)
|
13
|
+
end
|
14
|
+
|
15
|
+
private
|
16
|
+
|
17
|
+
def can_replace?(existing_remotes)
|
18
|
+
ui.warning 'The following remotes already exist and will be replaced:'
|
19
|
+
existing_remotes.each do |remote|
|
20
|
+
ui.info remote
|
21
|
+
end
|
22
|
+
|
23
|
+
ui.newline
|
24
|
+
ui.ask('Replace them? (y/n)[n]')
|
25
|
+
.argument(:required)
|
26
|
+
.default('n')
|
27
|
+
.modify(:downcase)
|
28
|
+
.read_string == 'y'
|
29
|
+
end
|
30
|
+
|
31
|
+
def add_remotes(remotes)
|
32
|
+
remotes.each do |remote_name, remote_config|
|
33
|
+
remote_url = render_remote_url(remote_config)
|
34
|
+
|
35
|
+
`git remote rm #{remote_name} &> /dev/null`
|
36
|
+
`git remote add #{remote_name} #{remote_url}`
|
37
|
+
|
38
|
+
if remote_config['push']
|
39
|
+
`git config remote.#{remote_name}.push #{remote_config['push']}`
|
40
|
+
end
|
41
|
+
|
42
|
+
ui.success "Added #{remote_name} #{remote_url}"
|
43
|
+
end
|
44
|
+
|
45
|
+
ui.newline
|
46
|
+
ui.info 'You can now push commits for review by running: ', newline: false
|
47
|
+
ui.print 'gerrit push'
|
48
|
+
end
|
49
|
+
|
50
|
+
def render_remote_url(remote_config)
|
51
|
+
remote_config['url'] % {
|
52
|
+
user: config[:user],
|
53
|
+
host: config[:host],
|
54
|
+
port: config[:port],
|
55
|
+
project: project_name,
|
56
|
+
}
|
57
|
+
end
|
58
|
+
|
59
|
+
# Allow a project name to be explicitly specified, otherwise just use the
|
60
|
+
# repo root directory name.
|
61
|
+
def project_name
|
62
|
+
if arguments[2]
|
63
|
+
arguments[2]
|
64
|
+
else
|
65
|
+
File.basename(repo.root)
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
data/lib/gerrit/configuration.rb
CHANGED
@@ -23,7 +23,9 @@ module Gerrit
|
|
23
23
|
from_file(config_file)
|
24
24
|
else
|
25
25
|
raise Errors::ConfigurationMissingError,
|
26
|
-
|
26
|
+
"No configuration file '#{FILE_NAME}' was found in the " \
|
27
|
+
"current directory or any ancestor directory.\n\n" \
|
28
|
+
"See #{REPO_URL}#configuration for instructions on setting up."
|
27
29
|
end
|
28
30
|
end
|
29
31
|
|
@@ -68,11 +70,19 @@ module Gerrit
|
|
68
70
|
# Access the configuration as if it were a hash.
|
69
71
|
#
|
70
72
|
# @param key [String, Symbol]
|
71
|
-
# @return [Array,Hash,Number,String]
|
73
|
+
# @return [Array, Hash, Number, String]
|
72
74
|
def [](key)
|
73
75
|
@options[key.to_s]
|
74
76
|
end
|
75
77
|
|
78
|
+
# Access the configuration as if it were a hash.
|
79
|
+
#
|
80
|
+
# @param key [String, Symbol]
|
81
|
+
# @return [Array, Hash, Number, String]
|
82
|
+
def fetch(key, *args)
|
83
|
+
@options.fetch(key.to_s, *args)
|
84
|
+
end
|
85
|
+
|
76
86
|
# Compares this configuration with another.
|
77
87
|
#
|
78
88
|
# @param other [HamlLint::Configuration]
|
data/lib/gerrit/error_handler.rb
CHANGED
data/lib/gerrit/errors.rb
CHANGED
@@ -12,6 +12,9 @@ module Gerrit::Errors
|
|
12
12
|
# Base class for all configuration-related errors.
|
13
13
|
class ConfigurationError < GerritError; end
|
14
14
|
|
15
|
+
# Raised when something is incorrect with the configuration.
|
16
|
+
class ConfigurationInvalidError < ConfigurationError; end
|
17
|
+
|
15
18
|
# Raised when a configuration file is not present.
|
16
19
|
class ConfigurationMissingError < ConfigurationError; end
|
17
20
|
|
data/lib/gerrit/repo.rb
CHANGED
@@ -8,6 +8,18 @@ module Gerrit
|
|
8
8
|
@config = config
|
9
9
|
end
|
10
10
|
|
11
|
+
# Returns the name of the currently checked-out branch or the branch the
|
12
|
+
# specified ref is on.
|
13
|
+
#
|
14
|
+
# Returns nil if it is detached.
|
15
|
+
#
|
16
|
+
# @return [String, nil]
|
17
|
+
def branch(ref = 'HEAD')
|
18
|
+
name = `git branch`.split("\n").grep(/^\* /).first[/\w+/]
|
19
|
+
# Check if detached head
|
20
|
+
name.start_with?('(') ? nil : name
|
21
|
+
end
|
22
|
+
|
11
23
|
# Returns the absolute path to the root of the current repository the
|
12
24
|
# current working directory resides within.
|
13
25
|
#
|
@@ -60,12 +72,42 @@ module Gerrit
|
|
60
72
|
#
|
61
73
|
# @return [String]
|
62
74
|
def project
|
63
|
-
|
75
|
+
if url = remote_url
|
76
|
+
File.basename(url[/\/[^\/]+$/], '.git')
|
77
|
+
else
|
78
|
+
# Otherwise just use the name of this repository
|
79
|
+
File.basename(root)
|
80
|
+
end
|
81
|
+
#
|
82
|
+
end
|
83
|
+
|
84
|
+
# Returns all remotes this repository has configured.
|
85
|
+
#
|
86
|
+
# @return [Hash] hash of remote names mapping to their URLs
|
87
|
+
def remotes
|
88
|
+
Hash[
|
89
|
+
`git config --get-regexp '^remote\..+\.url$'`.split("\n").map do |line|
|
90
|
+
match = line.match(/^remote\.(?<name>\S+)\.url\s+(?<url>.*)/)
|
91
|
+
[match[:name], match[:url]]
|
92
|
+
end
|
93
|
+
]
|
64
94
|
end
|
65
95
|
|
66
96
|
# Returns the Gerrit remote URL for this repo.
|
67
97
|
def remote_url
|
68
|
-
|
98
|
+
unless push_remote = @config[:push_remote]
|
99
|
+
raise Errors::ConfigurationInvalidError,
|
100
|
+
'You must specify the `push_remote` option in your configuration.'
|
101
|
+
end
|
102
|
+
|
103
|
+
unless url = remotes[push_remote]
|
104
|
+
raise Errors::ConfigurationInvalidError,
|
105
|
+
"The '#{push_remote}' `push_remote` specified in your " \
|
106
|
+
'configuration is not a remote in this repository. ' \
|
107
|
+
'Have you run `gerrit setup`?'
|
108
|
+
end
|
109
|
+
|
110
|
+
url
|
69
111
|
end
|
70
112
|
end
|
71
113
|
end
|
data/lib/gerrit/ui.rb
CHANGED
@@ -90,12 +90,28 @@ module Gerrit
|
|
90
90
|
print('')
|
91
91
|
end
|
92
92
|
|
93
|
+
# Execute a command with a spinner animation until it completes.
|
94
|
+
def spinner(*args, &block)
|
95
|
+
spinner = TTY::Spinner.new(*args)
|
96
|
+
spinner_thread = Thread.new do
|
97
|
+
loop do
|
98
|
+
sleep 0.1
|
99
|
+
spinner.spin
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
block.call
|
104
|
+
ensure
|
105
|
+
spinner_thread.kill
|
106
|
+
newline # Ensure next line of ouptut on separate line from spinner
|
107
|
+
end
|
108
|
+
|
93
109
|
# Prints a table.
|
94
110
|
#
|
95
111
|
# Customize the table by passing a block and operating on the table object
|
96
112
|
# passed to that block to add rows and customize its appearance.
|
97
|
-
def table(&block)
|
98
|
-
t = TTY::Table.new
|
113
|
+
def table(options = {}, &block)
|
114
|
+
t = TTY::Table.new(options)
|
99
115
|
block.call(t)
|
100
116
|
print(t.render(:unicode))
|
101
117
|
end
|
data/lib/gerrit/utils.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
require 'parallel'
|
2
|
+
|
1
3
|
module Gerrit
|
2
4
|
# A miscellaneous set of utility functions.
|
3
5
|
module Utils
|
@@ -13,6 +15,24 @@ module Gerrit
|
|
13
15
|
.join
|
14
16
|
end
|
15
17
|
|
18
|
+
# Returns whether a string appears to be a commit SHA1 hash.
|
19
|
+
#
|
20
|
+
# @param string [String]
|
21
|
+
# @return [Boolean]
|
22
|
+
def commit_hash?(string)
|
23
|
+
string =~ /^\h{7,40}$/
|
24
|
+
end
|
25
|
+
|
26
|
+
# Executing a block on each item in parallel.
|
27
|
+
#
|
28
|
+
# @param items [Enumerable]
|
29
|
+
# @return [Array]
|
30
|
+
def map_in_parallel(items, &block)
|
31
|
+
Parallel.map(items, in_threads: Parallel.processor_count) do |item|
|
32
|
+
block.call(item)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
16
36
|
# Convert string containing camel case or spaces into snake case.
|
17
37
|
#
|
18
38
|
# @see stackoverflow.com/questions/1509915/converting-camel-case-to-underscore-case-in-ruby
|
data/lib/gerrit/version.rb
CHANGED
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: gerrit
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.2.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Shane da Silva
|
@@ -24,6 +24,20 @@ dependencies:
|
|
24
24
|
- - "~>"
|
25
25
|
- !ruby/object:Gem::Version
|
26
26
|
version: 0.5.6
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: parallel
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: 1.6.0
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: 1.6.0
|
27
41
|
- !ruby/object:Gem::Dependency
|
28
42
|
name: tty
|
29
43
|
requirement: !ruby/object:Gem::Requirement
|
@@ -56,6 +70,8 @@ files:
|
|
56
70
|
- lib/gerrit/command/help.rb
|
57
71
|
- lib/gerrit/command/members.rb
|
58
72
|
- lib/gerrit/command/projects.rb
|
73
|
+
- lib/gerrit/command/push.rb
|
74
|
+
- lib/gerrit/command/setup.rb
|
59
75
|
- lib/gerrit/command/version.rb
|
60
76
|
- lib/gerrit/configuration.rb
|
61
77
|
- lib/gerrit/constants.rb
|