git 1.19.1 → 3.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.commitlintrc.yml +38 -0
- data/.github/pull_request_template.md +8 -0
- data/.github/workflows/continuous_integration.yml +19 -21
- data/.github/workflows/enforce_conventional_commits.yml +28 -0
- data/.github/workflows/experimental_continuous_integration.yml +50 -0
- data/.github/workflows/release.yml +52 -0
- data/.gitignore +3 -0
- data/.husky/commit-msg +1 -0
- data/.release-please-manifest.json +3 -0
- data/.yardopts +0 -1
- data/CHANGELOG.md +211 -0
- data/CONTRIBUTING.md +290 -102
- data/README.md +180 -62
- data/Rakefile +7 -0
- data/git.gemspec +11 -11
- data/lib/git/author.rb +3 -2
- data/lib/git/base.rb +222 -59
- data/lib/git/branch.rb +2 -0
- data/lib/git/branches.rb +15 -14
- data/lib/git/command_line.rb +287 -0
- data/lib/git/config.rb +7 -1
- data/lib/git/diff.rb +2 -0
- data/lib/git/errors.rb +206 -0
- data/lib/git/escaped_path.rb +1 -1
- data/lib/git/index.rb +2 -1
- data/lib/git/lib.rb +604 -234
- data/lib/git/log.rb +74 -6
- data/lib/git/object.rb +80 -76
- data/lib/git/path.rb +9 -8
- data/lib/git/remote.rb +2 -0
- data/lib/git/repository.rb +2 -0
- data/lib/git/stash.rb +7 -6
- data/lib/git/stashes.rb +11 -10
- data/lib/git/status.rb +135 -25
- data/lib/git/version.rb +3 -1
- data/lib/git/working_directory.rb +2 -0
- data/lib/git/worktree.rb +2 -0
- data/lib/git/worktrees.rb +2 -0
- data/lib/git.rb +21 -7
- data/package.json +10 -0
- data/release-please-config.json +36 -0
- metadata +52 -38
- data/.github/stale.yml +0 -25
- data/Dockerfile.changelog-rs +0 -12
- data/PULL_REQUEST_TEMPLATE.md +0 -9
- data/RELEASING.md +0 -70
- data/lib/git/base/factory.rb +0 -99
- 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/lib.rb
CHANGED
|
@@ -1,14 +1,17 @@
|
|
|
1
|
-
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'git/command_line'
|
|
4
|
+
require 'git/errors'
|
|
2
5
|
require 'logger'
|
|
6
|
+
require 'pp'
|
|
7
|
+
require 'process_executer'
|
|
8
|
+
require 'stringio'
|
|
3
9
|
require 'tempfile'
|
|
4
10
|
require 'zlib'
|
|
5
11
|
require 'open3'
|
|
6
12
|
|
|
7
13
|
module Git
|
|
8
14
|
class Lib
|
|
9
|
-
|
|
10
|
-
@@semaphore = Mutex.new
|
|
11
|
-
|
|
12
15
|
# The path to the Git working copy. The default is '"./.git"'.
|
|
13
16
|
#
|
|
14
17
|
# @return [Pathname] the path to the Git working copy.
|
|
@@ -37,14 +40,23 @@ module Git
|
|
|
37
40
|
|
|
38
41
|
# Create a new Git::Lib object
|
|
39
42
|
#
|
|
40
|
-
# @
|
|
41
|
-
#
|
|
43
|
+
# @overload initialize(base, logger)
|
|
44
|
+
#
|
|
45
|
+
# @param base [Hash] the hash containing paths to the Git working copy,
|
|
46
|
+
# the Git repository directory, and the Git index file.
|
|
42
47
|
#
|
|
43
|
-
#
|
|
48
|
+
# @option base [Pathname] :working_directory
|
|
49
|
+
# @option base [Pathname] :repository
|
|
50
|
+
# @option base [Pathname] :index
|
|
44
51
|
#
|
|
45
|
-
#
|
|
46
|
-
#
|
|
47
|
-
# @
|
|
52
|
+
# @param [Logger] logger
|
|
53
|
+
#
|
|
54
|
+
# @overload initialize(base, logger)
|
|
55
|
+
#
|
|
56
|
+
# @param base [#dir, #repo, #index] an object with methods to get the Git worktree (#dir),
|
|
57
|
+
# the Git repository directory (#repo), and the Git index file (#index).
|
|
58
|
+
#
|
|
59
|
+
# @param [Logger] logger
|
|
48
60
|
#
|
|
49
61
|
def initialize(base = nil, logger = nil)
|
|
50
62
|
@git_dir = nil
|
|
@@ -79,22 +91,34 @@ module Git
|
|
|
79
91
|
command('init', *arr_opts)
|
|
80
92
|
end
|
|
81
93
|
|
|
82
|
-
#
|
|
94
|
+
# Clones a repository into a newly created directory
|
|
83
95
|
#
|
|
84
|
-
#
|
|
85
|
-
#
|
|
86
|
-
# :branch:: name of branch to track (rather than 'master')
|
|
87
|
-
# :depth:: the number of commits back to pull
|
|
88
|
-
# :filter:: specify partial clone
|
|
89
|
-
# :origin:: name of remote (same as remote)
|
|
90
|
-
# :path:: directory where the repo will be cloned
|
|
91
|
-
# :remote:: name of remote (rather than 'origin')
|
|
92
|
-
# :recursive:: after the clone is created, initialize all submodules within, using their default settings.
|
|
96
|
+
# @param [String] repository_url the URL of the repository to clone
|
|
97
|
+
# @param [String, nil] directory the directory to clone into
|
|
93
98
|
#
|
|
94
|
-
#
|
|
99
|
+
# If nil, the repository is cloned into a directory with the same name as
|
|
100
|
+
# the repository.
|
|
101
|
+
#
|
|
102
|
+
# @param [Hash] opts the options for this command
|
|
103
|
+
#
|
|
104
|
+
# @option opts [Boolean] :bare (false) if true, clone as a bare repository
|
|
105
|
+
# @option opts [String] :branch the branch to checkout
|
|
106
|
+
# @option opts [String, Array] :config one or more configuration options to set
|
|
107
|
+
# @option opts [Integer] :depth the number of commits back to pull
|
|
108
|
+
# @option opts [String] :filter specify partial clone
|
|
109
|
+
# @option opts [String] :mirror set up a mirror of the source repository
|
|
110
|
+
# @option opts [String] :origin the name of the remote
|
|
111
|
+
# @option opts [String] :path an optional prefix for the directory parameter
|
|
112
|
+
# @option opts [String] :remote the name of the remote
|
|
113
|
+
# @option opts [Boolean] :recursive after the clone is created, initialize all submodules within, using their default settings
|
|
114
|
+
# @option opts [Numeric, nil] :timeout the number of seconds to wait for the command to complete
|
|
115
|
+
#
|
|
116
|
+
# See {Git::Lib#command} for more information about :timeout
|
|
95
117
|
#
|
|
96
118
|
# @return [Hash] the options to pass to {Git::Base.new}
|
|
97
119
|
#
|
|
120
|
+
# @todo make this work with SSH password or auth_key
|
|
121
|
+
#
|
|
98
122
|
def clone(repository_url, directory, opts = {})
|
|
99
123
|
@path = opts[:path] || '.'
|
|
100
124
|
clone_dir = opts[:path] ? File.join(@path, directory) : directory
|
|
@@ -102,7 +126,7 @@ module Git
|
|
|
102
126
|
arr_opts = []
|
|
103
127
|
arr_opts << '--bare' if opts[:bare]
|
|
104
128
|
arr_opts << '--branch' << opts[:branch] if opts[:branch]
|
|
105
|
-
arr_opts << '--depth' << opts[:depth].to_i if opts[:depth]
|
|
129
|
+
arr_opts << '--depth' << opts[:depth].to_i if opts[:depth]
|
|
106
130
|
arr_opts << '--filter' << opts[:filter] if opts[:filter]
|
|
107
131
|
Array(opts[:config]).each { |c| arr_opts << '--config' << c }
|
|
108
132
|
arr_opts << '--origin' << opts[:remote] || opts[:origin] if opts[:remote] || opts[:origin]
|
|
@@ -114,7 +138,7 @@ module Git
|
|
|
114
138
|
arr_opts << repository_url
|
|
115
139
|
arr_opts << clone_dir
|
|
116
140
|
|
|
117
|
-
command('clone', *arr_opts)
|
|
141
|
+
command('clone', *arr_opts, timeout: opts[:timeout])
|
|
118
142
|
|
|
119
143
|
return_base_opts_from_clone(clone_dir, opts)
|
|
120
144
|
end
|
|
@@ -142,32 +166,38 @@ module Git
|
|
|
142
166
|
match_data = output.match(%r{^ref: refs/heads/(?<default_branch>[^\t]+)\tHEAD$})
|
|
143
167
|
return match_data[:default_branch] if match_data
|
|
144
168
|
|
|
145
|
-
raise 'Unable to determine the default branch'
|
|
169
|
+
raise Git::UnexpectedResultError, 'Unable to determine the default branch'
|
|
146
170
|
end
|
|
147
171
|
|
|
148
172
|
## READ COMMANDS ##
|
|
149
173
|
|
|
174
|
+
# Finds most recent tag that is reachable from a commit
|
|
150
175
|
#
|
|
151
|
-
#
|
|
176
|
+
# @see https://git-scm.com/docs/git-describe git-describe
|
|
152
177
|
#
|
|
153
|
-
#
|
|
154
|
-
#
|
|
155
|
-
#
|
|
156
|
-
#
|
|
157
|
-
#
|
|
158
|
-
#
|
|
159
|
-
#
|
|
160
|
-
#
|
|
161
|
-
#
|
|
162
|
-
#
|
|
163
|
-
#
|
|
164
|
-
#
|
|
165
|
-
#
|
|
166
|
-
#
|
|
167
|
-
#
|
|
168
|
-
#
|
|
169
|
-
#
|
|
170
|
-
|
|
178
|
+
# @param commit_ish [String, nil] target commit sha or object name
|
|
179
|
+
#
|
|
180
|
+
# @param opts [Hash] the given options
|
|
181
|
+
#
|
|
182
|
+
# @option opts :all [Boolean]
|
|
183
|
+
# @option opts :tags [Boolean]
|
|
184
|
+
# @option opts :contains [Boolean]
|
|
185
|
+
# @option opts :debug [Boolean]
|
|
186
|
+
# @option opts :long [Boolean]
|
|
187
|
+
# @option opts :always [Boolean]
|
|
188
|
+
# @option opts :exact_match [Boolean]
|
|
189
|
+
# @option opts :dirty [true, String]
|
|
190
|
+
# @option opts :abbrev [String]
|
|
191
|
+
# @option opts :candidates [String]
|
|
192
|
+
# @option opts :match [String]
|
|
193
|
+
#
|
|
194
|
+
# @return [String] the tag name
|
|
195
|
+
#
|
|
196
|
+
# @raise [ArgumentError] if the commit_ish is a string starting with a hyphen
|
|
197
|
+
#
|
|
198
|
+
def describe(commit_ish = nil, opts = {})
|
|
199
|
+
assert_args_are_not_options('commit-ish object', commit_ish)
|
|
200
|
+
|
|
171
201
|
arr_opts = []
|
|
172
202
|
|
|
173
203
|
arr_opts << '--all' if opts[:all]
|
|
@@ -185,12 +215,42 @@ module Git
|
|
|
185
215
|
arr_opts << "--candidates=#{opts[:candidates]}" if opts[:candidates]
|
|
186
216
|
arr_opts << "--match=#{opts[:match]}" if opts[:match]
|
|
187
217
|
|
|
188
|
-
arr_opts <<
|
|
218
|
+
arr_opts << commit_ish if commit_ish
|
|
189
219
|
|
|
190
220
|
return command('describe', *arr_opts)
|
|
191
221
|
end
|
|
192
222
|
|
|
193
|
-
|
|
223
|
+
# Return the commits that are within the given revision range
|
|
224
|
+
#
|
|
225
|
+
# @see https://git-scm.com/docs/git-log git-log
|
|
226
|
+
#
|
|
227
|
+
# @param opts [Hash] the given options
|
|
228
|
+
#
|
|
229
|
+
# @option opts :count [Integer] the maximum number of commits to return (maps to max-count)
|
|
230
|
+
# @option opts :all [Boolean]
|
|
231
|
+
# @option opts :cherry [Boolean]
|
|
232
|
+
# @option opts :since [String]
|
|
233
|
+
# @option opts :until [String]
|
|
234
|
+
# @option opts :grep [String]
|
|
235
|
+
# @option opts :author [String]
|
|
236
|
+
# @option opts :between [Array<String>] an array of two commit-ish strings to specify a revision range
|
|
237
|
+
#
|
|
238
|
+
# Only :between or :object options can be used, not both.
|
|
239
|
+
#
|
|
240
|
+
# @option opts :object [String] the revision range for the git log command
|
|
241
|
+
#
|
|
242
|
+
# Only :between or :object options can be used, not both.
|
|
243
|
+
#
|
|
244
|
+
# @option opts :path_limiter [Array<String>, String] only include commits that impact files from the specified paths
|
|
245
|
+
#
|
|
246
|
+
# @return [Array<String>] the log output
|
|
247
|
+
#
|
|
248
|
+
# @raise [ArgumentError] if the resulting revision range is a string starting with a hyphen
|
|
249
|
+
#
|
|
250
|
+
def log_commits(opts = {})
|
|
251
|
+
assert_args_are_not_options('between', opts[:between]&.first)
|
|
252
|
+
assert_args_are_not_options('object', opts[:object])
|
|
253
|
+
|
|
194
254
|
arr_opts = log_common_options(opts)
|
|
195
255
|
|
|
196
256
|
arr_opts << '--pretty=oneline'
|
|
@@ -200,11 +260,53 @@ module Git
|
|
|
200
260
|
command_lines('log', *arr_opts).map { |l| l.split.first }
|
|
201
261
|
end
|
|
202
262
|
|
|
203
|
-
|
|
263
|
+
# Return the commits that are within the given revision range
|
|
264
|
+
#
|
|
265
|
+
# @see https://git-scm.com/docs/git-log git-log
|
|
266
|
+
#
|
|
267
|
+
# @param opts [Hash] the given options
|
|
268
|
+
#
|
|
269
|
+
# @option opts :count [Integer] the maximum number of commits to return (maps to max-count)
|
|
270
|
+
# @option opts :all [Boolean]
|
|
271
|
+
# @option opts :cherry [Boolean]
|
|
272
|
+
# @option opts :since [String]
|
|
273
|
+
# @option opts :until [String]
|
|
274
|
+
# @option opts :grep [String]
|
|
275
|
+
# @option opts :author [String]
|
|
276
|
+
# @option opts :between [Array<String>] an array of two commit-ish strings to specify a revision range
|
|
277
|
+
#
|
|
278
|
+
# Only :between or :object options can be used, not both.
|
|
279
|
+
#
|
|
280
|
+
# @option opts :object [String] the revision range for the git log command
|
|
281
|
+
#
|
|
282
|
+
# Only :between or :object options can be used, not both.
|
|
283
|
+
#
|
|
284
|
+
# @option opts :path_limiter [Array<String>, String] only include commits that impact files from the specified paths
|
|
285
|
+
# @option opts :skip [Integer]
|
|
286
|
+
#
|
|
287
|
+
# @return [Array<Hash>] the log output parsed into an array of hashs for each commit
|
|
288
|
+
#
|
|
289
|
+
# Each hash contains the following keys:
|
|
290
|
+
# * 'sha' [String] the commit sha
|
|
291
|
+
# * 'author' [String] the author of the commit
|
|
292
|
+
# * 'message' [String] the commit message
|
|
293
|
+
# * 'parent' [Array<String>] the commit shas of the parent commits
|
|
294
|
+
# * 'tree' [String] the tree sha
|
|
295
|
+
# * 'author' [String] the author of the commit and timestamp of when the changes were created
|
|
296
|
+
# * 'committer' [String] the committer of the commit and timestamp of when the commit was applied
|
|
297
|
+
# * 'merges' [Boolean] if truthy, only include merge commits (aka commits with 2 or more parents)
|
|
298
|
+
#
|
|
299
|
+
# @raise [ArgumentError] if the revision range (specified with :between or :object) is a string starting with a hyphen
|
|
300
|
+
#
|
|
301
|
+
def full_log_commits(opts = {})
|
|
302
|
+
assert_args_are_not_options('between', opts[:between]&.first)
|
|
303
|
+
assert_args_are_not_options('object', opts[:object])
|
|
304
|
+
|
|
204
305
|
arr_opts = log_common_options(opts)
|
|
205
306
|
|
|
206
307
|
arr_opts << '--pretty=raw'
|
|
207
308
|
arr_opts << "--skip=#{opts[:skip]}" if opts[:skip]
|
|
309
|
+
arr_opts << '--merges' if opts[:merges]
|
|
208
310
|
|
|
209
311
|
arr_opts += log_path_options(opts)
|
|
210
312
|
|
|
@@ -213,36 +315,147 @@ module Git
|
|
|
213
315
|
process_commit_log_data(full_log)
|
|
214
316
|
end
|
|
215
317
|
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
318
|
+
# Verify and resolve a Git revision to its full SHA
|
|
319
|
+
#
|
|
320
|
+
# @see https://git-scm.com/docs/git-rev-parse git-rev-parse
|
|
321
|
+
# @see https://git-scm.com/docs/git-rev-parse#_specifying_revisions Valid ways to specify revisions
|
|
322
|
+
# @see https://git-scm.com/docs/git-rev-parse#Documentation/git-rev-parse.txt-emltrefnamegtemegemmasterememheadsmasterememrefsheadsmasterem Ref disambiguation rules
|
|
323
|
+
#
|
|
324
|
+
# @example
|
|
325
|
+
# lib.rev_parse('HEAD') # => '9b9b31e704c0b85ffdd8d2af2ded85170a5af87d'
|
|
326
|
+
# lib.rev_parse('9b9b31e') # => '9b9b31e704c0b85ffdd8d2af2ded85170a5af87d'
|
|
327
|
+
#
|
|
328
|
+
# @param revision [String] the revision to resolve
|
|
329
|
+
#
|
|
330
|
+
# @return [String] the full commit hash
|
|
331
|
+
#
|
|
332
|
+
# @raise [Git::FailedError] if the revision cannot be resolved
|
|
333
|
+
# @raise [ArgumentError] if the revision is a string starting with a hyphen
|
|
334
|
+
#
|
|
335
|
+
def rev_parse(revision)
|
|
336
|
+
assert_args_are_not_options('rev', revision)
|
|
337
|
+
|
|
338
|
+
command('rev-parse', '--revs-only', '--end-of-options', revision, '--')
|
|
339
|
+
end
|
|
340
|
+
|
|
341
|
+
# For backwards compatibility with the old method name
|
|
342
|
+
alias :revparse :rev_parse
|
|
343
|
+
|
|
344
|
+
# Find the first symbolic name for given commit_ish
|
|
345
|
+
#
|
|
346
|
+
# @param commit_ish [String] the commit_ish to find the symbolic name of
|
|
347
|
+
#
|
|
348
|
+
# @return [String, nil] the first symbolic name or nil if the commit_ish isn't found
|
|
349
|
+
#
|
|
350
|
+
# @raise [ArgumentError] if the commit_ish is a string starting with a hyphen
|
|
351
|
+
#
|
|
352
|
+
def name_rev(commit_ish)
|
|
353
|
+
assert_args_are_not_options('commit_ish', commit_ish)
|
|
354
|
+
|
|
355
|
+
command('name-rev', commit_ish).split[1]
|
|
225
356
|
end
|
|
226
357
|
|
|
227
|
-
|
|
228
|
-
|
|
358
|
+
alias :namerev :name_rev
|
|
359
|
+
|
|
360
|
+
# Output the contents or other properties of one or more objects.
|
|
361
|
+
#
|
|
362
|
+
# @see https://git-scm.com/docs/git-cat-file git-cat-file
|
|
363
|
+
#
|
|
364
|
+
# @example Get the contents of a file without a block
|
|
365
|
+
# lib.cat_file_contents('README.md') # => "This is a README file\n"
|
|
366
|
+
#
|
|
367
|
+
# @example Get the contents of a file with a block
|
|
368
|
+
# lib.cat_file_contents('README.md') { |f| f.read } # => "This is a README file\n"
|
|
369
|
+
#
|
|
370
|
+
# @param object [String] the object whose contents to return
|
|
371
|
+
#
|
|
372
|
+
# @return [String] the object contents
|
|
373
|
+
#
|
|
374
|
+
# @raise [ArgumentError] if object is a string starting with a hyphen
|
|
375
|
+
#
|
|
376
|
+
def cat_file_contents(object, &block)
|
|
377
|
+
assert_args_are_not_options('object', object)
|
|
378
|
+
|
|
379
|
+
if block_given?
|
|
380
|
+
Tempfile.create do |file|
|
|
381
|
+
# If a block is given, write the output from the process to a temporary
|
|
382
|
+
# file and then yield the file to the block
|
|
383
|
+
#
|
|
384
|
+
command('cat-file', "-p", object, out: file, err: file)
|
|
385
|
+
file.rewind
|
|
386
|
+
yield file
|
|
387
|
+
end
|
|
388
|
+
else
|
|
389
|
+
# If a block is not given, return the file contents as a string
|
|
390
|
+
command('cat-file', '-p', object)
|
|
391
|
+
end
|
|
229
392
|
end
|
|
230
393
|
|
|
231
|
-
|
|
232
|
-
|
|
394
|
+
alias :object_contents :cat_file_contents
|
|
395
|
+
|
|
396
|
+
# Get the type for the given object
|
|
397
|
+
#
|
|
398
|
+
# @see https://git-scm.com/docs/git-cat-file git-cat-file
|
|
399
|
+
#
|
|
400
|
+
# @param object [String] the object to get the type
|
|
401
|
+
#
|
|
402
|
+
# @return [String] the object type
|
|
403
|
+
#
|
|
404
|
+
# @raise [ArgumentError] if object is a string starting with a hyphen
|
|
405
|
+
#
|
|
406
|
+
def cat_file_type(object)
|
|
407
|
+
assert_args_are_not_options('object', object)
|
|
408
|
+
|
|
409
|
+
command('cat-file', '-t', object)
|
|
233
410
|
end
|
|
234
411
|
|
|
235
|
-
|
|
236
|
-
|
|
412
|
+
alias :object_type :cat_file_type
|
|
413
|
+
|
|
414
|
+
# Get the size for the given object
|
|
415
|
+
#
|
|
416
|
+
# @see https://git-scm.com/docs/git-cat-file git-cat-file
|
|
417
|
+
#
|
|
418
|
+
# @param object [String] the object to get the type
|
|
419
|
+
#
|
|
420
|
+
# @return [String] the object type
|
|
421
|
+
#
|
|
422
|
+
# @raise [ArgumentError] if object is a string starting with a hyphen
|
|
423
|
+
#
|
|
424
|
+
def cat_file_size(object)
|
|
425
|
+
assert_args_are_not_options('object', object)
|
|
426
|
+
|
|
427
|
+
command('cat-file', '-s', object).to_i
|
|
237
428
|
end
|
|
238
429
|
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
430
|
+
alias :object_size :cat_file_size
|
|
431
|
+
|
|
432
|
+
# Return a hash of commit data
|
|
433
|
+
#
|
|
434
|
+
# @see https://git-scm.com/docs/git-cat-file git-cat-file
|
|
435
|
+
#
|
|
436
|
+
# @param object [String] the object to get the type
|
|
437
|
+
#
|
|
438
|
+
# @return [Hash] commit data
|
|
439
|
+
#
|
|
440
|
+
# The returned commit data has the following keys:
|
|
441
|
+
# * tree [String]
|
|
442
|
+
# * parent [Array<String>]
|
|
443
|
+
# * author [String] the author name, email, and commit timestamp
|
|
444
|
+
# * committer [String] the committer name, email, and merge timestamp
|
|
445
|
+
# * message [String] the commit message
|
|
446
|
+
# * gpgsig [String] the public signing key of the commit (if signed)
|
|
447
|
+
#
|
|
448
|
+
# @raise [ArgumentError] if object is a string starting with a hyphen
|
|
449
|
+
#
|
|
450
|
+
def cat_file_commit(object)
|
|
451
|
+
assert_args_are_not_options('object', object)
|
|
452
|
+
|
|
453
|
+
cdata = command_lines('cat-file', 'commit', object)
|
|
454
|
+
process_commit_data(cdata, object)
|
|
244
455
|
end
|
|
245
456
|
|
|
457
|
+
alias :commit_data :cat_file_commit
|
|
458
|
+
|
|
246
459
|
def process_commit_data(data, sha)
|
|
247
460
|
hsh = {
|
|
248
461
|
'sha' => sha,
|
|
@@ -277,12 +490,50 @@ module Git
|
|
|
277
490
|
end
|
|
278
491
|
end
|
|
279
492
|
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
493
|
+
# Return a hash of annotated tag data
|
|
494
|
+
#
|
|
495
|
+
# Does not work with lightweight tags. List all annotated tags in your repository with the following command:
|
|
496
|
+
#
|
|
497
|
+
# ```sh
|
|
498
|
+
# git for-each-ref --format='%(refname:strip=2)' refs/tags | while read tag; do git cat-file tag $tag >/dev/null 2>&1 && echo $tag; done
|
|
499
|
+
# ```
|
|
500
|
+
#
|
|
501
|
+
# @see https://git-scm.com/docs/git-cat-file git-cat-file
|
|
502
|
+
#
|
|
503
|
+
# @param object [String] the tag to retrieve
|
|
504
|
+
#
|
|
505
|
+
# @return [Hash] tag data
|
|
506
|
+
#
|
|
507
|
+
# Example tag data returned:
|
|
508
|
+
# ```ruby
|
|
509
|
+
# {
|
|
510
|
+
# "name" => "annotated_tag",
|
|
511
|
+
# "object" => "46abbf07e3c564c723c7c039a43ab3a39e5d02dd",
|
|
512
|
+
# "type" => "commit",
|
|
513
|
+
# "tag" => "annotated_tag",
|
|
514
|
+
# "tagger" => "Scott Chacon <schacon@gmail.com> 1724799270 -0700",
|
|
515
|
+
# "message" => "Creating an annotated tag\n"
|
|
516
|
+
# }
|
|
517
|
+
# ```
|
|
518
|
+
#
|
|
519
|
+
# The returned commit data has the following keys:
|
|
520
|
+
# * object [String] the sha of the tag object
|
|
521
|
+
# * type [String]
|
|
522
|
+
# * tag [String] tag name
|
|
523
|
+
# * tagger [String] the name and email of the user who created the tag and the timestamp of when the tag was created
|
|
524
|
+
# * message [String] the tag message
|
|
525
|
+
#
|
|
526
|
+
# @raise [ArgumentError] if object is a string starting with a hyphen
|
|
527
|
+
#
|
|
528
|
+
def cat_file_tag(object)
|
|
529
|
+
assert_args_are_not_options('object', object)
|
|
530
|
+
|
|
531
|
+
tdata = command_lines('cat-file', 'tag', object)
|
|
532
|
+
process_tag_data(tdata, object)
|
|
284
533
|
end
|
|
285
534
|
|
|
535
|
+
alias :tag_data :cat_file_tag
|
|
536
|
+
|
|
286
537
|
def process_tag_data(data, name)
|
|
287
538
|
hsh = { 'name' => name }
|
|
288
539
|
|
|
@@ -323,7 +574,7 @@ module Git
|
|
|
323
574
|
case key
|
|
324
575
|
when 'commit'
|
|
325
576
|
hsh_array << hsh if hsh
|
|
326
|
-
hsh = {'sha' => value, 'message' => '', 'parent' => []}
|
|
577
|
+
hsh = {'sha' => value, 'message' => +'', 'parent' => []}
|
|
327
578
|
when 'parent'
|
|
328
579
|
hsh['parent'] << value
|
|
329
580
|
else
|
|
@@ -336,14 +587,15 @@ module Git
|
|
|
336
587
|
return hsh_array
|
|
337
588
|
end
|
|
338
589
|
|
|
339
|
-
def
|
|
340
|
-
command('cat-file', '-p', sha, &block)
|
|
341
|
-
end
|
|
342
|
-
|
|
343
|
-
def ls_tree(sha)
|
|
590
|
+
def ls_tree(sha, opts = {})
|
|
344
591
|
data = { 'blob' => {}, 'tree' => {}, 'commit' => {} }
|
|
345
592
|
|
|
346
|
-
|
|
593
|
+
ls_tree_opts = []
|
|
594
|
+
ls_tree_opts << '-r' if opts[:recursive]
|
|
595
|
+
# path must be last arg
|
|
596
|
+
ls_tree_opts << opts[:path] if opts[:path]
|
|
597
|
+
|
|
598
|
+
command_lines('ls-tree', sha, *ls_tree_opts).each do |line|
|
|
347
599
|
(info, filenm) = line.split("\t")
|
|
348
600
|
(mode, type, sha) = info.split
|
|
349
601
|
data[type][filenm] = {:mode => mode, :sha => sha}
|
|
@@ -393,10 +645,13 @@ module Git
|
|
|
393
645
|
/x
|
|
394
646
|
|
|
395
647
|
def branches_all
|
|
396
|
-
command_lines('branch', '-a')
|
|
648
|
+
lines = command_lines('branch', '-a')
|
|
649
|
+
lines.each_with_index.map do |line, line_index|
|
|
397
650
|
match_data = line.match(BRANCH_LINE_REGEXP)
|
|
398
|
-
|
|
651
|
+
|
|
652
|
+
raise Git::UnexpectedResultError, unexpected_branch_line_error(lines, line, line_index) unless match_data
|
|
399
653
|
next nil if match_data[:not_a_branch] || match_data[:detached_ref]
|
|
654
|
+
|
|
400
655
|
[
|
|
401
656
|
match_data[:refname],
|
|
402
657
|
!match_data[:current].nil?,
|
|
@@ -406,6 +661,18 @@ module Git
|
|
|
406
661
|
end.compact
|
|
407
662
|
end
|
|
408
663
|
|
|
664
|
+
def unexpected_branch_line_error(lines, line, index)
|
|
665
|
+
<<~ERROR
|
|
666
|
+
Unexpected line in output from `git branch -a`, line #{index + 1}
|
|
667
|
+
|
|
668
|
+
Full output:
|
|
669
|
+
#{lines.join("\n ")}
|
|
670
|
+
|
|
671
|
+
Line #{index + 1}:
|
|
672
|
+
"#{line}"
|
|
673
|
+
ERROR
|
|
674
|
+
end
|
|
675
|
+
|
|
409
676
|
def worktrees_all
|
|
410
677
|
arr = []
|
|
411
678
|
directory = ''
|
|
@@ -449,8 +716,54 @@ module Git
|
|
|
449
716
|
files
|
|
450
717
|
end
|
|
451
718
|
|
|
719
|
+
# The state and name of branch pointed to by `HEAD`
|
|
720
|
+
#
|
|
721
|
+
# HEAD can be in the following states:
|
|
722
|
+
#
|
|
723
|
+
# **:active**: `HEAD` points to a branch reference which in turn points to a
|
|
724
|
+
# commit representing the tip of that branch. This is the typical state when
|
|
725
|
+
# working on a branch.
|
|
726
|
+
#
|
|
727
|
+
# **:unborn**: `HEAD` points to a branch reference that does not yet exist
|
|
728
|
+
# because no commits have been made on that branch. This state occurs in two
|
|
729
|
+
# scenarios:
|
|
730
|
+
#
|
|
731
|
+
# * When a repository is newly initialized, and no commits have been made on the
|
|
732
|
+
# initial branch.
|
|
733
|
+
# * When a new branch is created using `git checkout --orphan <branch>`, starting
|
|
734
|
+
# a new branch with no history.
|
|
735
|
+
#
|
|
736
|
+
# **:detached**: `HEAD` points directly to a specific commit (identified by its
|
|
737
|
+
# SHA) rather than a branch reference. This state occurs when you check out a
|
|
738
|
+
# commit, a tag, or any state that is not directly associated with a branch. The
|
|
739
|
+
# branch name in this case is `HEAD`.
|
|
740
|
+
#
|
|
741
|
+
HeadState = Struct.new(:state, :name)
|
|
742
|
+
|
|
743
|
+
# The current branch state which is the state of `HEAD`
|
|
744
|
+
#
|
|
745
|
+
# @return [HeadState] the state and name of the current branch
|
|
746
|
+
#
|
|
747
|
+
def current_branch_state
|
|
748
|
+
branch_name = command('branch', '--show-current')
|
|
749
|
+
return HeadState.new(:detached, 'HEAD') if branch_name.empty?
|
|
750
|
+
|
|
751
|
+
state =
|
|
752
|
+
begin
|
|
753
|
+
command('rev-parse', '--verify', '--quiet', branch_name)
|
|
754
|
+
:active
|
|
755
|
+
rescue Git::FailedError => e
|
|
756
|
+
raise unless e.result.status.exitstatus == 1 && e.result.stderr.empty?
|
|
757
|
+
|
|
758
|
+
:unborn
|
|
759
|
+
end
|
|
760
|
+
|
|
761
|
+
return HeadState.new(state, branch_name)
|
|
762
|
+
end
|
|
763
|
+
|
|
452
764
|
def branch_current
|
|
453
|
-
|
|
765
|
+
branch_name = command('branch', '--show-current')
|
|
766
|
+
branch_name.empty? ? 'HEAD' : branch_name
|
|
454
767
|
end
|
|
455
768
|
|
|
456
769
|
def branch_contains(commit, branch_name="")
|
|
@@ -474,16 +787,37 @@ module Git
|
|
|
474
787
|
grep_opts.push('--', *opts[:path_limiter]) if opts[:path_limiter].is_a?(Array)
|
|
475
788
|
|
|
476
789
|
hsh = {}
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
790
|
+
begin
|
|
791
|
+
command_lines('grep', *grep_opts).each do |line|
|
|
792
|
+
if m = /(.*?)\:(\d+)\:(.*)/.match(line)
|
|
793
|
+
hsh[m[1]] ||= []
|
|
794
|
+
hsh[m[1]] << [m[2].to_i, m[3]]
|
|
795
|
+
end
|
|
481
796
|
end
|
|
797
|
+
rescue Git::FailedError => e
|
|
798
|
+
raise unless e.result.status.exitstatus == 1 && e.result.stderr == ''
|
|
482
799
|
end
|
|
483
800
|
hsh
|
|
484
801
|
end
|
|
485
802
|
|
|
803
|
+
# Validate that the given arguments cannot be mistaken for a command-line option
|
|
804
|
+
#
|
|
805
|
+
# @param arg_name [String] the name of the arguments to mention in the error message
|
|
806
|
+
# @param args [Array<String, nil>] the arguments to validate
|
|
807
|
+
#
|
|
808
|
+
# @raise [ArgumentError] if any of the parameters are a string starting with a hyphen
|
|
809
|
+
# @return [void]
|
|
810
|
+
#
|
|
811
|
+
def assert_args_are_not_options(arg_name, *args)
|
|
812
|
+
invalid_args = args.select { |arg| arg&.start_with?('-') }
|
|
813
|
+
if invalid_args.any?
|
|
814
|
+
raise ArgumentError, "Invalid #{arg_name}: '#{invalid_args.join("', '")}'"
|
|
815
|
+
end
|
|
816
|
+
end
|
|
817
|
+
|
|
486
818
|
def diff_full(obj1 = 'HEAD', obj2 = nil, opts = {})
|
|
819
|
+
assert_args_are_not_options('commit or commit range', obj1, obj2)
|
|
820
|
+
|
|
487
821
|
diff_opts = ['-p']
|
|
488
822
|
diff_opts << obj1
|
|
489
823
|
diff_opts << obj2 if obj2.is_a?(String)
|
|
@@ -493,6 +827,8 @@ module Git
|
|
|
493
827
|
end
|
|
494
828
|
|
|
495
829
|
def diff_stats(obj1 = 'HEAD', obj2 = nil, opts = {})
|
|
830
|
+
assert_args_are_not_options('commit or commit range', obj1, obj2)
|
|
831
|
+
|
|
496
832
|
diff_opts = ['--numstat']
|
|
497
833
|
diff_opts << obj1
|
|
498
834
|
diff_opts << obj2 if obj2.is_a?(String)
|
|
@@ -513,6 +849,8 @@ module Git
|
|
|
513
849
|
end
|
|
514
850
|
|
|
515
851
|
def diff_name_status(reference1 = nil, reference2 = nil, opts = {})
|
|
852
|
+
assert_args_are_not_options('commit or commit range', reference1, reference2)
|
|
853
|
+
|
|
516
854
|
opts_arr = ['--name-status']
|
|
517
855
|
opts_arr << reference1 if reference1
|
|
518
856
|
opts_arr << reference2 if reference2
|
|
@@ -536,18 +874,52 @@ module Git
|
|
|
536
874
|
diff_as_hash('diff-index', treeish)
|
|
537
875
|
end
|
|
538
876
|
|
|
877
|
+
# List all files that are in the index
|
|
878
|
+
#
|
|
879
|
+
# @param location [String] the location to list the files from
|
|
880
|
+
#
|
|
881
|
+
# @return [Hash<String, Hash>] a hash of files in the index
|
|
882
|
+
# * key: file [String] the file path
|
|
883
|
+
# * value: file_info [Hash] the file information containing the following keys:
|
|
884
|
+
# * :path [String] the file path
|
|
885
|
+
# * :mode_index [String] the file mode
|
|
886
|
+
# * :sha_index [String] the file sha
|
|
887
|
+
# * :stage [String] the file stage
|
|
888
|
+
#
|
|
539
889
|
def ls_files(location=nil)
|
|
540
890
|
location ||= '.'
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
891
|
+
{}.tap do |files|
|
|
892
|
+
command_lines('ls-files', '--stage', location).each do |line|
|
|
893
|
+
(info, file) = line.split("\t")
|
|
894
|
+
(mode, sha, stage) = info.split
|
|
895
|
+
files[unescape_quoted_path(file)] = {
|
|
896
|
+
:path => file, :mode_index => mode, :sha_index => sha, :stage => stage
|
|
897
|
+
}
|
|
547
898
|
end
|
|
548
|
-
hsh[file] = {:path => file, :mode_index => mode, :sha_index => sha, :stage => stage}
|
|
549
899
|
end
|
|
550
|
-
|
|
900
|
+
end
|
|
901
|
+
|
|
902
|
+
# Unescape a path if it is quoted
|
|
903
|
+
#
|
|
904
|
+
# Git commands that output paths (e.g. ls-files, diff), will escape unusual
|
|
905
|
+
# characters.
|
|
906
|
+
#
|
|
907
|
+
# @example
|
|
908
|
+
# lib.unescape_if_quoted('"quoted_file_\\342\\230\\240"') # => 'quoted_file_☠'
|
|
909
|
+
# lib.unescape_if_quoted('unquoted_file') # => 'unquoted_file'
|
|
910
|
+
#
|
|
911
|
+
# @param path [String] the path to unescape if quoted
|
|
912
|
+
#
|
|
913
|
+
# @return [String] the unescaped path if quoted otherwise the original path
|
|
914
|
+
#
|
|
915
|
+
# @api private
|
|
916
|
+
#
|
|
917
|
+
def unescape_quoted_path(path)
|
|
918
|
+
if path.start_with?('"') && path.end_with?('"')
|
|
919
|
+
Git::EscapedPath.new(path[1..-2]).unescape
|
|
920
|
+
else
|
|
921
|
+
path
|
|
922
|
+
end
|
|
551
923
|
end
|
|
552
924
|
|
|
553
925
|
def ls_remote(location=nil, opts={})
|
|
@@ -568,9 +940,12 @@ module Git
|
|
|
568
940
|
end
|
|
569
941
|
|
|
570
942
|
def ignored_files
|
|
571
|
-
command_lines('ls-files', '--others', '-i', '--exclude-standard')
|
|
943
|
+
command_lines('ls-files', '--others', '-i', '--exclude-standard').map { |f| unescape_quoted_path(f) }
|
|
572
944
|
end
|
|
573
945
|
|
|
946
|
+
def untracked_files
|
|
947
|
+
command_lines('ls-files', '--others', '--exclude-standard', chdir: @git_work_dir)
|
|
948
|
+
end
|
|
574
949
|
|
|
575
950
|
def config_remote(name)
|
|
576
951
|
hsh = {}
|
|
@@ -638,18 +1013,20 @@ module Git
|
|
|
638
1013
|
command('config', '--global', name, value)
|
|
639
1014
|
end
|
|
640
1015
|
|
|
641
|
-
|
|
642
|
-
#
|
|
643
|
-
# lib.add('path/to/file')
|
|
644
|
-
# lib.add(['path/to/file1','path/to/file2'])
|
|
645
|
-
# lib.add(:all => true)
|
|
1016
|
+
|
|
1017
|
+
# Update the index from the current worktree to prepare the for the next commit
|
|
646
1018
|
#
|
|
647
|
-
#
|
|
648
|
-
#
|
|
649
|
-
#
|
|
1019
|
+
# @example
|
|
1020
|
+
# lib.add('path/to/file')
|
|
1021
|
+
# lib.add(['path/to/file1','path/to/file2'])
|
|
1022
|
+
# lib.add(:all => true)
|
|
650
1023
|
#
|
|
651
|
-
# @param [String,Array] paths files
|
|
1024
|
+
# @param [String, Array<String>] paths files to be added to the repository (relative to the worktree root)
|
|
652
1025
|
# @param [Hash] options
|
|
1026
|
+
#
|
|
1027
|
+
# @option options [Boolean] :all Add, modify, and remove index entries to match the worktree
|
|
1028
|
+
# @option options [Boolean] :force Allow adding otherwise ignored files
|
|
1029
|
+
#
|
|
653
1030
|
def add(paths='.',options={})
|
|
654
1031
|
arr_opts = []
|
|
655
1032
|
|
|
@@ -675,6 +1052,19 @@ module Git
|
|
|
675
1052
|
command('rm', *arr_opts)
|
|
676
1053
|
end
|
|
677
1054
|
|
|
1055
|
+
# Returns true if the repository is empty (meaning it has no commits)
|
|
1056
|
+
#
|
|
1057
|
+
# @return [Boolean]
|
|
1058
|
+
#
|
|
1059
|
+
def empty?
|
|
1060
|
+
command('rev-parse', '--verify', 'HEAD')
|
|
1061
|
+
false
|
|
1062
|
+
rescue Git::FailedError => e
|
|
1063
|
+
raise unless e.result.status.exitstatus == 128 &&
|
|
1064
|
+
e.result.stderr == 'fatal: Needed a single revision'
|
|
1065
|
+
true
|
|
1066
|
+
end
|
|
1067
|
+
|
|
678
1068
|
# Takes the commit message with the options and executes the commit command
|
|
679
1069
|
#
|
|
680
1070
|
# accepts options:
|
|
@@ -763,8 +1153,10 @@ module Git
|
|
|
763
1153
|
if File.exist?(filename)
|
|
764
1154
|
File.open(filename) do |f|
|
|
765
1155
|
f.each_with_index do |line, i|
|
|
766
|
-
|
|
767
|
-
|
|
1156
|
+
_, msg = line.split("\t")
|
|
1157
|
+
# NOTE this logic may be removed/changed in 3.x
|
|
1158
|
+
m = msg.match(/^[^:]+:(.*)$/)
|
|
1159
|
+
arr << [i, (m ? m[1] : msg).strip]
|
|
768
1160
|
end
|
|
769
1161
|
end
|
|
770
1162
|
end
|
|
@@ -865,16 +1257,17 @@ module Git
|
|
|
865
1257
|
|
|
866
1258
|
def conflicts # :yields: file, your, their
|
|
867
1259
|
self.unmerged.each do |f|
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
1260
|
+
Tempfile.create("YOUR-#{File.basename(f)}") do |your|
|
|
1261
|
+
command('show', ":2:#{f}", out: your)
|
|
1262
|
+
your.close
|
|
1263
|
+
|
|
1264
|
+
Tempfile.create("THEIR-#{File.basename(f)}") do |their|
|
|
1265
|
+
command('show', ":3:#{f}", out: their)
|
|
1266
|
+
their.close
|
|
1267
|
+
|
|
1268
|
+
yield(f, your.path, their.path)
|
|
1269
|
+
end
|
|
1270
|
+
end
|
|
878
1271
|
end
|
|
879
1272
|
end
|
|
880
1273
|
|
|
@@ -915,7 +1308,7 @@ module Git
|
|
|
915
1308
|
opts = opts.last.instance_of?(Hash) ? opts.last : {}
|
|
916
1309
|
|
|
917
1310
|
if (opts[:a] || opts[:annotate]) && !(opts[:m] || opts[:message])
|
|
918
|
-
raise
|
|
1311
|
+
raise ArgumentError, 'Cannot create an annotated tag without a message.'
|
|
919
1312
|
end
|
|
920
1313
|
|
|
921
1314
|
arr_opts = []
|
|
@@ -948,7 +1341,7 @@ module Git
|
|
|
948
1341
|
arr_opts << remote if remote
|
|
949
1342
|
arr_opts << opts[:ref] if opts[:ref]
|
|
950
1343
|
|
|
951
|
-
command('fetch', *arr_opts)
|
|
1344
|
+
command('fetch', *arr_opts, merge: true)
|
|
952
1345
|
end
|
|
953
1346
|
|
|
954
1347
|
def push(remote = nil, branch = nil, opts = nil)
|
|
@@ -988,10 +1381,11 @@ module Git
|
|
|
988
1381
|
end
|
|
989
1382
|
end
|
|
990
1383
|
|
|
991
|
-
def pull(remote = nil, branch = nil)
|
|
1384
|
+
def pull(remote = nil, branch = nil, opts = {})
|
|
992
1385
|
raise ArgumentError, "You must specify a remote if a branch is specified" if remote.nil? && !branch.nil?
|
|
993
1386
|
|
|
994
1387
|
arr_opts = []
|
|
1388
|
+
arr_opts << '--allow-unrelated-histories' if opts[:allow_unrelated_histories]
|
|
995
1389
|
arr_opts << remote if remote
|
|
996
1390
|
arr_opts << branch if branch
|
|
997
1391
|
command('pull', *arr_opts)
|
|
@@ -1001,7 +1395,13 @@ module Git
|
|
|
1001
1395
|
head = File.join(@git_dir, 'refs', 'tags', tag_name)
|
|
1002
1396
|
return File.read(head).chomp if File.exist?(head)
|
|
1003
1397
|
|
|
1004
|
-
|
|
1398
|
+
begin
|
|
1399
|
+
command('show-ref', '--tags', '-s', tag_name)
|
|
1400
|
+
rescue Git::FailedError => e
|
|
1401
|
+
raise unless e.result.status.exitstatus == 1 && e.result.stderr == ''
|
|
1402
|
+
|
|
1403
|
+
''
|
|
1404
|
+
end
|
|
1005
1405
|
end
|
|
1006
1406
|
|
|
1007
1407
|
def repack
|
|
@@ -1026,15 +1426,12 @@ module Git
|
|
|
1026
1426
|
|
|
1027
1427
|
def commit_tree(tree, opts = {})
|
|
1028
1428
|
opts[:message] ||= "commit tree #{tree}"
|
|
1029
|
-
t = Tempfile.new('commit-message')
|
|
1030
|
-
t.write(opts[:message])
|
|
1031
|
-
t.close
|
|
1032
|
-
|
|
1033
1429
|
arr_opts = []
|
|
1034
1430
|
arr_opts << tree
|
|
1035
1431
|
arr_opts << '-p' << opts[:parent] if opts[:parent]
|
|
1036
|
-
|
|
1037
|
-
|
|
1432
|
+
Array(opts[:parents]).each { |p| arr_opts << '-p' << p } if opts[:parents]
|
|
1433
|
+
arr_opts << '-m' << opts[:message]
|
|
1434
|
+
command('commit-tree', *arr_opts)
|
|
1038
1435
|
end
|
|
1039
1436
|
|
|
1040
1437
|
def update_ref(ref, commit)
|
|
@@ -1080,7 +1477,11 @@ module Git
|
|
|
1080
1477
|
arr_opts << "--remote=#{opts[:remote]}" if opts[:remote]
|
|
1081
1478
|
arr_opts << sha
|
|
1082
1479
|
arr_opts << '--' << opts[:path] if opts[:path]
|
|
1083
|
-
|
|
1480
|
+
|
|
1481
|
+
f = File.open(file, 'wb')
|
|
1482
|
+
command('archive', *arr_opts, out: f)
|
|
1483
|
+
f.close
|
|
1484
|
+
|
|
1084
1485
|
if opts[:add_gzip]
|
|
1085
1486
|
file_content = File.read(file)
|
|
1086
1487
|
Zlib::GzipWriter.open(file) do |gz|
|
|
@@ -1115,7 +1516,7 @@ module Git
|
|
|
1115
1516
|
end
|
|
1116
1517
|
|
|
1117
1518
|
def required_command_version
|
|
1118
|
-
[
|
|
1519
|
+
[2, 28]
|
|
1119
1520
|
end
|
|
1120
1521
|
|
|
1121
1522
|
def meets_required_version?
|
|
@@ -1133,11 +1534,6 @@ module Git
|
|
|
1133
1534
|
|
|
1134
1535
|
private
|
|
1135
1536
|
|
|
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
1537
|
def command_lines(cmd, *opts, chdir: nil)
|
|
1142
1538
|
cmd_op = command(cmd, *opts, chdir: chdir)
|
|
1143
1539
|
if cmd_op.encoding.name != "UTF-8"
|
|
@@ -1148,84 +1544,91 @@ module Git
|
|
|
1148
1544
|
op.split("\n")
|
|
1149
1545
|
end
|
|
1150
1546
|
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1547
|
+
def env_overrides
|
|
1548
|
+
{
|
|
1549
|
+
'GIT_DIR' => @git_dir,
|
|
1550
|
+
'GIT_WORK_TREE' => @git_work_dir,
|
|
1551
|
+
'GIT_INDEX_FILE' => @git_index_file,
|
|
1552
|
+
'GIT_SSH' => Git::Base.config.git_ssh,
|
|
1553
|
+
'LC_ALL' => 'en_US.UTF-8'
|
|
1554
|
+
}
|
|
1157
1555
|
end
|
|
1158
1556
|
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1557
|
+
def global_opts
|
|
1558
|
+
Array.new.tap do |global_opts|
|
|
1559
|
+
global_opts << "--git-dir=#{@git_dir}" if !@git_dir.nil?
|
|
1560
|
+
global_opts << "--work-tree=#{@git_work_dir}" if !@git_work_dir.nil?
|
|
1561
|
+
global_opts << '-c' << 'core.quotePath=true'
|
|
1562
|
+
global_opts << '-c' << 'color.ui=false'
|
|
1563
|
+
global_opts << '-c' << 'color.advice=false'
|
|
1564
|
+
global_opts << '-c' << 'color.diff=false'
|
|
1565
|
+
global_opts << '-c' << 'color.grep=false'
|
|
1566
|
+
global_opts << '-c' << 'color.push=false'
|
|
1567
|
+
global_opts << '-c' << 'color.remote=false'
|
|
1568
|
+
global_opts << '-c' << 'color.showBranch=false'
|
|
1569
|
+
global_opts << '-c' << 'color.status=false'
|
|
1570
|
+
global_opts << '-c' << 'color.transport=false'
|
|
1163
1571
|
end
|
|
1164
1572
|
end
|
|
1165
1573
|
|
|
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
|
|
1574
|
+
def command_line
|
|
1575
|
+
@command_line ||=
|
|
1576
|
+
Git::CommandLine.new(env_overrides, Git::Base.config.binary_path, global_opts, @logger)
|
|
1172
1577
|
end
|
|
1173
1578
|
|
|
1174
|
-
# Runs a
|
|
1175
|
-
# It restores the ENV after execution.
|
|
1579
|
+
# Runs a git command and returns the output
|
|
1176
1580
|
#
|
|
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
|
|
1581
|
+
# @param args [Array] the git command to run and its arguments
|
|
1582
|
+
#
|
|
1583
|
+
# This should exclude the 'git' command itself and global options.
|
|
1584
|
+
#
|
|
1585
|
+
# For example, to run `git log --pretty=oneline`, you would pass `['log',
|
|
1586
|
+
# '--pretty=oneline']`
|
|
1587
|
+
#
|
|
1588
|
+
# @param out [String, nil] the path to a file or an IO to write the command's
|
|
1589
|
+
# stdout to
|
|
1590
|
+
#
|
|
1591
|
+
# @param err [String, nil] the path to a file or an IO to write the command's
|
|
1592
|
+
# stdout to
|
|
1593
|
+
#
|
|
1594
|
+
# @param normalize [Boolean] true to normalize the output encoding
|
|
1595
|
+
#
|
|
1596
|
+
# @param chomp [Boolean] true to remove trailing newlines from the output
|
|
1597
|
+
#
|
|
1598
|
+
# @param merge [Boolean] true to merge stdout and stderr
|
|
1599
|
+
#
|
|
1600
|
+
# @param chdir [String, nil] the directory to run the command in
|
|
1601
|
+
#
|
|
1602
|
+
# @param timeout [Numeric, nil] the maximum seconds to wait for the command to complete
|
|
1603
|
+
#
|
|
1604
|
+
# If timeout is nil, the global timeout from {Git::Config} is used.
|
|
1605
|
+
#
|
|
1606
|
+
# If timeout is zero, the timeout will not be enforced.
|
|
1607
|
+
#
|
|
1608
|
+
# If the command times out, it is killed via a `SIGKILL` signal and `Git::TimeoutError` is raised.
|
|
1609
|
+
#
|
|
1610
|
+
# If the command does not respond to SIGKILL, it will hang this method.
|
|
1611
|
+
#
|
|
1612
|
+
# @see Git::CommandLine#run
|
|
1613
|
+
#
|
|
1614
|
+
# @return [String] the command's stdout (or merged stdout and stderr if `merge`
|
|
1615
|
+
# is true)
|
|
1616
|
+
#
|
|
1617
|
+
# @raise [Git::FailedError] if the command failed
|
|
1618
|
+
# @raise [Git::SignaledError] if the command was signaled
|
|
1619
|
+
# @raise [Git::TimeoutError] if the command times out
|
|
1620
|
+
# @raise [Git::ProcessIOError] if an exception was raised while collecting subprocess output
|
|
1621
|
+
#
|
|
1622
|
+
# The exception's `result` attribute is a {Git::CommandLineResult} which will
|
|
1623
|
+
# contain the result of the command including the exit status, stdout, and
|
|
1624
|
+
# stderr.
|
|
1625
|
+
#
|
|
1626
|
+
# @api private
|
|
1627
|
+
#
|
|
1628
|
+
def command(*args, out: nil, err: nil, normalize: true, chomp: true, merge: false, chdir: nil, timeout: nil)
|
|
1629
|
+
timeout = timeout || Git.config.timeout
|
|
1630
|
+
result = command_line.run(*args, out: out, err: err, normalize: normalize, chomp: chomp, merge: merge, chdir: chdir, timeout: timeout)
|
|
1631
|
+
result.stdout
|
|
1229
1632
|
end
|
|
1230
1633
|
|
|
1231
1634
|
# Takes the diff command line output (as Array) and parse it into a Hash
|
|
@@ -1291,38 +1694,5 @@ module Git
|
|
|
1291
1694
|
end
|
|
1292
1695
|
arr_opts
|
|
1293
1696
|
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
1697
|
end
|
|
1328
1698
|
end
|