git 5.7.1 → 6.0.0

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.
Files changed (53) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +132 -42
  3. data/CONTRIBUTING.md +44 -31
  4. data/README.md +138 -100
  5. data/UPGRADING.md +864 -259
  6. data/git.gemspec +6 -8
  7. data/lib/git/author_info.rb +1 -2
  8. data/lib/git/commands/base.rb +22 -2
  9. data/lib/git/commands/cat_file/raw.rb +0 -68
  10. data/lib/git/commands/maintenance/register.rb +0 -4
  11. data/lib/git/commands/maintenance/run.rb +0 -4
  12. data/lib/git/commands/maintenance/start.rb +0 -4
  13. data/lib/git/commands/maintenance/stop.rb +0 -4
  14. data/lib/git/commands/maintenance/unregister.rb +0 -4
  15. data/lib/git/commands/maintenance.rb +0 -2
  16. data/lib/git/commands/show_ref/exclude_existing.rb +1 -1
  17. data/lib/git/commands/show_ref/exists.rb +0 -5
  18. data/lib/git/commands/show_ref/list.rb +1 -1
  19. data/lib/git/commands/show_ref/verify.rb +1 -1
  20. data/lib/git/commands/show_ref.rb +1 -1
  21. data/lib/git/commands/worktree/repair.rb +0 -5
  22. data/lib/git/diff_path_status.rb +1 -1
  23. data/lib/git/encoding_utils.rb +25 -0
  24. data/lib/git/errors.rb +24 -18
  25. data/lib/git/factories.rb +361 -125
  26. data/lib/git/log.rb +3 -75
  27. data/lib/git/object.rb +4 -185
  28. data/lib/git/path_resolver.rb +104 -36
  29. data/lib/git/repository/branching.rb +21 -189
  30. data/lib/git/repository/committing.rb +0 -25
  31. data/lib/git/repository/context_helpers.rb +249 -155
  32. data/lib/git/repository/diffing.rb +2 -72
  33. data/lib/git/repository/merging.rb +32 -54
  34. data/lib/git/repository/object_operations.rb +120 -497
  35. data/lib/git/repository/remote_operations.rb +7 -219
  36. data/lib/git/repository/staging.rb +6 -184
  37. data/lib/git/repository/stashing.rb +126 -130
  38. data/lib/git/repository/status_operations.rb +1 -54
  39. data/lib/git/repository/worktree_operations.rb +30 -125
  40. data/lib/git/repository.rb +20 -299
  41. data/lib/git/system_call_guard.rb +95 -0
  42. data/lib/git/version.rb +1 -1
  43. data/lib/git.rb +47 -364
  44. metadata +24 -18
  45. data/lib/git/author.rb +0 -46
  46. data/lib/git/branch.rb +0 -693
  47. data/lib/git/branches.rb +0 -194
  48. data/lib/git/remote.rb +0 -191
  49. data/lib/git/stash.rb +0 -124
  50. data/lib/git/stashes.rb +0 -198
  51. data/lib/git/status.rb +0 -384
  52. data/lib/git/worktree.rb +0 -180
  53. data/lib/git/worktrees.rb +0 -152
@@ -1,6 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'git/commands/config_option_syntax'
4
3
  require 'git/commands/fetch'
5
4
  require 'git/commands/ls_remote'
6
5
  require 'git/commands/pull'
@@ -8,7 +7,6 @@ require 'git/commands/push'
8
7
  require 'git/commands/remote'
9
8
  require 'git/parsers/ls_remote'
10
9
  require 'git/parsers/remote'
11
- require 'git/remote'
12
10
 
13
11
  require 'git/repository/shared_private'
14
12
 
@@ -20,7 +18,7 @@ module Git
20
18
  #
21
19
  # @api private
22
20
  #
23
- module RemoteOperations # rubocop:disable Metrics/ModuleLength
21
+ module RemoteOperations
24
22
  # Key normalizations for {#fetch} options
25
23
  #
26
24
  # Maps dash-style option keys (which the 4.x `Git::Lib#fetch` accepted)
@@ -192,6 +190,12 @@ module Git
192
190
  #
