git 5.0.0.beta.3 → 5.0.0.beta.4

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 (268) hide show
  1. checksums.yaml +4 -4
  2. data/.dockerignore +27 -0
  3. data/.github/copilot-instructions.md +3 -8
  4. data/.github/hooks/bin-setup-on-worktree.json +11 -0
  5. data/.github/hooks/run-bin-setup-once.sh +14 -0
  6. data/.github/skills/breaking-change-analysis/SKILL.md +6 -9
  7. data/.github/skills/ci-cd-troubleshooting/SKILL.md +2 -2
  8. data/.github/skills/command-implementation/REFERENCE.md +34 -33
  9. data/.github/skills/command-implementation/SKILL.md +5 -4
  10. data/.github/skills/command-test-conventions/SKILL.md +13 -9
  11. data/.github/skills/command-yard-documentation/SKILL.md +14 -6
  12. data/.github/skills/development-workflow/SKILL.md +20 -14
  13. data/.github/skills/facade-implementation/REFERENCE.md +19 -22
  14. data/.github/skills/facade-implementation/SKILL.md +0 -5
  15. data/.github/skills/facade-test-conventions/SKILL.md +4 -7
  16. data/.github/skills/facade-yard-documentation/SKILL.md +8 -2
  17. data/.github/skills/make-skill-template/SKILL.md +53 -3
  18. data/.github/skills/pr-readiness-review/SKILL.md +22 -2
  19. data/.github/skills/project-context/SKILL.md +16 -23
  20. data/.github/skills/pull-request-review/SKILL.md +2 -2
  21. data/.github/skills/rebase/SKILL.md +148 -0
  22. data/.github/skills/refactor-command-to-commandlineresult/SKILL.md +1 -1
  23. data/.github/skills/resolve-feedback/SKILL.md +288 -0
  24. data/.github/skills/review-arguments-dsl/CHECKLIST.md +3 -3
  25. data/.github/skills/reviewing-skills/SKILL.md +28 -3
  26. data/.github/skills/test-debugging/SKILL.md +20 -19
  27. data/.github/skills/yard-documentation/SKILL.md +671 -483
  28. data/.github/skills/yard-documentation/element-rules.md +162 -0
  29. data/.github/skills-deprecated/README.md +21 -0
  30. data/.github/{skills → skills-deprecated}/extract-command-from-lib/SKILL.md +9 -9
  31. data/.github/{skills → skills-deprecated}/extract-facade-from-base-lib/SKILL.md +12 -12
  32. data/.github/workflows/continuous_integration.yml +3 -2
  33. data/.rubocop.yml +7 -39
  34. data/.yard-lint.yml +75 -0
  35. data/CONTRIBUTING.md +186 -174
  36. data/Gemfile +17 -0
  37. data/README.md +47 -12
  38. data/Rakefile +3 -21
  39. data/UPGRADING.md +219 -70
  40. data/docker/test/Dockerfile +32 -0
  41. data/docker/test/docker-compose.yml +0 -0
  42. data/git.gemspec +3 -4
  43. data/lib/git/author.rb +20 -1
  44. data/lib/git/branch.rb +26 -6
  45. data/lib/git/branch_delete_failure.rb +3 -0
  46. data/lib/git/branch_delete_result.rb +6 -3
  47. data/lib/git/branch_info.rb +6 -6
  48. data/lib/git/command_line/base.rb +6 -13
  49. data/lib/git/command_line/capturing.rb +67 -10
  50. data/lib/git/command_line/result.rb +0 -8
  51. data/lib/git/command_line/streaming.rb +45 -5
  52. data/lib/git/command_line.rb +4 -3
  53. data/lib/git/commands/add.rb +61 -53
  54. data/lib/git/commands/am/abort.rb +2 -2
  55. data/lib/git/commands/am/apply.rb +109 -98
  56. data/lib/git/commands/am/continue.rb +2 -2
  57. data/lib/git/commands/am/quit.rb +2 -2
  58. data/lib/git/commands/am/retry.rb +4 -2
  59. data/lib/git/commands/am/show_current_patch.rb +1 -1
  60. data/lib/git/commands/am/skip.rb +2 -2
  61. data/lib/git/commands/am.rb +2 -2
  62. data/lib/git/commands/apply.rb +7 -2
  63. data/lib/git/commands/archive/list_formats.rb +3 -3
  64. data/lib/git/commands/archive.rb +7 -2
  65. data/lib/git/commands/arguments.rb +1370 -359
  66. data/lib/git/commands/base.rb +111 -13
  67. data/lib/git/commands/branch/copy.rb +29 -21
  68. data/lib/git/commands/branch/create.rb +72 -68
  69. data/lib/git/commands/branch/delete.rb +25 -17
  70. data/lib/git/commands/branch/list.rb +76 -60
  71. data/lib/git/commands/branch/move.rb +29 -21
  72. data/lib/git/commands/branch/set_upstream.rb +8 -8
  73. data/lib/git/commands/branch/show_current.rb +3 -3
  74. data/lib/git/commands/branch/unset_upstream.rb +4 -8
  75. data/lib/git/commands/branch.rb +2 -2
  76. data/lib/git/commands/cat_file/batch.rb +36 -20
  77. data/lib/git/commands/cat_file/filtered.rb +12 -12
  78. data/lib/git/commands/cat_file/raw.rb +13 -13
  79. data/lib/git/commands/cat_file.rb +4 -4
  80. data/lib/git/commands/checkout/branch.rb +7 -5
  81. data/lib/git/commands/checkout/files.rb +5 -4
  82. data/lib/git/commands/checkout.rb +4 -4
  83. data/lib/git/commands/checkout_index.rb +5 -4
  84. data/lib/git/commands/clean.rb +6 -4
  85. data/lib/git/commands/clone.rb +5 -4
  86. data/lib/git/commands/commit.rb +5 -4
  87. data/lib/git/commands/commit_tree.rb +5 -4
  88. data/lib/git/commands/config_option_syntax/add.rb +7 -4
  89. data/lib/git/commands/config_option_syntax/get.rb +9 -5
  90. data/lib/git/commands/config_option_syntax/get_all.rb +7 -4
  91. data/lib/git/commands/config_option_syntax/get_color.rb +8 -4
  92. data/lib/git/commands/config_option_syntax/get_color_bool.rb +7 -4
  93. data/lib/git/commands/config_option_syntax/get_regexp.rb +8 -4
  94. data/lib/git/commands/config_option_syntax/get_urlmatch.rb +8 -4
  95. data/lib/git/commands/config_option_syntax/list.rb +8 -4
  96. data/lib/git/commands/config_option_syntax/remove_section.rb +7 -2
  97. data/lib/git/commands/config_option_syntax/rename_section.rb +7 -2
  98. data/lib/git/commands/config_option_syntax/replace_all.rb +7 -2
  99. data/lib/git/commands/config_option_syntax/set.rb +7 -2
  100. data/lib/git/commands/config_option_syntax/unset.rb +7 -4
  101. data/lib/git/commands/config_option_syntax/unset_all.rb +7 -2
  102. data/lib/git/commands/describe.rb +5 -4
  103. data/lib/git/commands/diff.rb +13 -12
  104. data/lib/git/commands/diff_files.rb +7 -6
  105. data/lib/git/commands/diff_index.rb +10 -7
  106. data/lib/git/commands/fetch.rb +6 -4
  107. data/lib/git/commands/fsck.rb +6 -4
  108. data/lib/git/commands/gc.rb +6 -4
  109. data/lib/git/commands/grep.rb +6 -5
  110. data/lib/git/commands/init.rb +6 -4
  111. data/lib/git/commands/log.rb +287 -285
  112. data/lib/git/commands/ls_files.rb +69 -65
  113. data/lib/git/commands/ls_remote.rb +62 -56
  114. data/lib/git/commands/ls_tree.rb +50 -46
  115. data/lib/git/commands/maintenance/register.rb +25 -23
  116. data/lib/git/commands/maintenance/run.rb +7 -2
  117. data/lib/git/commands/maintenance/start.rb +7 -2
  118. data/lib/git/commands/maintenance/stop.rb +7 -2
  119. data/lib/git/commands/maintenance/unregister.rb +7 -2
  120. data/lib/git/commands/merge/abort.rb +2 -2
  121. data/lib/git/commands/merge/continue.rb +2 -2
  122. data/lib/git/commands/merge/quit.rb +2 -2
  123. data/lib/git/commands/merge/start.rb +7 -2
  124. data/lib/git/commands/merge.rb +2 -2
  125. data/lib/git/commands/merge_base.rb +7 -2
  126. data/lib/git/commands/mv.rb +7 -2
  127. data/lib/git/commands/name_rev.rb +7 -2
  128. data/lib/git/commands/pull.rb +7 -2
  129. data/lib/git/commands/push.rb +7 -2
  130. data/lib/git/commands/read_tree.rb +7 -2
  131. data/lib/git/commands/remote/add.rb +7 -2
  132. data/lib/git/commands/remote/get_url.rb +4 -2
  133. data/lib/git/commands/remote/list.rb +4 -2
  134. data/lib/git/commands/remote/prune.rb +4 -2
  135. data/lib/git/commands/remote/remove.rb +4 -2
  136. data/lib/git/commands/remote/rename.rb +4 -2
  137. data/lib/git/commands/remote/set_branches.rb +9 -2
  138. data/lib/git/commands/remote/set_head.rb +12 -5
  139. data/lib/git/commands/remote/set_url.rb +9 -2
  140. data/lib/git/commands/remote/set_url_add.rb +11 -2
  141. data/lib/git/commands/remote/set_url_delete.rb +9 -2
  142. data/lib/git/commands/remote/show.rb +18 -12
  143. data/lib/git/commands/remote/update.rb +19 -12
  144. data/lib/git/commands/remote.rb +2 -2
  145. data/lib/git/commands/repack.rb +67 -63
  146. data/lib/git/commands/reset.rb +51 -44
  147. data/lib/git/commands/rev_parse.rb +135 -128
  148. data/lib/git/commands/revert/abort.rb +2 -2
  149. data/lib/git/commands/revert/continue.rb +7 -2
  150. data/lib/git/commands/revert/quit.rb +2 -2
  151. data/lib/git/commands/revert/skip.rb +2 -2
  152. data/lib/git/commands/revert/start.rb +7 -2
  153. data/lib/git/commands/revert.rb +2 -2
  154. data/lib/git/commands/rm.rb +35 -36
  155. data/lib/git/commands/show.rb +1 -1
  156. data/lib/git/commands/show_ref/exclude_existing.rb +3 -4
  157. data/lib/git/commands/show_ref/exists.rb +15 -13
  158. data/lib/git/commands/show_ref/list.rb +39 -35
  159. data/lib/git/commands/show_ref/verify.rb +29 -27
  160. data/lib/git/commands/show_ref.rb +4 -4
  161. data/lib/git/commands/stash/apply.rb +31 -25
  162. data/lib/git/commands/stash/branch.rb +10 -8
  163. data/lib/git/commands/stash/clear.rb +7 -5
  164. data/lib/git/commands/stash/create.rb +10 -8
  165. data/lib/git/commands/stash/drop.rb +15 -9
  166. data/lib/git/commands/stash/list.rb +9 -5
  167. data/lib/git/commands/stash/pop.rb +21 -12
  168. data/lib/git/commands/stash/push.rb +20 -11
  169. data/lib/git/commands/stash/show.rb +21 -12
  170. data/lib/git/commands/stash/store.rb +19 -10
  171. data/lib/git/commands/stash.rb +2 -2
  172. data/lib/git/commands/status.rb +7 -2
  173. data/lib/git/commands/symbolic_ref/delete.rb +18 -14
  174. data/lib/git/commands/symbolic_ref/read.rb +24 -20
  175. data/lib/git/commands/symbolic_ref/update.rb +20 -17
  176. data/lib/git/commands/symbolic_ref.rb +4 -4
  177. data/lib/git/commands/tag/create.rb +45 -42
  178. data/lib/git/commands/tag/delete.rb +5 -3
  179. data/lib/git/commands/tag/list.rb +47 -44
  180. data/lib/git/commands/tag/verify.rb +12 -12
  181. data/lib/git/commands/tag.rb +2 -2
  182. data/lib/git/commands/update_ref/batch.rb +6 -1
  183. data/lib/git/commands/update_ref/delete.rb +19 -21
  184. data/lib/git/commands/update_ref/update.rb +22 -25
  185. data/lib/git/commands/update_ref.rb +4 -4
  186. data/lib/git/commands/version.rb +14 -11
  187. data/lib/git/commands/worktree/add.rb +50 -51
  188. data/lib/git/commands/worktree/list.rb +17 -17
  189. data/lib/git/commands/worktree/lock.rb +12 -12
  190. data/lib/git/commands/worktree/move.rb +16 -16
  191. data/lib/git/commands/worktree/prune.rb +2 -2
  192. data/lib/git/commands/worktree/remove.rb +2 -2
  193. data/lib/git/commands/worktree/repair.rb +2 -2
  194. data/lib/git/commands/worktree/unlock.rb +2 -2
  195. data/lib/git/commands/write_tree.rb +2 -2
  196. data/lib/git/commands.rb +4 -3
  197. data/lib/git/config.rb +47 -1
  198. data/lib/git/configuring.rb +89 -15
  199. data/lib/git/detached_head_info.rb +4 -1
  200. data/lib/git/diff.rb +2 -5
  201. data/lib/git/diff_file_numstat_info.rb +3 -1
  202. data/lib/git/diff_file_patch_info.rb +3 -1
  203. data/lib/git/diff_file_raw_info.rb +3 -1
  204. data/lib/git/diff_info.rb +8 -15
  205. data/lib/git/diff_path_status.rb +16 -3
  206. data/lib/git/diff_result.rb +3 -1
  207. data/lib/git/diff_stats.rb +15 -0
  208. data/lib/git/dirstat_info.rb +24 -8
  209. data/lib/git/encoding_utils.rb +32 -1
  210. data/lib/git/errors.rb +19 -9
  211. data/lib/git/escaped_path.rb +55 -3
  212. data/lib/git/execution_context/repository.rb +15 -0
  213. data/lib/git/execution_context.rb +76 -74
  214. data/lib/git/file_ref.rb +6 -3
  215. data/lib/git/fsck_object.rb +8 -0
  216. data/lib/git/fsck_result.rb +26 -15
  217. data/lib/git/log.rb +152 -5
  218. data/lib/git/object.rb +284 -8
  219. data/lib/git/parsers/branch.rb +18 -0
  220. data/lib/git/parsers/cat_file.rb +2 -2
  221. data/lib/git/parsers/diff.rb +213 -5
  222. data/lib/git/parsers/fsck.rb +11 -0
  223. data/lib/git/parsers/ls_remote.rb +1 -1
  224. data/lib/git/parsers/stash.rb +51 -0
  225. data/lib/git/parsers/tag.rb +38 -0
  226. data/lib/git/remote.rb +34 -2
  227. data/lib/git/repository/branching.rb +23 -21
  228. data/lib/git/repository/committing.rb +46 -6
  229. data/lib/git/repository/context_helpers.rb +45 -16
  230. data/lib/git/repository/diffing.rb +32 -23
  231. data/lib/git/repository/factories.rb +153 -28
  232. data/lib/git/repository/inspecting.rb +1 -1
  233. data/lib/git/repository/logging.rb +169 -30
  234. data/lib/git/repository/maintenance.rb +1 -1
  235. data/lib/git/repository/merging.rb +3 -3
  236. data/lib/git/repository/object_operations.rb +108 -14
  237. data/lib/git/repository/remote_operations.rb +150 -171
  238. data/lib/git/repository/shared_private.rb +7 -5
  239. data/lib/git/repository/staging.rb +36 -12
  240. data/lib/git/repository/stashing.rb +16 -16
  241. data/lib/git/repository/status_operations.rb +1 -1
  242. data/lib/git/repository/worktree_operations.rb +1 -1
  243. data/lib/git/repository.rb +123 -63
  244. data/lib/git/stash.rb +16 -21
  245. data/lib/git/stash_info.rb +4 -4
  246. data/lib/git/stashes.rb +20 -26
  247. data/lib/git/status.rb +21 -7
  248. data/lib/git/tag_delete_failure.rb +3 -0
  249. data/lib/git/tag_delete_result.rb +6 -3
  250. data/lib/git/tag_info.rb +1 -0
  251. data/lib/git/url.rb +12 -5
  252. data/lib/git/version.rb +6 -4
  253. data/lib/git/version_constraint.rb +4 -0
  254. data/lib/git.rb +398 -314
  255. data/redesign/3_architecture_implementation.md +59 -29
  256. data/redesign/Phase 4 - Step B.md +921 -0
  257. data/redesign/Phase 4 - Step C.md +833 -0
  258. data/redesign/c1a-public-api-scope.tsv +256 -0
  259. data/redesign/phase-4-step-b-test-audit.tsv +485 -0
  260. data/tasks/yard.rake +24 -17
  261. metadata +24 -57
  262. data/commitlint.test +0 -4
  263. data/lib/git/args_builder.rb +0 -103
  264. data/lib/git/command_line_result.rb +0 -8
  265. data/lib/git/deprecation.rb +0 -9
  266. data/tasks/test.rake +0 -25
  267. /data/.github/{skills → skills-deprecated}/extract-facade-from-base-lib/KEYWORD_ARG_REMEDIATION.md +0 -0
  268. /data/.github/{skills → skills-deprecated}/review-backward-compatibility/SKILL.md +0 -0
