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.
data/lib/git/base.rb CHANGED
@@ -16,7 +16,7 @@ module Git
16
16
  # (see Git.bare)
17
17
  def self.bare(git_dir, options = {})
18
18
  normalize_paths(options, default_repository: git_dir, bare: true)
19
- self.new(options)
19
+ new(options)
20
20
  end
21
21
 
22
22
  # (see Git.clone)
@@ -35,40 +35,48 @@ module Git
35
35
  #
36
36
  # @return [Git::Config] the current config instance.
37
37
  def self.config
38
- @@config ||= Config.new
38
+ @config ||= Config.new
39
39
  end
40
40
 
41
41
  def self.binary_version(binary_path)
42
- result = nil
43
- status = nil
44
-
45
- begin
46
- result, status = Open3.capture2e(binary_path, "-c", "core.quotePath=true", "-c", "color.ui=false", "version")
47
- result = result.chomp
48
- rescue Errno::ENOENT
49
- raise RuntimeError, "Failed to get git version: #{binary_path} not found"
50
- end
42
+ result, status = execute_git_version(binary_path)
51
43
 
52
- if status.success?
53
- version = result[/\d+(\.\d+)+/]
54
- version_parts = version.split('.').collect { |i| i.to_i }
55
- version_parts.fill(0, version_parts.length...3)
56
- else
57
- raise RuntimeError, "Failed to get git version: #{status}\n#{result}"
58
- end
44
+ raise "Failed to get git version: #{status}\n#{result}" unless status.success?
45
+
46
+ parse_version_string(result)
47
+ end
48
+
49
+ private_class_method def self.execute_git_version(binary_path)
50
+ Open3.capture2e(
51
+ binary_path,
52
+ '-c', 'core.quotePath=true',
53
+ '-c', 'color.ui=false',
54
+ 'version'
55
+ )
56
+ rescue Errno::ENOENT
57
+ raise "Failed to get git version: #{binary_path} not found"
58
+ end
59
+
60
+ private_class_method def self.parse_version_string(raw_string)
61
+ version_match = raw_string.match(/\d+(\.\d+)+/)
62
+ return [0, 0, 0] unless version_match
63
+
64
+ version_parts = version_match[0].split('.').map(&:to_i)
65
+ version_parts.fill(0, version_parts.length...3)
59
66
  end
60
67
 
61
68
  # (see Git.init)
62
69
  def self.init(directory = '.', options = {})
63
- normalize_paths(options, default_working_directory: directory, default_repository: directory, bare: options[:bare])
70
+ normalize_paths(options, default_working_directory: directory, default_repository: directory,
71
+ bare: options[:bare])
64
72
 
65
73
  init_options = {
66
- :bare => options[:bare],
67
- :initial_branch => options[:initial_branch]
74
+ bare: options[:bare],
75
+ initial_branch: options[:initial_branch]
68
76
  }
69
77
 
70
78
  directory = options[:bare] ? options[:repository] : options[:working_directory]
71
- FileUtils.mkdir_p(directory) unless File.exist?(directory)
79
+ FileUtils.mkdir_p(directory)
72
80
 
73
81
  # TODO: this dance seems awkward: this creates a Git::Lib so we can call
74
82
  # init so we can create a new Git::Base which in turn (ultimately)
@@ -82,24 +90,32 @@ module Git
82
90
  #
83
91
  Git::Lib.new(options).init(init_options)
84
92
 
85
- self.new(options)
93
+ new(options)
86
94
  end
87
95
 
88
96
  def self.root_of_worktree(working_dir)
89
- result = working_dir
90
- status = nil
91
-
92
97
  raise ArgumentError, "'#{working_dir}' does not exist" unless Dir.exist?(working_dir)
93
98
 
