From 7e99b175eed5228f8253ea696c3c17383bff42c3 Mon Sep 17 00:00:00 2001 From: James Couball Date: Thu, 25 Apr 2024 15:52:26 -0700 Subject: [PATCH] Update documentation for new timeout functionality Signed-off-by: James Couball --- README.md | 34 +++++++++++++++++-------------- bin/command_line_test | 9 ++++++++- lib/git/command_line.rb | 18 ++++++++++++----- lib/git/lib.rb | 45 +++++++++++++++++++++++++++++------------ 4 files changed, 72 insertions(+), 34 deletions(-) diff --git a/README.md b/README.md index 2715d2f6..ab1ce6bb 100644 --- a/README.md +++ b/README.md @@ -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: @@ -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 #=> # - 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 #=> # + 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 ``` diff --git a/bin/command_line_test b/bin/command_line_test index 1827da2b..918e2024 100755 --- a/bin/command_line_test +++ b/bin/command_line_test @@ -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. # @@ -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 diff --git a/lib/git/command_line.rb b/lib/git/command_line.rb index ed81cba6..f52ff556 100644 --- a/lib/git/command_line.rb +++ b/lib/git/command_line.rb @@ -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 @@ -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. # @@ -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) @@ -267,7 +274,7 @@ def raise_pipe_error(git_cmd, pipe_name, pipe) # # @param cmd [Array] 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. @@ -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 # @@ -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. diff --git a/lib/git/lib.rb b/lib/git/lib.rb index 28c32b63..9d4fbe5c 100644 --- a/lib/git/lib.rb +++ b/lib/git/lib.rb @@ -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 @@ -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 #