git 5.7.0 → 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 -35
  3. data/CONTRIBUTING.md +44 -31
  4. data/README.md +138 -100
  5. data/UPGRADING.md +867 -262
  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,9 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require 'pathname'
4
- require 'git/branch'
5
4
  require 'git/branch_info'
6
- require 'git/branches'
7
5
  require 'git/commands/branch/create'
8
6
  require 'git/commands/branch/delete'
9
7
  require 'git/commands/branch/list'
@@ -175,6 +173,10 @@ module Git
175
173
  #
176
174
  # @raise [Git::FailedError] if git exits with a non-zero exit status
177
175
  #
176
+ # @note A refused checkout leaves HEAD, the index, and the working tree as
177
+ # they were, and the branch `:new_branch` or `:orphan` names is not
178
+ # created.
179
+ #
178
180
  def checkout(branch = nil, opts = {})
179
181
  if branch.is_a?(Hash) && opts.empty?
180
182
  opts = branch
@@ -197,18 +199,13 @@ module Git
197
199
  # checked out again. The hard reset discards changes to tracked files only;
198
200
  # untracked files created by the block are left in place.
199
201
  #
200
- # Unlike `Git::Branch#in_branch`, this method does not create `branch`. The
201
- # branch must be an existing local branch. Unlike {#checkout}, a commit SHA,
202
- # tag, or remote-tracking branch is rejected before any checkout happens:
203
- # those detach HEAD, and a commit made there would be left dangling once the
204
- # original branch is restored. HEAD must
205
- # be on a branch with at least one commit, or detached: an unborn branch (no
206
- # commits yet) cannot be checked out again by name, so it is rejected before
207
- # any checkout happens.
208
- #
209
- # **Note:** the restore checkout is not wrapped in `ensure`. If the block,
210
- # the commit, or the reset raises an exception, the repository is left
211
- # checked out on `branch` rather than restored to the original branch.
202
+ # This method does not create `branch`: it must be an existing local
203
+ # branch. Unlike {#checkout}, a commit SHA, tag, or remote-tracking branch
204
+ # is rejected before any checkout happens: those detach HEAD, and a commit
205
+ # made there would be left dangling once the original branch is restored.
206
+ # HEAD must be on a branch with at least one commit, or detached: an unborn
207
+ # branch (no commits yet) cannot be checked out again by name, so it is
208
+ # rejected before any checkout happens.
212
209
  #
213
210
  # @example Commit a new file on a feature branch
214
211
  # repo.in_branch('feature', 'Add README') do
@@ -242,6 +239,11 @@ module Git
242
239
  # @yieldreturn [Object] a truthy value to commit all changes, a falsy value to
243
240
  # hard-reset
244
241
  #
242
+ # @note If the block, the commit, the reset, or the restore checkout raises
243
+ # an exception, the repository is left checked out on `branch` rather than
244
+ # restored to the original branch. When the restore checkout is the step
245
+ # that fails, the commit or reset has already run.
246
+ #
245
247
  def in_branch(branch, message = 'in branch work')
246
248
  SharedPrivate.assert_local_branch!(self, branch)
247
249
  restore_point = SharedPrivate.head_restore_point(self)
@@ -251,6 +253,7 @@ module Git
251
253
  else
252
254
  reset(nil, hard: true)
253
255
  end
256
+ # Runs only on success. See ADR-0009.
254
257
  checkout(restore_point)
255
258
  end
256
259
 
@@ -283,6 +286,10 @@ module Git
283
286
  #
284
287
  # @raise [Git::FailedError] if git exits with a non-zero exit status
285
288
  #
289
+ # @note git does not stop at the first path it cannot write, so a failure
290
+ # leaves the files it did write in place, under `:prefix` when one is
291
+ # given.
292
+ #
286
293
  def checkout_index(options = {})
287
294
  SharedPrivate.assert_valid_opts!(CHECKOUT_INDEX_ALLOWED_OPTS, **options)
288
295
 
@@ -344,71 +351,6 @@ module Git
344
351
  local_branch?(branch) || remote_branch?(branch)
345
352
  end
346
353
 
