gerrit 0.1.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 +7 -0
- data/bin/gerrit +7 -0
- data/lib/gerrit/cli.rb +72 -0
- data/lib/gerrit/client.rb +67 -0
- data/lib/gerrit/command/base.rb +62 -0
- data/lib/gerrit/command/checkout.rb +37 -0
- data/lib/gerrit/command/groups.rb +10 -0
- data/lib/gerrit/command/help.rb +8 -0
- data/lib/gerrit/command/members.rb +37 -0
- data/lib/gerrit/command/projects.rb +12 -0
- data/lib/gerrit/command/version.rb +8 -0
- data/lib/gerrit/configuration.rb +84 -0
- data/lib/gerrit/constants.rb +7 -0
- data/lib/gerrit/error_handler.rb +45 -0
- data/lib/gerrit/errors.rb +29 -0
- data/lib/gerrit/input.rb +18 -0
- data/lib/gerrit/output.rb +25 -0
- data/lib/gerrit/repo.rb +71 -0
- data/lib/gerrit/subprocess.rb +53 -0
- data/lib/gerrit/ui.rb +103 -0
- data/lib/gerrit/utils.rb +30 -0
- data/lib/gerrit/version.rb +4 -0
- data/lib/gerrit.rb +11 -0
- metadata +96 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: e622e3bacf3ff603e41ef0aaad895a33c5c0c531
|
4
|
+
data.tar.gz: 463bc6a58c060e6ad3d8825fc108e9b0fcd3f1f6
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 25f0ee6364cd63cdc9d3a2519a6bccff966f03735b5bffba41400be186b23316a7df56b1f0bbf108322be73fc6fa41c4d488214ffe1825477dc95677e8b7ff8a
|
7
|
+
data.tar.gz: 6e95f4c7472f6ab0c5f422c44316cec1296665ca3f0a0fd6a9cd1b309237d50e20f809266e0710741bba71cede221dca108742e80995599eec8acf0871097c81
|
data/bin/gerrit
ADDED
data/lib/gerrit/cli.rb
ADDED
@@ -0,0 +1,72 @@
|
|
1
|
+
require 'gerrit'
|
2
|
+
|
3
|
+
module Gerrit
|
4
|
+
# Command line application interface.
|
5
|
+
class CLI
|
6
|
+
# Set of semantic exit codes we can return.
|
7
|
+
#
|
8
|
+
# @see http://www.gsp.com/cgi-bin/man.cgi?section=3&topic=sysexits
|
9
|
+
module ExitCodes
|
10
|
+
OK = 0 # Successful execution
|
11
|
+
USAGE = 64 # User error (bad command line or invalid input)
|
12
|
+
SOFTWARE = 70 # Internal software error (bug)
|
13
|
+
CONFIG = 78 # Configuration error (invalid file or options)
|
14
|
+
end
|
15
|
+
|
16
|
+
# Create a CLI that outputs to the given output destination.
|
17
|
+
#
|
18
|
+
# @param input [Gerrit::Input]
|
19
|
+
# @param output [Gerrit::Output]
|
20
|
+
def initialize(input:, output:)
|
21
|
+
@ui = UI.new(input, output)
|
22
|
+
end
|
23
|
+
|
24
|
+
# Parses the given command-line arguments and executes appropriate logic
|
25
|
+
# based on those arguments.
|
26
|
+
#
|
27
|
+
# @param [Array<String>] arguments
|
28
|
+
# @return [Integer] exit status code
|
29
|
+
def run(arguments)
|
30
|
+
config = Configuration.load_applicable
|
31
|
+
run_command(config, arguments)
|
32
|
+
|
33
|
+
ExitCodes::OK
|
34
|
+
rescue => ex
|
35
|
+
ErrorHandler.new(@ui).handle(ex)
|
36
|
+
end
|
37
|
+
|
38
|
+
private
|
39
|
+
|
40
|
+
# Executes the appropriate command given the list of command line arguments.
|
41
|
+
#
|
42
|
+
# @param config [Gerrit::Configuration]
|
43
|
+
# @param ui [Gerrit::UI]
|
44
|
+
# @param arguments [Array<String>]
|
45
|
+
# @raise [Gerrit::Errors::GerritError] when any exceptional circumstance occurs
|
46
|
+
def run_command(config, arguments)
|
47
|
+
# Display help documentation by default
|
48
|
+
arguments = ['help'] if arguments.empty?
|
49
|
+
|
50
|
+
command_class = find_command(arguments)
|
51
|
+
command_class.new(config, @ui, arguments).run
|
52
|
+
end
|
53
|
+
|
54
|
+
# Finds the {Command} corresponding to the given set of arguments.
|
55
|
+
#
|
56
|
+
# @param [Array<String>] arguments
|
57
|
+
# @return [Class]
|
58
|
+
def find_command(arguments)
|
59
|
+
cmd = arguments.first
|
60
|
+
|
61
|
+
begin
|
62
|
+
require 'gerrit/command/base'
|
63
|
+
require "gerrit/command/#{Utils.snake_case(cmd)}"
|
64
|
+
rescue LoadError => ex
|
65
|
+
raise Errors::CommandInvalidError,
|
66
|
+
"`gerrit #{cmd}` is not a valid command"
|
67
|
+
end
|
68
|
+
|
69
|
+
Command.const_get(Utils.camel_case(cmd))
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
@@ -0,0 +1,67 @@
|
|
1
|
+
require 'json'
|
2
|
+
|
3
|
+
module Gerrit
|
4
|
+
# Client for executing commands against the Gerrit server.
|
5
|
+
class Client
|
6
|
+
# Create a client using the given configuration settings.
|
7
|
+
#
|
8
|
+
# @param config [Gerrit::Configuration]
|
9
|
+
def initialize(config)
|
10
|
+
@config = config
|
11
|
+
end
|
12
|
+
|
13
|
+
# Executes a command against the Gerrit server, returning the output.
|
14
|
+
#
|
15
|
+
# @param command [Array<String>]
|
16
|
+
# @return [String]
|
17
|
+
def execute(command)
|
18
|
+
user = @config[:user]
|
19
|
+
host = @config[:host]
|
20
|
+
port = @config[:port]
|
21
|
+
ssh_cmd = %W[ssh -p #{port} #{user}@#{host} gerrit] + command
|
22
|
+
|
23
|
+
result = Subprocess.spawn(ssh_cmd)
|
24
|
+
unless result.success?
|
25
|
+
raise Errors::GerritCommandFailedError,
|
26
|
+
"Command `#{ssh_cmd.join(' ')}` failed:\n" \
|
27
|
+
"STATUS: #{result.status}\n" \
|
28
|
+
"STDOUT: #{result.stdout.inspect}\n" \
|
29
|
+
"STDERR: #{result.stderr.inspect}\n"
|
30
|
+
end
|
31
|
+
|
32
|
+
result.stdout
|
33
|
+
end
|
34
|
+
|
35
|
+
# Returns all groups visible to the user.
|
36
|
+
#
|
37
|
+
# @return [Array<String>]
|
38
|
+
def groups
|
39
|
+
execute(%w[ls-groups]).split("\n")
|
40
|
+
end
|
41
|
+
|
42
|
+
# Returns members associated with a group.
|
43
|
+
#
|
44
|
+
# @param group [String] full name of the group
|
45
|
+
# @param recursive [Boolean] whether to include members of sub-groups.
|
46
|
+
# @return [Array<String>]
|
47
|
+
def members(group, recursive: true)
|
48
|
+
flags = []
|
49
|
+
flags << '--recursive' if recursive
|
50
|
+
execute(%w[ls-members] + ["'#{group}'"] + flags)
|
51
|
+
end
|
52
|
+
|
53
|
+
# Returns basic information about a change.
|
54
|
+
def change(change_id_or_number)
|
55
|
+
rows = execute(%W[query --format=JSON
|
56
|
+
--current-patch-set
|
57
|
+
change:#{change_id_or_number}]).split("\n")[0..-2]
|
58
|
+
|
59
|
+
if rows.empty?
|
60
|
+
raise Errors::CommandFailedError,
|
61
|
+
"No change matches the id '#{change_id_or_number}'"
|
62
|
+
else
|
63
|
+
JSON.parse(rows.first)
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
@@ -0,0 +1,62 @@
|
|
1
|
+
require 'gerrit/client'
|
2
|
+
|
3
|
+
module Gerrit::Command
|
4
|
+
# Abstract base class of all commands.
|
5
|
+
#
|
6
|
+
# @abstract
|
7
|
+
class Base
|
8
|
+
# @param config [Gerrit::Configuration]
|
9
|
+
# @param ui [Gerrit::UI]
|
10
|
+
# @param arguments [Array<String>]
|
11
|
+
def initialize(config, ui, arguments)
|
12
|
+
@config = config
|
13
|
+
@ui = ui
|
14
|
+
@arguments = arguments
|
15
|
+
end
|
16
|
+
|
17
|
+
# Parses arguments and executes the command.
|
18
|
+
def run
|
19
|
+
# TODO: include a parse step here and remove duplicate parsing code from
|
20
|
+
# individual commands
|
21
|
+
execute
|
22
|
+
end
|
23
|
+
|
24
|
+
# Executes the command given the previously-parsed arguments.
|
25
|
+
def execute
|
26
|
+
raise NotImplementedError, 'Define `execute` in Command subclass'
|
27
|
+
end
|
28
|
+
|
29
|
+
private
|
30
|
+
|
31
|
+
# @return [Array<String>]
|
32
|
+
attr_reader :arguments
|
33
|
+
|
34
|
+
# @return [Gerrit::Configuration]
|
35
|
+
attr_reader :config
|
36
|
+
|
37
|
+
# @return [Gerrit::UI]
|
38
|
+
attr_reader :ui
|
39
|
+
|
40
|
+
# Returns a client for making requests to the Gerrit server.
|
41
|
+
#
|
42
|
+
# @return [Gerrit::Client]
|
43
|
+
def client
|
44
|
+
@client ||= Gerrit::Client.new(@config)
|
45
|
+
end
|
46
|
+
|
47
|
+
# Returns information about this repository.
|
48
|
+
#
|
49
|
+
# @return [Gerrit::Repo]
|
50
|
+
def repo
|
51
|
+
@repo ||= Gerrit::Repo.new(@config)
|
52
|
+
end
|
53
|
+
|
54
|
+
# Execute a process and return the result including status and output.
|
55
|
+
#
|
56
|
+
# @param args [Array<String>]
|
57
|
+
# @return [#status, #stdout, #stderr]
|
58
|
+
def spawn(args)
|
59
|
+
Subprocess.spawn(args)
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
module Gerrit::Command
|
2
|
+
# Check out a patchset locally.
|
3
|
+
class Checkout < Base
|
4
|
+
def execute
|
5
|
+
result = spawn(%W[git fetch #{repo.remote_url} #{change_refspec}])
|
6
|
+
if result.success?
|
7
|
+
spawn(%w[git checkout FETCH_HEAD])
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
private
|
12
|
+
|
13
|
+
# Returns the latest refspec for the given change number.
|
14
|
+
def change_refspec
|
15
|
+
change_md = change_metadata
|
16
|
+
change_number = change_md['number']
|
17
|
+
patchset = change_md['currentPatchSet']['number']
|
18
|
+
|
19
|
+
# Gerrit takes the last two digits of the change number to nest under a
|
20
|
+
# directory with that name so they don't exceed the per-directory file limit
|
21
|
+
prefix = change_number.rjust(2, '0').to_s[-2..-1]
|
22
|
+
|
23
|
+
"refs/changes/#{prefix}/#{change_number}/#{patchset}"
|
24
|
+
end
|
25
|
+
|
26
|
+
def change_metadata
|
27
|
+
change_num_or_id =
|
28
|
+
if arguments[1]
|
29
|
+
arguments[1]
|
30
|
+
else
|
31
|
+
ui.ask('Enter change number or Change-ID').argument(:required).read_string
|
32
|
+
end
|
33
|
+
|
34
|
+
client.change(change_num_or_id)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
module Gerrit::Command
|
2
|
+
# Lists members of a group.
|
3
|
+
#
|
4
|
+
# This allows you to list the members of a group by regex.
|
5
|
+
class Members < Base
|
6
|
+
def execute
|
7
|
+
ui.print client.members(find_group), newline: false
|
8
|
+
end
|
9
|
+
|
10
|
+
private
|
11
|
+
|
12
|
+
def find_group
|
13
|
+
matches = client.groups.grep(/#{search_term}/i)
|
14
|
+
|
15
|
+
if matches.empty?
|
16
|
+
ui.error 'No groups match the given name/regex'
|
17
|
+
raise Gerrit::Errors::CommandFailedError
|
18
|
+
elsif matches.size >= 2
|
19
|
+
ui.warning 'Multiple groups match the given regex:'
|
20
|
+
matches.each do |group|
|
21
|
+
ui.print group
|
22
|
+
end
|
23
|
+
raise Gerrit::Errors::CommandFailedError
|
24
|
+
end
|
25
|
+
|
26
|
+
matches[0]
|
27
|
+
end
|
28
|
+
|
29
|
+
def search_term
|
30
|
+
if arguments[1]
|
31
|
+
arguments[1]
|
32
|
+
else
|
33
|
+
ui.ask('Enter group name or regex').argument(:required).read_string
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,12 @@
|
|
1
|
+
module Gerrit::Command
|
2
|
+
# Displays a list of all projects the user has permissions to see.
|
3
|
+
#
|
4
|
+
# This is a light wrapper around `ls-projects` since it will just append any
|
5
|
+
# additional arguments to the underlying command.
|
6
|
+
class Projects < Base
|
7
|
+
def execute
|
8
|
+
ui.print client.execute(%w[ls-projects] + arguments[1..-1]),
|
9
|
+
newline: false
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
@@ -0,0 +1,84 @@
|
|
1
|
+
require 'pathname'
|
2
|
+
require 'yaml'
|
3
|
+
|
4
|
+
module Gerrit
|
5
|
+
# Stores runtime configuration for the application.
|
6
|
+
#
|
7
|
+
# This is intended to define helper methods for accessing configuration so
|
8
|
+
# this logic can be shared amongst the various components of the system.
|
9
|
+
class Configuration
|
10
|
+
# Name of the configuration file.
|
11
|
+
FILE_NAME = '.gerrit.yaml'
|
12
|
+
|
13
|
+
class << self
|
14
|
+
# Loads appropriate configuration file given the current working
|
15
|
+
# directory.
|
16
|
+
#
|
17
|
+
# @return [Gerrit::Configuration]
|
18
|
+
def load_applicable
|
19
|
+
current_directory = File.expand_path(Dir.pwd)
|
20
|
+
config_file = applicable_config_file(current_directory)
|
21
|
+
|
22
|
+
if config_file
|
23
|
+
from_file(config_file)
|
24
|
+
else
|
25
|
+
raise Errors::ConfigurationMissingError,
|
26
|
+
'No configuration file was found'
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
# Loads a configuration from a file.
|
31
|
+
#
|
32
|
+
# @return [Gerrit::Configuration]
|
33
|
+
def from_file(config_file)
|
34
|
+
options =
|
35
|
+
if yaml = YAML.load_file(config_file)
|
36
|
+
yaml.to_hash
|
37
|
+
else
|
38
|
+
{}
|
39
|
+
end
|
40
|
+
|
41
|
+
new(options)
|
42
|
+
end
|
43
|
+
|
44
|
+
private
|
45
|
+
|
46
|
+
# Returns the first valid configuration file found, starting from the
|
47
|
+
# current working directory and ascending to ancestor directories.
|
48
|
+
#
|
49
|
+
# @param directory [String]
|
50
|
+
# @return [String, nil]
|
51
|
+
def applicable_config_file(directory)
|
52
|
+
Pathname.new(directory)
|
53
|
+
.enum_for(:ascend)
|
54
|
+
.map { |dir| dir + FILE_NAME }
|
55
|
+
.find do |config_file|
|
56
|
+
config_file if config_file.exist?
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
# Creates a configuration from the given options hash.
|
62
|
+
#
|
63
|
+
# @param options [Hash]
|
64
|
+
def initialize(options)
|
65
|
+
@options = options
|
66
|
+
end
|
67
|
+
|
68
|
+
# Access the configuration as if it were a hash.
|
69
|
+
#
|
70
|
+
# @param key [String, Symbol]
|
71
|
+
# @return [Array,Hash,Number,String]
|
72
|
+
def [](key)
|
73
|
+
@options[key.to_s]
|
74
|
+
end
|
75
|
+
|
76
|
+
# Compares this configuration with another.
|
77
|
+
#
|
78
|
+
# @param other [HamlLint::Configuration]
|
79
|
+
# @return [true,false] whether the given configuration is equivalent
|
80
|
+
def ==(other)
|
81
|
+
super || @options == other.instance_variable_get('@options')
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
module Gerrit
|
2
|
+
# Central location of all logic for how exceptions are presented to the user.
|
3
|
+
class ErrorHandler
|
4
|
+
# Creates exception handler that can display output to user via the given
|
5
|
+
# user interface.
|
6
|
+
#
|
7
|
+
# @param [Gerrit::UI] user interface to print output to
|
8
|
+
def initialize(ui)
|
9
|
+
@ui = ui
|
10
|
+
end
|
11
|
+
|
12
|
+
# Display appropriate output to the user for the given exception, returning
|
13
|
+
# a semantic exit status code.
|
14
|
+
#
|
15
|
+
# @return [Integer] exit status code
|
16
|
+
def handle(ex)
|
17
|
+
case ex
|
18
|
+
when Errors::UsageError
|
19
|
+
CLI::ExitCodes::USAGE
|
20
|
+
when Errors::ConfigurationError
|
21
|
+
ui.error ex.message
|
22
|
+
CLI::ExitCodes::CONFIG
|
23
|
+
else
|
24
|
+
print_unexpected_exception(ex)
|
25
|
+
CLI::ExitCodes::SOFTWARE
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
private
|
30
|
+
|
31
|
+
attr_reader :ui
|
32
|
+
|
33
|
+
def print_unexpected_exception(ex)
|
34
|
+
ui.bold_error ex.message
|
35
|
+
ui.error ex.backtrace.join("\n")
|
36
|
+
ui.warning 'Report this bug at ', newline: false
|
37
|
+
ui.info BUG_REPORT_URL
|
38
|
+
ui.newline
|
39
|
+
ui.info 'To help fix this issue, please include:'
|
40
|
+
ui.print '- The above stack trace'
|
41
|
+
ui.print '- Ruby version: ', newline: false
|
42
|
+
ui.info RUBY_VERSION
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
# Collection of errors that can be thrown by the application.
|
2
|
+
#
|
3
|
+
# This implements an exception hierarchy which exceptions to be grouped by type
|
4
|
+
# so the {ExceptionHandler} can display them appropriately.
|
5
|
+
module Gerrit::Errors
|
6
|
+
# Base class for all errors reported by this tool.
|
7
|
+
class GerritError < StandardError; end
|
8
|
+
|
9
|
+
# Base class for all errors that are a result of incorrect user usage.
|
10
|
+
class UsageError < GerritError; end
|
11
|
+
|
12
|
+
# Base class for all configuration-related errors.
|
13
|
+
class ConfigurationError < GerritError; end
|
14
|
+
|
15
|
+
# Raised when a configuration file is not present.
|
16
|
+
class ConfigurationMissingError < ConfigurationError; end
|
17
|
+
|
18
|
+
# Raised when a command has failed due to user error.
|
19
|
+
class CommandFailedError < UsageError; end
|
20
|
+
|
21
|
+
# Raised when invalid/non-existent command was used.
|
22
|
+
class CommandInvalidError < UsageError; end
|
23
|
+
|
24
|
+
# Raised when remote Gerrit command returned a non-zero exit status.
|
25
|
+
class GerritCommandFailedError < GerritError; end
|
26
|
+
|
27
|
+
# Raised when run in a directory not part of a valid git repository.
|
28
|
+
class InvalidGitRepo < UsageError; end
|
29
|
+
end
|
data/lib/gerrit/input.rb
ADDED
@@ -0,0 +1,18 @@
|
|
1
|
+
module Gerrit
|
2
|
+
# Provides interface for collecting input from the user.
|
3
|
+
class Input
|
4
|
+
# Creates an {Gerrit::Input} wrapping the given IO stream.
|
5
|
+
#
|
6
|
+
# @param [IO] input the input stream
|
7
|
+
def initialize(input)
|
8
|
+
@input = input
|
9
|
+
end
|
10
|
+
|
11
|
+
# Blocks until a line of input is returned from the input source.
|
12
|
+
#
|
13
|
+
# @return [String, nil]
|
14
|
+
def get
|
15
|
+
@input.gets
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
module Gerrit
|
2
|
+
# Encapsulates all communication to an output source.
|
3
|
+
class Output
|
4
|
+
# Creates a {Gerrit::Output} which displays nothing.
|
5
|
+
#
|
6
|
+
# @return [Gerrit::Output]
|
7
|
+
def self.silent
|
8
|
+
new(File.open('/dev/null', 'w'))
|
9
|
+
end
|
10
|
+
|
11
|
+
# Creates a new {Gerrit::Output} instance.
|
12
|
+
#
|
13
|
+
# @param stream [IO] the output destination stream.
|
14
|
+
def initialize(stream)
|
15
|
+
@output_stream = stream
|
16
|
+
end
|
17
|
+
|
18
|
+
# Print the specified output.
|
19
|
+
#
|
20
|
+
# @param [String] output the output to display
|
21
|
+
def print(output)
|
22
|
+
@output_stream.print(output)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
data/lib/gerrit/repo.rb
ADDED
@@ -0,0 +1,71 @@
|
|
1
|
+
require 'pathname'
|
2
|
+
|
3
|
+
module Gerrit
|
4
|
+
# Exposes information about the current git repository.
|
5
|
+
class Repo
|
6
|
+
# @param config [Gerrit::Configuration]
|
7
|
+
def initialize(config)
|
8
|
+
@config = config
|
9
|
+
end
|
10
|
+
|
11
|
+
# Returns the absolute path to the root of the current repository the
|
12
|
+
# current working directory resides within.
|
13
|
+
#
|
14
|
+
# @return [String]
|
15
|
+
# @raise [Gerrit::Errors::InvalidGitRepoError] if the current directory
|
16
|
+
# doesn't reside within a git repository
|
17
|
+
def root
|
18
|
+
@root ||=
|
19
|
+
begin
|
20
|
+
git_dir = Pathname.new(File.expand_path('.'))
|
21
|
+
.enum_for(:ascend)
|
22
|
+
.find do |path|
|
23
|
+
(path + '.git').exist?
|
24
|
+
end
|
25
|
+
|
26
|
+
unless git_dir
|
27
|
+
raise Errors::InvalidGitRepoError, 'no .git directory found'
|
28
|
+
end
|
29
|
+
|
30
|
+
git_dir.to_s
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
# Returns an absolute path to the .git directory for a repo.
|
35
|
+
#
|
36
|
+
# @return [String]
|
37
|
+
def git_dir
|
38
|
+
@git_dir ||=
|
39
|
+
begin
|
40
|
+
git_dir = File.expand_path('.git', root)
|
41
|
+
|
42
|
+
# .git could also be a file that contains the location of the git directory
|
43
|
+
unless File.directory?(git_dir)
|
44
|
+
git_dir = File.read(git_dir)[/^gitdir: (.*)$/, 1]
|
45
|
+
|
46
|
+
# Resolve relative paths
|
47
|
+
unless git_dir.start_with?('/')
|
48
|
+
git_dir = File.expand_path(git_dir, repo_dir)
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
git_dir
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
# Returns the project name for this repo.
|
57
|
+
#
|
58
|
+
# Uses the project name specified by the configuration, otherwise just uses
|
59
|
+
# the repo root directory.
|
60
|
+
#
|
61
|
+
# @return [String]
|
62
|
+
def project
|
63
|
+
@config[:project] || File.basename(root)
|
64
|
+
end
|
65
|
+
|
66
|
+
# Returns the Gerrit remote URL for this repo.
|
67
|
+
def remote_url
|
68
|
+
"ssh://#{@config[:user]}@#{@config[:host]}:#{@config[:port]}/#{project}"
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
require 'childprocess'
|
2
|
+
require 'tempfile'
|
3
|
+
|
4
|
+
module Gerrit
|
5
|
+
# Manages execution of a child process, collecting the exit status and
|
6
|
+
# standard out/error output.
|
7
|
+
class Subprocess
|
8
|
+
# Encapsulates the result of a process.
|
9
|
+
#
|
10
|
+
# @attr_reader status [Integer] exit status code returned by process
|
11
|
+
# @attr_reader stdout [String] standard output stream output
|
12
|
+
# @attr_reader stderr [String] standard error stream output
|
13
|
+
Result = Struct.new(:status, :stdout, :stderr) do
|
14
|
+
def success?
|
15
|
+
status == 0
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
class << self
|
20
|
+
# Spawns a new process using the given array of arguments (the first
|
21
|
+
# element is the command).
|
22
|
+
#
|
23
|
+
# @param args [Array<String>]
|
24
|
+
# @return [Result]
|
25
|
+
def spawn(args)
|
26
|
+
process = ChildProcess.build(*args)
|
27
|
+
|
28
|
+
out, err = assign_output_streams(process)
|
29
|
+
|
30
|
+
process.start
|
31
|
+
process.wait
|
32
|
+
|
33
|
+
err.rewind
|
34
|
+
out.rewind
|
35
|
+
|
36
|
+
Result.new(process.exit_code, out.read, err.read)
|
37
|
+
end
|
38
|
+
|
39
|
+
private
|
40
|
+
|
41
|
+
# @param process [ChildProcess]
|
42
|
+
# @return [Array<IO>]
|
43
|
+
def assign_output_streams(process)
|
44
|
+
%w[out err].map do |stream_name|
|
45
|
+
::Tempfile.new(stream_name).tap do |stream|
|
46
|
+
stream.sync = true
|
47
|
+
process.io.send("std#{stream_name}=", stream)
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
data/lib/gerrit/ui.rb
ADDED
@@ -0,0 +1,103 @@
|
|
1
|
+
require 'forwardable'
|
2
|
+
require 'tty'
|
3
|
+
|
4
|
+
module Gerrit
|
5
|
+
# Manages all interaction with the user.
|
6
|
+
class UI
|
7
|
+
extend Forwardable
|
8
|
+
|
9
|
+
def_delegators :@shell, :ask, :confirm
|
10
|
+
|
11
|
+
# Creates a {UI} that mediates between the given input/output streams.
|
12
|
+
#
|
13
|
+
# @param input [Gerrit::Input]
|
14
|
+
# @param output [Gerrit::Output]
|
15
|
+
def initialize(input, output)
|
16
|
+
@input = input
|
17
|
+
@output = output
|
18
|
+
@pastel = Pastel.new
|
19
|
+
@shell = TTY::Shell.new
|
20
|
+
end
|
21
|
+
|
22
|
+
# Get user input, stripping extraneous whitespace.
|
23
|
+
#
|
24
|
+
# @return [String, nil]
|
25
|
+
def user_input
|
26
|
+
if input = @input.get
|
27
|
+
input.strip
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
# Print the specified output.
|
32
|
+
#
|
33
|
+
# @param output [String]
|
34
|
+
# @param newline [Boolean] whether to append a newline
|
35
|
+
def print(output, newline: true)
|
36
|
+
@output.print(output)
|
37
|
+
@output.print("\n") if newline
|
38
|
+
end
|
39
|
+
|
40
|
+
# Print output in bold face.
|
41
|
+
#
|
42
|
+
# @param args [Array]
|
43
|
+
# @param kwargs [Hash]
|
44
|
+
def bold(*args, **kwargs)
|
45
|
+
print(@pastel.bold(*args), **kwargs)
|
46
|
+
end
|
47
|
+
|
48
|
+
# Print the specified output in a color indicative of error.
|
49
|
+
#
|
50
|
+
# @param args [Array]
|
51
|
+
# @param kwargs [Hash]
|
52
|
+
def error(args, **kwargs)
|
53
|
+
print(@pastel.red(*args), **kwargs)
|
54
|
+
end
|
55
|
+
|
56
|
+
# Print the specified output in a bold face and color indicative of error.
|
57
|
+
#
|
58
|
+
# @param args [Array]
|
59
|
+
# @param kwargs [Hash]
|
60
|
+
def bold_error(*args, **kwargs)
|
61
|
+
print(@pastel.bold.red(*args), **kwargs)
|
62
|
+
end
|
63
|
+
|
64
|
+
# Print the specified output in a color indicative of success.
|
65
|
+
#
|
66
|
+
# @param args [Array]
|
67
|
+
# @param kwargs [Hash]
|
68
|
+
def success(*args, **kwargs)
|
69
|
+
print(@pastel.green(*args), **kwargs)
|
70
|
+
end
|
71
|
+
|
72
|
+
# Print the specified output in a color indicative of a warning.
|
73
|
+
#
|
74
|
+
# @param args [Array]
|
75
|
+
# @param kwargs [Hash]
|
76
|
+
def warning(*args, **kwargs)
|
77
|
+
print(@pastel.yellow(*args), **kwargs)
|
78
|
+
end
|
79
|
+
|
80
|
+
# Print the specified output in a color indicating information.
|
81
|
+
#
|
82
|
+
# @param args [Array]
|
83
|
+
# @param kwargs [Hash]
|
84
|
+
def info(*args, **kwargs)
|
85
|
+
print(@pastel.cyan(*args), **kwargs)
|
86
|
+
end
|
87
|
+
|
88
|
+
# Print a blank line.
|
89
|
+
def newline
|
90
|
+
print('')
|
91
|
+
end
|
92
|
+
|
93
|
+
# Prints a table.
|
94
|
+
#
|
95
|
+
# Customize the table by passing a block and operating on the table object
|
96
|
+
# passed to that block to add rows and customize its appearance.
|
97
|
+
def table(&block)
|
98
|
+
t = TTY::Table.new
|
99
|
+
block.call(t)
|
100
|
+
print(t.render(:unicode))
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|
data/lib/gerrit/utils.rb
ADDED
@@ -0,0 +1,30 @@
|
|
1
|
+
module Gerrit
|
2
|
+
# A miscellaneous set of utility functions.
|
3
|
+
module Utils
|
4
|
+
module_function
|
5
|
+
|
6
|
+
# Converts a string containing underscores/hyphens/spaces into CamelCase.
|
7
|
+
#
|
8
|
+
# @param [String] string
|
9
|
+
# @return [String]
|
10
|
+
def camel_case(string)
|
11
|
+
string.split(/_|-| /)
|
12
|
+
.map { |part| part.sub(/^\w/) { |c| c.upcase } }
|
13
|
+
.join
|
14
|
+
end
|
15
|
+
|
16
|
+
# Convert string containing camel case or spaces into snake case.
|
17
|
+
#
|
18
|
+
# @see stackoverflow.com/questions/1509915/converting-camel-case-to-underscore-case-in-ruby
|
19
|
+
#
|
20
|
+
# @param [String] string
|
21
|
+
# @return [String]
|
22
|
+
def snake_case(string)
|
23
|
+
string.gsub(/::/, '/')
|
24
|
+
.gsub(/([A-Z]+)([A-Z][a-z])/, '\1_\2')
|
25
|
+
.gsub(/([a-z\d])([A-Z])/, '\1_\2')
|
26
|
+
.tr('-', '_')
|
27
|
+
.downcase
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
data/lib/gerrit.rb
ADDED
@@ -0,0 +1,11 @@
|
|
1
|
+
require 'gerrit/constants'
|
2
|
+
require 'gerrit/errors'
|
3
|
+
require 'gerrit/error_handler'
|
4
|
+
require 'gerrit/configuration'
|
5
|
+
require 'gerrit/input'
|
6
|
+
require 'gerrit/output'
|
7
|
+
require 'gerrit/ui'
|
8
|
+
require 'gerrit/repo'
|
9
|
+
require 'gerrit/subprocess'
|
10
|
+
require 'gerrit/utils'
|
11
|
+
require 'gerrit/version'
|
metadata
ADDED
@@ -0,0 +1,96 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: gerrit
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Shane da Silva
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2015-07-18 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: childprocess
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: 0.5.6
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: 0.5.6
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: tty
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: 0.2.0
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: 0.2.0
|
41
|
+
description: Tool providing an effective CLI workflow with Gerrit
|
42
|
+
email:
|
43
|
+
- shane@dasilva.io
|
44
|
+
executables:
|
45
|
+
- gerrit
|
46
|
+
extensions: []
|
47
|
+
extra_rdoc_files: []
|
48
|
+
files:
|
49
|
+
- bin/gerrit
|
50
|
+
- lib/gerrit.rb
|
51
|
+
- lib/gerrit/cli.rb
|
52
|
+
- lib/gerrit/client.rb
|
53
|
+
- lib/gerrit/command/base.rb
|
54
|
+
- lib/gerrit/command/checkout.rb
|
55
|
+
- lib/gerrit/command/groups.rb
|
56
|
+
- lib/gerrit/command/help.rb
|
57
|
+
- lib/gerrit/command/members.rb
|
58
|
+
- lib/gerrit/command/projects.rb
|
59
|
+
- lib/gerrit/command/version.rb
|
60
|
+
- lib/gerrit/configuration.rb
|
61
|
+
- lib/gerrit/constants.rb
|
62
|
+
- lib/gerrit/error_handler.rb
|
63
|
+
- lib/gerrit/errors.rb
|
64
|
+
- lib/gerrit/input.rb
|
65
|
+
- lib/gerrit/output.rb
|
66
|
+
- lib/gerrit/repo.rb
|
67
|
+
- lib/gerrit/subprocess.rb
|
68
|
+
- lib/gerrit/ui.rb
|
69
|
+
- lib/gerrit/utils.rb
|
70
|
+
- lib/gerrit/version.rb
|
71
|
+
homepage: https://github.com/sds/gerrit
|
72
|
+
licenses:
|
73
|
+
- MIT
|
74
|
+
metadata: {}
|
75
|
+
post_install_message:
|
76
|
+
rdoc_options: []
|
77
|
+
require_paths:
|
78
|
+
- lib
|
79
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
80
|
+
requirements:
|
81
|
+
- - ">="
|
82
|
+
- !ruby/object:Gem::Version
|
83
|
+
version: 2.0.0
|
84
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
85
|
+
requirements:
|
86
|
+
- - ">="
|
87
|
+
- !ruby/object:Gem::Version
|
88
|
+
version: '0'
|
89
|
+
requirements: []
|
90
|
+
rubyforge_project:
|
91
|
+
rubygems_version: 2.4.8
|
92
|
+
signing_key:
|
93
|
+
specification_version: 4
|
94
|
+
summary: Gerrit command line interface
|
95
|
+
test_files: []
|
96
|
+
has_rdoc:
|