Skip to content

Commit 7321c6f

Browse files
committed
Change how the git CLI subprocess is executed
Signed-off-by: James Couball <[email protected]>
1 parent f93e042 commit 7321c6f

22 files changed

+1195
-549
lines changed

Diff for: .github/actions/show-context/action.yml

+130
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,130 @@
1+
# https://help.github.com/en/articles/metadata-syntax-for-github-actions
2+
name: 'Dump context'
3+
description: 'GitHub Action composite to dump context'
4+
author: 'crazy-max'
5+
branding:
6+
color: 'yellow'
7+
icon: 'activity'
8+
9+
runs:
10+
using: "composite"
11+
steps:
12+
-
13+
uses: actions/github-script@v6
14+
with:
15+
script: |
16+
const fs = require('fs');
17+
18+
await core.group(`Env vars`, async () => {
19+
const envs = Object.keys(process.env).sort().reduce(
20+
(obj, key) => {
21+
obj[key] = process.env[key];
22+
return obj;
23+
}, {}
24+
);
25+
core.info(JSON.stringify(Object.fromEntries(Object.entries(envs).filter(([key]) => !key.startsWith('GHACTION_DCTX_') && !key.startsWith('INPUT_'))), null, 2));
26+
});
27+
28+
await core.group(`GitHub context`, async () => {
29+
core.info(JSON.stringify(JSON.parse(`${process.env.GHACTION_DCTX_GITHUB_CONTEXT}`), null, 2));
30+
});
31+
await core.group(`Job context`, async () => {
32+
core.info(JSON.stringify(JSON.parse(`${process.env.GHACTION_DCTX_JOB_CONTEXT}`), null, 2));
33+
});
34+
await core.group(`Jobs context`, async () => {
35+
core.info(JSON.stringify(JSON.parse(`${process.env.GHACTION_DCTX_JOBS_CONTEXT}`), null, 2));
36+
});
37+
await core.group(`Steps context`, async () => {
38+
core.info(JSON.stringify(JSON.parse(`${process.env.GHACTION_DCTX_STEPS_CONTEXT}`), null, 2));
39+
});
40+
await core.group(`Runner context`, async () => {
41+
core.info(JSON.stringify(JSON.parse(`${process.env.GHACTION_DCTX_RUNNER_CONTEXT}`), null, 2));
42+
});
43+
await core.group(`Strategy context`, async () => {
44+
core.info(JSON.stringify(JSON.parse(`${process.env.GHACTION_DCTX_STRATEGY_CONTEXT}`), null, 2));
45+
});
46+
await core.group(`Matrix context`, async () => {
47+
core.info(JSON.stringify(JSON.parse(`${process.env.GHACTION_DCTX_MATRIX_CONTEXT}`), null, 2));
48+
});
49+
50+
await core.group(`Docker info`, async () => {
51+
await exec.exec('docker', ['info'], {ignoreReturnCode: true}).catch(error => {
52+
core.info(error);
53+
});
54+
});
55+
await core.group(`Docker version`, async () => {
56+
await exec.exec('docker', ['version'], {ignoreReturnCode: true}).catch(error => {
57+
core.info(error);
58+
});
59+
});
60+
await core.group(`Docker images`, async () => {
61+
await exec.exec('docker', ['image', 'ls'], {ignoreReturnCode: true}).catch(error => {
62+
core.info(error);
63+
});
64+
});
65+
66+
if (`${process.env.RUNNER_OS}` == 'Linux') {
67+
await core.group(`Install deps`, async () => {
68+
const sudo = await exec.getExecOutput('which sudo', [], {silent: true, ignoreReturnCode: true})
69+
if (sudo.stdout != "") {
70+
const aptget = await exec.getExecOutput('which apt-get', [], {silent: true, ignoreReturnCode: true})
71+
if (aptget.stdout != "") {
72+
await exec.exec('sudo apt-get update');
73+
await exec.exec('sudo apt-get install -y cgroup-tools cpuid');
74+
} else {
75+
core.info('apt-get not found; not installing deps')
76+
}
77+
} else {
78+
core.info('sudo not found; not installing deps')
79+
}
80+
});
81+
await core.group(`Print cpuinfo`, async () => {
82+
await exec.exec('cat /proc/cpuinfo');
83+
});
84+
await core.group(`Print cpuid`, async () => {
85+
const cpuid = await exec.getExecOutput('which cpuid', [], {silent: true, ignoreReturnCode: true})
86+
if (cpuid.stdout != "") {
87+
await exec.exec('cpuid');
88+
} else {
89+
core.info('cpuid not found')
90+
}
91+
});
92+
await core.group(`File system`, async () => {
93+
await exec.exec('df -ah');
94+
});
95+
await core.group(`Mounts`, async () => {
96+
await exec.exec('mount');
97+
});
98+
await core.group(`Docker daemon conf`, async () => {
99+
if ((fs.statSync('/etc/docker', {throwIfNoEntry: false}) != undefined) &&
100+
(fs.statSync('/etc/docker/daemon.json', {throwIfNoEntry: false}) != undefined)) {
101+
core.info(JSON.stringify(JSON.parse(fs.readFileSync('/etc/docker/daemon.json', {encoding: 'utf-8'}).trim()), null, 2));
102+
} else {
103+
core.info('/etc/docker/daemon.json not present')
104+
}
105+
});
106+
await core.group(`Cgroups`, async () => {
107+
const lscgroup = await exec.getExecOutput('which lscgroup', [], {silent: true, ignoreReturnCode: true})
108+
if (lscgroup.stdout != "") {
109+
await exec.exec('lscgroup');
110+
} else {
111+
core.info('lscgroup not found')
112+
}
113+
});
114+
await core.group(`containerd version`, async () => {
115+
const containerd = await exec.getExecOutput('which containerd', [], {silent: true, ignoreReturnCode: true})
116+
if (containerd.stdout != "") {
117+
await exec.exec('containerd --version');
118+
} else {
119+
core.info('containerd not found')
120+
}
121+
});
122+
}
123+
env:
124+
GHACTION_DCTX_GITHUB_CONTEXT: ${{ toJson(github) }}
125+
GHACTION_DCTX_JOB_CONTEXT: ${{ toJson(job) }}
126+
GHACTION_DCTX_JOBS_CONTEXT: ${{ toJson(jobs) }}
127+
GHACTION_DCTX_STEPS_CONTEXT: ${{ toJson(steps) }}
128+
GHACTION_DCTX_RUNNER_CONTEXT: ${{ toJson(runner) }}
129+
GHACTION_DCTX_STRATEGY_CONTEXT: ${{ toJson(strategy) }}
130+
GHACTION_DCTX_MATRIX_CONTEXT: ${{ toJson(matrix) }}

