git 4.3.2 → 5.0.0.beta.1

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 (280) hide show
  1. checksums.yaml +4 -4
  2. data/.github/copilot-instructions.md +67 -2705
  3. data/.github/pull_request_template.md +3 -1
  4. data/.github/skills/breaking-change-analysis/SKILL.md +102 -0
  5. data/.github/skills/ci-cd-troubleshooting/SKILL.md +264 -0
  6. data/.github/skills/command-implementation/REFERENCE.md +993 -0
  7. data/.github/skills/command-implementation/SKILL.md +229 -0
  8. data/.github/skills/command-test-conventions/SKILL.md +660 -0
  9. data/.github/skills/command-yard-documentation/SKILL.md +426 -0
  10. data/.github/skills/dependency-management/SKILL.md +72 -0
  11. data/.github/skills/development-workflow/SKILL.md +506 -0
  12. data/.github/skills/extract-command-from-lib/SKILL.md +487 -0
  13. data/.github/skills/extract-facade-from-base-lib/SKILL.md +586 -0
  14. data/.github/skills/facade-implementation/REFERENCE.md +840 -0
  15. data/.github/skills/facade-implementation/SKILL.md +260 -0
  16. data/.github/skills/facade-test-conventions/SKILL.md +380 -0
  17. data/.github/skills/facade-yard-documentation/SKILL.md +429 -0
  18. data/.github/skills/make-skill-template/SKILL.md +176 -0
  19. data/.github/skills/pr-readiness-review/SKILL.md +185 -0
  20. data/.github/skills/project-context/SKILL.md +313 -0
  21. data/.github/skills/pull-request-review/SKILL.md +168 -0
  22. data/.github/skills/refactor-command-to-commandlineresult/SKILL.md +131 -0
  23. data/.github/skills/release-management/SKILL.md +125 -0
  24. data/.github/skills/review-arguments-dsl/CHECKLIST.md +788 -0
  25. data/.github/skills/review-arguments-dsl/SKILL.md +214 -0
  26. data/.github/skills/review-backward-compatibility/SKILL.md +275 -0
  27. data/.github/skills/review-cross-command-consistency/SKILL.md +139 -0
  28. data/.github/skills/reviewing-skills/SKILL.md +189 -0
  29. data/.github/skills/rspec-unit-testing-standards/SKILL.md +639 -0
  30. data/.github/skills/tdd-refactor-step/SKILL.md +236 -0
  31. data/.github/skills/test-debugging/SKILL.md +160 -0
  32. data/.github/skills/yard-documentation/SKILL.md +793 -0
  33. data/.github/workflows/continuous_integration.yml +3 -2
  34. data/.github/workflows/enforce_conventional_commits.yml +1 -1
  35. data/.github/workflows/experimental_continuous_integration.yml +2 -2
  36. data/.github/workflows/release.yml +3 -4
  37. data/.gitignore +8 -0
  38. data/.husky/pre-commit +13 -0
  39. data/.release-please-manifest.json +1 -1
  40. data/.rspec +3 -0
  41. data/.rubocop.yml +7 -3
  42. data/.rubocop_todo.yml +23 -5
  43. data/.yardopts +1 -0
  44. data/CHANGELOG.md +0 -40
  45. data/CONTRIBUTING.md +694 -53
  46. data/README.md +17 -5
  47. data/Rakefile +61 -9
  48. data/commitlint.test +4 -0
  49. data/git.gemspec +14 -8
  50. data/lib/git/args_builder.rb +0 -8
  51. data/lib/git/base.rb +486 -410
  52. data/lib/git/branch.rb +380 -43
  53. data/lib/git/branch_delete_failure.rb +31 -0
  54. data/lib/git/branch_delete_result.rb +63 -0
  55. data/lib/git/branch_info.rb +178 -0
  56. data/lib/git/branches.rb +130 -24
  57. data/lib/git/command_line/base.rb +245 -0
  58. data/lib/git/command_line/capturing.rb +249 -0
  59. data/lib/git/command_line/result.rb +96 -0
  60. data/lib/git/command_line/streaming.rb +194 -0
  61. data/lib/git/command_line.rb +43 -322
  62. data/lib/git/command_line_result.rb +4 -88
  63. data/lib/git/commands/add.rb +131 -0
  64. data/lib/git/commands/am/abort.rb +43 -0
  65. data/lib/git/commands/am/apply.rb +252 -0
  66. data/lib/git/commands/am/continue.rb +43 -0
  67. data/lib/git/commands/am/quit.rb +43 -0
  68. data/lib/git/commands/am/retry.rb +47 -0
  69. data/lib/git/commands/am/show_current_patch.rb +64 -0
  70. data/lib/git/commands/am/skip.rb +42 -0
  71. data/lib/git/commands/am.rb +33 -0
  72. data/lib/git/commands/apply.rb +237 -0
  73. data/lib/git/commands/archive/list_formats.rb +46 -0
  74. data/lib/git/commands/archive.rb +140 -0
  75. data/lib/git/commands/arguments.rb +3510 -0
  76. data/lib/git/commands/base.rb +403 -0
  77. data/lib/git/commands/branch/copy.rb +94 -0
  78. data/lib/git/commands/branch/create.rb +173 -0
  79. data/lib/git/commands/branch/delete.rb +80 -0
  80. data/lib/git/commands/branch/list.rb +162 -0
  81. data/lib/git/commands/branch/move.rb +94 -0
  82. data/lib/git/commands/branch/set_upstream.rb +86 -0
  83. data/lib/git/commands/branch/show_current.rb +49 -0
  84. data/lib/git/commands/branch/unset_upstream.rb +57 -0
  85. data/lib/git/commands/branch.rb +34 -0
  86. data/lib/git/commands/cat_file/batch.rb +364 -0
  87. data/lib/git/commands/cat_file/filtered.rb +105 -0
  88. data/lib/git/commands/cat_file/raw.rb +210 -0
  89. data/lib/git/commands/cat_file.rb +49 -0
  90. data/lib/git/commands/checkout/branch.rb +151 -0
  91. data/lib/git/commands/checkout/files.rb +115 -0
  92. data/lib/git/commands/checkout.rb +38 -0
  93. data/lib/git/commands/checkout_index.rb +105 -0
  94. data/lib/git/commands/clean.rb +100 -0
  95. data/lib/git/commands/clone.rb +240 -0
  96. data/lib/git/commands/commit.rb +272 -0
  97. data/lib/git/commands/commit_tree.rb +100 -0
  98. data/lib/git/commands/config_option_syntax/add.rb +83 -0
  99. data/lib/git/commands/config_option_syntax/get.rb +117 -0
  100. data/lib/git/commands/config_option_syntax/get_all.rb +115 -0
  101. data/lib/git/commands/config_option_syntax/get_color.rb +91 -0
  102. data/lib/git/commands/config_option_syntax/get_color_bool.rb +93 -0
  103. data/lib/git/commands/config_option_syntax/get_regexp.rb +115 -0
  104. data/lib/git/commands/config_option_syntax/get_urlmatch.rb +102 -0
  105. data/lib/git/commands/config_option_syntax/list.rb +107 -0
  106. data/lib/git/commands/config_option_syntax/remove_section.rb +74 -0
  107. data/lib/git/commands/config_option_syntax/rename_section.rb +78 -0
  108. data/lib/git/commands/config_option_syntax/replace_all.rb +104 -0
  109. data/lib/git/commands/config_option_syntax/set.rb +114 -0
  110. data/lib/git/commands/config_option_syntax/unset.rb +89 -0
  111. data/lib/git/commands/config_option_syntax/unset_all.rb +89 -0
  112. data/lib/git/commands/config_option_syntax.rb +56 -0
  113. data/lib/git/commands/describe.rb +155 -0
  114. data/lib/git/commands/diff.rb +656 -0
  115. data/lib/git/commands/diff_files.rb +518 -0
  116. data/lib/git/commands/diff_index.rb +496 -0
  117. data/lib/git/commands/fetch.rb +352 -0
  118. data/lib/git/commands/fsck.rb +136 -0
  119. data/lib/git/commands/gc.rb +132 -0
  120. data/lib/git/commands/grep.rb +338 -0
  121. data/lib/git/commands/init.rb +99 -0
  122. data/lib/git/commands/log.rb +632 -0
  123. data/lib/git/commands/ls_files.rb +191 -0
  124. data/lib/git/commands/ls_remote.rb +155 -0
  125. data/lib/git/commands/ls_tree.rb +131 -0
  126. data/lib/git/commands/maintenance/register.rb +75 -0
  127. data/lib/git/commands/maintenance/run.rb +104 -0
  128. data/lib/git/commands/maintenance/start.rb +66 -0
  129. data/lib/git/commands/maintenance/stop.rb +55 -0
  130. data/lib/git/commands/maintenance/unregister.rb +79 -0
  131. data/lib/git/commands/maintenance.rb +31 -0
  132. data/lib/git/commands/merge/abort.rb +44 -0
  133. data/lib/git/commands/merge/continue.rb +44 -0
  134. data/lib/git/commands/merge/quit.rb +46 -0
  135. data/lib/git/commands/merge/start.rb +245 -0
  136. data/lib/git/commands/merge.rb +28 -0
  137. data/lib/git/commands/merge_base.rb +86 -0
  138. data/lib/git/commands/mv.rb +77 -0
  139. data/lib/git/commands/name_rev.rb +114 -0
  140. data/lib/git/commands/pull.rb +377 -0
  141. data/lib/git/commands/push.rb +246 -0
  142. data/lib/git/commands/read_tree.rb +149 -0
  143. data/lib/git/commands/remote/add.rb +91 -0
  144. data/lib/git/commands/remote/get_url.rb +66 -0
  145. data/lib/git/commands/remote/list.rb +54 -0
  146. data/lib/git/commands/remote/prune.rb +61 -0
  147. data/lib/git/commands/remote/remove.rb +52 -0
  148. data/lib/git/commands/remote/rename.rb +69 -0
  149. data/lib/git/commands/remote/set_branches.rb +63 -0
  150. data/lib/git/commands/remote/set_head.rb +82 -0
  151. data/lib/git/commands/remote/set_url.rb +71 -0
  152. data/lib/git/commands/remote/set_url_add.rb +61 -0
  153. data/lib/git/commands/remote/set_url_delete.rb +64 -0
  154. data/lib/git/commands/remote/show.rb +71 -0
  155. data/lib/git/commands/remote/update.rb +72 -0
  156. data/lib/git/commands/remote.rb +42 -0
  157. data/lib/git/commands/repack.rb +277 -0
  158. data/lib/git/commands/reset.rb +147 -0
  159. data/lib/git/commands/rev_parse.rb +297 -0
  160. data/lib/git/commands/revert/abort.rb +45 -0
  161. data/lib/git/commands/revert/continue.rb +57 -0
  162. data/lib/git/commands/revert/quit.rb +47 -0
  163. data/lib/git/commands/revert/skip.rb +44 -0
  164. data/lib/git/commands/revert/start.rb +153 -0
  165. data/lib/git/commands/revert.rb +29 -0
  166. data/lib/git/commands/rm.rb +114 -0
  167. data/lib/git/commands/show.rb +632 -0
  168. data/lib/git/commands/show_ref/exclude_existing.rb +120 -0
  169. data/lib/git/commands/show_ref/exists.rb +78 -0
  170. data/lib/git/commands/show_ref/list.rb +145 -0
  171. data/lib/git/commands/show_ref/verify.rb +120 -0
  172. data/lib/git/commands/show_ref.rb +42 -0
  173. data/lib/git/commands/stash/apply.rb +75 -0
  174. data/lib/git/commands/stash/branch.rb +65 -0
  175. data/lib/git/commands/stash/clear.rb +41 -0
  176. data/lib/git/commands/stash/create.rb +58 -0
  177. data/lib/git/commands/stash/drop.rb +67 -0
  178. data/lib/git/commands/stash/list.rb +39 -0
  179. data/lib/git/commands/stash/pop.rb +78 -0
  180. data/lib/git/commands/stash/push.rb +103 -0
  181. data/lib/git/commands/stash/show.rb +149 -0
  182. data/lib/git/commands/stash/store.rb +63 -0
  183. data/lib/git/commands/stash.rb +38 -0
  184. data/lib/git/commands/status.rb +169 -0
  185. data/lib/git/commands/symbolic_ref/delete.rb +68 -0
  186. data/lib/git/commands/symbolic_ref/read.rb +95 -0
  187. data/lib/git/commands/symbolic_ref/update.rb +76 -0
  188. data/lib/git/commands/symbolic_ref.rb +38 -0
  189. data/lib/git/commands/tag/create.rb +139 -0
  190. data/lib/git/commands/tag/delete.rb +55 -0
  191. data/lib/git/commands/tag/list.rb +143 -0
  192. data/lib/git/commands/tag/verify.rb +71 -0
  193. data/lib/git/commands/tag.rb +26 -0
  194. data/lib/git/commands/update_ref/batch.rb +140 -0
  195. data/lib/git/commands/update_ref/delete.rb +92 -0
  196. data/lib/git/commands/update_ref/update.rb +106 -0
  197. data/lib/git/commands/update_ref.rb +42 -0
  198. data/lib/git/commands/version.rb +52 -0
  199. data/lib/git/commands/worktree/add.rb +140 -0
  200. data/lib/git/commands/worktree/list.rb +64 -0
  201. data/lib/git/commands/worktree/lock.rb +58 -0
  202. data/lib/git/commands/worktree/management_base.rb +51 -0
  203. data/lib/git/commands/worktree/move.rb +66 -0
  204. data/lib/git/commands/worktree/prune.rb +67 -0
  205. data/lib/git/commands/worktree/remove.rb +63 -0
  206. data/lib/git/commands/worktree/repair.rb +76 -0
  207. data/lib/git/commands/worktree/unlock.rb +47 -0
  208. data/lib/git/commands/worktree.rb +43 -0
  209. data/lib/git/commands/write_tree.rb +68 -0
  210. data/lib/git/commands.rb +89 -0
  211. data/lib/git/detached_head_info.rb +54 -0
  212. data/lib/git/diff.rb +297 -7
  213. data/lib/git/diff_file_numstat_info.rb +29 -0
  214. data/lib/git/diff_file_patch_info.rb +134 -0
  215. data/lib/git/diff_file_raw_info.rb +127 -0
  216. data/lib/git/diff_info.rb +169 -0
  217. data/lib/git/diff_path_status.rb +78 -19
  218. data/lib/git/diff_result.rb +32 -0
  219. data/lib/git/diff_stats.rb +59 -14
  220. data/lib/git/dirstat_info.rb +86 -0
  221. data/lib/git/errors.rb +65 -2
  222. data/lib/git/execution_context/global.rb +56 -0
  223. data/lib/git/execution_context/repository.rb +147 -0
  224. data/lib/git/execution_context.rb +482 -0
  225. data/lib/git/file_ref.rb +74 -0
  226. data/lib/git/fsck_object.rb +9 -9
  227. data/lib/git/fsck_result.rb +1 -1
  228. data/lib/git/lib.rb +1606 -1028
  229. data/lib/git/log.rb +15 -2
  230. data/lib/git/object.rb +92 -22
  231. data/lib/git/parsers/branch.rb +224 -0
  232. data/lib/git/parsers/cat_file.rb +111 -0
  233. data/lib/git/parsers/diff.rb +585 -0
  234. data/lib/git/parsers/fsck.rb +133 -0
  235. data/lib/git/parsers/grep.rb +42 -0
  236. data/lib/git/parsers/ls_tree.rb +58 -0
  237. data/lib/git/parsers/stash.rb +208 -0
  238. data/lib/git/parsers/tag.rb +257 -0
  239. data/lib/git/remote.rb +133 -9
  240. data/lib/git/repository/branching.rb +572 -0
  241. data/lib/git/repository/committing.rb +191 -0
  242. data/lib/git/repository/configuring.rb +156 -0
  243. data/lib/git/repository/diffing.rb +775 -0
  244. data/lib/git/repository/inspecting.rb +153 -0
  245. data/lib/git/repository/logging.rb +247 -0
  246. data/lib/git/repository/merging.rb +295 -0
  247. data/lib/git/repository/object_operations.rb +1101 -0
  248. data/lib/git/repository/path_resolver.rb +207 -0
  249. data/lib/git/repository/remote_operations.rb +753 -0
  250. data/lib/git/repository/shared_private.rb +51 -0
  251. data/lib/git/repository/staging.rb +390 -0
  252. data/lib/git/repository/stashing.rb +107 -0
  253. data/lib/git/repository/status_operations.rb +180 -0
  254. data/lib/git/repository/worktree_operations.rb +159 -0
  255. data/lib/git/repository.rb +264 -1
  256. data/lib/git/stash.rb +85 -4
  257. data/lib/git/stash_info.rb +104 -0
  258. data/lib/git/stashes.rb +130 -13
  259. data/lib/git/status.rb +224 -18
  260. data/lib/git/tag_delete_failure.rb +31 -0
  261. data/lib/git/tag_delete_result.rb +63 -0
  262. data/lib/git/tag_info.rb +105 -0
  263. data/lib/git/version.rb +109 -2
  264. data/lib/git/version_constraint.rb +81 -0
  265. data/lib/git/worktree.rb +120 -5
  266. data/lib/git/worktrees.rb +107 -7
  267. data/lib/git.rb +114 -18
  268. data/redesign/1_architecture_existing.md +54 -18
  269. data/redesign/2_architecture_redesign.md +365 -46
  270. data/redesign/3_architecture_implementation.md +1451 -54
  271. data/tasks/gem_tasks.rake +4 -0
  272. data/tasks/npm_tasks.rake +7 -0
  273. data/tasks/rspec.rake +48 -0
  274. data/tasks/test.rake +13 -1
  275. data/tasks/yard.rake +34 -7
  276. metadata +349 -20
  277. data/lib/git/index.rb +0 -6
  278. data/lib/git/path.rb +0 -38
  279. data/lib/git/working_directory.rb +0 -6
  280. /data/{release-please-config.json → .release-please-config.json} +0 -0
@@ -1,6 +1,9 @@
1
1
  # Analysis of the Current Git Gem Architecture and Its Challenges
2
2
 
3
- This document provides an in-depth look at the current architecture of the `git` gem, outlining its primary components and the design challenges that have emerged over time. Understanding these challenges is the key motivation for the proposed architectural redesign.
3
+ This document provides an in-depth look at the current architecture of the `git` gem,
4
+ outlining its primary components and the design challenges that have emerged over
5
+ time. Understanding these challenges is the key motivation for the proposed
6
+ architectural redesign.
4
7
 
5
8
  - [1. Overview of the Current Architecture](#1-overview-of-the-current-architecture)
6
9
  - [2. Key Architectural Challenges](#2-key-architectural-challenges)
@@ -11,27 +14,43 @@ This document provides an in-depth look at the current architecture of the `git`
11
14
 
12
15
  ## 1. Overview of the Current Architecture
13
16
 
14
- The gem's current design is centered around three main classes: `Git`, `Git::Base`, and `Git::Lib`.
17
+ The gem's current design is centered around three main classes: `Git`, `Git::Base`,
18
+ and `Git::Lib`.
15
19
 
16
- - **`Git` (Top-Level Module)**: This module serves as the primary public entry point for creating repository objects. It contains class-level factory methods like `Git.open`, `Git.clone`, and `Git.init`. It also provides an interface for accessing global git configuration settings.
20
+ - **`Git` (Top-Level Module)**: This module serves as the primary public entry point
21
+ for creating repository objects. It contains class-level factory methods like
22
+ `Git.open`, `Git.clone`, and `Git.init`. It also provides an interface for
23
+ accessing global git configuration settings.
17
24
 
18
- **`Git::Base`**: This is the main object that users interact with after creating or opening a repository. It holds the high-level public API for most git operations (e.g., `g.commit`, `g.add`, `g.status`). It is responsible for managing the repository's state, such as the paths to the working directory and the `.git` directory.
25
+ - **`Git::Base`**: This is the main object that users interact with after creating or
26
+ opening a repository. It holds the high-level public API for most git operations
27
+ (e.g., `g.commit`, `g.add`, `g.status`). It is responsible for managing the
28
+ repository's state, such as the paths to the working directory and the `.git`
29
+ directory.
19
30
 
20
- **`Git::Lib`**: This class is intended to be the low-level wrapper around the `git` command-line tool. It contains the methods that build the specific command-line arguments and execute the `git` binary. In practice, it also contains a significant amount of logic for parsing the output of these commands.
31
+ - **`Git::Lib`**: This class is intended to be the low-level wrapper around the `git`
32
+ command-line tool. It contains the methods that build the specific command-line
33
+ arguments and execute the `git` binary. In practice, it also contains a significant
34
+ amount of logic for parsing the output of these commands.
21
35
 
22
36
  ## 2. Key Architectural Challenges
23
37
 
24
- While this structure has been functional, several significant design challenges make the codebase difficult to maintain, test, and evolve.
38
+ While this structure has been functional, several significant design challenges make
39
+ the codebase difficult to maintain, test, and evolve.
25
40
 
26
41
  ### A. Unclear Separation of Concerns
27
42
 
28
- The responsibilities between Git::Base and Git::Lib are "muddy" and overlap significantly.
43
+ The responsibilities between Git::Base and Git::Lib are "muddy" and overlap
44
+ significantly.
29
45
 
30
46
  - `Git::Base` sometimes contains logic that feels like it should be lower-level.
31
47
 
32
- - `Git::Lib`, which should ideally only be concerned with command execution, is filled with high-level logic for parsing command output into specific Ruby objects (e.g., parsing log output, diff stats, and branch lists).
48
+ - `Git::Lib`, which should ideally only be concerned with command execution, is
49
+ filled with high-level logic for parsing command output into specific Ruby objects
50
+ (e.g., parsing log output, diff stats, and branch lists).
33
51
 
34
- This blending of responsibilities makes it hard to determine where a specific piece of logic should reside, leading to an inconsistent and confusing internal structure.
52
+ This blending of responsibilities makes it hard to determine where a specific piece
53
+ of logic should reside, leading to an inconsistent and confusing internal structure.
35
54
 
36
55
  ### B. Circular Dependency
37
56
 
@@ -39,28 +58,45 @@ This is the most critical architectural flaw in the current design.
39
58
 
40
59
  - A `Git::Base` instance is created.
41
60
 
42
- - The first time a command is run, `Git::Base` lazily initializes a `Git::Lib` instance via its `.lib` accessor method.
61
+ - The first time a command is run, `Git::Base` lazily initializes a `Git::Lib`
62
+ instance via its `.lib` accessor method.
43
63
 
44
- - The `Git::Lib` constructor is passed the `Git::Base` instance (`self`) so that it can read the repository's path configuration back from the object that is creating it.
64
+ - The `Git::Lib` constructor is passed the `Git::Base` instance (`self`) so that it
65
+ can read the repository's path configuration back from the object that is creating
66
+ it.
45
67
 
46
- This creates a tight, circular coupling: `Git::Base` depends on `Git::Lib` to execute commands, but `Git::Lib` depends on `Git::Base` for its own configuration. This pattern makes the classes difficula to instantiate or test in isolation and creates a fragile system where changes in one class can have unexpected side effects in the other.
68
+ This creates a tight, circular coupling: `Git::Base` depends on `Git::Lib` to execute
69
+ commands, but `Git::Lib` depends on `Git::Base` for its own configuration. This
70
+ pattern makes the classes difficult to instantiate or test in isolation and creates a
71
+ fragile system where changes in one class can have unexpected side effects in the
72
+ other.
47
73
 
48
74
  ### C. Undefined Public API Boundary
49
75
 
50
- The gem lacks a formally defined public interface. Because `Git::Base` exposes its internal `Git::Lib` instance via the public `g.lib` accessor, many users have come to rely on `Git::Lib` and its methods as if they were part of the public API.
76
+ The gem lacks a formally defined public interface. Because `Git::Base` exposes its
77
+ internal `Git::Lib` instance via the public `g.lib` accessor, many users have come to
78
+ rely on `Git::Lib` and its methods as if they were part of the public API.
51
79
 
52
80
  This has two negative consequences:
53
81
 
54
- 1. It prevents the gem's maintainers from refactoring or changing the internal implementation of `Git::Lib` without causing breaking changes for users.
82
+ 1. It prevents the gem's maintainers from refactoring or changing the internal
83
+ implementation of `Git::Lib` without causing breaking changes for users.
55
84
 
56
- 2. It exposes complex, internal methods to users, creating a confusing and inconsistent user experience.
85
+ 2. It exposes complex, internal methods to users, creating a confusing and
86
+ inconsistent user experience.
57
87
 
58
88
  ### D. Slow and Brittle Test Suite
59
89
 
60
90
  The current testing strategy, built on `TestUnit`, suffers from two major issues:
61
91
 
62
- - **Over-reliance on Fixtures**: Most tests depend on having a complete, physical git repository on the filesystem to run against. Managing these fixtures is cumbersome.
92
+ - **Over-reliance on Fixtures**: Most tests depend on having a complete, physical git
93
+ repository on the filesystem to run against. Managing these fixtures is cumbersome.
63
94
 
64
- - **Excessive Shelling Out**: Because the logic for command execution and output parsing are tightly coupled, nearly every test must shell out to the actual `git` command-line tool.
95
+ - **Excessive Shelling Out**: Because the logic for command execution and output
96
+ parsing are tightly coupled, nearly every test must shell out to the actual `git`
97
+ command-line tool.
65
98
 
66
- This makes the test suite extremely slow, especially on non-UNIX platforms like Windows where process creation is more expensive. The slow feedback loop discourages frequent testing and makes development more difficult. The brittleness of filesystem-dependent tests also leads to flickering or unreliable test runs.
99
+ This makes the test suite extremely slow, especially on non-UNIX platforms like
100
+ Windows where process creation is more expensive. The slow feedback loop discourages
101
+ frequent testing and makes development more difficult. The brittleness of
102
+ filesystem-dependent tests also leads to flickering or unreliable test runs.
@@ -1,6 +1,9 @@
1
1
  # Proposed Redesigned Architecture for the Git Gem
2
2
 
3
- This document outlines a proposal for a major redesign of the git gem, targeted for version 5.0.0. The goal of this redesign is to modernize the gem's architecture, making it more robust, maintainable, testable, and easier for new contributors to understand.
3
+ This document outlines a proposal for a major redesign of the git gem, targeted for
4
+ version 5.0.0. The goal of this redesign is to modernize the gem's architecture,
5
+ making it more robust, maintainable, testable, and easier for new contributors to
6
+ understand.
4
7
 
5
8
  - [1. Motivation](#1-motivation)
6
9
  - [2. The New Architecture: A Three-Layered Approach](#2-the-new-architecture-a-three-layered-approach)
@@ -8,57 +11,310 @@ This document outlines a proposal for a major redesign of the git gem, targeted
8
11
  - [A. Clear Public vs. Private API](#a-clear-public-vs-private-api)
9
12
  - [B. Dependency Injection](#b-dependency-injection)
10
13
  - [C. Immutable Return Values](#c-immutable-return-values)
11
- - [D. Clear Naming for Path Objects](#d-clear-naming-for-path-objects)
14
+ - [D. Eliminate Custom Path Classes](#d-eliminate-custom-path-classes)
12
15
  - [4. Testing Strategy Overhaul](#4-testing-strategy-overhaul)
13
16
  - [5. Impact on Users: Breaking Changes for v5.0.0](#5-impact-on-users-breaking-changes-for-v500)
14
17
 
15
18
  ## 1. Motivation
16
19
 
17
- The current architecture, while functional, has several design issues that have accrued over time, making it difficult to extend and maintain.
20
+ The current architecture, while functional, has several design issues that have
21
+ accrued over time, making it difficult to extend and maintain.
18
22
 
19
- - **Unclear Separation of Concerns**: The responsibilities of the `Git`, `Git::Base`, and `Git::Lib` classes are "muddy." `Git::Base` acts as both a high-level API and a factory, while `Git::Lib` contains a mix of low-level command execution and high-level output parsing.
23
+ - **Unclear Separation of Concerns**: The responsibilities of the `Git`, `Git::Base`,
24
+ and `Git::Lib` classes are "muddy." `Git::Base` acts as both a high-level API and a
25
+ factory, while `Git::Lib` contains a mix of low-level command execution and
26
+ high-level output parsing.
20
27
 
21
- - **Circular Dependency**: A key architectural flaw is the circular dependency between `Git::Base` and `Git::Lib`. `Git::Base` creates and depends on `Git::Lib`, but `Git::Lib`'s constructor requires an instance of Git::Base to access configuration. This tight coupling makes the classes difficult to reason about and test in isolation.
28
+ - **Circular Dependency**: A key architectural flaw is the circular dependency
29
+ between `Git::Base` and `Git::Lib`. `Git::Base` creates and depends on `Git::Lib`,
30
+ but `Git::Lib`'s constructor requires an instance of Git::Base to access
31
+ configuration. This tight coupling makes the classes difficult to reason about and
32
+ test in isolation.
22
33
 
23
- - **Undefined Public API**: The boundary between the gem's public API and its internal implementation is not clearly defined. This has led some users to rely on internal classes like `Git::Lib`, making it difficult to refactor the internals without introducing breaking changes.
34
+ - **Undefined Public API**: The boundary between the gem's public API and its
35
+ internal implementation is not clearly defined. This has led some users to rely on
36
+ internal classes like `Git::Lib`, making it difficult to refactor the internals
37
+ without introducing breaking changes.
24
38
 
25
- - **Slow and Brittle Test Suite**: The current tests rely heavily on filesystem fixtures and shelling out to the git command line for almost every test case. This makes the test suite slow and difficult to maintain, especially on non-UNIX platforms.
39
+ - **Slow and Brittle Test Suite**: The current tests rely heavily on filesystem
40
+ fixtures and shelling out to the git command line for almost every test case. This
41
+ makes the test suite slow and difficult to maintain, especially on non-UNIX
42
+ platforms.
26
43
 
27
44
  ## 2. The New Architecture: A Three-Layered Approach
28
45
 
29
- The new design is built on a clear separation of concerns, dividing responsibilities into three distinct layers: a Facade, an Execution Context, and Command Objects.
46
+ The new design is built on a clear separation of concerns, dividing responsibilities
47
+ into three distinct layers: a Facade, an Execution Context, and Command Objects.
30
48
 
31
49
  1. The Facade Layer: Git::Repository
32
50
 
33
51
  This is the primary public interface that users will interact with.
34
52
 
35
- **Renaming**: `Git::Base` will be renamed to `Git::Repository`. This name is more descriptive and intuitive.
53
+ **Renaming**: `Git::Base` will be renamed to `Git::Repository`. This name is more
54
+ descriptive and intuitive.
36
55
 
37
- **Responsibility**: It will serve as a clean, high-level facade for all common git operations. Its methods will be simple, one-line calls that delegate the actual work to an appropriate command object.
56
+ **Responsibility**: The facade layer is responsible for:
38
57
 
39
- **Scalability**: To prevent this class from growing too large, its methods will be organized into logical modules (e.g., `Git::Repository::Branching`, `Git::Repository::History`) which are then included in the main class. This keeps the core class definition small and the features well-organized. These categories will be inspired by (but not slavishly follow) the git command line reference in [this page](https://git-scm.com/docs).
58
+ 1. **Managing the Execution Context**: Holding and providing access to the
59
+ configured execution context for command execution.
40
60
 
41
- 2. The Execution Layer: Git::ExecutionContext
61
+ 2. **Pre-processing Arguments**: Transforming user-provided arguments to fit the
62
+ command API (e.g., path expansion, Ruby-idiomatic defaults).
42
63
 
43
- This is the low-level, private engine for running commands.
64
+ 3. **Collecting Data**: Gathering additional information before or after command
65
+ execution that may be needed for building response objects.
44
66
 
45
- **Renaming**: `Git::Lib` will be renamed to `Git::ExecutionContext`.
67
+ 4. **Calling Commands**: Invoking one or more `Git::Commands::*` classes as
68
+ needed to fulfill the user's request.
69
+
70
+ 5. **Building Rich Response Objects**: Using Parser classes (e.g., `Git::Parsers::Diff`)
71
+ and Result class factory methods (e.g., `BranchDeleteResult.from(...)`) to
72
+ assemble meaningful return values from raw command output.
46
73
 
47
- **Responsibility**: Its sole purpose is to execute raw git commands. It will manage the repository's environment (working directory, .git path, logger) and use the existing `Git::CommandLine` class to interact with the system's git binary. It will have no knowledge of any specific git command's arguments or output.
74
+ **Facade as Orchestration Layer**: The facade layer acts as glue and orchestration
75
+ code. It coordinates the flow between components but contains minimal domain logic
76
+ itself. The actual implementation work is delegated to specialized components:
48
77
 
49
- 3. The Logic Layer: Git::Commands
78
+ - **Commands** handle argument building and execution, return `CommandLineResult`
79
+ - **Parsers** → transform stdout/stderr into structured data
80
+ - **Result classes** → assemble final objects via factory methods
50
81
 
51
- This is where all the command-specific implementation details will live.
82
+ This separation means the facade's role is to *wire things together* (which
83
+ component to call, in what order, with what inputs) rather than *implement
84
+ behavior* (how to parse output, which arguments are valid). This keeps each
85
+ component focused, independently testable, and reusable.
52
86
 
53
- **New Classes**: For each git operation, a new command class will be created within the `Git::Commands` namespace (e.g., `Git::Commands::Commit`, `Git::Commands::Diff`).
87
+ **Scalability**: To prevent this class from growing too large, its methods will
88
+ be organized into logical modules (e.g., `Git::Repository::Branching`,
89
+ `Git::Repository::History`) which are then included in the main class. This keeps
90
+ the core class definition small and the features well-organized. These categories
91
+ will be inspired by (but not slavishly follow) the git command line reference in
92
+ [this page](https://git-scm.com/docs).
93
+
94
+ 2. The Execution Layer: Git::ExecutionContext
95
+
96
+ This is the low-level, private engine for running commands.
54
97
 
55
- **Dual Responsibility**: Each command class will be responsible for:
98
+ **Renaming**: `Git::Lib` will be renamed to `Git::ExecutionContext`.
56
99
 
57
- 1. **Building Arguments**: Translating high-level Ruby options into the specific command-line flags and arguments that git expects.
100
+ **Responsibility**: Its purpose is to provide a configured `command` method for
101
+ executing git commands. This method wraps `Git::CommandLine` with essential
102
+ functionality including default options (normalize, chomp, timeout), option
103
+ validation, and a simplified interface that returns stdout. The execution context
104
+ has no knowledge of any specific git command's arguments or output.
105
+
106
+ **Two Context Types**: The execution layer will consist of an abstract base class
107
+ with two concrete implementations:
108
+
109
+ - **Git::ExecutionContext::Global**: For commands that do not require an existing repository
110
+ (`init`, `clone`, `config --global`, `version`). These commands execute in a
111
+ clean environment with no repository paths set. In the specific case of
112
+ `init`/`clone`, the command itself runs in `ExecutionContext::Global`, but on success it
113
+ yields a newly created `Git::Repository` instance backed by a
114
+ `Git::ExecutionContext::Repository`.
115
+
116
+ - **Git::ExecutionContext::Repository**: For repository-bound commands (`add`, `commit`,
117
+ `status`, `log`, etc.). Manages the repository environment (working directory,
118
+ .git path, index file) and provides the ability to override environment
119
+ variables per-command (e.g., unsetting `GIT_INDEX_FILE` for worktree
120
+ mutations).
121
+
122
+ The base `ExecutionContext` class provides the common `command` method that wraps
123
+ command execution with defaults, validation, and timeout handling. Subclasses
124
+ implement environment-specific configuration (paths, environment variables) to
125
+ create properly configured command execution contexts.
126
+
127
+ 3. The Command Layer: Git::Commands
128
+
129
+ This layer provides a structured interface to individual git commands.
130
+
131
+ **New Classes**: For each git operation, a new command class will be created
132
+ within the `Git::Commands` namespace (e.g., `Git::Commands::Commit`,
133
+ `Git::Commands::Diff`).
134
+
135
+ **Command Responsibilities**: Each command class is responsible for:
136
+
137
+ 1. **Defining the Git CLI API**: Using `Arguments.define` to declaratively
138
+ specify the command's accepted arguments. Command parameters should generally
139
+ match the underlying git command's interface to keep this layer thin and
140
+ transparent.
58
141
 
59
- 2. **Parsing Output**: Taking the raw string output from the ExecutionContext and converting it into rich, structured Ruby objects.
142
+ 2. **Binding Arguments**: Exposing bound arguments publicly via the `#bind`
143
+ method, allowing the facade layer to access argument values when needed for
144
+ orchestration or result building.
60
145
 
61
- **Handling Complexity**: For commands with multiple behaviors (like git diff), we can use specialized subclasses (e.g., Git::Commands::Diff::NameStatus, Git::Commands::Diff::Stats) to keep each class focused on a single responsibility.
146
+ 3. **Executing the Command**: Running the git command via the execution context
147
+ with any special setup/tweaking for expected results (e.g.,
148
+ `raise_on_failure: false` for partial failures), returning a
149
+ `Git::CommandLineResult`.
150
+
151
+ **Note**: Parsing output and building rich response objects is **not** a command
152
+ responsibility. That work belongs in separate Parser classes (e.g., `Git::Parsers::Diff`)
153
+ and Result class factory methods, orchestrated by the Facade layer. See
154
+ [issue #997](https://github.com/ruby-git/ruby-git/issues/997) for the work to
155
+ migrate existing commands that currently parse output.
156
+
157
+ **Commands::Base Pattern**: All command classes inherit from `Git::Commands::Base`.
158
+ Implemented in [issue #996](https://github.com/ruby-git/ruby-git/issues/996).
159
+
160
+ Simple commands declare `arguments do … end` and provide a YARD shim:
161
+
162
+ ```ruby
163
+ class Add < Base
164
+ arguments do
165
+ literal 'add'
166
+ flag_option :all
167
+ flag_option :force
168
+ operand :paths, repeatable: true, default: [], separator: '--'
169
+ end
170
+
171
+ # Execute the git add command
172
+ # ...YARD docs...
173
+ def call(...) = super # rubocop:disable Lint/UselessMethodDefinition
174
+ end
175
+ ```
176
+
177
+ Commands with non-zero successful exits declare their accepted range:
178
+
179
+ ```ruby
180
+ class Diff::Patch < Base
181
+ arguments do
182
+ literal 'diff'
183
+ literal '--patch'
184
+ # ...
185
+ end
186
+
187
+ # git diff exits 1 when differences are found (not an error)
188
+ allow_exit_status 0..1
189
+
190
+ def call(...) = super # rubocop:disable Lint/UselessMethodDefinition
191
+ end
192
+ ```
193
+
194
+ This pattern provides:
195
+
196
+ - Declarative argument definition via the class-level `arguments` DSL
197
+ - Behavioral inheritance from `Base` (`#initialize` and `#call`)
198
+ - Unified exit-status handling via `allow_exit_status <Range>` (default `0..0`)
199
+ - Per-command YARD documentation via `def call(...) = super` shim
200
+ - Automatic execution option forwarding (e.g., `timeout:`) via `Bound#execution_options`
201
+
202
+ **Method Return Values**:
203
+
204
+ - `#call` → Returns `Git::CommandLineResult` (stdout, stderr, status)
205
+ - `#args_definition` → Returns the frozen `Arguments` instance (class-level metadata)
206
+
207
+ **No Bind/Call Split**: Arguments are bound as a local variable inside `Base#call`.
208
+ Commands remain stateless beyond `@execution_context` — there is no separate `#bind`
209
+ method. If facade-layer access to bound argument values is needed in the future,
210
+ a `#bind` method can be added as a backward-compatible addition. See the design
211
+ rationale in [issue #996](https://github.com/ruby-git/ruby-git/issues/996).
212
+
213
+ **Migration Strategy: Git::Lib as Adapter Layer**
214
+
215
+ During the migration to this architecture, `Git::Lib` methods serve as **adapters**
216
+ between the legacy public interface and the new `Git::Commands::*` classes. This
217
+ separation ensures backward compatibility while enabling incremental migration:
218
+
219
+ - **Legacy Interface Acceptance**: `Git::Lib` methods continue to accept the
220
+ historical interface—positional arguments, deprecated options, and quirky
221
+ parameter names.
222
+
223
+ - **Interface Translation**: The adapter converts legacy patterns to the clean
224
+ `Git::Commands::*` API (e.g., positional `message` → `:message` keyword,
225
+ `:no_gpg_sign => true` → `:gpg_sign => false`).
226
+
227
+ - **Deprecation Handling**: Warnings about deprecated options are issued in the
228
+ adapter layer before delegating, ensuring users are informed even if they make
229
+ other errors.
230
+
231
+ - **Clean Command Classes**: `Git::Commands::*` classes remain free of legacy
232
+ baggage with a consistent, modern API.
233
+
234
+ - **Result Building**: The adapter layer is responsible for transforming
235
+ `CommandLineResult` into rich response objects using Parser classes and Result
236
+ factories. Commands return raw results; adapters build domain objects.
237
+
238
+ Once all commands are migrated and deprecation periods end, the adapter logic can
239
+ be simplified or moved to the new facade layer (`Git::Repository`).
240
+
241
+ **Arguments DSL**: To standardize argument building across commands, a declarative
242
+ `Git::Commands::Arguments` DSL is provided. This allows each command to define its
243
+ accepted arguments in a clear, self-documenting way:
244
+
245
+ ```ruby
246
+ ARGS = Git::Commands::Arguments.define do
247
+ flag_option :force # --force when true
248
+ flag_option :all # --all when true
249
+ value_option :branch # --branch <value>
250
+ value_option :config, repeatable: true # --config <v1> --config <v2>
251
+ flag_option :single_branch, negatable: true # --single-branch / --no-single-branch
252
+ custom_option(:depth) { |v| ['--depth', v.to_i] }
253
+ operand :paths, repeatable: true, separator: '--'
254
+ end
255
+ ```
256
+
257
+ The DSL supports several option types (`flag_option`, `value_option`, `flag_or_value_option`, `literal`,
258
+ `custom_option`, `execution_option`) and positional arguments, each with various modifiers. See
259
+ [Git::Commands::Arguments](../lib/git/commands/arguments.rb) for full documentation.
260
+
261
+ **Interface Convention**: With the `Base` pattern, all commands use
262
+ `def call(...) = super` as a YARD documentation shim. `Base#call` handles
263
+ argument binding and execution automatically; defaults defined in the DSL
264
+ (e.g., `operand :paths, default: []`) are applied during binding, so no manual
265
+ default checking is needed:
266
+
267
+ ```ruby
268
+ # All commands: YARD shim delegates to Base#call
269
+ def call(...) = super # rubocop:disable Lint/UselessMethodDefinition
270
+ ```
271
+
272
+ The facade layer (`Git::Base`, `Git::Lib`) handles translation from the public API
273
+ (which may accept single values or arrays) using `*Array(paths)`.
274
+
275
+ **Return Value Convention**: The `#call` method returns `Git::CommandLineResult`
276
+ by default. This is the standard return type for commands, containing stdout,
277
+ stderr, and status information.
278
+
279
+ **Important**: Rich objects (`StashInfo`, `BranchInfo`, `BranchDeleteResult`,
280
+ etc.) are built by the **Facade layer** (currently `Git::Lib`, eventually
281
+ `Git::Repository`), not by commands. The facade layer uses:
282
+
283
+ - **Parser classes** (e.g., `Git::Parsers::Diff`, `Git::Parsers::Stash`) to transform raw
284
+ stdout/stderr into structured data
285
+ - **Result class factory methods** (e.g., `BranchDeleteResult.from(...)`) to
286
+ assemble final objects from parsed data
287
+
288
+ This separation keeps commands focused on execution and the facade responsible
289
+ for building meaningful return values:
290
+
291
+ ```ruby
292
+ # Command layer: returns CommandLineResult
293
+ class Git::Commands::Stash::List < Base
294
+ arguments do
295
+ literal 'stash'
296
+ literal 'list'
297
+ # ...
298
+ end
299
+
300
+ def call(...) = super # rubocop:disable Lint/UselessMethodDefinition
301
+ end
302
+
303
+ # Facade layer: builds rich objects
304
+ def stashes
305
+ result = Git::Commands::Stash::List.new(self).call
306
+ StashListParser.parse(result.stdout).map { |info| Stash.new(self, info) }
307
+ end
308
+ ```
309
+
310
+ **Naming Convention for Return Types**: Use the `-Info` suffix for data objects
311
+ representing git entities (e.g., `TagInfo`, `BranchInfo`, `StashInfo`), and use
312
+ `Result` for operation outcomes (e.g., `FsckResult`, `TagDeleteResult`).
313
+
314
+ **Handling Complexity**: For commands with multiple behaviors (like git diff), we
315
+ can use specialized subclasses (e.g., Git::Commands::Diff::NameStatus,
316
+ Git::Commands::Diff::Stats) to keep each class focused on a single
317
+ responsibility.
62
318
 
63
319
  ## 3. Key Design Principles
64
320
 
@@ -66,65 +322,128 @@ The new architecture will be guided by the following modern design principles.
66
322
 
67
323
  ### A. Clear Public vs. Private API
68
324
 
69
- A primary goal of this redesign is to establish a crisp boundary between the public API and internal implementation details.
325
+ A primary goal of this redesign is to establish a crisp boundary between the public
326
+ API and internal implementation details.
70
327
 
71
- - **Public Interface**: The public API will consist of the `Git` module (for factories), the `Git::Repository` class, and the specialized data/query objects it returns (e.g., `Git::Log`, `Git::Status`, `Git::Object::Commit`).
328
+ - **Public Interface**: The public API will consist of the `Git` module (for
329
+ factories), the `Git::Repository` class, and the specialized data/query objects it
330
+ returns (e.g., `Git::Log`, `Git::Status`, `Git::Object::Commit`).
72
331
 
73
- - **Private Implementation**: All other components, including `Git::ExecutionContext` and all classes within the `Git::Commands` namespace, will be considered internal. They will be explicitly marked with the `@api private` YARD tag to discourage external use.
332
+ - **Private Implementation**: All other components, including `Git::ExecutionContext`
333
+ and all classes within the `Git::Commands` namespace, will be considered internal.
334
+ They will be explicitly marked with the `@api private` YARD tag to discourage
335
+ external use.
74
336
 
75
337
  ### B. Dependency Injection
76
338
 
77
- The circular dependency will be resolved by implementing a clear, one-way dependency flow.
339
+ The circular dependency will be resolved by implementing a clear, one-way dependency
340
+ flow.
78
341
 
79
- 1. The factory methods (`Git.open`, `Git.clone`) will create and configure an instance of `Git::ExecutionContext`.
342
+ 1. The factory methods (`Git.open`, `Git.clone`) will create and configure an
343
+ instance of the appropriate `Git::ExecutionContext` subclass (`Git::ExecutionContext::Global`
344
+ for `init`/`clone`, `Git::ExecutionContext::Repository` for `open`/`bare`).
80
345
 
81
- 2. This `ExecutionContext` instance will then be injected into the constructor of the `Git::Repository` object.
346
+ 2. This context instance will then be wired into the system in two ways:
347
+ - For commands that run before a repository exists (e.g., `Git::Commands::Init`,
348
+ `Git::Commands::Clone`), the context will be passed directly into the
349
+ constructor of the command object.
350
+ - For repository-scoped commands (e.g., `Git::Commands::Log`,
351
+ `Git::Commands::Status`), the context will be injected once into the
352
+ `Git::Repository` constructor (for `open`/`bare`), and those command objects
353
+ will access the context through the repository instance rather than receiving it
354
+ directly.
82
355
 
83
- This decouples the `Repository` from its execution environment, making the system more modular and easier to test.
356
+ This decouples the `Repository` from its execution environment, making the system
357
+ more modular and easier to test.
84
358
 
85
359
  ### C. Immutable Return Values
86
360
 
87
- To create a more predictable and robust API, methods will return structured, immutable data objects instead of raw strings or hashes.
361
+ To create a more predictable and robust API, methods will return structured,
362
+ immutable data objects instead of raw strings or hashes.
88
363
 
89
364
  This will be implemented using `Data.define` or simple, frozen `Struct`s.
90
365
 
91
- For example, instead of returning a raw string, `repo.config('user.name')` will return a `Git::Config::Value` object containing the key, value, scope, and source path.
366
+ For example, instead of returning a raw string, `repo.config('user.name')` will
367
+ return a `Git::Config::Value` object containing the key, value, scope, and source
368
+ path.
369
+
370
+ **Value Objects vs Domain Objects**: A critical architectural distinction exists
371
+ between:
372
+
373
+ - **Value objects** (e.g., `Git::BranchInfo`): Pure data returned by commands. No
374
+ repository context, no operations, no dependencies. Created by `Data.define`.
375
+ These are the return types of `Git::Commands::*` classes.
376
+
377
+ - **Domain objects** (e.g., `Git::Branch`): Rich objects with operations that require
378
+ repository context (`@base`). These wrap value objects and provide methods like
379
+ `checkout`, `merge`, `delete`.
92
380
 
93
- ### D. Clear Naming for Path Objects
381
+ Commands return value objects. The facade layer (`Git::Repository`) or collection
382
+ classes (`Git::Branches`) convert them to domain objects when needed. This separation
383
+ keeps commands pure and testable while domain objects provide the rich API users
384
+ expect.
94
385
 
95
- To improve clarity, all classes that represent filesystem paths will be renamed to follow a consistent `...Path` suffix convention.
386
+ **Note on `Data.define` constraints**: Objects created with `Data.define` are frozen.
387
+ This means memoization patterns like `@cached ||= ...` will raise `FrozenError`.
388
+ Either accept repeated computation or use a different approach for caching.
96
389
 
97
- - `Git::WorkingDirectory` -> `Git::WorkingTreePath`
390
+ ### D. Eliminate Custom Path Classes
98
391
 
99
- - `Git::Index` -> `Git::IndexPath`
392
+ The existing path wrapper classes (`Git::WorkingDirectory`, `Git::Index`,
393
+ `Git::Repository`, and their base class `Git::Path`) provide minimal value over
394
+ Ruby's standard library. These classes will be eliminated entirely.
100
395
 
101
- - The old `Git::Repository` (representing the .git directory/file) -> `Git::RepositoryPath`
396
+ - `Git::Path` -> **Removed**
397
+ - `Git::WorkingDirectory` -> **Removed**
398
+ - `Git::Index` -> **Removed**
399
+ - `Git::Repository` (the path class) -> **Removed**
400
+
401
+ Instead, the `dir`, `repo`, and `index` accessors on the repository object will
402
+ return `Pathname` objects directly. This provides:
403
+
404
+ - Built-in `readable?` and `writable?` methods (preserving existing API)
405
+ - Automatic path expansion and normalization
406
+ - Seamless string coercion via `to_s` and `to_path`
407
+ - No custom classes to maintain
408
+
409
+ **Breaking change:** Code using `.path` (e.g., `g.dir.path`) must change to `.to_s`
410
+ or use the `Pathname` directly. String interpolation and most other uses will
411
+ continue to work unchanged.
102
412
 
103
413
  ## 4. Testing Strategy Overhaul
104
414
 
105
- The test suite will be modernized to be faster, more reliable, and easier to work with.
415
+ The test suite will be modernized to be faster, more reliable, and easier to work
416
+ with.
106
417
 
107
- - **Migration to RSpec**: The entire test suite will be migrated from TestUnit to RSpec to leverage its modern tooling and expressive DSL.
418
+ - **Migration to RSpec**: The entire test suite will be migrated from TestUnit to
419
+ RSpec to leverage its modern tooling and expressive DSL.
108
420
 
109
421
  - **Layered Testing**: A three-layered testing strategy will be adopted:
110
422
 
111
- 1. **Unit Tests**: The majority of tests will be fast, isolated unit tests for the `Command` classes, using mock `ExecutionContexts`.
423
+ 1. **Unit Tests**: The majority of tests will be fast, isolated unit tests for the
424
+ `Command` classes, using mock `ExecutionContexts`.
112
425
 
113
- 2. **Integration Tests**: A small number of integration tests will verify that `ExecutionContext` correctly interacts with the system's `git` binary.
426
+ 2. **Integration Tests**: A small number of integration tests will verify that
427
+ `ExecutionContext` correctly interacts with the system's `git` binary.
114
428
 
115
- 3. **Feature Tests**: A minimal set of high-level tests will ensure the public facade on `Git::Repository` works end-to-end.
429
+ 3. **Feature Tests**: A minimal set of high-level tests will ensure the public
430
+ facade on `Git::Repository` works end-to-end.
116
431
 
117
- - **Reduced Filesystem Dependency**: This new structure will dramatically reduce the suite's reliance on slow and brittle filesystem fixtures.
432
+ - **Reduced Filesystem Dependency**: This new structure will dramatically reduce the
433
+ suite's reliance on slow and brittle filesystem fixtures.
118
434
 
119
435
  ## 5. Impact on Users: Breaking Changes for v5.0.0
120
436
 
121
- This redesign is a significant undertaking and will be released as version 5.0.0. It includes several breaking changes that users will need to be aware of when upgrading.
437
+ This redesign is a significant undertaking and will be released as version 5.0.0. It
438
+ includes several breaking changes that users will need to be aware of when upgrading.
122
439
 
123
440
  - **`Git::Lib` is Removed**: Any code directly referencing `Git::Lib` will break.
124
441
 
125
- - **g.lib Accessor is Removed**: The `.lib` accessor on repository objects will be removed.
442
+ - **g.lib Accessor is Removed**: The `.lib` accessor on repository objects will be
443
+ removed.
126
444
 
127
- - **Internal Methods Relocated**: Methods that were previously accessible via g.lib will now be private implementation details of the new command classes and will not be directly reachable.
445
+ - **Internal Methods Relocated**: Methods that were previously accessible via g.lib
446
+ will now be private implementation details of the new command classes and will not
447
+ be directly reachable.
128
448
 
129
449
  Users should only rely on the newly defined public interface.
130
-