class Git::CommandLine::Base
Abstract base class for git command-line execution strategies
Concrete subclasses must implement {#run} to execute a git command and return a {Git::CommandLine::Result}. Two implementations are provided:
-
{Git::CommandLine::Capturing} — buffers stdout and stderr in memory
-
{Git::CommandLine::Streaming} — streams stdout to a caller-supplied IO
@example Instantiate a concrete subclass
env = { 'GIT_DIR' => '/path/to/git/dir' } binary_path = '/usr/bin/git' global_opts = %w[--git-dir /path/to/git/dir] logger = Logger.new($stdout) cli = Git::CommandLine::Capturing.new(env, binary_path, global_opts, logger) cli.run('version') #=> #<Git::CommandLine::Result ...>
@abstract Subclass and implement {#run}
@api private
Attributes
The path to the command line binary to run
@example
cli = Git::CommandLine::Capturing.new({}, '/usr/bin/git', [], Logger.new(nil)) cli.binary_path #=> '/usr/bin/git'
@return [String]
Variables to set (or unset) in the git command’s environment
@example
env = { 'GIT_DIR' => '/path/to/git/dir' } cli = Git::CommandLine::Capturing.new(env, '/usr/bin/git', [], Logger.new(nil)) cli.env #=> { 'GIT_DIR' => '/path/to/git/dir' }
@return [Hash{String => String, nil}]
@see ruby-doc.org/3.2.1/Process.html#method-c-spawn Process.spawn
for details on how to set environment variables using the `env` parameter
The global options to pass to git
These are options that are passed to git before the command name and arguments. For example, in ‘git –git-dir /path/to/git/dir version`, the global options are %w[–git-dir /path/to/git/dir].
@example
global_opts = %w[--git-dir /path/to/git/dir] cli = Git::CommandLine::Capturing.new({}, '/usr/bin/git', global_opts, Logger.new(nil)) cli.global_opts #=> %w[--git-dir /path/to/git/dir]
@return [Array<String>]
The logger to use for logging git commands and results
@example
logger = Logger.new(nil) cli = Git::CommandLine::Capturing.new({}, '/usr/bin/git', [], logger) cli.logger == logger #=> true
@return [Logger]
Public Class Methods
Source
# File lib/git/command_line/base.rb, line 41 def initialize(env, binary_path, global_opts, logger) @env = env @binary_path = binary_path @global_opts = global_opts @logger = logger end
Create a Base (or subclass) object
@param env [Hash{String => String, nil}] environment variables to set or
unset. String values set the variable; `nil` values unset it.
@param binary_path [String] the path to the git binary
@param global_opts [Array<String>] global options to pass to git
@param logger [Logger] used to log git commands and their results
Public Instance Methods
Source
# File lib/git/command_line/base.rb, line 54 def run(*) raise NotImplementedError, "#{self.class}#run is not implemented" end
Execute a git command and return the result
Concrete subclasses must override this method.
@raise [NotImplementedError] always — must be implemented by subclasses
Private Instance Methods
Source
# File lib/git/command_line/base.rb, line 186 def build_git_cmd(args) raise ArgumentError, 'The args array can not contain an array' if args.any?(Array) [binary_path, *global_opts, *args].map(&:to_s) end
Build the git command line from the available sources to send to ‘Process.spawn`
@param args [Array<String>] command-line arguments to append after global options
@return [Array<String>]
@raise [ArgumentError] if any element of args is itself an Array
@api private
Source
# File lib/git/command_line/base.rb, line 236 def command_line_result(command, result, processed_out, processed_err, timeout, raise_on_failure) Git::CommandLine::Result.new(command, result, processed_out, processed_err).tap do |processed_result| raise Git::TimeoutError.new(processed_result, timeout) if result.timed_out? raise Git::SignaledError, processed_result if result.signaled? raise Git::FailedError, processed_result if raise_on_failure && !result.success? end end
Build a {Git::CommandLine::Result} and raise on timeout, signal, or failure
@param command [Array<String>] the full command that was run
@param result [ProcessExecuter::Result] the raw process result
@param processed_out [String] processed stdout string
@param processed_err [String] processed stderr string
@param timeout [Numeric, nil] the timeout value (for error context)
@param raise_on_failure [Boolean] whether to raise on non-zero exit status
@return [Git::CommandLine::Result]
@raise [Git::TimeoutError] if the command timed out
@raise [Git::SignaledError] if the command was terminated by a signal
@raise [Git::FailedError] if the command failed and raise_on_failure is true
@api private
rubocop:disable Metrics/ParameterLists
Source
# File lib/git/command_line/base.rb, line 206 def log_result(result, command, processed_out, processed_err) logger.info { "#{command} exited with status #{result}" } logger.debug { "stdout:\n#{processed_out.inspect}\nstderr:\n#{processed_err.inspect}" } end
Log the result of a git command at info/debug level
@param result [ProcessExecuter::Result] the raw process result
@param command [Array<String>] the full command that was run
@param processed_out [String] the post-processed stdout string
@param processed_err [String] the post-processed stderr string
@return [void]
@api private
Source
# File lib/git/command_line/base.rb, line 122 def merge_and_validate_options(defaults, options_hash) merged = defaults.merge(options_hash) extra = merged.keys - defaults.keys raise ArgumentError, "Unknown options: #{extra.join(', ')}" if extra.any? merged end
Merge caller-supplied options into ‘defaults` and raise if any unknown keys are present
@param defaults [Hash] the allowed keys and their default values (e.g. RUN_OPTION_DEFAULTS)
@param options_hash [Hash] caller-supplied options
@return [Hash] defaults with any supplied values overridden
@raise [ArgumentError] if options_hash contains keys not present in defaults
@api private
Source
# File lib/git/command_line/base.rb, line 138 def merged_env(options_hash) env.merge(options_hash[:env] || {}) end
Merge the instance-level env with any per-call overrides in options_hash
@param options_hash [Hash] options that may include an :env override
@return [Hash{String => String, nil}]
@api private
Source
# File lib/git/command_line/base.rb, line 161 def run_process_executer yield rescue ProcessExecuter::ArgumentError => e raise ::ArgumentError, e.message rescue ProcessExecuter::SpawnError => e raise Git::Error, e.message, cause: e.cause rescue ProcessExecuter::ProcessIOError => e raise Git::ProcessIOError, e.message, cause: e.cause rescue Errno::ESRCH => e # Ruby 4.0+: Process.kill raises Errno::ESRCH when the spawned process exits # in the narrow window between the timeout firing and the kill signal being sent. # This is a known race in process_executer's timeout handling. raise Git::ProcessIOError, "Git process no longer exists (timeout race): #{e.message}", cause: e end
Yield to a block that calls ProcessExecuter and translate any ProcessExecuter errors to their ruby-git equivalents
@return [Object] the return value of the block
@raise [ArgumentError] in place of ProcessExecuter::ArgumentError
@raise [Git::Error] in place of ProcessExecuter::SpawnError (binary not found or
failed to launch)
@raise [Git::ProcessIOError] in place of ProcessExecuter::ProcessIOError
@raise [Git::ProcessIOError] when a timeout race causes Errno::ESRCH. On Ruby 4.0+,
`Process.kill` raises `Errno::ESRCH` when the spawned process exits between the timeout firing and the kill signal being delivered. This is a race in process_executer's timeout handling and is semantically equivalent to a timeout.
@api private