Diff for: .github/workflows/continuous_integration.yml

+24-16
Original file line numberDiff line numberDiff line change
@@ -2,40 +2,48 @@ name: CI
22

33
on:
44
push:
5-
branches: [master]
5+
branches: [master,v1]
66
pull_request:
7-
branches: [master]
7+
branches: [master,v1]
88
workflow_dispatch:
99

1010
jobs:
11-
continuous_integration_build:
11+
build:
12+
name: Ruby ${{ matrix.ruby }} on ${{ matrix.operating-system }}
13+
runs-on: ${{ matrix.operating-system }}
14+
# continue-on-error: ${{ matrix.experimental == 'Yes' }}
1215
continue-on-error: true
16+
env: { JAVA_OPTS: -Djdk.io.File.enableADS=true }
17+
1318
strategy:
1419
fail-fast: false
1520
matrix:
16-
ruby: [3.0, 3.1, 3.2, 3.3]
21+
# Only the latest versions of JRuby and TruffleRuby are tested
22+
ruby: ["3.0", "3.1", "3.2", "3.3", "truffleruby-23.1.1", "jruby-9.4.5.0"]
1723
operating-system: [ubuntu-latest]
24+
experimental: [No]
1825
include:
19-
- ruby: head
26+
- # Building against head version of Ruby is considered experimental
27+
ruby: head
2028
operating-system: ubuntu-latest
21-
- ruby: truffleruby-23.1.1
22-
operating-system: ubuntu-latest
23-
- ruby: 3.0
24-
operating-system: windows-latest
25-
- ruby: jruby-9.4.5.0
26-
operating-system: windows-latest
29+
experimental: Yes
2730

28-
name: Ruby ${{ matrix.ruby }} on ${{ matrix.operating-system }}
29-
30-
runs-on: ${{ matrix.operating-system }}
31+
- # Only test with minimal Ruby version on Windows
32+
ruby: 3.0
33+
operating-system: windows-latest
3134

32-
env:
33-
JAVA_OPTS: -Djdk.io.File.enableADS=true
35+
- # Since JRuby on Windows is known to not work, consider this experimental
36+
ruby: jruby-9.4.5.0
37+
operating-system: windows-latest
38+
experimental: Yes
3439

3540
steps:
3641
- name: Checkout Code
3742
uses: actions/checkout@v3
3843

44+
- name: Dump context
45+
uses: ./.github/actions/show-context
46+
3947
- name: Setup Ruby
4048
uses: ruby/setup-ruby@v1
4149
with:

Diff for: README.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ The changes coming in this major release include:
4040
* Update the required Git command line version to at least 2.28
4141
* Update how CLI commands are called to use the [process_executer](https://github.com/main-branch/process_executer)
4242
gem which is built on top of [Kernel.spawn](https://ruby-doc.org/3.3.0/Kernel.html#method-i-spawn).
43-
See [PR #617](https://github.com/ruby-git/ruby-git/pull/617) for more details
43+
See [PR #684](https://github.com/ruby-git/ruby-git/pull/684) for more details
4444
on the motivation for this implementation.
4545

4646
The tentative plan is to release `2.0.0` near the end of March 2024 depending on

Diff for: bin/command_line_test

+180
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,180 @@
1+
#!/usr/bin/env ruby
2+
# frozen_string_literal: true
3+
4+
require 'optparse'
5+
6+
# A script used to test calling a command line program from Ruby
7+
#
8+
# This script is used to test the `Git::CommandLine` class. It is called
9+
# from the `test_command_line` unit test.
10+
#
11+
# --stdout: string to output to stdout
12+
# --stderr: string to output to stderr
13+
# --exitstatus: exit status to return (default is zero)
14+
# --signal: uncaught signal to raise (default is not to signal)
15+
#
16+
# Both --stdout and --stderr can be given.
17+
#
18+
# If --signal is given, --exitstatus is ignored.
19+
#
20+
# Examples:
21+
# Output "Hello, world!" to stdout and exit with status 0
22+
# $ bin/command_line_test --stdout="Hello, world!" --exitstatus=0
23+
#
24+
# Output "ERROR: timeout" to stderr and exit with status 1
25+
# $ bin/command_line_test --stderr="ERROR: timeout" --exitstatus=1
26+
#
27+
# Output "Fatal: killed by parent" to stderr and signal 9
28+
# $ bin/command_line_test --stderr="Fatal: killed by parent" --signal=9
29+
#
30+
# Output to both stdout and stderr return default exitstatus 0
31+
# $ bin/command_line_test --stdout="Hello, world!" --stderr="ERROR: timeout"
32+
#
33+
34+
35+
class CommandLineParser
36+
def initialize
37+
@option_parser = OptionParser.new
38+
define_options
39+
end
40+
41+
attr_reader :stdout, :stderr, :exitstatus, :signal
42+
43+
# Parse the command line arguements returning the options
44+
#
45+
# @example
46+
# parser = CommandLineParser.new
47+
# options = parser.parse(['major'])
48+
#
49+
# @param args [Array<String>] the command line arguments
50+
#
51+
# @return [CreateGithubRelease::Options] the options
52+
#
53+
def parse(*args)
54+
begin
55+
option_parser.parse!(remaining_args = args.dup)
56+
rescue OptionParser::InvalidOption, OptionParser::MissingArgument => e
57+
report_errors(e.message)
58+
end
59+
parse_remaining_args(remaining_args)
60+
# puts options unless options.quiet
61+
# report_errors(*options.errors) unless options.valid?
62+
self
63+
end
64+
65+
private
66+
67+
# @!attribute [rw] option_parser
68+
#
69+
# The option parser
70+
#
71+
# @return [OptionParser] the option parser
72+
#
73+
# @api private
74+
#
75+
attr_reader :option_parser
76+
77+
def define_options
78+
option_parser.banner = "Usage:\n#{command_template}"
79+
option_parser.separator ''
80+
option_parser.separator "Both --stdout and --stderr can be given."
81+
option_parser.separator 'If --signal is given, --exitstatus is ignored.'
82+
option_parser.separator 'If nothing is given, the script will exit with exitstatus 0.'
83+
option_parser.separator ''
84+
option_parser.separator 'Options:'
85+
%i[
86+
define_help_option define_stdout_option define_stderr_option
87+
define_exitstatus_option define_signal_option
88+
].each { |m| send(m) }
89+
end
90+
91+
# The command line template as a string
92+
# @return [String]
93+
# @api private
94+
def command_template
95+
<<~COMMAND
96+
#{File.basename($PROGRAM_NAME)} \
97+
--help | \
98+
[--stdout="string to stdout"] [--stderr="string to stderr"] [--exitstatus=1] [--signal=9]
99+
COMMAND
100+
end
101+
102+
# Define the stdout option
103+
# @return [void]
104+
# @api private
105+
def define_stdout_option
106+
option_parser.on('--stdout="string to stdout"', 'A string to send to stdout') do |string|
107+
@stdout = string
108+
end
109+
end
110+
111+
# Define the stderr option
112+
# @return [void]
113+
# @api private
114+
def define_stderr_option
115+
option_parser.on('--stderr="string to stderr"', 'A string to send to stderr') do |string|
116+
@stderr = string
117+
end
118+
end
119+
120+
# Define the exitstatus option
121+
# @return [void]
122+
# @api private
123+
def define_exitstatus_option
124+
option_parser.on('--exitstatus=1', 'The exitstatus to return') do |exitstatus|
125+
@exitstatus = Integer(exitstatus)
126+
end
127+
end
128+
129+
# Define the signal option
130+
# @return [void]
131+
# @api private
132+
def define_signal_option
133+
option_parser.on('--signal=9', 'The signal to raise') do |signal|
134+
@signal = Integer(signal)
135+
end
136+
end
137+
138+
# Define the help option
139+
# @return [void]
140+
# @api private
141+
def define_help_option
142+
option_parser.on_tail('-h', '--help', 'Show this message') do
143+
puts option_parser
144+
exit 0
145+
end
146+
end
147+
148+
# An error message constructed from the given errors array
149+
# @return [String]
150+
# @api private
151+
def error_message(errors)
152+
<<~MESSAGE
153+
#{errors.map { |e| "ERROR: #{e}" }.join("\n")}
154+
155+
Use --help for usage
156+
MESSAGE
157+
end
158+
159+
# Output an error message and useage to stderr and exit
160+
# @return [void]
161+
# @api private
162+
def report_errors(*errors)
163+
warn error_message(errors)
164+
exit 1
165+
end
166+
167+
# Parse non-option arguments (there are none for this parser)
168+
# @return [void]
169+
# @api private
170+
def parse_remaining_args(remaining_args)
171+
report_errors('Too many args') unless remaining_args.empty?
172+
end
173+
end
174+
175+
options = CommandLineParser.new.parse(*ARGV)
176+
177+
STDOUT.puts options.stdout if options.stdout
178+
STDERR.puts options.stderr if options.stderr
179+
Process.kill(options.signal, Process.pid) if options.signal
180+
exit(options.exitstatus) if options.exitstatus

Diff for: git.gemspec

+1
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ Gem::Specification.new do |s|
2828
s.requirements = ['git 2.28.0 or greater']
2929

3030
s.add_runtime_dependency 'addressable', '~> 2.8'
31+
s.add_runtime_dependency 'process_executer', '~> 0.7'
3132
s.add_runtime_dependency 'rchardet', '~> 1.8'
3233

3334
s.add_development_dependency 'minitar', '~> 0.9'

0 commit comments

Comments
 (0)