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
@@ -299,7 +299,7 @@ module Git
299
299
  #
300
300
  # @api private
301
301
  #
302
- DIFF_PATH_STATUS_ALLOWED_OPTS = %i[path_limiter path].freeze
302
+ DIFF_PATH_STATUS_ALLOWED_OPTS = %i[path_limiter].freeze
303
303
  private_constant :DIFF_PATH_STATUS_ALLOWED_OPTS
304
304
 
305
305
  # Returns the file path status between two trees
@@ -349,9 +349,6 @@ module Git
349
349
  # @option opts [String, Pathname, Array<String, Pathname>, nil] :path_limiter (nil)
350
350
  # limit the status report to the given path(s)
351
351
  #
352
- # @option opts [String, Pathname, Array<String, Pathname>, nil] :path (nil)
353
- # **deprecated** — use `:path_limiter` instead
354
- #
355
352
  # @return [Git::DiffPathStatus] the name-status report for the comparison
356
353
  #
357
354
  # @raise [ArgumentError] if unsupported options are provided
@@ -366,50 +363,12 @@ module Git
366
363
  SharedPrivate.assert_valid_opts!(DIFF_PATH_STATUS_ALLOWED_OPTS, **opts)
367
364
  raise ArgumentError, 'Invalid arguments: `from` is nil but `to` is not' if from.nil? && !to.nil?
368
365
 
369
- path_limiter = Private.resolve_path_limiter(opts)
370
- pathspecs = Private.normalize_pathspecs(path_limiter, 'path limiter')
366
+ pathspecs = Private.normalize_pathspecs(opts[:path_limiter], 'path limiter')
371
367
 
372
368
  result = Private.call_diff_command(@execution_context, from, to, pathspecs)
373
369
  Git::DiffPathStatus.new(Private.extract_name_status_from_raw(result.stdout))
374
370
  end
375
371
 
376
- # Reports each changed path with its status (deprecated name for {#diff_path_status})
377
- #
378
- # @example Report path statuses between HEAD and the working tree
379
- # repo.diff_name_status.to_h #=> { "lib/foo.rb" => "M" }
380
- #
381
- # @param from [String, nil] the first commit or object to compare
382
- #
383
- # @param to [String, nil] the second commit or object to compare
384
- #
385
- # @param opts [Hash] the options accepted by {#diff_path_status}
386
- #
387
- # @option opts [String, Pathname, Array<String, Pathname>, nil] :path_limiter (nil)
388
- # limit the status report to the given path(s)
389
- #
390
- # @option opts [String, Pathname, Array<String, Pathname>, nil] :path (nil)
391
- # **deprecated** — use `:path_limiter` instead
392
- #
393
- # @return [Git::DiffPathStatus] the name-status report for the comparison
394
- #
395
- # @raise [ArgumentError] if unsupported options are provided
396
- #
397
- # @raise [ArgumentError] if `from` is `nil` but `to` is not OR if `from` or `to` starts with `"-"`
398
- #
399
- # @raise [Git::FailedError] if git exits outside the allowed range (exit code > 2)
400
- #
401
- # @deprecated Use {#diff_path_status} instead. Will be removed in v6.0.0.
402
- #
403
- # @see #diff_path_status
404
- #
405
- def diff_name_status(from = 'HEAD', to = nil, opts = {})
406
- Git::Deprecation.warn(
407
- 'Git::Repository#diff_name_status is deprecated and will be removed in v6.0.0. ' \
408
- 'Use Git::Repository#diff_path_status instead.'
409
- )
410
- diff_path_status(from, to, opts)
411
- end
412
-
413
372
  # Compares the index and the working directory
414
373
  #
415
374
  # Runs `git diff-files` to list files that differ between the index
@@ -523,35 +482,6 @@ module Git
523
482
  module Private
524
483
  module_function
525
484
 
