git 5.1.0 → 5.2.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 (101) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +45 -0
  3. data/CONTRIBUTING.md +185 -165
  4. data/LICENSE +1 -1
  5. data/README.md +151 -151
  6. data/git.gemspec +35 -3
  7. data/lib/git/command_line/base.rb +1 -2
  8. data/lib/git/commands/base.rb +1 -1
  9. data/lib/git/commands/cat_file/raw.rb +8 -7
  10. data/lib/git/repository/branching.rb +79 -5
  11. data/lib/git/version.rb +1 -1
  12. metadata +5 -94
  13. data/.claude/commands/address-copilot-reviews.md +0 -14
  14. data/.claude/settings.json +0 -16
  15. data/.claude/skills +0 -1
  16. data/.commitlintrc.yml +0 -38
  17. data/.dockerignore +0 -27
  18. data/.github/copilot-instructions.md +0 -30
  19. data/.github/hooks/bin-setup-on-worktree.json +0 -11
  20. data/.github/hooks/run-bin-setup-once.sh +0 -20
  21. data/.github/issue_template.md +0 -15
  22. data/.github/prompts/iteratively-address-copilot-reviews.prompt.md +0 -188
  23. data/.github/pull_request_template.md +0 -21
  24. data/.github/skills/breaking-change-analysis/SKILL.md +0 -99
  25. data/.github/skills/ci-cd-troubleshooting/SKILL.md +0 -264
  26. data/.github/skills/command-implementation/REFERENCE.md +0 -994
  27. data/.github/skills/command-implementation/SKILL.md +0 -230
  28. data/.github/skills/command-test-conventions/SKILL.md +0 -664
  29. data/.github/skills/command-yard-documentation/SKILL.md +0 -434
  30. data/.github/skills/dependency-management/SKILL.md +0 -72
  31. data/.github/skills/development-workflow/SKILL.md +0 -512
  32. data/.github/skills/facade-implementation/REFERENCE.md +0 -837
  33. data/.github/skills/facade-implementation/SKILL.md +0 -269
  34. data/.github/skills/facade-test-conventions/SKILL.md +0 -391
  35. data/.github/skills/facade-yard-documentation/SKILL.md +0 -435
  36. data/.github/skills/make-skill-template/SKILL.md +0 -226
  37. data/.github/skills/pr-readiness-review/SKILL.md +0 -205
  38. data/.github/skills/project-context/SKILL.md +0 -306
  39. data/.github/skills/pull-request-review/SKILL.md +0 -168
  40. data/.github/skills/rebase/SKILL.md +0 -148
  41. data/.github/skills/refactor-command-to-commandlineresult/SKILL.md +0 -131
  42. data/.github/skills/release-management/SKILL.md +0 -125
  43. data/.github/skills/resolve-feedback/SKILL.md +0 -288
  44. data/.github/skills/review-arguments-dsl/CHECKLIST.md +0 -788
  45. data/.github/skills/review-arguments-dsl/SKILL.md +0 -214
  46. data/.github/skills/review-cross-command-consistency/SKILL.md +0 -139
  47. data/.github/skills/reviewing-skills/SKILL.md +0 -214
  48. data/.github/skills/rspec-unit-testing-standards/SKILL.md +0 -685
  49. data/.github/skills/tdd-refactor-step/SKILL.md +0 -236
  50. data/.github/skills/test-debugging/SKILL.md +0 -161
  51. data/.github/skills/yard-documentation/SKILL.md +0 -981
  52. data/.github/skills/yard-documentation/element-rules.md +0 -162
  53. data/.github/skills-deprecated/README.md +0 -21
  54. data/.github/skills-deprecated/extract-command-from-lib/SKILL.md +0 -487
  55. data/.github/skills-deprecated/extract-facade-from-base-lib/KEYWORD_ARG_REMEDIATION.md +0 -22
  56. data/.github/skills-deprecated/extract-facade-from-base-lib/SKILL.md +0 -600
  57. data/.github/skills-deprecated/review-backward-compatibility/SKILL.md +0 -275
  58. data/.github/workflows/continuous_integration.yml +0 -358
  59. data/.github/workflows/enforce_conventional_commits.yml +0 -35
  60. data/.github/workflows/experimental_continuous_integration.yml +0 -59
  61. data/.github/workflows/release.yml +0 -52
  62. data/.github/workflows/warm_bundler_caches.yml +0 -82
  63. data/.gitignore +0 -30
  64. data/.husky/commit-msg +0 -1
  65. data/.husky/pre-commit +0 -13
  66. data/.release-please-config.json +0 -36
  67. data/.release-please-manifest.json +0 -3
  68. data/.rspec +0 -2
  69. data/.rubocop.yml +0 -44
  70. data/.rubocop_todo.yml +0 -30
  71. data/.yard-lint.yml +0 -75
  72. data/CLAUDE.md +0 -11
  73. data/Gemfile +0 -22
  74. data/Rakefile +0 -41
  75. data/docker/test/Dockerfile +0 -32
  76. data/docker/test/docker-compose.yml +0 -0
  77. data/package.json +0 -10
  78. data/redesign/1_architecture_existing.md +0 -102
  79. data/redesign/2_architecture_redesign.md +0 -449
  80. data/redesign/3_architecture_implementation.md +0 -1623
  81. data/redesign/Phase 4 - Step A.md +0 -366
  82. data/redesign/Phase 4 - Step B.md +0 -921
  83. data/redesign/Phase 4 - Step C.md +0 -833
  84. data/redesign/beta_release.md +0 -107
  85. data/redesign/branch_parse_refactor_plan.md +0 -163
  86. data/redesign/c1a-public-api-scope.tsv +0 -256
  87. data/redesign/c1c2_audit.md +0 -566
  88. data/redesign/c1c2_bucket6_lib_orphans.md +0 -626
  89. data/redesign/config_design.rb +0 -501
  90. data/redesign/index.md +0 -34
  91. data/redesign/info_object_migration_plan.md +0 -126
  92. data/redesign/integration_test_analysis.md +0 -521
  93. data/redesign/phase-4-step-b-test-audit.tsv +0 -485
  94. data/redesign/remote_refactor_plan.md +0 -164
  95. data/redesign/reverse_dependencies.sql +0 -44
  96. data/tasks/gem_tasks.rake +0 -14
  97. data/tasks/npm_tasks.rake +0 -7
  98. data/tasks/rspec.rake +0 -111
  99. data/tasks/rubocop.rake +0 -5
  100. data/tasks/test_gem.rake +0 -12
  101. data/tasks/yard.rake +0 -57
