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,360 @@
1
+ # Phase 4 / Step A — Remove Old Code: Execution Plan
2
+
3
+ ## Goal
4
+
5
+ Delete `Git::Base`, `Git::Lib`, and the `from_base`/`base_object` bridge in a
6
+ single atomic breaking PR. The preceding PRs are strictly preparatory and
7
+ backward-compatible — each is releasable independently unless a dependency is
8
+ noted.
9
+
10
+ ---
11
+
12
+ ## Done-When Criteria
13
+
14
+ - `lib/git/lib.rb` and `lib/git/base.rb` are deleted.
15
+ - `Git::ExecutionContext::Repository` has no `base_object:` param, no
16
+ `attr_reader :base_object`, no `.from_base` factory, and no propagation of
17
+ `@base_object` in `dup_with`.
18
+ - `grep -rEn 'Git::Lib|Git::Base' lib/` returns only YARD history comments,
19
+ no runtime references.
20
+ - Entry-point behavior (`Git.open`, `.clone`, `.init`, `.bare`,
21
+ `.default_branch`, `.git_version`) still works entirely through
22
+ `Git::Repository` / command / parser paths.
23
+ - Full CI pipeline (RSpec + Test::Unit + linters) is green after all removals.
24
+
25
+ ---
26
+
27
+ ## Sequencing
28
+
29
+ PRs with no incoming edges may be merged in any order or in parallel. PR 4
30
+ cannot begin until every other PR has merged.
31
+
32
+ ```mermaid
33
+ graph TD
34
+ PR1a["PR 1a"]
35
+ PR1b["PR 1b"]
36
+ PR1c["PR 1c"]
37
+ PR1d["PR 1d"]
38
+ PR2a["PR 2a"]
39
+ PR2b["PR 2b"]
40
+ PR2c["PR 2c"]
41
+ PR3a["PR 3a"]
42
+ PR3b["PR 3b"]
43
+ PR3c["PR 3c"]
44
+ PR3d["PR 3d"]
45
+ PR4["PR 4"]
46
+
47
+ PR1a --> PR4
48
+ PR1b --> PR4
49
+ PR1c --> PR4
50
+ PR1d --> PR4
51
+ PR2a --> PR4
52
+ PR2b --> PR4
53
+ PR2c --> PR4
54
+ PR3a --> PR4
55
+ PR3b --> PR4
56
+ PR3c --> PR4
57
+ PR3d --> PR4
58
+ ```
59
+
60
+ ---
61
+
62
+ ## PR 1a — Rewire `Git.default_branch`
63
+
64
+ **Releasable independently. No breaking changes.**
65
+
66
+ In `lib/git.rb`, replace `Git.default_branch`'s delegation to
67
+ `Base.repository_default_branch` with the direct
68
+ `Git::Commands::LsRemote` + `Git::Parsers::LsRemote.parse_default_branch`
69
+ flow.
70
+
71
+ **Co-merge the spec that asserts the old delegation** — this rewire breaks an
72
+ existing expectation, so its update must ship in this PR to keep CI green:
73
+
74
+ - `spec/unit/git/git_remote_utilities_spec.rb` — the `.default_branch` example
75
+ asserts `expect(Git::Base).to receive(:repository_default_branch)`. Replace
76
+ it with a test against the direct `LsRemote` + parser path.
77
+
78
+ (The `.repository_default_branch` example in `spec/unit/git/base_spec.rb` tests
79
+ `Git::Base` directly, still passes, and is deleted by PR 3c — leave it alone
80
+ here.)
81
+
82
+ **Gate:** `Git.default_branch` works; full CI green.
83
+
84
+ ---
85
+
86
+ ## PR 1b — Rewire `Git.git_version`
87
+
88
+ **Releasable independently. No breaking changes.**
89
+
90
+ Move the git-version cache out of `Git::Lib` to a module-level cache on `Git`
91
+ (in `lib/git.rb`) so that `Git.git_version` no longer routes through
92
+ `Git::Lib`.
93
+
94
+ - **Relocate the cache state.** Move the cache initialization currently in
95
+ `Git::Lib` (`@git_version_cache = {}` and `@git_version_cache_mutex =
96
+ Mutex.new`) to module-level instance variables on `Git` in `lib/git.rb`.
97
+ Keep the mutex — it guards parallelism on JRuby/TruffleRuby.
98
+ - **Relocate the cache API.** Add `Git.cached_git_version(binary_path,
99
+ &block)` (memoize per binary path) and `Git.clear_git_version_cache`
100
+ (used by tests) as class methods on `Git`. Mark both `@api private`.
101
+ - **Rewire the caller.** In `lib/git.rb`, change `Git.git_version` to call
102
+ `Git.cached_git_version` instead of `Git::Lib.cached_git_version`
103
+ ([lib/git.rb#L512](../lib/git.rb#L512)).
104
+ - The instance method `Git::Lib#git_version` and the class methods on
105
+ `Git::Lib` are deleted wholesale in PR 4, so they need no rewiring here; do
106
+ not leave a second live cache behind.
107
+
108
+ **Co-merge the specs that assert the old cache owner** — this rewire breaks
109
+ existing expectations, so their updates must ship in this PR to keep CI green:
110
+
111
+ - `spec/unit/git/git_configure_spec.rb` — replace the
112
+ `Git::Lib.clear_git_version_cache` setup and the
113
+ `expect(Git::Lib).to have_received(:cached_git_version)` assertion with
114
+ `Git.clear_git_version_cache` / `Git.cached_git_version`.
115
+ - `spec/unit/git/git_version_spec.rb` — replace the
116
+ `before { Git::Lib.clear_git_version_cache }` setup with
117
+ `Git.clear_git_version_cache`; otherwise it clears the now-unused cache and
118
+ the new `Git` cache leaks across examples (flaky/failing per-binary-path
119
+ tests).
120
+
121
+ **Gate:** `Git.git_version` works and caches per binary path; full CI green.
122
+
123
+ ---
124
+
125
+ ## PR 1c — Remove `require 'git/base'` from Domain Object Files
126
+
127
+ **Releasable independently. No breaking changes.**
128
+
129
+ Remove `require 'git/base'` from each of these files:
130
+ `branch.rb`, `branches.rb`, `remote.rb`, `object.rb`, `log.rb`,
131
+ `stash.rb`, `stashes.rb`, `worktree.rb`, `worktrees.rb`.
132
+
133
+ This is safe because `lib/git.rb` still requires `git/base` and `git/lib`
134
+ directly, so both constants remain loaded. The only effect is that `base.rb`
135
+ is no longer force-loaded early (out of order) by the domain-object files;
136
+ load order then depends entirely on `lib/git.rb`, which the CI gate verifies.
137
+ Doing this early is beneficial — it shrinks PR 4 and exercises the
138
+ `lib/git.rb` load order under CI ahead of the breaking change.
139
+
140
+ > The removal of the `require 'git/lib'` / `require 'git/base'` lines from
141
+ > `lib/git.rb` itself is **not** done here — that is the breaking change and is
142
+ > folded into [PR 4](#pr-4--atomic-removal).
143
+
144
+ **Gate:** full CI green; no `require 'git/base'` in domain object files.
145
+
146
+ ---
147
+
148
+ ## PR 1d — Update `{Git::Base}`/`{Git::Lib}` YARD Cross-References in Domain Object and Command Files
149
+
150
+ **Releasable independently. Documentation only.**
151
+
152
+ `.yardopts` sets `--fail-on-warning`, so any `{Git::Base#…}` or `{Git::Lib#…}` YARD
153
+ cross-reference link that survives into PR 4 becomes a CI failure the moment
154
+ `Git::Base` and `Git::Lib` are deleted. PRs 2a–2c cover the command layer,
155
+ repository layer, and diff/status domain types, but five additional files have
156
+ unresolved cross-references that are not covered by those PRs.
157
+
158
+ ### `lib/git/branch.rb`
159
+
160
+ Replace two class-doc and one method-param cross-references:
161
+
162
+ - Class docstring (lines 9–10): `{Git::Base#branch}` and `{Git::Base#branches}`
163
+ → `{Git::Repository#branch}` and `{Git::Repository#branches}`.
164
+ - `attr_accessor :full` docstring (line 26): `{Git::Base#branches}`
165
+ → `{Git::Repository#branches}`.
166
+ - `archive` method `@param opts` (line 149): `{Git::Base#archive}`
167
+ → `{Git::Repository#archive}`.
168
+
169
+ ### `lib/git/remote.rb`
170
+
171
+ Replace two cross-references:
172
+
173
+ - Class docstring (line 10): `{Git::Base#remote}` → `{Git::Repository#remote}`.
174
+ - `fetch` method `@param opts` (line 63): `{Git::Base#fetch}`
175
+ → `{Git::Repository#fetch}`.
176
+
177
+ ### `lib/git/object.rb`
178
+
179
+ - `archive` method `@param opts` (line 105): `{Git::Lib#archive}`
180
+ → `{Git::Repository#archive}`.
181
+
182
+ ### `lib/git/commands/base.rb`
183
+
184
+ - `#initialize` `@param execution_context` (lines 176–177): change type from
185
+ `[Git::ExecutionContext, Git::Lib]` to `[Git::ExecutionContext]`; replace
186
+ `{Git::Lib#command_capturing}` / `{Git::Lib#command_streaming}` with
187
+ `{Git::ExecutionContext#command_capturing}` /
188
+ `{Git::ExecutionContext#command_streaming}`.
189
+ - `with_stdin_pipe` inline doc (line 354): `{Git::Lib#command_capturing}`
190
+ → `{Git::ExecutionContext#command_capturing}`.
191
+
192
+ ### `lib/git/version.rb`
193
+
194
+ - `to_a` docstring (line 103): demote `{Git::Lib#current_command_version}` from a
195
+ YARD cross-reference link to a code literal
196
+ (`Git::Lib#current_command_version`), since `Git::Lib` will not exist
197
+ after PR 4.
198
+
199
+ **Gate:** linters and YARD doc build pass (`bundle exec rake rubocop yard build`);
200
+ no behavior changes.
201
+
202
+ ---
203
+
204
+ ## PR 2a — Update Command-Layer YARD
205
+
206
+ **Releasable independently. Documentation only.**
207
+
208
+ - `lib/git/commands.rb` — update architecture diagram to reference
209
+ `Git::Repository` instead of `Git::Base`/`Git::Lib`.
210
+ - `lib/git/command_line.rb` — update factory helper references.
211
+ - `lib/git/command_line/capturing.rb`, `streaming.rb` — update
212
+ `@see Git::Lib#…` tags.
213
+
214
+ **Gate:** linters and YARD doc build pass; no behavior changes.
215
+
216
+ ---
217
+
218
+ ## PR 2b — Update Repository-Layer YARD
219
+
220
+ **Releasable independently. Documentation only.**
221
+
222
+ - `lib/git/repository/path_resolver.rb` — update `Git::Base.config.binary_path`
223
+ reference.
224
+ - `lib/git/repository/factories.rb` — update or remove deprecation message
225
+ strings mentioning `Git::Lib#clone`.
226
+
227
+ **Gate:** linters and YARD doc build pass; no behavior changes.
228
+
229
+ ---
230
+
231
+ ## PR 2c — Update Domain Object `@param base` YARD Types
232
+
233
+ **Releasable independently. Documentation only.**
234
+
235
+ In `diff.rb`, `status.rb`, `diff_path_status.rb`, update `@param base`
236
+ YARD type from `[Git::Base, Git::Repository]` → `[Git::Repository]`.
237
+
238
+ **Gate:** linters and YARD doc build pass; no behavior changes.
239
+
240
+ ---
241
+
242
+ ## PR 3a — Delete Confirmed-Removable Test::Unit Files
243
+
244
+ **Releasable independently. No production behavior changes.**
245
+
246
+ Delete these files, which test removed API only and have no surviving
247
+ behavioral value:
248
+
249
+ | File | Reason |
250
+ | --- | --- |
251
+ | `tests/units/test_base.rb` | Tests `Git::Base` directly |
252
+ | `tests/units/test_lib.rb` | Tests `Git::Lib` directly |
253
+ | `tests/units/test_lib_repository_default_branch.rb` | Tests `Git::Lib` default-branch path |
254
+ | `tests/units/test_lib_meets_required_version.rb` | Tests `Git::Lib` version deprecation shim |
255
+ | `tests/units/test_git_base_root_of_worktree.rb` | Tests `Git::Base.root_of_worktree` |
256
+
257
+ **Gate:** full CI green.
258
+
259
+ ---
260
+
261
+ ## PR 3b — Patch Incidental Test::Unit References
262
+
263
+ **Releasable independently. No production behavior changes.**
264
+
265
+ Audit all remaining `tests/units/` files for incidental `Git::Base`/`Git::Lib`
266
+ references and patch them to use `Git::Repository` / `Git.open` entry points.
267
+ Port any still-valuable behavioral coverage to RSpec before removing a file.
268
+
269
+ **Gate:** full CI green; no `tests/units/` file references `Git::Base`
270
+ or `Git::Lib` at runtime.
271
+
272
+ ---
273
+
274
+ ## PR 3c — Delete RSpec Specs Testing Only Removed API
275
+
276
+ **Releasable independently. No production behavior changes.**
277
+
278
+ | File | Reason |
279
+ | --- | --- |
280
+ | `spec/unit/git/base_spec.rb` | Tests `Git::Base` |
281
+ | `spec/unit/git/lib_command_spec.rb` | Tests `Git::Lib` |
282
+ | `spec/unit/git/lib_version_deprecations_spec.rb` | Tests `Git::Lib` version deprecation shims |
283
+ | `spec/unit/git/lib/git_version_spec.rb` | Tests `Git::Lib` version cache |
284
+ | `spec/integration/git/base/` (all files) | Tests `Git::Base` delegators |
285
+
286
+ **Gate:** full CI green.
287
+
288
+ ---
289
+
290
+ ## PR 3d — Migrate RSpec Bridge and `from_base` References
291
+
292
+ **Releasable independently. No production behavior changes.**
293
+
294
+ - `spec/integration/git/repository/*_spec.rb` — replace `from_base` setup
295
+ with `Git::Repository.open(…)` or `ExecutionContext::Repository.from_hash`.
296
+ - `spec/unit/git/execution_context/repository_spec.rb` — remove `from_base`
297
+ and `base_object` test contexts; keep all other execution context coverage.
298
+ - `spec/unit/git/repository/remote_operations_spec.rb` — remove the
299
+ `base_object:` instance double.
300
+ - `spec/integration/git/branches_spec.rb`, `worktrees_spec.rb` — two changes
301
+ each:
302
+ 1. Remove the "initialized via `Git::Base`" context block entirely.
303
+ 2. In the surviving "initialized via `Git::Repository`" context, replace the
304
+ `let(:execution_context) { Git::ExecutionContext::Repository.from_base(repo) }`
305
+ setup with `ExecutionContext::Repository.from_hash` or
306
+ `Git::Repository.open`.
307
+ - `spec/integration/git/commands/init_spec.rb`, `clone_spec.rb` — replace
308
+ `Git::Lib.new(nil)` with a proper `ExecutionContext::Global` or
309
+ `ExecutionContext::Repository`.
310
+
311
+ **Gate:** full CI green; no spec references `from_base` or `base_object`.
312
+
313
+ ---
314
+
315
+ ## PR 4 — Atomic Removal
316
+
317
+ **Breaking change. Targets `main` (v5.x only). Cannot be further decomposed.**
318
+
319
+ With all callers rewired (1a–1c), docs updated (2a–2c), and tests migrated
320
+ (3a–3d), this PR is the single deletion event. Unloading `Git::Base` /
321
+ `Git::Lib` is itself the breaking change, so it must happen here — after every
322
+ test that references those constants (3a–3d) has been migrated.
323
+
324
+ **Remove the legacy `require` lines from `lib/git.rb`**
325
+
326
+ File: `lib/git.rb`
327
+ - Remove `require 'git/lib'`.
328
+ - Remove `require 'git/base'`.
329
+
330
+ After this, `Git::Base` and `Git::Lib` are no longer loaded at runtime.
331
+
332
+ **Remove the `ExecutionContext::Repository` bridge**
333
+
334
+ File: `lib/git/execution_context/repository.rb`
335
+ - Remove `base_object:` keyword and `@base_object = base_object` from `#initialize`.
336
+ - Remove `attr_reader :base_object`.
337
+ - Remove `base_object: @base_object` from `dup_with`.
338
+ - Delete the `.from_base` class method.
339
+ - Update `#initialize` YARD docs to drop the `base_object` param entry.
340
+
341
+ **Delete legacy class files**
342
+ - Delete `lib/git/lib.rb`.
343
+ - Delete `lib/git/base.rb`.
344
+
345
+ ---
346
+
347
+ ## Done-Gates (All Must Pass Before PR 4 Merges)
348
+
349
+ | Gate | How to verify |
350
+ | --- | --- |
351
+ | `lib/git/lib.rb` deleted | `ls lib/git/lib.rb` → not found |
352
+ | `lib/git/base.rb` deleted | `ls lib/git/base.rb` → not found |
353
+ | No `base_object` / `from_base` in `ExecutionContext::Repository` | Read file |
354
+ | No `require 'git/lib'` / `require 'git/base'` anywhere in `lib/` | `grep -rEn "require 'git/(lib|base)'" lib/` returns nothing |
355
+ | No runtime `Git::Lib` / `Git::Base` references in `lib/` | `grep -rEn 'Git::Lib|Git::Base' lib/` returns no code lines |
356
+ | `Git.open`, `.clone`, `.init`, `.bare` return `Git::Repository` and work | Existing RSpec integration suite |
357
+ | `Git.default_branch`, `Git.git_version` work without `Git::Lib` | Existing unit + integration specs |
358
+ | Full RSpec suite green | `bundle exec rake spec:parallel` |
359
+ | Full Test::Unit suite green | `bundle exec rake test:parallel` |
360
+ | Linters pass | `bundle exec rake rubocop yard build` |
@@ -0,0 +1,107 @@
1
+ # Beta Release Process
2
+
3
+ ## Why This Is a Manual Process
4
+
5
+ Pre-releases of the `git` gem (e.g., `5.0.0.beta.1`) are published manually rather
6
+ than through the normal release-please automation. This is necessary because:
7
+
8
+ - **release-please does not support Ruby pre-release version formats** such as
9
+ `5.0.0.beta.1`. release-please follows SemVer prerelease formatting and produces
10
+ versions with a dash separator (e.g., `5.0.0-beta.0`), which is incompatible with
11
+ RubyGems' dotted prerelease format (`5.0.0.beta.X`). For this reason,
12
+ `.release-please-config.json` explicitly sets `"prerelease": false` — there is no
13
+ configuration that makes release-please produce the right format.
14
+ - **We want release-please to keep accumulating commits toward the final `5.0.0`
15
+ release.** If we merged a release-please PR for a beta version, it would update
16
+ `.release-please-manifest.json` and lose the record of changes that should appear
17
+ in the `5.0.0` changelog. By managing betas manually and leaving the manifest
18
+ untouched, release-please continues to build the `5.0.0` release PR in the
19
+ background.
20
+ - **Beta tags use a `pre/` prefix** (e.g., `pre/v5.0.0.beta.1`) so that
21
+ release-please does not match them when searching for the last release SHA. This
22
+ keeps the change accumulation correct.
23
+
24
+ ---
25
+
26
+ ## Beta Release Checklist
27
+
28
+ ### 0. Set the beta number
29
+
30
+ Set `BETA` once at the start of each session. All commands below use it.
31
+
32
+ ```bash
33
+ BETA=2 # replace with the actual beta number
34
+ ```
35
+
36
+ > **Note:** Steps 1–3 are done before merging the release PR; steps 4–6 are done
37
+ > after. You will need to re-set `BETA` if you start a new shell session between
38
+ > the two phases.
39
+
40
+ ### 1. Prepare the release branch
41
+
42
+ ```bash
43
+ git switch main && git pull
44
+ git switch -c release/5.0.0.beta.${BETA}
45
+ ```
46
+
47
+ ### 2. Update files
48
+
49
+ - [ ] Bump `lib/git/version.rb`:
50
+
51
+ ```bash
52
+ sed -i "" "s/VERSION = '.*'/VERSION = '5.0.0.beta.${BETA}'/" lib/git/version.rb
53
+ ```
54
+
55
+ - [ ] Update the announcement in `README.md`:
56
+ - Change the heading date and version number
57
+ - Update the % complete estimate
58
+ - Update the RubyGems link to point to the new version
59
+
60
+ ### 3. Commit and open a PR
61
+
62
+ ```bash
63
+ git add lib/git/version.rb README.md
64
+ git commit -m "chore: release v5.0.0.beta.${BETA}"
65
+ git push -u origin release/5.0.0.beta.${BETA}
66
+ ```
67
+
68
+ Open a PR targeting `main` and merge it once CI passes.
69
+
70
+ > **Important:** Do **not** update `.release-please-manifest.json`. Leaving it at
71
+ > the last stable 4.x release version is what allows release-please to keep
72
+ > accumulating changes toward `5.0.0`.
73
+
74
+ ### 4. Tag the release commit
75
+
76
+ Tag before building to ensure the gem and the GitHub release are pinned to the
77
+ exact same commit. If `main` advances after the PR merges, building first and
78
+ tagging later would cause the published gem and tag to diverge.
79
+
80
+ ```bash
81
+ BETA=2 # re-set if this is a new shell session
82
+ git switch main && git pull
83
+ git tag pre/v5.0.0.beta.${BETA}
84
+ git push origin pre/v5.0.0.beta.${BETA}
85
+ ```
86
+
87
+ ### 5. Build, publish, and create a GitHub release
88
+
89
+ Check out the tag before building to guarantee the gem is built from the exact
90
+ tagged commit, not from whatever `main` happens to be at build time.
91
+
92
+ ```bash
93
+ git checkout pre/v5.0.0.beta.${BETA}
94
+ bundle exec rake build
95
+ gem push pkg/git-5.0.0.beta.${BETA}.gem
96
+
97
+ gh release create pre/v5.0.0.beta.${BETA} \
98
+ pkg/git-5.0.0.beta.${BETA}.gem \
99
+ --title "v5.0.0.beta.${BETA}" \
100
+ --notes "Beta ${BETA} pre-release of v5.0.0. See the README for details." \
101
+ --prerelease
102
+ ```
103
+
104
+ ### 6. Verify
105
+
106
+ - [ ] RubyGems: `https://rubygems.org/gems/git/versions/5.0.0.beta.${BETA}`
107
+ - [ ] GitHub release: `https://github.com/ruby-git/ruby-git/releases/tag/pre/v5.0.0.beta.${BETA}`