526
- # Resolves the effective path limiter from the options hash
527
- #
528
- # When `:path_limiter` is present it is used directly and no warning is
529
- # emitted. When only `:path` is present a deprecation warning is emitted
530
- # and its value is used. Returns `nil` when neither key is present.
531
- #
532
- # @param opts [Hash] the options hash from {#diff_path_status}
533
- #
534
- # @option opts [String, Pathname, Array<String, Pathname>, nil] :path_limiter (nil)
535
- # primary path limiter value used as-is when present
536
- #
537
- # @option opts [String, Pathname, Array<String, Pathname>, nil] :path (nil)
538
- # **deprecated** — fallback path limiter when `:path_limiter` is not provided
539
- #
540
- # @return [String, Pathname, Array<String, Pathname>, nil]
541
- # the effective path limiter
542
- #
543
- def resolve_path_limiter(opts)
544
- if opts.key?(:path_limiter)
545
- opts[:path_limiter]
546
- elsif opts.key?(:path)
547
- Git::Deprecation.warn(
548
- 'Git::Repository#diff_path_status :path option is deprecated and will be removed in v6.0.0. ' \
549
- 'Use :path_limiter instead.'
550
- )
551
- opts[:path]
552
- end
553
- end
554
-
555
485
  # Extracts only the patch text from combined diff command output
556
486
  #
557
487
  # When {Git::Commands::Diff} is called with `patch: true, numstat: true,
@@ -7,6 +7,7 @@ require 'git/commands/merge_base'
7
7
  require 'git/commands/revert/start'
8
8
  require 'git/commands/show'
9
9
  require 'git/repository/shared_private'
10
+ require 'git/system_call_guard'
10
11
 
11
12
  module Git
12
13
  class Repository
@@ -81,6 +82,10 @@ module Git
81
82
  #
82
83
  # @raise [Git::FailedError] when git exits with a non-zero exit status
83
84
  #
85
+ # @note On a conflict, the merge is left in progress: the conflicted paths
86
+ # are in the index and working tree, and `MERGE_HEAD` is set until the
87
+ # caller commits the resolution or aborts the merge.
88
+ #
84
89
  def merge(branch, message = nil, opts = {})
85
90
  SharedPrivate.assert_valid_opts!(MERGE_ALLOWED_OPTS, **opts)
86
91
 
@@ -126,18 +131,13 @@ module Git
126
131
  # branch is checked out, so those failures never leave the repository on
127
132
  # `target_branch`. Anything rejected later, such as an option value that is
128
133
  # not accepted or a source ref that does not exist, surfaces inside {#merge}
129
- # after the checkout and follows the Note below.
134
+ # after the checkout and leaves the repository checked out on `target_branch`.
130
135
  #
131
136
  # The `:no_commit` option is not accepted: a merge stopped before its commit
132
137
  # would leave `target_branch` unchanged and the restore checkout would carry
133
138
  # the staged result onto the original branch. To merge without committing,
134
139
  # call {#checkout} and {#merge} directly.
135
140
  #
136
- # **Note:** the restore checkout is not wrapped in `ensure`. If the merge
137
- # fails (for example, on a conflict), the repository is left checked out on
138
- # `target_branch` with the merge in progress rather than restored to the
139
- # original branch.
140
- #
141
141
  # @example Merge a feature branch into main while staying on the current branch
142
142
  # repo.merge_into('main', 'feature')
143
143
  #
@@ -179,6 +179,12 @@ module Git
179
179
  #
180
180
  # @raise [Git::FailedError] when git exits with a non-zero exit status
181
181
  #
182
+ # @note If the merge or the restore checkout fails, the repository is left
183
+ # checked out on `target_branch` rather than restored to the original
184
+ # branch. On a conflict, the merge is also left in progress. When the
185
+ # restore checkout is the step that fails, the merge has already been
186
+ # committed on `target_branch`.
187
+ #
182
188
  def merge_into(target_branch, branch, message = nil, opts = {})
183
189
  SharedPrivate.assert_valid_opts!(MERGE_INTO_ALLOWED_OPTS, **opts)
184
190
  raise ArgumentError, 'at least one branch to merge is required' if Array(branch).empty?
@@ -187,6 +193,7 @@ module Git
187
193
  restore_point = SharedPrivate.head_restore_point(self)
188
194
  checkout(target_branch)
189
195
  output = merge(branch, message, opts)
196
+ # Runs only on success. See ADR-0009.
190
197
  checkout(restore_point)
191
198
  output
192
199
  end
@@ -278,6 +285,9 @@ module Git
278
285
  # @raise [Git::FailedError] when `git diff --cached` exits outside the
279
286
  # allowed range (exit code > 2)
280
287
  #
288
+ # @raise [Git::Error] when the staged content cannot be written to a
289
+ # temporary file
290
+ #
281
291
  # @yield [file, your_version, their_version] passes conflict details for
282
292
  # each unmerged file
283
293
  #
@@ -302,49 +312,6 @@ module Git
302
312
  end
303
313
  end
304
314
 
305
- # Iterate over files with merge conflicts, yielding conflict details for each
306
- #
307
- # For each unmerged file, the staged content for both sides of the conflict
308
- # (stage 2 "ours" and stage 3 "theirs") is written to temporary files whose
309
- # paths are yielded alongside the file path. The temporary files are deleted
310
- # automatically when the block returns.
311
- #
312
- # @example Inspect conflicting files
313
- # repo.conflicts do |file, your_version, their_version|
314
- # puts "Conflict in #{file}"
315
- # puts File.read(your_version)
316
- # puts File.read(their_version)
317
- # end
318
- #
319
- # @return [Array<String>] the list of unmerged file paths
320
- #
321
- # @raise [Git::FailedError] when `git diff --cached` exits outside the
322
- # allowed range (exit code > 2)
323
- #
324
- # @yield [file, your_version, their_version] passes conflict details for
325
- # each unmerged file
326
- #
327
- # @yieldparam file [String] path to the conflicting file, relative to the
328
- # working tree
329
- #
330
- # @yieldparam your_version [String] path to a temporary file containing the
331
- # stage-2 (ours) content for the conflicting file
332
- #
333
- # @yieldparam their_version [String] path to a temporary file containing the
334
- # stage-3 (theirs) content for the conflicting file
335
- #
336
- # @yieldreturn [void]
337
- #
338
- # @deprecated Use {#each_conflict} instead
339
- #
340
- def conflicts(&)
341
- Git::Deprecation.warn(
342
- 'Git::Repository#conflicts is deprecated and will be removed in v6.0.0. ' \
343
- 'Use Git::Repository#each_conflict instead.'
344
- )
345
- each_conflict(&)
346
- end
347
-
348
315
  # Option keys accepted by {#revert}
349
316
  #
350
317
  # Derived from the 4.x option map for `Git::Lib#revert`.
@@ -385,6 +352,12 @@ module Git
385
352
  #
386
353
  # @raise [Git::FailedError] when git exits with a non-zero exit status
387
354
  #
355
+ # @note On a conflict, the revert is left in progress with the conflicted
356
+ # paths in the index and working tree until the caller continues, skips,
357
+ # or aborts it. When more than one commit is reverted, the revert commits
358
+ # made before the conflict are on the branch; aborting the revert removes
359
+ # them again.
360
+ #
388
361
  def revert(commitish = nil, opts = {})
389
362
  commitish = 'HEAD' if commitish.nil?
390
363
  SharedPrivate.assert_valid_opts!(REVERT_ALLOWED_OPTS, **opts)
@@ -429,6 +402,9 @@ module Git
429
402
  #
430
403
  # @return [void]
431
404
  #
405
+ # @raise [Git::Error] if the staged content cannot be written to a
406
+ # temporary file
407
+ #
432
408
  # @yield [f] yields the open Tempfile containing the staged content
433
409
  #
434
410
  # @yieldparam f [Tempfile] open IO object for the staged content
@@ -437,11 +413,13 @@ module Git
437
413
  #
438
414
  # @api private
439
415
  #
440
- def write_staged_file(execution_context, file_path, stage)
441
- Tempfile.create([STAGE_PREFIXES[stage], File.basename(file_path)]) do |f|
442
- Git::Commands::Show.new(execution_context).call(":#{stage}:#{file_path}", out: f)
443
- f.flush
444
- yield f
416
+ def write_staged_file(execution_context, file_path, stage, &block)
417
+ Git::SystemCallGuard.call('Failed to write the staged content to a temporary file') do |guard|
418
+ Tempfile.create([STAGE_PREFIXES[stage], File.basename(file_path)]) do |f|
419
+ Git::Commands::Show.new(execution_context).call(":#{stage}:#{file_path}", out: f)
420
+ f.flush
421
+ guard.unguarded { block.call(f) }
422
+ end
445
423
  end
446
424
  end
447
425
  end