git 4.0.0 → 4.0.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/.gitignore +1 -0
- data/.release-please-manifest.json +1 -1
- data/.rubocop.yml +51 -0
- data/.rubocop_todo.yml +12 -0
- data/CHANGELOG.md +44 -0
- data/Rakefile +13 -1
- data/git.gemspec +35 -30
- data/lib/git/args_builder.rb +103 -0
- data/lib/git/author.rb +6 -5
- data/lib/git/base.rb +254 -165
- data/lib/git/branch.rb +14 -15
- data/lib/git/branches.rb +9 -13
- data/lib/git/command_line.rb +97 -55
- data/lib/git/config.rb +4 -6
- data/lib/git/diff.rb +75 -39
- data/lib/git/diff_path_status.rb +4 -3
- data/lib/git/diff_stats.rb +1 -1
- data/lib/git/errors.rb +8 -2
- data/lib/git/escaped_path.rb +1 -1
- data/lib/git/lib.rb +865 -557
- data/lib/git/log.rb +74 -210
- data/lib/git/object.rb +85 -66
- data/lib/git/path.rb +18 -8
- data/lib/git/remote.rb +3 -4
- data/lib/git/repository.rb +0 -2
- data/lib/git/stash.rb +13 -6
- data/lib/git/stashes.rb +5 -5
- data/lib/git/status.rb +108 -247
- data/lib/git/url.rb +3 -3
- data/lib/git/version.rb +1 -1
- data/lib/git/worktree.rb +4 -5
- data/lib/git/worktrees.rb +4 -6
- data/lib/git.rb +16 -15
- metadata +35 -3
data/lib/git/lib.rb
CHANGED
@@ -1,5 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require_relative 'args_builder'
|
4
|
+
|
3
5
|
require 'git/command_line'
|
4
6
|
require 'git/errors'
|
5
7
|
require 'logger'
|
@@ -11,6 +13,8 @@ require 'zlib'
|
|
11
13
|
require 'open3'
|
12
14
|
|
13
15
|
module Git
|
16
|
+
# Internal git operations
|
17
|
+
# @api private
|
14
18
|
class Lib
|
15
19
|
# The path to the Git working copy. The default is '"./.git"'.
|
16
20
|
#
|
@@ -59,23 +63,21 @@ module Git
|
|
59
63
|
# @param [Logger] logger
|
60
64
|
#
|
61
65
|
def initialize(base = nil, logger = nil)
|
62
|
-
@git_dir = nil
|
63
|
-
@git_index_file = nil
|
64
|
-
@git_work_dir = nil
|
65
|
-
@path = nil
|
66
66
|
@logger = logger || Logger.new(nil)
|
67
67
|
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
@git_dir = base[:repository]
|
74
|
-
@git_index_file = base[:index]
|
75
|
-
@git_work_dir = base[:working_directory]
|
68
|
+
case base
|
69
|
+
when Git::Base
|
70
|
+
initialize_from_base(base)
|
71
|
+
when Hash
|
72
|
+
initialize_from_hash(base)
|
76
73
|
end
|
77
74
|
end
|
78
75
|
|
76
|
+
INIT_OPTION_MAP = [
|
77
|
+
{ keys: [:bare], flag: '--bare', type: :boolean },
|
78
|
+
{ keys: [:initial_branch], flag: '--initial-branch', type: :valued_equals }
|
79
|
+
].freeze
|
80
|
+
|
79
81
|
# creates or reinitializes the repository
|
80
82
|
#
|
81
83
|
# options:
|
@@ -83,17 +85,30 @@ module Git
|
|
83
85
|
# :working_directory
|
84
86
|
# :initial_branch
|
85
87
|
#
|
86
|
-
def init(opts={})
|
87
|
-
|
88
|
-
|
89
|
-
arr_opts << "--initial-branch=#{opts[:initial_branch]}" if opts[:initial_branch]
|
90
|
-
|
91
|
-
command('init', *arr_opts)
|
88
|
+
def init(opts = {})
|
89
|
+
args = build_args(opts, INIT_OPTION_MAP)
|
90
|
+
command('init', *args)
|
92
91
|
end
|
93
92
|
|
93
|
+
CLONE_OPTION_MAP = [
|
94
|
+
{ keys: [:bare], flag: '--bare', type: :boolean },
|
95
|
+
{ keys: [:recursive], flag: '--recursive', type: :boolean },
|
96
|
+
{ keys: [:mirror], flag: '--mirror', type: :boolean },
|
97
|
+
{ keys: [:branch], flag: '--branch', type: :valued_space },
|
98
|
+
{ keys: [:filter], flag: '--filter', type: :valued_space },
|
99
|
+
{ keys: %i[remote origin], flag: '--origin', type: :valued_space },
|
100
|
+
{ keys: [:config], flag: '--config', type: :repeatable_valued_space },
|
101
|
+
{
|
102
|
+
keys: [:depth],
|
103
|
+
type: :custom,
|
104
|
+
builder: ->(value) { ['--depth', value.to_i] if value }
|
105
|
+
}
|
106
|
+
].freeze
|
107
|
+
|
94
108
|
# Clones a repository into a newly created directory
|
95
109
|
#
|
96
110
|
# @param [String] repository_url the URL of the repository to clone
|
111
|
+
#
|
97
112
|
# @param [String, nil] directory the directory to clone into
|
98
113
|
#
|
99
114
|
# If nil, the repository is cloned into a directory with the same name as
|
@@ -102,16 +117,28 @@ module Git
|
|
102
117
|
# @param [Hash] opts the options for this command
|
103
118
|
#
|
104
119
|
# @option opts [Boolean] :bare (false) if true, clone as a bare repository
|
120
|
+
#
|
105
121
|
# @option opts [String] :branch the branch to checkout
|
122
|
+
#
|
106
123
|
# @option opts [String, Array] :config one or more configuration options to set
|
124
|
+
#
|
107
125
|
# @option opts [Integer] :depth the number of commits back to pull
|
126
|
+
#
|
108
127
|
# @option opts [String] :filter specify partial clone
|
128
|
+
#
|
109
129
|
# @option opts [String] :mirror set up a mirror of the source repository
|
130
|
+
#
|
110
131
|
# @option opts [String] :origin the name of the remote
|
132
|
+
#
|
111
133
|
# @option opts [String] :path an optional prefix for the directory parameter
|
134
|
+
#
|
112
135
|
# @option opts [String] :remote the name of the remote
|
113
|
-
#
|
114
|
-
# @option opts [
|
136
|
+
#
|
137
|
+
# @option opts [Boolean] :recursive after the clone is created, initialize all
|
138
|
+
# within, using their default settings
|
139
|
+
#
|
140
|
+
# @option opts [Numeric, nil] :timeout the number of seconds to wait for the
|
141
|
+
# command to complete
|
115
142
|
#
|
116
143
|
# See {Git::Lib#command} for more information about :timeout
|
117
144
|
#
|
@@ -123,34 +150,14 @@ module Git
|
|
123
150
|
@path = opts[:path] || '.'
|
124
151
|
clone_dir = opts[:path] ? File.join(@path, directory) : directory
|
125
152
|
|
126
|
-
|
127
|
-
|
128
|
-
arr_opts << '--branch' << opts[:branch] if opts[:branch]
|
129
|
-
arr_opts << '--depth' << opts[:depth].to_i if opts[:depth]
|
130
|
-
arr_opts << '--filter' << opts[:filter] if opts[:filter]
|
131
|
-
Array(opts[:config]).each { |c| arr_opts << '--config' << c }
|
132
|
-
arr_opts << '--origin' << opts[:remote] || opts[:origin] if opts[:remote] || opts[:origin]
|
133
|
-
arr_opts << '--recursive' if opts[:recursive]
|
134
|
-
arr_opts << '--mirror' if opts[:mirror]
|
135
|
-
|
136
|
-
arr_opts << '--'
|
137
|
-
|
138
|
-
arr_opts << repository_url
|
139
|
-
arr_opts << clone_dir
|
153
|
+
args = build_args(opts, CLONE_OPTION_MAP)
|
154
|
+
args.push('--', repository_url, clone_dir)
|
140
155
|
|
141
|
-
command('clone', *
|
156
|
+
command('clone', *args, timeout: opts[:timeout])
|
142
157
|
|
143
158
|
return_base_opts_from_clone(clone_dir, opts)
|
144
159
|
end
|
145
160
|
|
146
|
-
def return_base_opts_from_clone(clone_dir, opts)
|
147
|
-
base_opts = {}
|
148
|
-
base_opts[:repository] = clone_dir if (opts[:bare] || opts[:mirror])
|
149
|
-
base_opts[:working_directory] = clone_dir unless (opts[:bare] || opts[:mirror])
|
150
|
-
base_opts[:log] = opts[:log] if opts[:log]
|
151
|
-
base_opts
|
152
|
-
end
|
153
|
-
|
154
161
|
# Returns the name of the default branch of the given repository
|
155
162
|
#
|
156
163
|
# @param repository [URI, Pathname, String] The (possibly remote) repository to clone from
|
@@ -171,6 +178,29 @@ module Git
|
|
171
178
|
|
172
179
|
## READ COMMANDS ##
|
173
180
|
|
181
|
+
# The map defining how to translate user options to git command arguments.
|
182
|
+
DESCRIBE_OPTION_MAP = [
|
183
|
+
{ keys: [:all], flag: '--all', type: :boolean },
|
184
|
+
{ keys: [:tags], flag: '--tags', type: :boolean },
|
185
|
+
{ keys: [:contains], flag: '--contains', type: :boolean },
|
186
|
+
{ keys: [:debug], flag: '--debug', type: :boolean },
|
187
|
+
{ keys: [:long], flag: '--long', type: :boolean },
|
188
|
+
{ keys: [:always], flag: '--always', type: :boolean },
|
189
|
+
{ keys: %i[exact_match exact-match], flag: '--exact-match', type: :boolean },
|
190
|
+
{ keys: [:abbrev], flag: '--abbrev', type: :valued_equals },
|
191
|
+
{ keys: [:candidates], flag: '--candidates', type: :valued_equals },
|
192
|
+
{ keys: [:match], flag: '--match', type: :valued_equals },
|
193
|
+
{
|
194
|
+
keys: [:dirty],
|
195
|
+
type: :custom,
|
196
|
+
builder: lambda do |value|
|
197
|
+
return '--dirty' if value == true
|
198
|
+
|
199
|
+
"--dirty=#{value}" if value.is_a?(String)
|
200
|
+
end
|
201
|
+
}
|
202
|
+
].freeze
|
203
|
+
|
174
204
|
# Finds most recent tag that is reachable from a commit
|
175
205
|
#
|
176
206
|
# @see https://git-scm.com/docs/git-describe git-describe
|
@@ -198,26 +228,10 @@ module Git
|
|
198
228
|
def describe(commit_ish = nil, opts = {})
|
199
229
|
assert_args_are_not_options('commit-ish object', commit_ish)
|
200
230
|
|
201
|
-
|
202
|
-
|
203
|
-
arr_opts << '--all' if opts[:all]
|
204
|
-
arr_opts << '--tags' if opts[:tags]
|
205
|
-
arr_opts << '--contains' if opts[:contains]
|
206
|
-
arr_opts << '--debug' if opts[:debug]
|
207
|
-
arr_opts << '--long' if opts[:long]
|
208
|
-
arr_opts << '--always' if opts[:always]
|
209
|
-
arr_opts << '--exact-match' if opts[:exact_match] || opts[:"exact-match"]
|
231
|
+
args = build_args(opts, DESCRIBE_OPTION_MAP)
|
232
|
+
args << commit_ish if commit_ish
|
210
233
|
|
211
|
-
|
212
|
-
arr_opts << "--dirty=#{opts[:dirty]}" if opts[:dirty].is_a?(String)
|
213
|
-
|
214
|
-
arr_opts << "--abbrev=#{opts[:abbrev]}" if opts[:abbrev]
|
215
|
-
arr_opts << "--candidates=#{opts[:candidates]}" if opts[:candidates]
|
216
|
-
arr_opts << "--match=#{opts[:match]}" if opts[:match]
|
217
|
-
|
218
|
-
arr_opts << commit_ish if commit_ish
|
219
|
-
|
220
|
-
command('describe', *arr_opts)
|
234
|
+
command('describe', *args)
|
221
235
|
end
|
222
236
|
|
223
237
|
# Return the commits that are within the given revision range
|
@@ -260,20 +274,35 @@ module Git
|
|
260
274
|
command_lines('log', *arr_opts).map { |l| l.split.first }
|
261
275
|
end
|
262
276
|
|
277
|
+
FULL_LOG_EXTRA_OPTIONS_MAP = [
|
278
|
+
{ type: :static, flag: '--pretty=raw' },
|
279
|
+
{ keys: [:skip], flag: '--skip', type: :valued_equals },
|
280
|
+
{ keys: [:merges], flag: '--merges', type: :boolean }
|
281
|
+
].freeze
|
282
|
+
|
263
283
|
# Return the commits that are within the given revision range
|
264
284
|
#
|
265
285
|
# @see https://git-scm.com/docs/git-log git-log
|
266
286
|
#
|
267
287
|
# @param opts [Hash] the given options
|
268
288
|
#
|
269
|
-
# @option opts :count [Integer] the maximum number of commits to return (maps to
|
289
|
+
# @option opts :count [Integer] the maximum number of commits to return (maps to
|
290
|
+
# max-count)
|
291
|
+
#
|
270
292
|
# @option opts :all [Boolean]
|
293
|
+
#
|
271
294
|
# @option opts :cherry [Boolean]
|
295
|
+
#
|
272
296
|
# @option opts :since [String]
|
297
|
+
#
|
273
298
|
# @option opts :until [String]
|
299
|
+
#
|
274
300
|
# @option opts :grep [String]
|
301
|
+
#
|
275
302
|
# @option opts :author [String]
|
276
|
-
#
|
303
|
+
#
|
304
|
+
# @option opts :between [Array<String>] an array of two commit-ish strings to
|
305
|
+
# specify a revision range
|
277
306
|
#
|
278
307
|
# Only :between or :object options can be used, not both.
|
279
308
|
#
|
@@ -281,37 +310,39 @@ module Git
|
|
281
310
|
#
|
282
311
|
# Only :between or :object options can be used, not both.
|
283
312
|
#
|
284
|
-
# @option opts :path_limiter [Array<String>, String] only include commits that
|
313
|
+
# @option opts :path_limiter [Array<String>, String] only include commits that
|
314
|
+
# impact files from the specified paths
|
315
|
+
#
|
285
316
|
# @option opts :skip [Integer]
|
286
317
|
#
|
287
318
|
# @return [Array<Hash>] the log output parsed into an array of hashs for each commit
|
288
319
|
#
|
289
320
|
# Each hash contains the following keys:
|
321
|
+
#
|
290
322
|
# * 'sha' [String] the commit sha
|
291
323
|
# * 'author' [String] the author of the commit
|
292
324
|
# * 'message' [String] the commit message
|
293
325
|
# * 'parent' [Array<String>] the commit shas of the parent commits
|
294
326
|
# * 'tree' [String] the tree sha
|
295
|
-
# * 'author' [String] the author of the commit and timestamp of when the
|
296
|
-
#
|
297
|
-
# * '
|
327
|
+
# * 'author' [String] the author of the commit and timestamp of when the
|
328
|
+
# changes were created
|
329
|
+
# * 'committer' [String] the committer of the commit and timestamp of when the
|
330
|
+
# commit was applied
|
331
|
+
# * 'merges' [Boolean] if truthy, only include merge commits (aka commits with
|
332
|
+
# 2 or more parents)
|
298
333
|
#
|
299
|
-
# @raise [ArgumentError] if the revision range (specified with :between or
|
334
|
+
# @raise [ArgumentError] if the revision range (specified with :between or
|
335
|
+
# :object) is a string starting with a hyphen
|
300
336
|
#
|
301
337
|
def full_log_commits(opts = {})
|
302
338
|
assert_args_are_not_options('between', opts[:between]&.first)
|
303
339
|
assert_args_are_not_options('object', opts[:object])
|
304
340
|
|
305
|
-
|
306
|
-
|
307
|
-
|
308
|
-
arr_opts << "--skip=#{opts[:skip]}" if opts[:skip]
|
309
|
-
arr_opts << '--merges' if opts[:merges]
|
310
|
-
|
311
|
-
arr_opts += log_path_options(opts)
|
312
|
-
|
313
|
-
full_log = command_lines('log', *arr_opts)
|
341
|
+
args = log_common_options(opts)
|
342
|
+
args += build_args(opts, FULL_LOG_EXTRA_OPTIONS_MAP)
|
343
|
+
args += log_path_options(opts)
|
314
344
|
|
345
|
+
full_log = command_lines('log', *args)
|
315
346
|
process_commit_log_data(full_log)
|
316
347
|
end
|
317
348
|
|
@@ -319,7 +350,8 @@ module Git
|
|
319
350
|
#
|
320
351
|
# @see https://git-scm.com/docs/git-rev-parse git-rev-parse
|
321
352
|
# @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
|
353
|
+
# @see https://git-scm.com/docs/git-rev-parse#Documentation/git-rev-parse.txt-emltrefnamegtemegemmasterememheadsmasterememrefsheadsmasterem
|
354
|
+
# Ref disambiguation rules
|
323
355
|
#
|
324
356
|
# @example
|
325
357
|
# lib.rev_parse('HEAD') # => '9b9b31e704c0b85ffdd8d2af2ded85170a5af87d'
|
@@ -339,7 +371,7 @@ module Git
|
|
339
371
|
end
|
340
372
|
|
341
373
|
# For backwards compatibility with the old method name
|
342
|
-
alias
|
374
|
+
alias revparse rev_parse
|
343
375
|
|
344
376
|
# Find the first symbolic name for given commit_ish
|
345
377
|
#
|
@@ -355,7 +387,7 @@ module Git
|
|
355
387
|
command('name-rev', commit_ish).split[1]
|
356
388
|
end
|
357
389
|
|
358
|
-
alias
|
390
|
+
alias namerev name_rev
|
359
391
|
|
360
392
|
# Output the contents or other properties of one or more objects.
|
361
393
|
#
|
@@ -373,7 +405,7 @@ module Git
|
|
373
405
|
#
|
374
406
|
# @raise [ArgumentError] if object is a string starting with a hyphen
|
375
407
|
#
|
376
|
-
def cat_file_contents(object
|
408
|
+
def cat_file_contents(object)
|
377
409
|
assert_args_are_not_options('object', object)
|
378
410
|
|
379
411
|
if block_given?
|
@@ -381,7 +413,7 @@ module Git
|
|
381
413
|
# If a block is given, write the output from the process to a temporary
|
382
414
|
# file and then yield the file to the block
|
383
415
|
#
|
384
|
-
command('cat-file',
|
416
|
+
command('cat-file', '-p', object, out: file, err: file)
|
385
417
|
file.rewind
|
386
418
|
yield file
|
387
419
|
end
|
@@ -391,7 +423,7 @@ module Git
|
|
391
423
|
end
|
392
424
|
end
|
393
425
|
|
394
|
-
alias
|
426
|
+
alias object_contents cat_file_contents
|
395
427
|
|
396
428
|
# Get the type for the given object
|
397
429
|
#
|
@@ -409,7 +441,7 @@ module Git
|
|
409
441
|
command('cat-file', '-t', object)
|
410
442
|
end
|
411
443
|
|
412
|
-
alias
|
444
|
+
alias object_type cat_file_type
|
413
445
|
|
414
446
|
# Get the size for the given object
|
415
447
|
#
|
@@ -427,7 +459,7 @@ module Git
|
|
427
459
|
command('cat-file', '-s', object).to_i
|
428
460
|
end
|
429
461
|
|
430
|
-
alias
|
462
|
+
alias object_size cat_file_size
|
431
463
|
|
432
464
|
# Return a hash of commit data
|
433
465
|
#
|
@@ -454,25 +486,15 @@ module Git
|
|
454
486
|
process_commit_data(cdata, object)
|
455
487
|
end
|
456
488
|
|
457
|
-
alias
|
489
|
+
alias commit_data cat_file_commit
|
458
490
|
|
459
491
|
def process_commit_data(data, sha)
|
460
|
-
|
461
|
-
|
462
|
-
|
463
|
-
}
|
492
|
+
# process_commit_headers consumes the header lines from the `data` array,
|
493
|
+
# leaving only the message lines behind.
|
494
|
+
headers = process_commit_headers(data)
|
495
|
+
message = "#{data.join("\n")}\n"
|
464
496
|
|
465
|
-
|
466
|
-
if key == 'parent'
|
467
|
-
hsh['parent'] << value
|
468
|
-
else
|
469
|
-
hsh[key] = value
|
470
|
-
end
|
471
|
-
end
|
472
|
-
|
473
|
-
hsh['message'] = data.join("\n") + "\n"
|
474
|
-
|
475
|
-
hsh
|
497
|
+
{ 'sha' => sha, 'message' => message }.merge(headers)
|
476
498
|
end
|
477
499
|
|
478
500
|
CAT_FILE_HEADER_LINE = /\A(?<key>\w+) (?<value>.*)\z/
|
@@ -482,9 +504,7 @@ module Git
|
|
482
504
|
key = match[:key]
|
483
505
|
value_lines = [match[:value]]
|
484
506
|
|
485
|
-
while data.first.start_with?(' ')
|
486
|
-
value_lines << data.shift.lstrip
|
487
|
-
end
|
507
|
+
value_lines << data.shift.lstrip while data.first.start_with?(' ')
|
488
508
|
|
489
509
|
yield key, value_lines.join("\n")
|
490
510
|
end
|
@@ -492,10 +512,12 @@ module Git
|
|
492
512
|
|
493
513
|
# Return a hash of annotated tag data
|
494
514
|
#
|
495
|
-
# Does not work with lightweight tags. List all annotated tags in your repository
|
515
|
+
# Does not work with lightweight tags. List all annotated tags in your repository
|
516
|
+
# with the following command:
|
496
517
|
#
|
497
518
|
# ```sh
|
498
|
-
# git for-each-ref --format='%(refname:strip=2)' refs/tags |
|
519
|
+
# git for-each-ref --format='%(refname:strip=2)' refs/tags | \
|
520
|
+
# while read tag; do git cat-file tag $tag >/dev/null 2>&1 && echo $tag; done
|
499
521
|
# ```
|
500
522
|
#
|
501
523
|
# @see https://git-scm.com/docs/git-cat-file git-cat-file
|
@@ -520,7 +542,8 @@ module Git
|
|
520
542
|
# * object [String] the sha of the tag object
|
521
543
|
# * type [String]
|
522
544
|
# * tag [String] tag name
|
523
|
-
# * tagger [String] the name and email of the user who created the tag
|
545
|
+
# * tagger [String] the name and email of the user who created the tag
|
546
|
+
# and the timestamp of when the tag was created
|
524
547
|
# * message [String] the tag message
|
525
548
|
#
|
526
549
|
# @raise [ArgumentError] if object is a string starting with a hyphen
|
@@ -532,7 +555,7 @@ module Git
|
|
532
555
|
process_tag_data(tdata, object)
|
533
556
|
end
|
534
557
|
|
535
|
-
alias
|
558
|
+
alias tag_data cat_file_tag
|
536
559
|
|
537
560
|
def process_tag_data(data, name)
|
538
561
|
hsh = { 'name' => name }
|
@@ -541,64 +564,88 @@ module Git
|
|
541
564
|
hsh[key] = value
|
542
565
|
end
|
543
566
|
|
544
|
-
hsh['message'] = data.join("\n")
|
567
|
+
hsh['message'] = "#{data.join("\n")}\n"
|
545
568
|
|
546
569
|
hsh
|
547
570
|
end
|
548
571
|
|
549
572
|
def process_commit_log_data(data)
|
550
|
-
|
573
|
+
RawLogParser.new(data).parse
|
574
|
+
end
|
551
575
|
|
552
|
-
|
576
|
+
# A private parser class to process the output of `git log --pretty=raw`
|
577
|
+
# @api private
|
578
|
+
class RawLogParser
|
579
|
+
def initialize(lines)
|
580
|
+
@lines = lines
|
581
|
+
@commits = []
|
582
|
+
@current_commit = nil
|
583
|
+
@in_message = false
|
584
|
+
end
|
553
585
|
|
554
|
-
|
586
|
+
def parse
|
587
|
+
@lines.each { |line| process_line(line.chomp) }
|
588
|
+
finalize_commit
|
589
|
+
@commits
|
590
|
+
end
|
555
591
|
|
556
|
-
|
557
|
-
line = line.chomp
|
592
|
+
private
|
558
593
|
|
559
|
-
|
560
|
-
|
561
|
-
|
594
|
+
def process_line(line)
|
595
|
+
if line.empty?
|
596
|
+
@in_message = !@in_message
|
597
|
+
return
|
562
598
|
end
|
563
599
|
|
564
|
-
in_message = false if in_message && line
|
600
|
+
@in_message = false if @in_message && !line.start_with?(' ')
|
565
601
|
|
566
|
-
|
567
|
-
|
568
|
-
next
|
569
|
-
end
|
602
|
+
@in_message ? process_message_line(line) : process_metadata_line(line)
|
603
|
+
end
|
570
604
|
|
605
|
+
def process_message_line(line)
|
606
|
+
@current_commit['message'] << "#{line[4..]}\n"
|
607
|
+
end
|
608
|
+
|
609
|
+
def process_metadata_line(line)
|
571
610
|
key, *value = line.split
|
572
611
|
value = value.join(' ')
|
573
612
|
|
574
613
|
case key
|
575
|
-
|
576
|
-
|
577
|
-
|
578
|
-
|
579
|
-
|
580
|
-
|
581
|
-
hsh[key] = value
|
614
|
+
when 'commit'
|
615
|
+
start_new_commit(value)
|
616
|
+
when 'parent'
|
617
|
+
@current_commit['parent'] << value
|
618
|
+
else
|
619
|
+
@current_commit[key] = value
|
582
620
|
end
|
583
621
|
end
|
584
622
|
|
585
|
-
|
623
|
+
def start_new_commit(sha)
|
624
|
+
finalize_commit
|
625
|
+
@current_commit = { 'sha' => sha, 'message' => +'', 'parent' => [] }
|
626
|
+
end
|
586
627
|
|
587
|
-
|
628
|
+
def finalize_commit
|
629
|
+
@commits << @current_commit if @current_commit
|
630
|
+
end
|
588
631
|
end
|
632
|
+
private_constant :RawLogParser
|
633
|
+
|
634
|
+
LS_TREE_OPTION_MAP = [
|
635
|
+
{ keys: [:recursive], flag: '-r', type: :boolean }
|
636
|
+
].freeze
|
589
637
|
|
590
638
|
def ls_tree(sha, opts = {})
|
591
639
|
data = { 'blob' => {}, 'tree' => {}, 'commit' => {} }
|
640
|
+
args = build_args(opts, LS_TREE_OPTION_MAP)
|
592
641
|
|
593
|
-
|
594
|
-
|
595
|
-
# path must be last arg
|
596
|
-
ls_tree_opts << opts[:path] if opts[:path]
|
642
|
+
args.unshift(sha)
|
643
|
+
args << opts[:path] if opts[:path]
|
597
644
|
|
598
|
-
command_lines('ls-tree',
|
645
|
+
command_lines('ls-tree', *args).each do |line|
|
599
646
|
(info, filenm) = line.split("\t")
|
600
647
|
(mode, type, sha) = info.split
|
601
|
-
data[type][filenm] = {:
|
648
|
+
data[type][filenm] = { mode: mode, sha: sha }
|
602
649
|
end
|
603
650
|
|
604
651
|
data
|
@@ -646,31 +693,9 @@ module Git
|
|
646
693
|
|
647
694
|
def branches_all
|
648
695
|
lines = command_lines('branch', '-a')
|
649
|
-
lines.each_with_index.
|
650
|
-
|
651
|
-
|
652
|
-
raise Git::UnexpectedResultError, unexpected_branch_line_error(lines, line, line_index) unless match_data
|
653
|
-
next nil if match_data[:not_a_branch] || match_data[:detached_ref]
|
654
|
-
|
655
|
-
[
|
656
|
-
match_data[:refname],
|
657
|
-
!match_data[:current].nil?,
|
658
|
-
!match_data[:worktree].nil?,
|
659
|
-
match_data[:symref]
|
660
|
-
]
|
661
|
-
end.compact
|
662
|
-
end
|
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
|
696
|
+
lines.each_with_index.filter_map do |line, index|
|
697
|
+
parse_branch_line(line, index, lines)
|
698
|
+
end
|
674
699
|
end
|
675
700
|
|
676
701
|
def worktrees_all
|
@@ -686,7 +711,7 @@ module Git
|
|
686
711
|
# detached
|
687
712
|
#
|
688
713
|
command_lines('worktree', 'list', '--porcelain').each do |w|
|
689
|
-
s = w.split
|
714
|
+
s = w.split
|
690
715
|
directory = s[1] if s[0] == 'worktree'
|
691
716
|
arr << [directory, s[1]] if s[0] == 'HEAD'
|
692
717
|
end
|
@@ -694,7 +719,8 @@ module Git
|
|
694
719
|
end
|
695
720
|
|
696
721
|
def worktree_add(dir, commitish = nil)
|
697
|
-
return command('worktree', 'add', dir, commitish)
|
722
|
+
return command('worktree', 'add', dir, commitish) unless commitish.nil?
|
723
|
+
|
698
724
|
command('worktree', 'add', dir)
|
699
725
|
end
|
700
726
|
|
@@ -708,12 +734,7 @@ module Git
|
|
708
734
|
|
709
735
|
def list_files(ref_dir)
|
710
736
|
dir = File.join(@git_dir, 'refs', ref_dir)
|
711
|
-
|
712
|
-
begin
|
713
|
-
files = Dir.glob('**/*', base: dir).select { |f| File.file?(File.join(dir, f)) }
|
714
|
-
rescue
|
715
|
-
end
|
716
|
-
files
|
737
|
+
Dir.glob('**/*', base: dir).select { |f| File.file?(File.join(dir, f)) }
|
717
738
|
end
|
718
739
|
|
719
740
|
# The state and name of branch pointed to by `HEAD`
|
@@ -748,16 +769,7 @@ module Git
|
|
748
769
|
branch_name = command('branch', '--show-current')
|
749
770
|
return HeadState.new(:detached, 'HEAD') if branch_name.empty?
|
750
771
|
|
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
|
-
|
772
|
+
state = get_branch_state(branch_name)
|
761
773
|
HeadState.new(state, branch_name)
|
762
774
|
end
|
763
775
|
|
@@ -766,38 +778,35 @@ module Git
|
|
766
778
|
branch_name.empty? ? 'HEAD' : branch_name
|
767
779
|
end
|
768
780
|
|
769
|
-
def branch_contains(commit, branch_name=
|
770
|
-
command(
|
781
|
+
def branch_contains(commit, branch_name = '')
|
782
|
+
command('branch', branch_name, '--contains', commit)
|
771
783
|
end
|
772
784
|
|
785
|
+
GREP_OPTION_MAP = [
|
786
|
+
{ keys: [:ignore_case], flag: '-i', type: :boolean },
|
787
|
+
{ keys: [:invert_match], flag: '-v', type: :boolean },
|
788
|
+
{ keys: [:extended_regexp], flag: '-E', type: :boolean },
|
789
|
+
# For validation only, as these are handled manually
|
790
|
+
{ keys: [:object], type: :validate_only },
|
791
|
+
{ keys: [:path_limiter], type: :validate_only }
|
792
|
+
].freeze
|
793
|
+
|
773
794
|
# returns hash
|
774
795
|
# [tree-ish] = [[line_no, match], [line_no, match2]]
|
775
796
|
# [tree-ish] = [[line_no, match], [line_no, match2]]
|
776
797
|
def grep(string, opts = {})
|
777
798
|
opts[:object] ||= 'HEAD'
|
799
|
+
ArgsBuilder.validate!(opts, GREP_OPTION_MAP)
|
778
800
|
|
779
|
-
|
780
|
-
|
781
|
-
grep_opts << '-v' if opts[:invert_match]
|
782
|
-
grep_opts << '-E' if opts[:extended_regexp]
|
783
|
-
grep_opts << '-e'
|
784
|
-
grep_opts << string
|
785
|
-
grep_opts << opts[:object] if opts[:object].is_a?(String)
|
786
|
-
grep_opts.push('--', opts[:path_limiter]) if opts[:path_limiter].is_a?(String)
|
787
|
-
grep_opts.push('--', *opts[:path_limiter]) if opts[:path_limiter].is_a?(Array)
|
801
|
+
boolean_flags = build_args(opts, GREP_OPTION_MAP)
|
802
|
+
args = ['-n', *boolean_flags, '-e', string, opts[:object]]
|
788
803
|
|
789
|
-
|
790
|
-
|
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
|
796
|
-
end
|
797
|
-
rescue Git::FailedError => e
|
798
|
-
raise unless e.result.status.exitstatus == 1 && e.result.stderr == ''
|
804
|
+
if (limiter = opts[:path_limiter])
|
805
|
+
args.push('--', *Array(limiter))
|
799
806
|
end
|
800
|
-
|
807
|
+
|
808
|
+
lines = execute_grep_command(args)
|
809
|
+
parse_grep_output(lines)
|
801
810
|
end
|
802
811
|
|
803
812
|
# Validate that the given arguments cannot be mistaken for a command-line option
|
@@ -810,58 +819,64 @@ module Git
|
|
810
819
|
#
|
811
820
|
def assert_args_are_not_options(arg_name, *args)
|
812
821
|
invalid_args = args.select { |arg| arg&.start_with?('-') }
|
813
|
-
|
814
|
-
|
815
|
-
|
822
|
+
return unless invalid_args.any?
|
823
|
+
|
824
|
+
raise ArgumentError, "Invalid #{arg_name}: '#{invalid_args.join("', '")}'"
|
816
825
|
end
|
817
826
|
|
827
|
+
DIFF_FULL_OPTION_MAP = [
|
828
|
+
{ type: :static, flag: '-p' },
|
829
|
+
{ keys: [:path_limiter], type: :validate_only }
|
830
|
+
].freeze
|
831
|
+
|
818
832
|
def diff_full(obj1 = 'HEAD', obj2 = nil, opts = {})
|
819
833
|
assert_args_are_not_options('commit or commit range', obj1, obj2)
|
834
|
+
ArgsBuilder.validate!(opts, DIFF_FULL_OPTION_MAP)
|
820
835
|
|
821
|
-
|
822
|
-
|
823
|
-
diff_opts << obj2 if obj2.is_a?(String)
|
824
|
-
diff_opts << '--' << opts[:path_limiter] if opts[:path_limiter].is_a? String
|
836
|
+
args = build_args(opts, DIFF_FULL_OPTION_MAP)
|
837
|
+
args.push(obj1, obj2).compact!
|
825
838
|
|
826
|
-
|
839
|
+
if (path = opts[:path_limiter]) && path.is_a?(String)
|
840
|
+
args.push('--', path)
|
841
|
+
end
|
842
|
+
|
843
|
+
command('diff', *args)
|
827
844
|
end
|
828
845
|
|
846
|
+
DIFF_STATS_OPTION_MAP = [
|
847
|
+
{ type: :static, flag: '--numstat' },
|
848
|
+
{ keys: [:path_limiter], type: :validate_only }
|
849
|
+
].freeze
|
850
|
+
|
829
851
|
def diff_stats(obj1 = 'HEAD', obj2 = nil, opts = {})
|
830
852
|
assert_args_are_not_options('commit or commit range', obj1, obj2)
|
853
|
+
ArgsBuilder.validate!(opts, DIFF_STATS_OPTION_MAP)
|
831
854
|
|
832
|
-
|
833
|
-
|
834
|
-
diff_opts << obj2 if obj2.is_a?(String)
|
835
|
-
diff_opts << '--' << opts[:path_limiter] if opts[:path_limiter].is_a? String
|
855
|
+
args = build_args(opts, DIFF_STATS_OPTION_MAP)
|
856
|
+
args.push(obj1, obj2).compact!
|
836
857
|
|
837
|
-
|
838
|
-
|
839
|
-
command_lines('diff', *diff_opts).each do |file|
|
840
|
-
(insertions, deletions, filename) = file.split("\t")
|
841
|
-
hsh[:total][:insertions] += insertions.to_i
|
842
|
-
hsh[:total][:deletions] += deletions.to_i
|
843
|
-
hsh[:total][:lines] = (hsh[:total][:deletions] + hsh[:total][:insertions])
|
844
|
-
hsh[:total][:files] += 1
|
845
|
-
hsh[:files][filename] = {:insertions => insertions.to_i, :deletions => deletions.to_i}
|
858
|
+
if (path = opts[:path_limiter]) && path.is_a?(String)
|
859
|
+
args.push('--', path)
|
846
860
|
end
|
847
861
|
|
848
|
-
|
862
|
+
output_lines = command_lines('diff', *args)
|
863
|
+
parse_diff_stats_output(output_lines)
|
849
864
|
end
|
850
865
|
|
866
|
+
DIFF_PATH_STATUS_OPTION_MAP = [
|
867
|
+
{ type: :static, flag: '--name-status' },
|
868
|
+
{ keys: [:path], type: :validate_only }
|
869
|
+
].freeze
|
870
|
+
|
851
871
|
def diff_path_status(reference1 = nil, reference2 = nil, opts = {})
|
852
872
|
assert_args_are_not_options('commit or commit range', reference1, reference2)
|
873
|
+
ArgsBuilder.validate!(opts, DIFF_PATH_STATUS_OPTION_MAP)
|
853
874
|
|
854
|
-
|
855
|
-
|
856
|
-
|
875
|
+
args = build_args(opts, DIFF_PATH_STATUS_OPTION_MAP)
|
876
|
+
args.push(reference1, reference2).compact!
|
877
|
+
args.push('--', opts[:path]) if opts[:path]
|
857
878
|
|
858
|
-
|
859
|
-
|
860
|
-
command_lines('diff', *opts_arr).inject({}) do |memo, line|
|
861
|
-
status, path = line.split("\t")
|
862
|
-
memo[path] = status
|
863
|
-
memo
|
864
|
-
end
|
879
|
+
parse_diff_path_status(args)
|
865
880
|
end
|
866
881
|
|
867
882
|
# compares the index and the working directory
|
@@ -886,14 +901,14 @@ module Git
|
|
886
901
|
# * :sha_index [String] the file sha
|
887
902
|
# * :stage [String] the file stage
|
888
903
|
#
|
889
|
-
def ls_files(location=nil)
|
904
|
+
def ls_files(location = nil)
|
890
905
|
location ||= '.'
|
891
906
|
{}.tap do |files|
|
892
907
|
command_lines('ls-files', '--stage', location).each do |line|
|
893
908
|
(info, file) = line.split("\t")
|
894
909
|
(mode, sha, stage) = info.split
|
895
910
|
files[unescape_quoted_path(file)] = {
|
896
|
-
:
|
911
|
+
path: file, mode_index: mode, sha_index: sha, stage: stage
|
897
912
|
}
|
898
913
|
end
|
899
914
|
end
|
@@ -922,21 +937,18 @@ module Git
|
|
922
937
|
end
|
923
938
|
end
|
924
939
|
|
925
|
-
|
926
|
-
|
927
|
-
|
928
|
-
|
929
|
-
|
930
|
-
|
931
|
-
|
932
|
-
|
933
|
-
|
934
|
-
|
935
|
-
|
936
|
-
|
937
|
-
hsh[type].update( name.nil? ? value : { name => value })
|
938
|
-
end
|
939
|
-
end
|
940
|
+
LS_REMOTE_OPTION_MAP = [
|
941
|
+
{ keys: [:refs], flag: '--refs', type: :boolean }
|
942
|
+
].freeze
|
943
|
+
|
944
|
+
def ls_remote(location = nil, opts = {})
|
945
|
+
ArgsBuilder.validate!(opts, LS_REMOTE_OPTION_MAP)
|
946
|
+
|
947
|
+
flags = build_args(opts, LS_REMOTE_OPTION_MAP)
|
948
|
+
positional_arg = location || '.'
|
949
|
+
|
950
|
+
output_lines = command_lines('ls-remote', *flags, positional_arg)
|
951
|
+
parse_ls_remote_output(output_lines)
|
940
952
|
end
|
941
953
|
|
942
954
|
def ignored_files
|
@@ -950,9 +962,7 @@ module Git
|
|
950
962
|
def config_remote(name)
|
951
963
|
hsh = {}
|
952
964
|
config_list.each do |key, value|
|
953
|
-
if /remote.#{name}/.match(key)
|
954
|
-
hsh[key.gsub("remote.#{name}.", '')] = value
|
955
|
-
end
|
965
|
+
hsh[key.gsub("remote.#{name}.", '')] = value if /remote.#{name}/.match(key)
|
956
966
|
end
|
957
967
|
hsh
|
958
968
|
end
|
@@ -991,7 +1001,7 @@ module Git
|
|
991
1001
|
# @param [String|NilClass] objectish the target object reference (nil == HEAD)
|
992
1002
|
# @param [String|NilClass] path the path of the file to be shown
|
993
1003
|
# @return [String] the object information
|
994
|
-
def show(objectish=nil, path=nil)
|
1004
|
+
def show(objectish = nil, path = nil)
|
995
1005
|
arr_opts = []
|
996
1006
|
|
997
1007
|
arr_opts << (path ? "#{objectish}:#{path}" : objectish)
|
@@ -1001,18 +1011,24 @@ module Git
|
|
1001
1011
|
|
1002
1012
|
## WRITE COMMANDS ##
|
1003
1013
|
|
1014
|
+
CONFIG_SET_OPTION_MAP = [
|
1015
|
+
{ keys: [:file], flag: '--file', type: :valued_space }
|
1016
|
+
].freeze
|
1017
|
+
|
1004
1018
|
def config_set(name, value, options = {})
|
1005
|
-
|
1006
|
-
|
1007
|
-
|
1008
|
-
command('config', '--file', options[:file], name, value)
|
1009
|
-
end
|
1019
|
+
ArgsBuilder.validate!(options, CONFIG_SET_OPTION_MAP)
|
1020
|
+
flags = build_args(options, CONFIG_SET_OPTION_MAP)
|
1021
|
+
command('config', *flags, name, value)
|
1010
1022
|
end
|
1011
1023
|
|
1012
1024
|
def global_config_set(name, value)
|
1013
1025
|
command('config', '--global', name, value)
|
1014
1026
|
end
|
1015
1027
|
|
1028
|
+
ADD_OPTION_MAP = [
|
1029
|
+
{ keys: [:all], flag: '--all', type: :boolean },
|
1030
|
+
{ keys: [:force], flag: '--force', type: :boolean }
|
1031
|
+
].freeze
|
1016
1032
|
|
1017
1033
|
# Update the index from the current worktree to prepare the for the next commit
|
1018
1034
|
#
|
@@ -1027,29 +1043,28 @@ module Git
|
|
1027
1043
|
# @option options [Boolean] :all Add, modify, and remove index entries to match the worktree
|
1028
1044
|
# @option options [Boolean] :force Allow adding otherwise ignored files
|
1029
1045
|
#
|
1030
|
-
def add(paths='.',options={})
|
1031
|
-
|
1032
|
-
|
1033
|
-
arr_opts << '--all' if options[:all]
|
1034
|
-
arr_opts << '--force' if options[:force]
|
1035
|
-
|
1036
|
-
arr_opts << '--'
|
1046
|
+
def add(paths = '.', options = {})
|
1047
|
+
args = build_args(options, ADD_OPTION_MAP)
|
1037
1048
|
|
1038
|
-
|
1049
|
+
args << '--'
|
1050
|
+
args.concat(Array(paths))
|
1039
1051
|
|
1040
|
-
|
1041
|
-
|
1042
|
-
command('add', *arr_opts)
|
1052
|
+
command('add', *args)
|
1043
1053
|
end
|
1044
1054
|
|
1055
|
+
RM_OPTION_MAP = [
|
1056
|
+
{ type: :static, flag: '-f' },
|
1057
|
+
{ keys: [:recursive], flag: '-r', type: :boolean },
|
1058
|
+
{ keys: [:cached], flag: '--cached', type: :boolean }
|
1059
|
+
].freeze
|
1060
|
+
|
1045
1061
|
def rm(path = '.', opts = {})
|
1046
|
-
|
1047
|
-
arr_opts << '-r' if opts[:recursive]
|
1048
|
-
arr_opts << '--cached' if opts[:cached]
|
1049
|
-
arr_opts << '--'
|
1050
|
-
arr_opts += Array(path)
|
1062
|
+
args = build_args(opts, RM_OPTION_MAP)
|
1051
1063
|
|
1052
|
-
|
1064
|
+
args << '--'
|
1065
|
+
args.concat(Array(path))
|
1066
|
+
|
1067
|
+
command('rm', *args)
|
1053
1068
|
end
|
1054
1069
|
|
1055
1070
|
# Returns true if the repository is empty (meaning it has no commits)
|
@@ -1061,10 +1076,32 @@ module Git
|
|
1061
1076
|
false
|
1062
1077
|
rescue Git::FailedError => e
|
1063
1078
|
raise unless e.result.status.exitstatus == 128 &&
|
1064
|
-
|
1079
|
+
e.result.stderr == 'fatal: Needed a single revision'
|
1080
|
+
|
1065
1081
|
true
|
1066
1082
|
end
|
1067
1083
|
|
1084
|
+
COMMIT_OPTION_MAP = [
|
1085
|
+
{ keys: %i[add_all all], flag: '--all', type: :boolean },
|
1086
|
+
{ keys: [:allow_empty], flag: '--allow-empty', type: :boolean },
|
1087
|
+
{ keys: [:no_verify], flag: '--no-verify', type: :boolean },
|
1088
|
+
{ keys: [:allow_empty_message], flag: '--allow-empty-message', type: :boolean },
|
1089
|
+
{ keys: [:author], flag: '--author', type: :valued_equals },
|
1090
|
+
{ keys: [:message], flag: '--message', type: :valued_equals },
|
1091
|
+
{ keys: [:no_gpg_sign], flag: '--no-gpg-sign', type: :boolean },
|
1092
|
+
{ keys: [:date], flag: '--date', type: :valued_equals, validator: ->(v) { v.is_a?(String) } },
|
1093
|
+
{ keys: [:amend], type: :custom, builder: ->(value) { ['--amend', '--no-edit'] if value } },
|
1094
|
+
{
|
1095
|
+
keys: [:gpg_sign],
|
1096
|
+
type: :custom,
|
1097
|
+
builder: lambda { |value|
|
1098
|
+
if value
|
1099
|
+
value == true ? '--gpg-sign' : "--gpg-sign=#{value}"
|
1100
|
+
end
|
1101
|
+
}
|
1102
|
+
}
|
1103
|
+
].freeze
|
1104
|
+
|
1068
1105
|
# Takes the commit message with the options and executes the commit command
|
1069
1106
|
#
|
1070
1107
|
# accepts options:
|
@@ -1080,59 +1117,52 @@ module Git
|
|
1080
1117
|
#
|
1081
1118
|
# @param [String] message the commit message to be used
|
1082
1119
|
# @param [Hash] opts the commit options to be used
|
1120
|
+
|
1083
1121
|
def commit(message, opts = {})
|
1084
|
-
|
1085
|
-
|
1086
|
-
|
1087
|
-
|
1088
|
-
arr_opts << '--allow-empty' if opts[:allow_empty]
|
1089
|
-
arr_opts << "--author=#{opts[:author]}" if opts[:author]
|
1090
|
-
arr_opts << "--date=#{opts[:date]}" if opts[:date].is_a? String
|
1091
|
-
arr_opts << '--no-verify' if opts[:no_verify]
|
1092
|
-
arr_opts << '--allow-empty-message' if opts[:allow_empty_message]
|
1093
|
-
|
1094
|
-
if opts[:gpg_sign] && opts[:no_gpg_sign]
|
1095
|
-
raise ArgumentError, 'cannot specify :gpg_sign and :no_gpg_sign'
|
1096
|
-
elsif opts[:gpg_sign]
|
1097
|
-
arr_opts <<
|
1098
|
-
if opts[:gpg_sign] == true
|
1099
|
-
'--gpg-sign'
|
1100
|
-
else
|
1101
|
-
"--gpg-sign=#{opts[:gpg_sign]}"
|
1102
|
-
end
|
1103
|
-
elsif opts[:no_gpg_sign]
|
1104
|
-
arr_opts << '--no-gpg-sign'
|
1105
|
-
end
|
1122
|
+
opts[:message] = message if message # Handle message arg for backward compatibility
|
1123
|
+
|
1124
|
+
# Perform cross-option validation before building args
|
1125
|
+
raise ArgumentError, 'cannot specify :gpg_sign and :no_gpg_sign' if opts[:gpg_sign] && opts[:no_gpg_sign]
|
1106
1126
|
|
1107
|
-
|
1127
|
+
ArgsBuilder.validate!(opts, COMMIT_OPTION_MAP)
|
1128
|
+
|
1129
|
+
args = build_args(opts, COMMIT_OPTION_MAP)
|
1130
|
+
command('commit', *args)
|
1108
1131
|
end
|
1132
|
+
RESET_OPTION_MAP = [
|
1133
|
+
{ keys: [:hard], flag: '--hard', type: :boolean }
|
1134
|
+
].freeze
|
1109
1135
|
|
1110
1136
|
def reset(commit, opts = {})
|
1111
|
-
|
1112
|
-
|
1113
|
-
|
1114
|
-
command('reset', *arr_opts)
|
1137
|
+
args = build_args(opts, RESET_OPTION_MAP)
|
1138
|
+
args << commit if commit
|
1139
|
+
command('reset', *args)
|
1115
1140
|
end
|
1116
1141
|
|
1117
|
-
|
1118
|
-
|
1119
|
-
|
1120
|
-
|
1121
|
-
|
1122
|
-
|
1142
|
+
CLEAN_OPTION_MAP = [
|
1143
|
+
{ keys: [:force], flag: '--force', type: :boolean },
|
1144
|
+
{ keys: [:ff], flag: '-ff', type: :boolean },
|
1145
|
+
{ keys: [:d], flag: '-d', type: :boolean },
|
1146
|
+
{ keys: [:x], flag: '-x', type: :boolean }
|
1147
|
+
].freeze
|
1123
1148
|
|
1124
|
-
|
1149
|
+
def clean(opts = {})
|
1150
|
+
args = build_args(opts, CLEAN_OPTION_MAP)
|
1151
|
+
command('clean', *args)
|
1125
1152
|
end
|
1126
1153
|
|
1154
|
+
REVERT_OPTION_MAP = [
|
1155
|
+
{ keys: [:no_edit], flag: '--no-edit', type: :boolean }
|
1156
|
+
].freeze
|
1157
|
+
|
1127
1158
|
def revert(commitish, opts = {})
|
1128
1159
|
# Forcing --no-edit as default since it's not an interactive session.
|
1129
|
-
opts = {:
|
1160
|
+
opts = { no_edit: true }.merge(opts)
|
1130
1161
|
|
1131
|
-
|
1132
|
-
|
1133
|
-
arr_opts << commitish
|
1162
|
+
args = build_args(opts, REVERT_OPTION_MAP)
|
1163
|
+
args << commitish
|
1134
1164
|
|
1135
|
-
command('revert', *
|
1165
|
+
command('revert', *args)
|
1136
1166
|
end
|
1137
1167
|
|
1138
1168
|
def apply(patch_file)
|
@@ -1148,19 +1178,9 @@ module Git
|
|
1148
1178
|
end
|
1149
1179
|
|
1150
1180
|
def stashes_all
|
1151
|
-
|
1152
|
-
|
1153
|
-
if File.exist?(filename)
|
1154
|
-
File.open(filename) do |f|
|
1155
|
-
f.each_with_index do |line, i|
|
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]
|
1160
|
-
end
|
1161
|
-
end
|
1181
|
+
stash_log_lines.each_with_index.map do |line, index|
|
1182
|
+
parse_stash_log_line(line, index)
|
1162
1183
|
end
|
1163
|
-
arr
|
1164
1184
|
end
|
1165
1185
|
|
1166
1186
|
def stash_save(message)
|
@@ -1192,6 +1212,12 @@ module Git
|
|
1192
1212
|
command('branch', '-D', branch)
|
1193
1213
|
end
|
1194
1214
|
|
1215
|
+
CHECKOUT_OPTION_MAP = [
|
1216
|
+
{ keys: %i[force f], flag: '--force', type: :boolean },
|
1217
|
+
{ keys: %i[new_branch b], type: :validate_only },
|
1218
|
+
{ keys: [:start_point], type: :validate_only }
|
1219
|
+
].freeze
|
1220
|
+
|
1195
1221
|
# Runs checkout command to checkout or create branch
|
1196
1222
|
#
|
1197
1223
|
# accepts options:
|
@@ -1202,18 +1228,16 @@ module Git
|
|
1202
1228
|
# @param [String] branch
|
1203
1229
|
# @param [Hash] opts
|
1204
1230
|
def checkout(branch = nil, opts = {})
|
1205
|
-
if branch.is_a?(Hash) && opts
|
1231
|
+
if branch.is_a?(Hash) && opts.empty?
|
1206
1232
|
opts = branch
|
1207
1233
|
branch = nil
|
1208
1234
|
end
|
1235
|
+
ArgsBuilder.validate!(opts, CHECKOUT_OPTION_MAP)
|
1209
1236
|
|
1210
|
-
|
1211
|
-
|
1212
|
-
arr_opts << '--force' if opts[:force] || opts[:f]
|
1213
|
-
arr_opts << branch if branch
|
1214
|
-
arr_opts << opts[:start_point] if opts[:start_point] && arr_opts.include?('-b')
|
1237
|
+
flags = build_args(opts, CHECKOUT_OPTION_MAP)
|
1238
|
+
positional_args = build_checkout_positional_args(branch, opts)
|
1215
1239
|
|
1216
|
-
command('checkout', *
|
1240
|
+
command('checkout', *flags, *positional_args)
|
1217
1241
|
end
|
1218
1242
|
|
1219
1243
|
def checkout_file(version, file)
|
@@ -1223,63 +1247,74 @@ module Git
|
|
1223
1247
|
command('checkout', *arr_opts)
|
1224
1248
|
end
|
1225
1249
|
|
1250
|
+
MERGE_OPTION_MAP = [
|
1251
|
+
{ keys: [:no_commit], flag: '--no-commit', type: :boolean },
|
1252
|
+
{ keys: [:no_ff], flag: '--no-ff', type: :boolean },
|
1253
|
+
{ keys: [:m], flag: '-m', type: :valued_space }
|
1254
|
+
].freeze
|
1255
|
+
|
1226
1256
|
def merge(branch, message = nil, opts = {})
|
1227
|
-
|
1228
|
-
|
1229
|
-
|
1230
|
-
|
1231
|
-
|
1232
|
-
|
1257
|
+
# For backward compatibility, treat the message arg as the :m option.
|
1258
|
+
opts[:m] = message if message
|
1259
|
+
ArgsBuilder.validate!(opts, MERGE_OPTION_MAP)
|
1260
|
+
|
1261
|
+
args = build_args(opts, MERGE_OPTION_MAP)
|
1262
|
+
args.concat(Array(branch))
|
1263
|
+
|
1264
|
+
command('merge', *args)
|
1233
1265
|
end
|
1234
1266
|
|
1267
|
+
MERGE_BASE_OPTION_MAP = [
|
1268
|
+
{ keys: [:octopus], flag: '--octopus', type: :boolean },
|
1269
|
+
{ keys: [:independent], flag: '--independent', type: :boolean },
|
1270
|
+
{ keys: [:fork_point], flag: '--fork-point', type: :boolean },
|
1271
|
+
{ keys: [:all], flag: '--all', type: :boolean }
|
1272
|
+
].freeze
|
1273
|
+
|
1235
1274
|
def merge_base(*args)
|
1236
1275
|
opts = args.last.is_a?(Hash) ? args.pop : {}
|
1276
|
+
ArgsBuilder.validate!(opts, MERGE_BASE_OPTION_MAP)
|
1237
1277
|
|
1238
|
-
|
1239
|
-
|
1240
|
-
arg_opts << '--octopus' if opts[:octopus]
|
1241
|
-
arg_opts << '--independent' if opts[:independent]
|
1242
|
-
arg_opts << '--fork-point' if opts[:fork_point]
|
1243
|
-
arg_opts << '--all' if opts[:all]
|
1278
|
+
flags = build_args(opts, MERGE_BASE_OPTION_MAP)
|
1279
|
+
command_args = flags + args
|
1244
1280
|
|
1245
|
-
|
1246
|
-
|
1247
|
-
command('merge-base', *arg_opts).lines.map(&:strip)
|
1281
|
+
command('merge-base', *command_args).lines.map(&:strip)
|
1248
1282
|
end
|
1249
1283
|
|
1250
1284
|
def unmerged
|
1251
1285
|
unmerged = []
|
1252
|
-
command_lines('diff',
|
1253
|
-
unmerged <<
|
1286
|
+
command_lines('diff', '--cached').each do |line|
|
1287
|
+
unmerged << ::Regexp.last_match(1) if line =~ /^\* Unmerged path (.*)/
|
1254
1288
|
end
|
1255
1289
|
unmerged
|
1256
1290
|
end
|
1257
1291
|
|
1258
1292
|
def conflicts # :yields: file, your, their
|
1259
|
-
|
1260
|
-
Tempfile.create(
|
1261
|
-
|
1262
|
-
your.close
|
1263
|
-
|
1264
|
-
Tempfile.create("THEIR-#{File.basename(f)}") do |their|
|
1265
|
-
command('show', ":3:#{f}", out: their)
|
1266
|
-
their.close
|
1293
|
+
unmerged.each do |file_path|
|
1294
|
+
Tempfile.create(['YOUR-', File.basename(file_path)]) do |your_file|
|
1295
|
+
write_staged_content(file_path, 2, your_file).flush
|
1267
1296
|
|
1268
|
-
|
1297
|
+
Tempfile.create(['THEIR-', File.basename(file_path)]) do |their_file|
|
1298
|
+
write_staged_content(file_path, 3, their_file).flush
|
1299
|
+
yield(file_path, your_file.path, their_file.path)
|
1269
1300
|
end
|
1270
1301
|
end
|
1271
1302
|
end
|
1272
1303
|
end
|
1273
1304
|
|
1305
|
+
REMOTE_ADD_OPTION_MAP = [
|
1306
|
+
{ keys: %i[with_fetch fetch], flag: '-f', type: :boolean },
|
1307
|
+
{ keys: [:track], flag: '-t', type: :valued_space }
|
1308
|
+
].freeze
|
1309
|
+
|
1274
1310
|
def remote_add(name, url, opts = {})
|
1275
|
-
|
1276
|
-
arr_opts << '-f' if opts[:with_fetch] || opts[:fetch]
|
1277
|
-
arr_opts << '-t' << opts[:track] if opts[:track]
|
1278
|
-
arr_opts << '--'
|
1279
|
-
arr_opts << name
|
1280
|
-
arr_opts << url
|
1311
|
+
ArgsBuilder.validate!(opts, REMOTE_ADD_OPTION_MAP)
|
1281
1312
|
|
1282
|
-
|
1313
|
+
flags = build_args(opts, REMOTE_ADD_OPTION_MAP)
|
1314
|
+
positional_args = ['--', name, url]
|
1315
|
+
command_args = ['add'] + flags + positional_args
|
1316
|
+
|
1317
|
+
command('remote', *command_args)
|
1283
1318
|
end
|
1284
1319
|
|
1285
1320
|
def remote_set_url(name, url)
|
@@ -1302,93 +1337,90 @@ module Git
|
|
1302
1337
|
command_lines('tag')
|
1303
1338
|
end
|
1304
1339
|
|
1305
|
-
|
1306
|
-
|
1307
|
-
|
1308
|
-
|
1340
|
+
TAG_OPTION_MAP = [
|
1341
|
+
{ keys: %i[force f], flag: '-f', type: :boolean },
|
1342
|
+
{ keys: %i[annotate a], flag: '-a', type: :boolean },
|
1343
|
+
{ keys: %i[sign s], flag: '-s', type: :boolean },
|
1344
|
+
{ keys: %i[delete d], flag: '-d', type: :boolean },
|
1345
|
+
{ keys: %i[message m], flag: '-m', type: :valued_space }
|
1346
|
+
].freeze
|
1309
1347
|
|
1310
|
-
|
1311
|
-
|
1312
|
-
|
1313
|
-
|
1314
|
-
arr_opts = []
|
1348
|
+
def tag(name, *args)
|
1349
|
+
opts = args.last.is_a?(Hash) ? args.pop : {}
|
1350
|
+
target = args.first
|
1315
1351
|
|
1316
|
-
|
1317
|
-
|
1318
|
-
arr_opts << '-s' if opts[:s] || opts[:sign]
|
1319
|
-
arr_opts << '-d' if opts[:d] || opts[:delete]
|
1320
|
-
arr_opts << name
|
1321
|
-
arr_opts << target if target
|
1352
|
+
validate_tag_options!(opts)
|
1353
|
+
ArgsBuilder.validate!(opts, TAG_OPTION_MAP)
|
1322
1354
|
|
1323
|
-
|
1324
|
-
|
1325
|
-
end
|
1355
|
+
flags = build_args(opts, TAG_OPTION_MAP)
|
1356
|
+
positional_args = [name, target].compact
|
1326
1357
|
|
1327
|
-
command('tag', *
|
1358
|
+
command('tag', *flags, *positional_args)
|
1328
1359
|
end
|
1329
1360
|
|
1330
|
-
|
1331
|
-
|
1332
|
-
|
1333
|
-
|
1334
|
-
|
1335
|
-
|
1336
|
-
|
1337
|
-
|
1338
|
-
|
1339
|
-
|
1340
|
-
|
1341
|
-
arr_opts << remote if remote
|
1342
|
-
arr_opts << opts[:ref] if opts[:ref]
|
1343
|
-
|
1344
|
-
command('fetch', *arr_opts, merge: true)
|
1345
|
-
end
|
1361
|
+
FETCH_OPTION_MAP = [
|
1362
|
+
{ keys: [:all], flag: '--all', type: :boolean },
|
1363
|
+
{ keys: %i[tags t], flag: '--tags', type: :boolean },
|
1364
|
+
{ keys: %i[prune p], flag: '--prune', type: :boolean },
|
1365
|
+
{ keys: %i[prune-tags P], flag: '--prune-tags', type: :boolean },
|
1366
|
+
{ keys: %i[force f], flag: '--force', type: :boolean },
|
1367
|
+
{ keys: %i[update-head-ok u], flag: '--update-head-ok', type: :boolean },
|
1368
|
+
{ keys: [:unshallow], flag: '--unshallow', type: :boolean },
|
1369
|
+
{ keys: [:depth], flag: '--depth', type: :valued_space },
|
1370
|
+
{ keys: [:ref], type: :validate_only }
|
1371
|
+
].freeze
|
1346
1372
|
|
1347
|
-
def
|
1348
|
-
|
1349
|
-
|
1350
|
-
branch = nil
|
1351
|
-
end
|
1373
|
+
def fetch(remote, opts)
|
1374
|
+
ArgsBuilder.validate!(opts, FETCH_OPTION_MAP)
|
1375
|
+
args = build_args(opts, FETCH_OPTION_MAP)
|
1352
1376
|
|
1353
|
-
if
|
1354
|
-
|
1355
|
-
remote
|
1377
|
+
if remote || opts[:ref]
|
1378
|
+
args << '--'
|
1379
|
+
args << remote if remote
|
1380
|
+
args << opts[:ref] if opts[:ref]
|
1356
1381
|
end
|
1357
1382
|
|
1358
|
-
|
1383
|
+
command('fetch', *args, merge: true)
|
1384
|
+
end
|
1359
1385
|
|
1360
|
-
|
1361
|
-
|
1386
|
+
PUSH_OPTION_MAP = [
|
1387
|
+
{ keys: [:mirror], flag: '--mirror', type: :boolean },
|
1388
|
+
{ keys: [:delete], flag: '--delete', type: :boolean },
|
1389
|
+
{ keys: %i[force f], flag: '--force', type: :boolean },
|
1390
|
+
{ keys: [:push_option], flag: '--push-option', type: :repeatable_valued_space },
|
1391
|
+
{ keys: [:all], type: :validate_only }, # For validation purposes
|
1392
|
+
{ keys: [:tags], type: :validate_only } # From the `push` method's logic
|
1393
|
+
].freeze
|
1362
1394
|
|
1363
|
-
|
1395
|
+
def push(remote = nil, branch = nil, opts = nil)
|
1396
|
+
remote, branch, opts = normalize_push_args(remote, branch, opts)
|
1397
|
+
ArgsBuilder.validate!(opts, PUSH_OPTION_MAP)
|
1364
1398
|
|
1365
|
-
|
1366
|
-
arr_opts << '--mirror' if opts[:mirror]
|
1367
|
-
arr_opts << '--delete' if opts[:delete]
|
1368
|
-
arr_opts << '--force' if opts[:force] || opts[:f]
|
1369
|
-
arr_opts << '--all' if opts[:all] && remote
|
1399
|
+
raise ArgumentError, 'remote is required if branch is specified' if !remote && branch
|
1370
1400
|
|
1371
|
-
|
1372
|
-
arr_opts << remote if remote
|
1373
|
-
arr_opts_with_branch = arr_opts.dup
|
1374
|
-
arr_opts_with_branch << branch if branch
|
1401
|
+
args = build_push_args(remote, branch, opts)
|
1375
1402
|
|
1376
1403
|
if opts[:mirror]
|
1377
|
-
|
1404
|
+
command('push', *args)
|
1378
1405
|
else
|
1379
|
-
|
1380
|
-
|
1406
|
+
command('push', *args)
|
1407
|
+
command('push', '--tags', *(args - [branch].compact)) if opts[:tags]
|
1381
1408
|
end
|
1382
1409
|
end
|
1383
1410
|
|
1411
|
+
PULL_OPTION_MAP = [
|
1412
|
+
{ keys: [:allow_unrelated_histories], flag: '--allow-unrelated-histories', type: :boolean }
|
1413
|
+
].freeze
|
1414
|
+
|
1384
1415
|
def pull(remote = nil, branch = nil, opts = {})
|
1385
|
-
raise ArgumentError,
|
1416
|
+
raise ArgumentError, 'You must specify a remote if a branch is specified' if remote.nil? && !branch.nil?
|
1386
1417
|
|
1387
|
-
|
1388
|
-
|
1389
|
-
|
1390
|
-
|
1391
|
-
|
1418
|
+
ArgsBuilder.validate!(opts, PULL_OPTION_MAP)
|
1419
|
+
|
1420
|
+
flags = build_args(opts, PULL_OPTION_MAP)
|
1421
|
+
positional_args = [remote, branch].compact
|
1422
|
+
|
1423
|
+
command('pull', *flags, *positional_args)
|
1392
1424
|
end
|
1393
1425
|
|
1394
1426
|
def tag_sha(tag_name)
|
@@ -1396,7 +1428,7 @@ module Git
|
|
1396
1428
|
return File.read(head).chomp if File.exist?(head)
|
1397
1429
|
|
1398
1430
|
begin
|
1399
|
-
command('show-ref',
|
1431
|
+
command('show-ref', '--tags', '-s', tag_name)
|
1400
1432
|
rescue Git::FailedError => e
|
1401
1433
|
raise unless e.result.status.exitstatus == 1 && e.result.stderr == ''
|
1402
1434
|
|
@@ -1412,82 +1444,77 @@ module Git
|
|
1412
1444
|
command('gc', '--prune', '--aggressive', '--auto')
|
1413
1445
|
end
|
1414
1446
|
|
1415
|
-
|
1447
|
+
READ_TREE_OPTION_MAP = [
|
1448
|
+
{ keys: [:prefix], flag: '--prefix', type: :valued_equals }
|
1449
|
+
].freeze
|
1450
|
+
|
1416
1451
|
def read_tree(treeish, opts = {})
|
1417
|
-
|
1418
|
-
|
1419
|
-
|
1420
|
-
command('read-tree', *arr_opts)
|
1452
|
+
ArgsBuilder.validate!(opts, READ_TREE_OPTION_MAP)
|
1453
|
+
flags = build_args(opts, READ_TREE_OPTION_MAP)
|
1454
|
+
command('read-tree', *flags, treeish)
|
1421
1455
|
end
|
1422
1456
|
|
1423
1457
|
def write_tree
|
1424
1458
|
command('write-tree')
|
1425
1459
|
end
|
1426
1460
|
|
1461
|
+
COMMIT_TREE_OPTION_MAP = [
|
1462
|
+
{ keys: %i[parent parents], flag: '-p', type: :repeatable_valued_space },
|
1463
|
+
{ keys: [:message], flag: '-m', type: :valued_space }
|
1464
|
+
].freeze
|
1465
|
+
|
1427
1466
|
def commit_tree(tree, opts = {})
|
1428
1467
|
opts[:message] ||= "commit tree #{tree}"
|
1429
|
-
|
1430
|
-
|
1431
|
-
|
1432
|
-
|
1433
|
-
arr_opts << '-m' << opts[:message]
|
1434
|
-
command('commit-tree', *arr_opts)
|
1468
|
+
ArgsBuilder.validate!(opts, COMMIT_TREE_OPTION_MAP)
|
1469
|
+
|
1470
|
+
flags = build_args(opts, COMMIT_TREE_OPTION_MAP)
|
1471
|
+
command('commit-tree', tree, *flags)
|
1435
1472
|
end
|
1436
1473
|
|
1437
1474
|
def update_ref(ref, commit)
|
1438
1475
|
command('update-ref', ref, commit)
|
1439
1476
|
end
|
1440
1477
|
|
1478
|
+
CHECKOUT_INDEX_OPTION_MAP = [
|
1479
|
+
{ keys: [:prefix], flag: '--prefix', type: :valued_equals },
|
1480
|
+
{ keys: [:force], flag: '--force', type: :boolean },
|
1481
|
+
{ keys: [:all], flag: '--all', type: :boolean },
|
1482
|
+
{ keys: [:path_limiter], type: :validate_only }
|
1483
|
+
].freeze
|
1484
|
+
|
1441
1485
|
def checkout_index(opts = {})
|
1442
|
-
|
1443
|
-
|
1444
|
-
|
1445
|
-
|
1446
|
-
|
1486
|
+
ArgsBuilder.validate!(opts, CHECKOUT_INDEX_OPTION_MAP)
|
1487
|
+
args = build_args(opts, CHECKOUT_INDEX_OPTION_MAP)
|
1488
|
+
|
1489
|
+
if (path = opts[:path_limiter]) && path.is_a?(String)
|
1490
|
+
args.push('--', path)
|
1491
|
+
end
|
1447
1492
|
|
1448
|
-
command('checkout-index', *
|
1493
|
+
command('checkout-index', *args)
|
1449
1494
|
end
|
1450
1495
|
|
1451
|
-
|
1452
|
-
|
1453
|
-
|
1454
|
-
|
1455
|
-
|
1456
|
-
|
1457
|
-
|
1496
|
+
ARCHIVE_OPTION_MAP = [
|
1497
|
+
{ keys: [:prefix], flag: '--prefix', type: :valued_equals },
|
1498
|
+
{ keys: [:remote], flag: '--remote', type: :valued_equals },
|
1499
|
+
# These options are used by helpers or handled manually
|
1500
|
+
{ keys: [:path], type: :validate_only },
|
1501
|
+
{ keys: [:format], type: :validate_only },
|
1502
|
+
{ keys: [:add_gzip], type: :validate_only }
|
1503
|
+
].freeze
|
1504
|
+
|
1458
1505
|
def archive(sha, file = nil, opts = {})
|
1459
|
-
opts
|
1506
|
+
ArgsBuilder.validate!(opts, ARCHIVE_OPTION_MAP)
|
1507
|
+
file ||= temp_file_name
|
1508
|
+
format, gzip = parse_archive_format_options(opts)
|
1460
1509
|
|
1461
|
-
|
1462
|
-
|
1463
|
-
|
1464
|
-
|
1510
|
+
args = build_args(opts, ARCHIVE_OPTION_MAP)
|
1511
|
+
args.unshift("--format=#{format}")
|
1512
|
+
args << sha
|
1513
|
+
args.push('--', opts[:path]) if opts[:path]
|
1465
1514
|
|
1466
|
-
|
1467
|
-
|
1468
|
-
file = tempfile.path
|
1469
|
-
# delete it now, before we write to it, so that Ruby doesn't delete it
|
1470
|
-
# when it finalizes the Tempfile.
|
1471
|
-
tempfile.close!
|
1472
|
-
end
|
1515
|
+
File.open(file, 'wb') { |f| command('archive', *args, out: f) }
|
1516
|
+
apply_gzip(file) if gzip
|
1473
1517
|
|
1474
|
-
arr_opts = []
|
1475
|
-
arr_opts << "--format=#{opts[:format]}" if opts[:format]
|
1476
|
-
arr_opts << "--prefix=#{opts[:prefix]}" if opts[:prefix]
|
1477
|
-
arr_opts << "--remote=#{opts[:remote]}" if opts[:remote]
|
1478
|
-
arr_opts << sha
|
1479
|
-
arr_opts << '--' << opts[:path] if opts[:path]
|
1480
|
-
|
1481
|
-
f = File.open(file, 'wb')
|
1482
|
-
command('archive', *arr_opts, out: f)
|
1483
|
-
f.close
|
1484
|
-
|
1485
|
-
if opts[:add_gzip]
|
1486
|
-
file_content = File.read(file)
|
1487
|
-
Zlib::GzipWriter.open(file) do |gz|
|
1488
|
-
gz.write(file_content)
|
1489
|
-
end
|
1490
|
-
end
|
1491
1518
|
file
|
1492
1519
|
end
|
1493
1520
|
|
@@ -1495,7 +1522,7 @@ module Git
|
|
1495
1522
|
def current_command_version
|
1496
1523
|
output = command('version')
|
1497
1524
|
version = output[/\d+(\.\d+)+/]
|
1498
|
-
version_parts = version.split('.').collect
|
1525
|
+
version_parts = version.split('.').collect(&:to_i)
|
1499
1526
|
version_parts.fill(0, version_parts.length...3)
|
1500
1527
|
end
|
1501
1528
|
|
@@ -1520,27 +1547,331 @@ module Git
|
|
1520
1547
|
end
|
1521
1548
|
|
1522
1549
|
def meets_required_version?
|
1523
|
-
(
|
1550
|
+
(current_command_version <=> required_command_version) >= 0
|
1524
1551
|
end
|
1525
1552
|
|
1526
|
-
def self.warn_if_old_command(lib)
|
1553
|
+
def self.warn_if_old_command(lib) # rubocop:disable Naming/PredicateMethod
|
1554
|
+
Git::Deprecation.warn('Git::Lib#warn_if_old_command is deprecated. Use meets_required_version?.')
|
1555
|
+
|
1527
1556
|
return true if @version_checked
|
1557
|
+
|
1528
1558
|
@version_checked = true
|
1529
1559
|
unless lib.meets_required_version?
|
1530
|
-
|
1560
|
+
warn "[WARNING] The git gem requires git #{lib.required_command_version.join('.')} or later, " \
|
1561
|
+
"but only found #{lib.current_command_version.join('.')}. You should probably upgrade."
|
1531
1562
|
end
|
1532
1563
|
true
|
1533
1564
|
end
|
1534
1565
|
|
1566
|
+
COMMAND_ARG_DEFAULTS = {
|
1567
|
+
out: nil,
|
1568
|
+
err: nil,
|
1569
|
+
normalize: true,
|
1570
|
+
chomp: true,
|
1571
|
+
merge: false,
|
1572
|
+
chdir: nil,
|
1573
|
+
timeout: nil # Don't set to Git.config.timeout here since it is mutable
|
1574
|
+
}.freeze
|
1575
|
+
|
1576
|
+
STATIC_GLOBAL_OPTS = %w[
|
1577
|
+
-c core.quotePath=true
|
1578
|
+
-c color.ui=false
|
1579
|
+
-c color.advice=false
|
1580
|
+
-c color.diff=false
|
1581
|
+
-c color.grep=false
|
1582
|
+
-c color.push=false
|
1583
|
+
-c color.remote=false
|
1584
|
+
-c color.showBranch=false
|
1585
|
+
-c color.status=false
|
1586
|
+
-c color.transport=false
|
1587
|
+
].freeze
|
1588
|
+
|
1589
|
+
LOG_OPTION_MAP = [
|
1590
|
+
{ type: :static, flag: '--no-color' },
|
1591
|
+
{ keys: [:all], flag: '--all', type: :boolean },
|
1592
|
+
{ keys: [:cherry], flag: '--cherry', type: :boolean },
|
1593
|
+
{ keys: [:since], flag: '--since', type: :valued_equals },
|
1594
|
+
{ keys: [:until], flag: '--until', type: :valued_equals },
|
1595
|
+
{ keys: [:grep], flag: '--grep', type: :valued_equals },
|
1596
|
+
{ keys: [:author], flag: '--author', type: :valued_equals },
|
1597
|
+
{ keys: [:count], flag: '--max-count', type: :valued_equals },
|
1598
|
+
{ keys: [:between], type: :custom, builder: ->(value) { "#{value[0]}..#{value[1]}" if value } }
|
1599
|
+
].freeze
|
1600
|
+
|
1535
1601
|
private
|
1536
1602
|
|
1603
|
+
def parse_diff_path_status(args)
|
1604
|
+
command_lines('diff', *args).each_with_object({}) do |line, memo|
|
1605
|
+
status, path = line.split("\t")
|
1606
|
+
memo[path] = status
|
1607
|
+
end
|
1608
|
+
end
|
1609
|
+
|
1610
|
+
def build_checkout_positional_args(branch, opts)
|
1611
|
+
args = []
|
1612
|
+
if opts[:new_branch] || opts[:b]
|
1613
|
+
args.push('-b', branch)
|
1614
|
+
args << opts[:start_point] if opts[:start_point]
|
1615
|
+
elsif branch
|
1616
|
+
args << branch
|
1617
|
+
end
|
1618
|
+
args
|
1619
|
+
end
|
1620
|
+
|
1621
|
+
def build_args(opts, option_map)
|
1622
|
+
Git::ArgsBuilder.new(opts, option_map).build
|
1623
|
+
end
|
1624
|
+
|
1625
|
+
def initialize_from_base(base_object)
|
1626
|
+
@git_dir = base_object.repo.path
|
1627
|
+
@git_index_file = base_object.index&.path
|
1628
|
+
@git_work_dir = base_object.dir&.path
|
1629
|
+
end
|
1630
|
+
|
1631
|
+
def initialize_from_hash(base_hash)
|
1632
|
+
@git_dir = base_hash[:repository]
|
1633
|
+
@git_index_file = base_hash[:index]
|
1634
|
+
@git_work_dir = base_hash[:working_directory]
|
1635
|
+
end
|
1636
|
+
|
1637
|
+
def return_base_opts_from_clone(clone_dir, opts)
|
1638
|
+
base_opts = {}
|
1639
|
+
base_opts[:repository] = clone_dir if opts[:bare] || opts[:mirror]
|
1640
|
+
base_opts[:working_directory] = clone_dir unless opts[:bare] || opts[:mirror]
|
1641
|
+
base_opts[:log] = opts[:log] if opts[:log]
|
1642
|
+
base_opts
|
1643
|
+
end
|
1644
|
+
|
1645
|
+
def process_commit_headers(data)
|
1646
|
+
headers = { 'parent' => [] } # Pre-initialize for multiple parents
|
1647
|
+
each_cat_file_header(data) do |key, value|
|
1648
|
+
if key == 'parent'
|
1649
|
+
headers['parent'] << value
|
1650
|
+
else
|
1651
|
+
headers[key] = value
|
1652
|
+
end
|
1653
|
+
end
|
1654
|
+
headers
|
1655
|
+
end
|
1656
|
+
|
1657
|
+
def parse_branch_line(line, index, all_lines)
|
1658
|
+
match_data = match_branch_line(line, index, all_lines)
|
1659
|
+
|
1660
|
+
return nil if match_data[:not_a_branch] || match_data[:detached_ref]
|
1661
|
+
|
1662
|
+
format_branch_data(match_data)
|
1663
|
+
end
|
1664
|
+
|
1665
|
+
def match_branch_line(line, index, all_lines)
|
1666
|
+
match_data = line.match(BRANCH_LINE_REGEXP)
|
1667
|
+
raise Git::UnexpectedResultError, unexpected_branch_line_error(all_lines, line, index) unless match_data
|
1668
|
+
|
1669
|
+
match_data
|
1670
|
+
end
|
1671
|
+
|
1672
|
+
def format_branch_data(match_data)
|
1673
|
+
[
|
1674
|
+
match_data[:refname],
|
1675
|
+
!match_data[:current].nil?,
|
1676
|
+
!match_data[:worktree].nil?,
|
1677
|
+
match_data[:symref]
|
1678
|
+
]
|
1679
|
+
end
|
1680
|
+
|
1681
|
+
def unexpected_branch_line_error(lines, line, index)
|
1682
|
+
<<~ERROR
|
1683
|
+
Unexpected line in output from `git branch -a`, line #{index + 1}
|
1684
|
+
|
1685
|
+
Full output:
|
1686
|
+
#{lines.join("\n ")}
|
1687
|
+
|
1688
|
+
Line #{index + 1}:
|
1689
|
+
"#{line}"
|
1690
|
+
ERROR
|
1691
|
+
end
|
1692
|
+
|
1693
|
+
def get_branch_state(branch_name)
|
1694
|
+
command('rev-parse', '--verify', '--quiet', branch_name)
|
1695
|
+
:active
|
1696
|
+
rescue Git::FailedError => e
|
1697
|
+
# An exit status of 1 with empty stderr from `rev-parse --verify`
|
1698
|
+
# indicates a ref that exists but does not yet point to a commit.
|
1699
|
+
raise unless e.result.status.exitstatus == 1 && e.result.stderr.empty?
|
1700
|
+
|
1701
|
+
:unborn
|
1702
|
+
end
|
1703
|
+
|
1704
|
+
def execute_grep_command(args)
|
1705
|
+
command_lines('grep', *args)
|
1706
|
+
rescue Git::FailedError => e
|
1707
|
+
# `git grep` returns 1 when no lines are selected.
|
1708
|
+
raise unless e.result.status.exitstatus == 1 && e.result.stderr.empty?
|
1709
|
+
|
1710
|
+
[] # Return an empty array for "no matches found"
|
1711
|
+
end
|
1712
|
+
|
1713
|
+
def parse_grep_output(lines)
|
1714
|
+
lines.each_with_object(Hash.new { |h, k| h[k] = [] }) do |line, hsh|
|
1715
|
+
match = line.match(/\A(.*?):(\d+):(.*)/)
|
1716
|
+
next unless match
|
1717
|
+
|
1718
|
+
_full, filename, line_num, text = match.to_a
|
1719
|
+
hsh[filename] << [line_num.to_i, text]
|
1720
|
+
end
|
1721
|
+
end
|
1722
|
+
|
1723
|
+
def parse_diff_stats_output(lines)
|
1724
|
+
file_stats = parse_stat_lines(lines)
|
1725
|
+
build_final_stats_hash(file_stats)
|
1726
|
+
end
|
1727
|
+
|
1728
|
+
def parse_stat_lines(lines)
|
1729
|
+
lines.map do |line|
|
1730
|
+
insertions_s, deletions_s, filename = line.split("\t")
|
1731
|
+
{
|
1732
|
+
filename: filename,
|
1733
|
+
insertions: insertions_s.to_i,
|
1734
|
+
deletions: deletions_s.to_i
|
1735
|
+
}
|
1736
|
+
end
|
1737
|
+
end
|
1738
|
+
|
1739
|
+
def build_final_stats_hash(file_stats)
|
1740
|
+
{
|
1741
|
+
total: build_total_stats(file_stats),
|
1742
|
+
files: build_files_hash(file_stats)
|
1743
|
+
}
|
1744
|
+
end
|
1745
|
+
|
1746
|
+
def build_total_stats(file_stats)
|
1747
|
+
insertions = file_stats.sum { |s| s[:insertions] }
|
1748
|
+
deletions = file_stats.sum { |s| s[:deletions] }
|
1749
|
+
{
|
1750
|
+
insertions: insertions,
|
1751
|
+
deletions: deletions,
|
1752
|
+
lines: insertions + deletions,
|
1753
|
+
files: file_stats.size
|
1754
|
+
}
|
1755
|
+
end
|
1756
|
+
|
1757
|
+
def build_files_hash(file_stats)
|
1758
|
+
file_stats.to_h { |s| [s[:filename], s.slice(:insertions, :deletions)] }
|
1759
|
+
end
|
1760
|
+
|
1761
|
+
def parse_ls_remote_output(lines)
|
1762
|
+
lines.each_with_object(Hash.new { |h, k| h[k] = {} }) do |line, hsh|
|
1763
|
+
type, name, value = parse_ls_remote_line(line)
|
1764
|
+
if name
|
1765
|
+
hsh[type][name] = value
|
1766
|
+
else # Handles the HEAD entry, which has no name
|
1767
|
+
hsh[type].update(value)
|
1768
|
+
end
|
1769
|
+
end
|
1770
|
+
end
|
1771
|
+
|
1772
|
+
def parse_ls_remote_line(line)
|
1773
|
+
sha, info = line.split("\t", 2)
|
1774
|
+
ref, type, name = info.split('/', 3)
|
1775
|
+
|
1776
|
+
type ||= 'head'
|
1777
|
+
type = 'branches' if type == 'heads'
|
1778
|
+
|
1779
|
+
value = { ref: ref, sha: sha }
|
1780
|
+
|
1781
|
+
[type, name, value]
|
1782
|
+
end
|
1783
|
+
|
1784
|
+
def stash_log_lines
|
1785
|
+
path = File.join(@git_dir, 'logs/refs/stash')
|
1786
|
+
return [] unless File.exist?(path)
|
1787
|
+
|
1788
|
+
File.readlines(path, chomp: true)
|
1789
|
+
end
|
1790
|
+
|
1791
|
+
def parse_stash_log_line(line, index)
|
1792
|
+
full_message = line.split("\t", 2).last
|
1793
|
+
match_data = full_message.match(/^[^:]+:(.*)$/)
|
1794
|
+
message = match_data ? match_data[1] : full_message
|
1795
|
+
|
1796
|
+
[index, message.strip]
|
1797
|
+
end
|
1798
|
+
|
1799
|
+
# Writes the staged content of a conflicted file to an IO stream
|
1800
|
+
#
|
1801
|
+
# @param path [String] the path to the file in the index
|
1802
|
+
#
|
1803
|
+
# @param stage [Integer] the stage of the file to show (e.g., 2 for 'ours', 3 for 'theirs')
|
1804
|
+
#
|
1805
|
+
# @param out_io [IO] the IO object to write the staged content to
|
1806
|
+
#
|
1807
|
+
# @return [IO] the IO object that was written to
|
1808
|
+
#
|
1809
|
+
def write_staged_content(path, stage, out_io)
|
1810
|
+
command('show', ":#{stage}:#{path}", out: out_io)
|
1811
|
+
out_io
|
1812
|
+
end
|
1813
|
+
|
1814
|
+
def validate_tag_options!(opts)
|
1815
|
+
is_annotated = opts[:a] || opts[:annotate]
|
1816
|
+
has_message = opts[:m] || opts[:message]
|
1817
|
+
|
1818
|
+
return unless is_annotated && !has_message
|
1819
|
+
|
1820
|
+
raise ArgumentError, 'Cannot create an annotated tag without a message.'
|
1821
|
+
end
|
1822
|
+
|
1823
|
+
def normalize_push_args(remote, branch, opts)
|
1824
|
+
if branch.is_a?(Hash)
|
1825
|
+
opts = branch
|
1826
|
+
branch = nil
|
1827
|
+
elsif remote.is_a?(Hash)
|
1828
|
+
opts = remote
|
1829
|
+
remote = nil
|
1830
|
+
end
|
1831
|
+
|
1832
|
+
opts ||= {}
|
1833
|
+
# Backwards compatibility for `push(remote, branch, true)`
|
1834
|
+
opts = { tags: opts } if [true, false].include?(opts)
|
1835
|
+
[remote, branch, opts]
|
1836
|
+
end
|
1837
|
+
|
1838
|
+
def build_push_args(remote, branch, opts)
|
1839
|
+
# Build the simple flags using the ArgsBuilder
|
1840
|
+
args = build_args(opts, PUSH_OPTION_MAP)
|
1841
|
+
|
1842
|
+
# Manually handle the flag with external dependencies and positional args
|
1843
|
+
args << '--all' if opts[:all] && remote
|
1844
|
+
args << remote if remote
|
1845
|
+
args << branch if branch
|
1846
|
+
args
|
1847
|
+
end
|
1848
|
+
|
1849
|
+
def temp_file_name
|
1850
|
+
tempfile = Tempfile.new('archive')
|
1851
|
+
file = tempfile.path
|
1852
|
+
tempfile.close! # Prevents Ruby from deleting the file on garbage collection
|
1853
|
+
file
|
1854
|
+
end
|
1855
|
+
|
1856
|
+
def parse_archive_format_options(opts)
|
1857
|
+
format = opts[:format] || 'zip'
|
1858
|
+
gzip = opts[:add_gzip] == true || format == 'tgz'
|
1859
|
+
format = 'tar' if format == 'tgz'
|
1860
|
+
[format, gzip]
|
1861
|
+
end
|
1862
|
+
|
1863
|
+
def apply_gzip(file)
|
1864
|
+
file_content = File.read(file)
|
1865
|
+
Zlib::GzipWriter.open(file) { |gz| gz.write(file_content) }
|
1866
|
+
end
|
1867
|
+
|
1537
1868
|
def command_lines(cmd, *opts, chdir: nil)
|
1538
1869
|
cmd_op = command(cmd, *opts, chdir: chdir)
|
1539
|
-
if cmd_op.encoding.name
|
1540
|
-
|
1541
|
-
|
1542
|
-
|
1543
|
-
|
1870
|
+
op = if cmd_op.encoding.name == 'UTF-8'
|
1871
|
+
cmd_op
|
1872
|
+
else
|
1873
|
+
cmd_op.encode('UTF-8', 'binary', invalid: :replace, undef: :replace)
|
1874
|
+
end
|
1544
1875
|
op.split("\n")
|
1545
1876
|
end
|
1546
1877
|
|
@@ -1555,19 +1886,10 @@ module Git
|
|
1555
1886
|
end
|
1556
1887
|
|
1557
1888
|
def global_opts
|
1558
|
-
|
1559
|
-
global_opts << "--git-dir=#{@git_dir}"
|
1560
|
-
global_opts << "--work-tree=#{@git_work_dir}"
|
1561
|
-
global_opts
|
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'
|
1889
|
+
[].tap do |global_opts|
|
1890
|
+
global_opts << "--git-dir=#{@git_dir}" unless @git_dir.nil?
|
1891
|
+
global_opts << "--work-tree=#{@git_work_dir}" unless @git_work_dir.nil?
|
1892
|
+
global_opts.concat(STATIC_GLOBAL_OPTS)
|
1571
1893
|
end
|
1572
1894
|
end
|
1573
1895
|
|
@@ -1578,28 +1900,21 @@ module Git
|
|
1578
1900
|
|
1579
1901
|
# Runs a git command and returns the output
|
1580
1902
|
#
|
1581
|
-
#
|
1582
|
-
#
|
1583
|
-
#
|
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
|
1903
|
+
# Additional args are passed to the command line. They should exclude the 'git'
|
1904
|
+
# command itself and global options. Remember to splat the the arguments if given
|
1905
|
+
# as an array.
|
1599
1906
|
#
|
1600
|
-
#
|
1907
|
+
# For example, to run `git log --pretty=oneline`, you would create the array
|
1908
|
+
# `args = ['log', '--pretty=oneline']` and call `command(*args)`.
|
1601
1909
|
#
|
1602
|
-
# @param
|
1910
|
+
# @param options_hash [Hash] the options to pass to the command
|
1911
|
+
# @option options_hash [IO, String, #write, nil] :out the destination for captured stdout
|
1912
|
+
# @option options_hash [IO, String, #write, nil] :err the destination for captured stderr
|
1913
|
+
# @option options_hash [Boolean] :normalize true to normalize the output encoding to UTF-8
|
1914
|
+
# @option options_hash [Boolean] :chomp true to remove trailing newlines from the output
|
1915
|
+
# @option options_hash [Boolean] :merge true to merge stdout and stderr into a single output
|
1916
|
+
# @option options_hash [String, nil] :chdir the directory to run the command in
|
1917
|
+
# @option options_hash [Numeric, nil] :timeout the maximum seconds to wait for the command to complete
|
1603
1918
|
#
|
1604
1919
|
# If timeout is nil, the global timeout from {Git::Config} is used.
|
1605
1920
|
#
|
@@ -1614,9 +1929,14 @@ module Git
|
|
1614
1929
|
# @return [String] the command's stdout (or merged stdout and stderr if `merge`
|
1615
1930
|
# is true)
|
1616
1931
|
#
|
1932
|
+
# @raise [ArgumentError] if an unknown option is passed
|
1933
|
+
#
|
1617
1934
|
# @raise [Git::FailedError] if the command failed
|
1935
|
+
#
|
1618
1936
|
# @raise [Git::SignaledError] if the command was signaled
|
1937
|
+
#
|
1619
1938
|
# @raise [Git::TimeoutError] if the command times out
|
1939
|
+
#
|
1620
1940
|
# @raise [Git::ProcessIOError] if an exception was raised while collecting subprocess output
|
1621
1941
|
#
|
1622
1942
|
# The exception's `result` attribute is a {Git::CommandLineResult} which will
|
@@ -1625,9 +1945,14 @@ module Git
|
|
1625
1945
|
#
|
1626
1946
|
# @api private
|
1627
1947
|
#
|
1628
|
-
def command(
|
1629
|
-
|
1630
|
-
|
1948
|
+
def command(*, **options_hash)
|
1949
|
+
options_hash = COMMAND_ARG_DEFAULTS.merge(options_hash)
|
1950
|
+
options_hash[:timeout] ||= Git.config.timeout
|
1951
|
+
|
1952
|
+
extra_options = options_hash.keys - COMMAND_ARG_DEFAULTS.keys
|
1953
|
+
raise ArgumentError, "Unknown options: #{extra_options.join(', ')}" if extra_options.any?
|
1954
|
+
|
1955
|
+
result = command_line.run(*, **options_hash)
|
1631
1956
|
result.stdout
|
1632
1957
|
end
|
1633
1958
|
|
@@ -1636,23 +1961,18 @@ module Git
|
|
1636
1961
|
# @param [String] diff_command the diff commadn to be used
|
1637
1962
|
# @param [Array] opts the diff options to be used
|
1638
1963
|
# @return [Hash] the diff as Hash
|
1639
|
-
def diff_as_hash(diff_command, opts=[])
|
1964
|
+
def diff_as_hash(diff_command, opts = [])
|
1640
1965
|
# update index before diffing to avoid spurious diffs
|
1641
1966
|
command('status')
|
1642
|
-
command_lines(diff_command, *opts).
|
1967
|
+
command_lines(diff_command, *opts).each_with_object({}) do |line, memo|
|
1643
1968
|
info, file = line.split("\t")
|
1644
1969
|
mode_src, mode_dest, sha_src, sha_dest, type = info.split
|
1645
1970
|
|
1646
1971
|
memo[file] = {
|
1647
|
-
:
|
1648
|
-
:
|
1649
|
-
:
|
1650
|
-
:sha_repo => sha_src,
|
1651
|
-
:sha_index => sha_dest,
|
1652
|
-
:type => type
|
1972
|
+
mode_index: mode_dest, mode_repo: mode_src.to_s[1, 7],
|
1973
|
+
path: file, sha_repo: sha_src, sha_index: sha_dest,
|
1974
|
+
type: type
|
1653
1975
|
}
|
1654
|
-
|
1655
|
-
memo
|
1656
1976
|
end
|
1657
1977
|
end
|
1658
1978
|
|
@@ -1661,23 +1981,11 @@ module Git
|
|
1661
1981
|
# @param [Hash] opts the given options
|
1662
1982
|
# @return [Array] the set of common options that the log command will use
|
1663
1983
|
def log_common_options(opts)
|
1664
|
-
arr_opts = []
|
1665
|
-
|
1666
1984
|
if opts[:count] && !opts[:count].is_a?(Integer)
|
1667
1985
|
raise ArgumentError, "The log count option must be an Integer but was #{opts[:count].inspect}"
|
1668
1986
|
end
|
1669
1987
|
|
1670
|
-
|
1671
|
-
arr_opts << "--all" if opts[:all]
|
1672
|
-
arr_opts << "--no-color"
|
1673
|
-
arr_opts << "--cherry" if opts[:cherry]
|
1674
|
-
arr_opts << "--since=#{opts[:since]}" if opts[:since].is_a? String
|
1675
|
-
arr_opts << "--until=#{opts[:until]}" if opts[:until].is_a? String
|
1676
|
-
arr_opts << "--grep=#{opts[:grep]}" if opts[:grep].is_a? String
|
1677
|
-
arr_opts << "--author=#{opts[:author]}" if opts[:author].is_a? String
|
1678
|
-
arr_opts << "#{opts[:between][0].to_s}..#{opts[:between][1].to_s}" if (opts[:between] && opts[:between].size == 2)
|
1679
|
-
|
1680
|
-
arr_opts
|
1988
|
+
build_args(opts, LOG_OPTION_MAP)
|
1681
1989
|
end
|
1682
1990
|
|
1683
1991
|
# Retrurns an array holding path options for the log commands
|