git 1.19.1 → 4.4.5

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 (76) hide show
  1. checksums.yaml +4 -4
  2. data/.commitlintrc.yml +38 -0
  3. data/.github/copilot-instructions.md +2733 -0
  4. data/.github/pull_request_template.md +17 -0
  5. data/.github/workflows/continuous_integration.yml +92 -21
  6. data/.github/workflows/enforce_conventional_commits.yml +29 -0
  7. data/.github/workflows/experimental_continuous_integration.yml +59 -0
  8. data/.github/workflows/release.yml +53 -0
  9. data/.gitignore +5 -0
  10. data/.husky/commit-msg +1 -0
  11. data/.release-please-manifest.json +3 -0
  12. data/.rubocop.yml +55 -0
  13. data/.rubocop_todo.yml +12 -0
  14. data/.yardopts +4 -1
  15. data/AI_POLICY.md +24 -0
  16. data/CHANGELOG.md +501 -0
  17. data/CODE_OF_CONDUCT.md +25 -0
  18. data/CONTRIBUTING.md +323 -102
  19. data/GOVERNANCE.md +106 -0
  20. data/LICENSE +1 -1
  21. data/MAINTAINERS.md +17 -4
  22. data/README.md +575 -246
  23. data/Rakefile +13 -55
  24. data/git.gemspec +36 -30
  25. data/lib/git/args_builder.rb +111 -0
  26. data/lib/git/author.rb +9 -7
  27. data/lib/git/base.rb +602 -173
  28. data/lib/git/branch.rb +318 -38
  29. data/lib/git/branches.rb +21 -24
  30. data/lib/git/command_line.rb +330 -0
  31. data/lib/git/command_line_result.rb +9 -3
  32. data/lib/git/config.rb +10 -6
  33. data/lib/git/diff.rb +149 -81
  34. data/lib/git/diff_path_status.rb +46 -0
  35. data/lib/git/diff_stats.rb +59 -0
  36. data/lib/git/errors.rb +212 -0
  37. data/lib/git/escaped_path.rb +2 -2
  38. data/lib/git/fsck_object.rb +48 -0
  39. data/lib/git/fsck_result.rb +121 -0
  40. data/lib/git/index.rb +2 -1
  41. data/lib/git/lib.rb +1648 -643
  42. data/lib/git/log.rb +143 -106
  43. data/lib/git/object.rb +151 -125
  44. data/lib/git/path.rb +23 -16
  45. data/lib/git/remote.rb +5 -4
  46. data/lib/git/repository.rb +2 -2
  47. data/lib/git/stash.rb +11 -12
  48. data/lib/git/stashes.rb +16 -15
  49. data/lib/git/status.rb +104 -143
  50. data/lib/git/url.rb +3 -3
  51. data/lib/git/version.rb +3 -1
  52. data/lib/git/working_directory.rb +2 -0
  53. data/lib/git/worktree.rb +6 -5
  54. data/lib/git/worktrees.rb +6 -6
  55. data/lib/git.rb +131 -28
  56. data/package.json +10 -0
  57. data/redesign/1_architecture_existing.md +66 -0
  58. data/redesign/2_architecture_redesign.md +130 -0
  59. data/redesign/3_architecture_implementation.md +138 -0
  60. data/redesign/index.md +34 -0
  61. data/release-please-config.json +36 -0
  62. data/tasks/gem_tasks.rake +10 -0
  63. data/tasks/rubocop.rake +12 -0
  64. data/tasks/test.rake +13 -0
  65. data/tasks/test_gem.rake +12 -0
  66. data/tasks/yard.rake +23 -0
  67. metadata +114 -37
  68. data/.github/stale.yml +0 -25
  69. data/Dockerfile.changelog-rs +0 -12
  70. data/PULL_REQUEST_TEMPLATE.md +0 -9
  71. data/RELEASING.md +0 -70
  72. data/lib/git/base/factory.rb +0 -99
  73. data/lib/git/failed_error.rb +0 -53
  74. data/lib/git/git_execute_error.rb +0 -7
  75. data/lib/git/signaled_error.rb +0 -50
  76. /data/{ISSUE_TEMPLATE.md → .github/issue_template.md} +0 -0
@@ -0,0 +1,46 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Git
4
+ # The files and their status (e.g., added, modified, deleted) between two commits
5
+ class DiffPathStatus
6
+ include Enumerable
7
+
8
+ # @private
9
+ def initialize(base, from, to, path_limiter = nil)
10
+ # Eagerly check for invalid arguments
11
+ [from, to].compact.each do |arg|
12
+ raise ArgumentError, "Invalid argument: '#{arg}'" if arg.start_with?('-')
13
+ end
14
+
15
+ @base = base
16
+ @from = from
17
+ @to = to
18
+ @path_limiter = path_limiter
19
+ @path_status = nil
20
+ end
21
+
22
+ # Iterates over each file's status.
23
+ #
24
+ # @yield [path, status]
25
+ def each(&)
26
+ fetch_path_status.each(&)
27
+ end
28
+
29
+ # Returns the name-status report as a Hash.
30
+ #
31
+ # @return [Hash<String, String>] A hash where keys are file paths
32
+ # and values are their status codes.
33
+ def to_h
34
+ fetch_path_status
35
+ end
36
+
37
+ private
38
+
39
+ # Lazily fetches and caches the path status from the git lib.
40
+ def fetch_path_status
41
+ @fetch_path_status ||= @base.lib.diff_path_status(
42
+ @from, @to, { path_limiter: @path_limiter }
43
+ )
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,59 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Git
4
+ # Provides access to the statistics of a diff between two commits,
5
+ # including insertions, deletions, and file-level details.
6
+ class DiffStats
7
+ # @private
8
+ def initialize(base, from, to, path_limiter = nil)
9
+ # Eagerly check for invalid arguments
10
+ [from, to].compact.each do |arg|
11
+ raise ArgumentError, "Invalid argument: '#{arg}'" if arg.start_with?('-')
12
+ end
13
+
14
+ @base = base
15
+ @from = from
16
+ @to = to
17
+ @path_limiter = path_limiter
18
+ @stats = nil
19
+ end
20
+
21
+ # Returns the total number of lines deleted.
22
+ def deletions
23
+ fetch_stats[:total][:deletions]
24
+ end
25
+
26
+ # Returns the total number of lines inserted.
27
+ def insertions
28
+ fetch_stats[:total][:insertions]
29
+ end
30
+
31
+ # Returns the total number of lines changed (insertions + deletions).
32
+ def lines
33
+ fetch_stats[:total][:lines]
34
+ end
35
+
36
+ # Returns a hash of statistics for each file in the diff.
37
+ #
38
+ # @return [Hash<String, {insertions: Integer, deletions: Integer}>]
39
+ def files
40
+ fetch_stats[:files]
41
+ end
42
+
43
+ # Returns a hash of the total statistics for the diff.
44
+ #
45
+ # @return [{insertions: Integer, deletions: Integer, lines: Integer, files: Integer}]
46
+ def total
47
+ fetch_stats[:total]
48
+ end
49
+
50
+ private
51
+
52
+ # Lazily fetches and caches the stats from the git lib.
53
+ def fetch_stats
54
+ @fetch_stats ||= @base.lib.diff_stats(
55
+ @from, @to, { path_limiter: @path_limiter }
56
+ )
57
+ end
58
+ end
59
+ end
data/lib/git/errors.rb ADDED
@@ -0,0 +1,212 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Git
4
+ # rubocop:disable Layout/LineLength
5
+
6
+ # Base class for all custom git module errors
7
+ #
8
+ # The git gem will only raise an `ArgumentError` or an error that is a subclass of
9
+ # `Git::Error`. It does not explicitly raise any other types of errors.
10
+ #
11
+ # It is recommended to rescue `Git::Error` to catch any runtime error raised by
12
+ # this gem unless you need more specific error handling.
13
+ #
14
+ # Git's custom errors are arranged in the following class heirarchy:
15
+ #
16
+ # ```text
17
+ # StandardError
18
+ # └─> Git::Error
19
+ # ├─> Git::CommandLineError
20
+ # │ ├─> Git::FailedError
21
+ # │ └─> Git::SignaledError
22
+ # │ └─> Git::TimeoutError
23
+ # ├─> Git::ProcessIOError
24
+ # └─> Git::UnexpectedResultError
25
+ # ```
26
+ #
27
+ # | Error Class | Description |
28
+ # | --- | --- |
29
+ # | `Error` | This catch-all error serves as the base class for other custom errors raised by the git gem. |
30
+ # | `CommandLineError` | A subclass of this error is raised when there is a problem executing the git command line. |
31
+ # | `FailedError` | This error is raised when the git command line exits with a non-zero status code that is not expected by the git gem. |
32
+ # | `SignaledError` | This error is raised when the git command line is terminated as a result of receiving a signal. This could happen if the process is forcibly terminated or if there is a serious system error. |
33
+ # | `TimeoutError` | This is a specific type of `SignaledError` that is raised when the git command line operation times out and is killed via the SIGKILL signal. This happens if the operation takes longer than the timeout duration configured in `Git.config.timeout` or via the `:timeout` parameter given in git methods that support timeouts. |
34
+ # | `ProcessIOError` | An error was encountered reading or writing to a subprocess. |
35
+ # | `UnexpectedResultError` | The command line ran without error but did not return the expected results. |
36
+ #
37
+ # @example Rescuing a generic error
38
+ # begin
39
+ # # some git operation
40
+ # rescue Git::Error => e
41
+ # puts "An error occurred: #{e.message}"
42
+ # end
43
+ #
44
+ # @example Rescuing a timeout error
45
+ # begin
46
+ # timeout_duration = 0.001 # seconds
47
+ # repo = Git.clone('https://github.com/ruby-git/ruby-git', 'ruby-git-temp', timeout: timeout_duration)
48
+ # rescue Git::TimeoutError => e # Catch the more specific error first!
49
+ # puts "Git clone took too long and timed out #{e}"
50
+ # rescue Git::Error => e
51
+ # puts "Received the following error: #{e}"
52
+ # end
53
+ #
54
+ # @see Git::CommandLineError
55
+ # @see Git::FailedError
56
+ # @see Git::SignaledError
57
+ # @see Git::TimeoutError
58
+ # @see Git::ProcessIOError
59
+ # @see Git::UnexpectedResultError
60
+ #
61
+ # @api public
62
+ #
63
+ class Error < StandardError; end
64
+
65
+ # rubocop:enable Layout/LineLength
66
+
67
+ # An alias for Git::Error
68
+ #
69
+ # Git::GitExecuteError error class is an alias for Git::Error for backwards
70
+ # compatibility. It is recommended to use Git::Error directly.
71
+ #
72
+ # @deprecated Use Git::Error instead
73
+ #
74
+ GitExecuteError = ActiveSupport::Deprecation::DeprecatedConstantProxy.new('Git::GitExecuteError', 'Git::Error', Git::Deprecation)
75
+
76
+ # Raised when a git command fails or exits because of an uncaught signal
77
+ #
78
+ # The git command executed, status, stdout, and stderr are available from this
79
+ # object.
80
+ #
81
+ # The Gem will raise a more specific error for each type of failure:
82
+ #
83
+ # * {Git::FailedError}: when the git command exits with a non-zero status
84
+ # * {Git::SignaledError}: when the git command exits because of an uncaught signal
85
+ # * {Git::TimeoutError}: when the git command times out
86
+ #
87
+ # @api public
88
+ #
89
+ class CommandLineError < Git::Error
90
+ # Create a CommandLineError object
91
+ #
92
+ # @example
93
+ # `exit 1` # set $? appropriately for this example
94
+ # result = Git::CommandLineResult.new(%w[git status], $?, 'stdout', 'stderr')
95
+ # error = Git::CommandLineError.new(result)
96
+ # error.to_s #=> '["git", "status"], status: pid 89784 exit 1, stderr: "stderr"'
97
+ #
98
+ # @param result [Git::CommandLineResult] the result of the git command including
99
+ # the git command, status, stdout, and stderr
100
+ #
101
+ def initialize(result)
102
+ @result = result
103
+ super(error_message)
104
+ end
105
+
106
+ # The human readable representation of this error
107
+ #
108
+ # @example
109
+ # error.error_message #=> '["git", "status"], status: pid 89784 exit 1, stderr: "stderr"'
110
+ #
111
+ # @return [String]
112
+ #
113
+ def error_message = <<~MESSAGE.chomp
114
+ #{result.git_cmd}, status: #{result.status}, stderr: #{result.stderr.inspect}
115
+ MESSAGE
116
+
117
+ # @attribute [r] result
118
+ #
119
+ # The result of the git command including the git command and its status and output
120
+ #
121
+ # @example
122
+ # error.result #=> #<Git::CommandLineResult:0x00000001046bd488 ...>
123
+ #
124
+ # @return [Git::CommandLineResult]
125
+ #
126
+ attr_reader :result
127
+ end
128
+
129
+ # This error is raised when a git command returns a non-zero exitstatus
130
+ #
131
+ # The git command executed, status, stdout, and stderr are available from this
132
+ # object.
133
+ #
134
+ # @api public
135
+ #
136
+ class FailedError < Git::CommandLineError; end
137
+
138
+ # This error is raised when a git command exits because of an uncaught signal
139
+ #
140
+ # @api public
141
+ #
142
+ class SignaledError < Git::CommandLineError; end
143
+
144
+ # This error is raised when a git command takes longer than the configured timeout
145
+ #
146
+ # The git command executed, status, stdout, and stderr, and the timeout duration
147
+ # are available from this object.
148
+ #
149
+ # result.status.timeout? will be `true`
150
+ #
151
+ # @api public
152
+ #
153
+ class TimeoutError < Git::SignaledError
154
+ # Create a TimeoutError object
155
+ #
156
+ # @example
157
+ # command = %w[sleep 10]
158
+ # timeout_duration = 1
159
+ # status = ProcessExecuter.spawn(*command, timeout: timeout_duration)
160
+ # result = Git::CommandLineResult.new(command, status, 'stdout', 'err output')
161
+ # error = Git::TimeoutError.new(result, timeout_duration)
162
+ # error.error_message
163
+ # #=> '["sleep", "10"], status: pid 70144 SIGKILL (signal 9), stderr: "err output", timed out after 1s'
164
+ #
165
+ # @param result [Git::CommandLineResult] the result of the git command including
166
+ # the git command, status, stdout, and stderr
167
+ #
168
+ # @param timeout_duration [Numeric] the amount of time the subprocess was allowed
169
+ # to run before being killed
170
+ #
171
+ def initialize(result, timeout_duration)
172
+ @timeout_duration = timeout_duration
173
+ super(result)
174
+ end
175
+
176
+ # The human readable representation of this error
177
+ #
178
+ # @example
179
+ # error.error_message
180
+ # #=> '["sleep", "10"], status: pid 88811 SIGKILL (signal 9), stderr: "err output", timed out after 1s'
181
+ #
182
+ # @return [String]
183
+ #
184
+ def error_message = <<~MESSAGE.chomp
185
+ #{super}, timed out after #{timeout_duration}s
186
+ MESSAGE
187
+
188
+ # The amount of time the subprocess was allowed to run before being killed
189
+ #
190
+ # @example
191
+ # `kill -9 $$` # set $? appropriately for this example
192
+ # result = Git::CommandLineResult.new(%w[git status], $?, '', "killed")
193
+ # error = Git::TimeoutError.new(result, 10)
194
+ # error.timeout_duration #=> 10
195
+ #
196
+ # @return [Numeric]
197
+ #
198
+ attr_reader :timeout_duration
199
+ end
200
+
201
+ # Raised when the output of a git command can not be read
202
+ #
203
+ # @api public
204
+ #
205
+ class ProcessIOError < Git::Error; end
206
+
207
+ # Raised when the git command result was not as expected
208
+ #
209
+ # @api public
210
+ #
211
+ class UnexpectedResultError < Git::Error; end
212
+ end
@@ -3,7 +3,7 @@
3
3
  module Git
4
4
  # Represents an escaped Git path string
5
5
  #
6
- # Git commands that output paths (e.g. ls-files, diff), will escape usual
6
+ # Git commands that output paths (e.g. ls-files, diff), will escape unusual
7
7
  # characters in the path with backslashes in the same way C escapes control
8
8
  # characters (e.g. \t for TAB, \n for LF, \\ for backslash) or bytes with values
9
9
  # larger than 0x80 (e.g. octal \302\265 for "micro" in UTF-8).
@@ -42,7 +42,7 @@ module Git
42
42
  private
43
43
 
44
44
  def extract_octal(path, index)
45
- [path[index + 1..index + 3].to_i(8), 4]
45
+ [path[(index + 1)..(index + 3)].to_i(8), 4]
46
46
  end
47
47
 
48
48
  def extract_escape(path, index)
@@ -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/index.rb CHANGED
@@ -1,5 +1,6 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Git
2
4
  class Index < Git::Path
3
-
4
5
  end
5
6
  end