94
- begin
95
- result, status = Open3.capture2e(Git::Base.config.binary_path, "-c", "core.quotePath=true", "-c", "color.ui=false", "rev-parse", "--show-toplevel", chdir: File.expand_path(working_dir))
96
- result = result.chomp
97
- rescue Errno::ENOENT
98
- raise ArgumentError, "Failed to find the root of the worktree: git binary not found"
99
- end
99
+ result, status = execute_rev_parse_toplevel(working_dir)
100
+ process_rev_parse_result(result, status, working_dir)
101
+ end
102
+
103
+ private_class_method def self.execute_rev_parse_toplevel(working_dir)
104
+ Open3.capture2e(
105
+ Git::Base.config.binary_path,
106
+ '-c', 'core.quotePath=true',
107
+ '-c', 'color.ui=false',
108
+ 'rev-parse', '--show-toplevel',
109
+ chdir: File.expand_path(working_dir)
110
+ )
111
+ rescue Errno::ENOENT
112
+ raise ArgumentError, 'Failed to find the root of the worktree: git binary not found'
113
+ end
100
114
 
115
+ private_class_method def self.process_rev_parse_result(result, status, working_dir)
101
116
  raise ArgumentError, "'#{working_dir}' is not in a git working tree" unless status.success?
102
- result
117
+
118
+ result.chomp
103
119
  end
104
120
 
105
121
  # (see Git.open)
@@ -110,7 +126,7 @@ module Git
110
126
 
111
127
  normalize_paths(options, default_working_directory: working_dir)
112
128
 
113
- self.new(options)
129
+ new(options)
114
130
  end
115
131
 
116
132
  # Create an object that executes Git commands in the context of a working
@@ -136,16 +152,9 @@ module Git
136
152
  # of the opened working copy or bare repository
137
153
  #
138
154
  def initialize(options = {})
139
- if working_dir = options[:working_directory]
140
- options[:repository] ||= File.join(working_dir, '.git')
141
- options[:index] ||= File.join(options[:repository], 'index')
142
- end
143
- @logger = (options[:log] || Logger.new(nil))
144
- @logger.info("Starting Git")
145
-
146
- @working_directory = options[:working_directory] ? Git::WorkingDirectory.new(options[:working_directory]) : nil
147
- @repository = options[:repository] ? Git::Repository.new(options[:repository]) : nil
148
- @index = options[:index] ? Git::Index.new(options[:index], false) : nil
155
+ options = default_paths(options)
156
+ setup_logger(options[:log])
157
+ initialize_components(options)
149
158
  end
150
159
 
151
160
  # Update the index from the current worktree to prepare the for the next commit
@@ -162,7 +171,7 @@ module Git
162
171
  # @option options [Boolean] :force Allow adding otherwise ignored files
163
172
  #
164
173
  def add(paths = '.', **options)
165
- self.lib.add(paths, options)
174
+ lib.add(paths, options)
166
175
  end
167
176
 
168
177
  # adds a new remote to this repository
@@ -177,33 +186,10 @@ module Git
177
186
  # :track => <branch_name>
178
187
  def add_remote(name, url, opts = {})
179
188
  url = url.repo.path if url.is_a?(Git::Base)
180
- self.lib.remote_add(name, url, opts)
189
+ lib.remote_add(name, url, opts)
181
190
  Git::Remote.new(self, name)
182
191
  end
183
192
 
184
- # Create a new git tag
185
- #
186
- # @example
187
- # repo.add_tag('tag_name', object_reference)
188
- # repo.add_tag('tag_name', object_reference, {:options => 'here'})
189
- # repo.add_tag('tag_name', {:options => 'here'})
190
- #
191
- # @param [String] name The name of the tag to add
192
- # @param [Hash] options Opstions to pass to `git tag`.
193
- # See [git-tag](https://git-scm.com/docs/git-tag) for more details.
194
- # @option options [boolean] :annotate Make an unsigned, annotated tag object
195
- # @option options [boolean] :a An alias for the `:annotate` option
196
- # @option options [boolean] :d Delete existing tag with the given names.
197
- # @option options [boolean] :f Replace an existing tag with the given name (instead of failing)
198
- # @option options [String] :message Use the given tag message
199
- # @option options [String] :m An alias for the `:message` option
200
- # @option options [boolean] :s Make a GPG-signed tag.
201
- #
202
- def add_tag(name, *options)
203
- self.lib.tag(name, *options)
204
- self.tag(name)
205
- end
206
-
207
193
  # changes current working directory for a block
