git 5.0.0.beta.2 → 5.0.0.beta.3

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: 30866d1c683ae76cdb07eb526d9adecd45c8ec08bb53824c3a027984297ec256
4
- data.tar.gz: a326ea01ed821d33c4edc60963cd2f6473ecaae2cad0daa82c06747456daa8dc
3
+ metadata.gz: '085739aa78a2d5bfacbf1f2048fb15997b8021ea40fda26715e9a66f7d2d8178'
4
+ data.tar.gz: f83f025bc403549cb4f4f889f2ed83ee2af9afdc2947a535345970c47ffa87b5
5
5
  SHA512:
6
- metadata.gz: a0f4cc725c36422a2afa046897d89bef4597fe8120215f83b22c9814d8c1817d4b795525c2aa4edd2c8d52bf617e36bba3b181d5a67af9067a5a6b859bbd6707
7
- data.tar.gz: 15957da7f84ef75aa7ad78d6eb7eac4d96aab21b56c265749c5def8a7cb035b5fdce651a395a98ce30fccab3d62c5e66f479fae621d16227c1eb1468c4372f5c
6
+ metadata.gz: 2769eab52af46ddc54bfea220b51f5b450e6f3e9999288f3b7d2003940dc4bf428a3b682c34ed41b36294d297ecda67abb48fa680063c396afbd1e45cbd71b75
7
+ data.tar.gz: 81aaa29f87a49c7cf1573e3fc1bbc32e9eb9883952c7550b69f983b73ebf33264f700d0465bb1ac390390083b8becdaefe80faebe3200adcd4d815c895cad57e
data/README.md CHANGED
@@ -30,6 +30,7 @@ Commits](https://img.shields.io/badge/Conventional%20Commits-1.0.0-%23FE5196?log
30
30
  - [Ruby Version Support Policy](#ruby-version-support-policy)
31
31
  - [Git Version Support Policy](#git-version-support-policy)
32
32
  - [📢 Project Announcements 📢](#-project-announcements-)
33
+ - [2026-06-26: v5.0.0.beta.3 Released](#2026-06-26-v500beta3-released)
33
34
  - [2026-06-25: v5.0.0.beta.2 Released](#2026-06-25-v500beta2-released)
34
35
  - [2026-06-04: v5.0.0.beta.1 Released](#2026-06-04-v500beta1-released)
35
36
  - [2026-01-07: AI Policy Introduced](#2026-01-07-ai-policy-introduced)
@@ -134,15 +135,17 @@ Configure the `git` command line:
134
135
 
135
136
  ```ruby
136
137
  # Global config (in ~/.gitconfig)
137
- settings = Git.global_config # returns a Hash
138
- username = Git.global_config('user.email')
139
- Git.global_config('user.email', 'user@example.com')
138
+ entries = Git.config_list(global: true) # returns Array<Git::ConfigEntryInfo>
139
+ entry = Git.config_get('user.email', global: true) # returns Git::ConfigEntryInfo or nil
140
+ email = entry&.value # => "user@example.com" or nil
141
+ Git.config_set('user.email', 'user@example.com', global: true)
140
142
 
141
143
  # Repository config
142
144
  repo = Git.open('path/to/repo')
143
- settings = repo.config # returns a Hash
144
- username = repo.config('user.email')
145
- repo.config('user.email', 'anotheruser@example.com')
145
+ entries = repo.config_list # returns Array<Git::ConfigEntryInfo>
146
+ entry = repo.config_get('user.email') # returns Git::ConfigEntryInfo or nil
147
+ email = entry&.value # => "anotheruser@example.com" or nil
148
+ repo.config_set('user.email', 'anotheruser@example.com')
146
149
  ```
147
150
 
148
151
  Configure the git gem:
@@ -293,8 +296,8 @@ result = repo.fsck(unreachable: true, strict: true)
293
296
  # Suppress dangling object output
294
297
  result = repo.fsck(dangling: false)
295
298
 
296
- repo.config('user.name') # returns 'Scott Chacon'
297
- repo.config # returns whole config hash
299
+ repo.config_get('user.name')&.value # returns 'Scott Chacon'
300
+ repo.config_list # returns Array<Git::ConfigEntryInfo>
298
301
 
299
302
  # Configuration can be set when cloning using the :config option.
300
303
  # This option can be an single configuration String or an Array
@@ -343,8 +346,8 @@ path = '/tmp/clone'
343
346
  repo = Git.clone(git_url, name, :path => path)
344
347
  repo.dir #=> /tmp/clone/ruby-git-clean
345
348
 
346
- repo.config('user.name', 'Scott Chacon')
347
- repo.config('user.email', 'email@email.com')
349
+ repo.config_set('user.name', 'Scott Chacon')
350
+ repo.config_set('user.email', 'email@email.com')
348
351
 
349
352
  # Clone can take a filter to tell the serve to send a partial clone
350
353
  repo = Git.clone(git_url, name, :path => path, :filter => 'tree:0')
@@ -371,7 +374,7 @@ repo.commit('message')
371
374
  repo.commit_all('message')
372
375
 
373
376
  # Sign a commit using the gpg key configured in the user.signingkey config setting
374
- repo.config('user.signingkey', '0A46826A')
377
+ repo.config_set('user.signingkey', '0A46826A')
375
378
  repo.commit('message', gpg_sign: true)
376
379
 
377
380
  # Sign a commit using a specified gpg key
@@ -651,6 +654,31 @@ notes.
651
654
 
652
655
  ## 📢 Project Announcements 📢
653
656
 
657
+ ### 2026-06-26: v5.0.0.beta.3 Released
658
+
659
+ The architectural redesign is approximately **93% complete** and we have published
660
+ [`git v5.0.0.beta.3`](https://rubygems.org/gems/git/versions/5.0.0.beta.3) as our
661
+ third pre-release.
662
+
663
+ **To try the beta**, add the pre-release version to your `Gemfile`:
664
+
665
+ ```ruby
666
+ gem 'git', '~> 5.0.0.beta'
667
+ ```
668
+
669
+ Or install it directly:
670
+
671
+ ```sh
672
+ gem install git --pre
673
+ ```
674
+
675
+ The intent is full backward compatibility with v4.x, but given the size and scope of
676
+ the redesign, some incompatibilities may exist. Please give the latest beta a try and
677
+ [open an issue](https://github.com/ruby-git/ruby-git/issues) if you hit anything
678
+ unexpected — your feedback helps us ship a solid v5.0.0.
679
+
680
+ See [UPGRADING.md](UPGRADING.md) for a full list of deprecations and breaking changes.
681
+
654
682
  ### 2026-06-25: v5.0.0.beta.2 Released
655
683
 
656
684
  The architectural redesign is approximately **90% complete** and we have published
@@ -9,7 +9,6 @@ require 'git/execution_context/repository'
9
9
  require 'git/repository/branching'
10
10
  require 'git/repository/context_helpers'
11
11
  require 'git/repository/committing'
12
- require 'git/repository/configuring'
13
12
  require 'git/repository/diffing'
14
13
  require 'git/repository/factories'
15
14
  require 'git/repository/inspecting'
@@ -18,6 +17,7 @@ require 'git/repository/maintenance'
18
17
  require 'git/repository/merging'
19
18
  require 'git/repository/object_operations'
20
19
  require 'git/repository/remote_operations'
20
+ require 'git/repository/shared_private'
21
21
  require 'git/repository/staging'
22
22
  require 'git/repository/stashing'
23
23
  require 'git/repository/status_operations'
@@ -48,14 +48,13 @@ module Git
48
48
  #
49
49
  # @api public
50
50
  #
51
- class Repository
51
+ class Repository # rubocop:disable Metrics/ClassLength
52
52
  extend Git::Repository::Factories
53
53
 
54
54
  include Git::Configuring
55
55
  include Git::Repository::Branching
56
56
  include Git::Repository::ContextHelpers
57
57
  include Git::Repository::Committing
58
- include Git::Repository::Configuring
59
58
  include Git::Repository::Diffing
60
59
  include Git::Repository::Inspecting
61
60
  include Git::Repository::Logging
@@ -68,6 +67,23 @@ module Git
68
67
  include Git::Repository::StatusOperations
69
68
  include Git::Repository::WorktreeOperations
70
69
 
70
+ CONFIG_SET_ALLOWED_OPTS = %i[file].freeze
71
+ private_constant :CONFIG_SET_ALLOWED_OPTS
72
+
73
+ CONFIG_READ_ALLOWED_OPTS = %i[file].freeze
74
+ private_constant :CONFIG_READ_ALLOWED_OPTS
75
+
76
+ CONFIG_DEPRECATION_WARNING =
77
+ 'Git::Repository#config is deprecated and will be removed in v6.0.0. ' \
78
+ 'Use config_get(name), config_set(name, value), or config_list instead.'
79
+ private_constant :CONFIG_DEPRECATION_WARNING
80
+
81
+ GLOBAL_CONFIG_DEPRECATION_WARNING =
82
+ 'Git::Repository#global_config is deprecated and will be removed in v6.0.0. ' \
83
+ 'Use config_get(name, global: true), config_set(name, value, global: true), ' \
84
+ 'or config_list(global: true) instead.'
85
+ private_constant :GLOBAL_CONFIG_DEPRECATION_WARNING
86
+
71
87
  # @return [Git::ExecutionContext::Repository] the execution context used to run
72
88
  # git commands for this repository
73
89
  # @api private
@@ -168,6 +184,154 @@ module Git
168
184
  # @api private
169
185
  def binary_path = execution_context.binary_path
170
186
 
187
+ # Read or write a git configuration entry
188
+ #
189
+ # Dispatches to one of three modes depending on the arguments supplied:
190
+ #
191
+ # * **List** — `config()` returns all visible config entries as a `Hash`.
192
+ # * **Get** — `config(name)` returns the value for a single key as a `String`.
193
+ # * **Set** — `config(name, value)` writes a value and returns the raw
194
+ # command result.
195
+ #
196
+ # @overload config(options = {})
197
+ #
198
+ # @example List all config entries
199
+ # repo.config #=> { "user.name" => "Alice", "core.bare" => "false" }
200
+ #
201
+ # @example List all entries from a custom config file
202
+ # repo.config(file: '/path/to/.gitconfig')
203
+ # #=> { "user.name" => "Alice", "core.bare" => "false" }
204
+ #
205
+ # @param options [Hash] options for the list operation
206
+ #
207
+ # @option options [String, nil] :file (nil) path to a custom config file
208
+ # to read from instead of the default resolution chain
209
+ #
210
+ # @return [Hash{String => String}] all visible config entries, keyed by
211
+ # their full dotted key names (e.g. `"user.name"`)
212
+ #
213
+ # @raise [ArgumentError] if unsupported options are provided
214
+ #
215
+ # @overload config(name, options = {})
216
+ #
217
+ # @example Read the committer name from config
218
+ # repo.config('user.name') #=> "Alice"
219
+ #
220
+ # @example Read a value from a custom config file
221
+ # repo.config('user.name', file: '/path/to/.gitconfig') #=> "Alice"
222
+ #
223
+ # @param name [String] the dotted config key to look up (e.g.
224
+ # `"user.name"`)
225
+ #
226
+ # @param options [Hash] options for the get operation
227
+ #
228
+ # @option options [String, nil] :file (nil) path to a custom config file
229
+ # to read from instead of the default resolution chain
230
+ #
231
+ # @return [String] the value of the config entry
232
+ #
233
+ # @raise [ArgumentError] if unsupported options are provided
234
+ #
235
+ # @overload config(name, value, options = {})
236
+ #
237
+ # @example Set the committer name in local config
238
+ # repo.config('user.name', 'Alice')
239
+ #
240
+ # @example Write a value to a custom config file
241
+ # repo.config('user.name', 'Alice', file: '/path/to/custom/config')
242
+ #
243
+ # @param name [String] the dotted config key to write (e.g.
244
+ # `"user.name"`)
245
+ #
246
+ # @param value [#to_s] the value to assign; must not be `nil` (a `nil`
247
+ # value is treated as "no value" and routes to the get overload).
248
+ # Must not be a `Hash` (a Hash is treated as the `options` argument;
249
+ # call `value.to_s` explicitly before passing if a stringified Hash
250
+ # is genuinely needed). Any other non-nil object is converted to a
251
+ # String via `#to_s` before being passed to git
252
+ #
253
+ # @param options [Hash] options for the set operation
254
+ #
255
+ # @option options [String, nil] :file (nil) path to a custom config file
256
+ # to write to instead of the repository's default `.git/config`
257
+ #
258
+ # @return [Git::CommandLineResult] the raw result of
259
+ # `git config <name> <value>`
260
+ #
261
+ # @raise [ArgumentError] if unsupported options are provided
262
+ #
263
+ # @raise [Git::FailedError] if git exits with a non-zero exit status
264
+ #
265
+ def config(name = nil, value = nil, options = {})
266
+ Git::Deprecation.warn(CONFIG_DEPRECATION_WARNING)
267
+ name, value, options = deprecated_normalize_config_args(name, value, options)
268
+
269
+ if !name.nil? && !value.nil?
270
+ deprecated_config_set(name, value, **options)
271
+ elsif name
272
+ deprecated_config_get(name, **options)
273
+ else
274
+ deprecated_config_list(**options)
275
+ end
276
+ end
277
+
278
+ # Read or write a global git configuration entry
279
+ #
280
+ # Dispatches to one of three modes depending on the arguments supplied,
281
+ # targeting the git global config scope (`git config --global`):
282
+ #
283
+ # * **List** — `global_config()` returns all global config entries as a `Hash`.
284
+ # * **Get** — `global_config(name)` returns the value for a single key as a `String`.
285
+ # * **Set** — `global_config(name, value)` writes a value and returns the raw
286
+ # command result.
287
+ #
288
+ # @overload global_config
289
+ #
290
+ # @example List all global config entries
291
+ # repo.global_config #=> { "user.name" => "Alice", "core.autocrlf" => "false" }
292
+ #
293
+ # @return [Hash{String => String}] all global config entries, keyed by their
294
+ # full dotted key names (e.g. `"user.name"`)
295
+ #
296
+ # @raise [Git::FailedError] if git exits with a non-zero exit status
297
+ #
298
+ # @overload global_config(name)
299
+ #
300
+ # @example Read the global committer name
301
+ # repo.global_config('user.name') #=> "Alice"
302
+ #
303
+ # @param name [String] the dotted config key to look up (e.g. `"user.name"`)
304
+ #
305
+ # @return [String] the value of the global config entry
306
+ #
307
+ # @raise [Git::FailedError] if git exits with a non-zero exit status
308
+ #
309
+ # @overload global_config(name, value)
310
+ #
311
+ # @example Set the global committer name
312
+ # repo.global_config('user.name', 'Alice')
313
+ #
314
+ # @param name [String] the dotted config key to write (e.g. `"user.name"`)
315
+ #
316
+ # @param value [#to_s] the value to assign; any object is accepted and
317
+ # converted to a String via `#to_s` before being passed to git
318
+ #
319
+ # @return [Git::CommandLineResult] the raw result of
320
+ # `git config --global <name> <value>`
321
+ #
322
+ # @raise [Git::FailedError] if git exits with a non-zero exit status
323
+ #
324
+ def global_config(name = nil, value = nil)
325
+ Git::Deprecation.warn(GLOBAL_CONFIG_DEPRECATION_WARNING)
326
+ if !name.nil? && !value.nil?
327
+ deprecated_global_config_set(name, value)
328
+ elsif !name.nil?
329
+ deprecated_global_config_get(name)
330
+ else
331
+ deprecated_global_config_list
332
+ end
333
+ end
334
+
171
335
  # Returns the size of the repository directory in bytes
172
336
  #
173
337
  # Sums the sizes of every regular file under the repository (`.git`)
@@ -197,6 +361,63 @@ module Git
197
361
 
198
362
  private
199
363
 
364
+ def deprecated_normalize_config_args(name, value, options)
365
+ if name.is_a?(Hash)
366
+ raise ArgumentError, 'unexpected positional arguments after options hash' if !value.nil? || !options.empty?
367
+
368
+ [nil, nil, name]
369
+ elsif value.is_a?(Hash)
370
+ raise ArgumentError, 'unexpected third argument when second argument is options hash' unless options.empty?
371
+
372
+ [name, nil, value]
373
+ else
374
+ [name, value, options]
375
+ end
376
+ end
377
+
378
+ def deprecated_config_set(name, value, **)
379
+ SharedPrivate.assert_valid_opts!(CONFIG_SET_ALLOWED_OPTS, **)
380
+ Git::Commands::ConfigOptionSyntax::Set.new(@execution_context).call(name, value, **)
381
+ end
382
+
383
+ def deprecated_config_get(name, **options)
384
+ SharedPrivate.assert_valid_opts!(CONFIG_READ_ALLOWED_OPTS, **options)
385
+ opts = options[:file] ? { file: options[:file] } : {}
386
+ result = Git::Commands::ConfigOptionSyntax::Get.new(@execution_context).call(name, **opts)
387
+ raise Git::FailedError, result if result.status.exitstatus != 0
388
+
389
+ result.stdout
390
+ end
391
+
392
+ def deprecated_config_list(**options)
393
+ SharedPrivate.assert_valid_opts!(CONFIG_READ_ALLOWED_OPTS, **options)
394
+ opts = options[:file] ? { file: options[:file] } : {}
395
+ lines = Git::Commands::ConfigOptionSyntax::List.new(@execution_context).call(**opts).stdout.split("\n")
396
+ lines.each_with_object({}) do |line, hsh|
397
+ key, value = line.split('=', 2)
398
+ hsh[key] = value || ''
399
+ end
400
+ end
401
+
402
+ def deprecated_global_config_get(name)
403
+ result = Git::Commands::ConfigOptionSyntax::Get.new(@execution_context).call(name, global: true)
404
+ raise Git::FailedError, result if result.status.exitstatus != 0
405
+
406
+ result.stdout
407
+ end
408
+
409
+ def deprecated_global_config_list
410
+ lines = Git::Commands::ConfigOptionSyntax::List.new(@execution_context).call(global: true).stdout.split("\n")
411
+ lines.each_with_object({}) do |line, hsh|
412
+ key, value = line.split('=', 2)
413
+ hsh[key] = value || ''
414
+ end
415
+ end
416
+
417
+ def deprecated_global_config_set(name, value)
418
+ Git::Commands::ConfigOptionSyntax::Set.new(@execution_context).call(name, value, global: true)
419
+ end
420
+
200
421
  # All git config scopes are valid in a repository context
201
422
  #
202
423
  # @return [void]
data/lib/git/status.rb CHANGED
@@ -172,17 +172,15 @@ module Git
172
172
 
173
173
  # Return `true` when git is configured to ignore filename case
174
174
  #
175
- # Reads `core.ignoreCase` from the repository config. Returns `false` if
176
- # the config value is absent or if reading it raises {Git::FailedError}.
175
+ # Reads `core.ignoreCase` with {Git::Repository#config_get}. Returns `false`
176
+ # when the config value is absent.
177
177
  #
178
178
  # @return [Boolean] `true` when `core.ignoreCase` is `"true"`
179
179
  #
180
180
  def ignore_case?
181
- return @_ignore_case if defined?(@_ignore_case)
181
+ return @ignore_case if defined?(@ignore_case)
182
182
 
183
- @_ignore_case = (@base.config('core.ignoreCase') == 'true')
184
- rescue Git::FailedError
185
- @_ignore_case = false
183
+ @ignore_case = (@base.config_get('core.ignoreCase')&.value == 'true')
186
184
  end
187
185
  end
188
186
  end
data/lib/git/version.rb CHANGED
@@ -4,7 +4,7 @@ module Git
4
4
  # The current gem version
5
5
  #
6
6
  # @return [String] the current gem version
7
- VERSION = '5.0.0.beta.2'
7
+ VERSION = '5.0.0.beta.3'
8
8
 
9
9
  # Represents a git version with major, minor, and patch components
10
10
  #
data/lib/git.rb CHANGED
@@ -75,11 +75,11 @@ module Git
75
75
  extend Git::Configuring
76
76
 
77
77
  # @deprecated Mixing in the `Git` module is deprecated and will be removed in v6.0.0.
78
- # Use `Git.open(Dir.pwd).config(...)` instead.
78
+ # Use `Git.config_get(name)`, `Git.config_set(name, value)`, or `Git.config_list` instead.
79
79
  def config(name = nil, value = nil)
80
80
  Git::Deprecation.warn(
81
81
  'Git#config is deprecated and will be removed in v6.0.0. ' \
82
- 'Use Git.open(Dir.pwd).config(...) instead.'
82
+ 'Use Git.config_get(name), Git.config_set(name, value), or Git.config_list instead.'
83
83
  )
84
84
  Git.__send__(:run_config_utility, name, value, global: false)
85
85
  end
@@ -114,11 +114,13 @@ module Git
114
114
  end
115
115
 
116
116
  # @deprecated Mixing in the `Git` module is deprecated and will be removed in v6.0.0.
117
- # Use `Git.global_config(...)` instead.
117
+ # Use `Git.config_get(name, global: true)`, `Git.config_set(name, value, global: true)`, or
118
+ # `Git.config_list(global: true)` instead.
118
119
  def global_config(name = nil, value = nil)
119
120
  Git::Deprecation.warn(
120
121
  'Git#global_config is deprecated and will be removed in v6.0.0. ' \
121
- 'Use Git.global_config(...) instead.'
122
+ 'Use Git.config_get(name, global: true), Git.config_set(name, value, global: true), ' \
123
+ 'or Git.config_list(global: true) instead.'
122
124
  )
123
125
  Git.global_config(name, value)
124
126
  end
@@ -44,8 +44,8 @@ risk and allows for a gradual, controlled migration to the new architecture.
44
44
  | Phase 1 | ✅ Complete | Foundation and scaffolding | 5% | 100% |
45
45
  | Phase 2 | ✅ Complete | Migrating commands (all checklist items done) | 40% | 100% |
46
46
  | Phase 3 | ✅ Complete | Refactoring public interface — see [Facade Modules Completed](#facade-modules-completed) and [Facade coverage checklist](#facade-coverage-checklist) | 45% | 100% |
47
- | Phase 4 | 🔲 Not Started | Final cleanup and release | 10% | 0% |
48
- | **TOTAL** | -- | -- | **100%** | **90%** |
47
+ | Phase 4 | 🚧 In Progress | Final cleanup and release — Step A complete; Steps B and C remain | 10% | 33% |
48
+ | **TOTAL** | -- | -- | **100%** | **93%** |
49
49
 
50
50
  ### Facade Modules Completed
51
51
 
@@ -85,20 +85,32 @@ such as `Branch`, `Diff`, `Log`, `Object`, `Remote`, `Status`, `Worktree`, etc.
85
85
 
86
86
  ### Next Task
87
87
 
88
- #### D2 / Phase 4 is the remaining redesign step
88
+ #### Phase 4 Step B (finalize test suite) is the next step
89
89
 
90
- F1 and F2 are both ✅ complete. All Phase 4 prerequisites are now satisfied, and
91
- the remaining unchecked redesign item is D2.
90
+ Phase 4 **Step A Remove old code** is ✅ complete. The atomic removal landed in
91
+ [PR #1456](https://github.com/ruby-git/ruby-git/pull/1456) (commit `c1c53999`),
92
+ which deleted `Git::Base` and `Git::Lib`, removed the `base_object` / `from_base`
93
+ bridge from `Git::ExecutionContext::Repository`, and dropped the legacy `require`
94
+ lines from `lib/git.rb`. This also satisfies the long-standing **D2** redesign
95
+ item. The only remaining `Git::Base` / `Git::Lib` strings in `lib/` are YARD/comment
96
+ references to historical 4.x behavior, which the Step A done-criteria explicitly
97
+ allow.
92
98
 
93
- Phase 4 (final cleanup and release deleting `Git::Base`/`Git::Lib`) can now begin.
94
- The first cleanup PR should remove the `base_object` / `from_base` bridge in the
95
- same releasable change that deletes or retires `Git::Base`.
96
- The following are also required and are already ✅ complete:
99
+ The remaining redesign work is the rest of Phase 4:
100
+
101
+ | Step | Status | Summary |
102
+ | ---- | ------ | ------- |
103
+ | A — Remove old code | ✅ Complete | `Git::Base`/`Git::Lib` and the bridge deleted ([PR #1456](https://github.com/ruby-git/ruby-git/pull/1456)) |
104
+ | B — Finalize test suite | 🔲 Not Started | Port remaining Test::Unit coverage in `tests/units/` to RSpec, drop the `test-unit` dependency from `git.gemspec`, remove the `test`/`test-all` Rake tasks, and remove the `tests/` directory |
105
+ | C — Update documentation | 🚧 Partial | `UPGRADING.md` and broad `@api private` coverage exist; verify `yard stats` reports no missing public-API docs and that `README.md` reflects the new entry points |
106
+
107
+ The following earlier prerequisites are all ✅ complete:
97
108
 
98
109
  | Step | Status |
99
110
  | ---- | ------ |
100
111
  | C1c-2: public-API parity audit and remediation sweep | ✅ |
101
112
  | E: block-based helper/path-context methods migrated | ✅ |
113
+ | D2: remove the `base_object` / `from_base` bridge | ✅ |
102
114
 
103
115
  Steps C1d-1, C1d-2, and C1d-3 are ✅ complete (see their detail sections below for full specs).
104
116
 
@@ -122,8 +134,8 @@ All 9 domain-object migrations are ✅ complete:
122
134
 
123
135
  The work was organized into six workstreams (A–F). All workstreams that are
124
136
  prerequisites for C1d are now ✅ complete. F1 and F2 are both ✅ complete — F2
125
- moved the remaining `Git` module utility methods off `Git::Lib`. Phase 4 is
126
- ready to proceed.
137
+ moved the remaining `Git` module utility methods off `Git::Lib`. Phase 4 Step A
138
+ is complete; Steps B and C remain (see [Next Task](#next-task)).
127
139
 
128
140
  **Sequencing** (see [Phase 3 dependency order](#phase-3-dependency-order) for the
129
141
  reasoning behind each edge):
@@ -1544,29 +1556,38 @@ graph LR
1544
1556
 
1545
1557
  #### Step A — Remove old code
1546
1558
 
1559
+ **Status: ✅ Complete.** Completed in [PR #1456](https://github.com/ruby-git/ruby-git/pull/1456)
1560
+ (commit `c1c53999`).
1561
+
1547
1562
  - Delete `attr_reader :base_object`, remove `base_object:` from `#initialize`, and remove or convert `from_base`.
1548
1563
  - Delete the `Git::Lib` class entirely.
1549
1564
  - Delete the `Git::Base` class file.
1550
1565
  - Remove any other dead code that was part of the old implementation.
1551
1566
 
1552
- **Done when**: `lib/git/lib.rb` and `lib/git/base.rb` are deleted; `Git::ExecutionContext::Repository` no longer accepts or exposes `base_object`; no references to `Git::Lib` or `Git::Base` remain in `lib/`.
1567
+ **Done when**: `lib/git/lib.rb` and `lib/git/base.rb` are deleted; `Git::ExecutionContext::Repository` no longer accepts or exposes `base_object`; no runtime references to `Git::Lib` or `Git::Base` remain in `lib/` (YARD/comment references to historical 4.x behavior are allowed). ✅ Met — the only surviving `Git::Lib`/`Git::Base` strings in `lib/` are such doc/comment references.
1553
1568
 
1554
1569
  **Planning tip**: Before generating a deletion plan, audit all remaining callers of `Git::Lib`, `Git::Base`, and `from_base` across `lib/` and `spec/`. The bridge removal and `Git::Base` deletion must land atomically in the same PR — plan for a single large deletion commit rather than incremental removals.
1555
1570
 
1556
1571
  #### Step B — Finalize test suite
1557
1572
 
1573
+ **Status: 🔲 Not Started.**
1574
+
1558
1575
  - Convert any remaining, relevant Test::Unit tests to RSpec.
1559
- - Remove the `test-unit` dependency from the `Gemfile`.
1576
+ - Remove the `test-unit` dependency from `git.gemspec`.
1560
1577
  - Ensure the RSpec suite has comprehensive coverage for the new architecture.
1561
1578
 
1562
1579
  **Done when**: The `tests/` directory is empty or removed; `test-unit` is no longer
1563
- in `Gemfile`; RSpec is the sole test framework.
1580
+ in `git.gemspec`; RSpec is the sole test framework.
1564
1581
 
1565
1582
  #### Step C — Update documentation
1566
1583
 
1584
+ **Status: 🚧 Partial.**
1585
+
1567
1586
  - Thoroughly document the new public API (`Git`, `Git::Repository`, etc.).
1568
1587
  - Mark all internal classes (`ExecutionContext`, `Commands`, `*Path`) with `@api private` in the YARD documentation.
1569
1588
  - Update the `README.md` and create a `UPGRADING.md` guide explaining the breaking changes for v5.0.0.
1570
1589
 
1571
1590
  **Done when**: `yard stats` reports no missing docs on public API; `UPGRADING.md`
1572
- covers all breaking changes; `README.md` reflects the new entry points.
1591
+ covers all breaking changes; `README.md` reflects the new entry points. `UPGRADING.md`
1592
+ exists and `@api private` markers are applied broadly; remaining work is to confirm
1593
+ full public-API doc coverage and that `README.md` reflects the new entry points.
@@ -1,5 +1,11 @@
1
1
  # Phase 4 / Step A — Remove Old Code: Execution Plan
2
2
 
3
+ > **✅ Status: Complete.** All PRs in this plan (1a–1d, 2a–2c, 3a–3d, and the
4
+ > atomic removal PR 4) have merged. The final removal landed in
5
+ > [PR #1456](https://github.com/ruby-git/ruby-git/pull/1456) (commit `c1c53999`),
6
+ > deleting `Git::Base`, `Git::Lib`, and the `base_object` / `from_base` bridge.
7
+ > This document is retained as a historical record of how Step A was executed.
8
+
3
9
  ## Goal
4
10
 
5
11
  Delete `Git::Base`, `Git::Lib`, and the `from_base`/`base_object` bridge in a
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: 5.0.0.beta.2
4
+ version: 5.0.0.beta.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Scott Chacon and others
@@ -602,7 +602,6 @@ files:
602
602
  - lib/git/repository.rb
603
603
  - lib/git/repository/branching.rb
604
604
  - lib/git/repository/committing.rb
605
- - lib/git/repository/configuring.rb
606
605
  - lib/git/repository/context_helpers.rb
607
606
  - lib/git/repository/diffing.rb
608
607
  - lib/git/repository/factories.rb
@@ -653,8 +652,8 @@ licenses:
653
652
  metadata:
654
653
  homepage_uri: http://github.com/ruby-git/ruby-git
655
654
  source_code_uri: http://github.com/ruby-git/ruby-git
656
- changelog_uri: https://rubydoc.info/gems/git/5.0.0.beta.2/file/CHANGELOG.md
657
- documentation_uri: https://rubydoc.info/gems/git/5.0.0.beta.2
655
+ changelog_uri: https://rubydoc.info/gems/git/5.0.0.beta.3/file/CHANGELOG.md
656
+ documentation_uri: https://rubydoc.info/gems/git/5.0.0.beta.3
658
657
  rubygems_mfa_required: 'true'
659
658
  rdoc_options: []
660
659
  require_paths:
@@ -1,351 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require 'git/commands/config_option_syntax'
4
- require 'git/repository/shared_private'
5
-
6
- module Git
7
- class Repository
8
- # Legacy facade methods for reading and writing git configuration
9
- #
10
- # Provides the {#config} and {#global_config} dispatch methods for 4.x
11
- # compatibility. These methods return raw `String` / `Hash` values instead of
12
- # {Git::ConfigEntryInfo} objects and are retained so that internal callers such
13
- # as `Git::Status` continue to work unchanged.
14
- #
15
- # The structured `config_*` methods (e.g. `config_get`, `config_list`) are
16
- # provided by {Git::Configuring}, which is included directly into
17
- # {Git::Repository}.
18
- #
19
- # Included by {Git::Repository}.
20
- #
21
- # @api public
22
- #
23
- module Configuring
24
- # Option keys accepted by {#config} when writing a value
25
- CONFIG_SET_ALLOWED_OPTS = %i[file].freeze
26
- private_constant :CONFIG_SET_ALLOWED_OPTS
27
-
28
- # Option keys accepted by {#config} when reading a single value or listing
29
- CONFIG_READ_ALLOWED_OPTS = %i[file].freeze
30
- private_constant :CONFIG_READ_ALLOWED_OPTS
31
-
32
- # Read or write a git configuration entry
33
- #
34
- # Dispatches to one of three modes depending on the arguments supplied:
35
- #
36
- # * **List** — `config()` returns all visible config entries as a `Hash`.
37
- # * **Get** — `config(name)` returns the value for a single key as a `String`.
38
- # * **Set** — `config(name, value)` writes a value and returns the raw
39
- # command result.
40
- #
41
- # @overload config(options = {})
42
- #
43
- # @example List all config entries
44
- # repo.config #=> { "user.name" => "Alice", "core.bare" => "false" }
45
- #
46
- # @example List all entries from a custom config file
47
- # repo.config(file: '/path/to/.gitconfig')
48
- # #=> { "user.name" => "Alice", "core.bare" => "false" }
49
- #
50
- # @param options [Hash] options for the list operation
51
- #
52
- # @option options [String, nil] :file (nil) path to a custom config file
53
- # to read from instead of the default resolution chain
54
- #
55
- # @return [Hash{String => String}] all visible config entries, keyed by
56
- # their full dotted key names (e.g. `"user.name"`)
57
- #
58
- # @raise [ArgumentError] if unsupported options are provided
59
- #
60
- # @overload config(name, options = {})
61
- #
62
- # @example Read the committer name from config
63
- # repo.config('user.name') #=> "Alice"
64
- #
65
- # @example Read a value from a custom config file
66
- # repo.config('user.name', file: '/path/to/.gitconfig') #=> "Alice"
67
- #
68
- # @param name [String] the dotted config key to look up (e.g.
69
- # `"user.name"`)
70
- #
71
- # @param options [Hash] options for the get operation
72
- #
73
- # @option options [String, nil] :file (nil) path to a custom config file
74
- # to read from instead of the default resolution chain
75
- #
76
- # @return [String] the value of the config entry
77
- #
78
- # @raise [ArgumentError] if unsupported options are provided
79
- #
80
- # @overload config(name, value, options = {})
81
- #
82
- # @example Set the committer name in local config
83
- # repo.config('user.name', 'Alice')
84
- #
85
- # @example Write a value to a custom config file
86
- # repo.config('user.name', 'Alice', file: '/path/to/custom/config')
87
- #
88
- # @param name [String] the dotted config key to write (e.g.
89
- # `"user.name"`)
90
- #
91
- # @param value [#to_s] the value to assign; must not be `nil` (a `nil`
92
- # value is treated as "no value" and routes to the get overload).
93
- # Must not be a `Hash` (a Hash is treated as the `options` argument;
94
- # call `value.to_s` explicitly before passing if a stringified Hash
95
- # is genuinely needed). Any other non-nil object is converted to a
96
- # String via `#to_s` before being passed to git
97
- #
98
- # @param options [Hash] options for the set operation
99
- #
100
- # @option options [String, nil] :file (nil) path to a custom config file
101
- # to write to instead of the repository's default `.git/config`
102
- #
103
- # @return [Git::CommandLineResult] the raw result of
104
- # `git config <name> <value>`
105
- #
106
- # @raise [ArgumentError] if unsupported options are provided
107
- #
108
- # @raise [Git::FailedError] if git exits with a non-zero exit status
109
- #
110
- def config(name = nil, value = nil, options = {})
111
- name, value, options = Private.normalize_config_args(name, value, options)
112
-
113
- if !name.nil? && !value.nil?
114
- Private.config_set(@execution_context, name, value, **options)
115
- elsif name
116
- Private.config_get(@execution_context, name, **options)
117
- else
118
- Private.config_list(@execution_context, **options)
119
- end
120
- end
121
-
122
- # Read or write a global git configuration entry
123
- #
124
- # Dispatches to one of three modes depending on the arguments supplied,
125
- # targeting the git global config scope (`git config --global`):
126
- #
127
- # * **List** — `global_config()` returns all global config entries as a `Hash`.
128
- # * **Get** — `global_config(name)` returns the value for a single key as a `String`.
129
- # * **Set** — `global_config(name, value)` writes a value and returns the raw
130
- # command result.
131
- #
132
- # @overload global_config
133
- #
134
- # @example List all global config entries
135
- # repo.global_config #=> { "user.name" => "Alice", "core.autocrlf" => "false" }
136
- #
137
- # @return [Hash{String => String}] all global config entries, keyed by their
138
- # full dotted key names (e.g. `"user.name"`)
139
- #
140
- # @raise [Git::FailedError] if git exits with a non-zero exit status
141
- #
142
- # @overload global_config(name)
143
- #
144
- # @example Read the global committer name
145
- # repo.global_config('user.name') #=> "Alice"
146
- #
147
- # @param name [String] the dotted config key to look up (e.g. `"user.name"`)
148
- #
149
- # @return [String] the value of the global config entry
150
- #
151
- # @raise [Git::FailedError] if git exits with a non-zero exit status
152
- #
153
- # @overload global_config(name, value)
154
- #
155
- # @example Set the global committer name
156
- # repo.global_config('user.name', 'Alice')
157
- #
158
- # @param name [String] the dotted config key to write (e.g. `"user.name"`)
159
- #
160
- # @param value [#to_s] the value to assign; any object is accepted and
161
- # converted to a String via `#to_s` before being passed to git
162
- #
163
- # @return [Git::CommandLineResult] the raw result of
164
- # `git config --global <name> <value>`
165
- #
166
- # @raise [Git::FailedError] if git exits with a non-zero exit status
167
- #
168
- def global_config(name = nil, value = nil)
169
- if !name.nil? && !value.nil?
170
- Private.global_config_set(@execution_context, name, value)
171
- elsif !name.nil?
172
- Private.global_config_get(@execution_context, name)
173
- else
174
- Private.global_config_list(@execution_context)
175
- end
176
- end
177
-
178
- # Private helpers local to {Git::Repository::Configuring}
179
- #
180
- # @api private
181
- #
182
- module Private
183
- module_function
184
-
185
- # Normalize `config()` positional arguments
186
- #
187
- # In Ruby 3.x, calling `config(file: '/path')` passes the hash as the
188
- # first positional argument. This helper re-maps those patterns so that
189
- # `name`, `value`, and `options` are always in their canonical positions.
190
- #
191
- # Raises `ArgumentError` for call shapes that are ambiguous or clearly
192
- # wrong, such as passing extra positional arguments after an options Hash.
193
- #
194
- # @param name [String, Hash, nil] the raw first argument to {#config}
195
- #
196
- # @param value [Object, Hash, nil] the raw second argument to {#config}
197
- #
198
- # @param options [Hash] the raw third argument to {#config}
199
- #
200
- # @return [Array(String|nil, Object|nil, Hash)] normalized [name, value, options]
201
- #
202
- # @raise [ArgumentError] if extra positional arguments follow an options Hash
203
- #
204
- def normalize_config_args(name, value, options)
205
- if name.is_a?(Hash)
206
- raise ArgumentError, 'unexpected positional arguments after options hash' if !value.nil? || !options.empty?
207
-
208
- [nil, nil, name]
209
- elsif value.is_a?(Hash)
210
- raise ArgumentError, 'unexpected third argument when second argument is options hash' unless options.empty?
211
-
212
- [name, nil, value]
213
- else
214
- [name, value, options]
215
- end
216
- end
217
-
218
- # Set a config value by key name
219
- #
220
- # @overload config_set(execution_context, name, value, **options)
221
- #
222
- # @param execution_context [Git::ExecutionContext] the execution context
223
- #
224
- # @param name [String] the dotted config key to write (e.g. `"user.name"`)
225
- #
226
- # @param value [String] the value to assign
227
- #
228
- # @param options [Hash] keyword options forwarded to the command
229
- #
230
- # @option options [String, nil] :file (nil) path to a custom config file
231
- # to write to instead of the repository's default `.git/config`
232
- #
233
- # @return [Git::CommandLineResult] the raw result of
234
- # `git config <name> <value>`
235
- #
236
- # @raise [ArgumentError] if unsupported options are provided
237
- #
238
- # @raise [Git::FailedError] if git exits with a non-zero exit status
239
- #
240
- def config_set(execution_context, name, value, **)
241
- SharedPrivate.assert_valid_opts!(CONFIG_SET_ALLOWED_OPTS, **)
242
- Git::Commands::ConfigOptionSyntax::Set.new(execution_context).call(name, value, **)
243
- end
244
-
245
- # Retrieve a config value by key name
246
- #
247
- # @param execution_context [Git::ExecutionContext] the execution context
248
- #
249
- # @param name [String] the dotted config key to look up (e.g. `"user.name"`)
250
- #
251
- # @param options [Hash] keyword options
252
- #
253
- # @option options [String, nil] :file (nil) path to a custom config file to read from
254
- #
255
- # @return [String] the value of the config entry
256
- #
257
- # @raise [ArgumentError] if unsupported options are provided
258
- #
259
- # @raise [Git::FailedError] if git exits with a non-zero exit status
260
- #
261
- def config_get(execution_context, name, **options)
262
- SharedPrivate.assert_valid_opts!(CONFIG_READ_ALLOWED_OPTS, **options)
263
- opts = options[:file] ? { file: options[:file] } : {}
264
- result = Git::Commands::ConfigOptionSyntax::Get.new(execution_context).call(name, **opts)
265
- raise Git::FailedError, result if result.status.exitstatus != 0
266
-
267
- result.stdout
268
- end
269
-
270
- # Retrieve all config entries as a hash
271
- #
272
- # @param execution_context [Git::ExecutionContext] the execution context
273
- #
274
- # @param options [Hash] keyword options
275
- #
276
- # @option options [String, nil] :file (nil) path to a custom config file to read from
277
- #
278
- # @return [Hash{String => String}] all config entries, keyed by their full
279
- # dotted key names (e.g. `"user.name"`)
280
- #
281
- # @raise [ArgumentError] if unsupported options are provided
282
- #
283
- # @raise [Git::FailedError] if git exits with a non-zero exit status
284
- #
285
- def config_list(execution_context, **options)
286
- SharedPrivate.assert_valid_opts!(CONFIG_READ_ALLOWED_OPTS, **options)
287
- opts = options[:file] ? { file: options[:file] } : {}
288
- lines = Git::Commands::ConfigOptionSyntax::List.new(execution_context).call(**opts).stdout.split("\n")
289
- lines.each_with_object({}) do |line, hsh|
290
- key, value = line.split('=', 2)
291
- hsh[key] = value || ''
292
- end
293
- end
294
-
295
- # Retrieve a global config value by key name
296
- #
297
- # @param execution_context [Git::ExecutionContext] the execution context
298
- #
299
- # @param name [String] the dotted config key to look up (e.g. `"user.name"`)
300
- #
301
- # @return [String] the value of the global config entry
302
- #
303
- # @raise [Git::FailedError] if git exits with a non-zero exit status
304
- #
305
- def global_config_get(execution_context, name)
306
- result = Git::Commands::ConfigOptionSyntax::Get.new(execution_context).call(name, global: true)
307
- raise Git::FailedError, result if result.status.exitstatus != 0
308
-
309
- result.stdout
310
- end
311
-
312
- # Retrieve all global config entries as a hash
313
- #
314
- # @param execution_context [Git::ExecutionContext] the execution context
315
- #
316
- # @return [Hash{String => String}] all global config entries, keyed by their full
317
- # dotted key names (e.g. `"user.name"`)
318
- #
319
- # @raise [Git::FailedError] if git exits with a non-zero exit status
320
- #
321
- def global_config_list(execution_context)
322
- lines = Git::Commands::ConfigOptionSyntax::List.new(execution_context).call(global: true).stdout.split("\n")
323
- lines.each_with_object({}) do |line, hsh|
324
- key, value = line.split('=', 2)
325
- hsh[key] = value || ''
326
- end
327
- end
328
-
329
- # Set a global config value by key name
330
- #
331
- # @param execution_context [Git::ExecutionContext] the execution context
332
- #
333
- # @param name [String] the dotted config key to write (e.g. `"user.name"`)
334
- #
335
- # @param value [#to_s] the value to assign; any object is accepted and
336
- # converted to a String via `#to_s` before being passed to git
337
- #
338
- # @return [Git::CommandLineResult] the raw result of
339
- # `git config --global <name> <value>`
340
- #
341
- # @raise [Git::FailedError] if git exits with a non-zero exit status
342
- #
343
- def global_config_set(execution_context, name, value)
344
- Git::Commands::ConfigOptionSyntax::Set.new(execution_context).call(name, value, global: true)
345
- end
346
- end
347
-
348
- private_constant :Private
349
- end
350
- end
351
- end