193
191
  # @raise [Git::FailedError] when git exits with a non-zero status
194
192
  #
193
+ # @note The fetch completes before the merge starts, so a failure after the
194
+ # fetch leaves `FETCH_HEAD` and any updated remote-tracking branches in
195
+ # place. On a conflict, the merge is left in progress as described at
196
+ # {#merge}, or the rebase when the repository is configured to pull with
197
+ # rebase.
198
+ #
195
199
  def pull(remote = nil, branch = nil, opts = {})
196
200
  raise ArgumentError, 'You must specify a remote if a branch is specified' if remote.nil? && !branch.nil?
197
201
 
@@ -346,41 +350,6 @@ module Git
346
350
  nil
347
351
  end
348
352
 
349
- # @param name [String] the name for the new remote
350
- #
351
- # @param url [String, Git::Repository] the URL of the remote repository
352
- #
353
- # A {Git::Repository} instance is accepted for local references and converted
354
- # to `url.repo.to_s`.
355
- #
356
- # @param opts [Hash] options for adding the remote
357
- #
358
- # @option opts [Boolean, nil] :fetch (nil) fetch from the remote immediately
359
- # after adding it (`-f`)
360
- #
361
- # The deprecated alias `:with_fetch` is accepted and normalized
362
- # automatically.
363
- #
364
- # @option opts [String, nil] :track (nil) track only the given branch during
365
- # fetch (`-t`)
366
- #
367
- # @return [Git::Remote] the newly added remote
368
- #
369
- # @raise [ArgumentError] when unsupported option keys are provided
370
- #
371
- # @raise [Git::FailedError] if git exits with a non-zero exit status
372
- #
373
- # @deprecated Use {#remote_add} instead
374
- #
375
- def add_remote(name, url, opts = {})
376
- Git::Deprecation.warn(
377
- 'Git::Repository#add_remote is deprecated and will be removed in v6.0.0. ' \
378
- 'Use Git::Repository#remote_add instead.'
379
- )
380
- remote_add(name, url, opts)
381
- Git::Remote.new(self, name)
382
- end
383
-
384
353
  # Removes a remote from this repository
385
354
  #
386
355
  # Deletes the remote named `name` along with its associated configuration,
@@ -399,22 +368,6 @@ module Git
399
368
  Git::Commands::Remote::Remove.new(@execution_context).call(name)
400
369
  end
401
370
 
402
- # @param name [String] the name of the remote to remove
403
- #
404
- # @return [Git::CommandLine::Result] the result of calling `git remote remove`
405
- #
406
- # @raise [Git::FailedError] when git exits with a non-zero status
407
- #
408
- # @deprecated Use {#remote_remove} instead
409
- #
410
- def remove_remote(name)
411
- Git::Deprecation.warn(
412
- 'Git::Repository#remove_remote is deprecated and will be removed in v6.0.0. ' \
413
- 'Use Git::Repository#remote_remove instead.'
414
- )
415
- remote_remove(name)
416
- end
417
-
418
371
  # Sets the URL for an existing remote
419
372
  #
420
373
  # Replaces the fetch URL configured for the remote named `name`.
@@ -444,28 +397,6 @@ module Git
444
397
  nil
445
398
  end
446
399
 
447
- # @param name [String] the name of the remote to update
448
- #
449
- # @param url [String, Git::Repository] the new URL for the remote
450
- #
451
- # A {Git::Repository} instance is accepted for local references and converted
452
- # to `url.repo.to_s`.
453
- #
454
- # @return [Git::Remote] the updated remote
455
- #
456
- # @raise [Git::FailedError] if git exits with a non-zero exit status
457
- #
458
- # @deprecated Use {#remote_set_url} instead
459
- #
460
- def set_remote_url(name, url)
461
- Git::Deprecation.warn(
462
- 'Git::Repository#set_remote_url is deprecated and will be removed in v6.0.0. ' \
463
- 'Use Git::Repository#remote_set_url instead.'
464
- )
465
- remote_set_url(name, url)
466
- Git::Remote.new(self, name)
467
- end
468
-
469
400
  # Configures which branches are fetched for a remote
470
401
  #
471
402
  # Uses `git remote set-branches` to set or append fetch refspecs. When the
@@ -503,65 +434,6 @@ module Git
503
434
  nil
504
435
  end
505
436
 
506
- # Return the git configuration entries for a named remote
507
- #
508
- # Reads `git config --list` and returns all entries whose keys begin with
509
- # `remote.<name>.`, with the `remote.<name>.` prefix stripped. This
510
- # typically yields at least `"url"` and `"fetch"` for a configured remote.
511
- #
512
- # @example Retrieve the config for the 'origin' remote
513
- # repo.config_remote('origin')
514
- # #=> {
515
- # # 'url' => 'https://github.com/user/repo.git',
516
- # # 'fetch' => '+refs/heads/*:refs/remotes/origin/*'
517
- # # }
518
- #
519
- # @param name [String] the name of the remote (e.g. `"origin"`)
520
- #
521
- # @return [Hash{String => String}] configuration entries for the remote,
522
- # keyed without the `remote.<name>.` prefix
523
- #
524
- # Returns an empty hash when no entries are found.
525
- #
526
- # @raise [Git::FailedError] when git exits with a non-zero status
527
- #
528
- # @deprecated Use `remote_list.find { |r| r.name == name }` for the fields
529
- # {Git::RemoteInfo} models, or filter {Git::Configuring#config_list} on
530
- # the `remote.<name>.` key prefix to keep every entry
531
- #
532
- # {#remote_list} returns a {Git::RemoteInfo} per remote. Its `url` and
533
- # `fetch` members hold every configured value as `Array<String>`,
534
- # whereas this method returns a flat hash in which a repeated `url` or
535
- # `fetch` key overwrites the earlier value.
536
- #
537
- # {Git::RemoteInfo} models only the remote variables git defines and
538
- # drops any other `remote.<name>.*` entry, whereas this method returns
539
- # every entry. Callers that read custom keys should filter
540
- # {Git::Configuring#config_list} instead, which returns the same hash
541
- # (shown here for the `origin` remote):
542
- #
543
- # prefix = 'remote.origin.'
544
- # repo.config_list
545
- # .select { |entry| entry.key.start_with?(prefix) }
546
- # .to_h { |entry| [entry.key.delete_prefix(prefix), entry.value] }
547
- #
548
- # @see #remote_list
549
- #
550
- # @see Git::Configuring#config_list
551
- #
552
- def config_remote(name)
553
- Git::Deprecation.warn(
554
- 'Git::Repository#config_remote is deprecated and will be removed in v6.0.0. ' \
555
- 'Use Git::Repository#remote_list.find { |r| r.name == name } for the fields ' \
556
- 'Git::RemoteInfo models, or filter Git::Repository#config_list on the ' \
557
- '"remote.<name>." key prefix to keep every entry.'
558
- )
559
- prefix = "remote.#{name}."
560
- Private.config_list(@execution_context).each_with_object({}) do |(key, value), hsh|
561
- hsh[key.delete_prefix(prefix)] = value if key.start_with?(prefix)
562
- end
563
- end
564
-
565
437
  # List all configured remotes as {Git::RemoteInfo} objects
566
438
  #
567
439
  # Reads the repository configuration via {Git::Configuring#config_list} and
@@ -590,67 +462,6 @@ module Git
590
462
  Git::Parsers::Remote.parse_list(config_list)
591
463
  end
592
464
 