208
194
  # to the git working directory
209
195
  #
@@ -219,11 +205,11 @@ module Git
219
205
  end
220
206
  end
221
207
 
222
- #g.config('user.name', 'Scott Chacon') # sets value
223
- #g.config('user.email', 'email@email.com') # sets value
224
- #g.config('user.email', 'email@email.com', file: 'path/to/custom/config) # sets value in file
225
- #g.config('user.name') # returns 'Scott Chacon'
226
- #g.config # returns whole config hash
208
+ # g.config('user.name', 'Scott Chacon') # sets value
209
+ # g.config('user.email', 'email@email.com') # sets value
210
+ # g.config('user.email', 'email@email.com', file: 'path/to/custom/config) # sets value in file
211
+ # g.config('user.name') # returns 'Scott Chacon'
212
+ # g.config # returns whole config hash
227
213
  def config(name = nil, value = nil, options = {})
228
214
  if name && value
229
215
  # set value
@@ -245,9 +231,7 @@ module Git
245
231
  end
246
232
 
247
233
  # returns reference to the git index file
248
- def index
249
- @index
250
- end
234
+ attr_reader :index
251
235
 
252
236
  # returns reference to the git repository directory
253
237
  # @git.dir.path
@@ -257,43 +241,77 @@ module Git
257
241
 
258
242
  # returns the repository size in bytes
259
243
  def repo_size
260
- Dir.glob(File.join(repo.path, '**', '*'), File::FNM_DOTMATCH).reject do |f|
261
- f.include?('..')
262
- end.map do |f|
263
- File.expand_path(f)
264
- end.uniq.map do |f|
265
- File.stat(f).size.to_i
266
- end.reduce(:+)
244
+ all_files = Dir.glob(File.join(repo.path, '**', '*'), File::FNM_DOTMATCH)
245
+
246
+ all_files.reject { |file| file.include?('..') }
247
+ .map { |file| File.expand_path(file) }
248
+ .uniq
249
+ .sum { |file| File.stat(file).size.to_i }
267
250
  end
268
251
 
269
- def set_index(index_file, check = true)
252
+ def set_index(index_file, check = nil, must_exist: nil)
253
+ unless check.nil?
254
+ Git::Deprecation.warn(
255
+ 'The "check" argument is deprecated and will be removed in a future version. ' \
256
+ 'Use "must_exist:" instead.'
257
+ )
258
+ end
259
+
260
+ # default is true
261
+ must_exist = must_exist.nil? && check.nil? ? true : must_exist | check
262
+
270
263
  @lib = nil
271
- @index = Git::Index.new(index_file.to_s, check)
264
+ @index = Git::Index.new(index_file.to_s, must_exist:)
272
265
  end
273
266
 
274
- def set_working(work_dir, check = true)
267
+ def set_working(work_dir, check = nil, must_exist: nil)
268
+ unless check.nil?
269
+ Git::Deprecation.warn(
270
+ 'The "check" argument is deprecated and will be removed in a future version. ' \
271
+ 'Use "must_exist:" instead.'
272
+ )
273
+ end
274
+
275
+ # default is true
276
+ must_exist = must_exist.nil? && check.nil? ? true : must_exist | check
277
+
275
278
  @lib = nil
276
- @working_directory = Git::WorkingDirectory.new(work_dir.to_s, check)
279
+ @working_directory = Git::WorkingDirectory.new(work_dir.to_s, must_exist:)
277
280
  end
278
281
 
279
282
  # returns +true+ if the branch exists locally
280
- def is_local_branch?(branch)
281
- branch_names = self.branches.local.map {|b| b.name}
283
+ def local_branch?(branch)
284
+ branch_names = branches.local.map(&:name)
282
285
  branch_names.include?(branch)
283
286
  end
284
287
 
288
+ def is_local_branch?(branch) # rubocop:disable Naming/PredicatePrefix
289
+ Git.deprecation('Git::Base#is_local_branch? is deprecated. Use Git::Base#local_branch? instead.')
290
+ local_branch?(branch)
291
+ end
292
+
285
293
  # returns +true+ if the branch exists remotely
