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.
- checksums.yaml +4 -4
- data/.github/copilot-instructions.md +6 -0
- data/.github/prompts/iteratively-address-copilot-reviews.prompt.md +188 -0
- data/.github/skills/extract-facade-from-base-lib/KEYWORD_ARG_REMEDIATION.md +22 -0
- data/.github/skills/extract-facade-from-base-lib/SKILL.md +28 -14
- data/.github/skills/facade-implementation/SKILL.md +14 -0
- data/.github/skills/facade-test-conventions/SKILL.md +14 -0
- data/.rubocop.yml +5 -0
- data/README.md +51 -11
- data/UPGRADING.md +141 -0
- data/git.gemspec +5 -0
- data/lib/git/branch.rb +7 -18
- data/lib/git/branches.rb +2 -10
- data/lib/git/command_line/base.rb +10 -0
- data/lib/git/command_line/capturing.rb +5 -3
- data/lib/git/command_line/streaming.rb +5 -3
- data/lib/git/command_line.rb +3 -3
- data/lib/git/commands/base.rb +7 -6
- data/lib/git/commands/cat_file/batch.rb +6 -1
- data/lib/git/commands/cat_file/raw.rb +7 -1
- data/lib/git/commands/config_option_syntax/get_urlmatch.rb +5 -0
- data/lib/git/commands/show_ref/exclude_existing.rb +1 -1
- data/lib/git/commands/update_ref/batch.rb +1 -1
- data/lib/git/commands/version.rb +5 -0
- data/lib/git/commands.rb +5 -7
- data/lib/git/config.rb +17 -0
- data/lib/git/config_entry_info.rb +106 -0
- data/lib/git/configuring.rb +665 -0
- data/lib/git/deprecation.rb +9 -0
- data/lib/git/diff.rb +4 -8
- data/lib/git/diff_path_status.rb +2 -13
- data/lib/git/diff_stats.rb +1 -9
- data/lib/git/execution_context/global.rb +3 -28
- data/lib/git/execution_context/repository.rb +30 -41
- data/lib/git/execution_context.rb +43 -24
- data/lib/git/log.rb +3 -9
- data/lib/git/object.rb +14 -21
- data/lib/git/parsers/config_entry.rb +110 -0
- data/lib/git/parsers/ls_remote.rb +79 -0
- data/lib/git/remote.rb +7 -20
- data/lib/git/repository/branching.rb +183 -12
- data/lib/git/repository/committing.rb +64 -68
- data/lib/git/repository/configuring.rb +208 -13
- data/lib/git/repository/context_helpers.rb +264 -0
- data/lib/git/repository/factories.rb +682 -0
- data/lib/git/repository/inspecting.rb +99 -0
- data/lib/git/repository/maintenance.rb +65 -0
- data/lib/git/repository/merging.rb +63 -1
- data/lib/git/repository/object_operations.rb +133 -35
- data/lib/git/repository/path_resolver.rb +1 -1
- data/lib/git/repository/remote_operations.rb +166 -21
- data/lib/git/repository/staging.rb +187 -23
- data/lib/git/repository/stashing.rb +39 -3
- data/lib/git/repository/status_operations.rb +21 -0
- data/lib/git/repository.rb +68 -129
- data/lib/git/stash.rb +2 -9
- data/lib/git/stashes.rb +2 -7
- data/lib/git/status.rb +8 -17
- data/lib/git/version.rb +2 -2
- data/lib/git/worktree.rb +2 -15
- data/lib/git/worktrees.rb +2 -15
- data/lib/git.rb +180 -77
- data/redesign/3_architecture_implementation.md +148 -111
- data/redesign/Phase 4 - Step A.md +360 -0
- data/redesign/beta_release.md +107 -0
- data/redesign/c1c2_audit.md +566 -0
- data/redesign/c1c2_bucket6_lib_orphans.md +626 -0
- data/redesign/config_design.rb +501 -0
- metadata +19 -5
- data/lib/git/base.rb +0 -1204
- data/lib/git/lib.rb +0 -2855
|
@@ -0,0 +1,626 @@
|
|
|
1
|
+
# Bucket 6 Decision Brief — `Git::Lib` Orphaned Public Methods
|
|
2
|
+
|
|
3
|
+
**Date:** 2026-06-09
|
|
4
|
+
|
|
5
|
+
**Branch:** `agents/redesignc1c2bucket6decisionbrief`
|
|
6
|
+
|
|
7
|
+
**Companion to:** `redesign/c1c2_audit.md` §7.3
|
|
8
|
+
|
|
9
|
+
- [1. Purpose](#1-purpose)
|
|
10
|
+
- [2. The ❌ Confirmed Removal](#2-the--confirmed-removal)
|
|
11
|
+
- [`list_files(ref_dir)`](#list_filesref_dir)
|
|
12
|
+
- [3. Decision Items](#3-decision-items)
|
|
13
|
+
- [3.1 `change_head_branch(branch_name)`](#31-change_head_branchbranch_name)
|
|
14
|
+
- [3.2 `config_get(name)`, `config_list`, and `config_set(name, value, options = {})`](#32-config_getname-config_list-and-config_setname-value-options--)
|
|
15
|
+
- [`config_get(name)`](#config_getname)
|
|
16
|
+
- [`config_list`](#config_list)
|
|
17
|
+
- [`config_set(name, value, options = {})`](#config_setname-value-options--)
|
|
18
|
+
- [3.3 `global_config_get(name)`, `global_config_list`, and `global_config_set(name, value)`](#33-global_config_getname-global_config_list-and-global_config_setname-value)
|
|
19
|
+
- [`global_config_get(name)`](#global_config_getname)
|
|
20
|
+
- [`global_config_list`](#global_config_list)
|
|
21
|
+
- [`global_config_set(name, value)`](#global_config_setname-value)
|
|
22
|
+
- [3.4 `parse_config(file)`](#34-parse_configfile)
|
|
23
|
+
- [3.5 `stash_list`](#35-stash_list)
|
|
24
|
+
- [3.6 `unmerged`](#36-unmerged)
|
|
25
|
+
- [4. Pre-resolved questions](#4-pre-resolved-questions)
|
|
26
|
+
- [4.1 Global config API surface breadth (§3.3) — **Resolved**](#41-global-config-api-surface-breadth-33--resolved)
|
|
27
|
+
- [4.2 `change_head_branch` guard semantics (§3.1) — **Resolved**](#42-change_head_branch-guard-semantics-31--resolved)
|
|
28
|
+
- [4.3 `config()` overload complexity with `:file` for reads (§3.4) — **Resolved**](#43-config-overload-complexity-with-file-for-reads-34--resolved)
|
|
29
|
+
- [4.4 Semver classification of `stash_list` non-promotion (§3.5) — **Resolved**](#44-semver-classification-of-stash_list-non-promotion-35--resolved)
|
|
30
|
+
- [5. Next Steps](#5-next-steps)
|
|
31
|
+
|
|
32
|
+
---
|
|
33
|
+
|
|
34
|
+
## 1. Purpose
|
|
35
|
+
|
|
36
|
+
This document is the companion decision brief for the 10 🔍 human-decision
|
|
37
|
+
items and 1 ❌ confirmed removal identified in §7.3 of
|
|
38
|
+
`redesign/c1c2_audit.md`. Each of those items is a public method on
|
|
39
|
+
`Git::Lib` that has no existing `Git::Repository` facade and therefore has no
|
|
40
|
+
safe landing place when `Git::Lib` is deleted in Phase 4. Before the final
|
|
41
|
+
Bucket 6 remediation work (PR 5h+) can begin, a human reviewer must examine
|
|
42
|
+
each item, evaluate the concrete options provided below, and record a binding
|
|
43
|
+
decision in the **Decision** field of the relevant section. Once all 11 items
|
|
44
|
+
are resolved, this document serves as the authoritative specification for
|
|
45
|
+
whoever opens PR 5h.
|
|
46
|
+
|
|
47
|
+
---
|
|
48
|
+
|
|
49
|
+
## 2. The ❌ Confirmed Removal
|
|
50
|
+
|
|
51
|
+
### `list_files(ref_dir)`
|
|
52
|
+
|
|
53
|
+
**Current implementation:** Constructs the path
|
|
54
|
+
`File.join(@git_dir, 'refs', ref_dir)` and returns
|
|
55
|
+
`Dir.glob('**/*', base: dir).select { |f| File.file?(...) }` — a raw
|
|
56
|
+
filesystem walk over the `.git/refs/` directory tree. No git command is
|
|
57
|
+
invoked; this is pure internal plumbing that bypasses the git object model
|
|
58
|
+
entirely.
|
|
59
|
+
|
|
60
|
+
**Why it must go:** There is no plausible clean public use for directly
|
|
61
|
+
walking `.git/refs/`. Callers who need to enumerate refs should use the
|
|
62
|
+
`git for-each-ref` family of commands, which is already surfaced via
|
|
63
|
+
`Git::Repository::Branching`, `Git::Repository::ObjectOperations`, and the
|
|
64
|
+
various ref-inspection methods on `Git::Repository`. Exposing filesystem
|
|
65
|
+
access to the packed-refs store would be misleading and would silently break
|
|
66
|
+
on repositories that use the packed-refs file instead of loose ref files.
|
|
67
|
+
|
|
68
|
+
**Upgrade note for callers:**
|
|
69
|
+
|
|
70
|
+
> Direct `repo.lib.list_files` calls are unsupported. Use
|
|
71
|
+
> `Git::Repository` ref-inspection methods (e.g., `branches`, `tags`,
|
|
72
|
+
> `remotes`) instead.
|
|
73
|
+
|
|
74
|
+
**Action required:** Add this note to `UPGRADING.md`. No migration
|
|
75
|
+
path is provided.
|
|
76
|
+
|
|
77
|
+
---
|
|
78
|
+
|
|
79
|
+
## 3. Decision Items
|
|
80
|
+
|
|
81
|
+
### 3.1 `change_head_branch(branch_name)`
|
|
82
|
+
|
|
83
|
+
**Signature:** `change_head_branch(branch_name)`
|
|
84
|
+
|
|
85
|
+
**Description:** Calls
|
|
86
|
+
`Git::Commands::SymbolicRef::Update.new(self).call('HEAD', "refs/heads/#{branch_name}")`,
|
|
87
|
+
which is a direct write of the `HEAD` symbolic reference to
|
|
88
|
+
`refs/heads/<branch_name>`. This is equivalent to
|
|
89
|
+
`git symbolic-ref HEAD refs/heads/<name>` and is how git implements branch
|
|
90
|
+
renaming and orphan-branch checkout under the hood. `Git::Lib` uses it
|
|
91
|
+
internally in at least two workflows. There is no `Git::Base` delegator.
|
|
92
|
+
|
|
93
|
+
**Context:** No existing `Git::Repository` facade covers this operation.
|
|
94
|
+
The `Git::Commands::SymbolicRef::Update` command class already exists and is
|
|
95
|
+
wired correctly. The operation is low-level and requires caller awareness: pointing HEAD at a
|
|
96
|
+
branch that does not yet exist places the repository in unborn-branch state,
|
|
97
|
+
which is valid and intentional for initialization workflows but unexpected if
|
|
98
|
+
done by mistake. It is exactly what external tooling (e.g., repository
|
|
99
|
+
initialization scripts, branch-rename utilities) needs when git's own
|
|
100
|
+
higher-level commands are not available. No test-suite usage was found for
|
|
101
|
+
direct `lib.change_head_branch` calls, which suggests no external callers
|
|
102
|
+
have relied on it via `g.lib`.
|
|
103
|
+
|
|
104
|
+
**Options:**
|
|
105
|
+
|
|
106
|
+
- **A — Promote to `Git::Repository::Branching`:** Add a public facade
|
|
107
|
+
method `change_head_branch(branch_name)` with appropriate YARD docs and a
|
|
108
|
+
`Git::Base` delegator. Surface the footgun risk in the documentation.
|
|
109
|
+
- **B — Mark as internal:** Leave the method behind `private` in `Git::Lib`
|
|
110
|
+
and document that it has no public equivalent. Callers requiring this
|
|
111
|
+
low-level operation must use `git symbolic-ref` directly.
|
|
112
|
+
- **C — Promote with a guard:** Promote as Option A but add a guard that
|
|
113
|
+
raises `ArgumentError` when the target branch does not exist locally.
|
|
114
|
+
|
|
115
|
+
**Recommendation:** **Option A.** The operation has genuine tooling utility
|
|
116
|
+
and the command class is already in place, making the implementation cost
|
|
117
|
+
low. No guard should be added: the unborn-branch workflow — pointing HEAD
|
|
118
|
+
at a branch that does not yet exist before any commits land — is a
|
|
119
|
+
first-class git pattern used by repository initialization tooling. A guard
|
|
120
|
+
that rejects non-existent branches would silently break that workflow (see
|
|
121
|
+
§4.2). Promote to `Git::Repository::Branching` as a clean pass-through with
|
|
122
|
+
clear documentation explaining unborn-branch semantics.
|
|
123
|
+
|
|
124
|
+
**Decision:** Accepted. Promote `change_head_branch(branch_name)` to `Git::Repository::Branching` with a `Git::Base` delegator. No guard. Document unborn-branch semantics in YARD.
|
|
125
|
+
|
|
126
|
+
**Status:** ✅ Implemented — facade in `Git::Repository::Branching` + `Git::Base` delegator added (PR 5h-1).
|
|
127
|
+
|
|
128
|
+
---
|
|
129
|
+
|
|
130
|
+
### 3.2 `config_get(name)`, `config_list`, and `config_set(name, value, options = {})`
|
|
131
|
+
|
|
132
|
+
These three are treated together because they are the low-level primitives
|
|
133
|
+
that the existing `Git::Repository::Configuring#config()` facade already
|
|
134
|
+
dispatches to internally. The key question is whether to expose them as
|
|
135
|
+
additional public methods or to clarify that `config()` already covers all
|
|
136
|
+
three cases.
|
|
137
|
+
|
|
138
|
+
#### `config_get(name)`
|
|
139
|
+
|
|
140
|
+
**Signature:** `config_get(name)`
|
|
141
|
+
|
|
142
|
+
**Description:** Calls `Git::Commands::ConfigOptionSyntax::Get`, raises
|
|
143
|
+
`Git::FailedError` if the exit status is non-zero, and returns the raw
|
|
144
|
+
stdout string (the config value, newline-stripped).
|
|
145
|
+
|
|
146
|
+
**Context:** `Git::Repository::Configuring#config(name)` (one-argument form)
|
|
147
|
+
already calls the identical private helper `Private.config_get` with
|
|
148
|
+
identical behavior. The two implementations are functionally the same.
|
|
149
|
+
There is no `Git::Base` delegator.
|
|
150
|
+
|
|
151
|
+
#### `config_list`
|
|
152
|
+
|
|
153
|
+
**Signature:** `config_list`
|
|
154
|
+
|
|
155
|
+
**Description:** Calls `Git::Commands::ConfigOptionSyntax::List` and parses
|
|
156
|
+
the output into a `Hash{String => String}` via `parse_config_list`.
|
|
157
|
+
|
|
158
|
+
**Context:** `Git::Repository::Configuring#config()` (zero-argument form)
|
|
159
|
+
already calls the identical private helper `Private.config_list` and returns
|
|
160
|
+
the same hash. Functionally identical.
|
|
161
|
+
|
|
162
|
+
#### `config_set(name, value, options = {})`
|
|
163
|
+
|
|
164
|
+
**Signature:** `config_set(name, value, options = {})`
|
|
165
|
+
|
|
166
|
+
**Description:** Validates options against `CONFIG_SET_ALLOWED_OPTS =
|
|
167
|
+
%i[file]` and calls `Git::Commands::ConfigOptionSyntax::Set`. The `:file`
|
|
168
|
+
option allows writing to an arbitrary config file rather than the default
|
|
169
|
+
`.git/config`. The facade `config(name, value, options = {})` also accepts
|
|
170
|
+
a `:file` option and calls the same `Set` command.
|
|
171
|
+
|
|
172
|
+
**Context:** `Git::Repository::Configuring#config(name, value)` (two-argument
|
|
173
|
+
form) already calls the identical private helper `Private.config_set` with
|
|
174
|
+
the same `:file` passthrough. Functionally identical.
|
|
175
|
+
|
|
176
|
+
**Options for all three:**
|
|
177
|
+
|
|
178
|
+
- **A — Mark as covered; no new public methods:** Document in the upgrade
|
|
179
|
+
guide that `config_get`, `config_list`, and `config_set` are now accessed
|
|
180
|
+
via the unified `config()` facade, and that callers should migrate to
|
|
181
|
+
`repo.config`, `repo.config(name)`, and `repo.config(name, value)`
|
|
182
|
+
respectively.
|
|
183
|
+
- **B — Promote all three as public aliases:** Add `config_get`, `config_list`,
|
|
184
|
+
and `config_set` as public methods on `Git::Repository::Configuring` that
|
|
185
|
+
simply delegate to the private helpers, giving callers a named-method API.
|
|
186
|
+
- **C — Partial promotion:** Promote only `config_set` (since it takes extra
|
|
187
|
+
options that users may wish to call explicitly) while documenting that
|
|
188
|
+
`config_get` and `config_list` are covered by the overloaded `config()`.
|
|
189
|
+
|
|
190
|
+
**Recommendation:** **Deprecated forwarding aliases over `config()`, with Option A as the canonical v5 API.**
|
|
191
|
+
The `config()` facade is already a clean, well-documented, three-overload API
|
|
192
|
+
that covers every case. Adding parallel methods with the same behavior would
|
|
193
|
+
create API surface duplication without benefit in the long term. However, to
|
|
194
|
+
preserve backward compatibility, also add `config_get`, `config_list`, and
|
|
195
|
+
`config_set` as deprecated forwarding methods on `Git::Repository::Configuring`
|
|
196
|
+
(and corresponding `Git::Base` delegators), each emitting a runtime warning
|
|
197
|
+
via `Git::Deprecation.warn`:
|
|
198
|
+
|
|
199
|
+
```ruby
|
|
200
|
+
# @deprecated Use {#config} instead.
|
|
201
|
+
def config_get(name)
|
|
202
|
+
Git::Deprecation.warn(
|
|
203
|
+
'config_get is deprecated and will be removed in a future version. ' \
|
|
204
|
+
'Use config(name) instead.'
|
|
205
|
+
)
|
|
206
|
+
config(name)
|
|
207
|
+
end
|
|
208
|
+
|
|
209
|
+
# @deprecated Use {#config} instead.
|
|
210
|
+
def config_list
|
|
211
|
+
Git::Deprecation.warn(
|
|
212
|
+
'config_list is deprecated and will be removed in a future version. ' \
|
|
213
|
+
'Use config instead.'
|
|
214
|
+
)
|
|
215
|
+
config
|
|
216
|
+
end
|
|
217
|
+
|
|
218
|
+
# @deprecated Use {#config} instead.
|
|
219
|
+
def config_set(name, value, opts = {})
|
|
220
|
+
Git::Deprecation.warn(
|
|
221
|
+
'config_set is deprecated and will be removed in a future version. ' \
|
|
222
|
+
'Use config(name, value) instead.'
|
|
223
|
+
)
|
|
224
|
+
config(name, value, opts)
|
|
225
|
+
end
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
Callers drop the `.lib.` prefix and get a runtime deprecation warning; no
|
|
229
|
+
method-name change is required until v6. Remove all three aliases in v6.
|
|
230
|
+
|
|
231
|
+
**Decision:** Accepted. Add deprecated forwarding methods `config_get`, `config_list`, and `config_set` to `Git::Repository::Configuring` with `Git::Deprecation.warn` calls pointing to `config()`. Add `Git::Base` delegators. Remove in v6.
|
|
232
|
+
|
|
233
|
+
**Status:** ✅ Implemented — deprecated forwarding wrappers in `Git::Repository::Configuring` + `Git::Base` delegators added (PR 5h-2).
|
|
234
|
+
|
|
235
|
+
---
|
|
236
|
+
|
|
237
|
+
### 3.3 `global_config_get(name)`, `global_config_list`, and `global_config_set(name, value)`
|
|
238
|
+
|
|
239
|
+
These three are treated together as the global-config variants of the local
|
|
240
|
+
config primitives above.
|
|
241
|
+
|
|
242
|
+
#### `global_config_get(name)`
|
|
243
|
+
|
|
244
|
+
**Signature:** `global_config_get(name)`
|
|
245
|
+
|
|
246
|
+
**Description:** Identical to `config_get` except it passes `global: true`
|
|
247
|
+
to `Git::Commands::ConfigOptionSyntax::Get`, targeting `~/.gitconfig` (or
|
|
248
|
+
`$XDG_CONFIG_HOME/git/config`) rather than the repository's `.git/config`.
|
|
249
|
+
|
|
250
|
+
#### `global_config_list`
|
|
251
|
+
|
|
252
|
+
**Signature:** `global_config_list`
|
|
253
|
+
|
|
254
|
+
**Description:** Identical to `config_list` except it passes `global: true`
|
|
255
|
+
to `Git::Commands::ConfigOptionSyntax::List`.
|
|
256
|
+
|
|
257
|
+
#### `global_config_set(name, value)`
|
|
258
|
+
|
|
259
|
+
**Signature:** `global_config_set(name, value)`
|
|
260
|
+
|
|
261
|
+
**Description:** Calls `Git::Commands::ConfigOptionSyntax::Set` with
|
|
262
|
+
`global: true`, writing to the user's global git config. Takes no `:file`
|
|
263
|
+
option (global mode is mutually exclusive with `--file`).
|
|
264
|
+
|
|
265
|
+
**Context:** This is a **genuine gap** in the current `Git::Repository`
|
|
266
|
+
API. Unlike the local config methods above, `Git::Repository::Configuring#config()`
|
|
267
|
+
has no `:global` option and provides no way to read or write global config
|
|
268
|
+
at all. Any caller currently using `g.lib.global_config_get(name)` would
|
|
269
|
+
lose that capability entirely under v5 with no migration path unless this
|
|
270
|
+
gap is addressed.
|
|
271
|
+
|
|
272
|
+
**Options:**
|
|
273
|
+
|
|
274
|
+
- **A — Add a `:global` option to `config()`:** Extend the existing facade
|
|
275
|
+
with `config(name = nil, value = nil, global: false, **options)`. All
|
|
276
|
+
three overloads (get, list, set) would gain a global variant by passing
|
|
277
|
+
`global: true`. Keeps the API surface minimal.
|
|
278
|
+
- **B — Add dedicated `global_config_get/list/set` methods:** Mirror the
|
|
279
|
+
`Git::Lib` names exactly as public methods on `Git::Repository::Configuring`.
|
|
280
|
+
More discoverable by name; no overloading.
|
|
281
|
+
- **C — Add a separate `global_config(name = nil, value = nil)` facade:**
|
|
282
|
+
A parallel unified method that follows the same dispatch logic as `config()`
|
|
283
|
+
but targets global config. Clean API symmetry, no keyword arg overloading.
|
|
284
|
+
|
|
285
|
+
**Recommendation:** **Option C, with backward-compatible deprecated aliases.**
|
|
286
|
+
A dedicated `global_config()` method with identical dispatch logic to
|
|
287
|
+
`config()` (list/get/set based on argument count) gives the cleanest API
|
|
288
|
+
symmetry. Option A risks a confusing mix of positional and keyword arguments
|
|
289
|
+
on an already-overloaded method. Option B re-introduces three separate names
|
|
290
|
+
when the project has already committed to the unified `config()` pattern.
|
|
291
|
+
`global_config()` is obvious, memorable, and mirrors the existing `config()`
|
|
292
|
+
contract exactly.
|
|
293
|
+
|
|
294
|
+
To preserve backward compatibility, also add the three `Git::Lib` names as
|
|
295
|
+
deprecated forwarding methods on `Git::Repository::Configuring` (and
|
|
296
|
+
corresponding `Git::Base` delegators):
|
|
297
|
+
|
|
298
|
+
```ruby
|
|
299
|
+
# @deprecated Use {#global_config} instead.
|
|
300
|
+
def global_config_get(name)
|
|
301
|
+
Git::Deprecation.warn(
|
|
302
|
+
'global_config_get is deprecated and will be removed in a future version. ' \
|
|
303
|
+
'Use global_config(name) instead.'
|
|
304
|
+
)
|
|
305
|
+
global_config(name)
|
|
306
|
+
end
|
|
307
|
+
|
|
308
|
+
# @deprecated Use {#global_config} instead.
|
|
309
|
+
def global_config_list
|
|
310
|
+
Git::Deprecation.warn(
|
|
311
|
+
'global_config_list is deprecated and will be removed in a future version. ' \
|
|
312
|
+
'Use global_config instead.'
|
|
313
|
+
)
|
|
314
|
+
global_config
|
|
315
|
+
end
|
|
316
|
+
|
|
317
|
+
# @deprecated Use {#global_config} instead.
|
|
318
|
+
def global_config_set(name, value)
|
|
319
|
+
Git::Deprecation.warn(
|
|
320
|
+
'global_config_set is deprecated and will be removed in a future version. ' \
|
|
321
|
+
'Use global_config(name, value) instead.'
|
|
322
|
+
)
|
|
323
|
+
global_config(name, value)
|
|
324
|
+
end
|
|
325
|
+
```
|
|
326
|
+
|
|
327
|
+
These can be removed in v6 after a full deprecation cycle.
|
|
328
|
+
|
|
329
|
+
**Decision:** Accepted. Add `global_config(name = nil, value = nil)` to `Git::Repository::Configuring` with three-way dispatch (list/get/set). Add deprecated forwarding methods `global_config_get`, `global_config_list`, `global_config_set` with `Git::Deprecation.warn` calls. Add `Git::Base` delegators for all four. Remove deprecated aliases in v6.
|
|
330
|
+
|
|
331
|
+
**Implemented in PR 5h-3.** `global_config` facade added to `Git::Repository::Configuring` with three-way dispatch; private helpers `global_config_get/list/set` in the `Private` module; deprecated forwarding methods on the public API; `Git::Base` delegators for all four methods.
|
|
332
|
+
|
|
333
|
+
---
|
|
334
|
+
|
|
335
|
+
### 3.4 `parse_config(file)`
|
|
336
|
+
|
|
337
|
+
**Signature:** `parse_config(file)`
|
|
338
|
+
|
|
339
|
+
**Description:** Calls
|
|
340
|
+
`Git::Commands::ConfigOptionSyntax::List.new(self).call(file: file)` and
|
|
341
|
+
parses the output into a `Hash{String => String}` via `parse_config_list`.
|
|
342
|
+
The `file` argument is an arbitrary filesystem path to any
|
|
343
|
+
`.gitconfig`-format file — not necessarily the repository's own config.
|
|
344
|
+
This is the mechanism `Git::Lib` uses internally for reading `~/.gitconfig`
|
|
345
|
+
or any other git-style config file by path.
|
|
346
|
+
|
|
347
|
+
**Context:** `Git::Repository::Configuring#config()` already accepts a
|
|
348
|
+
`:file` option in its **set** overload (to write to a custom file), but has
|
|
349
|
+
no `:file` support in the **get** or **list** overloads, and there is no
|
|
350
|
+
way to read an arbitrary config file via the current facade. This is a
|
|
351
|
+
partial gap: write-to-file is covered, read-from-file is not.
|
|
352
|
+
|
|
353
|
+
**Options:**
|
|
354
|
+
|
|
355
|
+
- **A — Extend `config()` with a `:file` option for read operations:**
|
|
356
|
+
Allow `repo.config(file: '/path')` (list-from-file) and
|
|
357
|
+
`repo.config('key', file: '/path')` (get-from-file). Consistent with the
|
|
358
|
+
existing `config()` contract and the `:file` option already used for set.
|
|
359
|
+
- **B — Promote as a standalone `parse_config(file)` method:** Add
|
|
360
|
+
`parse_config(file)` directly to `Git::Repository::Configuring` (or a
|
|
361
|
+
suitable module) with the same signature. Preserves backward compatibility
|
|
362
|
+
for any callers using `g.lib.parse_config(path)`.
|
|
363
|
+
- **C — Mark as internal; no public equivalent:** The use case of reading
|
|
364
|
+
an arbitrary git config file is niche. Callers who need it can invoke
|
|
365
|
+
`git config --list --file <path>` via `Git::Commands` directly.
|
|
366
|
+
|
|
367
|
+
**Recommendation:** **Option A, with a backward-compatible deprecated alias.**
|
|
368
|
+
The `:file` option is already part of the `config()` facade for writes;
|
|
369
|
+
extending it to reads creates a consistent, symmetric API. The implementation
|
|
370
|
+
is trivial (pass `file:` to the `Get` and `List` command calls). To preserve
|
|
371
|
+
backward compatibility, also add `parse_config` as a deprecated forwarding
|
|
372
|
+
method on `Git::Repository::Configuring` (and a corresponding `Git::Base`
|
|
373
|
+
delegator):
|
|
374
|
+
|
|
375
|
+
```ruby
|
|
376
|
+
# @deprecated Use {#config} with the :file option instead.
|
|
377
|
+
def parse_config(file)
|
|
378
|
+
Git::Deprecation.warn(
|
|
379
|
+
'parse_config is deprecated and will be removed in a future version. ' \
|
|
380
|
+
'Use config(file: <path>) instead.'
|
|
381
|
+
)
|
|
382
|
+
config(file: file)
|
|
383
|
+
end
|
|
384
|
+
```
|
|
385
|
+
|
|
386
|
+
Callers migrating from `lib.parse_config(path)` can drop to
|
|
387
|
+
`repo.parse_config(path)` immediately (no method-name change needed), then
|
|
388
|
+
migrate to `repo.config(file: path)` at their own pace. Remove in v6.
|
|
389
|
+
|
|
390
|
+
**Decision:** Accepted. Extend `Git::Repository::Configuring#config()` to accept `:file` on the get and list overloads. Add deprecated `parse_config(file)` forwarding method with `Git::Deprecation.warn` call pointing to `config(file: <path>)`. Add `Git::Base` delegator. Remove deprecated alias in v6.
|
|
391
|
+
|
|
392
|
+
**Implemented in PR 5h-4.** `config()` extended to accept `:file` on the get and list overloads; deprecated `parse_config(file)` forwarding wrapper added to `Git::Repository::Configuring` with `Git::Deprecation.warn`; `Git::Base#parse_config` delegator added.
|
|
393
|
+
|
|
394
|
+
---
|
|
395
|
+
|
|
396
|
+
### 3.5 `stash_list`
|
|
397
|
+
|
|
398
|
+
**Signature:** `stash_list`
|
|
399
|
+
|
|
400
|
+
**Description:** Calls `Git::Commands::Stash::List` and parses the result
|
|
401
|
+
via `Git::Parsers::Stash.parse_list`, then formats the structured data back
|
|
402
|
+
into a plain `String` of the form `"stash@{0}: On main: WIP\nstash@{1}: On
|
|
403
|
+
feature: test"`. This replicates the raw output of `git stash list` and
|
|
404
|
+
exists specifically to preserve the v4 backward-compatible return type (a
|
|
405
|
+
formatted string rather than structured objects).
|
|
406
|
+
|
|
407
|
+
**Context:** `Git::Repository::Stashing#stashes_all` (already promoted in
|
|
408
|
+
§7.2) returns `Array<[Integer, String]>` — pairs of `[index, message_string]`
|
|
409
|
+
where the message has the branch prefix stripped (e.g., `[0, "Fix bug"]`
|
|
410
|
+
rather than `"stash@{0}: WIP on main: Fix bug"`). The two methods serve
|
|
411
|
+
different callers: `stashes_all` is the clean v5 API returning structured
|
|
412
|
+
data; `stash_list` returns the full formatted-string representation that v4
|
|
413
|
+
callers depending on `"stash@{n}: ..."` format may depend on. There is no
|
|
414
|
+
`Git::Base` delegator for `stash_list`.
|
|
415
|
+
|
|
416
|
+
**Options:**
|
|
417
|
+
|
|
418
|
+
- **A — Promote `stash_list` as a deprecated method:** Add `stash_list` to
|
|
419
|
+
`Git::Repository::Stashing` with a `@deprecated` YARD tag pointing callers
|
|
420
|
+
to `stashes_all`. Keep the formatted-string behavior to avoid a breaking
|
|
421
|
+
change for v4 callers.
|
|
422
|
+
- **B — Deprecate without promotion; add upgrade note only:** Do not expose
|
|
423
|
+
`stash_list` on `Git::Repository`. Document in `UPGRADING.md` that
|
|
424
|
+
callers should migrate to `stashes_all`. This is a breaking change for
|
|
425
|
+
anyone calling `g.lib.stash_list` and expecting a string.
|
|
426
|
+
- **C — Mark as covered; no action:** Treat `stash_list` as already
|
|
427
|
+
superseded by `stashes_all` and silently remove it. Same breaking-change
|
|
428
|
+
risk as Option B but without an upgrade note.
|
|
429
|
+
|
|
430
|
+
**Recommendation:** **Option A.** The formatted-string return type is the
|
|
431
|
+
kind of low-effort backward-compatible shim that prevents v4 callers from
|
|
432
|
+
hitting a hard error during migration. The method must emit a runtime
|
|
433
|
+
deprecation warning via `Git::Deprecation.warn`, consistent with every other
|
|
434
|
+
deprecated method in the project. Because `stashes_all` returns
|
|
435
|
+
`[index, message_string]` pairs with a different format, `stash_list` must
|
|
436
|
+
call the stash command directly to preserve the `"stash@{n}: <full message>"`
|
|
437
|
+
format that v4 callers expect. The implementation is:
|
|
438
|
+
|
|
439
|
+
```ruby
|
|
440
|
+
# @deprecated Use {#stashes_all} instead.
|
|
441
|
+
def stash_list
|
|
442
|
+
Git::Deprecation.warn(
|
|
443
|
+
'stash_list is deprecated and will be removed in a future version. ' \
|
|
444
|
+
'Use stashes_all instead.'
|
|
445
|
+
)
|
|
446
|
+
result = Git::Commands::Stash::List.new(@execution_context).call
|
|
447
|
+
stashes = Git::Parsers::Stash.parse_list(result.stdout)
|
|
448
|
+
stashes.map { |info| "#{info.name}: #{info.message}" }.join("\n")
|
|
449
|
+
end
|
|
450
|
+
```
|
|
451
|
+
|
|
452
|
+
Remove the method in v6 after a full deprecation cycle.
|
|
453
|
+
|
|
454
|
+
**Decision:** Accepted. Add `stash_list` to `Git::Repository::Stashing` as a deprecated method with `Git::Deprecation.warn` pointing to `stashes_all`. Add `Git::Base` delegator. Remove in v6.
|
|
455
|
+
|
|
456
|
+
**Status: ✅ Implemented** — `Git::Repository::Stashing#stash_list` + `Git::Base#stash_list` delegator added (PR 5h-5).
|
|
457
|
+
|
|
458
|
+
---
|
|
459
|
+
|
|
460
|
+
### 3.6 `unmerged`
|
|
461
|
+
|
|
462
|
+
**Signature:** `unmerged`
|
|
463
|
+
|
|
464
|
+
**Description:** Calls `Git::Commands::Diff.new(self).call(cached: true)`
|
|
465
|
+
and scans the output for lines matching `* Unmerged path (.*)`, returning
|
|
466
|
+
an `Array<String>` of repository-relative file paths that have unresolved
|
|
467
|
+
merge conflicts. It is the same logic as
|
|
468
|
+
`Git::Repository::Merging::Private.unmerged_paths` — the private helper
|
|
469
|
+
already extracted into the `Merging` module for use by `each_conflict`.
|
|
470
|
+
|
|
471
|
+
**Context:** `Git::Repository::Merging#each_conflict` already uses
|
|
472
|
+
`Private.unmerged_paths` internally and yields those same paths (alongside
|
|
473
|
+
temporary files of staged content) to its block. If a caller only needs the
|
|
474
|
+
list of conflicting file paths — not the staged content — they currently have
|
|
475
|
+
no public API for that; they must call `each_conflict` with a block that
|
|
476
|
+
discards the tempfile arguments. The logic for extracting unmerged paths is
|
|
477
|
+
already implemented and tested inside the `Merging` module's private layer.
|
|
478
|
+
|
|
479
|
+
**Options:**
|
|
480
|
+
|
|
481
|
+
- **A — Promote `unmerged` as a public method on `Git::Repository::Merging`:**
|
|
482
|
+
Surface `Private.unmerged_paths` directly as `unmerged` (or
|
|
483
|
+
`unmerged_paths`). Add a `Git::Base` delegator. One-line implementation;
|
|
484
|
+
no new commands needed.
|
|
485
|
+
- **B — Keep `each_conflict` as the only public API:** Document that callers
|
|
486
|
+
who only need the path list should call `each_conflict` and collect the
|
|
487
|
+
first argument in the block. No new methods added.
|
|
488
|
+
- **C — Rename the promotion to `conflict_files` (or similar):** Promote
|
|
489
|
+
the functionality under a clearer name that signals it returns a list of
|
|
490
|
+
conflicting file paths rather than "unmerged index entries".
|
|
491
|
+
|
|
492
|
+
**Recommendation:** **Option A.** Promoting `unmerged` is low-cost (the
|
|
493
|
+
private helper already exists) and fills a real usability gap: forcing
|
|
494
|
+
callers to use `each_conflict` when they only want a path list is
|
|
495
|
+
unnecessarily heavyweight. The name `unmerged` has v4 precedent and is
|
|
496
|
+
consistent with git's own terminology (`git diff --cached` unmerged paths).
|
|
497
|
+
Use `unmerged` as the promoted name to preserve backward compatibility.
|
|
498
|
+
|
|
499
|
+
**Decision:** Accepted. Promote `unmerged` as a public method on `Git::Repository::Merging` by surfacing `Private.unmerged_paths`. Add `Git::Base` delegator.
|
|
500
|
+
|
|
501
|
+
**Status:** ✅ Implemented — `Git::Repository::Merging#unmerged` added; `Git::Base#unmerged` delegator added.
|
|
502
|
+
|
|
503
|
+
## 4. Pre-resolved questions
|
|
504
|
+
|
|
505
|
+
The following questions were raised during initial drafting and are now
|
|
506
|
+
resolved. The answers are binding on PR 5h unless explicitly overridden.
|
|
507
|
+
|
|
508
|
+
### 4.1 Global config API surface breadth (§3.3) — **Resolved**
|
|
509
|
+
|
|
510
|
+
**Question:** Should `global_config()` also accept a `:system` flag for
|
|
511
|
+
system-level config, or a `:file` option for arbitrary-path reads?
|
|
512
|
+
|
|
513
|
+
**Answer: Narrow implementation only — global list/get/set, no `:system`,
|
|
514
|
+
no `:file`.**
|
|
515
|
+
|
|
516
|
+
System-level config (`/etc/gitconfig`) requires elevated filesystem
|
|
517
|
+
permissions and belongs to git administration, not application-level
|
|
518
|
+
tooling. There is no credible use case for a Ruby gem managing system config.
|
|
519
|
+
The `:file` option for arbitrary-path reads is already covered by
|
|
520
|
+
`config(file: path)` per §3.4's resolution — `global_config` does not
|
|
521
|
+
need to duplicate it. Keeping `global_config()` narrow also avoids questions
|
|
522
|
+
about whether `global: true` and `file:` can be combined (they cannot; git
|
|
523
|
+
rejects `--global --file` together). The initial signature is:
|
|
524
|
+
|
|
525
|
+
```ruby
|
|
526
|
+
global_config(name = nil, value = nil)
|
|
527
|
+
```
|
|
528
|
+
|
|
529
|
+
with the same three-way dispatch as `config()`. A `:system` variant can be
|
|
530
|
+
added in a later minor release if demand materializes.
|
|
531
|
+
|
|
532
|
+
---
|
|
533
|
+
|
|
534
|
+
### 4.2 `change_head_branch` guard semantics (§3.1) — **Resolved**
|
|
535
|
+
|
|
536
|
+
**Question:** Should `change_head_branch` guard against non-existent branch
|
|
537
|
+
names, raising `ArgumentError` if the branch does not yet exist?
|
|
538
|
+
|
|
539
|
+
**Answer: No guard.**
|
|
540
|
+
|
|
541
|
+
The unborn-branch workflow — pointing HEAD at a ref that does not yet exist,
|
|
542
|
+
before the first commit is made — is a first-class git pattern. It is how
|
|
543
|
+
`git init --initial-branch=main` works, and it is how repository
|
|
544
|
+
initialization scripts change the default branch before any commits land.
|
|
545
|
+
A guard that rejects non-existent branches would silently break this
|
|
546
|
+
workflow. `git symbolic-ref` itself imposes no such constraint, and neither
|
|
547
|
+
should the facade. The method should be promoted as a clean pass-through
|
|
548
|
+
with clear documentation explaining that the target branch need not exist,
|
|
549
|
+
that pointing HEAD at a non-existent ref places the repository in unborn
|
|
550
|
+
state, and that this is intentional for initialization workflows. Callers
|
|
551
|
+
who want an existence check can call `repo.branches.local.any? { |b| b.name
|
|
552
|
+
== branch_name }` before calling `change_head_branch`.
|
|
553
|
+
|
|
554
|
+
---
|
|
555
|
+
|
|
556
|
+
### 4.3 `config()` overload complexity with `:file` for reads (§3.4) — **Resolved**
|
|
557
|
+
|
|
558
|
+
**Question:** Does extending `config()` to accept `:file` on the get and
|
|
559
|
+
list overloads create unacceptable dispatch complexity?
|
|
560
|
+
|
|
561
|
+
**Answer: Acceptable. Confirm Option A for §3.4.**
|
|
562
|
+
|
|
563
|
+
The dispatch rule for `config()` is purely positional-argument-count-based
|
|
564
|
+
(0 args → list, 1 arg → get, 2 args → set). Adding `:file` as a keyword
|
|
565
|
+
modifier does not change that rule; it is a pure option, not a new dispatch
|
|
566
|
+
dimension. The resulting overload table is:
|
|
567
|
+
|
|
568
|
+
| Positional args | `:file` present | Mode |
|
|
569
|
+
|-----------------|-----------------|------|
|
|
570
|
+
| 0 | no | list from repo |
|
|
571
|
+
| 0 | yes | list from file |
|
|
572
|
+
| 1 | no | get from repo |
|
|
573
|
+
| 1 | yes | get from file |
|
|
574
|
+
| 2 | no/yes | set (`:file` already works) |
|
|
575
|
+
|
|
576
|
+
Every cell is unambiguous. The `:file` option is already documented and
|
|
577
|
+
validated for the set overload; extending its meaning to reads is symmetric
|
|
578
|
+
and matches caller expectations. There is no new dispatch rule to learn.
|
|
579
|
+
|
|
580
|
+
---
|
|
581
|
+
|
|
582
|
+
### 4.4 Semver classification of `stash_list` non-promotion (§3.5) — **Resolved**
|
|
583
|
+
|
|
584
|
+
**Question:** If `stash_list` is not promoted (Options B or C in §3.5), is
|
|
585
|
+
a `BREAKING CHANGE:` CHANGELOG entry mandatory?
|
|
586
|
+
|
|
587
|
+
**Answer: Yes, mandatory — but moot if the recommended Option A is followed.**
|
|
588
|
+
|
|
589
|
+
`Git::Base#lib` is a public accessor. The audit's own framing (§7.1)
|
|
590
|
+
recognizes that every public method on `Git::Lib` is reachable from external
|
|
591
|
+
call sites as `g.lib.method_name`. Any method that disappears from that
|
|
592
|
+
surface without a deprecation cycle is a semver major breaking change,
|
|
593
|
+
regardless of `Git::Lib`'s `@api private` annotation. If the reviewer
|
|
594
|
+
overrides §3.5's recommendation and chooses Option B or C, the implementing
|
|
595
|
+
commit **must** carry a `BREAKING CHANGE:` footer and `UPGRADING.md`
|
|
596
|
+
must document the removal explicitly. Choosing Option A (promote as
|
|
597
|
+
`@deprecated`) avoids this entirely: the method remains callable in v5 with
|
|
598
|
+
a deprecation warning, and is removed in v6 after a full deprecation cycle.
|
|
599
|
+
Option A is therefore the only path that does not require a BREAKING CHANGE
|
|
600
|
+
entry for this specific method.
|
|
601
|
+
|
|
602
|
+
---
|
|
603
|
+
|
|
604
|
+
## 5. Next Steps
|
|
605
|
+
|
|
606
|
+
Complete items 1–4 below, then open PR 5h to implement the decisions.
|
|
607
|
+
|
|
608
|
+
- [x] Record a decision for each of the 10 items above (§3.1–§3.6) in the
|
|
609
|
+
**Decision** fields. (The four questions in §4 are pre-resolved and
|
|
610
|
+
do not require separate reviewer input.)
|
|
611
|
+
- [x] For items decided as **"promote"** (§3.1, §3.5, §3.6): raise a
|
|
612
|
+
follow-up issue or include in the PR 5h scope, specifying the target
|
|
613
|
+
`Git::Repository` module. *(Issues: #1399 `unmerged`, #1400 `stash_list`, #1401 `change_head_branch`)*
|
|
614
|
+
- [x] For items decided as **"deprecated forwarding alias over existing facade"**
|
|
615
|
+
(§3.2, §3.3, §3.4): include both the facade extension *and* the
|
|
616
|
+
deprecated forwarding methods (with `Git::Deprecation.warn` calls) in
|
|
617
|
+
PR 5h scope. *(Issues: #1402 `config_get/list/set`, #1403 `global_config`, #1404 `parse_config`)*
|
|
618
|
+
- [x] For items decided as **"mark internal / no public equivalent"**: confirm
|
|
619
|
+
the upgrade note wording and ensure it appears in `UPGRADING.md`
|
|
620
|
+
before PR 5h merges. *(No items landed in this bucket — all decisions
|
|
621
|
+
were "promote" or "deprecated forwarding alias". `UPGRADING.md` created
|
|
622
|
+
with all entries pre-populated.)*
|
|
623
|
+
- [x] Once all decisions are recorded, open PR 5h targeting `main` to
|
|
624
|
+
implement the promotions, add the deprecated aliases with runtime
|
|
625
|
+
warnings, add the upgrade notes to `UPGRADING.md`, and close this document's action items.
|
|
626
|
+
*(All PR 5h-1 through 5h-6 implementations merged; `UPGRADING.md` populated.)*
|