class Git::CommandLine::Streaming
Executes a git command in streaming mode without buffering stdout in memory
{Git::CommandLine::Streaming} is the non-buffering strategy: it calls ‘ProcessExecuter.run` and streams stdout directly to the caller-supplied `out:` IO object. Stderr is always captured internally in a `StringIO` for error diagnostics and is available as `result.stderr`.
Use this class (via {Git::ExecutionContext#command_streaming}) for commands such as ‘cat-file -p <blob>` whose stdout may be too large to buffer in memory.
{Git::CommandLine::Capturing} is the complementary strategy for the common case where buffering stdout is acceptable.
@example Stream a blob to a file
streaming = Git::CommandLine::Streaming.new( {}, '/usr/bin/git', %w[--git-dir /repo/.git], Logger.new($stdout) ) File.open('/tmp/blob', 'wb') do |f| streaming.run('cat-file', 'blob', sha, out: f) end
@see Git::ExecutionContext#command_streaming
@see Git::CommandLine::Capturing
@api private
Constants
- RUN_OPTION_DEFAULTS
-
Default options accepted by {#run}
@api private
Public Instance Methods
Source
# File lib/git/command_line/streaming.rb, line 113 def run(*, **options_hash) options = merge_and_validate_options(RUN_OPTION_DEFAULTS, options_hash) internal_err = StringIO.new # Tee stderr to the caller-provided destination (if any) AND the internal # StringIO. This ensures result.stderr is always available even when err: # is a non-StringIO IO object. err_dest = options[:err] ? build_stderr_tee(internal_err, options[:err]) : internal_err result = execute(*, err_io: err_dest, **options) process_result(result, internal_err, options) end
Execute a git command in streaming mode and return the result
Unlike {Git::CommandLine::Capturing#run}, this method does not buffer stdout in memory. Stdout is written only to the IO object provided via the ‘out:` option. Stderr is captured internally via a `StringIO` for error diagnostics.
Use this entry point for commands that stream large content (e.g. blobs) where capturing stdout in memory would be unacceptable.
@example Stream a blob to a file
file = File.open('/tmp/blob', 'wb') streaming.run('cat-file', 'blob', sha, out: file)
@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 IO/object to stream stdout into.
Stdout is NOT buffered in the returned result; this is the only way to read it.
@option options_hash [#write, nil] :err an optional additional destination to
receive stderr output in real time (e.g. `$stderr` or a `File`). Stderr is always captured internally in a `StringIO` for error diagnostics. When `err:` is provided, writes are teed to both the internal buffer and this destination. `result.stderr` always reflects what was captured in the internal buffer, regardless of whether `err:` is supplied.
@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
`result.stdout` will always be `''` (empty) — stdout was streamed to `out:`. `result.stderr` contains any stderr output captured for diagnostics.
@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/streaming.rb, line 199 def build_stderr_tee(primary, secondary) ::Object.new.tap do |tee| tee.define_singleton_method(:write) do |data| primary.write(data) secondary.write(data) data.bytesize end end end
Build a tee writer that forwards write calls to two destinations simultaneously.
Used to capture stderr in an internal StringIO while also streaming to a caller-provided destination.
@param primary [StringIO] the internal capture buffer
@param secondary [#write] the caller-supplied destination
@return [#write] an object whose write method delegates to both destinations
@api private
Source
# File lib/git/command_line/streaming.rb, line 152 def execute(*args, err_io:, **options_hash) git_cmd = build_git_cmd(args) options = execute_options(err_io:, **options_hash) run_process_executer do ProcessExecuter.run(merged_env(options_hash), *git_cmd, **options) end end
Execute the git command in streaming mode
@param args [Array<String>] the git command arguments
@param err_io [StringIO, write] the internal stderr destination
@param options_hash [Hash] the merged run options forwarded from {#run}
Only the keys consumed by this method are listed below; `:err` and `:raise_on_failure` are present in the hash but not used here (`:err` is handled via the separate `err_io:` argument).
@option options_hash [IO, nil] :in stdin IO object for the subprocess
@option options_hash [#write, nil] :out stdout streaming destination
@option options_hash [String, nil] :chdir working directory for the subprocess
@option options_hash [Numeric, nil] :timeout execution timeout in seconds
@option options_hash [Hash] :env ({}) environment variable overrides
@return [ProcessExecuter::Result] the result of running the command (non-capturing)
@api private
Source
# File lib/git/command_line/streaming.rb, line 177 def execute_options(err_io:, **options_hash) chdir = options_hash[:chdir] || :not_set timeout_after = options_hash[:timeout] { chdir:, timeout_after:, raise_errors: false, err: err_io }.tap do |options| options[:in] = options_hash[:in] unless options_hash[:in].nil? options[:out] = options_hash[:out] unless options_hash[:out].nil? end end
Build the ProcessExecuter options hash for a streaming run
@param err_io [StringIO, write] the stderr destination (internal buffer or tee)
@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 streaming destination
@option options_hash [String, nil] :chdir working directory for the subprocess
@option options_hash [Numeric, nil] :timeout execution timeout in seconds
@return [Hash]
@api private
Source
# File lib/git/command_line/streaming.rb, line 226 def process_result(result, err_io, options) command = result.command stderr = err_io.string log_result(result, command, '', stderr) command_line_result( command, result, '', stderr, options[:timeout], options[:raise_on_failure] ) end
Process the result of a streaming command and return a Git::CommandLine::Result
Constructs stdout as ‘”` (not captured) and stderr from the internal StringIO.
@param result [ProcessExecuter::Result] the raw process result
@param err_io [StringIO] the internal StringIO that captured stderr
@param options [Hash] the merged run options forwarded from {#run}
@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]
@api private