286
- def is_remote_branch?(branch)
287
- branch_names = self.branches.remote.map {|b| b.name}
294
+ def remote_branch?(branch)
295
+ branch_names = branches.remote.map(&:name)
288
296
  branch_names.include?(branch)
289
297
  end
290
298
 
299
+ def is_remote_branch?(branch) # rubocop:disable Naming/PredicatePrefix
300
+ Git.deprecated('Git::Base#is_remote_branch? is deprecated. Use Git::Base#remote_branch? instead.')
301
+ remote_branch?(branch)
302
+ end
303
+
291
304
  # returns +true+ if the branch exists
292
- def is_branch?(branch)
293
- branch_names = self.branches.map {|b| b.name}
305
+ def branch?(branch)
306
+ branch_names = branches.map(&:name)
294
307
  branch_names.include?(branch)
295
308
  end
296
309
 
310
+ def is_branch?(branch) # rubocop:disable Naming/PredicatePrefix
311
+ Git.deprecated('Git::Base#is_branch? is deprecated. Use Git::Base#branch? instead.')
312
+ branch?(branch)
313
+ end
314
+
297
315
  # this is a convenience method for accessing the class that wraps all the
298
316
  # actual 'git' forked system calls. At some point I hope to replace the Git::Lib
299
317
  # class with one that uses native methods or libgit C bindings
@@ -333,32 +351,32 @@ module Git
333
351
  # ```
334
352
  #
335
353
  def grep(string, path_limiter = nil, opts = {})
336
- self.object('HEAD').grep(string, path_limiter, opts)
354
+ object('HEAD').grep(string, path_limiter, opts)
337
355
  end
338
356
 
339
357
  # List the files in the worktree that are ignored by git
340
358
  # @return [Array<String>] the list of ignored files relative to teh root of the worktree
341
359
  #
342
360
  def ignored_files
343
- self.lib.ignored_files
361
+ lib.ignored_files
344
362
  end
345
363
 
346
364
  # removes file(s) from the git repository
347
365
  def rm(path = '.', opts = {})
348
- self.lib.rm(path, opts)
366
+ lib.rm(path, opts)
349
367
  end
350
368
 
351
369
  alias remove rm
352
370
 
353
371
  # resets the working directory to the provided commitish
354
372
  def reset(commitish = nil, opts = {})
355
- self.lib.reset(commitish, opts)
373
+ lib.reset(commitish, opts)
356
374
  end
357
375
 
358
376
  # resets the working directory to the commitish with '--hard'
359
377
  def reset_hard(commitish = nil, opts = {})
360
- opts = {:hard => true}.merge(opts)
361
- self.lib.reset(commitish, opts)
378
+ opts = { hard: true }.merge(opts)
379
+ lib.reset(commitish, opts)
362
380
  end
363
381
 
364
382
  # cleans the working directory
@@ -369,7 +387,7 @@ module Git
369
387
  # :ff
370
388
  #
371
389
  def clean(opts = {})
372
- self.lib.clean(opts)
390
+ lib.clean(opts)
373
391
  end
374
392
 
375
393
  # returns the most recent tag that is reachable from a commit
@@ -387,8 +405,8 @@ module Git
387
405
  # :always
388
406
  # :match
389
407
  #
390
- def describe(committish=nil, opts={})
391
- self.lib.describe(committish, opts)
408
+ def describe(committish = nil, opts = {})
409
+ lib.describe(committish, opts)
392
410
  end
393
411
 
394
412
  # reverts the working directory to the provided commitish.
@@ -398,7 +416,7 @@ module Git
398
416
  # :no_edit
399
417
  #
400
418
  def revert(commitish = nil, opts = {})
401
- self.lib.revert(commitish, opts)
419
+ lib.revert(commitish, opts)
402
420
  end
403
421
 
404
422
  # commits all pending changes in the index file to the git repository
@@ -410,25 +428,25 @@ module Git
410
428
  # :author
411
429
  #
412
430
  def commit(message, opts = {})