347
- # Checks whether the named branch exists locally
348
- #
349
- # @example Check whether main exists locally
350
- # repo.is_local_branch?('main') # => true
351
- #
352
- # @param branch [String] the local branch name to look up
353
- #
354
- # @return [Boolean] `true` if the branch exists locally, `false` otherwise
355
- #
356
- # @raise [Git::FailedError] if git exits with a non-zero exit status
357
- #
358
- # @deprecated use {#local_branch?} instead
359
- #
360
- def is_local_branch?(branch) # rubocop:disable Naming/PredicatePrefix
361
- Git::Deprecation.warn(
362
- 'Git::Repository#is_local_branch? is deprecated and will be removed in v6.0.0. ' \
363
- 'Use Git::Repository#local_branch? instead.'
364
- )
365
- local_branch?(branch)
366
- end
367
-
368
- # Checks whether the named branch exists as a remote-tracking branch
369
- #
370
- # @example Check whether master exists on any remote
371
- # repo.is_remote_branch?('master') # => true
372
- #
373
- # @param branch [String] the short branch name to look up across all remotes
374
- #
375
- # @return [Boolean] `true` if a remote-tracking branch with that short name
376
- # exists, `false` otherwise
377
- #
378
- # @raise [Git::FailedError] if git exits with a non-zero exit status
379
- #
380
- # @deprecated use {#remote_branch?} instead
381
- #
382
- def is_remote_branch?(branch) # rubocop:disable Naming/PredicatePrefix
383
- Git::Deprecation.warn(
384
- 'Git::Repository#is_remote_branch? is deprecated and will be removed in v6.0.0. ' \
385
- 'Use Git::Repository#remote_branch? instead.'
386
- )
387
- remote_branch?(branch)
388
- end
389
-
390
- # Checks whether the named branch exists locally or as a remote-tracking branch
391
- #
392
- # @example Check whether main exists anywhere
393
- # repo.is_branch?('main') # => true
394
- #
395
- # @param branch [String] the branch name to look up
396
- #
397
- # @return [Boolean] `true` if the branch exists locally or remotely,
398
- # `false` otherwise
399
- #
400
- # @raise [Git::FailedError] if git exits with a non-zero exit status
401
- #
402
- # @deprecated use {#branch?} instead
403
- #
404
- def is_branch?(branch) # rubocop:disable Naming/PredicatePrefix
405
- Git::Deprecation.warn(
406
- 'Git::Repository#is_branch? is deprecated and will be removed in v6.0.0. ' \
407
- 'Use Git::Repository#branch? instead.'
408
- )
409
- branch?(branch)
410
- end
411
-
412
354
  # Option keys accepted by {#branch_new}
413
355
  #
414
356
  BRANCH_NEW_ALLOWED_OPTS = %i[].freeze
@@ -624,30 +566,6 @@ module Git
624
566
  Git::Parsers::Branch.parse_list(result.stdout, remote_names:)
625
567
  end
626
568
 
627
- # Returns all local and remote-tracking branches in the 4.x-compatible format
628
- #
629
- # Each entry is a 4-element array: `[refname, current, worktree, symref]`.
630
- # The `refname` uses the short form (`main`, `remotes/origin/main`) to
631
- # match the output of the legacy `Git::Lib#branches_all` method.
632
- #
633
- # @return [Array<Array>] array of `[refname, current, worktree, symref]` tuples
634
- #
635
- # @raise [Git::FailedError] if git exits with a non-zero exit status
636
- #
637
- # @deprecated Use {#branch_list} instead, which returns richer
638
- # {Git::BranchInfo} objects.
639
- #
640
- def branches_all
641
- Git::Deprecation.warn(
642
- 'Git::Repository#branches_all is deprecated and will be removed in v6.0.0. ' \
643
- 'Use Git::Repository#branch_list instead.'
644
- )
645
- branch_list.map do |info|
646
- refname = info.remote? ? "remotes/#{info.remote_name}/#{info.short_name}" : info.short_name
647
- [refname, info.current, info.other_worktree?, info.symref]
648
- end
649
- end
650
-
651
569
  # Update a branch ref to point to a new commit
652
570
  #
653
571
  # Derives the full ref from the `branch` argument:
@@ -683,89 +601,6 @@ module Git
683
601
  Git::Commands::UpdateRef::Update.new(@execution_context).call(ref, commit)
684
602
  end
685
603
 