593
- # Returns a {Git::Remote} object for the named remote
594
- #
595
- # @example Get the default 'origin' remote
596
- # repo.remote #=> #<Git::Remote 'origin'>
597
- #
598
- # @example Get a named remote
599
- # repo.remote('upstream') #=> #<Git::Remote 'upstream'>
600
- #
601
- # @param name [String] the remote name (defaults to `'origin'`)
602
- #
603
- # @return [Git::Remote] the remote object
604
- #
605
- # @raise [Git::FailedError] if git exits with a non-zero exit status
606
- #
607
- # @deprecated Use `remote_list.find { |r| r.name == name }` instead
608
- #
609
- # {#remote_list} returns immutable {Git::RemoteInfo} value objects
610
- # rather than {Git::Remote}. Call the corresponding {Git::Repository}
611
- # method (e.g. {#fetch}, {#remote_remove}) for operations on a remote.
612
- #
613
- # @see #remote_list
614
- #
615
- def remote(name = 'origin')
616
- Git::Deprecation.warn(
617
- 'Git::Repository#remote is deprecated and will be removed in v6.0.0. ' \
618
- 'Use Git::Repository#remote_list.find { |r| r.name == name } instead.'
619
- )
620
- Git::Remote.new(self, name)
621
- end
622
-
623
- # Returns all configured remotes as {Git::Remote} objects
624
- #
625
- # @example List all remotes
626
- # repo.remotes #=> [#<Git::Remote 'origin'>, #<Git::Remote 'upstream'>]
627
- #
628
- # @return [Array<Git::Remote>] one {Git::Remote} for each configured remote
629
- #
630
- # Returns an empty array when no remotes are configured.
631
- #
632
- # @raise [Git::FailedError] if git exits with a non-zero exit status
633
- #
634
- # @deprecated Use {#remote_list} instead
635
- #
636
- # {#remote_list} returns `Array<Git::RemoteInfo>` (immutable value
637
- # objects) rather than `Array<Git::Remote>`. Call the corresponding
638
- # {Git::Repository} method (e.g. {#fetch}, {#remote_remove}) for
639
- # operations on a remote. Each {Git::Remote} this method constructs
640
- # emits its own deprecation warning, so a call produces one warning
641
- # for this method plus one per remote returned.
642
- #
643
- # @see #remote_list
644
- #
645
- def remotes
646
- Git::Deprecation.warn(
647
- 'Git::Repository#remotes is deprecated and will be removed in v6.0.0. ' \
648
- 'Use Git::Repository#remote_list instead.'
649
- )
650
- result = Git::Commands::Remote::List.new(@execution_context).call
651
- result.stdout.split("\n").map { |name| Git::Remote.new(self, name) }
652
- end
653
-
654
465
  # Returns the names of all configured remotes
655
466
  #
656
467
  # Lists remote names by running `git remote`. This is a lightweight
@@ -954,29 +765,6 @@ module Git
954
765
  normalized[:fetch] = normalized.delete(:with_fetch) if normalized.key?(:with_fetch)
955
766
  normalized
956
767
  end
957
-
958
- # Retrieve all config entries as a flat hash
959
- #
960
- # Runs `git config --list` and parses each `key=value` line into a hash.
961
- # When no value is present for a key, the value defaults to an empty string.
962
- #
963
- # @param execution_context [Git::ExecutionContext::Repository] the
964
- # execution context for the repository
965
- #
966
- # @return [Hash{String => String}] all visible config entries, keyed by
967
- # their full dotted key names
968
- #
969
- # For example, `"remote.origin.url"` is a valid key.
970
- #
971
- # @api private
972
- #
973
- def config_list(execution_context)
974
- lines = Git::Commands::ConfigOptionSyntax::List.new(execution_context).call.stdout.split("\n")
975
- lines.each_with_object({}) do |line, hsh|
976
- key, value = line.split('=', 2)
977
- hsh[key] = value || ''
978
- end
979
- end
980
768
  end
981
769
  private_constant :Private
982
770
  end
@@ -92,38 +92,6 @@ module Git
92
92
  Git::Commands::Reset.new(@execution_context).call(commitish, **opts).stdout
93
93
  end
94
94
 