413
- self.lib.commit(message, opts)
431
+ lib.commit(message, opts)
414
432
  end
415
433
 
416
434
  # commits all pending changes in the index file to the git repository,
417
435
  # but automatically adds all modified files without having to explicitly
418
436
  # calling @git.add() on them.
419
437
  def commit_all(message, opts = {})
420
- opts = {:add_all => true}.merge(opts)
421
- self.lib.commit(message, opts)
438
+ opts = { add_all: true }.merge(opts)
439
+ lib.commit(message, opts)
422
440
  end
423
441
 
424
442
  # checks out a branch as the new git working directory
425
- def checkout(*args, **options)
426
- self.lib.checkout(*args, **options)
443
+ def checkout(*, **)
444
+ lib.checkout(*, **)
427
445
  end
428
446
 
429
447
  # checks out an old version of a file
430
448
  def checkout_file(version, file)
431
- self.lib.checkout_file(version,file)
449
+ lib.checkout_file(version, file)
432
450
  end
433
451
 
434
452
  # fetches changes from a remote branch - this does not modify the working directory,
@@ -438,7 +456,7 @@ module Git
438
456
  opts = remote
439
457
  remote = nil
440
458
  end
441
- self.lib.fetch(remote, opts)
459
+ lib.fetch(remote, opts)
442
460
  end
443
461
 
444
462
  # Push changes to a remote repository
@@ -459,20 +477,20 @@ module Git
459
477
  # @raise [Git::FailedError] if the push fails
460
478
  # @raise [ArgumentError] if a branch is given without a remote
461
479
  #
462
- def push(*args, **options)
463
- self.lib.push(*args, **options)
480
+ def push(*, **)
481
+ lib.push(*, **)
464
482
  end
465
483
 
466
484
  # merges one or more branches into the current working branch
467
485
  #
468
486
  # you can specify more than one branch to merge by passing an array of branches
469
487
  def merge(branch, message = 'merge', opts = {})
470
- self.lib.merge(branch, message, opts)
488
+ lib.merge(branch, message, opts)
471
489
  end
472
490
 
473
491
  # iterates over the files which are unmerged
474
- def each_conflict(&block) # :yields: file, your_version, their_version
475
- self.lib.conflicts(&block)
492
+ def each_conflict(&) # :yields: file, your_version, their_version
493
+ lib.conflicts(&)
476
494
  end
477
495
 
478
496
  # Pulls the given branch from the given remote into the current branch
@@ -495,12 +513,12 @@ module Git
495
513
  # @raise [Git::FailedError] if the pull fails
496
514
  # @raise [ArgumentError] if a branch is given without a remote
497
515
  def pull(remote = nil, branch = nil, opts = {})
498
- self.lib.pull(remote, branch, opts)
516
+ lib.pull(remote, branch, opts)
499
517
  end
500
518
 
501
519
  # returns an array of Git:Remote objects
502
520
  def remotes
503
- self.lib.remotes.map { |r| Git::Remote.new(self, r) }
521
+ lib.remotes.map { |r| Git::Remote.new(self, r) }
504
522
  end
505
523
 
506
524
  # sets the url for a remote
@@ -510,7 +528,7 @@ module Git
510
528
  #
511
529
  def set_remote_url(name, url)
512
530
  url = url.repo.path if url.is_a?(Git::Base)
513
- self.lib.remote_set_url(name, url)
531
+ lib.remote_set_url(name, url)
514
532
  Git::Remote.new(self, name)
515
533
  end
516
534
 
@@ -518,12 +536,12 @@ module Git
518
536
  #
519
537
  # @git.remove_remote('scott_git')
520
538
  def remove_remote(name)
521
- self.lib.remote_remove(name)
539
+ lib.remote_remove(name)
522
540
  end
523
541
 
524
542
  # returns an array of all Git::Tag objects for this repository
525
543
  def tags
526
- self.lib.tags.map { |r| tag(r) }
544
+ lib.tags.map { |r| tag(r) }
527
545
  end
528
546
 
529
547
  # Create a new git tag
@@ -545,37 +563,37 @@ module Git
545
563
  # @option options [boolean] :s Make a GPG-signed tag.