data/CONTRIBUTING.md CHANGED
@@ -20,8 +20,10 @@
20
20
  - [Before requesting review](#before-requesting-review)
21
21
  - [Branch strategy](#branch-strategy)
22
22
  - [AI-assisted contributions](#ai-assisted-contributions)
23
+ - [Agent skills](#agent-skills)
23
24
  - [Design philosophy](#design-philosophy)
24
- - [Command-layer neutrality](#command-layer-neutrality)
25
+ - [Layered architecture](#layered-architecture)
26
+ - [Command layer responsibilities](#command-layer-responsibilities)
25
27
  - [Wrapping a git command](#wrapping-a-git-command)
26
28
  - [Method placement](#method-placement)
27
29
  - [Method naming](#method-naming)
@@ -221,6 +223,37 @@ and "CI is green" are not substitutes for the submitter running
221
223
  [the local validation step](#contributor-validation-policy) themselves; CI is a
222
224
  backstop, not a primary validation surface.
223
225
 
226
+ ### Agent skills
227
+
228
+ If you use an AI coding agent that understands repository skills, the
229
+ [`.github/skills/`](.github/skills/) directory contains optional, project-specific
230
+ guidance that mirrors maintainer expectations:
231
+
232
+ - [`project-context`](.github/skills/project-context/SKILL.md) — architecture, coding
233
+ standards, design philosophy, and compatibility requirements
234
+ - [`development-workflow`](.github/skills/development-workflow/SKILL.md) — TDD workflow
235
+ for bug fixes, features, refactoring, and maintenance tasks
236
+ - [`command-implementation`](.github/skills/command-implementation/SKILL.md) and
237
+ [`facade-implementation`](.github/skills/facade-implementation/SKILL.md) — guidance for
238
+ adding or updating command classes and `Git::Repository` facade methods
239
+ - [`review-arguments-dsl`](.github/skills/review-arguments-dsl/SKILL.md) — audits
240
+ `arguments do ... end` blocks against the git CLI
241
+ - [`rspec-unit-testing-standards`](.github/skills/rspec-unit-testing-standards/SKILL.md),
242
+ [`command-test-conventions`](.github/skills/command-test-conventions/SKILL.md), and
243
+ [`facade-test-conventions`](.github/skills/facade-test-conventions/SKILL.md) — testing
244
+ conventions for new and updated code
245
+ - [`yard-documentation`](.github/skills/yard-documentation/SKILL.md),
246
+ [`command-yard-documentation`](.github/skills/command-yard-documentation/SKILL.md), and
247
+ [`facade-yard-documentation`](.github/skills/facade-yard-documentation/SKILL.md) —
248
+ documentation standards
249
+ - [`test-debugging`](.github/skills/test-debugging/SKILL.md) and
250
+ [`ci-cd-troubleshooting`](.github/skills/ci-cd-troubleshooting/SKILL.md) — help for
251
+ failing or flaky tests and CI failures
252
+ - [`breaking-change-analysis`](.github/skills/breaking-change-analysis/SKILL.md) — impact
253
+ analysis before removing methods, changing interfaces, or planning deprecations
254
+ - [`pr-readiness-review`](.github/skills/pr-readiness-review/SKILL.md) — final checks
255
+ before requesting review
256
+
224
257
  ## Design philosophy
225
258
 
226
259
  The `git` gem is designed as a lightweight wrapper around the `git` command-line
@@ -236,55 +269,62 @@ By following this philosophy, the `git` gem allows users to leverage their exist
236
269
  knowledge of Git while benefiting from the expressiveness and power of Ruby's syntax
237
270
  and paradigms.
238
271
 
239
- ## Command-layer neutrality
240
-
241
- Command classes (`Git::Commands::*`) are **faithful, neutral representations of the
242
- git CLI**. They declare every option via the DSL but never embed policy choices —
243
- output-control flags, editor suppression, progress output, verbose mode, etc.
244
- Policy decisions belong to the facade (`Git::Lib`), which sets safe defaults at
245
- each call site. Callers may override those defaults when they have a legitimate
246
- reason (e.g., running in a TTY-attached environment where an editor is desired).
247
-
248
- This principle serves two purposes: it keeps the command layer a reusable, unbiased
249
- interface to git, and it supports this gem's **non-interactive execution model** (git
250
- is never allowed to prompt for input, open an editor, or wait for TTY interaction
251
- by default). The execution layer provides an unconditional safety net regardless of
252
- what the caller passes.
272
+ ## Layered architecture
253
273
 
254
274
  The three architectural layers each play a distinct role:
255
275
 
256
276
  | Layer | Responsibility | Mechanism |
257
277
  | --- | --- | --- |
258
- | **Command** (`Git::Commands::*`) | Neutral git CLI interface | Declares options via DSL (e.g. `flag_option :edit, negatable: true`) no policy |
259
- | **Facade** (`Git::Lib`) | Safe defaults | Sets policy options at each call site (e.g. `edit: false`); callers may override |
260
- | **Execution** (`Git::CommandLine`) | Unconditional safety net | `GIT_EDITOR='true'` in every subprocess environment |
278
+ | **Facade** (`Git::Repository::*` and `Git` module) | Public API and policy | Normalizes Ruby arguments, sets safe defaults, calls one or more `Git::Commands::*` classes, and may parse output into public Ruby objects |
279
+ | **Command** (`Git::Commands::*`) | Neutral git CLI interface | Declares CLI arguments via the [Arguments DSL](lib/git/commands/arguments.rb), executes git, and returns `Git::CommandLine::Result` |
280
+ | **Execution** (`Git::ExecutionContext`) | Execution context and subprocess defaults | Carries repository/global execution settings such as working directory, environment, timeout, binary path, and logging; runs the git CLI with subprocess defaults such as `GIT_EDITOR='true'` |
261
281
 
262
- > **Anti-pattern:** `literal '--no-edit'`, `literal '--verbose'`,
263
- > `literal '--no-progress'` inside a command class — embeds policy in the wrong layer.
264
- >
265
- > **Correct pattern:** `flag_option :edit, negatable: true` in the command;
266
- > `edit: false` passed from the facade call site.
282
+ ### Command layer responsibilities
267
283
 
268
- ## Wrapping a git command
284
+ Command classes (`Git::Commands::*`) are **faithful, neutral representations of the
285
+ git CLI**. Each command class does the following:
286
+
287
+ - Declares every CLI argument/option via the [Arguments DSL](lib/git/commands/arguments.rb)
288
+ - Binds `#call` parameters with the [Arguments DSL](lib/git/commands/arguments.rb) to
289
+ build the git argv
290
+ - Executes a git CLI command via `Git::ExecutionContext`
291
+ - Returns the raw git CLI result as a `Git::CommandLine::Result` object
292
+
293
+ Command classes should not embed policy choices such as output-control flags, editor
294
+ suppression, progress output, or verbose mode. These policy decisions belong to the
295
+ `Git::Repository::*` facade methods, which set safe defaults at each call site when
296
+ policy is needed. In most cases, the facade gives callers the choice to override those
297
+ defaults when they have a legitimate reason (e.g., running in a TTY-attached
298
+ environment where an editor is desired).
299
+
300
+ For example:
301
+
302
+ - **Anti-pattern:** `literal '--no-edit'`, `literal '--verbose'`, or
303
+ `literal '--no-progress'` inside a command class — embeds policy in the wrong layer
304
+ - **Correct pattern:** `flag_option :edit, negatable: true` in the command; `edit:
305
+ false` passed from the facade call site
306
+
307
+ This separation keeps command classes reusable across facade methods with different
308
+ policy needs. For example, a facade method that parses command output may pass
309
+ options such as `no_color: true`, `z: true`, or a fixed `format:` value so git emits
310
+ a stable, parseable output shape. Those parser-contract options belong at the facade
311
+ call site, not as hard-coded literals in the command class. Other facade methods can
312
+ reuse the same command class with different options.
269
313
 
270
- > **Note:** This documentation reflects **Phase 2 (Strangler Fig)** of the architectural
271
- > redesign. It will be updated in **Phase 3** when `Git::Repository` becomes the primary
272
- > public API and `Git::Lib` is bypassed. Currently, `Git::Base` remains the public API
273
- > and `Git::Lib` acts as the delegation layer.
314
+ ## Wrapping a git command
274
315
 
275
316
  This section guides you through wrapping a git command. The first subsections focus
276
317
  on **API design**: where methods belong, how to name them, and how to handle
277
318
  parameters and output. These describe the public interface that gem users will see.
278
319
 
279
320
  [From design to implementation](#from-design-to-implementation) then shows how to
280
- structure your code using the gem's three-layer architecture. Note that while we are
281
- transitioning to `Git::Repository`, the current public API is `Git::Base`, which
282
- delegates to `Git::Lib`, which in turn delegates to internal command classes.
321
+ structure your code using the gem's three-layer architecture. The public API is
322
+ `Git::Repository` (and the `Git` module), whose facade methods delegate directly to
323
+ internal `Git::Commands::*` classes.
283
324
 
284
- > **Note:** When adding new git command wrappers, **always use the new architecture**
325
+ > **Note:** When adding new git command wrappers, **always use the architecture**
285
326
  > described in "From design to implementation" with `Git::Commands::*` classes and
286
- > the Arguments DSL. The gem is being incrementally migrated from `Git::Lib` to this
287
- > pattern. Do not add new methods directly to `Git::Lib`.
327
+ > the [Arguments DSL](lib/git/commands/arguments.rb).
288
328
 
289
329
  ### Method placement
290
330
 
@@ -292,8 +332,8 @@ When implementing a git command, first determine what type of command it is. Thi
292
332
  determines where to implement it in the Ruby API:
293
333
 
294
334
  > **Note:** These placement guidelines define the **public API**. Always add public
295
- > methods to `Git` module or `Git::Base` (which acts as the current facade for
296
- > `Git::Repository`), even though the implementation will be in a `Git::Commands::*` class.
335
+ > methods to the `Git` module or `Git::Repository` (the facade), even though the
336
+ > implementation will be in a `Git::Commands::*` class.
297
337
 
298
338
  **Repository factory methods** are implemented on the `Git` module. Use these to
299
339
  obtain a repository object for subsequent operations:
@@ -305,7 +345,7 @@ repo = Git.open('.')
305
345
  ```
306
346
 
307
347
  **Repository-scoped commands** operate within a repository context. Implement these
308
- `Git::Base` instance methods:
348
+ `Git::Repository` instance methods:
309
349
 
310
350
  ```ruby
311
351
  repo.add('file.txt')
@@ -326,14 +366,14 @@ Some commands, like `git config`, can operate in multiple contexts:
326
366
  - **On the `Git` module**: A scope parameter (`global: true`, `system: true`) or
327
367
  `file:` parameter is required. The `local:` and `worktree:` options are not allowed
328
368
  since they require a repository.
329
- - **On a `Git::Base` instance**: The command defaults to the repository's local
369
+ - **On a `Git::Repository` instance**: The command defaults to the repository's local
330
370
  scope. The `worktree: true` option is also available.
331
371
 
332
372
  ### Method naming
333
373
 
334
374
  Each method corresponds directly to a `git` command. For example, the `git add`
335
- command is implemented as `Git::Base#add`, and the `git ls-files` command is
336
- implemented as `Git::Base#ls_files`.
375
+ command is implemented as `Git::Repository#add`, and the `git ls-files` command is
376
+ implemented as `Git::Repository#ls_files`.
337
377
 
338
378
  When a single Git command serves multiple distinct purposes, method names should use
339
379
  the git command name as a prefix, followed by a descriptive suffix indicating the
@@ -540,47 +580,21 @@ targeted parsing logic appropriate to each output format.
540
580
 
541
581
  ### From design to implementation
542
582
 
543
- > **Note:** **Use this architecture for all new commands.** The gem is being
544
- > incrementally migrated using the "Strangler Fig" pattern:
545
- >
546
- > 1. **Phase 1 (completed)**: Foundation work to introduce the new command architecture
547
- > and prepare the codebase for incremental migration.
548
- > 2. **Phase 2 (current)**: New `Git::Commands::*` classes are created, and `Git::Lib`
549
- > methods delegate to them. `Git::Lib` remains but becomes a thin wrapper.
550
- > 3. **Phase 3 (planned)**: Public API (`Git::Base`) will be refactored to use
551
- > `Git::Commands::*` directly, bypassing `Git::Lib`.
552
- > 4. **Phase 4 (planned)**: `Git::Lib` will be removed entirely.
553
- >
554
- > When adding new commands, create the `Git::Commands::*` class and have the
555
- > corresponding `Git::Lib` method delegate to it (see `Git::Lib#add` for an example).
556
- > When you encounter existing commands, you may optionally refactor them to this
557
- > pattern following the TDD workflow.
558
-
559
- The gem uses a three-layer architecture that separates the public API from internal
560
- implementation:
561
-
562
- 1. **Facade layer (`Git::Base` and `Git` module)** — The current public interface.
563
- Methods here are thin wrappers that delegate to `Git::Lib`, which in turn
564
- delegates to internal command classes.
565
-
566
- 2. **Command layer (`Git::Commands::*`)** — Internal classes that implement git
567
- commands. Each command class handles argument building and output parsing.
568
-
569
- 3. **Execution layer (`Git::ExecutionContext`)** — Runs raw git commands. Command
570
- classes use this to execute git and receive output.
571
-
572
- When wrapping a new git command:
583
+ The gem uses the three-layer architecture described in
584
+ [Layered architecture](#layered-architecture). When wrapping a git command, keep the
585
+ layer responsibilities separate:
573
586
 
574
587
  1. **Design the public API** using the guidelines in this section (placement, naming,
575
588
  parameters, output)
576
589
 
577
590
  2. **Create a command class** in `lib/git/commands/` that:
578
- - Accepts an `ExecutionContext` and any required arguments
579
- - Defines arguments using the Arguments DSL
580
- - Parses the output into Ruby objects
591
+ - Accepts a `Git::ExecutionContext` at initialization
592
+ - Defines arguments using the [Arguments DSL](lib/git/commands/arguments.rb)
593
+ - Returns a raw `Git::CommandLine::Result`
581
594
 
582
- 3. **Add the facade method** to `Git::Base` (or `Git` module) that delegates to
583
- `Git::Lib`.
595
+ 3. **Add the facade method** to `Git::Repository` (or the `Git` module) that applies
596
+ facade policy, calls the command class, and parses the raw result when returning
597
+ structured Ruby objects.
584
598
 
585
599
  Example structure for `git add`:
586
600
 
@@ -590,45 +604,59 @@ require 'git/commands/base'
590
604
 
591
605
  module Git
592
606
  module Commands
593
- class Add < Base
607
+ class Add < Git::Commands::Base
594
608
  arguments do
595
609
  literal 'add'
596
- flag_option :all
597
- flag_option :force
598
- operand :paths, repeatable: true, default: [], separator: '--'
610
+ flag_option %i[verbose v]
611
+ flag_option %i[force f]
612
+ # ...additional flag and value options elided for brevity...
613
+ end_of_options
614
+ operand :pathspec, repeatable: true
599
615
  end
600
616
 
601
- # Execute the git add command
617
+ # @!method call(*, **)
618
+ #
619
+ # @overload call(*pathspec, **options)
620
+ #
621
+ # Execute the `git add` command
622
+ #
623
+ # @param pathspec [Array<String>] files to be added to the repository
624
+ # (relative to the worktree root)
625
+ #
626
+ # @param options [Hash] command options
627
+ #
628
+ # @option options [Boolean, nil] :verbose (nil) be verbose
602
629
  #
603
- # @overload call(*paths, all: nil, force: nil)
630
+ # Alias: :v
604
631
  #
605
- # @param paths [Array<String>] files to be added
632
+ # @option options [Boolean, nil] :force (nil) allow adding otherwise ignored
633
+ # files
606
634
  #
607
- # @param all [Boolean] Add, modify, and remove index entries to match the worktree
635
+ # Alias: :f
608
636
  #
609
- # @param force [Boolean] Allow adding otherwise ignored files
637
+ # @return [Git::CommandLine::Result] the result of calling `git add`
610
638
  #
611
- # @return [Git::CommandLineResult] the result of the command
639
+ # @raise [ArgumentError] if unsupported options are provided
612
640
  #
613
- def call(...) = super # rubocop:disable Lint/UselessMethodDefinition
641
+ # @raise [Git::FailedError] if git exits with a non-zero exit status
642
+ #
643
+ # @api public
614
644
  end
615
645
  end
616
646
  end
617
647
  ```
618
648
 
619
- **How `Base` works**: `Base` provides default `#initialize` (accepts an
620
- `execution_context`) and `#call` (binds arguments via the DSL, calls
621
- `execution_context.command`, validates exit status). Simple commands only need to
622
- declare `arguments do end` and write `def call(...) = super` (which also serves as
623
- the YARD documentation anchor for per-command documentation). That forwarding method
624
- looks "useless" to RuboCop, so we disable `Lint/UselessMethodDefinition` on it; the
625
- method body must exist for YARD to attach command-specific docs, even though all work
626
- is delegated to `Base#call`.
627
-
628
- **Method Signature Convention**: Most commands use `def call(...) = super`, which
629
- forwards all arguments to `Base#call`. `Base#call` binds them via the Arguments DSL,
630
- calls `@execution_context.command(*args, **args.execution_options,
631
- raise_on_failure: false)`, and validates the exit status.
649
+ **How `Git::Commands::Base` works**: `Base` provides a default `#initialize` (accepts
650
+ an `execution_context`) and `#call(*, **)` (binds arguments via the DSL, dispatches to
651
+ `@execution_context.command_capturing` or `@execution_context.command_streaming`
652
+ depending on whether an `out:` execution option is present, and validates the exit
653
+ status). Simple commands need only declare `arguments do end` and inherit
654
+ `Base#call(*, **)` unchanged. To attach command-specific YARD documentation to the
655
+ inherited `call`, use either a `# @!method call(*, **)` directive (when there is no
656
+ `def call` in the class) or place the YARD tags directly above an explicit
657
+ `def call(*, **); super; end` — both patterns produce identical runtime behavior.
658
+ Only add real logic to `def call` when the command needs custom behavior beyond what
659
+ `Base` provides.
632
660
 
633
661
  Override `call` explicitly in three situations:
634
662
 
@@ -644,7 +672,7 @@ Override `call` explicitly in three situations:
644
672
 
645
673
  When overriding, work with `args_definition.bind(...)` directly and delegate
646
674
  exit-status handling to the inherited `validate_exit_status!`. Extract bulk logic
647
- into private helpers to satisfy Rubocop `Metrics` thresholds:
675
+ into private helpers to satisfy RuboCop `Metrics` thresholds:
648
676
 
649
677
  ```ruby
650
678
  def call(*objects, **options)
@@ -657,21 +685,25 @@ end
657
685
  private
658
686
 
659
687
  def run_batch(bound, reader)
660
- result = @execution_context.command(*bound, in: reader, **bound.execution_options, raise_on_failure: false)
688
+ result = @execution_context.command_capturing(*bound, in: reader, **bound.execution_options, raise_on_failure: false)
661
689
  validate_exit_status!(result)
662
690
  result
663
691
  end
664
692
  ```
665
693
 
666
- Validation of supported options is handled by the `Arguments` DSL, which raises
667
- `ArgumentError` for unsupported keywords. The public API in `Git::Lib` handles the
668
- translation from single values or arrays to the splat format.
669
-
670
- > **YARD Documentation Note:** When using anonymous keyword forwarding (`**`), YARD
671
- > cannot infer the method signature. Use the `@overload` directive with **explicit
672
- > keyword parameters** (e.g., `@overload call(paths, all: nil, force: nil)`) and
673
- > document each keyword with its own `@param` tag. Do not use `@option` with
674
- > `@overload`. See the example above for the pattern.
694
+ Option validation happens at two layers: the `Git::Repository` facade method calls
695
+ `SharedPrivate.assert_valid_opts!` against its own documented public option list first,
696
+ then the [`Arguments` DSL](lib/git/commands/arguments.rb) raises `ArgumentError` for
697
+ any keyword the command class does not recognize during argument binding. The facade
698
+ also handles translation from single values or arrays to the splat format.
699
+
700
+ > **YARD Documentation Note:** Because `call` uses anonymous argument forwarding
701
+ > (`*, **`), YARD cannot infer its signature. Document it with an `@overload` that names
702
+ > the operands and an `**options` hash (e.g., `@overload call(*pathspec, **options)`),
703
+ > add a `@param options [Hash]` tag, and document each supported keyword with its own
704
+ > `@option` tag. When the class defines no `def call`, place these tags under a
705
+ > `# @!method call(*, **)` directive (as shown above); when it defines an explicit
706
+ > `def call` override, place them directly above that method instead.
675
707
  >
676
708
  > **Testing Requirement:** When defining arguments with the DSL, you must write RSpec
677
709
  > tests that verify each argument handles valid values correctly (booleans, strings,
@@ -680,61 +712,57 @@ translation from single values or arrays to the splat format.
680
712
  > `spec/unit/git/commands/add_spec.rb` for examples of comprehensive argument testing.
681
713
 
682
714
  ```ruby
683
- # lib/git/lib.rb (delegation)
684
- class Git::Lib
685
- # Git::Lib may accept an options hash for backward compatibility
686
- def add(paths = '.', options = {})
687
- # Convert to splat + keyword arguments when calling the command class
688
- Git::Commands::Add.new(self).call(*Array(paths), **options)
689
- end
690
- end
691
-
692
- # lib/git/base.rb (public facade)
693
- class Git::Base
694
- def add(paths = '.', **options)
695
- lib.add(paths, options)
715
+ # lib/git/repository/staging.rb (facade — a topic module included into Git::Repository)
716
+ module Git
717
+ class Repository
718
+ module Staging
719
+ ADD_ALLOWED_OPTS = %i[all force].freeze
720
+ private_constant :ADD_ALLOWED_OPTS
721
+
722
+ def add(paths = '.', **)
723
+ SharedPrivate.assert_valid_opts!(ADD_ALLOWED_OPTS, **)
724
+ Git::Commands::Add.new(@execution_context).call(*Array(paths), **).stdout
725
+ end
726
+ end
696
727
  end
697
728
  end
698
729
  ```
699
730
 
700
- For factory methods and non-repository commands, the pattern is similar but differs
701
- in how the `ExecutionContext` is obtained:
731
+ For factory methods and module-level commands, the pattern is the same but
732
+ `Git::ExecutionContext::Global` is used instead of the repository's `@execution_context`:
702
733
 
703
734
  ```ruby
704
- # Factory method (Git.clone) — creates context, runs command, returns repository
735
+ # Factory method (Git.clone) — creates a global context, runs the command, returns a repository
705
736
  module Git
706
- def self.clone(url, path = nil, **options)
707
- # logic to call Git::Commands::Clone via Git::Lib
708
- end
709
- end
710
-
711
- # Non-repository command (Git.global_config) — standalone context
712
- module Git
713
- def self.global_config(name, value = nil)
714
- Git::Lib.new.global_config(name, value)
737
+ def self.clone(repository_url, directory = nil, options = {})
738
+ context = Git::ExecutionContext::Global.new
739
+ Git::Commands::Clone.new(context).call(repository_url, directory, **options)
740
+ # ... then build and return a Git::Repository for the cloned working tree
715
741
  end
716
742
  end
717
743
  ```
718
744
 
719
- > **Note:** The `Git::Lib` class currently acts as the execution context. In the new
720
- > architecture, `Git::Lib` methods delegate to `Git::Commands::*` classes, passing `self`
721
- > (the `Git::Lib` instance) as the execution context.
745
+ > **Note:** `Git::Repository` facade methods pass `@execution_context` (a
746
+ > `Git::ExecutionContext::Repository`) to each command class they invoke. Module-level
747
+ > methods such as `Git.clone` construct a `Git::ExecutionContext::Global` instead.
722
748
 
723
749
  ### Example implementations
724
750
 
725
- The following command classes demonstrate patterns for implementing new commands.
751
+ The following command classes demonstrate implementation patterns.
726
752
  See `lib/git/commands/` and `spec/unit/git/commands/` for the full implementations:
727
753
 
728
754
  - **Simple command**: `Git::Commands::Add` — straightforward argument building with
729
- the Arguments DSL
730
- - **Command with output parsing**: `Git::Commands::Fsck` — parses git output into
755
+ the [Arguments DSL](lib/git/commands/arguments.rb)
756
+ - **Command with parser-backed facade result**: `Git::Commands::Fsck` with
757
+ `Git::Parsers::Fsck` — returns raw command output that the facade parses into
731
758
  structured Ruby objects
732
- - **Factory command**: `Git::Commands::Clone` — returns data for creating a
733
- repository object
734
- - **Multiple outputs**: `Git::Commands::Diff::*` subclasses for different output
735
- formats (planned)
736
- - **Multi-context**: `Git::Commands::Config` handles both module and instance
737
- variants (planned)
759
+ - **Factory command**: `Git::Commands::Clone` — used by `Git.clone`; returns a
760
+ `Git::CommandLine::Result` like all command classes (the factory method then builds
761
+ and returns a `Git::Repository` from the cloned working tree)
762
+ - **Multiple output modes**: `Git::Commands::Diff` — declares output-mode options that
763
+ facade methods choose from when building different Ruby-facing results
764
+ - **Multi-context command family**: `Git::Commands::ConfigOptionSyntax::*` — command
765
+ classes shared by module-level and repository-scoped config methods
738
766
 
739
767
  ## Coding standards
740
768
 
@@ -773,7 +801,7 @@ and skip the hook, and commit-message validation will only run in CI.
773
801
 
774
802
  #### What to know about Conventional Commits
775
803
 
776
- The simplist conventional commit is in the form `type: description` where `type`
804
+ The simplest conventional commit is in the form `type: description` where `type`
777
805
  indicates the type of change and `description` is your usual commit message (with
778
806
  some limitations).
779
807
 
@@ -784,11 +812,11 @@ some limitations).
784
812
 
785
813
  Examples of valid commits:
786
814
 
787
- - `feat: add the --merges option to Git::Lib.log`
788
- - `fix: exception thrown by Git::Lib.log when repo has no commits`
815
+ - `feat: add the --merges option to Git::Repository#log`
816
+ - `fix: exception thrown by Git::Repository#log when repo has no commits`
789
817
  - `docs: add conventional commit announcement to README.md`
790
818
 
791
- Commits that include breaking changes must include an exclaimation mark before the
819
+ Commits that include breaking changes must include an exclamation mark before the
792
820
  colon:
793
821
 
794
822
  - `feat!: removed Git::Repository#commit_force`
@@ -816,8 +844,8 @@ The full conventional commit format is:
816
844
  should describe the nature of the backward incompatibility.
817
845
 
818
846
  Use of the `BREAKING CHANGE:` footer flags a backward incompatible change even if it
819
- is not flagged with an exclaimation mark after the `type`. Other footers are allowed
820
- by not acted upon.
847
+ is not flagged with an exclamation mark after the `type`. Other footers are allowed
848
+ but not acted upon.
821
849
 
822
850
  See [the Conventional Commits
823
851
  specification](https://www.conventionalcommits.org/en/v1.0.0/) for more details.
@@ -862,11 +890,7 @@ process.stdin.on('end', () =>
862
890
  - The entire test suite must pass when `bundle exec rake default` is run from the
863
891
  project's local working copy.
864
892
 
865
- This project uses two test frameworks:
866
-
867
- - **RSpec** (`spec/`) - **Primary framework for all new tests.**
868
- - **Test::Unit** (`tests/units/`) - **Legacy test suite.** Maintained for existing
869
- coverage but should not be extended for new features unless absolutely necessary.
893
+ This project uses **RSpec** (`spec/`) as its sole test framework.
870
894
 
871
895
  #### RSpec best practices
872
896
 
@@ -919,13 +943,7 @@ correctly parses git's branch list format, not that git can create branches.
919
943
  While working on specific features, you can run tests using:
920
944
 
921
945
  ```bash
922
- # Run all tests (TestUnit + RSpec):
923
- $ bundle exec rake test_all
924
-
925
- # Run only TestUnit integration tests:
926
- $ bundle exec rake test
927
-
928
- # Run only RSpec tests (unit + integration):
946
+ # Run all RSpec tests (unit + integration):
929
947
  $ bundle exec rake spec
930
948
 
931
949
  # Run only RSpec unit tests:
@@ -934,17 +952,11 @@ $ bundle exec rake spec:unit
934
952
  # Run only RSpec integration tests:
935
953
  $ bundle exec rake spec:integration
936
954
 
937
- # Run a single TestUnit file (from tests/units):
938
- $ bin/test test_object
939
-
940
- # Run multiple TestUnit files:
941
- $ bin/test test_object test_archive
942
-
943
955
  # Run a specific RSpec file:
944
956
  $ bundle exec rspec spec/unit/git/commands/add_spec.rb
945
957
 
946
- # Run TestUnit tests with a different version of the git command line:
947
- $ GIT_PATH=/Users/james/Downloads/git-2.30.2/bin-wrappers bin/test
958
+ # Run tests with a different version of the git command line:
959
+ $ GIT_PATH=/Users/james/Downloads/git-2.30.2/bin-wrappers bundle exec rake spec
948
960
  ```
949
961
 
950
962
  New and updated public-facing features should be documented in the project's
@@ -1011,7 +1023,7 @@ assert_equal(Git::Version.new(2, 30, 2), Git.git_version)
1011
1023
  Tests can be run using the newly built Git version as follows:
1012
1024
 
1013
1025
  ```shell
1014
- GIT_PATH=/Users/james/Downloads/git-2.30.2/bin-wrappers bin/test
1026
+ GIT_PATH=/Users/james/Downloads/git-2.30.2/bin-wrappers bundle exec rake spec
1015
1027
  ```
1016
1028
 
1017
1029
  Note: `GIT_PATH` refers to the directory containing the `git` executable.
data/Gemfile CHANGED
@@ -3,3 +3,20 @@
3
3
  source 'https://rubygems.org'
4
4
 
5
5
  gemspec name: 'git'
6
+
7
+ # Temporarily source yard-lint from a fork branch that adds the
8
+ # Tags/TagSeparator validator (enabled in .yard-lint.yml). Revert to the
9
+ # released gem once the validator is merged upstream and published.
10
+ # See: https://github.com/mensfeld/yard-lint
11
+ #
12
+ # Pinned to a specific commit (rather than the branch name) so installs stay
13
+ # deterministic while the branch continues to move. Bump this ref
14
+ # intentionally: https://github.com/jcouball/yard-lint/tree/feature/tag-separator
15
+ #
16
+ # Scope this to the same runtimes as the gemspec's yard-lint dependency: it is
17
+ # excluded on JRuby (RUBY_PLATFORM == 'java') and TruffleRuby, and requires
18
+ # Ruby >= 3.3.
19
+ if !(RUBY_PLATFORM == 'java' || RUBY_ENGINE == 'truffleruby') &&
20
+ Gem::Version.new(RUBY_VERSION) >= Gem::Version.new('3.3.0')
21
+ gem 'yard-lint', git: 'https://github.com/jcouball/yard-lint', ref: 'ef71742f2c88e2c296a20ab25fb4f25a4a384374'
22
+ end