95
- # Reset the current HEAD to a specified state with `--hard`
96
- #
97
- # @example Hard reset to HEAD
98
- # repo.reset_hard
99
- #
100
- # @example Hard reset to a specific commit
101
- # repo.reset_hard('HEAD~1')
102
- #
103
- # @param commitish [String, nil] the commit or tree-ish to reset to;
104
- # defaults to HEAD when `nil`
105
- #
106
- # @param opts [Hash] options passed through to {#reset}
107
- #
108
- # @option opts [Boolean, nil] :hard (nil) ignored; this method always forces
109
- # `hard: true`
110
- #
111
- # @return [String] git's stdout from the reset
112
- #
113
- # @raise [ArgumentError] when unsupported options are provided
114
- #
115
- # @raise [Git::FailedError] when git exits with a non-zero exit status
116
- #
117
- # @deprecated Use {#reset} with `hard: true` instead
118
- #
119
- def reset_hard(commitish = nil, opts = {})
120
- Git::Deprecation.warn(
121
- 'Git::Repository#reset_hard is deprecated and will be removed in v6.0.0. ' \
122
- 'Use Git::Repository#reset(commitish, hard: true) instead.'
123
- )
124
- reset(commitish, **opts, hard: true)
125
- end
126
-
127
95
  # Apply a patch file to the working tree
128
96
  #
129
97
  # Reads the unified diff in `file` and applies it to the working tree via
@@ -164,6 +132,11 @@ module Git
164
132
  #
165
133
  # @raise [Git::FailedError] when git exits with a non-zero exit status
166
134
  #
135
+ # @note If a patch fails to apply, the `git am` session is left in progress
136
+ # until the caller continues, skips, or aborts it. The patches applied
137
+ # before it are already committed on the current branch; aborting the
138
+ # session removes them again.
139
+ #
167
140
  def apply_mail(file)
168
141
  return unless File.exist?(file)
169
142
 
@@ -325,10 +298,6 @@ module Git
325
298
  end
326
299
 
327
300
  # Option keys accepted by {#clean}
328
- #
329
- # The deprecated `:ff` and `:force_force` keys are handled by
330
- # {Git::Repository::Staging::Private.migrate_clean_legacy_options} before this
331
- # whitelist is enforced, so they are intentionally absent here.
332
301
  CLEAN_ALLOWED_OPTS = %i[d force f dry_run n quiet q exclude e x X pathspec].freeze
333
302
  private_constant :CLEAN_ALLOWED_OPTS
334
303
 
@@ -377,13 +346,11 @@ module Git
377
346
  #
378
347
  # @return [String] git's stdout from the clean
379
348
  #
380
- # @raise [ArgumentError] when unsupported options are provided, or when a
381
- # deprecated `:ff`/`:force_force` value is not `true`, `false`, or `nil`
349
+ # @raise [ArgumentError] when unsupported options are provided
382
350
  #
383
351
  # @raise [Git::FailedError] when git exits with a non-zero exit status
384
352
  #
385
353
  def clean(opts = {})
386
- opts = Private.migrate_clean_legacy_options(opts)
387
354
  SharedPrivate.assert_valid_opts!(CLEAN_ALLOWED_OPTS, **opts)
388
355
  Git::Commands::Clean.new(@execution_context).call(**opts).stdout
389
356
  end
@@ -417,151 +384,6 @@ module Git
417
384
  module Private
418
385
  module_function
419
386
 
