Skip to content

Commit

Permalink
Update documentation for new timeout functionality
Browse files Browse the repository at this point in the history
Signed-off-by: James Couball <jcouball@yahoo.com>
  • Loading branch information
jcouball committed Apr 26, 2024
1 parent 705e983 commit 7e99b17
Show file tree
Hide file tree
Showing 4 changed files with 72 additions and 34 deletions.
34 changes: 19 additions & 15 deletions README.md
Expand Up @@ -158,22 +158,25 @@ rescue Git::Error => e

The timeout feature was added in git gem version `2.0.0`.

A timeout for git operations can be set either globally or for specific method calls
that accept a `:timeout` parameter.
A timeout for git command line operations can be set either globally or for specific
method calls that accept a `:timeout` parameter.

The timeout value must be a real, non-negative `Numeric` value that specifies a
number of seconds a `git` command will be given to complete before being sent a KILL
signal. This library may hang if the `git` command does not terminate after receiving
the KILL signal.

When a command times out, a `Git::TimeoutError` is raised.
When a command times out, it is killed by sending it the `SIGKILL` signal and a
`Git::TimeoutError` is raised. This error derives from the `Git::SignaledError` and
`Git::Error`.

If the timeout value is `0` or `nil`, no timeout will be enforced.

If a method accepts a `:timeout` parameter and a receives a non-nil value, it will
override the global timeout value. In this context, a value of `nil` (which is
usually the default) will use the global timeout value and a value of `0` will turn
off timeout enforcement for that method call no matter what the global value is.
If a method accepts a `:timeout` parameter and a receives a non-nil value, the value
of this parameter will override the global timeout value. In this context, a value of
`nil` (which is usually the default) will use the global timeout value and a value of
`0` will turn off timeout enforcement for that method call no matter what the global
value is.

To set a global timeout, use the `Git.config` object:

Expand All @@ -193,19 +196,20 @@ Git.clone(repo_url, timeout: 0) # Do not enforce a timeout
Git.clone(repo_url, timeout: 10.5) # Timeout after 10.5 seconds raising Git::SignaledError
```

If the command takes too long, a `Git::SignaledError` will be raised:
If the command takes too long, a `Git::TimeoutError` will be raised:

```ruby
begin
Git.clone(repo_url, timeout: 10)
rescue Git::TimeoutError => e
result = e.result
result.class #=> Git::CommandLineResult
result.status #=> #<Process::Status: pid 62173 SIGKILL (signal 9)>
result.status.timeout? #=> true
result.git_cmd # The git command ran as an array of strings
result.stdout # The command's output to stdout until it was terminated
result.stderr # The command's output to stderr until it was terminated
e.result.tap do |r|
r.class #=> Git::CommandLineResult
r.status #=> #<Process::Status: pid 62173 SIGKILL (signal 9)>
r.status.timeout? #=> true
r.git_cmd # The git command ran as an array of strings
r.stdout # The command's output to stdout until it was terminated
r.stderr # The command's output to stderr until it was terminated
end
end
```

Expand Down
9 changes: 8 additions & 1 deletion bin/command_line_test
Expand Up @@ -12,6 +12,7 @@ require 'optparse'
# --stderr: string to output to stderr
# --exitstatus: exit status to return (default is zero)
# --signal: uncaught signal to raise (default is not to signal)
# --duration: number of seconds to sleep before exiting (default is zero)
#
# Both --stdout and --stderr can be given.
#
Expand All @@ -31,7 +32,13 @@ require 'optparse'
# $ bin/command_line_test --stdout="Hello, world!" --stderr="ERROR: timeout"
#


# The command line parser for this script
#
# @example
# parser = CommandLineParser.new
# options = parser.parse(['--exitstatus', '1', '--stderr', 'ERROR: timeout', '--duration', '5'])
#
# @api private
class CommandLineParser
def initialize
@option_parser = OptionParser.new
Expand Down
18 changes: 13 additions & 5 deletions lib/git/command_line.rb
Expand Up @@ -114,7 +114,6 @@ def initialize(env, binary_path, global_opts, logger)
# the normalize option will be ignored.
#
# @example Run a command and return the output
#
# cli.run('version') #=> "git version 2.39.1\n"
#
# @example The args array should be splatted into the parameter list
Expand Down Expand Up @@ -162,14 +161,18 @@ def initialize(env, binary_path, global_opts, logger)
# `stderr_writer.string` will be merged into the output returned by this method.
#
# @param normalize [Boolean] whether to normalize the output to a valid encoding
#
# @param chomp [Boolean] whether to chomp the output
#
# @param merge [Boolean] whether to merge stdout and stderr in the string returned
#
# @param chdir [String] the directory to run the command in
#
# @param timeout [Numeric, nil] the maximum seconds to wait for the command to complete
#
# If timeout is zero or nil, the command will not time out. If the command
# times out, it is killed via a SIGKILL signal and `Git::TimeoutError` is raised.
# If timeout is zero, the timeout will not be enforced.
#
# If the command times out, it is killed via a `SIGKILL` signal and `Git::TimeoutError` is raised.
#
# If the command does not respond to SIGKILL, it will hang this method.
#
Expand All @@ -178,9 +181,13 @@ def initialize(env, binary_path, global_opts, logger)
# This result of running the command.
#
# @raise [ArgumentError] if `args` is not an array of strings
#
# @raise [Git::SignaledError] if the command was terminated because of an uncaught signal
#
# @raise [Git::FailedError] if the command returned a non-zero exitstatus
#
# @raise [Git::GitExecuteError] if an exception was raised while collecting subprocess output
#
# @raise [Git::TimeoutError] if the command times out
#
def run(*args, out:, err:, normalize:, chomp:, merge:, chdir: nil, timeout: nil)
Expand Down Expand Up @@ -267,7 +274,7 @@ def raise_pipe_error(git_cmd, pipe_name, pipe)
#
# @param cmd [Array<String>] the git command to execute
# @param chdir [String] the directory to run the command in
# @param timeout [Float, Integer, nil] the maximum seconds to wait for the command to complete
# @param timeout [Numeric, nil] the maximum seconds to wait for the command to complete
#
# If timeout is zero of nil, the command will not time out. If the command
# times out, it is killed via a SIGKILL signal and `Git::TimeoutError` is raised.
Expand Down Expand Up @@ -321,6 +328,7 @@ def writers(out, err)
# @param err [#write] the object that stderr was written to
# @param normalize [Boolean] whether to normalize the output of each writer
# @param chomp [Boolean] whether to chomp the output of each writer
# @param timeout [Numeric, nil] the maximum seconds to wait for the command to complete
#
# @return [Git::CommandLineResult] the result of the command to return to the caller
#
Expand All @@ -346,7 +354,7 @@ def process_result(git_cmd, status, out, err, normalize, chomp, timeout)
# @param out [#write] the object to write stdout to
# @param err [#write] the object to write stderr to
# @param chdir [String] the directory to run the command in
# @param timeout [Float, Integer, nil] the maximum seconds to wait for the command to complete
# @param timeout [Numeric, nil] the maximum seconds to wait for the command to complete
#
# If timeout is zero of nil, the command will not time out. If the command
# times out, it is killed via a SIGKILL signal and `Git::TimeoutError` is raised.
Expand Down
45 changes: 32 additions & 13 deletions lib/git/lib.rb
Expand Up @@ -80,22 +80,34 @@ def init(opts={})
command('init', *arr_opts)
end

# tries to clone the given repo
# Clones a repository into a newly created directory
#
# accepts options:
# :bare:: no working directory
# :branch:: name of branch to track (rather than 'master')
# :depth:: the number of commits back to pull
# :filter:: specify partial clone
# :origin:: name of remote (same as remote)
# :path:: directory where the repo will be cloned
# :remote:: name of remote (rather than 'origin')
# :recursive:: after the clone is created, initialize all submodules within, using their default settings.
# @param [String] repository_url the URL of the repository to clone
# @param [String, nil] directory the directory to clone into
#
# If nil, the repository is cloned into a directory with the same name as
# the repository.
#
# @param [Hash] opts the options for this command
#
# TODO - make this work with SSH password or auth_key
# @option opts [Boolean] :bare (false) if true, clone as a bare repository
# @option opts [String] :branch the branch to checkout
# @option opts [String, Array] :config one or more configuration options to set
# @option opts [Integer] :depth the number of commits back to pull
# @option opts [String] :filter specify partial clone
# @option opts [String] :mirror set up a mirror of the source repository
# @option opts [String] :origin the name of the remote
# @option opts [String] :path an optional prefix for the directory parameter
# @option opts [String] :remote the name of the remote
# @option opts [Boolean] :recursive after the clone is created, initialize all submodules within, using their default settings
# @option opts [Numeric, nil] :timeout the number of seconds to wait for the command to complete
#
# See {Git::Lib#command} for more information about :timeout
#
# @return [Hash] the options to pass to {Git::Base.new}
#
# @todo make this work with SSH password or auth_key
#
def clone(repository_url, directory, opts = {})
@path = opts[:path] || '.'
clone_dir = opts[:path] ? File.join(@path, directory) : directory
Expand Down Expand Up @@ -1215,8 +1227,15 @@ def command_line
#
# @param chdir [String, nil] the directory to run the command in
#
# @param timeout [Numeric, nil] the maximum time to wait for the command to
# complete
# @param timeout [Numeric, nil] the maximum seconds to wait for the command to complete
#
# If timeout is nil, the global timeout from {Git::Config} is used.
#
# If timeout is zero, the timeout will not be enforced.
#
# If the command times out, it is killed via a `SIGKILL` signal and `Git::TimeoutError` is raised.
#
# If the command does not respond to SIGKILL, it will hang this method.
#
# @see Git::CommandLine#run
#
Expand Down

0 comments on commit 7e99b17

Please sign in to comment.