class Git::CommandLine::Capturing
Executes a git command and captures both stdout and stderr in memory
{Git::CommandLine::Capturing} is the buffering strategy: it calls ‘ProcessExecuter.run_with_capture`, which reads all subprocess output into `String` objects before returning. Use this class (via {Git::ExecutionContext#command_capturing}) for the vast majority of git subcommands whose output fits comfortably in memory.
{Git::CommandLine::Streaming} is the complementary strategy for commands (such as ‘cat-file -p <blob>`) whose stdout may be too large to buffer.
@example
capturing = Git::CommandLine::Capturing.new( {}, '/usr/bin/git', %w[--git-dir /repo/.git], Logger.new($stdout) ) result = capturing.run('log', '--oneline', '-5') result.stdout # => "abc1234 Initial commit\n..." result.stderr # => ""
@see Git::ExecutionContext#command_capturing
@see Git::CommandLine::Streaming
@api private
Constants
- RUN_OPTION_DEFAULTS
-
Default options accepted by {#run}
@api private
Public Instance Methods
Source
# File lib/git/command_line/capturing.rb, line 161 def run(*, **options_hash) options = merge_and_validate_options(RUN_OPTION_DEFAULTS, options_hash) result = execute(*, **options) process_result(result, options) end
Execute a git command, capture stdout and stderr, and return the result
Non-option command-line arguments to pass to git. If you collect the arguments in an array, splat the array into the parameter list.
NORMALIZATION
The command output is returned as a Unicode string containing the binary output from the command. If the binary output is not valid UTF-8, the output will cause problems because the encoding will be invalid.
Normalization is a process that tries to convert the binary output to a valid UTF-8 string. It uses the ‘rchardet` gem to detect the encoding of the binary output and then converts it to UTF-8.
Normalization is not enabled by default. Pass ‘normalize: true` to enable it. When enabled, normalization is applied to both stdout and stderr in the returned result object, regardless of the `out:` or `err:` options. Only the captured in-memory strings are normalized; any external IO you provide will receive the raw subprocess output.
@example Run a command and return the output
result = capturing.run('version') result.stdout #=> "git version 2.39.1\n"
@example The args array should be splatted into the parameter list
args = %w[log -n 1 --oneline] result = capturing.run(*args) result.stdout #=> "f5baa11 beginning of Ruby/Git project\n"
@example Run a command and return the chomped output
result = capturing.run('version', chomp: true) result.stdout #=> "git version 2.39.1"
@example Run a command without normalizing the output
capturing.run('version', normalize: false) #=> "git version 2.39.1\n"
@example Capture stdout in a temporary file
require 'tempfile' Tempfile.create('git') do |file| capturing.run('version', out: file) file.rewind file.read #=> "git version 2.39.1\n" end
@example Capture stderr in a StringIO object
require 'stringio' stderr = StringIO.new begin capturing.run('log', 'nonexistent-branch', err: stderr) rescue Git::FailedError => e stderr.string #=> "unknown revision or path not in the working tree.\n" end
@param options_hash [Hash] the options to pass to the command
@option options_hash [IO, nil] :in the IO object to use as stdin for the
command, or nil to inherit the parent process stdin. Must be a real IO object with a file descriptor (not StringIO).
@option options_hash [#write, nil] :out the object to write stdout to, or
nil to capture stdout in the returned result. If this is a `StringIO` object, `stdout_writer.string` will be returned. In general, only specify a `stdout_writer` when you want to redirect stdout to a file or other `#write`-responding object. The default behaviour returns the command output.
@option options_hash [#write, nil] :err the object to write stderr to, or
nil to capture stderr in the returned result.
@option options_hash [Boolean] :normalize (false) whether to normalize the
encoding of stdout and stderr output
@option options_hash [Boolean] :chomp (false) whether to chomp both stdout
and stderr output
@option options_hash [Boolean] :merge (false) whether to merge stdout and
stderr in the returned string
@option options_hash [String, nil] :chdir the directory to run the command in
@option options_hash [Numeric, nil] :timeout the maximum seconds to wait for
the command to complete. Zero means no timeout. A timeout kills the
process via `SIGKILL` and raises {Git::TimeoutError}.
@option options_hash [Boolean] :raise_on_failure (true) whether to raise
{Git::FailedError} on non-zero exit status.
{Git::TimeoutError} and {Git::SignaledError} are always raised regardless.
@option options_hash [Hash] :env ({}) additional environment variable
overrides for this command. String keys map to String values (to set) or `nil` (to unset).
@return [Git::CommandLine::Result] the result of the command
@raise [ArgumentError] if ‘args` contains an array or an unknown option is
passed
@raise [Git::SignaledError] if the command was terminated by an uncaught signal
@raise [Git::FailedError] if the command returned a non-zero exit status
@raise [Git::ProcessIOError] if an exception was raised while collecting
subprocess output, or (Ruby 4.0+) if a timeout-handling race causes `Errno::ESRCH` when the spawned process exits between the timeout firing and the kill signal being delivered
@raise [Git::TimeoutError] if the command times out
Private Instance Methods
Source
# File lib/git/command_line/capturing.rb, line 197 def execute(*args, **options_hash) git_cmd = build_git_cmd(args) options = execution_options(**options_hash) run_process_executer do ProcessExecuter.run_with_capture(merged_env(options_hash), *git_cmd, **options) end end
Execute the git command with the given arguments and options, capturing the output
@param args [Array<String>] the git command arguments
@param options_hash [Hash] the merged run options forwarded from {#run}
Only the keys consumed by this method are listed below; remaining keys
from {RUN_OPTION_DEFAULTS} (`:normalize`, `:chomp`, `:raise_on_failure`)
are present in the hash but are not used here.
@option options_hash [IO, nil] :in stdin IO object for the subprocess
@option options_hash [#write, nil] :out stdout redirect target
@option options_hash [#write, nil] :err stderr redirect target
@option options_hash [String, nil] :chdir working directory for the subprocess
@option options_hash [Numeric, nil] :timeout execution timeout in seconds
@option options_hash [Boolean] :merge (false) merge stdout into stderr
@option options_hash [Hash] :env ({}) environment variable overrides
@return [ProcessExecuter::ResultWithCapture] the process result with captured output
@api private
Source
# File lib/git/command_line/capturing.rb, line 224 def execution_options(**options_hash) chdir = options_hash[:chdir] || :not_set timeout_after = options_hash[:timeout] merge_output = options_hash[:merge] || false { chdir:, timeout_after:, merge_output:, raise_errors: false }.tap do |options| redirect_options(options_hash).each { |k, v| options[k] = v } end end
Build the ProcessExecuter options hash for a capturing run
@param options_hash [Hash] the merged run options forwarded from {#run}
@option options_hash [IO, nil] :in stdin IO object for the subprocess
@option options_hash [#write, nil] :out stdout redirect target
@option options_hash [#write, nil] :err stderr redirect target
@option options_hash [String, nil] :chdir working directory for the subprocess
@option options_hash [Numeric, nil] :timeout execution timeout in seconds
@option options_hash [Boolean] :merge (false) merge stdout into stderr
@return [Hash]
@api private
Source
# File lib/git/command_line/capturing.rb, line 298 def post_process_output(result, normalize, chomp) [result.stdout, result.stderr].map do |raw_output| output = raw_output.dup output = output.lines.map { |l| Git::EncodingUtils.normalize_encoding(l) }.join if normalize output.chomp! if chomp output end end
Normalize and/or chomp the raw stdout and stderr strings.
@param result [ProcessExecuter::ResultWithCapture] the raw result
@param normalize [Boolean]
@param chomp [Boolean]
@return [Array<String>] two-element array: [processed_stdout, processed_stderr]
@api private
Source
# File lib/git/command_line/capturing.rb, line 278 def process_result(result, options) command = result.command processed_out, processed_err = post_process_output(result, options[:normalize], options[:chomp]) log_result(result, command, processed_out, processed_err) command_line_result( command, result, processed_out, processed_err, options[:timeout], options[:raise_on_failure] ) end
Post-process and return the stdout/stderr strings from the captured result, then log and raise on failure if required.
@param result [ProcessExecuter::ResultWithCapture] the raw result
@param options [Hash] the merged run options forwarded from {#run}
@option options [Boolean] :normalize (false) normalize encoding of captured output
@option options [Boolean] :chomp (false) chomp trailing newlines from captured output
@option options [Numeric, nil] :timeout execution timeout used to construct the result
@option options [Boolean] :raise_on_failure (true) raise {Git::FailedError} on non-zero exit
@return [Git::CommandLine::Result]
@raise [Git::FailedError] if the command failed and raise_on_failure is true
@raise [Git::SignaledError] if the command was signaled
@raise [Git::TimeoutError] if the command timed out
@api private
Source
# File lib/git/command_line/capturing.rb, line 247 def redirect_options(options) %i[in out err].filter_map do |key| val = options[key] [key, val] unless val.nil? end.to_h end
Extract non-nil redirect options (‘:in`, `:out`, `:err`) from `options`
@param options [Hash] the options hash containing potential redirect options
@option options [IO, nil] :in the input IO stream
@option options [#write, nil] :out the output IO stream
@option options [#write, nil] :err the error IO stream
@return [Hash] the non-nil redirect options
@api private