420
- # Translate deprecated `git clean` option keys into their modern form
421
- #
422
- # Maps the legacy `:ff` and `:force_force` boolean options onto the
423
- # `:force` option, emitting a deprecation warning for each.
424
- #
425
- # @param opts [Hash] the caller-provided clean options
426
- #
427
- # @option opts [Boolean, nil] :ff (nil) deprecated alias for requesting a
428
- # double-force clean
429
- #
430
- # @option opts [Boolean, nil] :force_force (nil) deprecated alias for
431
- # requesting a double-force clean
432
- #
433
- # @option opts [Boolean, Integer, nil] :force (nil) existing force value
434
- # merged with deprecated options when present
435
- #
436
- # @return [Hash] a new options hash with deprecated keys translated
437
- #
438
- # @raise [ArgumentError] when a deprecated value is not `true`, `false`, or `nil`
439
- #
440
- def migrate_clean_legacy_options(opts)
441
- opts = deprecate_clean_option(
442
- :ff,
443
- ':ff option is deprecated and will be removed in v6.0.0. Use force: 2 instead.',
444
- opts
445
- )
446
-
447
- deprecate_clean_option(
448
- :force_force,
449
- ':force_force option is deprecated and will be removed in v6.0.0. Use force: 2 instead.',
450
- opts
451
- )
452
- end
453
-
454
- # Translate a single deprecated clean option key onto `:force`
455
- #
456
- # @param key [Symbol] the deprecated option key (`:ff` or `:force_force`)
457
- #
458
- # @param message [String] the deprecation message to emit
459
- #
460
- # @param opts [Hash] the clean options
461
- #
462
- # @option opts [Boolean, nil] :ff (nil) deprecated alias for requesting a
463
- # double-force clean
464
- #
465
- # @option opts [Boolean, nil] :force_force (nil) deprecated alias for
466
- # requesting a double-force clean
467
- #
468
- # @option opts [Boolean, Integer, nil] :force (nil) existing force value
469
- # updated when the deprecated key is true
470
- #
471
- # @return [Hash] a new options hash with the deprecated key removed
472
- #
473
- # @raise [ArgumentError] when the deprecated value is not `true`, `false`,
474
- # or `nil`
475
- #
476
- def deprecate_clean_option(key, message, opts)
477
- return opts unless opts.key?(key)
478
-
479
- opts = opts.dup
480
- deprecated_value = opts.delete(key)
481
- validate_deprecated_clean_option_value!(key, deprecated_value)
482
-
483
- Git::Deprecation.warn(message)
484
- return opts unless deprecated_value
485
-
486
- opts[:force] = merge_clean_force_option(opts[:force], force_specified: force_option_specified?(opts))
487
- opts
488
- end
489
-
490
- # Whether the caller explicitly set a non-nil `:force` value
491
- #
492
- # @param opts [Hash] the clean options
493
- #
494
- # @option opts [Boolean, Integer, nil] :force (nil) the clean force value
495
- # to inspect
496
- #
497
- # @return [Boolean] true if `:force` was set to a non-nil value, false
498
- # otherwise
499
- #
500
- def force_option_specified?(opts)
501
- opts.key?(:force) && !opts[:force].nil?
502
- end
503
-
504
- # Validate the value passed to a deprecated clean option
505
- #
506
- # @param key [Symbol] the deprecated option key
507
- #
508
- # @param value [Object] the value provided for the deprecated key
509
- #
510
- # @return [void]
511
- #
512
- # @raise [ArgumentError] when `value` is not `true`, `false`, or `nil`
513
- #
514
- def validate_deprecated_clean_option_value!(key, value)
515
- return if value.nil? || value == true || value == false
516
-
517
- raise ArgumentError, "#{key} option only accepts true, false, or nil"
518
- end
519
-
520
- # Merge a deprecated force request into the existing `:force` value
521
- #
522
- # @param existing_force [Boolean, Integer, nil] the caller's `:force` value
523
- #
524
- # @param force_specified [Boolean] whether the caller explicitly set `:force`
525
- #
526
- # @return [Integer] the resolved `:force` value
527
- #
528
- def merge_clean_force_option(existing_force, force_specified: false)
529
- return 2 unless force_specified
530
-
531
- normalized_force = normalize_clean_force_option(existing_force)
532
-
533
- case normalized_force
534
- when Integer then merge_integer_clean_force_option(normalized_force)
535
- when false then 2
536
- else normalized_force
537
- end
538
- end
539
-
540
- # Merge an integer `:force` value with the deprecated force request
541
- #
542
- # @param normalized_force [Integer] the caller's normalized `:force` value
543
- #
544
- # @return [Integer] the resolved `:force` value
545
- #
546
- def merge_integer_clean_force_option(normalized_force)
547
- return normalized_force if normalized_force < 1
548
-
549
- [normalized_force, 2].max
550
- end
551
-
552
- # Normalize a `:force` value, coercing `true` to the integer `1`
553
- #
554
- # @param value [Boolean, Integer, nil] the `:force` value
555
- #
556
- # @return [Integer, Boolean, nil] the normalized value
557
- #
558
- def normalize_clean_force_option(value)
559
- case value
560
- when true then 1
561
- else value
562
- end
563
- end
564
-
565
387
  # Unescape a git-quoted path
566
388
  #
567
389
  # Git wraps paths containing non-ASCII or special characters in