546
564
  #
547
565
  def add_tag(name, *options)
548
- self.lib.tag(name, *options)
549
- self.tag(name)
566
+ lib.tag(name, *options)
567
+ tag(name)
550
568
  end
551
569
 
552
570
  # deletes a tag
553
571
  def delete_tag(name)
554
- self.lib.tag(name, {:d => true})
572
+ lib.tag(name, { d: true })
555
573
  end
556
574
 
557
575
  # creates an archive file of the given tree-ish
558
576
  def archive(treeish, file = nil, opts = {})
559
- self.object(treeish).archive(file, opts)
577
+ object(treeish).archive(file, opts)
560
578
  end
561
579
 
562
580
  # repacks the repository
563
581
  def repack
564
- self.lib.repack
582
+ lib.repack
565
583
  end
566
584
 
567
585
  def gc
568
- self.lib.gc
586
+ lib.gc
569
587
  end
570
588
 
571
589
  def apply(file)
572
- if File.exist?(file)
573
- self.lib.apply(file)
574
- end
590
+ return unless File.exist?(file)
591
+
592
+ lib.apply(file)
575
593
  end
576
594
 
577
595
  def apply_mail(file)
578
- self.lib.apply_mail(file) if File.exist?(file)
596
+ lib.apply_mail(file) if File.exist?(file)
579
597
  end
580
598
 
581
599
  # Shows objects
@@ -583,8 +601,8 @@ module Git
583
601
  # @param [String|NilClass] objectish the target object reference (nil == HEAD)
584
602
  # @param [String|NilClass] path the path of the file to be shown
585
603
  # @return [String] the object information
586
- def show(objectish=nil, path=nil)
587
- self.lib.show(objectish, path)
604
+ def show(objectish = nil, path = nil)
605
+ lib.show(objectish, path)
588
606
  end
589
607
 
590
608
  ## LOWER LEVEL INDEX OPERATIONS ##
@@ -597,11 +615,11 @@ module Git
597
615
  return_value
598
616
  end
599
617
 
600
- def with_temp_index &blk
618
+ def with_temp_index(&)
601
619
  # Workaround for JRUBY, since they handle the TempFile path different.
602
620
  # MUST be improved to be safer and OS independent.
603
621
  if RUBY_PLATFORM == 'java'
604
- temp_path = "/tmp/temp-index-#{(0...15).map{ ('a'..'z').to_a[rand(26)] }.join}"
622
+ temp_path = "/tmp/temp-index-#{(0...15).map { ('a'..'z').to_a[rand(26)] }.join}"
605
623
  else
606
624
  tempfile = Tempfile.new('temp-index')
607
625
  temp_path = tempfile.path
@@ -609,19 +627,19 @@ module Git
609
627
  tempfile.unlink
610
628
  end
611
629
 
612
- with_index(temp_path, &blk)
630
+ with_index(temp_path, &)
613
631
  end
614
632
 
615
633
  def checkout_index(opts = {})
616
- self.lib.checkout_index(opts)
634
+ lib.checkout_index(opts)
617
635
  end
618
636
 
619
637
  def read_tree(treeish, opts = {})
620
- self.lib.read_tree(treeish, opts)
638
+ lib.read_tree(treeish, opts)
621
639
  end
622
640
 
623
641
  def write_tree
624
- self.lib.write_tree
642
+ lib.write_tree
625
643
  end
626
644
 
627
645
  def write_and_commit_tree(opts = {})
@@ -633,9 +651,8 @@ module Git
633
651
  branch(branch).update_ref(commit)
634
652
  end
635
653
 
636
-
637
- def ls_files(location=nil)
638
- self.lib.ls_files(location)
654
+ def ls_files(location = nil)
655
+ lib.ls_files(location)
639
656
  end
640
657
 
641
658
  def with_working(work_dir) # :yields: the Git::WorkingDirectory
@@ -649,13 +666,13 @@ module Git
649
666
  return_value
650
667
  end
651
668
 
