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.
- checksums.yaml +4 -4
- data/.github/copilot-instructions.md +67 -2705
- data/.github/pull_request_template.md +3 -1
- data/.github/skills/breaking-change-analysis/SKILL.md +102 -0
- data/.github/skills/ci-cd-troubleshooting/SKILL.md +264 -0
- data/.github/skills/command-implementation/REFERENCE.md +993 -0
- data/.github/skills/command-implementation/SKILL.md +229 -0
- data/.github/skills/command-test-conventions/SKILL.md +660 -0
- data/.github/skills/command-yard-documentation/SKILL.md +426 -0
- data/.github/skills/dependency-management/SKILL.md +72 -0
- data/.github/skills/development-workflow/SKILL.md +506 -0
- data/.github/skills/extract-command-from-lib/SKILL.md +487 -0
- data/.github/skills/extract-facade-from-base-lib/SKILL.md +586 -0
- data/.github/skills/facade-implementation/REFERENCE.md +840 -0
- data/.github/skills/facade-implementation/SKILL.md +260 -0
- data/.github/skills/facade-test-conventions/SKILL.md +380 -0
- data/.github/skills/facade-yard-documentation/SKILL.md +429 -0
- data/.github/skills/make-skill-template/SKILL.md +176 -0
- data/.github/skills/pr-readiness-review/SKILL.md +185 -0
- data/.github/skills/project-context/SKILL.md +313 -0
- data/.github/skills/pull-request-review/SKILL.md +168 -0
- data/.github/skills/refactor-command-to-commandlineresult/SKILL.md +131 -0
- data/.github/skills/release-management/SKILL.md +125 -0
- data/.github/skills/review-arguments-dsl/CHECKLIST.md +788 -0
- data/.github/skills/review-arguments-dsl/SKILL.md +214 -0
- data/.github/skills/review-backward-compatibility/SKILL.md +275 -0
- data/.github/skills/review-cross-command-consistency/SKILL.md +139 -0
- data/.github/skills/reviewing-skills/SKILL.md +189 -0
- data/.github/skills/rspec-unit-testing-standards/SKILL.md +639 -0
- data/.github/skills/tdd-refactor-step/SKILL.md +236 -0
- data/.github/skills/test-debugging/SKILL.md +160 -0
- data/.github/skills/yard-documentation/SKILL.md +793 -0
- data/.github/workflows/continuous_integration.yml +3 -2
- data/.github/workflows/enforce_conventional_commits.yml +1 -1
- data/.github/workflows/experimental_continuous_integration.yml +2 -2
- data/.github/workflows/release.yml +3 -4
- data/.gitignore +8 -0
- data/.husky/pre-commit +13 -0
- data/.release-please-manifest.json +1 -1
- data/.rspec +3 -0
- data/.rubocop.yml +7 -3
- data/.rubocop_todo.yml +23 -5
- data/.yardopts +1 -0
- data/CHANGELOG.md +0 -40
- data/CONTRIBUTING.md +694 -53
- data/README.md +17 -5
- data/Rakefile +61 -9
- data/commitlint.test +4 -0
- data/git.gemspec +14 -8
- data/lib/git/args_builder.rb +0 -8
- data/lib/git/base.rb +486 -410
- data/lib/git/branch.rb +380 -43
- data/lib/git/branch_delete_failure.rb +31 -0
- data/lib/git/branch_delete_result.rb +63 -0
- data/lib/git/branch_info.rb +178 -0
- data/lib/git/branches.rb +130 -24
- data/lib/git/command_line/base.rb +245 -0
- data/lib/git/command_line/capturing.rb +249 -0
- data/lib/git/command_line/result.rb +96 -0
- data/lib/git/command_line/streaming.rb +194 -0
- data/lib/git/command_line.rb +43 -322
- data/lib/git/command_line_result.rb +4 -88
- data/lib/git/commands/add.rb +131 -0
- data/lib/git/commands/am/abort.rb +43 -0
- data/lib/git/commands/am/apply.rb +252 -0
- data/lib/git/commands/am/continue.rb +43 -0
- data/lib/git/commands/am/quit.rb +43 -0
- data/lib/git/commands/am/retry.rb +47 -0
- data/lib/git/commands/am/show_current_patch.rb +64 -0
- data/lib/git/commands/am/skip.rb +42 -0
- data/lib/git/commands/am.rb +33 -0
- data/lib/git/commands/apply.rb +237 -0
- data/lib/git/commands/archive/list_formats.rb +46 -0
- data/lib/git/commands/archive.rb +140 -0
- data/lib/git/commands/arguments.rb +3510 -0
- data/lib/git/commands/base.rb +403 -0
- data/lib/git/commands/branch/copy.rb +94 -0
- data/lib/git/commands/branch/create.rb +173 -0
- data/lib/git/commands/branch/delete.rb +80 -0
- data/lib/git/commands/branch/list.rb +162 -0
- data/lib/git/commands/branch/move.rb +94 -0
- data/lib/git/commands/branch/set_upstream.rb +86 -0
- data/lib/git/commands/branch/show_current.rb +49 -0
- data/lib/git/commands/branch/unset_upstream.rb +57 -0
- data/lib/git/commands/branch.rb +34 -0
- data/lib/git/commands/cat_file/batch.rb +364 -0
- data/lib/git/commands/cat_file/filtered.rb +105 -0
- data/lib/git/commands/cat_file/raw.rb +210 -0
- data/lib/git/commands/cat_file.rb +49 -0
- data/lib/git/commands/checkout/branch.rb +151 -0
- data/lib/git/commands/checkout/files.rb +115 -0
- data/lib/git/commands/checkout.rb +38 -0
- data/lib/git/commands/checkout_index.rb +105 -0
- data/lib/git/commands/clean.rb +100 -0
- data/lib/git/commands/clone.rb +240 -0
- data/lib/git/commands/commit.rb +272 -0
- data/lib/git/commands/commit_tree.rb +100 -0
- data/lib/git/commands/config_option_syntax/add.rb +83 -0
- data/lib/git/commands/config_option_syntax/get.rb +117 -0
- data/lib/git/commands/config_option_syntax/get_all.rb +115 -0
- data/lib/git/commands/config_option_syntax/get_color.rb +91 -0
- data/lib/git/commands/config_option_syntax/get_color_bool.rb +93 -0
- data/lib/git/commands/config_option_syntax/get_regexp.rb +115 -0
- data/lib/git/commands/config_option_syntax/get_urlmatch.rb +102 -0
- data/lib/git/commands/config_option_syntax/list.rb +107 -0
- data/lib/git/commands/config_option_syntax/remove_section.rb +74 -0
- data/lib/git/commands/config_option_syntax/rename_section.rb +78 -0
- data/lib/git/commands/config_option_syntax/replace_all.rb +104 -0
- data/lib/git/commands/config_option_syntax/set.rb +114 -0
- data/lib/git/commands/config_option_syntax/unset.rb +89 -0
- data/lib/git/commands/config_option_syntax/unset_all.rb +89 -0
- data/lib/git/commands/config_option_syntax.rb +56 -0
- data/lib/git/commands/describe.rb +155 -0
- data/lib/git/commands/diff.rb +656 -0
- data/lib/git/commands/diff_files.rb +518 -0
- data/lib/git/commands/diff_index.rb +496 -0
- data/lib/git/commands/fetch.rb +352 -0
- data/lib/git/commands/fsck.rb +136 -0
- data/lib/git/commands/gc.rb +132 -0
- data/lib/git/commands/grep.rb +338 -0
- data/lib/git/commands/init.rb +99 -0
- data/lib/git/commands/log.rb +632 -0
- data/lib/git/commands/ls_files.rb +191 -0
- data/lib/git/commands/ls_remote.rb +155 -0
- data/lib/git/commands/ls_tree.rb +131 -0
- data/lib/git/commands/maintenance/register.rb +75 -0
- data/lib/git/commands/maintenance/run.rb +104 -0
- data/lib/git/commands/maintenance/start.rb +66 -0
- data/lib/git/commands/maintenance/stop.rb +55 -0
- data/lib/git/commands/maintenance/unregister.rb +79 -0
- data/lib/git/commands/maintenance.rb +31 -0
- data/lib/git/commands/merge/abort.rb +44 -0
- data/lib/git/commands/merge/continue.rb +44 -0
- data/lib/git/commands/merge/quit.rb +46 -0
- data/lib/git/commands/merge/start.rb +245 -0
- data/lib/git/commands/merge.rb +28 -0
- data/lib/git/commands/merge_base.rb +86 -0
- data/lib/git/commands/mv.rb +77 -0
- data/lib/git/commands/name_rev.rb +114 -0
- data/lib/git/commands/pull.rb +377 -0
- data/lib/git/commands/push.rb +246 -0
- data/lib/git/commands/read_tree.rb +149 -0
- data/lib/git/commands/remote/add.rb +91 -0
- data/lib/git/commands/remote/get_url.rb +66 -0
- data/lib/git/commands/remote/list.rb +54 -0
- data/lib/git/commands/remote/prune.rb +61 -0
- data/lib/git/commands/remote/remove.rb +52 -0
- data/lib/git/commands/remote/rename.rb +69 -0
- data/lib/git/commands/remote/set_branches.rb +63 -0
- data/lib/git/commands/remote/set_head.rb +82 -0
- data/lib/git/commands/remote/set_url.rb +71 -0
- data/lib/git/commands/remote/set_url_add.rb +61 -0
- data/lib/git/commands/remote/set_url_delete.rb +64 -0
- data/lib/git/commands/remote/show.rb +71 -0
- data/lib/git/commands/remote/update.rb +72 -0
- data/lib/git/commands/remote.rb +42 -0
- data/lib/git/commands/repack.rb +277 -0
- data/lib/git/commands/reset.rb +147 -0
- data/lib/git/commands/rev_parse.rb +297 -0
- data/lib/git/commands/revert/abort.rb +45 -0
- data/lib/git/commands/revert/continue.rb +57 -0
- data/lib/git/commands/revert/quit.rb +47 -0
- data/lib/git/commands/revert/skip.rb +44 -0
- data/lib/git/commands/revert/start.rb +153 -0
- data/lib/git/commands/revert.rb +29 -0
- data/lib/git/commands/rm.rb +114 -0
- data/lib/git/commands/show.rb +632 -0
- data/lib/git/commands/show_ref/exclude_existing.rb +120 -0
- data/lib/git/commands/show_ref/exists.rb +78 -0
- data/lib/git/commands/show_ref/list.rb +145 -0
- data/lib/git/commands/show_ref/verify.rb +120 -0
- data/lib/git/commands/show_ref.rb +42 -0
- data/lib/git/commands/stash/apply.rb +75 -0
- data/lib/git/commands/stash/branch.rb +65 -0
- data/lib/git/commands/stash/clear.rb +41 -0
- data/lib/git/commands/stash/create.rb +58 -0
- data/lib/git/commands/stash/drop.rb +67 -0
- data/lib/git/commands/stash/list.rb +39 -0
- data/lib/git/commands/stash/pop.rb +78 -0
- data/lib/git/commands/stash/push.rb +103 -0
- data/lib/git/commands/stash/show.rb +149 -0
- data/lib/git/commands/stash/store.rb +63 -0
- data/lib/git/commands/stash.rb +38 -0
- data/lib/git/commands/status.rb +169 -0
- data/lib/git/commands/symbolic_ref/delete.rb +68 -0
- data/lib/git/commands/symbolic_ref/read.rb +95 -0
- data/lib/git/commands/symbolic_ref/update.rb +76 -0
- data/lib/git/commands/symbolic_ref.rb +38 -0
- data/lib/git/commands/tag/create.rb +139 -0
- data/lib/git/commands/tag/delete.rb +55 -0
- data/lib/git/commands/tag/list.rb +143 -0
- data/lib/git/commands/tag/verify.rb +71 -0
- data/lib/git/commands/tag.rb +26 -0
- data/lib/git/commands/update_ref/batch.rb +140 -0
- data/lib/git/commands/update_ref/delete.rb +92 -0
- data/lib/git/commands/update_ref/update.rb +106 -0
- data/lib/git/commands/update_ref.rb +42 -0
- data/lib/git/commands/version.rb +52 -0
- data/lib/git/commands/worktree/add.rb +140 -0
- data/lib/git/commands/worktree/list.rb +64 -0
- data/lib/git/commands/worktree/lock.rb +58 -0
- data/lib/git/commands/worktree/management_base.rb +51 -0
- data/lib/git/commands/worktree/move.rb +66 -0
- data/lib/git/commands/worktree/prune.rb +67 -0
- data/lib/git/commands/worktree/remove.rb +63 -0
- data/lib/git/commands/worktree/repair.rb +76 -0
- data/lib/git/commands/worktree/unlock.rb +47 -0
- data/lib/git/commands/worktree.rb +43 -0
- data/lib/git/commands/write_tree.rb +68 -0
- data/lib/git/commands.rb +89 -0
- data/lib/git/detached_head_info.rb +54 -0
- data/lib/git/diff.rb +297 -7
- data/lib/git/diff_file_numstat_info.rb +29 -0
- data/lib/git/diff_file_patch_info.rb +134 -0
- data/lib/git/diff_file_raw_info.rb +127 -0
- data/lib/git/diff_info.rb +169 -0
- data/lib/git/diff_path_status.rb +78 -19
- data/lib/git/diff_result.rb +32 -0
- data/lib/git/diff_stats.rb +59 -14
- data/lib/git/dirstat_info.rb +86 -0
- data/lib/git/errors.rb +65 -2
- data/lib/git/execution_context/global.rb +56 -0
- data/lib/git/execution_context/repository.rb +147 -0
- data/lib/git/execution_context.rb +482 -0
- data/lib/git/file_ref.rb +74 -0
- data/lib/git/fsck_object.rb +9 -9
- data/lib/git/fsck_result.rb +1 -1
- data/lib/git/lib.rb +1606 -1028
- data/lib/git/log.rb +15 -2
- data/lib/git/object.rb +92 -22
- data/lib/git/parsers/branch.rb +224 -0
- data/lib/git/parsers/cat_file.rb +111 -0
- data/lib/git/parsers/diff.rb +585 -0
- data/lib/git/parsers/fsck.rb +133 -0
- data/lib/git/parsers/grep.rb +42 -0
- data/lib/git/parsers/ls_tree.rb +58 -0
- data/lib/git/parsers/stash.rb +208 -0
- data/lib/git/parsers/tag.rb +257 -0
- data/lib/git/remote.rb +133 -9
- data/lib/git/repository/branching.rb +572 -0
- data/lib/git/repository/committing.rb +191 -0
- data/lib/git/repository/configuring.rb +156 -0
- data/lib/git/repository/diffing.rb +775 -0
- data/lib/git/repository/inspecting.rb +153 -0
- data/lib/git/repository/logging.rb +247 -0
- data/lib/git/repository/merging.rb +295 -0
- data/lib/git/repository/object_operations.rb +1101 -0
- data/lib/git/repository/path_resolver.rb +207 -0
- data/lib/git/repository/remote_operations.rb +753 -0
- data/lib/git/repository/shared_private.rb +51 -0
- data/lib/git/repository/staging.rb +390 -0
- data/lib/git/repository/stashing.rb +107 -0
- data/lib/git/repository/status_operations.rb +180 -0
- data/lib/git/repository/worktree_operations.rb +159 -0
- data/lib/git/repository.rb +264 -1
- data/lib/git/stash.rb +85 -4
- data/lib/git/stash_info.rb +104 -0
- data/lib/git/stashes.rb +130 -13
- data/lib/git/status.rb +224 -18
- data/lib/git/tag_delete_failure.rb +31 -0
- data/lib/git/tag_delete_result.rb +63 -0
- data/lib/git/tag_info.rb +105 -0
- data/lib/git/version.rb +109 -2
- data/lib/git/version_constraint.rb +81 -0
- data/lib/git/worktree.rb +120 -5
- data/lib/git/worktrees.rb +107 -7
- data/lib/git.rb +114 -18
- data/redesign/1_architecture_existing.md +54 -18
- data/redesign/2_architecture_redesign.md +365 -46
- data/redesign/3_architecture_implementation.md +1451 -54
- data/tasks/gem_tasks.rake +4 -0
- data/tasks/npm_tasks.rake +7 -0
- data/tasks/rspec.rake +48 -0
- data/tasks/test.rake +13 -1
- data/tasks/yard.rake +34 -7
- metadata +349 -20
- data/lib/git/index.rb +0 -6
- data/lib/git/path.rb +0 -38
- data/lib/git/working_directory.rb +0 -6
- /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,
|
|
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`,
|
|
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
|
|
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
|
|
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`
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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`
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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.
|
|
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
|
|
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`,
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
53
|
+
**Renaming**: `Git::Base` will be renamed to `Git::Repository`. This name is more
|
|
54
|
+
descriptive and intuitive.
|
|
36
55
|
|
|
37
|
-
**Responsibility**:
|
|
56
|
+
**Responsibility**: The facade layer is responsible for:
|
|
38
57
|
|
|
39
|
-
|
|
58
|
+
1. **Managing the Execution Context**: Holding and providing access to the
|
|
59
|
+
configured execution context for command execution.
|
|
40
60
|
|
|
41
|
-
2.
|
|
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
|
-
|
|
64
|
+
3. **Collecting Data**: Gathering additional information before or after command
|
|
65
|
+
execution that may be needed for building response objects.
|
|
44
66
|
|
|
45
|
-
**
|
|
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
|
-
**
|
|
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
|
-
|
|
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
|
|
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
|
-
**
|
|
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
|
-
**
|
|
98
|
+
**Renaming**: `Git::Lib` will be renamed to `Git::ExecutionContext`.
|
|
56
99
|
|
|
57
|
-
|
|
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. **
|
|
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
|
-
**
|
|
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
|
|
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
|
|
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`
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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,
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
390
|
+
### D. Eliminate Custom Path Classes
|
|
98
391
|
|
|
99
|
-
|
|
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
|
-
-
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|