git 4.2.0 → 4.3.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: bd870e7039cc00b042255eaf275983499afc046343ddae22a0ae121c0ccae5e2
4
- data.tar.gz: c107f2b83705d0c5353328f011346c01dbed28671323e36258d73c3c5205f835
3
+ metadata.gz: e100c7e550894cd173db6f141a4a79963d1ccb832d028a459cf7e4193ae968ec
4
+ data.tar.gz: 9275a29a6a88c441e44084a9ba26dbbd0d40889784225635d410965dafaea2af
5
5
  SHA512:
6
- metadata.gz: 82be60353d5481abd360bc7c0a1b18ef5c6dacadfe9c6185d524d754e0203b4dd2a687942c8813962ccc329cea6c14014d0a77296801c7d557e62b9b91880f75
7
- data.tar.gz: b6aeeb8d2f12639ff26837de8ca5d910457b58b8356cd82c5843ebc4ea269f49b8cf0fe1b1e1c0f598304b8f82018416c5f2d701b84cc8f0494ce127316605c9
6
+ metadata.gz: e29086daa9c6089c6e7f1e4b889aae7cac9e1cd199937f3e8eb44e5b59dccaa3c484d85dab56f2da07172161b6e2f87269b78e5aab868b947a2f161e7c9de977
7
+ data.tar.gz: f09a9f3a7e7e9cdc218016737e2dc444392351191f456b6ace8dd32088c17517b43de0f93b6f6925e0d9ec2a8e8be9866d4b35b28420ebc7f6e43adc889e4bab
@@ -1,3 +1,3 @@
1
1
  {
2
- ".": "4.2.0"
2
+ ".": "4.3.0"
3
3
  }
data/.rubocop_todo.yml CHANGED
@@ -9,4 +9,4 @@
9
9
  # Offense count: 2
10
10
  # Configuration parameters: CountComments, CountAsOne.
11
11
  Metrics/ClassLength:
12
- Max: 1150
12
+ Max: 1160
data/CHANGELOG.md CHANGED
@@ -5,6 +5,13 @@
5
5
 
6
6
  # Change Log
7
7
 