652
- def with_temp_working &blk
653
- tempfile = Tempfile.new("temp-workdir")
669
+ def with_temp_working(&)
670
+ tempfile = Tempfile.new('temp-workdir')
654
671
  temp_dir = tempfile.path
655
672
  tempfile.close
656
673
  tempfile.unlink
657
- Dir.mkdir(temp_dir, 0700)
658
- with_working(temp_dir, &blk)
674
+ Dir.mkdir(temp_dir, 0o700)
675
+ with_working(temp_dir, &)
659
676
  end
660
677
 
661
678
  # runs git rev-parse to convert the objectish to a full sha
@@ -666,18 +683,18 @@ module Git
666
683
  # git.rev_parse('v2.4:/doc/index.html')
667
684
  #
668
685
  def rev_parse(objectish)
669
- self.lib.rev_parse(objectish)
686
+ lib.rev_parse(objectish)
670
687
  end
671
688
 
672
689
  # For backwards compatibility
673
690
  alias revparse rev_parse
674
691
 
675
692
  def ls_tree(objectish, opts = {})
676
- self.lib.ls_tree(objectish, opts)
693
+ lib.ls_tree(objectish, opts)
677
694
  end
678
695
 
679
696
  def cat_file(objectish)
680
- self.lib.cat_file(objectish)
697
+ lib.cat_file(objectish)
681
698
  end
682
699
 
683
700
  # The name of the branch HEAD refers to or 'HEAD' if detached
@@ -689,11 +706,11 @@ module Git
689
706
  # @return [String] the name of the branch HEAD refers to or 'HEAD' if detached
690
707
  #
691
708
  def current_branch
692
- self.lib.branch_current
709
+ lib.branch_current
693
710
  end
694
711
 
695
712
  # @return [Git::Branch] an object for branch_name
696
- def branch(branch_name = self.current_branch)
713
+ def branch(branch_name = current_branch)
697
714
  Git::Branch.new(self, branch_name)
698
715
  end
699
716
 
@@ -716,7 +733,7 @@ module Git
716
733
 
717
734
  # @return [Git::Object::Commit] a commit object
718
735
  def commit_tree(tree = nil, opts = {})
719
- Git::Object::Commit.new(self, self.lib.commit_tree(tree, opts))
736
+ Git::Object::Commit.new(self, lib.commit_tree(tree, opts))
720
737
  end
721
738
 
722
739
  # @return [Git::Diff] a Git::Diff object
@@ -770,19 +787,19 @@ module Git
770
787
 
771
788
  # @return [Git::Object::Tag] a tag object
772
789
  def tag(tag_name)
773
- Git::Object.new(self, tag_name, 'tag', true)
790
+ Git::Object::Tag.new(self, tag_name)
774
791
  end
775
792
 
776
793
  # Find as good common ancestors as possible for a merge
777
794
  # example: g.merge_base('master', 'some_branch', 'some_sha', octopus: true)
778
795
  #
779
796
  # @return [Array<Git::Object::Commit>] a collection of common ancestors
780
- def merge_base(*args)
781
- shas = self.lib.merge_base(*args)
797
+ def merge_base(*)
798
+ shas = lib.merge_base(*)
782
799
  shas.map { |sha| gcommit(sha) }
783
800
  end
784
801
 
785
- # Returns a Git::Diff::Stats object for accessing diff statistics.
802
+ # Returns a Git::Diff::Stats object for accessing diff statistics.
786
803
  #
787
804
  # @param objectish [String] The first commit or object to compare. Defaults to 'HEAD'.
788
805
  # @param obj2 [String, nil] The second commit or object to compare.
@@ -805,6 +822,38 @@ module Git
805
822
 
806
823
  private
807
824
 
825
+ # Sets default paths in the options hash for direct `Git::Base.new` calls
826
+ #
827
+ # Factory methods like `Git.open` pre-populate these options by calling
828
+ # `normalize_paths`, making this a fallback. It avoids mutating the
829
+ # original options hash by returning a new one.
830
+ #
831
+ # @param options [Hash] the original options hash
832
+ # @return [Hash] a new options hash with defaults applied
833
+ def default_paths(options)
834
+ return options unless (working_dir = options[:working_directory])
835
+
836
+ options.dup.tap do |opts|
837
+ opts[:repository] ||= File.join(working_dir, '.git')
838
+ opts[:index] ||= File.join(opts[:repository], 'index')
839
+ end
840
+ end
841
+
842
+ # Initializes the logger from the provided options
843
+ # @param log_option [Logger, nil] The logger instance from options.
844
+ def setup_logger(log_option)
845
+ @logger = log_option || Logger.new(nil)
846
+ @logger.info('Starting Git')
847
+ end
848
+
849
+ # Initializes the core git objects based on the provided options
850
+ # @param options [Hash] The processed options hash.
851
+ def initialize_components(options)
852
+ @working_directory = Git::WorkingDirectory.new(options[:working_directory]) if options[:working_directory]
853
+ @repository = Git::Repository.new(options[:repository]) if options[:repository]
854
+ @index = Git::Index.new(options[:index], false) if options[:index]
855
+ end
856
+
808
857
  # Normalize options before they are sent to Git::Base.new
809
858
  #
810
859
  # Updates the options parameter by setting appropriate values for the following keys:
@@ -868,18 +917,58 @@ module Git
868
917
  # 2. the working directory if NOT working with a bare repository
869
918
  #
870
919
  private_class_method def self.normalize_repository(options, default:, bare: false)
871
- repository =
872
- if bare
873
- File.expand_path(options[:repository] || default || Dir.pwd)
874
- else
875
- File.expand_path(options[:repository] || '.git', options[:working_directory])
876
- end
920
+ initial_path = initial_repository_path(options, default: default, bare: bare)
921
+ final_path = resolve_gitdir_if_present(initial_path, options[:working_directory])
922
+ options[:repository] = final_path
923
+ end
877
924
 
878
- if File.file?(repository)
879
- repository = File.expand_path(File.open(repository).read[8..-1].strip, options[:working_directory])
925
+ # Determines the initial, potential path to the repository directory
926
+ #
927
+ # This path is considered 'initial' because it is not guaranteed to be the
928
+ # final repository location. For features like submodules or worktrees,
929
+ # this path may point to a text file containing a `gitdir:` pointer to the
930
+ # actual repository directory elsewhere. This initial path must be
931
+ # subsequently resolved.
932
+ #
933
+ # @api private
934
+ #
935
+ # @param options [Hash] The options hash, checked for `[:repository]`.
936
+ #
937
+ # @param default [String] A fallback path if `options[:repository]` is not set.
938
+ #
939
+ # @param bare [Boolean] Whether the repository is bare, which changes path resolution.
940
+ #
941
+ # @return [String] The initial, absolute path to the `.git` directory or file.
942
+ #
943
+ private_class_method def self.initial_repository_path(options, default:, bare:)
944
+ if bare
945
+ File.expand_path(options[:repository] || default || Dir.pwd)
946
+ else
947
+ File.expand_path(options[:repository] || '.git', options[:working_directory])
880
948
  end
949
+ end
950
+
951
+ # Resolves the path to the actual repository if it's a `gitdir:` pointer file.
952
+ #
953
+ # If `path` points to a file (common in submodules and worktrees), this
954
+ # method reads the `gitdir:` path from it and returns the real repository
955
+ # path. Otherwise, it returns the original path.
956
+ #
957
+ # @api private
958
+ #
959
+ # @param path [String] The initial path to the repository, which may be a pointer file.
960
+ #
961
+ # @param working_dir [String] The working directory, used as a base to resolve the path.
962
+ #
963
+ # @return [String] The final, resolved absolute path to the repository directory.
964
+ #
965
+ private_class_method def self.resolve_gitdir_if_present(path, working_dir)
966
+ return path unless File.file?(path)
881
967
 
882
- options[:repository] = repository
968
+ # The file contains `gitdir: <path>`, so we read the file,
969
+ # extract the path part, and expand it.
970
+ gitdir_pointer = File.read(path).sub(/\Agitdir: /, '').strip
971
+ File.expand_path(gitdir_pointer, working_dir)
883
972
  end
884
973
 
885
974
  # Normalize options[:index]