@@ -1,501 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- # This is a forward looking document (in Ruby format) on the evolution of `git
4
- # config` methods in Ruby Git. Here is how `git config` methods will change in
5
- # v5.x and v6.x.
6
- #
7
- # In v5.x, `Git.config` and `Git.global_config` will continue to work as they do in
8
- # v4.x but will be deprecated. New methods in the Git::Configuring module will be
9
- # introduced in Git and Git::Repository to provide structured access to configuration
10
- # entries. These will not be strict drop-in replacements for the existing deprecated
11
- # methods.
12
- #
13
- # In v6.x, the deprecated methods will be removed.
14
-
15
- # Top level Git module
16
- module Git
17
- # Represents a single Git configuration entry
18
- #
19
- # @example
20
- # scope = 'local'
21
- # origin = 'path/to/config'
22
- # key = 'remote.origin.url'
23
- # value = 'https://github.com/ruby-git/ruby-git'
24
- # entry = Git::ConfigEntryInfo.new(scope:, origin:, key: , value:)
25
- #
26
- # @!attribute [r] scope
27
- #
28
- # The scope of the configuration entry
29
- #
30
- # May be one of "system", "global", "local", "worktree", "file", or "blob".
31
- #
32
- # @return [String]
33
- #
34
- # @!attribute [r] origin
35
- #
36
- # Where the configuration entry originates
37
- #
38
- # The origin is in the format: `<origin-type>:<actual-origin>`. It is never
39
- # blank.
40
- #
41
- # ### `origin-type`
42
- #
43
- # This prefix explains the context of the configuration source. The four possible
44
- # types are `file:`, `blob:`, `command line:`, and `standard input:`.
45
- #
46
- # ### `actual-origin`
47
- #
48
- # This provides the specific location for the configuration source. Only the
49
- # `file:` and `blob:` origin types have an actual origin. For `command line:` and
50
- # `standard input:`, Git drops this portion entirely and places the tab character
51
- # immediately after the colon.
52
- #
53
- # #### Path Resolution
54
- #
55
- # When the origin type is a file, the actual origin can be formatted as either an
56
- # absolute or relative path depending on how Git resolves it.
57
- #
58
- # - **Absolute Paths**: Git outputs the full system path for system-level configurations,
59
- # global configurations, explicitly provided absolute paths, or absolute
60
- # paths used in [include] directives.
61
- # - **Relative Paths**: Git outputs a relative path for local repository configurations,
62
- # explicitly provided relative paths, or relative paths used in [include]
63
- # directives.
64
- # - **Relative Anchors**: Local repository paths are relative to the repository
65
- # root. Command-line relative paths are relative to your current working
66
- # directory. Relative paths from an [include] directive are anchored to
67
- # the parent configuration file that included them.
68
- #
69
- # @return [String]
70
- #
71
- # @!attribute [r] key
72
- #
73
- # The full key name of the configuration entry (e.g., remote.origin.url)
74
- #
75
- # @return [String]
76
- #
77
- # @!attribute [r] value
78
- #
79
- # The value of the configuration entry
80
- #
81
- # @return [String]
82
- #
83
- # @api public
84
- #
85
- ConfigEntryInfo = Data.define(:scope, :origin, :key, :value) do
86
- # Everything up to the first dot in the {key}
87
- #
88
- # Returns an empty string if {key} contains no dot.
89
- #
90
- # @example
91
- # entry.section # => 'remote'
92
- #
93
- # @return [String]
94
- #
95
- def section = first_dot ? key[0...first_dot] : ''
96
-
97
- # Everything between the first and last dot in the {key}
98
- #
99
- # Returns an empty string if {key} has no subsection (zero or one dot).
100
- #
101
- # @example
102
- # entry.subsection # => 'origin'
103
- #
104
- # @return [String]
105
- #
106
- def subsection = first_dot && first_dot != last_dot ? key[(first_dot + 1)...last_dot] : ''
107
-
108
- # Everything after the last dot in the {key}
109
- #
110
- # Returns the full {key} if {key} contains no dot.
111
- #
112
- # @example
113
- # entry.variable # => 'url'
114
- #
115
- # @return [String]
116
- #
117
- def variable = last_dot ? key[(last_dot + 1)..] : key
118
-
119
- private
120
-
121
- def first_dot = key.index('.')
122
-
123
- def last_dot = key.rindex('.')
124
- end
125
-
126
- module Parsers
127
- # Parser for `git config --get` and `git config --list` output
128
- # when called with `--show-scope --show-origin --null`.
129
- #
130
- # @api private
131
- #
132
- module ConfigEntry
133
- module_function
134
-
135
- # Parse `git config --get --show-scope --show-origin --null` output.
136
- #
137
- # Output format (per entry): `scope\0origin\0value\0`
138
- # The key name is not present in --get output; it must be supplied.
139
- #
140
- # @param key [String] the config key name that was queried
141
- # @param output [String] raw stdout from the command
142
- # @return [Git::ConfigEntryInfo, nil] the parsed entry, or nil if not found
143
- #
144
- def parse_get(key, output)
145
- return nil if output.empty?
146
-
147
- scope, origin, value = output.split("\0", -1)
148
- Git::ConfigEntryInfo.new(scope: scope, origin: origin, key: key, value: value)
149
- end
150
-
151
- # Parse `git config --get-all --show-scope --show-origin --null` output.
152
- #
153
- # Output format (per entry): `scope\0origin\0value\0`
154
- # The key name is not present in --get-all output; it must be supplied.
155
- # Entries repeat back-to-back in the same string.
156
- #
157
- # @param key [String] the config key name that was queried
158
- # @param output [String] raw stdout from the command
159
- # @return [Array<Git::ConfigEntryInfo>] the parsed entries
160
- #
161
- def parse_get_all(key, output)
162
- return [] if output.empty?
163
-
164
- tokens = output.split("\0", -1)
165
- tokens.pop if tokens.last && tokens.last.empty?
166
- tokens.each_slice(3).map do |scope, origin, value|
167
- Git::ConfigEntryInfo.new(scope: scope, origin: origin, key: key, value: value)
168
- end
169
- end
170
-
171
- # Parse `git config --list --show-scope --show-origin --null` output.
172
- #
173
- # Also used for `--get-regexp` and `--get-urlmatch` output, which share
174
- # the same format.
175
- #
176
- # Output format (per entry): `scope\0origin\0key\nvalue\0`
177
- # Entries repeat back-to-back in the same string.
178
- #
179
- # @param output [String] raw stdout from the command
180
- # @return [Array<Git::ConfigEntryInfo>] the parsed entries
181
- #
182
- def parse_list(output)
183
- return [] if output.empty?
184
-
185
- tokens = output.split("\0", -1)
186
- tokens.pop if tokens.last && tokens.last.empty?
187
- tokens.each_slice(3).map do |scope, origin, key_value|
188
- key, value = key_value.split("\n", 2)
189
- Git::ConfigEntryInfo.new(scope: scope, origin: origin, key: key, value: value || '')
190
- end
191
- end
192
- end
193
- end
194
-
195
- # Designed to be included in the Git module and the Git::Repository module
196
- #
197
- # The including/extending class must implement {#execution_context} and
198
- # {#assert_valid_scope!}.
199
- #
200
- module Configuring # rubocop:disable Metrics/ModuleLength
201
- # @!group Read Operations
202
-
203
- CONFIG_GET_ALLOWED_OPTS = %i[global system local worktree file f blob includes no_includes type default].freeze
204
- private_constant :CONFIG_GET_ALLOWED_OPTS
205
-
206
- # @return [Git::ConfigEntryInfo, nil] nil if the key is not found
207
- def config_get(name, value_regex = nil, **options)
208
- Private.assert_valid_opts!(CONFIG_GET_ALLOWED_OPTS, **options)
209
- assert_valid_scope!(**options)
210
- options = options.merge(show_scope: true, show_origin: true, null: true)
211
- cmd = Git::Commands::ConfigOptionSyntax::Get.new(execution_context)
212
- output = cmd.call(name, value_regex, **options).stdout
213
- Git::Parsers::ConfigEntry.parse_get(name, output)
214
- end
215
-
216
- CONFIG_GET_ALL_ALLOWED_OPTS = %i[global system local worktree file f blob includes no_includes type].freeze
217
- private_constant :CONFIG_GET_ALL_ALLOWED_OPTS
218
-
219
- # @return [Array<Git::ConfigEntryInfo>]
220
- def config_get_all(name, value_regex = nil, **options)
221
- Private.assert_valid_opts!(CONFIG_GET_ALL_ALLOWED_OPTS, **options)
222
- assert_valid_scope!(**options)
223
- options = options.merge(show_scope: true, show_origin: true, null: true)
224
- cmd = Git::Commands::ConfigOptionSyntax::GetAll.new(execution_context)
225
- output = cmd.call(name, value_regex, **options).stdout
226
- Git::Parsers::ConfigEntry.parse_get_all(name, output)
227
- end
228
-
229
- # `git config --get --type=color [--default=<default>]` is preferred over
230
- # --get-color
231
- #
232
- # def config_get_color(name, default = nil, **)
233
- # ...
234
- # end
235
-
236
- CONFIG_GET_COLORBOOL_ALLOWED_OPTS = %i[global system local worktree file f blob includes no_includes].freeze
237
- private_constant :CONFIG_GET_COLORBOOL_ALLOWED_OPTS
238
-
239
- # @return [String] always 'true' or 'false'
240
- def config_get_colorbool(name, stdout_is_tty = nil, **)
241
- Private.assert_valid_opts!(CONFIG_GET_COLORBOOL_ALLOWED_OPTS, **)
242
- assert_valid_scope!(**)
243
- cmd = Git::Commands::ConfigOptionSyntax::GetColorBool.new(execution_context)
244
- cmd.call(name, stdout_is_tty, **).stdout.chomp
245
- end
246
-
247
- CONFIG_GET_REGEXP_ALLOWED_OPTS = %i[global system local worktree file f blob includes no_includes type].freeze
248
- private_constant :CONFIG_GET_REGEXP_ALLOWED_OPTS
249
-
250
- # @return [Array<Git::ConfigEntryInfo>]
251
- def config_get_regexp(name_regex, value_regex = nil, **options)
252
- Private.assert_valid_opts!(CONFIG_GET_REGEXP_ALLOWED_OPTS, **options)
253
- assert_valid_scope!(**options)
254
- options = options.merge(show_scope: true, show_origin: true, null: true)
255
- cmd = Git::Commands::ConfigOptionSyntax::GetRegexp.new(execution_context)
256
- output = cmd.call(name_regex, value_regex, **options).stdout
257
- Git::Parsers::ConfigEntry.parse_list(output)
258
- end
259
-
260
- CONFIG_GET_URLMATCH_ALLOWED_OPTS = %i[global system local worktree file f blob includes no_includes type].freeze
261
- private_constant :CONFIG_GET_URLMATCH_ALLOWED_OPTS
262
-
263
- # @return [Array<Git::ConfigEntryInfo>]
264
- def config_get_urlmatch(name, url, **options)
265
- Private.assert_valid_opts!(CONFIG_GET_URLMATCH_ALLOWED_OPTS, **options)
266
- assert_valid_scope!(**options)
267
- options = options.merge(show_scope: true, show_origin: true, null: true)
268
- cmd = Git::Commands::ConfigOptionSyntax::GetUrlmatch.new(execution_context)
269
- output = cmd.call(name, url, **options).stdout
270
- Git::Parsers::ConfigEntry.parse_list(output)
271
- end
272
-
273
- CONFIG_LIST_ALLOWED_OPTS = %i[global system local worktree file f blob includes no_includes type].freeze
274
- private_constant :CONFIG_LIST_ALLOWED_OPTS
275
-
276
- # @return [Array<Git::ConfigEntryInfo>]
277
- def config_list(**options)
278
- Private.assert_valid_opts!(CONFIG_LIST_ALLOWED_OPTS, **options)
279
- assert_valid_scope!(**options)
280
- options = options.merge(show_scope: true, show_origin: true, null: true)
281
- cmd = Git::Commands::ConfigOptionSyntax::List.new(execution_context)
282
- output = cmd.call(**options).stdout
283
- Git::Parsers::ConfigEntry.parse_list(output)
284
- end
285
-
286
- # @!endgroup
287
-
288
- # @!group Write Operations
289
-
290
- CONFIG_ADD_ALLOWED_OPTS = %i[global system local worktree file f blob type].freeze
291
- private_constant :CONFIG_ADD_ALLOWED_OPTS
292
-
293
- # @return [void]
294
- #
295
- # @raise [ArgumentError] if unsupported options are provided
296
- #
297
- # @raise [Git::FailedError] if git exits with a non-zero exit status
298
- def config_add(name, value, **)
299
- Private.assert_valid_opts!(CONFIG_ADD_ALLOWED_OPTS, **)
300
- assert_valid_scope!(**)
301
- cmd = Git::Commands::ConfigOptionSyntax::Add.new(execution_context)
302
- cmd.call(name, value, **)
303
- nil
304
- end
305
-
306
- CONFIG_REMOVE_SECTION_ALLOWED_OPTS = %i[global system local worktree file f blob].freeze
307
- private_constant :CONFIG_REMOVE_SECTION_ALLOWED_OPTS
308
-
309
- # @return [void]
310
- #
311
- # @raise [ArgumentError] if unsupported options are provided
312
- #
313
- # @raise [Git::FailedError] if git exits with a non-zero exit status
314
- def config_remove_section(name, **)
315
- Private.assert_valid_opts!(CONFIG_REMOVE_SECTION_ALLOWED_OPTS, **)
316
- assert_valid_scope!(**)
317
- cmd = Git::Commands::ConfigOptionSyntax::RemoveSection.new(execution_context)
318
- cmd.call(name, **)
319
- nil
320
- end
321
-
322
- CONFIG_RENAME_SECTION_ALLOWED_OPTS = %i[global system local worktree file f blob].freeze
323
- private_constant :CONFIG_RENAME_SECTION_ALLOWED_OPTS
324
-
325
- # @return [void]
326
- #
327
- # @raise [ArgumentError] if unsupported options are provided
328
- #
329
- # @raise [Git::FailedError] if git exits with a non-zero exit status
330
- def config_rename_section(old_name, new_name, **)
331
- Private.assert_valid_opts!(CONFIG_RENAME_SECTION_ALLOWED_OPTS, **)
332
- assert_valid_scope!(**)
333
- cmd = Git::Commands::ConfigOptionSyntax::RenameSection.new(execution_context)
334
- cmd.call(old_name, new_name, **)
335
- nil
336
- end
337
-
338
- CONFIG_REPLACE_ALL_ALLOWED_OPTS = %i[global system local worktree file f blob type].freeze
339
- private_constant :CONFIG_REPLACE_ALL_ALLOWED_OPTS
340
-
341
- # @return [void]
342
- #
343
- # @raise [ArgumentError] if unsupported options are provided
344
- #
345
- # @raise [Git::FailedError] if git exits with a non-zero exit status
346
- def config_replace_all(name, value, value_regex = nil, **)
347
- Private.assert_valid_opts!(CONFIG_REPLACE_ALL_ALLOWED_OPTS, **)
348
- assert_valid_scope!(**)
349
- cmd = Git::Commands::ConfigOptionSyntax::ReplaceAll.new(execution_context)
350
- cmd.call(name, value, value_regex, **)
351
- nil
352
- end
353
-
354
- CONFIG_SET_ALLOWED_OPTS = %i[global system local worktree file f blob type].freeze
355
- private_constant :CONFIG_SET_ALLOWED_OPTS
356
-
357
- # @return [void]
358
- #
359
- # @raise [ArgumentError] if unsupported options are provided
360
- #
361
- # @raise [Git::FailedError] if git exits with a non-zero exit status
362
- def config_set(name, value, **)
363
- Private.assert_valid_opts!(CONFIG_SET_ALLOWED_OPTS, **)
364
- assert_valid_scope!(**)
365
- cmd = Git::Commands::ConfigOptionSyntax::Set.new(execution_context)
366
- cmd.call(name, value, **)
367
- nil
368
- end
369
-
370
- CONFIG_UNSET_ALLOWED_OPTS = %i[global system local worktree file f blob].freeze
371
- private_constant :CONFIG_UNSET_ALLOWED_OPTS
372
-
373
- # @return [void]
374
- #
375
- # @raise [ArgumentError] if unsupported options are provided
376
- #
377
- # @raise [Git::FailedError] if git exits with a non-zero exit status
378
- def config_unset(name, value_regex = nil, **)
379
- Private.assert_valid_opts!(CONFIG_UNSET_ALLOWED_OPTS, **)
380
- assert_valid_scope!(**)
381
- cmd = Git::Commands::ConfigOptionSyntax::Unset.new(execution_context)
382
- cmd.call(name, value_regex, **)
383
- nil
384
- end
385
-
386
- CONFIG_UNSET_ALL_ALLOWED_OPTS = %i[global system local worktree file f blob].freeze
387
- private_constant :CONFIG_UNSET_ALL_ALLOWED_OPTS
388
-
389
- # @return [void]
390
- #
391
- # @raise [ArgumentError] if unsupported options are provided
392
- #
393
- # @raise [Git::FailedError] if git exits with a non-zero exit status
394
- def config_unset_all(name, value_regex = nil, **)
395
- Private.assert_valid_opts!(CONFIG_UNSET_ALL_ALLOWED_OPTS, **)
396
- assert_valid_scope!(**)
397
- cmd = Git::Commands::ConfigOptionSyntax::UnsetAll.new(execution_context)
398
- cmd.call(name, value_regex, **)
399
- nil
400
- end
401
-
402
- # @!endgroup
403
-
404
- private
405
-
406
- # @abstract Including/extending class must implement execution_context
407
- #
408
- # @return [Git::ExecutionContext]
409
- #
410
- def execution_context
411
- raise NotImplementedError
412
- end
413
-
414
- # @abstract Including/extending class must implement assert_valid_scope!
415
- #
416
- # Called before every config operation to validate that the requested scope
417
- # is appropriate for the context (e.g., repository-specific scopes such as
418
- # `local` are not valid when called without a repository).
419
- #
420
- # @raise [ArgumentError] if the scope is not permitted in this context
421
- #
422
- # @return [void]
423
- #
424
- def assert_valid_scope!(**)
425
- raise NotImplementedError
426
- end
427
-
428
- # Internal helpers local to {Git::Configuring}
429
- #
430
- # @api private
431
- #
432
- module Private
433
- module_function
434
-
435
- # Validate that `options` contains only keys listed in `allowed`
436
- #
437
- # @example Reject an undocumented option
438
- # Private.assert_valid_opts!(%i[all force], bogus: true)
439
- # #=> raises ArgumentError: Unknown options: bogus
440
- #
441
- # @param allowed [Array<Symbol>] the keys permitted by the facade method
442
- #
443
- # @param options [Hash] the options hash provided by the caller
444
- #
445
- # @return [void]
446
- #
447
- # @raise [ArgumentError] when `options` contains any key not in `allowed`
448
- #
449
- def assert_valid_opts!(allowed, **options)
450
- unknown = options.keys - allowed
451
- return if unknown.empty?
452
-
453
- raise ArgumentError, "Unknown options: #{unknown.join(', ')}"
454
- end
455
- end
456
- private_constant :Private
457
- end
458
-
459
- REPOSITORY_SPECIFIC_SCOPES = %i[local worktree blob].freeze
460
- private_constant :REPOSITORY_SPECIFIC_SCOPES
461
-
462
- # Reopens Git::Repository to mix in config read/write operations.
463
- #
464
- # In the real implementation this `include` lives in
465
- # `lib/git/repository/configuring.rb` and is picked up by the
466
- # `Git::Repository` class definition in `lib/git/repository.rb`.
467
- class Repository
468
- # Mixes in config_get, config_list, config_set, etc.
469
- include Git::Configuring
470
-
471
- private
472
-
473
- # @return [Git::ExecutionContext::Repository]
474
- attr_reader :execution_context
475
-
476
- # All scopes are permitted when called with a repository context.
477
- def assert_valid_scope!(**)
478
- # All scopes allowed
479
- end
480
- end
481
-
482
- # Enables calling Git.config_get('user.name')
483
- extend Git::Configuring
484
-
485
- # Returns a global execution context for non-repository config operations.
486
- #
487
- # Overrides the abstract {Git::Configuring#execution_context} for the
488
- # module-level case.
489
- #
490
- # @return [Git::ExecutionContext::Global]
491
- def self.execution_context
492
- Git::ExecutionContext::Global.new
493
- end
494
- private_class_method :execution_context
495
-
496
- def self.assert_valid_scope!(**options)
497
- repo_scopes = options.keys.select { |k| options[k] && REPOSITORY_SPECIFIC_SCOPES.include?(k) }
498
- raise ArgumentError, "scope #{repo_scopes.first} requires a repository" if repo_scopes.any?
499
- end
500
- private_class_method :assert_valid_scope!
501
- end
data/redesign/index.md DELETED
@@ -1,34 +0,0 @@
1
- # Architectural Redesign Project
2
-
3
- [This project was announced in the project's README](../README.md#2025-07-09-architectural-redesign)
4
-
5
- The git gem is undergoing a significant architectural redesign for the upcoming
6
- v5.0.0 release. The current architecture has several design challenges that make it
7
- difficult to maintain and evolve. This redesign aims to address these issues by
8
- introducing a clearer, more robust, and more testable structure.
9
-
10
- We have prepared detailed documents outlining the analysis of the current
11
- architecture and the proposed changes. We encourage our community and contributors to
12
- review them:
13
-
14
- 1. [Analysis of the Current Architecture](1_architecture_existing.md): A
15
- breakdown of the existing design and its challenges.
16
- 2. [The Proposed Redesign](2_architecture_redesign.md): An overview of the
17
- new three-layered architecture.
18
- 3. [Implementation Plan](3_architecture_implementation.md): The step-by-step
19
- plan for implementing the redesign.
20
-
21
- Your feedback is welcome! Please feel free to open an issue to discuss the proposed
22
- changes.
23
-
24
- > **DON'T PANIC!**
25
- >
26
- > While this is a major internal refactoring, our goal is to keep the primary public
27
- API on the main repository object as stable as possible. Most users who rely on
28
- documented methods like `g.commit`, `g.add`, and `g.status` should find the
29
- transition to v5.0.0 straightforward.
30
- >
31
- > The breaking changes will primarily affect users who have been relying on the
32
- internal g.lib accessor, which will be removed as part of this cleanup. For more
33
- details, please see the "Impact on Users" section in [the redesign
34
- > document](2_architecture_redesign.md).
@@ -1,126 +0,0 @@
1
- # Plan: Migrate ActiveRecord-style classes to `*Info` value objects
2
-
3
- This document is the durable summary and issue index for the effort to migrate
4
- ruby-git's "ActiveRecord-style" domain classes (rich objects constructed with
5
- `def initialize(base, ...)` that both hold data **and** perform git operations)
6
- toward immutable `*Info` value objects (DAOs), following the pattern established
7
- by `Git::BranchInfo` and `Git::RemoteInfo`.
8
-
9
- It captures analysis and decisions so they are not re-derived in future sessions.
10
- It is a planning/index document, not a specification — each tracked issue owns the
11
- detailed design for its area.
12
-
13
- ## Contents
14
-
15
- - [Motivation](#motivation)
16
- - [The pattern](#the-pattern)
17
- - [Status of each class](#status-of-each-class)
18
- - [Issue index](#issue-index)
19
- - [Versioning and sequencing](#versioning-and-sequencing)
20
- - [Key decisions](#key-decisions)
21
- - [Open threads / not yet tracked](#open-threads--not-yet-tracked)
22
-
23
- ## Motivation
24
-
25
- The modern facade design returns immutable, parser-built value objects
26
- (`Git::BranchInfo`, `Git::RemoteInfo`, `Git::StashInfo`, `Git::TagInfo`,
27
- `Git::ConfigEntryInfo`, the `Diff*Info` family, …). Several older classes predate
28
- this and mix data with operations while holding a repository reference (`@base`).
29
- They are harder to test, encourage stateful usage, and (in some cases) carry
30
- latent bugs. Migrating them to `*Info` value objects plus name-based facade
31
- operations aligns the whole API and unblocks cleaner deprecations.
32
-
33
- The recurring "tell" for a migration candidate is a `def initialize(base, ...)`
34
- constructor combined with operation methods — the object both *is* data and
35
- *does* work.
36
-
37
- ## The pattern
38
-
39
- For each domain:
40
-
41
- 1. **Value object** — an immutable `Data.define` `*Info` at the top-level `Git::`
42
- namespace holding only parsed metadata, with `#to_s` returning the string a
43
- caller can pass back to git-facing methods (e.g. a refname or stash selector).
44
- 2. **Parser** — a `Git::Parsers::*` that builds `Array<*Info>` from raw git output.
45
- 3. **Facade** — `Git::Repository::*` methods that return the value objects and that
46
- accept names / `*Info` / any `#to_s` for operations.
47
- 4. **Deprecation** — the legacy AR class, its collection, and the facade methods
48
- that return them are deprecated (additive, non-breaking) in one major and
49
- removed in the next.
50
-
51
- ## Status of each class
52
-
53
- | Domain | AR class(es) | Value object | State |
54
- | --- | --- | --- | --- |
55
- | Branch | `Git::Branch` / `Git::Branches` | `Git::BranchInfo`, `Git::DetachedHeadInfo` | Value object + `branch_list` shipped; deprecation of AR classes tracked (#1639) |
56
- | Remote | `Git::Remote` | `Git::RemoteInfo` | `RemoteInfo` + parser shipped; `remotes` deprecation tracked (#1640); `Git::Remote` class deprecation tracked (#1643) |
57
- | Stash | `Git::Stash` / `Git::Stashes` | `Git::StashInfo` (exists) | Redesign tracked (#1634); `Git::Branch#stashes` cleanup (#1637) |
58
- | Worktree | `Git::Worktree` / `Git::Worktrees` | *(needs `Git::WorktreeInfo`)* | Redesign tracked (#1635) — value object + parser do not exist yet |
59
- | Tag | `Git::Object::Tag` | `Git::TagInfo` (exists) | Half-migrated; finish tracked under umbrella (#1636) |
60
- | Commit / Tree / Blob | `Git::Object::Commit/Tree/Blob` | *(none)* | Deferred, umbrella (#1636) |
61
- | Status | `Git::Status` / `Git::Status::StatusFile` | *(none)* | Deferred, umbrella (#1636) |
62
- | Author | `Git::Author` (mutable) | *(none)* | Deferred, umbrella (#1636) |
63
- | Diff / Log | `Git::Diff`, `Git::Log` | many `Diff*Info`, `Git::Log::Result` | Value layer largely exists; return-type audit deferred (#1636) |
64
-
65
- ## Issue index
66
-
67
- - **#1631** — bug: slash-containing remote names in the `Git::Branch` string
68
- constructor path. Kept open; resolution is to route users to the `BranchInfo`
69
- path (folds into #1639).
70
- - **#1634** — redesign `Git::Repository::Stashing` around `Git::StashInfo`.
71
- - **#1635** — redesign `Git::Repository::WorktreeOperations` around a new
72
- `Git::WorktreeInfo` (needs value object + parser first).
73
- - **#1636** — umbrella tracker for post-7.x AR→`*Info` migrations
74
- (Commit/Tree/Blob, finish Tag, Status, Author, Diff/Log audit).
75
- - **#1637** — deprecate `Git::Branch#stashes` (ignores the branch, returns all
76
- repo stashes) in favor of `Git::Repository#stashes_all`.
77
- - **#1639** — deprecate `Git::Branch` / `Git::Branches` and
78
- `Git::Repository#branch` / `#branches` in favor of the `BranchInfo` API.
79
- - **#1640** — deprecate `Git::Repository#remotes` in favor of `#remote_list`
80
- (from `remote_refactor_plan.md` PR 3, Step 5).
81
- - **#1641** — add `Git::Repository#in_branch` and a merge-into-branch facade path
82
- (preconditions for #1639).
83
- - **#1643** — deprecate `Git::Remote` (and `Git::Repository#remote`) in favor of
84
- the `RemoteInfo` API (remote analog of #1639; depends on #1640).
85
-
86
- ## Versioning and sequencing
87
-
88
- - Each migration is **additive + deprecate** in one major, **remove** in the next
89
- (e.g. deprecate in 5.x → remove in 6.x, or the analogous later pair).
90
- - Stash (#1634) and Worktree (#1635) are the in-flight redesigns; the remaining
91
- candidates in #1636 are intentionally deferred until after the 7.x cycle.
92
- - **Guardrail during any major:** do not add *new* public APIs that return
93
- AR-style objects that are already destined for deprecation. When touching these
94
- areas, prefer adding the value-object-returning method. The `Git::Object::Tag`
95
- vs `Git::TagInfo` split is the most important to watch (already half-migrated).
96
-
97
- ## Key decisions
98
-
99
- - **`Git::WorktreeInfo` should capture the full porcelain record** — `path`,
100
- `head`, `branch`, `bare`, `detached`, `locked` (+reason), `prunable` (+reason) —
101
- not just the `[dir, sha]` tuple `worktrees_all` returns today.
102
- - **Branch has no capability gap.** An audit confirmed every `Git::Branch`
103
- operation already delegates to an existing `Git::Repository` facade method that
104
- accepts a name/string. The only missing pieces are two composites — `in_branch`
105
- and the `merge(branch)` overload — tracked in #1641 as deprecation preconditions.
106
- - **`checkout` semantic wrinkle:** `Git::Branch#checkout` auto-creates the branch
107
- first; `Git::Repository#checkout(name)` does not. Deprecation notes must call
108
- this out.
109
- - **`Git::Branch#stashes` is effectively a latent bug** — it ignores the branch
110
- receiver and returns all repository stashes — so it is "deprecate and delete,"
111
- not "migrate" (#1637).
112
- - **`Git::Repository::Branching` / `RemoteOperations` / `WorktreeOperations` are
113
- the replacements, not deprecation targets.** Only the specific methods that
114
- return AR objects (`#branch`, `#branches`, `#remotes`, `#worktree`,
115
- `#worktrees`) are deprecated; the operation methods stay.
116
- - **Stashes are not branch-scoped in git**, so no branch-scoped stash API is
117
- warranted.
118
-
119
- ## Open threads
120
-
121
- All previously loose threads are now tracked within the issues above:
122
-
123
- - **`Git::Repository#worktree` / `#worktrees` factory deprecation** — in scope of
124
- #1635 (confirmed in its transition plan).
125
- - **`config_remote` relationship to `remote_list`** — folded into #1640 as an open
126
- question to resolve during that work.