8
+ ## [4.3.0](https://github.com/ruby-git/ruby-git/compare/v4.2.0...v4.3.0) (2026-01-15)
9
+
10
+
11
+ ### Features
12
+
13
+ * Add support for git fsck command ([c402003](https://github.com/ruby-git/ruby-git/commit/c402003ce0c626a5c4737838cddc4b932638c172)), closes [#218](https://github.com/ruby-git/ruby-git/issues/218)
14
+
8
15
  ## [4.2.0](https://github.com/ruby-git/ruby-git/compare/v4.1.2...v4.2.0) (2026-01-11)
9
16
 
10
17
 
data/README.md CHANGED
@@ -277,6 +277,20 @@ repo.worktrees.each do |worktree|
277
277
  worktree.to_s
278
278
  end
279
279
 
280
+ # Check repository integrity with fsck
281
+ result = repo.fsck
282
+ result.dangling.each { |obj| puts "dangling #{obj.type}: #{obj.sha}" }
283
+ result.missing.each { |obj| puts "missing #{obj.type}: #{obj.sha}" }
284
+
285
+ # Check if repository has any issues
286
+ puts "Repository is clean" if result.empty?
287
+
288
+ # fsck with options
289
+ result = repo.fsck(unreachable: true, strict: true)
290
+
291
+ # Suppress dangling object output
292
+ result = repo.fsck(dangling: false)
293
+
280
294
  repo.config('user.name') # returns 'Scott Chacon'
281
295
  repo.config # returns whole config hash
282
296
 
@@ -12,6 +12,14 @@ module Git
12
12
  ARG_BUILDERS = {
13
13
  boolean: ->(config, value) { value ? config[:flag] : [] },
14
14
 
15
+ boolean_negatable: lambda do |config, value|
16
+ case value
17
+ when true then config[:flag]
18
+ when false then config[:flag].sub('--', '--no-')
19
+ else []
20
+ end
21
+ end,
22
+
15
23
  valued_equals: ->(config, value) { "#{config[:flag]}=#{value}" if value },
16
24
 
17
25
  valued_space: ->(config, value) { [config[:flag], value.to_s] if value },
data/lib/git/base.rb CHANGED
@@ -654,6 +654,57 @@ module Git
654
654
  lib.gc
655
655
  end
656
656
 
657
+ # Verifies the connectivity and validity of objects in the database
658
+ #
659
+ # Runs `git fsck` to check repository integrity and identify dangling,
660
+ # missing, or unreachable objects.
661
+ #
662
+ # @overload fsck(objects = [], options = {})
663
+ # @param objects [Array<String>] specific objects to treat as heads for unreachability trace.
664
+ # If no objects are given, git fsck defaults to using the index file, all SHA-1
665
+ # references in the refs namespace, and all reflogs.
666
+ # @param [Hash] options options to pass to the underlying `git fsck` command
667
+ #
668
+ # @option options [Boolean] :unreachable print unreachable objects
669
+ # @option options [Boolean] :strict enable strict checking
670
+ # @option options [Boolean] :connectivity_only check only connectivity (faster)
671
+ # @option options [Boolean] :root report root nodes
672
+ # @option options [Boolean] :tags report tags
673
+ # @option options [Boolean] :cache consider objects in the index
674
+ # @option options [Boolean] :no_reflogs do not consider reflogs
675
+ # @option options [Boolean] :lost_found write dangling objects to .git/lost-found
676
+ # (note: this modifies the repository by creating files)
677
+ # @option options [Boolean, nil] :dangling print dangling objects (true/false/nil for default)
678
+ # @option options [Boolean, nil] :full check objects in alternate pools (true/false/nil for default)
679
+ # @option options [Boolean, nil] :name_objects name objects by refs (true/false/nil for default)
680
+ # @option options [Boolean, nil] :references check refs database consistency (true/false/nil for default)
681
+ #
682
+ # @return [Git::FsckResult] categorized objects flagged by fsck
683
+ #
684
+ # @example Check repository integrity
685
+ # result = git.fsck
686
+ # result.dangling.each { |obj| puts "#{obj.type}: #{obj.sha}" }
687
+ #
688
+ # @example Check with strict mode and suppress dangling output
689
+ # result = git.fsck(strict: true, dangling: false)
690
+ #
691
+ # @example Check if repository has any issues
692
+ # result = git.fsck
693
+ # puts "Repository is clean" if result.empty?
694
+ #
695
+ # @example List root commits
696
+ # result = git.fsck(root: true)
697
+ # result.root.each { |obj| puts obj.sha }
698
+ #
699
+ # @example Check specific objects
700
+ # result = git.fsck('abc1234', 'def5678')
701
+ #
702
+ # rubocop:disable Style/ArgumentsForwarding
703
+ def fsck(*objects, **opts)
704
+ lib.fsck(*objects, **opts)
705
+ end
706
+ # rubocop:enable Style/ArgumentsForwarding
707
+
657
708
  def apply(file)
658
709
  return unless File.exist?(file)
659
710
 
@@ -0,0 +1,48 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Git
4
+ # Represents an object returned by `git fsck`
5
+ #
6
+ # This class provides information about dangling, missing, unreachable, or
7
+ # problematic Git objects found during repository integrity checks.
8
+ #
9
+ # @api public
10
+ #
11
+ class FsckObject
12
+ # The type of the Git object
13
+ # @return [Symbol] one of :commit, :tree, :blob, or :tag
14
+ attr_reader :type
15
+
16
+ # The SHA-1 hash of the object
17
+ # @return [String] the 40-character SHA-1 hash
18
+ attr_reader :sha
19
+
20
+ # A warning or error message associated with this object
21
+ # @return [String, nil] the message, or nil if no message
22
+ attr_reader :message
23
+
24
+ # A name describing how the object is reachable (from --name-objects)
25
+ # @return [String, nil] the name, or nil if not provided
26
+ attr_reader :name
27
+
28
+ # Create a new FsckObject
29
+ #
30
+ # @param type [Symbol] the object type (:commit, :tree, :blob, or :tag)
31
+ # @param sha [String] the 40-character SHA-1 hash
32
+ # @param message [String, nil] optional warning/error message
33
+ # @param name [String, nil] optional name from --name-objects (e.g., "HEAD~2^2:src/")
34
+ #
35
+ def initialize(type:, sha:, message: nil, name: nil)
36
+ @type = type
37
+ @sha = sha
38
+ @message = message
39
+ @name = name
40
+ end
41
+
42
+ # Returns the SHA as the string representation
43
+ # @return [String] the SHA-1 hash
44
+ def to_s
45
+ sha
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,121 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Git
4
+ # Represents the result of running `git fsck`
5
+ #
6
+ # This class provides structured access to the objects found during a
7
+ # repository integrity check, categorized by their status.
8
+ #
9
+ # @api public
10
+ #
11
+ class FsckResult
12
+ # Objects not referenced by any other object
13
+ # @return [Array<Git::FsckObject>]
14
+ attr_reader :dangling
15
+
16
+ # Objects that are referenced but not present in the repository
17
+ # @return [Array<Git::FsckObject>]
18
+ attr_reader :missing
19
+
20
+ # Objects not reachable from any ref
21
+ # @return [Array<Git::FsckObject>]
22
+ attr_reader :unreachable
23
+
24
+ # Objects with warnings (each includes a message)
25
+ # @return [Array<Git::FsckObject>]
26
+ attr_reader :warnings
27
+
28
+ # Root nodes (commits with no parents) when --root is used
29
+ # @return [Array<Git::FsckObject>]
30
+ attr_reader :root
31
+
32
+ # Tagged objects when --tags is used
33
+ # @return [Array<Git::FsckObject>]
34
+ attr_reader :tagged
35
+
36
+ # rubocop:disable Metrics/ParameterLists
37
+
38
+ # Create a new FsckResult
39
+ #
40
+ # @param dangling [Array<Git::FsckObject>] dangling objects
41
+ # @param missing [Array<Git::FsckObject>] missing objects
42
+ # @param unreachable [Array<Git::FsckObject>] unreachable objects
43
+ # @param warnings [Array<Git::FsckObject>] objects with warnings
44
+ # @param root [Array<Git::FsckObject>] root nodes
45
+ # @param tagged [Array<Git::FsckObject>] tagged objects
46
+ #
47
+ def initialize(dangling: [], missing: [], unreachable: [], warnings: [], root: [], tagged: [])
48
+ @dangling = dangling
49
+ @missing = missing
50
+ @unreachable = unreachable
51
+ @warnings = warnings
52
+ @root = root
53
+ @tagged = tagged
54
+ end
55
+
56
+ # rubocop:enable Metrics/ParameterLists
57
+
58
+ # Returns true if any issues were found
59
+ #
60
+ # @return [Boolean]
61
+ #
62
+ # @example
63
+ # result = git.fsck
64
+ # puts "Repository has issues!" if result.any_issues?
65
+ #
66
+ def any_issues?
67
+ [dangling, missing, unreachable, warnings].any?(&:any?)
68
+ end
69
+
70
+ # Returns true if no issues were found
71
+ #
72
+ # @return [Boolean]
73
+ #
74
+ # @example
75
+ # result = git.fsck
76
+ # puts "Repository is clean" if result.empty?
77
+ #
78
+ def empty?
79
+ !any_issues?
80
+ end
81
+
82
+ # Returns all objects from all categories (excluding informational root/tagged)
83
+ #
84
+ # @return [Array<Git::FsckObject>]
85
+ #
86
+ # @example
87
+ # result = git.fsck
88
+ # result.all_objects.each { |obj| puts obj.sha }
89
+ #
90
+ def all_objects
91
+ dangling + missing + unreachable + warnings
92
+ end
93
+
94
+ # Returns the total number of issues found
95
+ #
96
+ # @return [Integer]
97
+ #
98
+ # @example
99
+ # result = git.fsck
100
+ # puts "Found #{result.count} issues"
101
+ #
102
+ def count
103
+ all_objects.size
104
+ end
105
+
106
+ # Returns a hash representation of the result
107
+ #
108
+ # @return [Hash{Symbol => Array<Git::FsckObject>}]
109
+ #
110
+ # @example
111
+ # result = git.fsck
112
+ # result.to_h # => { dangling: [...], missing: [...], ... }
113
+ #
114
+ def to_h
115
+ {
116
+ dangling: dangling, missing: missing, unreachable: unreachable,
117
+ warnings: warnings, root: root, tagged: tagged
118
+ }
119
+ end
120
+ end
121
+ end
data/lib/git/lib.rb CHANGED
@@ -1530,6 +1530,38 @@ module Git
1530
1530
  command('gc', '--prune', '--aggressive', '--auto')
1531
1531
  end
1532
1532
 
1533
+ FSCK_OPTION_MAP = [
1534
+ { flag: '--no-progress', type: :static },
1535
+ { keys: [:unreachable], flag: '--unreachable', type: :boolean },
1536
+ { keys: [:strict], flag: '--strict', type: :boolean },
1537
+ { keys: [:connectivity_only], flag: '--connectivity-only', type: :boolean },
1538
+ { keys: [:root], flag: '--root', type: :boolean },
1539
+ { keys: [:tags], flag: '--tags', type: :boolean },
1540
+ { keys: [:cache], flag: '--cache', type: :boolean },
1541
+ { keys: [:no_reflogs], flag: '--no-reflogs', type: :boolean },
1542
+ { keys: [:lost_found], flag: '--lost-found', type: :boolean },
1543
+ { keys: [:dangling], flag: '--dangling', type: :boolean_negatable },
1544
+ { keys: [:full], flag: '--full', type: :boolean_negatable },
1545
+ { keys: [:name_objects], flag: '--name-objects', type: :boolean_negatable },
1546
+ { keys: [:references], flag: '--references', type: :boolean_negatable }
1547
+ ].freeze
1548
+
1549
+ def fsck(*objects, **opts)
1550
+ args = ArgsBuilder.build(opts, FSCK_OPTION_MAP)
1551
+ args.concat(objects) unless objects.empty?
1552
+ # fsck returns non-zero exit status when issues are found:
1553
+ # 1 = errors found, 2 = missing objects, 4 = warnings
1554
+ # We still want to parse the output in these cases
1555
+ output = begin
1556
+ command('fsck', *args)
1557
+ rescue Git::FailedError => e
1558
+ raise unless [1, 2, 4].include?(e.result.status.exitstatus)
1559
+
1560
+ e.result.stdout
1561
+ end
1562
+ parse_fsck_output(output)
1563
+ end
1564
+
1533
1565
  READ_TREE_OPTION_MAP = [
1534
1566
  { keys: [:prefix], flag: '--prefix', type: :valued_equals }
1535
1567
  ].freeze
@@ -1684,8 +1716,52 @@ module Git
1684
1716
  { keys: [:between], type: :custom, builder: ->(value) { "#{value[0]}..#{value[1]}" if value } }
1685
1717
  ].freeze
1686
1718
 
1719
+ FSCK_OBJECT_PATTERN = /\A(dangling|missing|unreachable) (\w+) ([0-9a-f]{40})(?: \((.+)\))?\z/
1720
+ FSCK_WARNING_PATTERN = /\Awarning in (\w+) ([0-9a-f]{40}): (.+)\z/
1721
+ FSCK_ROOT_PATTERN = /\Aroot ([0-9a-f]{40})\z/
1722
+ FSCK_TAGGED_PATTERN = /\Atagged (\w+) ([0-9a-f]{40}) \((.+)\) in ([0-9a-f]{40})\z/
1723
+
1724
+ private_constant :FSCK_OBJECT_PATTERN, :FSCK_WARNING_PATTERN, :FSCK_ROOT_PATTERN, :FSCK_TAGGED_PATTERN
1725
+
1687
1726
  private
1688
1727
 
1728
+ def parse_fsck_output(output)
1729
+ result = { dangling: [], missing: [], unreachable: [], warnings: [], root: [], tagged: [] }
1730
+ output.each_line { |line| parse_fsck_line(line.strip, result) }
1731
+ Git::FsckResult.new(**result)
1732
+ end
1733
+
1734
+ def parse_fsck_line(line, result)
1735
+ parse_fsck_object_line(line, result) ||
1736
+ parse_fsck_warning_line(line, result) ||
1737
+ parse_fsck_root_line(line, result) ||
1738
+ parse_fsck_tagged_line(line, result)
1739
+ end
1740
+
1741
+ def parse_fsck_object_line(line, result)
1742
+ return unless (match = FSCK_OBJECT_PATTERN.match(line))
1743
+
1744
+ result[match[1].to_sym] << Git::FsckObject.new(type: match[2].to_sym, sha: match[3], name: match[4])
1745
+ end
1746
+
1747
+ def parse_fsck_warning_line(line, result)
1748
+ return unless (match = FSCK_WARNING_PATTERN.match(line))
1749
+
1750
+ result[:warnings] << Git::FsckObject.new(type: match[1].to_sym, sha: match[2], message: match[3])
1751
+ end
1752
+
1753
+ def parse_fsck_root_line(line, result)
1754
+ return unless (match = FSCK_ROOT_PATTERN.match(line))
1755
+
1756
+ result[:root] << Git::FsckObject.new(type: :commit, sha: match[1])
1757
+ end
1758
+
1759
+ def parse_fsck_tagged_line(line, result)
1760
+ return unless (match = FSCK_TAGGED_PATTERN.match(line))
1761
+
1762
+ result[:tagged] << Git::FsckObject.new(type: match[1].to_sym, sha: match[2], name: match[3])
1763
+ end
1764
+
1689
1765
  def parse_diff_path_status(args)
1690
1766
  command_lines('diff', *args).each_with_object({}) do |line, memo|
1691
1767
  status, path = split_status_line(line)
data/lib/git/version.rb CHANGED
@@ -3,5 +3,5 @@
3
3
  module Git
4
4
  # The current gem version
5
5
  # @return [String] the current gem version.
6
- VERSION = '4.2.0'
6
+ VERSION = '4.3.0'
7
7
  end
data/lib/git.rb CHANGED
@@ -18,6 +18,8 @@ require 'git/diff'
18
18
  require 'git/encoding_utils'
19
19
  require 'git/errors'
20
20
  require 'git/escaped_path'
21
+ require 'git/fsck_object'
22
+ require 'git/fsck_result'
21
23
  require 'git/index'
22
24
  require 'git/lib'
23
25
  require 'git/log'
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: git
3
3
  version: !ruby/object:Gem::Version
4
- version: 4.2.0
4
+ version: 4.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Scott Chacon and others
@@ -276,6 +276,8 @@ files:
276
276
  - lib/git/encoding_utils.rb
277
277
  - lib/git/errors.rb
278
278
  - lib/git/escaped_path.rb
279
+ - lib/git/fsck_object.rb
280
+ - lib/git/fsck_result.rb
279
281
  - lib/git/index.rb
280
282
  - lib/git/lib.rb
281
283
  - lib/git/log.rb
@@ -308,8 +310,8 @@ licenses:
308
310
  metadata:
309
311
  homepage_uri: http://github.com/ruby-git/ruby-git
310
312
  source_code_uri: http://github.com/ruby-git/ruby-git
311
- changelog_uri: https://rubydoc.info/gems/git/4.2.0/file/CHANGELOG.md
312
- documentation_uri: https://rubydoc.info/gems/git/4.2.0
313
+ changelog_uri: https://rubydoc.info/gems/git/4.3.0/file/CHANGELOG.md
314
+ documentation_uri: https://rubydoc.info/gems/git/4.3.0
313
315
  rubygems_mfa_required: 'true'
314
316
  rdoc_options: []
315
317
  require_paths: