Skip to content

Change how the git CLI subprocess is executed #681

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’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Integrate process_executor gem for controlling the git subprocess
Signed-off-by: James Couball <jcouball@yahoo.com>
  • Loading branch information
jcouball committed Jan 3, 2024
commit cc86c260f78f4f8cc4d428c69ff0e228122e408d
25 changes: 14 additions & 11 deletions .github/workflows/continuous_integration.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,32 +2,35 @@ name: CI

on:
push:
branches: [master]
branches: [master,release-2.x]
pull_request:
branches: [master]
branches: [master,release-2.x]
workflow_dispatch:

jobs:
continuous_integration_build:
continue-on-error: true
strategy:
fail-fast: false

matrix:
ruby: [2.7, 3.0, 3.1, 3.2]
ruby: [2.7, 3.0, 3.1, 3.2, head, jruby-head, truffleruby-head]
operating-system: [ubuntu-latest]
experimental: [No]
include:
- ruby: head
operating-system: ubuntu-latest
- ruby: truffleruby-head
operating-system: ubuntu-latest
- ruby: 2.7
- # Run minimal Ruby version supported on windows-latest
ruby: 3.0
operating-system: windows-latest
- ruby: jruby-head

- # Run jruby-latest on windows-latest
ruby: jruby-head
operating-system: windows-latest
# Currently jruby is not support on windows for git-2.x.
# Remove this `experimental` flag when this build succeeds.
experimental: Yes

name: Ruby ${{ matrix.ruby }} on ${{ matrix.operating-system }}

runs-on: ${{ matrix.operating-system }}
continue-on-error: ${{ matrix.experimental == 'Yes' }}

env:
JAVA_OPTS: -Djdk.io.File.enableADS=true
Expand Down
180 changes: 180 additions & 0 deletions bin/command_line_test
Original file line number Diff line number Diff line change
@@ -0,0 +1,180 @@
#!/usr/bin/env ruby
# frozen_string_literal: true

require 'optparse'

# A script used to test calling a command line program from Ruby
#
# This script is used to test the `Git::CommandLine` class. It is called
# from the `test_command_line` unit test.
#
# --stdout: string to output to stdout
# --stderr: string to output to stderr
# --exitstatus: exit status to return (default is zero)
# --signal: uncaught signal to raise (default is not to signal)
#
# Both --stdout and --stderr can be given.
#
# If --signal is given, --exitstatus is ignored.
#
# Examples:
# Output "Hello, world!" to stdout and exit with status 0
# $ bin/command_line_test --stdout="Hello, world!" --exitstatus=0
#
# Output "ERROR: timeout" to stderr and exit with status 1
# $ bin/command_line_test --stderr="ERROR: timeout" --exitstatus=1
#
# Output "Fatal: killed by parent" to stderr and signal 9
# $ bin/command_line_test --stderr="Fatal: killed by parent" --signal=9
#
# Output to both stdout and stderr return default exitstatus 0
# $ bin/command_line_test --stdout="Hello, world!" --stderr="ERROR: timeout"
#


class CommandLineParser
def initialize
@option_parser = OptionParser.new
define_options
end

attr_reader :stdout, :stderr, :exitstatus, :signal

# Parse the command line arguements returning the options
#
# @example
# parser = CommandLineParser.new
# options = parser.parse(['major'])
#
# @param args [Array<String>] the command line arguments
#
# @return [CreateGithubRelease::Options] the options
#
def parse(*args)
begin
option_parser.parse!(remaining_args = args.dup)
rescue OptionParser::InvalidOption, OptionParser::MissingArgument => e
report_errors(e.message)
end
parse_remaining_args(remaining_args)
# puts options unless options.quiet
# report_errors(*options.errors) unless options.valid?
self
end

private

# @!attribute [rw] option_parser
#
# The option parser
#
# @return [OptionParser] the option parser
#
# @api private
#
attr_reader :option_parser

def define_options
option_parser.banner = "Usage:\n#{command_template}"
option_parser.separator ''
option_parser.separator "Both --stdout and --stderr can be given."
option_parser.separator 'If --signal is given, --exitstatus is ignored.'
option_parser.separator 'If nothing is given, the script will exit with exitstatus 0.'
option_parser.separator ''
option_parser.separator 'Options:'
%i[
define_help_option define_stdout_option define_stderr_option
define_exitstatus_option define_signal_option
].each { |m| send(m) }
end

# The command line template as a string
# @return [String]
# @api private
def command_template
<<~COMMAND
#{File.basename($PROGRAM_NAME)} \
--help | \
[--stdout="string to stdout"] [--stderr="string to stderr"] [--exitstatus=1] [--signal=9]
COMMAND
end

# Define the stdout option
# @return [void]
# @api private
def define_stdout_option
option_parser.on('--stdout="string to stdout"', 'A string to send to stdout') do |string|
@stdout = string
end
end

# Define the stderr option
# @return [void]
# @api private
def define_stderr_option
option_parser.on('--stderr="string to stderr"', 'A string to send to stderr') do |string|
@stderr = string
end
end

# Define the exitstatus option
# @return [void]
# @api private
def define_exitstatus_option
option_parser.on('--exitstatus=1', 'The exitstatus to return') do |exitstatus|
@exitstatus = Integer(exitstatus)
end
end

# Define the signal option
# @return [void]
# @api private
def define_signal_option
option_parser.on('--signal=9', 'The signal to raise') do |signal|
@signal = Integer(signal)
end
end

# Define the help option
# @return [void]
# @api private
def define_help_option
option_parser.on_tail('-h', '--help', 'Show this message') do
puts option_parser
exit 0
end
end

# An error message constructed from the given errors array
# @return [String]
# @api private
def error_message(errors)
<<~MESSAGE
#{errors.map { |e| "ERROR: #{e}" }.join("\n")}

Use --help for usage
MESSAGE
end

# Output an error message and useage to stderr and exit
# @return [void]
# @api private
def report_errors(*errors)
warn error_message(errors)
exit 1
end

# Parse non-option arguments (there are none for this parser)
# @return [void]
# @api private
def parse_remaining_args(remaining_args)
report_errors('Too many args') unless remaining_args.empty?
end
end

options = CommandLineParser.new.parse(*ARGV)

STDOUT.puts options.stdout if options.stdout
STDERR.puts options.stderr if options.stderr
Process.kill(options.signal, Process.pid) if options.signal
exit(options.exitstatus) if options.exitstatus
1 change: 1 addition & 0 deletions git.gemspec
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ Gem::Specification.new do |s|
s.requirements = ['git 1.6.0.0, or greater']

s.add_runtime_dependency 'addressable', '~> 2.8'
s.add_runtime_dependency 'process_executer', '~> 0.7'
s.add_runtime_dependency 'rchardet', '~> 1.8'

s.add_development_dependency 'bump', '~> 0.10'
Expand Down
2 changes: 2 additions & 0 deletions lib/git.rb
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
require 'git/branch'
require 'git/branches'
require 'git/command_line_result'
require 'git/command_line'
require 'git/config'
require 'git/diff'
require 'git/encoding_utils'
Expand All @@ -23,6 +24,7 @@
require 'git/repository'
require 'git/signaled_error'
require 'git/status'
require 'git/signaled_error'
require 'git/stash'
require 'git/stashes'
require 'git/url'
Expand Down
Loading