git 1.19.1 → 2.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.github/pull_request_template.md +8 -0
- data/.github/workflows/continuous_integration.yml +14 -20
- data/.github/workflows/experimental_continuous_integration.yml +43 -0
- data/CHANGELOG.md +54 -0
- data/CONTRIBUTING.md +22 -67
- data/README.md +147 -41
- data/RELEASING.md +49 -34
- data/git.gemspec +8 -8
- data/lib/git/base.rb +21 -8
- data/lib/git/command_line.rb +377 -0
- data/lib/git/config.rb +5 -1
- data/lib/git/errors.rb +206 -0
- data/lib/git/lib.rb +151 -153
- data/lib/git/object.rb +69 -67
- data/lib/git/version.rb +1 -1
- data/lib/git.rb +8 -7
- metadata +41 -29
- data/.github/stale.yml +0 -25
- data/Dockerfile.changelog-rs +0 -12
- data/PULL_REQUEST_TEMPLATE.md +0 -9
- data/lib/git/failed_error.rb +0 -53
- data/lib/git/git_execute_error.rb +0 -7
- data/lib/git/signaled_error.rb +0 -50
- /data/{ISSUE_TEMPLATE.md → .github/issue_template.md} +0 -0
data/RELEASING.md
CHANGED
@@ -7,64 +7,79 @@
|
|
7
7
|
|
8
8
|
Releasing a new version of the `git` gem requires these steps:
|
9
9
|
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
These instructions use an example where:
|
17
|
-
|
18
|
-
- The default branch is `master`
|
19
|
-
- The current release version is `1.5.0`
|
20
|
-
- You want to create a new *minor* release, `1.6.0`
|
10
|
+
* [Install Prerequisites](#install-prerequisites)
|
11
|
+
* [Determine the SemVer release type](#determine-the-semver-release-type)
|
12
|
+
* [Create the release](#create-the-release)
|
13
|
+
* [Review the CHANGELOG and release PR](#review-the-changelog-and-release-pr)
|
14
|
+
* [Manually merge the release PR](#manually-merge-the-release-pr)
|
15
|
+
* [Publish the git gem to RubyGems.org](#publish-the-git-gem-to-rubygemsorg)
|
21
16
|
|
22
17
|
## Install Prerequisites
|
23
18
|
|
24
19
|
The following tools need to be installed in order to create the release:
|
25
20
|
|
26
|
-
|
27
|
-
|
28
|
-
|
21
|
+
* [create_githhub_release](https://github.com/main-branch/create_github_release) is used to create the release
|
22
|
+
* [git](https://git-scm.com) is used by `create-github-release` to interact with the local and remote repositories
|
23
|
+
* [gh](https://cli.github.com) is used by `create-github-release` to create the release and PR in GitHub
|
29
24
|
|
30
|
-
On a Mac, these tools can be installed using [brew](https://brew.sh):
|
25
|
+
On a Mac, these tools can be installed using [gem](https://guides.rubygems.org/rubygems-basics/) and [brew](https://brew.sh):
|
31
26
|
|
32
27
|
```shell
|
28
|
+
$ gem install create_github_release
|
29
|
+
...
|
33
30
|
$ brew install git
|
34
31
|
...
|
35
32
|
$ brew install gh
|
36
33
|
...
|
37
|
-
$ brew install --cask docker
|
38
|
-
...
|
39
34
|
$
|
40
35
|
```
|
41
36
|
|
42
|
-
##
|
37
|
+
## Determine the SemVer release type
|
43
38
|
|
44
|
-
|
39
|
+
Determine the SemVer version increment that should be applied for the new release:
|
45
40
|
|
46
|
-
|
41
|
+
* `major`: when the release includes incompatible API or functional changes.
|
42
|
+
* `minor`: when the release adds functionality in a backward-compatible manner
|
43
|
+
* `patch`: when the release includes small user-facing changes that are
|
44
|
+
backward-compatible and do not introduce new functionality.
|
47
45
|
|
48
|
-
|
49
|
-
- Install development dependencies using bundle `bundle install`
|
50
|
-
- Based upon the nature of the changes, decide on the type of release: `major`, `minor`, or `patch` (in this example we will use `minor`)
|
51
|
-
- Run the release script `bundle exec create-github-release minor`
|
46
|
+
## Create the release
|
52
47
|
|
53
|
-
|
48
|
+
Create the release using the `create-github-release` command. If the release type
|
49
|
+
is `major`, the command is:
|
54
50
|
|
55
|
-
|
51
|
+
```shell
|
52
|
+
create-github-release major
|
53
|
+
```
|
56
54
|
|
57
|
-
|
55
|
+
Follow the directions given by the `create-github-release` command to finish the
|
56
|
+
release. Where the instructions given by the command differ than the instructions
|
57
|
+
below, follow the instructions given by the command.
|
58
58
|
|
59
|
-
|
59
|
+
## Review the CHANGELOG and release PR
|
60
60
|
|
61
|
-
-
|
62
|
-
|
61
|
+
The `create-github-release` command will output a link to the CHANGELOG and the PR
|
62
|
+
it created for the release. Review the CHANGELOG and have someone review and approve
|
63
|
+
the release PR.
|
63
64
|
|
64
|
-
##
|
65
|
+
## Manually merge the release PR
|
65
66
|
|
66
|
-
|
67
|
+
It is important to manually merge the PR so a separate merge commit can be avoided.
|
68
|
+
Use the commands output by the `create-github-release` which will looks like this
|
69
|
+
if you are creating a 2.0.0 release:
|
67
70
|
|
68
|
-
|
71
|
+
```shell
|
72
|
+
git checkout master
|
73
|
+
git merge --ff-only release-v2.0.0
|
74
|
+
git push
|
75
|
+
```
|
76
|
+
|
77
|
+
This will automatically close the release PR.
|
78
|
+
|
79
|
+
## Publish the git gem to RubyGems.org
|
69
80
|
|
70
|
-
|
81
|
+
Finally, publish the git gem to RubyGems.org using the following command:
|
82
|
+
|
83
|
+
```shell
|
84
|
+
rake release:rubygem_push
|
85
|
+
```
|
data/git.gemspec
CHANGED
@@ -24,22 +24,22 @@ Gem::Specification.new do |s|
|
|
24
24
|
s.metadata['documentation_uri'] = "https://rubydoc.info/gems/#{s.name}/#{s.version}"
|
25
25
|
|
26
26
|
s.require_paths = ['lib']
|
27
|
-
s.required_ruby_version = '>=
|
28
|
-
s.
|
29
|
-
s.requirements = ['git 1.6.0.0, or greater']
|
27
|
+
s.required_ruby_version = '>= 3.0.0'
|
28
|
+
s.requirements = ['git 2.28.0 or greater']
|
30
29
|
|
30
|
+
s.add_runtime_dependency 'activesupport', '>= 5.0'
|
31
31
|
s.add_runtime_dependency 'addressable', '~> 2.8'
|
32
|
+
s.add_runtime_dependency 'process_executer', '~> 1.1'
|
32
33
|
s.add_runtime_dependency 'rchardet', '~> 1.8'
|
33
34
|
|
34
|
-
s.add_development_dependency '
|
35
|
-
s.add_development_dependency 'create_github_release', '~> 0.2'
|
35
|
+
s.add_development_dependency 'create_github_release', '~> 1.4'
|
36
36
|
s.add_development_dependency 'minitar', '~> 0.9'
|
37
37
|
s.add_development_dependency 'mocha', '~> 2.1'
|
38
|
-
s.add_development_dependency 'rake', '~> 13.
|
39
|
-
s.add_development_dependency 'test-unit', '~> 3.
|
38
|
+
s.add_development_dependency 'rake', '~> 13.1'
|
39
|
+
s.add_development_dependency 'test-unit', '~> 3.6'
|
40
40
|
|
41
41
|
unless RUBY_PLATFORM == 'java'
|
42
|
-
s.add_development_dependency 'redcarpet', '~> 3.
|
42
|
+
s.add_development_dependency 'redcarpet', '~> 3.6'
|
43
43
|
s.add_development_dependency 'yard', '~> 0.9', '>= 0.9.28'
|
44
44
|
s.add_development_dependency 'yardstick', '~> 0.9'
|
45
45
|
end
|
data/lib/git/base.rb
CHANGED
@@ -409,14 +409,27 @@ module Git
|
|
409
409
|
self.lib.conflicts(&block)
|
410
410
|
end
|
411
411
|
|
412
|
-
#
|
413
|
-
#
|
414
|
-
#
|
415
|
-
#
|
416
|
-
#
|
417
|
-
#
|
418
|
-
|
419
|
-
|
412
|
+
# Pulls the given branch from the given remote into the current branch
|
413
|
+
#
|
414
|
+
# @param remote [String] the remote repository to pull from
|
415
|
+
# @param branch [String] the branch to pull from
|
416
|
+
# @param opts [Hash] options to pass to the pull command
|
417
|
+
#
|
418
|
+
# @option opts [Boolean] :allow_unrelated_histories (false) Merges histories of two projects that started their
|
419
|
+
# lives independently
|
420
|
+
# @example pulls from origin/master
|
421
|
+
# @git.pull
|
422
|
+
# @example pulls from upstream/master
|
423
|
+
# @git.pull('upstream')
|
424
|
+
# @example pulls from upstream/develop
|
425
|
+
# @git.pull('upstream', 'develop')
|
426
|
+
#
|
427
|
+
# @return [Void]
|
428
|
+
#
|
429
|
+
# @raise [Git::FailedError] if the pull fails
|
430
|
+
# @raise [ArgumentError] if a branch is given without a remote
|
431
|
+
def pull(remote = nil, branch = nil, opts = {})
|
432
|
+
self.lib.pull(remote, branch, opts)
|
420
433
|
end
|
421
434
|
|
422
435
|
# returns an array of Git:Remote objects
|
@@ -0,0 +1,377 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'git/base'
|
4
|
+
require 'git/command_line_result'
|
5
|
+
require 'git/errors'
|
6
|
+
require 'stringio'
|
7
|
+
|
8
|
+
module Git
|
9
|
+
# Runs a git command and returns the result
|
10
|
+
#
|
11
|
+
# @api public
|
12
|
+
#
|
13
|
+
class CommandLine
|
14
|
+
# Create a Git::CommandLine object
|
15
|
+
#
|
16
|
+
# @example
|
17
|
+
# env = { 'GIT_DIR' => '/path/to/git/dir' }
|
18
|
+
# binary_path = '/usr/bin/git'
|
19
|
+
# global_opts = %w[--git-dir /path/to/git/dir]
|
20
|
+
# logger = Logger.new(STDOUT)
|
21
|
+
# cli = CommandLine.new(env, binary_path, global_opts, logger)
|
22
|
+
# cli.run('version') #=> #<Git::CommandLineResult:0x00007f9b0c0b0e00
|
23
|
+
#
|
24
|
+
# @param env [Hash<String, String>] environment variables to set
|
25
|
+
# @param global_opts [Array<String>] global options to pass to git
|
26
|
+
# @param logger [Logger] the logger to use
|
27
|
+
#
|
28
|
+
def initialize(env, binary_path, global_opts, logger)
|
29
|
+
@env = env
|
30
|
+
@binary_path = binary_path
|
31
|
+
@global_opts = global_opts
|
32
|
+
@logger = logger
|
33
|
+
end
|
34
|
+
|
35
|
+
# @attribute [r] env
|
36
|
+
#
|
37
|
+
# Variables to set (or unset) in the git command's environment
|
38
|
+
#
|
39
|
+
# @example
|
40
|
+
# env = { 'GIT_DIR' => '/path/to/git/dir' }
|
41
|
+
# command_line = Git::CommandLine.new(env, '/usr/bin/git', [], Logger.new(STDOUT))
|
42
|
+
# command_line.env #=> { 'GIT_DIR' => '/path/to/git/dir' }
|
43
|
+
#
|
44
|
+
# @return [Hash<String, String>]
|
45
|
+
#
|
46
|
+
# @see https://ruby-doc.org/3.2.1/Process.html#method-c-spawn Process.spawn
|
47
|
+
# for details on how to set environment variables using the `env` parameter
|
48
|
+
#
|
49
|
+
attr_reader :env
|
50
|
+
|
51
|
+
# @attribute [r] binary_path
|
52
|
+
#
|
53
|
+
# The path to the command line binary to run
|
54
|
+
#
|
55
|
+
# @example
|
56
|
+
# binary_path = '/usr/bin/git'
|
57
|
+
# command_line = Git::CommandLine.new({}, binary_path, ['version'], Logger.new(STDOUT))
|
58
|
+
# command_line.binary_path #=> '/usr/bin/git'
|
59
|
+
#
|
60
|
+
# @return [String]
|
61
|
+
#
|
62
|
+
attr_reader :binary_path
|
63
|
+
|
64
|
+
# @attribute [r] global_opts
|
65
|
+
#
|
66
|
+
# The global options to pass to git
|
67
|
+
#
|
68
|
+
# These are options that are passed to git before the command name and
|
69
|
+
# arguments. For example, in `git --git-dir /path/to/git/dir version`, the
|
70
|
+
# global options are %w[--git-dir /path/to/git/dir].
|
71
|
+
#
|
72
|
+
# @example
|
73
|
+
# env = {}
|
74
|
+
# global_opts = %w[--git-dir /path/to/git/dir]
|
75
|
+
# logger = Logger.new(nil)
|
76
|
+
# cli = CommandLine.new(env, '/usr/bin/git', global_opts, logger)
|
77
|
+
# cli.global_opts #=> %w[--git-dir /path/to/git/dir]
|
78
|
+
#
|
79
|
+
# @return [Array<String>]
|
80
|
+
#
|
81
|
+
attr_reader :global_opts
|
82
|
+
|
83
|
+
# @attribute [r] logger
|
84
|
+
#
|
85
|
+
# The logger to use for logging git commands and results
|
86
|
+
#
|
87
|
+
# @example
|
88
|
+
# env = {}
|
89
|
+
# global_opts = %w[]
|
90
|
+
# logger = Logger.new(STDOUT)
|
91
|
+
# cli = CommandLine.new(env, '/usr/bin/git', global_opts, logger)
|
92
|
+
# cli.logger == logger #=> true
|
93
|
+
#
|
94
|
+
# @return [Logger]
|
95
|
+
#
|
96
|
+
attr_reader :logger
|
97
|
+
|
98
|
+
# Execute a git command, wait for it to finish, and return the result
|
99
|
+
#
|
100
|
+
# NORMALIZATION
|
101
|
+
#
|
102
|
+
# The command output is returned as a Unicde string containing the binary output
|
103
|
+
# from the command. If the binary output is not valid UTF-8, the output will
|
104
|
+
# cause problems because the encoding will be invalid.
|
105
|
+
#
|
106
|
+
# Normalization is a process that trys to convert the binary output to a valid
|
107
|
+
# UTF-8 string. It uses the `rchardet` gem to detect the encoding of the binary
|
108
|
+
# output and then converts it to UTF-8.
|
109
|
+
#
|
110
|
+
# Normalization is not enabled by default. Pass `normalize: true` to Git::CommandLine#run
|
111
|
+
# to enable it. Normalization will only be performed on stdout and only if the `out:`` option
|
112
|
+
# is nil or is a StringIO object. If the out: option is set to a file or other IO object,
|
113
|
+
# the normalize option will be ignored.
|
114
|
+
#
|
115
|
+
# @example Run a command and return the output
|
116
|
+
# cli.run('version') #=> "git version 2.39.1\n"
|
117
|
+
#
|
118
|
+
# @example The args array should be splatted into the parameter list
|
119
|
+
# args = %w[log -n 1 --oneline]
|
120
|
+
# cli.run(*args) #=> "f5baa11 beginning of Ruby/Git project\n"
|
121
|
+
#
|
122
|
+
# @example Run a command and return the chomped output
|
123
|
+
# cli.run('version', chomp: true) #=> "git version 2.39.1"
|
124
|
+
#
|
125
|
+
# @example Run a command and without normalizing the output
|
126
|
+
# cli.run('version', normalize: false) #=> "git version 2.39.1\n"
|
127
|
+
#
|
128
|
+
# @example Capture stdout in a temporary file
|
129
|
+
# require 'tempfile'
|
130
|
+
# tempfile = Tempfile.create('git') do |file|
|
131
|
+
# cli.run('version', out: file)
|
132
|
+
# file.rewind
|
133
|
+
# file.read #=> "git version 2.39.1\n"
|
134
|
+
# end
|
135
|
+
#
|
136
|
+
# @example Capture stderr in a StringIO object
|
137
|
+
# require 'stringio'
|
138
|
+
# stderr = StringIO.new
|
139
|
+
# begin
|
140
|
+
# cli.run('log', 'nonexistent-branch', err: stderr)
|
141
|
+
# rescue Git::FailedError => e
|
142
|
+
# stderr.string #=> "unknown revision or path not in the working tree.\n"
|
143
|
+
# end
|
144
|
+
#
|
145
|
+
# @param args [Array<String>] the command line arguements to pass to git
|
146
|
+
#
|
147
|
+
# This array should be splatted into the parameter list.
|
148
|
+
#
|
149
|
+
# @param out [#write, nil] the object to write stdout to or nil to ignore stdout
|
150
|
+
#
|
151
|
+
# If this is a 'StringIO' object, then `stdout_writer.string` will be returned.
|
152
|
+
#
|
153
|
+
# In general, only specify a `stdout_writer` object when you want to redirect
|
154
|
+
# stdout to a file or some other object that responds to `#write`. The default
|
155
|
+
# behavior will return the output of the command.
|
156
|
+
#
|
157
|
+
# @param err [#write] the object to write stderr to or nil to ignore stderr
|
158
|
+
#
|
159
|
+
# If this is a 'StringIO' object and `merged_output` is `true`, then
|
160
|
+
# `stderr_writer.string` will be merged into the output returned by this method.
|
161
|
+
#
|
162
|
+
# @param normalize [Boolean] whether to normalize the output to a valid encoding
|
163
|
+
#
|
164
|
+
# @param chomp [Boolean] whether to chomp the output
|
165
|
+
#
|
166
|
+
# @param merge [Boolean] whether to merge stdout and stderr in the string returned
|
167
|
+
#
|
168
|
+
# @param chdir [String] the directory to run the command in
|
169
|
+
#
|
170
|
+
# @param timeout [Numeric, nil] the maximum seconds to wait for the command to complete
|
171
|
+
#
|
172
|
+
# If timeout is zero, the timeout will not be enforced.
|
173
|
+
#
|
174
|
+
# If the command times out, it is killed via a `SIGKILL` signal and `Git::TimeoutError` is raised.
|
175
|
+
#
|
176
|
+
# If the command does not respond to SIGKILL, it will hang this method.
|
177
|
+
#
|
178
|
+
# @return [Git::CommandLineResult] the output of the command
|
179
|
+
#
|
180
|
+
# This result of running the command.
|
181
|
+
#
|
182
|
+
# @raise [ArgumentError] if `args` is not an array of strings
|
183
|
+
#
|
184
|
+
# @raise [Git::SignaledError] if the command was terminated because of an uncaught signal
|
185
|
+
#
|
186
|
+
# @raise [Git::FailedError] if the command returned a non-zero exitstatus
|
187
|
+
#
|
188
|
+
# @raise [Git::ProcessIOError] if an exception was raised while collecting subprocess output
|
189
|
+
#
|
190
|
+
# @raise [Git::TimeoutError] if the command times out
|
191
|
+
#
|
192
|
+
def run(*args, out:, err:, normalize:, chomp:, merge:, chdir: nil, timeout: nil)
|
193
|
+
git_cmd = build_git_cmd(args)
|
194
|
+
out ||= StringIO.new
|
195
|
+
err ||= (merge ? out : StringIO.new)
|
196
|
+
status = execute(git_cmd, out, err, chdir: (chdir || :not_set), timeout: timeout)
|
197
|
+
|
198
|
+
process_result(git_cmd, status, out, err, normalize, chomp, timeout)
|
199
|
+
end
|
200
|
+
|
201
|
+
private
|
202
|
+
|
203
|
+
# Build the git command line from the available sources to send to `Process.spawn`
|
204
|
+
# @return [Array<String>]
|
205
|
+
# @api private
|
206
|
+
#
|
207
|
+
def build_git_cmd(args)
|
208
|
+
raise ArgumentError.new('The args array can not contain an array') if args.any? { |a| a.is_a?(Array) }
|
209
|
+
|
210
|
+
[binary_path, *global_opts, *args].map { |e| e.to_s }
|
211
|
+
end
|
212
|
+
|
213
|
+
# Determine the output to return in the `CommandLineResult`
|
214
|
+
#
|
215
|
+
# If the writer can return the output by calling `#string` (such as a StringIO),
|
216
|
+
# then return the result of normalizing the encoding and chomping the output
|
217
|
+
# as requested.
|
218
|
+
#
|
219
|
+
# If the writer does not support `#string`, then return nil. The output is
|
220
|
+
# assumed to be collected by the writer itself such as when the writer
|
221
|
+
# is a file instead of a StringIO.
|
222
|
+
#
|
223
|
+
# @param writer [#string] the writer to post-process
|
224
|
+
#
|
225
|
+
# @return [String, nil]
|
226
|
+
#
|
227
|
+
# @api private
|
228
|
+
#
|
229
|
+
def post_process(writer, normalize, chomp)
|
230
|
+
if writer.respond_to?(:string)
|
231
|
+
output = writer.string.dup
|
232
|
+
output = output.lines.map { |l| Git::EncodingUtils.normalize_encoding(l) }.join if normalize
|
233
|
+
output.chomp! if chomp
|
234
|
+
output
|
235
|
+
else
|
236
|
+
nil
|
237
|
+
end
|
238
|
+
end
|
239
|
+
|
240
|
+
# Post-process all writers and return an array of the results
|
241
|
+
#
|
242
|
+
# @param writers [Array<#write>] the writers to post-process
|
243
|
+
# @param normalize [Boolean] whether to normalize the output of each writer
|
244
|
+
# @param chomp [Boolean] whether to chomp the output of each writer
|
245
|
+
#
|
246
|
+
# @return [Array<String, nil>] the output of each writer that supports `#string`
|
247
|
+
#
|
248
|
+
# @api private
|
249
|
+
#
|
250
|
+
def post_process_all(writers, normalize, chomp)
|
251
|
+
Array.new.tap do |result|
|
252
|
+
writers.each { |writer| result << post_process(writer, normalize, chomp) }
|
253
|
+
end
|
254
|
+
end
|
255
|
+
|
256
|
+
# Raise an error when there was exception while collecting the subprocess output
|
257
|
+
#
|
258
|
+
# @param git_cmd [Array<String>] the git command that was executed
|
259
|
+
# @param pipe_name [Symbol] the name of the pipe that raised the exception
|
260
|
+
# @param pipe [ProcessExecuter::MonitoredPipe] the pipe that raised the exception
|
261
|
+
#
|
262
|
+
# @raise [Git::ProcessIOError]
|
263
|
+
#
|
264
|
+
# @return [void] this method always raises an error
|
265
|
+
#
|
266
|
+
# @api private
|
267
|
+
#
|
268
|
+
def raise_pipe_error(git_cmd, pipe_name, pipe)
|
269
|
+
raise Git::ProcessIOError.new("Pipe Exception for #{git_cmd}: #{pipe_name}"), cause: pipe.exception
|
270
|
+
end
|
271
|
+
|
272
|
+
# Execute the git command and collect the output
|
273
|
+
#
|
274
|
+
# @param cmd [Array<String>] the git command to execute
|
275
|
+
# @param chdir [String] the directory to run the command in
|
276
|
+
# @param timeout [Numeric, nil] the maximum seconds to wait for the command to complete
|
277
|
+
#
|
278
|
+
# If timeout is zero of nil, the command will not time out. If the command
|
279
|
+
# times out, it is killed via a SIGKILL signal and `Git::TimeoutError` is raised.
|
280
|
+
#
|
281
|
+
# If the command does not respond to SIGKILL, it will hang this method.
|
282
|
+
#
|
283
|
+
# @raise [Git::ProcessIOError] if an exception was raised while collecting subprocess output
|
284
|
+
# @raise [Git::TimeoutError] if the command times out
|
285
|
+
#
|
286
|
+
# @return [ProcessExecuter::Status] the status of the completed subprocess
|
287
|
+
#
|
288
|
+
# @api private
|
289
|
+
#
|
290
|
+
def spawn(cmd, out_writers, err_writers, chdir:, timeout:)
|
291
|
+
out_pipe = ProcessExecuter::MonitoredPipe.new(*out_writers, chunk_size: 10_000)
|
292
|
+
err_pipe = ProcessExecuter::MonitoredPipe.new(*err_writers, chunk_size: 10_000)
|
293
|
+
ProcessExecuter.spawn(env, *cmd, out: out_pipe, err: err_pipe, chdir: chdir, timeout: timeout)
|
294
|
+
ensure
|
295
|
+
out_pipe.close
|
296
|
+
err_pipe.close
|
297
|
+
raise_pipe_error(cmd, :stdout, out_pipe) if out_pipe.exception
|
298
|
+
raise_pipe_error(cmd, :stderr, err_pipe) if err_pipe.exception
|
299
|
+
end
|
300
|
+
|
301
|
+
# The writers that will be used to collect stdout and stderr
|
302
|
+
#
|
303
|
+
# Additional writers could be added here if you wanted to tee output
|
304
|
+
# or send output to the terminal.
|
305
|
+
#
|
306
|
+
# @param out [#write] the object to write stdout to
|
307
|
+
# @param err [#write] the object to write stderr to
|
308
|
+
#
|
309
|
+
# @return [Array<Array<#write>, Array<#write>>] the writers for stdout and stderr
|
310
|
+
#
|
311
|
+
# @api private
|
312
|
+
#
|
313
|
+
def writers(out, err)
|
314
|
+
out_writers = [out]
|
315
|
+
err_writers = [err]
|
316
|
+
[out_writers, err_writers]
|
317
|
+
end
|
318
|
+
|
319
|
+
# Process the result of the command and return a Git::CommandLineResult
|
320
|
+
#
|
321
|
+
# Post process output, log the command and result, and raise an error if the
|
322
|
+
# command failed.
|
323
|
+
#
|
324
|
+
# @param git_cmd [Array<String>] the git command that was executed
|
325
|
+
# @param status [Process::Status] the status of the completed subprocess
|
326
|
+
# @param out [#write] the object that stdout was written to
|
327
|
+
# @param err [#write] the object that stderr was written to
|
328
|
+
# @param normalize [Boolean] whether to normalize the output of each writer
|
329
|
+
# @param chomp [Boolean] whether to chomp the output of each writer
|
330
|
+
# @param timeout [Numeric, nil] the maximum seconds to wait for the command to complete
|
331
|
+
#
|
332
|
+
# @return [Git::CommandLineResult] the result of the command to return to the caller
|
333
|
+
#
|
334
|
+
# @raise [Git::FailedError] if the command failed
|
335
|
+
# @raise [Git::SignaledError] if the command was signaled
|
336
|
+
# @raise [Git::TimeoutError] if the command times out
|
337
|
+
# @raise [Git::ProcessIOError] if an exception was raised while collecting subprocess output
|
338
|
+
#
|
339
|
+
# @api private
|
340
|
+
#
|
341
|
+
def process_result(git_cmd, status, out, err, normalize, chomp, timeout)
|
342
|
+
out_str, err_str = post_process_all([out, err], normalize, chomp)
|
343
|
+
logger.info { "#{git_cmd} exited with status #{status}" }
|
344
|
+
logger.debug { "stdout:\n#{out_str.inspect}\nstderr:\n#{err_str.inspect}" }
|
345
|
+
Git::CommandLineResult.new(git_cmd, status, out_str, err_str).tap do |result|
|
346
|
+
raise Git::TimeoutError.new(result, timeout) if status.timeout?
|
347
|
+
raise Git::SignaledError.new(result) if status.signaled?
|
348
|
+
raise Git::FailedError.new(result) unless status.success?
|
349
|
+
end
|
350
|
+
end
|
351
|
+
|
352
|
+
# Execute the git command and write the command output to out and err
|
353
|
+
#
|
354
|
+
# @param git_cmd [Array<String>] the git command to execute
|
355
|
+
# @param out [#write] the object to write stdout to
|
356
|
+
# @param err [#write] the object to write stderr to
|
357
|
+
# @param chdir [String] the directory to run the command in
|
358
|
+
# @param timeout [Numeric, nil] the maximum seconds to wait for the command to complete
|
359
|
+
#
|
360
|
+
# If timeout is zero of nil, the command will not time out. If the command
|
361
|
+
# times out, it is killed via a SIGKILL signal and `Git::TimeoutError` is raised.
|
362
|
+
#
|
363
|
+
# If the command does not respond to SIGKILL, it will hang this method.
|
364
|
+
#
|
365
|
+
# @raise [Git::ProcessIOError] if an exception was raised while collecting subprocess output
|
366
|
+
# @raise [Git::TimeoutError] if the command times out
|
367
|
+
#
|
368
|
+
# @return [Git::CommandLineResult] the result of the command to return to the caller
|
369
|
+
#
|
370
|
+
# @api private
|
371
|
+
#
|
372
|
+
def execute(git_cmd, out, err, chdir:, timeout:)
|
373
|
+
out_writers, err_writers = writers(out, err)
|
374
|
+
spawn(git_cmd, out_writers, err_writers, chdir: chdir, timeout: timeout)
|
375
|
+
end
|
376
|
+
end
|
377
|
+
end
|
data/lib/git/config.rb
CHANGED
@@ -2,11 +2,12 @@ module Git
|
|
2
2
|
|
3
3
|
class Config
|
4
4
|
|
5
|
-
attr_writer :binary_path, :git_ssh
|
5
|
+
attr_writer :binary_path, :git_ssh, :timeout
|
6
6
|
|
7
7
|
def initialize
|
8
8
|
@binary_path = nil
|
9
9
|
@git_ssh = nil
|
10
|
+
@timeout = nil
|
10
11
|
end
|
11
12
|
|
12
13
|
def binary_path
|
@@ -17,6 +18,9 @@ module Git
|
|
17
18
|
@git_ssh || ENV['GIT_SSH']
|
18
19
|
end
|
19
20
|
|
21
|
+
def timeout
|
22
|
+
@timeout || (ENV['GIT_TIMEOUT'] && ENV['GIT_TIMEOUT'].to_i)
|
23
|
+
end
|
20
24
|
end
|
21
25
|
|
22
26
|
end
|