686
- # Returns a {Git::Branch} object for the given branch name
687
- #
688
- # @example Get a branch object for 'main'
689
- # repo.branch('main') #=> #<Git::Branch 'main'>
690
- #
691
- # @example Get a branch object for the current branch
692
- # repo.branch #=> #<Git::Branch 'main'>
693
- #
694
- # @param branch_name [String] the branch name (defaults to the current branch)
695
- #
696
- # @return [Git::Branch] the branch object
697
- #
698
- # @raise [Git::FailedError] if git exits with a non-zero exit status
699
- #
700
- # @deprecated Use `branch_list(name).first` and the name-based branch
701
- # operations instead
702
- #
703
- # {#branch_list} returns immutable {Git::BranchInfo} value objects
704
- # rather than {Git::Branch}. It takes `git branch --list` patterns, so
705
- # pass the short name of a local branch or `"#{remote}/#{name}"` for a
706
- # remote-tracking branch; the `remotes/` and `refs/` prefixes this
707
- # method accepts match nothing. A `"#{remote}/#{name}"` pattern also
708
- # matches a local branch of that name, so take `find(&:remote?)` rather
709
- # than `first` for a remote-tracking branch. With no argument this
710
- # method wraps {#current_branch}, which is `'HEAD'` when HEAD is
711
- # detached; {#branch_list} has no entry for a detached or unborn HEAD,
712
- # so use {#current_branch_state} in those states. Call the
713
- # corresponding {Git::Repository} method (e.g. {#checkout},
714
- # {#branch_new}, {#branch_delete}) for operations on a branch.
715
- #
716
- # @see #branch_list
717
- #
718
- def branch(branch_name = current_branch)
719
- Git::Deprecation.warn(
720
- 'Git::Repository#branch is deprecated and will be removed in v6.0.0. ' \
721
- 'Use Git::Repository#branch_list(name).first for a local branch, ' \
722
- 'Git::Repository#branch_list("remote/name").find(&:remote?) for a remote-tracking branch, ' \
723
- 'and the name-based branch operations instead.'
724
- )
725
- Git::Branch.new(self, branch_name)
726
- end
727
-
728
- # Returns a {Git::Branches} collection of all branches in the repository
729
- #
730
- # @example List all branches
731
- # repo.branches
732
- # # => #<Git::Branches ...>
733
- #
734
- # @example Iterate over all branches
735
- # repo.branches.each { |b| puts b.name }
736
- #
737
- # @example Access local branches only
738
- # repo.branches.local
739
- #
740
- # @example Access remote-tracking branches only
741
- # repo.branches.remote
742
- #
743
- # @example Look up a branch by name
744
- # repo.branches['main'] # => #<Git::Branch 'main'>
745
- #
746
- # @return [Git::Branches] a collection wrapping all local and
747
- # remote-tracking branches in the repository
748
- #
749
- # @raise [Git::FailedError] if git exits with a non-zero exit status
750
- #
751
- # @deprecated Use {#branch_list} instead
752
- #
753
- # {#branch_list} returns `Array<Git::BranchInfo>` (immutable value
754
- # objects) rather than a {Git::Branches} collection. Filter it with
755
- # `select(&:remote?)` or `reject(&:remote?)` in place of
756
- # `branches.remote` and `branches.local`, and look a branch up by name
757
- # with `branch_list(name).first` in place of `branches[name]`.
758
- #
759
- # @see #branch_list
760
- #
761
- def branches
762
- Git::Deprecation.warn(
763
- 'Git::Repository#branches is deprecated and will be removed in v6.0.0. ' \
764
- 'Use Git::Repository#branch_list instead.'
765
- )
766
- Git::Branches.new(self)
767
- end
768
-
769
604
  # Private helpers local to {Git::Repository::Branching}
770
605
  #
771
606
  # @api private
@@ -939,9 +774,6 @@ module Git
939
774
 
940
775
  # Builds the full git ref string from a branch name argument
941
776
  #
942
- # Mirrors the routing logic of `Git::Branch#update_ref` for backward
943
- # compatibility:
944
- #
945
777
  # - `remotes/<remote>/<name>` or `refs/remotes/<remote>/<name>` →
946
778
  # `refs/remotes/<remote>/<name>`
947
779
  # - Any other value → `refs/heads/<branch>`
@@ -73,7 +73,6 @@ module Git
73
73
  # @raise [Git::FailedError] when git exits with a non-zero exit status
74
74
  #
75
75
  def commit(message, opts = {})
76
- opts = normalize_commit_options(opts)
77
76
  SharedPrivate.assert_valid_opts!(COMMIT_ALLOWED_OPTS, **opts)
78
77
 
79
78
  call_opts = { no_edit: true }
@@ -217,30 +216,6 @@ module Git
217
216
  def write_and_commit_tree(opts = {})
218
217
  commit_tree(write_tree, opts)
219
218
  end
220
-
221
- private
222
-
223
- # Normalizes deprecated commit options into their supported form
224
- #
225
- # @param opts [Hash] caller-provided commit options
226
- #
227
- # @option opts [Boolean, nil] :add_all (nil) deprecated alias for `:all`
228
- #
229
- # @return [Hash] options with deprecated aliases translated
230
- #
231
- # @api private
232
- #
233
- def normalize_commit_options(opts)
234
- opts = opts.dup
235
- return opts unless opts.key?(:add_all)
236
-
237
- Git::Deprecation.warn(
238
- 'The :add_all option for #commit is deprecated and will be removed in v6.0.0. ' \
239
- 'Use :all instead.'
240
- )
241
- opts[:all] = opts.delete(:add_all)
242
- opts
243
- end
244
219
  end
245
220
  end
246
221
  end