Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We鈥檒l occasionally send you account related emails.

Already on GitHub? Sign in to your account

Update documentation for new timeout functionality #708

Merged
merged 1 commit into from
Apr 26, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
34 changes: 19 additions & 15 deletions README.md
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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