git 5.0.0.beta.1 → 5.0.0.beta.2

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 (71) hide show
  1. checksums.yaml +4 -4
  2. data/.github/copilot-instructions.md +6 -0
  3. data/.github/prompts/iteratively-address-copilot-reviews.prompt.md +188 -0
  4. data/.github/skills/extract-facade-from-base-lib/KEYWORD_ARG_REMEDIATION.md +22 -0
  5. data/.github/skills/extract-facade-from-base-lib/SKILL.md +28 -14
  6. data/.github/skills/facade-implementation/SKILL.md +14 -0
  7. data/.github/skills/facade-test-conventions/SKILL.md +14 -0
  8. data/.rubocop.yml +5 -0
  9. data/README.md +51 -11
  10. data/UPGRADING.md +141 -0
  11. data/git.gemspec +5 -0
  12. data/lib/git/branch.rb +7 -18
  13. data/lib/git/branches.rb +2 -10
  14. data/lib/git/command_line/base.rb +10 -0
  15. data/lib/git/command_line/capturing.rb +5 -3
  16. data/lib/git/command_line/streaming.rb +5 -3
  17. data/lib/git/command_line.rb +3 -3
  18. data/lib/git/commands/base.rb +7 -6
  19. data/lib/git/commands/cat_file/batch.rb +6 -1
  20. data/lib/git/commands/cat_file/raw.rb +7 -1
  21. data/lib/git/commands/config_option_syntax/get_urlmatch.rb +5 -0
  22. data/lib/git/commands/show_ref/exclude_existing.rb +1 -1
  23. data/lib/git/commands/update_ref/batch.rb +1 -1
  24. data/lib/git/commands/version.rb +5 -0
  25. data/lib/git/commands.rb +5 -7
  26. data/lib/git/config.rb +17 -0
  27. data/lib/git/config_entry_info.rb +106 -0
  28. data/lib/git/configuring.rb +665 -0
  29. data/lib/git/deprecation.rb +9 -0
  30. data/lib/git/diff.rb +4 -8
  31. data/lib/git/diff_path_status.rb +2 -13
  32. data/lib/git/diff_stats.rb +1 -9
  33. data/lib/git/execution_context/global.rb +3 -28
  34. data/lib/git/execution_context/repository.rb +30 -41
  35. data/lib/git/execution_context.rb +43 -24
  36. data/lib/git/log.rb +3 -9
  37. data/lib/git/object.rb +14 -21
  38. data/lib/git/parsers/config_entry.rb +110 -0
  39. data/lib/git/parsers/ls_remote.rb +79 -0
  40. data/lib/git/remote.rb +7 -20
  41. data/lib/git/repository/branching.rb +183 -12
  42. data/lib/git/repository/committing.rb +64 -68
  43. data/lib/git/repository/configuring.rb +208 -13
  44. data/lib/git/repository/context_helpers.rb +264 -0
  45. data/lib/git/repository/factories.rb +682 -0
  46. data/lib/git/repository/inspecting.rb +99 -0
  47. data/lib/git/repository/maintenance.rb +65 -0
  48. data/lib/git/repository/merging.rb +63 -1
  49. data/lib/git/repository/object_operations.rb +133 -35
  50. data/lib/git/repository/path_resolver.rb +1 -1
  51. data/lib/git/repository/remote_operations.rb +166 -21
  52. data/lib/git/repository/staging.rb +187 -23
  53. data/lib/git/repository/stashing.rb +39 -3
  54. data/lib/git/repository/status_operations.rb +21 -0
  55. data/lib/git/repository.rb +68 -129
  56. data/lib/git/stash.rb +2 -9
  57. data/lib/git/stashes.rb +2 -7
  58. data/lib/git/status.rb +8 -17
  59. data/lib/git/version.rb +2 -2
  60. data/lib/git/worktree.rb +2 -15
  61. data/lib/git/worktrees.rb +2 -15
  62. data/lib/git.rb +180 -77
  63. data/redesign/3_architecture_implementation.md +148 -111
  64. data/redesign/Phase 4 - Step A.md +360 -0
  65. data/redesign/beta_release.md +107 -0
  66. data/redesign/c1c2_audit.md +566 -0
  67. data/redesign/c1c2_bucket6_lib_orphans.md +626 -0
  68. data/redesign/config_design.rb +501 -0
  69. metadata +19 -5
  70. data/lib/git/base.rb +0 -1204
  71. data/lib/git/lib.rb +0 -2855
