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/lib/git/errors.rb
ADDED
@@ -0,0 +1,206 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Git
|
4
|
+
# Base class for all custom git module errors
|
5
|
+
#
|
6
|
+
# The git gem will only raise an `ArgumentError` or an error that is a subclass of
|
7
|
+
# `Git::Error`. It does not explicitly raise any other types of errors.
|
8
|
+
#
|
9
|
+
# It is recommended to rescue `Git::Error` to catch any runtime error raised by
|
10
|
+
# this gem unless you need more specific error handling.
|
11
|
+
#
|
12
|
+
# Git's custom errors are arranged in the following class heirarchy:
|
13
|
+
#
|
14
|
+
# ```text
|
15
|
+
# StandardError
|
16
|
+
# └─> Git::Error
|
17
|
+
# ├─> Git::CommandLineError
|
18
|
+
# │ ├─> Git::FailedError
|
19
|
+
# │ └─> Git::SignaledError
|
20
|
+
# │ └─> Git::TimeoutError
|
21
|
+
# ├─> Git::ProcessIOError
|
22
|
+
# └─> Git::UnexpectedResultError
|
23
|
+
# ```
|
24
|
+
#
|
25
|
+
# | Error Class | Description |
|
26
|
+
# | --- | --- |
|
27
|
+
# | `Error` | This catch-all error serves as the base class for other custom errors raised by the git gem. |
|
28
|
+
# | `CommandLineError` | A subclass of this error is raised when there is a problem executing the git command line. |
|
29
|
+
# | `FailedError` | This error is raised when the git command line exits with a non-zero status code that is not expected by the git gem. |
|
30
|
+
# | `SignaledError` | This error is raised when the git command line is terminated as a result of receiving a signal. This could happen if the process is forcibly terminated or if there is a serious system error. |
|
31
|
+
# | `TimeoutError` | This is a specific type of `SignaledError` that is raised when the git command line operation times out and is killed via the SIGKILL signal. This happens if the operation takes longer than the timeout duration configured in `Git.config.timeout` or via the `:timeout` parameter given in git methods that support timeouts. |
|
32
|
+
# | `ProcessIOError` | An error was encountered reading or writing to a subprocess. |
|
33
|
+
# | `UnexpectedResultError` | The command line ran without error but did not return the expected results. |
|
34
|
+
#
|
35
|
+
# @example Rescuing a generic error
|
36
|
+
# begin
|
37
|
+
# # some git operation
|
38
|
+
# rescue Git::Error => e
|
39
|
+
# puts "An error occurred: #{e.message}"
|
40
|
+
# end
|
41
|
+
#
|
42
|
+
# @example Rescuing a timeout error
|
43
|
+
# begin
|
44
|
+
# timeout_duration = 0.001 # seconds
|
45
|
+
# repo = Git.clone('https://github.com/ruby-git/ruby-git', 'ruby-git-temp', timeout: timeout_duration)
|
46
|
+
# rescue Git::TimeoutError => e # Catch the more specific error first!
|
47
|
+
# puts "Git clone took too long and timed out #{e}"
|
48
|
+
# rescue Git::Error => e
|
49
|
+
# puts "Received the following error: #{e}"
|
50
|
+
# end
|
51
|
+
#
|
52
|
+
# @see Git::CommandLineError
|
53
|
+
# @see Git::FailedError
|
54
|
+
# @see Git::SignaledError
|
55
|
+
# @see Git::TimeoutError
|
56
|
+
# @see Git::ProcessIOError
|
57
|
+
# @see Git::UnexpectedResultError
|
58
|
+
#
|
59
|
+
# @api public
|
60
|
+
#
|
61
|
+
class Error < StandardError; end
|
62
|
+
|
63
|
+
# An alias for Git::Error
|
64
|
+
#
|
65
|
+
# Git::GitExecuteError error class is an alias for Git::Error for backwards
|
66
|
+
# compatibility. It is recommended to use Git::Error directly.
|
67
|
+
#
|
68
|
+
# @deprecated Use Git::Error instead
|
69
|
+
#
|
70
|
+
GitExecuteError = ActiveSupport::Deprecation::DeprecatedConstantProxy.new('Git::GitExecuteError', 'Git::Error', Git::Deprecation)
|
71
|
+
|
72
|
+
# Raised when a git command fails or exits because of an uncaught signal
|
73
|
+
#
|
74
|
+
# The git command executed, status, stdout, and stderr are available from this
|
75
|
+
# object.
|
76
|
+
#
|
77
|
+
# The Gem will raise a more specific error for each type of failure:
|
78
|
+
#
|
79
|
+
# * {Git::FailedError}: when the git command exits with a non-zero status
|
80
|
+
# * {Git::SignaledError}: when the git command exits because of an uncaught signal
|
81
|
+
# * {Git::TimeoutError}: when the git command times out
|
82
|
+
#
|
83
|
+
# @api public
|
84
|
+
#
|
85
|
+
class CommandLineError < Git::Error
|
86
|
+
# Create a CommandLineError object
|
87
|
+
#
|
88
|
+
# @example
|
89
|
+
# `exit 1` # set $? appropriately for this example
|
90
|
+
# result = Git::CommandLineResult.new(%w[git status], $?, 'stdout', 'stderr')
|
91
|
+
# error = Git::CommandLineError.new(result)
|
92
|
+
# error.to_s #=> '["git", "status"], status: pid 89784 exit 1, stderr: "stderr"'
|
93
|
+
#
|
94
|
+
# @param result [Git::CommandLineResult] the result of the git command including
|
95
|
+
# the git command, status, stdout, and stderr
|
96
|
+
#
|
97
|
+
def initialize(result)
|
98
|
+
@result = result
|
99
|
+
super(error_message)
|
100
|
+
end
|
101
|
+
|
102
|
+
# The human readable representation of this error
|
103
|
+
#
|
104
|
+
# @example
|
105
|
+
# error.error_message #=> '["git", "status"], status: pid 89784 exit 1, stderr: "stderr"'
|
106
|
+
#
|
107
|
+
# @return [String]
|
108
|
+
#
|
109
|
+
def error_message = <<~MESSAGE.chomp
|
110
|
+
#{result.git_cmd}, status: #{result.status}, stderr: #{result.stderr.inspect}
|
111
|
+
MESSAGE
|
112
|
+
|
113
|
+
# @attribute [r] result
|
114
|
+
#
|
115
|
+
# The result of the git command including the git command and its status and output
|
116
|
+
#
|
117
|
+
# @example
|
118
|
+
# error.result #=> #<Git::CommandLineResult:0x00000001046bd488 ...>
|
119
|
+
#
|
120
|
+
# @return [Git::CommandLineResult]
|
121
|
+
#
|
122
|
+
attr_reader :result
|
123
|
+
end
|
124
|
+
|
125
|
+
# This error is raised when a git command returns a non-zero exitstatus
|
126
|
+
#
|
127
|
+
# The git command executed, status, stdout, and stderr are available from this
|
128
|
+
# object.
|
129
|
+
#
|
130
|
+
# @api public
|
131
|
+
#
|
132
|
+
class FailedError < Git::CommandLineError; end
|
133
|
+
|
134
|
+
# This error is raised when a git command exits because of an uncaught signal
|
135
|
+
#
|
136
|
+
# @api public
|
137
|
+
#
|
138
|
+
class SignaledError < Git::CommandLineError; end
|
139
|
+
|
140
|
+
# This error is raised when a git command takes longer than the configured timeout
|
141
|
+
#
|
142
|
+
# The git command executed, status, stdout, and stderr, and the timeout duration
|
143
|
+
# are available from this object.
|
144
|
+
#
|
145
|
+
# result.status.timeout? will be `true`
|
146
|
+
#
|
147
|
+
# @api public
|
148
|
+
#
|
149
|
+
class TimeoutError < Git::SignaledError
|
150
|
+
# Create a TimeoutError object
|
151
|
+
#
|
152
|
+
# @example
|
153
|
+
# command = %w[sleep 10]
|
154
|
+
# timeout_duration = 1
|
155
|
+
# status = ProcessExecuter.spawn(*command, timeout: timeout_duration)
|
156
|
+
# result = Git::CommandLineResult.new(command, status, 'stdout', 'err output')
|
157
|
+
# error = Git::TimeoutError.new(result, timeout_duration)
|
158
|
+
# error.error_message #=> '["sleep", "10"], status: pid 70144 SIGKILL (signal 9), stderr: "err output", timed out after 1s'
|
159
|
+
#
|
160
|
+
# @param result [Git::CommandLineResult] the result of the git command including
|
161
|
+
# the git command, status, stdout, and stderr
|
162
|
+
#
|
163
|
+
# @param timeout_duration [Numeric] the amount of time the subprocess was allowed
|
164
|
+
# to run before being killed
|
165
|
+
#
|
166
|
+
def initialize(result, timeout_duration)
|
167
|
+
@timeout_duration = timeout_duration
|
168
|
+
super(result)
|
169
|
+
end
|
170
|
+
|
171
|
+
# The human readable representation of this error
|
172
|
+
#
|
173
|
+
# @example
|
174
|
+
# error.error_message #=> '["sleep", "10"], status: pid 88811 SIGKILL (signal 9), stderr: "err output", timed out after 1s'
|
175
|
+
#
|
176
|
+
# @return [String]
|
177
|
+
#
|
178
|
+
def error_message = <<~MESSAGE.chomp
|
179
|
+
#{super}, timed out after #{timeout_duration}s
|
180
|
+
MESSAGE
|
181
|
+
|
182
|
+
# The amount of time the subprocess was allowed to run before being killed
|
183
|
+
#
|
184
|
+
# @example
|
185
|
+
# `kill -9 $$` # set $? appropriately for this example
|
186
|
+
# result = Git::CommandLineResult.new(%w[git status], $?, '', "killed")
|
187
|
+
# error = Git::TimeoutError.new(result, 10)
|
188
|
+
# error.timeout_duration #=> 10
|
189
|
+
#
|
190
|
+
# @return [Numeric]
|
191
|
+
#
|
192
|
+
attr_reader :timeout_duration
|
193
|
+
end
|
194
|
+
|
195
|
+
# Raised when the output of a git command can not be read
|
196
|
+
#
|
197
|
+
# @api public
|
198
|
+
#
|
199
|
+
class ProcessIOError < Git::Error; end
|
200
|
+
|
201
|
+
# Raised when the git command result was not as expected
|
202
|
+
#
|
203
|
+
# @api public
|
204
|
+
#
|
205
|
+
class UnexpectedResultError < Git::Error; end
|
206
|
+
end
|
data/lib/git/lib.rb
CHANGED
@@ -1,14 +1,15 @@
|
|
1
|
-
require 'git/
|
1
|
+
require 'git/command_line'
|
2
|
+
require 'git/errors'
|
2
3
|
require 'logger'
|
4
|
+
require 'pp'
|
5
|
+
require 'process_executer'
|
6
|
+
require 'stringio'
|
3
7
|
require 'tempfile'
|
4
8
|
require 'zlib'
|
5
9
|
require 'open3'
|
6
10
|
|
7
11
|
module Git
|
8
12
|
class Lib
|
9
|
-
|
10
|
-
@@semaphore = Mutex.new
|
11
|
-
|
12
13
|
# The path to the Git working copy. The default is '"./.git"'.
|
13
14
|
#
|
14
15
|
# @return [Pathname] the path to the Git working copy.
|
@@ -79,22 +80,34 @@ module Git
|
|
79
80
|
command('init', *arr_opts)
|
80
81
|
end
|
81
82
|
|
82
|
-
#
|
83
|
+
# Clones a repository into a newly created directory
|
83
84
|
#
|
84
|
-
#
|
85
|
-
#
|
86
|
-
#
|
87
|
-
#
|
88
|
-
#
|
89
|
-
#
|
90
|
-
#
|
91
|
-
# :remote:: name of remote (rather than 'origin')
|
92
|
-
# :recursive:: after the clone is created, initialize all submodules within, using their default settings.
|
85
|
+
# @param [String] repository_url the URL of the repository to clone
|
86
|
+
# @param [String, nil] directory the directory to clone into
|
87
|
+
#
|
88
|
+
# If nil, the repository is cloned into a directory with the same name as
|
89
|
+
# the repository.
|
90
|
+
#
|
91
|
+
# @param [Hash] opts the options for this command
|
93
92
|
#
|
94
|
-
#
|
93
|
+
# @option opts [Boolean] :bare (false) if true, clone as a bare repository
|
94
|
+
# @option opts [String] :branch the branch to checkout
|
95
|
+
# @option opts [String, Array] :config one or more configuration options to set
|
96
|
+
# @option opts [Integer] :depth the number of commits back to pull
|
97
|
+
# @option opts [String] :filter specify partial clone
|
98
|
+
# @option opts [String] :mirror set up a mirror of the source repository
|
99
|
+
# @option opts [String] :origin the name of the remote
|
100
|
+
# @option opts [String] :path an optional prefix for the directory parameter
|
101
|
+
# @option opts [String] :remote the name of the remote
|
102
|
+
# @option opts [Boolean] :recursive after the clone is created, initialize all submodules within, using their default settings
|
103
|
+
# @option opts [Numeric, nil] :timeout the number of seconds to wait for the command to complete
|
104
|
+
#
|
105
|
+
# See {Git::Lib#command} for more information about :timeout
|
95
106
|
#
|
96
107
|
# @return [Hash] the options to pass to {Git::Base.new}
|
97
108
|
#
|
109
|
+
# @todo make this work with SSH password or auth_key
|
110
|
+
#
|
98
111
|
def clone(repository_url, directory, opts = {})
|
99
112
|
@path = opts[:path] || '.'
|
100
113
|
clone_dir = opts[:path] ? File.join(@path, directory) : directory
|
@@ -114,7 +127,7 @@ module Git
|
|
114
127
|
arr_opts << repository_url
|
115
128
|
arr_opts << clone_dir
|
116
129
|
|
117
|
-
command('clone', *arr_opts)
|
130
|
+
command('clone', *arr_opts, timeout: opts[:timeout])
|
118
131
|
|
119
132
|
return_base_opts_from_clone(clone_dir, opts)
|
120
133
|
end
|
@@ -142,7 +155,7 @@ module Git
|
|
142
155
|
match_data = output.match(%r{^ref: refs/heads/(?<default_branch>[^\t]+)\tHEAD$})
|
143
156
|
return match_data[:default_branch] if match_data
|
144
157
|
|
145
|
-
raise 'Unable to determine the default branch'
|
158
|
+
raise Git::UnexpectedResultError, 'Unable to determine the default branch'
|
146
159
|
end
|
147
160
|
|
148
161
|
## READ COMMANDS ##
|
@@ -337,7 +350,19 @@ module Git
|
|
337
350
|
end
|
338
351
|
|
339
352
|
def object_contents(sha, &block)
|
340
|
-
|
353
|
+
if block_given?
|
354
|
+
Tempfile.create do |file|
|
355
|
+
# If a block is given, write the output from the process to a temporary
|
356
|
+
# file and then yield the file to the block
|
357
|
+
#
|
358
|
+
command('cat-file', "-p", sha, out: file, err: file)
|
359
|
+
file.rewind
|
360
|
+
yield file
|
361
|
+
end
|
362
|
+
else
|
363
|
+
# If a block is not given, return stdout
|
364
|
+
command('cat-file', '-p', sha)
|
365
|
+
end
|
341
366
|
end
|
342
367
|
|
343
368
|
def ls_tree(sha)
|
@@ -395,7 +420,7 @@ module Git
|
|
395
420
|
def branches_all
|
396
421
|
command_lines('branch', '-a').map do |line|
|
397
422
|
match_data = line.match(BRANCH_LINE_REGEXP)
|
398
|
-
raise
|
423
|
+
raise Git::UnexpectedResultError, 'Unexpected branch line format' unless match_data
|
399
424
|
next nil if match_data[:not_a_branch] || match_data[:detached_ref]
|
400
425
|
[
|
401
426
|
match_data[:refname],
|
@@ -474,11 +499,15 @@ module Git
|
|
474
499
|
grep_opts.push('--', *opts[:path_limiter]) if opts[:path_limiter].is_a?(Array)
|
475
500
|
|
476
501
|
hsh = {}
|
477
|
-
|
478
|
-
|
479
|
-
|
480
|
-
|
502
|
+
begin
|
503
|
+
command_lines('grep', *grep_opts).each do |line|
|
504
|
+
if m = /(.*?)\:(\d+)\:(.*)/.match(line)
|
505
|
+
hsh[m[1]] ||= []
|
506
|
+
hsh[m[1]] << [m[2].to_i, m[3]]
|
507
|
+
end
|
481
508
|
end
|
509
|
+
rescue Git::FailedError => e
|
510
|
+
raise unless e.result.status.exitstatus == 1 && e.result.stderr == ''
|
482
511
|
end
|
483
512
|
hsh
|
484
513
|
end
|
@@ -865,16 +894,17 @@ module Git
|
|
865
894
|
|
866
895
|
def conflicts # :yields: file, your, their
|
867
896
|
self.unmerged.each do |f|
|
868
|
-
|
869
|
-
|
870
|
-
|
871
|
-
|
872
|
-
|
873
|
-
|
874
|
-
|
875
|
-
|
876
|
-
|
877
|
-
|
897
|
+
Tempfile.create("YOUR-#{File.basename(f)}") do |your|
|
898
|
+
command('show', ":2:#{f}", out: your)
|
899
|
+
your.close
|
900
|
+
|
901
|
+
Tempfile.create("THEIR-#{File.basename(f)}") do |their|
|
902
|
+
command('show', ":3:#{f}", out: their)
|
903
|
+
their.close
|
904
|
+
|
905
|
+
yield(f, your.path, their.path)
|
906
|
+
end
|
907
|
+
end
|
878
908
|
end
|
879
909
|
end
|
880
910
|
|
@@ -915,7 +945,7 @@ module Git
|
|
915
945
|
opts = opts.last.instance_of?(Hash) ? opts.last : {}
|
916
946
|
|
917
947
|
if (opts[:a] || opts[:annotate]) && !(opts[:m] || opts[:message])
|
918
|
-
raise
|
948
|
+
raise ArgumentError, 'Cannot create an annotated tag without a message.'
|
919
949
|
end
|
920
950
|
|
921
951
|
arr_opts = []
|
@@ -948,7 +978,7 @@ module Git
|
|
948
978
|
arr_opts << remote if remote
|
949
979
|
arr_opts << opts[:ref] if opts[:ref]
|
950
980
|
|
951
|
-
command('fetch', *arr_opts)
|
981
|
+
command('fetch', *arr_opts, merge: true)
|
952
982
|
end
|
953
983
|
|
954
984
|
def push(remote = nil, branch = nil, opts = nil)
|
@@ -988,10 +1018,11 @@ module Git
|
|
988
1018
|
end
|
989
1019
|
end
|
990
1020
|
|
991
|
-
def pull(remote = nil, branch = nil)
|
1021
|
+
def pull(remote = nil, branch = nil, opts = {})
|
992
1022
|
raise ArgumentError, "You must specify a remote if a branch is specified" if remote.nil? && !branch.nil?
|
993
1023
|
|
994
1024
|
arr_opts = []
|
1025
|
+
arr_opts << '--allow-unrelated-histories' if opts[:allow_unrelated_histories]
|
995
1026
|
arr_opts << remote if remote
|
996
1027
|
arr_opts << branch if branch
|
997
1028
|
command('pull', *arr_opts)
|
@@ -1001,7 +1032,13 @@ module Git
|
|
1001
1032
|
head = File.join(@git_dir, 'refs', 'tags', tag_name)
|
1002
1033
|
return File.read(head).chomp if File.exist?(head)
|
1003
1034
|
|
1004
|
-
|
1035
|
+
begin
|
1036
|
+
command('show-ref', '--tags', '-s', tag_name)
|
1037
|
+
rescue Git::FailedError => e
|
1038
|
+
raise unless e.result.status.exitstatus == 1 && e.result.stderr == ''
|
1039
|
+
|
1040
|
+
''
|
1041
|
+
end
|
1005
1042
|
end
|
1006
1043
|
|
1007
1044
|
def repack
|
@@ -1026,15 +1063,12 @@ module Git
|
|
1026
1063
|
|
1027
1064
|
def commit_tree(tree, opts = {})
|
1028
1065
|
opts[:message] ||= "commit tree #{tree}"
|
1029
|
-
t = Tempfile.new('commit-message')
|
1030
|
-
t.write(opts[:message])
|
1031
|
-
t.close
|
1032
|
-
|
1033
1066
|
arr_opts = []
|
1034
1067
|
arr_opts << tree
|
1035
1068
|
arr_opts << '-p' << opts[:parent] if opts[:parent]
|
1036
|
-
|
1037
|
-
|
1069
|
+
Array(opts[:parents]).each { |p| arr_opts << '-p' << p } if opts[:parents]
|
1070
|
+
arr_opts << '-m' << opts[:message]
|
1071
|
+
command('commit-tree', *arr_opts)
|
1038
1072
|
end
|
1039
1073
|
|
1040
1074
|
def update_ref(ref, commit)
|
@@ -1080,7 +1114,11 @@ module Git
|
|
1080
1114
|
arr_opts << "--remote=#{opts[:remote]}" if opts[:remote]
|
1081
1115
|
arr_opts << sha
|
1082
1116
|
arr_opts << '--' << opts[:path] if opts[:path]
|
1083
|
-
|
1117
|
+
|
1118
|
+
f = File.open(file, 'wb')
|
1119
|
+
command('archive', *arr_opts, out: f)
|
1120
|
+
f.close
|
1121
|
+
|
1084
1122
|
if opts[:add_gzip]
|
1085
1123
|
file_content = File.read(file)
|
1086
1124
|
Zlib::GzipWriter.open(file) do |gz|
|
@@ -1115,7 +1153,7 @@ module Git
|
|
1115
1153
|
end
|
1116
1154
|
|
1117
1155
|
def required_command_version
|
1118
|
-
[
|
1156
|
+
[2, 28]
|
1119
1157
|
end
|
1120
1158
|
|
1121
1159
|
def meets_required_version?
|
@@ -1133,11 +1171,6 @@ module Git
|
|
1133
1171
|
|
1134
1172
|
private
|
1135
1173
|
|
1136
|
-
# Systen ENV variables involved in the git commands.
|
1137
|
-
#
|
1138
|
-
# @return [<String>] the names of the EVN variables involved in the git commands
|
1139
|
-
ENV_VARIABLE_NAMES = ['GIT_DIR', 'GIT_WORK_TREE', 'GIT_INDEX_FILE', 'GIT_SSH']
|
1140
|
-
|
1141
1174
|
def command_lines(cmd, *opts, chdir: nil)
|
1142
1175
|
cmd_op = command(cmd, *opts, chdir: chdir)
|
1143
1176
|
if cmd_op.encoding.name != "UTF-8"
|
@@ -1148,84 +1181,82 @@ module Git
|
|
1148
1181
|
op.split("\n")
|
1149
1182
|
end
|
1150
1183
|
|
1151
|
-
|
1152
|
-
|
1153
|
-
|
1154
|
-
|
1155
|
-
|
1156
|
-
|
1184
|
+
def env_overrides
|
1185
|
+
{
|
1186
|
+
'GIT_DIR' => @git_dir,
|
1187
|
+
'GIT_WORK_TREE' => @git_work_dir,
|
1188
|
+
'GIT_INDEX_FILE' => @git_index_file,
|
1189
|
+
'GIT_SSH' => Git::Base.config.git_ssh
|
1190
|
+
}
|
1157
1191
|
end
|
1158
1192
|
|
1159
|
-
|
1160
|
-
|
1161
|
-
|
1162
|
-
|
1193
|
+
def global_opts
|
1194
|
+
Array.new.tap do |global_opts|
|
1195
|
+
global_opts << "--git-dir=#{@git_dir}" if !@git_dir.nil?
|
1196
|
+
global_opts << "--work-tree=#{@git_work_dir}" if !@git_work_dir.nil?
|
1197
|
+
global_opts << '-c' << 'core.quotePath=true'
|
1198
|
+
global_opts << '-c' << 'color.ui=false'
|
1163
1199
|
end
|
1164
1200
|
end
|
1165
1201
|
|
1166
|
-
|
1167
|
-
|
1168
|
-
|
1169
|
-
ENV['GIT_WORK_TREE'] = @git_work_dir
|
1170
|
-
ENV['GIT_INDEX_FILE'] = @git_index_file
|
1171
|
-
ENV['GIT_SSH'] = Git::Base.config.git_ssh
|
1202
|
+
def command_line
|
1203
|
+
@command_line ||=
|
1204
|
+
Git::CommandLine.new(env_overrides, Git::Base.config.binary_path, global_opts, @logger)
|
1172
1205
|
end
|
1173
1206
|
|
1174
|
-
# Runs a
|
1175
|
-
# It restores the ENV after execution.
|
1207
|
+
# Runs a git command and returns the output
|
1176
1208
|
#
|
1177
|
-
# @param [
|
1178
|
-
|
1179
|
-
|
1180
|
-
|
1181
|
-
|
1182
|
-
|
1183
|
-
|
1184
|
-
|
1185
|
-
|
1186
|
-
|
1187
|
-
|
1188
|
-
|
1189
|
-
|
1190
|
-
|
1191
|
-
|
1192
|
-
|
1193
|
-
|
1194
|
-
|
1195
|
-
|
1196
|
-
|
1197
|
-
|
1198
|
-
|
1199
|
-
|
1200
|
-
|
1201
|
-
|
1202
|
-
|
1203
|
-
|
1204
|
-
|
1205
|
-
|
1206
|
-
|
1207
|
-
|
1208
|
-
|
1209
|
-
|
1210
|
-
|
1211
|
-
|
1212
|
-
|
1213
|
-
|
1214
|
-
|
1215
|
-
|
1216
|
-
|
1217
|
-
|
1218
|
-
|
1219
|
-
|
1220
|
-
|
1221
|
-
|
1222
|
-
|
1223
|
-
|
1224
|
-
|
1225
|
-
|
1226
|
-
|
1227
|
-
|
1228
|
-
output
|
1209
|
+
# @param args [Array] the git command to run and its arguments
|
1210
|
+
#
|
1211
|
+
# This should exclude the 'git' command itself and global options.
|
1212
|
+
#
|
1213
|
+
# For example, to run `git log --pretty=oneline`, you would pass `['log',
|
1214
|
+
# '--pretty=oneline']`
|
1215
|
+
#
|
1216
|
+
# @param out [String, nil] the path to a file or an IO to write the command's
|
1217
|
+
# stdout to
|
1218
|
+
#
|
1219
|
+
# @param err [String, nil] the path to a file or an IO to write the command's
|
1220
|
+
# stdout to
|
1221
|
+
#
|
1222
|
+
# @param normalize [Boolean] true to normalize the output encoding
|
1223
|
+
#
|
1224
|
+
# @param chomp [Boolean] true to remove trailing newlines from the output
|
1225
|
+
#
|
1226
|
+
# @param merge [Boolean] true to merge stdout and stderr
|
1227
|
+
#
|
1228
|
+
# @param chdir [String, nil] the directory to run the command in
|
1229
|
+
#
|
1230
|
+
# @param timeout [Numeric, nil] the maximum seconds to wait for the command to complete
|
1231
|
+
#
|
1232
|
+
# If timeout is nil, the global timeout from {Git::Config} is used.
|
1233
|
+
#
|
1234
|
+
# If timeout is zero, the timeout will not be enforced.
|
1235
|
+
#
|
1236
|
+
# If the command times out, it is killed via a `SIGKILL` signal and `Git::TimeoutError` is raised.
|
1237
|
+
#
|
1238
|
+
# If the command does not respond to SIGKILL, it will hang this method.
|
1239
|
+
#
|
1240
|
+
# @see Git::CommandLine#run
|
1241
|
+
#
|
1242
|
+
# @return [String] the command's stdout (or merged stdout and stderr if `merge`
|
1243
|
+
# is true)
|
1244
|
+
#
|
1245
|
+
# @raise [Git::FailedError] if the command failed
|
1246
|
+
# @raise [Git::SignaledError] if the command was signaled
|
1247
|
+
# @raise [Git::TimeoutError] if the command times out
|
1248
|
+
# @raise [Git::ProcessIOError] if an exception was raised while collecting subprocess output
|
1249
|
+
#
|
1250
|
+
# The exception's `result` attribute is a {Git::CommandLineResult} which will
|
1251
|
+
# contain the result of the command including the exit status, stdout, and
|
1252
|
+
# stderr.
|
1253
|
+
#
|
1254
|
+
# @api private
|
1255
|
+
#
|
1256
|
+
def command(*args, out: nil, err: nil, normalize: true, chomp: true, merge: false, chdir: nil, timeout: nil)
|
1257
|
+
timeout = timeout || Git.config.timeout
|
1258
|
+
result = command_line.run(*args, out: out, err: err, normalize: normalize, chomp: chomp, merge: merge, chdir: chdir, timeout: timeout)
|
1259
|
+
result.stdout
|
1229
1260
|
end
|
1230
1261
|
|
1231
1262
|
# Takes the diff command line output (as Array) and parse it into a Hash
|
@@ -1291,38 +1322,5 @@ module Git
|
|
1291
1322
|
end
|
1292
1323
|
arr_opts
|
1293
1324
|
end
|
1294
|
-
|
1295
|
-
def run_command(git_cmd, chdir=nil, &block)
|
1296
|
-
block ||= Proc.new do |io|
|
1297
|
-
io.readlines.map { |l| Git::EncodingUtils.normalize_encoding(l) }.join
|
1298
|
-
end
|
1299
|
-
|
1300
|
-
opts = {}
|
1301
|
-
opts[:chdir] = File.expand_path(chdir) if chdir
|
1302
|
-
|
1303
|
-
Open3.popen2(git_cmd, opts) do |stdin, stdout, wait_thr|
|
1304
|
-
[block.call(stdout), wait_thr.value]
|
1305
|
-
end
|
1306
|
-
end
|
1307
|
-
|
1308
|
-
def escape(s)
|
1309
|
-
windows_platform? ? escape_for_windows(s) : escape_for_sh(s)
|
1310
|
-
end
|
1311
|
-
|
1312
|
-
def escape_for_sh(s)
|
1313
|
-
"'#{s && s.to_s.gsub('\'','\'"\'"\'')}'"
|
1314
|
-
end
|
1315
|
-
|
1316
|
-
def escape_for_windows(s)
|
1317
|
-
# Escape existing double quotes in s and then wrap the result with double quotes
|
1318
|
-
escaped_string = s.to_s.gsub('"','\\"')
|
1319
|
-
%Q{"#{escaped_string}"}
|
1320
|
-
end
|
1321
|
-
|
1322
|
-
def windows_platform?
|
1323
|
-
# Check if on Windows via RUBY_PLATFORM (CRuby) and RUBY_DESCRIPTION (JRuby)
|
1324
|
-
win_platform_regex = /mingw|mswin/
|
1325
|
-
RUBY_PLATFORM =~ win_platform_regex || RUBY_DESCRIPTION =~ win_platform_regex
|
1326
|
-
end
|
1327
1325
|
end
|
1328
1326
|
end
|