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