@@ -0,0 +1,665 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'git/config_entry_info'
4
+ require 'git/parsers/config_entry'
5
+ require 'git/commands/config_option_syntax'
6
+
7
+ module Git
8
+ # Mixin that adds structured `git config` read and write operations
9
+ #
10
+ # Include or extend this module to gain the full suite of `config_*` methods.
11
+ # The including/extending class must implement two private methods:
12
+ #
13
+ # - {#execution_context} — returns a `Git::ExecutionContext` used to run commands
14
+ # - {#assert_valid_scope!} — raises `ArgumentError` if a requested scope is not
15
+ # valid in this context (e.g., `:local` is not valid without a repository)
16
+ #
17
+ # Read methods that return {Git::ConfigEntryInfo} objects merge
18
+ # `show_scope: true, show_origin: true, null: true` into the options so that
19
+ # every returned entry carries its full provenance. Two exceptions apply:
20
+ # {#config_get_urlmatch} merges only `show_scope: true, null: true` because
21
+ # git does not support `--show-origin` with `--get-urlmatch` (those entries
22
+ # always have `origin: nil`); {#config_get_colorbool} returns a plain `String`
23
+ # and does not use these output-format options at all.
24
+ #
25
+ # @example Include in a repository class
26
+ # class MyRepo
27
+ # include Git::Configuring
28
+ # private
29
+ # def execution_context = @ctx
30
+ # def assert_valid_scope!(**) = nil # all scopes allowed
31
+ # end
32
+ #
33
+ # @example Extend the Git module for global/system config
34
+ # extend Git::Configuring
35
+ # def self.execution_context = Git::ExecutionContext::Global.new
36
+ # private_class_method :execution_context
37
+ # def self.assert_valid_scope!(**opts)
38
+ # # reject :local, :worktree, :blob when called without a repository
39
+ # end
40
+ # private_class_method :assert_valid_scope!
41
+ #
42
+ # @api public
43
+ #
44
+ module Configuring # rubocop:disable Metrics/ModuleLength
45
+ # @!group Read Operations
46
+
47
+ # @api private
48
+ CONFIG_GET_ALLOWED_OPTS = %i[global system local worktree file f blob includes no_includes type default].freeze
49
+ private_constant :CONFIG_GET_ALLOWED_OPTS
50
+
51
+ # Retrieve a single config entry by key name
52
+ #
53
+ # Wraps `git config --get --show-scope --show-origin --null`.
54
+ #
55
+ # @example Get a single config entry
56
+ # entry = repo.config_get('user.name')
57
+ # entry&.value # => "Alice"
58
+ #
59
+ # @param name [String] the full dotted config key (e.g. `"user.name"`)
60
+ #
61
+ # @param value_regex [String, nil] optional regex to filter by value
62
+ #
63
+ # @param options [Hash] scope and filter options forwarded to the command
64
+ #
65
+ # @option options [Boolean, nil] :global (nil) read from `~/.gitconfig`
66
+ #
67
+ # @option options [Boolean, nil] :system (nil) read from the system config file
68
+ #
69
+ # @option options [Boolean, nil] :local (nil) read from `.git/config`
70
+ #
71
+ # @option options [Boolean, nil] :worktree (nil) read from the worktree config
72
+ #
73
+ # @option options [String, nil] :file (nil) path to a custom config file (alias: `:f`)
74
+ #
75
+ # @option options [String, nil] :blob (nil) read from a git blob object
76
+ #
77
+ # @option options [Boolean, nil] :includes (nil) follow include directives
78
+ #
79
+ # @option options [Boolean, nil] :no_includes (nil) suppress include directives
80
+ #
81
+ # @option options [String, nil] :type (nil) enforce a type constraint on the value
82
+ #
83
+ # @option options [String, nil] :default (nil) value to return when the key is missing
84
+ #
85
+ # @return [Git::ConfigEntryInfo, nil] the matching entry, or `nil` when not found
86
+ #
87
+ # @raise [ArgumentError] if unsupported options are provided
88
+ #
89
+ # @raise [Git::FailedError] if git exits with an unexpected non-zero status
90
+ #
91
+ def config_get(name, value_regex = nil, **options)
92
+ Private.assert_valid_opts!(CONFIG_GET_ALLOWED_OPTS, **options)
93
+ assert_valid_scope!(**options)
94
+ options = options.merge(show_scope: true, show_origin: true, null: true)
95
+ cmd = Git::Commands::ConfigOptionSyntax::Get.new(execution_context)
96
+ output = cmd.call(name, value_regex, **options).stdout
97
+ Git::Parsers::ConfigEntry.parse_get(name, output)
98
+ end
99
+
100
+ # @api private
101
+ CONFIG_GET_ALL_ALLOWED_OPTS = %i[global system local worktree file f blob includes no_includes type].freeze
102
+ private_constant :CONFIG_GET_ALL_ALLOWED_OPTS
103
+
104
+ # Retrieve all values for a multi-valued config key
105
+ #
106
+ # Wraps `git config --get-all --show-scope --show-origin --null`.
107
+ #
108
+ # @example Get all values for a multi-valued key
109
+ # entries = repo.config_get_all('remote.origin.url')
110
+ # entries.map(&:value) # => ["https://...", "git@..."]
111
+ #
112
+ # @param name [String] the full dotted config key
113
+ #
114
+ # @param value_regex [String, nil] optional regex to filter by value
115
+ #
116
+ # @param options [Hash] scope and filter options (see {#config_get})
117
+ #
118
+ # @return [Array<Git::ConfigEntryInfo>] all entries matching the key
119
+ #
120
+ # @raise [ArgumentError] if unsupported options are provided
121
+ #
122
+ # @raise [Git::FailedError] if git exits with an unexpected non-zero status
123
+ #
124
+ def config_get_all(name, value_regex = nil, **options)
125
+ Private.assert_valid_opts!(CONFIG_GET_ALL_ALLOWED_OPTS, **options)
126
+ assert_valid_scope!(**options)
127
+ options = options.merge(show_scope: true, show_origin: true, null: true)
128
+ cmd = Git::Commands::ConfigOptionSyntax::GetAll.new(execution_context)
129
+ output = cmd.call(name, value_regex, **options).stdout
130
+ Git::Parsers::ConfigEntry.parse_get_all(name, output)
131
+ end
132
+
133
+ # @api private
134
+ CONFIG_GET_COLORBOOL_ALLOWED_OPTS = %i[global system local worktree file f blob includes no_includes].freeze
135
+ private_constant :CONFIG_GET_COLORBOOL_ALLOWED_OPTS
136
+
137
+ # @overload config_get_colorbool(name, stdout_is_tty = nil, **options)
138
+ #
139
+ # Query whether color output is enabled for a given config slot
140
+ #
141
+ # Wraps `git config --get-colorbool`.
142
+ #
143
+ # @example Check color status for color.ui
144
+ # repo.config_get_colorbool('color.ui') # => "true"
145
+ #
146
+ # @param name [String] the config key to check (e.g. `"color.ui"`)
147
+ #
148
+ # @param stdout_is_tty [Boolean, nil] whether stdout is a TTY
149
+ #
150
+ # @param options [Hash] scope and filter options
151
+ #
152
+ # @option options [Boolean, nil] :global (nil) read from `~/.gitconfig`
153
+ #
154
+ # @option options [Boolean, nil] :system (nil) read from the system config file
155
+ #
156
+ # @option options [Boolean, nil] :local (nil) read from `.git/config`
157
+ #
158
+ # @option options [Boolean, nil] :worktree (nil) read from the worktree config
159
+ #
160
+ # @option options [String, nil] :file (nil) path to a custom config file (alias: `:f`)
161
+ #
162
+ # @option options [String, nil] :blob (nil) read from a git blob object
163
+ #
164
+ # @option options [Boolean, nil] :includes (nil) follow include directives (`--includes`)
165
+ #
166
+ # @option options [Boolean, nil] :no_includes (nil) suppress include directives (`--no-includes`)
167
+ #
168
+ # @return [String] `"true"` or `"false"`
169
+ #
170
+ # @raise [ArgumentError] if unsupported options are provided
171
+ #
172
+ # @raise [Git::FailedError] if git exits with an unexpected non-zero status
173
+ #
174
+ def config_get_colorbool(name, stdout_is_tty = nil, **)
175
+ Private.assert_valid_opts!(CONFIG_GET_COLORBOOL_ALLOWED_OPTS, **)
176
+ assert_valid_scope!(**)
177
+ cmd = Git::Commands::ConfigOptionSyntax::GetColorBool.new(execution_context)
178
+ cmd.call(name, stdout_is_tty, **).stdout.chomp
179
+ end
180
+
181
+ # @api private
182
+ CONFIG_GET_REGEXP_ALLOWED_OPTS = %i[global system local worktree file f blob includes no_includes type].freeze
183
+ private_constant :CONFIG_GET_REGEXP_ALLOWED_OPTS
184
+
185
+ # Retrieve all config entries whose key matches a regular expression
186
+ #
187
+ # Wraps `git config --get-regexp --show-scope --show-origin --null`.
188
+ #
189
+ # @example Get all remote-related config entries
190
+ # entries = repo.config_get_regexp('remote\\.')
191
+ # entries.map(&:key) # => ["remote.origin.url", ...]
192
+ #
193
+ # @param name_regex [String] regex matched against config key names
194
+ #
195
+ # @param value_regex [String, nil] optional regex to filter by value
196
+ #
197
+ # @param options [Hash] scope and filter options (see {#config_get})
198
+ #
199
+ # @return [Array<Git::ConfigEntryInfo>] all entries whose key matches the regex
200
+ #
201
+ # @raise [ArgumentError] if unsupported options are provided
202
+ #
203
+ # @raise [Git::FailedError] if git exits with an unexpected non-zero status
204
+ #
205
+ def config_get_regexp(name_regex, value_regex = nil, **options)
206
+ Private.assert_valid_opts!(CONFIG_GET_REGEXP_ALLOWED_OPTS, **options)
207
+ assert_valid_scope!(**options)
208
+ options = options.merge(show_scope: true, show_origin: true, null: true)
209
+ cmd = Git::Commands::ConfigOptionSyntax::GetRegexp.new(execution_context)
210
+ output = cmd.call(name_regex, value_regex, **options).stdout
211
+ Git::Parsers::ConfigEntry.parse_list(output)
212
+ end
213
+
214
+ # @api private
215
+ CONFIG_GET_URLMATCH_ALLOWED_OPTS = %i[global system local worktree file f blob includes no_includes type].freeze
216
+ private_constant :CONFIG_GET_URLMATCH_ALLOWED_OPTS
217
+
218
+ # Retrieve config entries whose URL pattern matches a given URL
219
+ #
220
+ # Wraps `git config --get-urlmatch --show-scope --null`.
221
+ #
222
+ # Note: `--show-origin` is not supported by git for `--get-urlmatch`, so
223
+ # the {Git::ConfigEntryInfo} entries returned by this method always have
224
+ # `origin: nil`.
225
+ #
226
+ # @example Get config entries for a specific URL
227
+ # entries = repo.config_get_urlmatch('http', 'https://github.com/user/repo')
228
+ # entries.map(&:key)
229
+ #
230
+ # @param name [String] the config section or key prefix to look up
231
+ #
232
+ # @param url [String] the URL to match against
233
+ #
234
+ # @param options [Hash] scope and filter options (see {#config_get})
235
+ #
236
+ # @return [Array<Git::ConfigEntryInfo>] all entries matching the URL; `origin` is `nil` on each
237
+ #
238
+ # @raise [ArgumentError] if unsupported options are provided
239
+ #
240
+ # @raise [Git::FailedError] if git exits with an unexpected non-zero status
241
+ #
242
+ def config_get_urlmatch(name, url, **options)
243
+ Private.assert_valid_opts!(CONFIG_GET_URLMATCH_ALLOWED_OPTS, **options)
244
+ assert_valid_scope!(**options)
245
+ options = options.merge(show_scope: true, null: true)
246
+ cmd = Git::Commands::ConfigOptionSyntax::GetUrlmatch.new(execution_context)
247
+ output = cmd.call(name, url, **options).stdout
248
+ Git::Parsers::ConfigEntry.parse_urlmatch(output)
249
+ end
250
+
251
+ # @api private
252
+ CONFIG_LIST_ALLOWED_OPTS = %i[global system local worktree file f blob includes no_includes type].freeze
253
+ private_constant :CONFIG_LIST_ALLOWED_OPTS
254
+
255
+ # List all visible config entries
256
+ #
257
+ # Wraps `git config --list --show-scope --show-origin --null`.
258
+ #
259
+ # @example List all config entries
260
+ # entries = repo.config_list
261
+ # entries.first.scope # => "local"
262
+ #
263
+ # @param options [Hash] scope and filter options (see {#config_get})
264
+ #
265
+ # @return [Array<Git::ConfigEntryInfo>] all visible config entries
266
+ #
267
+ # @raise [ArgumentError] if unsupported options are provided
268
+ #
269
+ # @raise [Git::FailedError] if git exits with an unexpected non-zero status
270
+ #
271
+ def config_list(**options)
272
+ Private.assert_valid_opts!(CONFIG_LIST_ALLOWED_OPTS, **options)
273
+ assert_valid_scope!(**options)
274
+ options = options.merge(show_scope: true, show_origin: true, null: true)
275
+ cmd = Git::Commands::ConfigOptionSyntax::List.new(execution_context)
276
+ output = cmd.call(**options).stdout
277
+ Git::Parsers::ConfigEntry.parse_list(output)
278
+ end
279
+
280
+ # @!endgroup
281
+
282
+ # @!group Write Operations
283
+
284
+ # @api private
285
+ CONFIG_ADD_ALLOWED_OPTS = %i[global system local worktree file f blob type].freeze
286
+ private_constant :CONFIG_ADD_ALLOWED_OPTS
287
+
288
+ # @overload config_add(name, value, **options)
289
+ #
290
+ # Append a value to a multi-valued config key
291
+ #
292
+ # Wraps `git config --add`.
293
+ #
294
+ # @example Append a URL to a multi-valued remote key
295
+ # repo.config_add('remote.origin.url', 'git@github.com:user/repo.git')
296
+ #
297
+ # @param name [String] the full dotted config key
298
+ #
299
+ # @param value [String] the value to append
300
+ #
301
+ # @param options [Hash] scope options
302
+ #
303
+ # @option options [Boolean, nil] :global (nil) write to `~/.gitconfig`
304
+ #
305
+ # @option options [Boolean, nil] :system (nil) write to the system config file
306
+ #
307
+ # @option options [Boolean, nil] :local (nil) write to `.git/config`
308
+ #
309
+ # @option options [Boolean, nil] :worktree (nil) write to the worktree config
310
+ #
311
+ # @option options [String, nil] :file (nil) path to a custom config file (alias: `:f`)
312
+ #
313
+ # @option options [String, nil] :blob (nil) write to a git blob object
314
+ #
315
+ # @option options [String, nil] :type (nil) coerce the value to the given type (e.g. `"bool"`, `"int"`)
316
+ #
317
+ # @return [nil]
318
+ #
319
+ # @raise [ArgumentError] if unsupported options are provided
320
+ #
321
+ # @raise [Git::FailedError] if git exits with a non-zero exit status
322
+ #
323
+ def config_add(name, value, **)
324
+ Private.assert_valid_opts!(CONFIG_ADD_ALLOWED_OPTS, **)
325
+ assert_valid_scope!(**)
326
+ cmd = Git::Commands::ConfigOptionSyntax::Add.new(execution_context)
327
+ cmd.call(name, value, **)
328
+ nil
329
+ end
330
+
331
+ # @api private
332
+ CONFIG_REMOVE_SECTION_ALLOWED_OPTS = %i[global system local worktree file f blob].freeze
333
+ private_constant :CONFIG_REMOVE_SECTION_ALLOWED_OPTS
334
+
335
+ # @overload config_remove_section(name, **options)
336
+ #
337
+ # Remove an entire config section
338
+ #
339
+ # Wraps `git config --remove-section`.
340
+ #
341
+ # @example Remove the origin remote section
342
+ # repo.config_remove_section('remote.origin')
343
+ #
344
+ # @param name [String] the section name to remove (e.g. `"remote.origin"`)
345
+ #
346
+ # @param options [Hash] scope options
347
+ #
348
+ # @option options [Boolean, nil] :global (nil) remove from `~/.gitconfig`
349
+ #
350
+ # @option options [Boolean, nil] :system (nil) remove from the system config file
351
+ #
352
+ # @option options [Boolean, nil] :local (nil) remove from `.git/config`
353
+ #
354
+ # @option options [Boolean, nil] :worktree (nil) remove from the worktree config
355
+ #
356
+ # @option options [String, nil] :file (nil) path to a custom config file (alias: `:f`)
357
+ #
358
+ # @option options [String, nil] :blob (nil) remove from a git blob object
359
+ #
360
+ # @return [nil]
361
+ #
362
+ # @raise [ArgumentError] if unsupported options are provided
363
+ #
364
+ # @raise [Git::FailedError] if git exits with a non-zero exit status
365
+ #
366
+ def config_remove_section(name, **)
367
+ Private.assert_valid_opts!(CONFIG_REMOVE_SECTION_ALLOWED_OPTS, **)
368
+ assert_valid_scope!(**)
369
+ cmd = Git::Commands::ConfigOptionSyntax::RemoveSection.new(execution_context)
370
+ cmd.call(name, **)
371
+ nil
372
+ end
373
+
374
+ # @api private
375
+ CONFIG_RENAME_SECTION_ALLOWED_OPTS = %i[global system local worktree file f blob].freeze
376
+ private_constant :CONFIG_RENAME_SECTION_ALLOWED_OPTS
377
+
378
+ # @overload config_rename_section(old_name, new_name, **options)
379
+ #
380
+ # Rename a config section
381
+ #
382
+ # Wraps `git config --rename-section`.
383
+ #
384
+ # @example Rename a remote section
385
+ # repo.config_rename_section('remote.old', 'remote.new')
386
+ #
387
+ # @param old_name [String] the current section name
388
+ #
389
+ # @param new_name [String] the new section name
390
+ #
391
+ # @param options [Hash] scope options
392
+ #
393
+ # @option options [Boolean, nil] :global (nil) rename in `~/.gitconfig`
394
+ #
395
+ # @option options [Boolean, nil] :system (nil) rename in the system config file
396
+ #
397
+ # @option options [Boolean, nil] :local (nil) rename in `.git/config`
398
+ #
399
+ # @option options [Boolean, nil] :worktree (nil) rename in the worktree config
400
+ #
401
+ # @option options [String, nil] :file (nil) path to a custom config file (alias: `:f`)
402
+ #
403
+ # @option options [String, nil] :blob (nil) rename in a git blob object
404
+ #
405
+ # @return [nil]
406
+ #
407
+ # @raise [ArgumentError] if unsupported options are provided
408
+ #
409
+ # @raise [Git::FailedError] if git exits with a non-zero exit status
410
+ #
411
+ def config_rename_section(old_name, new_name, **)
412
+ Private.assert_valid_opts!(CONFIG_RENAME_SECTION_ALLOWED_OPTS, **)
413
+ assert_valid_scope!(**)
414
+ cmd = Git::Commands::ConfigOptionSyntax::RenameSection.new(execution_context)
415
+ cmd.call(old_name, new_name, **)
416
+ nil
417
+ end
418
+
419
+ # @api private
420
+ CONFIG_REPLACE_ALL_ALLOWED_OPTS = %i[global system local worktree file f blob type].freeze
421
+ private_constant :CONFIG_REPLACE_ALL_ALLOWED_OPTS
422
+
423
+ # @overload config_replace_all(name, value, value_regex = nil, **options)
424
+ #
425
+ # Replace all values matching a key (and optional value regex)
426
+ #
427
+ # Wraps `git config --replace-all`.
428
+ #
429
+ # @example Replace all values for a key
430
+ # repo.config_replace_all('remote.origin.url', 'https://github.com/user/repo')
431
+ #
432
+ # @param name [String] the full dotted config key
433
+ #
434
+ # @param value [String] the new value
435
+ #
436
+ # @param value_regex [String, nil] optional regex; only matching values are replaced
437
+ #
438
+ # @param options [Hash] scope options
439
+ #
440
+ # @option options [Boolean, nil] :global (nil) write to `~/.gitconfig`
441
+ #
442
+ # @option options [Boolean, nil] :system (nil) write to the system config file
443
+ #
444
+ # @option options [Boolean, nil] :local (nil) write to `.git/config`
445
+ #
446
+ # @option options [Boolean, nil] :worktree (nil) write to the worktree config
447
+ #
448
+ # @option options [String, nil] :file (nil) path to a custom config file (alias: `:f`)
449
+ #
450
+ # @option options [String, nil] :blob (nil) write to a git blob object
451
+ #
452
+ # @option options [String, nil] :type (nil) coerce the value to the given type (e.g. `"bool"`, `"int"`)
453
+ #
454
+ # @return [nil]
455
+ #
456
+ # @raise [ArgumentError] if unsupported options are provided
457
+ #
458
+ # @raise [Git::FailedError] if git exits with a non-zero exit status
459
+ #
460
+ def config_replace_all(name, value, value_regex = nil, **)
461
+ Private.assert_valid_opts!(CONFIG_REPLACE_ALL_ALLOWED_OPTS, **)
462
+ assert_valid_scope!(**)
463
+ cmd = Git::Commands::ConfigOptionSyntax::ReplaceAll.new(execution_context)
464
+ cmd.call(name, value, value_regex, **)
465
+ nil
466
+ end
467
+
468
+ # @api private
469
+ CONFIG_SET_ALLOWED_OPTS = %i[global system local worktree file f blob type].freeze
470
+ private_constant :CONFIG_SET_ALLOWED_OPTS
471
+
472
+ # @overload config_set(name, value, **options)
473
+ #
474
+ # Set a config entry to a new value
475
+ #
476
+ # Wraps the implicit set mode of `git config`.
477
+ #
478
+ # @example Set the user name in local config
479
+ # repo.config_set('user.name', 'Alice')
480
+ #
481
+ # @param name [String] the full dotted config key
482
+ #
483
+ # @param value [String] the value to set
484
+ #
485
+ # @param options [Hash] scope options
486
+ #
487
+ # @option options [Boolean, nil] :global (nil) write to `~/.gitconfig`
488
+ #
489
+ # @option options [Boolean, nil] :system (nil) write to the system config file
490
+ #
491
+ # @option options [Boolean, nil] :local (nil) write to `.git/config`
492
+ #
493
+ # @option options [Boolean, nil] :worktree (nil) write to the worktree config
494
+ #
495
+ # @option options [String, nil] :file (nil) path to a custom config file (alias: `:f`)
496
+ #
497
+ # @option options [String, nil] :blob (nil) write to a git blob object
498
+ #
499
+ # @option options [String, nil] :type (nil) coerce the value to the given type (e.g. `"bool"`, `"int"`)
500
+ #
501
+ # @return [nil]
502
+ #
503
+ # @raise [ArgumentError] if unsupported options are provided
504
+ #
505
+ # @raise [Git::FailedError] if git exits with a non-zero exit status
506
+ #
507
+ def config_set(name, value, **)
508
+ Private.assert_valid_opts!(CONFIG_SET_ALLOWED_OPTS, **)
509
+ assert_valid_scope!(**)
510
+ cmd = Git::Commands::ConfigOptionSyntax::Set.new(execution_context)
511
+ cmd.call(name, value, **)
512
+ nil
513
+ end
514
+
515
+ # @api private
516
+ CONFIG_UNSET_ALLOWED_OPTS = %i[global system local worktree file f blob].freeze
517
+ private_constant :CONFIG_UNSET_ALLOWED_OPTS
518
+
519
+ # @overload config_unset(name, value_regex = nil, **options)
520
+ #
521
+ # Remove a config entry
522
+ #
523
+ # Wraps `git config --unset`.
524
+ #
525
+ # @example Remove a config entry
526
+ # repo.config_unset('user.name')
527
+ #
528
+ # @param name [String] the full dotted config key
529
+ #
530
+ # @param value_regex [String, nil] optional regex; only the matching value is removed
531
+ #
532
+ # @param options [Hash] scope options
533
+ #
534
+ # @option options [Boolean, nil] :global (nil) remove from `~/.gitconfig`
535
+ #
536
+ # @option options [Boolean, nil] :system (nil) remove from the system config file
537
+ #
538
+ # @option options [Boolean, nil] :local (nil) remove from `.git/config`
539
+ #
540
+ # @option options [Boolean, nil] :worktree (nil) remove from the worktree config
541
+ #
542
+ # @option options [String, nil] :file (nil) path to a custom config file (alias: `:f`)
543
+ #
544
+ # @option options [String, nil] :blob (nil) remove from a git blob object
545
+ #
546
+ # @return [nil]
547
+ #
548
+ # @raise [ArgumentError] if unsupported options are provided
549
+ #
550
+ # @raise [Git::FailedError] if git exits with a non-zero exit status
551
+ #
552
+ def config_unset(name, value_regex = nil, **)
553
+ Private.assert_valid_opts!(CONFIG_UNSET_ALLOWED_OPTS, **)
554
+ assert_valid_scope!(**)
555
+ cmd = Git::Commands::ConfigOptionSyntax::Unset.new(execution_context)
556
+ cmd.call(name, value_regex, **)
557
+ nil
558
+ end
559
+
560
+ # @api private
561
+ CONFIG_UNSET_ALL_ALLOWED_OPTS = %i[global system local worktree file f blob].freeze
562
+ private_constant :CONFIG_UNSET_ALL_ALLOWED_OPTS
563
+
564
+ # @overload config_unset_all(name, value_regex = nil, **options)
565
+ #
566
+ # Remove all config entries for a key
567
+ #
568
+ # Wraps `git config --unset-all`.
569
+ #
570
+ # @example Remove all values for a multi-valued key
571
+ # repo.config_unset_all('remote.origin.url')
572
+ #
573
+ # @param name [String] the full dotted config key
574
+ #
575
+ # @param value_regex [String, nil] optional regex; only matching values are removed
576
+ #
577
+ # @param options [Hash] scope options
578
+ #
579
+ # @option options [Boolean, nil] :global (nil) remove from `~/.gitconfig`
580
+ #
581
+ # @option options [Boolean, nil] :system (nil) remove from the system config file
582
+ #
583
+ # @option options [Boolean, nil] :local (nil) remove from `.git/config`
584
+ #
585
+ # @option options [Boolean, nil] :worktree (nil) remove from the worktree config
586
+ #
587
+ # @option options [String, nil] :file (nil) path to a custom config file (alias: `:f`)
588
+ #
589
+ # @option options [String, nil] :blob (nil) remove from a git blob object
590
+ #
591
+ # @return [nil]
592
+ #
593
+ # @raise [ArgumentError] if unsupported options are provided
594
+ #
595
+ # @raise [Git::FailedError] if git exits with a non-zero exit status
596
+ #
597
+ def config_unset_all(name, value_regex = nil, **)
598
+ Private.assert_valid_opts!(CONFIG_UNSET_ALL_ALLOWED_OPTS, **)
599
+ assert_valid_scope!(**)
600
+ cmd = Git::Commands::ConfigOptionSyntax::UnsetAll.new(execution_context)
601
+ cmd.call(name, value_regex, **)
602
+ nil
603
+ end
604
+
605
+ # @!endgroup
606
+
607
+ private
608
+
609
+ # @abstract
610
+ #
611
+ # Returns the execution context used to run git commands
612
+ #
613
+ # @return [Git::ExecutionContext]
614
+ #
615
+ def execution_context
616
+ raise NotImplementedError
617
+ end
618
+
619
+ # @abstract
620
+ #
621
+ # @overload assert_valid_scope!(**options)
622
+ #
623
+ # Validates that the requested scope options are appropriate for this context
624
+ #
625
+ # Called before every config operation. Raise `ArgumentError` when a scope
626
+ # (e.g. `:local`) is not permitted without a repository.
627
+ #
628
+ # @param options [Hash] scope options forwarded from the calling config method
629
+ #
630
+ # @raise [ArgumentError] if the scope is not permitted in this context
631
+ #
632
+ # @return [void]
633
+ #
634
+ def assert_valid_scope!(**)
635
+ raise NotImplementedError
636
+ end
637
+
638
+ # Internal helpers for {Git::Configuring}.
639
+ #
640
+ # @api private
641
+ #
642
+ module Private
643
+ module_function
644
+
645
+ # Validate that `options` contains only keys listed in `allowed`
646
+ #
647
+ # @param allowed [Array<Symbol>] the permitted option keys
648
+ #
649
+ # @param options [Hash] the options hash provided by the caller
650
+ #
651
+ # @return [void]
652
+ #
653
+ # @raise [ArgumentError] when `options` contains any key not in `allowed`
654
+ #
655
+ def assert_valid_opts!(allowed, **options)
656
+ unknown = options.keys - allowed
657
+ return if unknown.empty?
658
+
659
+ raise ArgumentError, "Unknown options: #{unknown.join(', ')}"
660
+ end
661
+ end
662
+
663
+ private_constant :Private
664
+ end
665
+ end
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'active_support/deprecation'
4
+
5
+ # The Git module provides a Ruby interface to Git version control.
6
+ module Git
7
+ # @api private
8
+ Deprecation = ActiveSupport::Deprecation.new('5.0.0', 'Git')
9
+ end