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
|
@@ -0,0 +1,487 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: extract-command-from-lib
|
|
3
|
+
description: "Migrates a direct #command call in Git::Lib to a Git::Commands::* class as part of the architectural redesign. Use when extracting a specific command during the Strangler Fig migration."
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Extract Command from Lib
|
|
7
|
+
|
|
8
|
+
Replace a direct `#command` call in `Git::Lib` with a call to a `Git::Commands::*`
|
|
9
|
+
class. The git subcommand is determined by the first (or first few) arguments to the
|
|
10
|
+
`#command` method call.
|
|
11
|
+
|
|
12
|
+
## Contents
|
|
13
|
+
|
|
14
|
+
- [How to use this skill](#how-to-use-this-skill)
|
|
15
|
+
- [Prerequisites](#prerequisites)
|
|
16
|
+
- [Related skills](#related-skills)
|
|
17
|
+
- [Input](#input)
|
|
18
|
+
- [Workflow](#workflow)
|
|
19
|
+
- [Branch setup](#branch-setup)
|
|
20
|
+
- [Step 1 — Identify the `#command` call](#step-1-identify-the-command-call)
|
|
21
|
+
- [Step 2 — Plan the migration and get approval](#step-2-plan-the-migration-and-get-approval)
|
|
22
|
+
- [Step 3 — Ensure adequate legacy tests](#step-3-ensure-adequate-legacy-tests)
|
|
23
|
+
- [Step 4 — Ensure the `Git::Commands::*` class exists](#step-4-ensure-the-gitcommands-class-exists)
|
|
24
|
+
- [Step 5 — Update `Git::Lib` to delegate to the command class](#step-5-update-gitlib-to-delegate-to-the-command-class)
|
|
25
|
+
- [Commit discipline](#commit-discipline)
|
|
26
|
+
- [Create a pull request](#create-a-pull-request)
|
|
27
|
+
- [Quality gates (run at every step)](#quality-gates-run-at-every-step)
|
|
28
|
+
- [Common patterns](#common-patterns)
|
|
29
|
+
- [Simple delegation (stdout passthrough)](#simple-delegation-stdout-passthrough)
|
|
30
|
+
- [Delegation with post-processing](#delegation-with-post-processing)
|
|
31
|
+
- [Delegation with parsed return value](#delegation-with-parsed-return-value)
|
|
32
|
+
- [Delegation with opts-hash key normalization](#delegation-with-opts-hash-key-normalization)
|
|
33
|
+
- [Delegation with option filtering (preventing API expansion)](#delegation-with-option-filtering-preventing-api-expansion)
|
|
34
|
+
- [What stays in `Git::Lib`](#what-stays-in-gitlib)
|
|
35
|
+
- [What moves to `Git::Commands::*`](#what-moves-to-gitcommands)
|
|
36
|
+
|
|
37
|
+
## How to use this skill
|
|
38
|
+
|
|
39
|
+
Attach this file to your Copilot Chat context, then invoke it with a short message
|
|
40
|
+
identifying the `Git::Lib` method or `#command` call to migrate. Examples:
|
|
41
|
+
|
|
42
|
+
```text
|
|
43
|
+
Using the Extract Command from Lib skill, migrate Git::Lib#worktree_add —
|
|
44
|
+
it calls command('worktree', 'add', ...).
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
```text
|
|
48
|
+
Extract Command from Lib: command('ls-tree', ...)
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
The invocation needs either the `Git::Lib` method name or the git subcommand string
|
|
52
|
+
from the `#command` call (or both).
|
|
53
|
+
|
|
54
|
+
## Prerequisites
|
|
55
|
+
|
|
56
|
+
Before starting, you **MUST** load the following skill(s) in their entirety:
|
|
57
|
+
|
|
58
|
+
- [YARD Documentation](../yard-documentation/SKILL.md) — authoritative
|
|
59
|
+
source for YARD formatting rules and writing standards;
|
|
60
|
+
|
|
61
|
+
## Related skills
|
|
62
|
+
|
|
63
|
+
Run or reference these skills during the workflow:
|
|
64
|
+
|
|
65
|
+
- [Command Implementation](../command-implementation/SKILL.md) — generates and reviews `Git::Commands::*`
|
|
66
|
+
classes, unit tests, integration tests, and YARD docs (used in Step 4 if the
|
|
67
|
+
command class does not exist yet); also the canonical class-shape checklist,
|
|
68
|
+
phased rollout gates, and internal compatibility contracts
|
|
69
|
+
- [Review Arguments DSL](../review-arguments-dsl/SKILL.md) — verifying DSL entries match git CLI
|
|
70
|
+
- [Command Test Conventions](../command-test-conventions/SKILL.md) — unit/integration test conventions for command classes
|
|
71
|
+
- [Command YARD Documentation](../command-yard-documentation/SKILL.md) — documentation completeness for command classes
|
|
72
|
+
- [Review Cross-Command Consistency](../review-cross-command-consistency/SKILL.md) — sibling consistency within a command family
|
|
73
|
+
- [Review Backward Compatibility](../review-backward-compatibility/SKILL.md) — preserving `Git::Lib` return-value contracts
|
|
74
|
+
- [Extract Facade from Base/Lib](../extract-facade-from-base-lib/SKILL.md) — the
|
|
75
|
+
follow-on extraction that moves the public method from `Git::Base` /
|
|
76
|
+
`Git::Lib` into a `Git::Repository::*` facade method (Phase 4 deletes both
|
|
77
|
+
`Git::Base` and `Git::Lib`)
|
|
78
|
+
|
|
79
|
+
## Input
|
|
80
|
+
|
|
81
|
+
Required:
|
|
82
|
+
|
|
83
|
+
1. A `Git::Lib` method that contains one or more `command(...)` calls to replace
|
|
84
|
+
2. The git subcommand name (derived from the first arguments to `#command`)
|
|
85
|
+
|
|
86
|
+
## Workflow
|
|
87
|
+
|
|
88
|
+
### Branch setup
|
|
89
|
+
|
|
90
|
+
All work must be done on a feature branch. **Never commit or push directly to
|
|
91
|
+
`main`.**
|
|
92
|
+
|
|
93
|
+
Before starting, create a new branch:
|
|
94
|
+
|
|
95
|
+
```bash
|
|
96
|
+
git checkout -b <feature-branch-name>
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
All commits in this workflow go on the feature branch. When work is complete,
|
|
100
|
+
open a pull request — do not merge or push directly into `main`.
|
|
101
|
+
|
|
102
|
+
### Step 1 — Identify the `#command` call
|
|
103
|
+
|
|
104
|
+
1. Locate the `Git::Lib` method that calls `command(...)`.
|
|
105
|
+
2. Note:
|
|
106
|
+
- the git subcommand (first argument(s) to `#command`)
|
|
107
|
+
- the options/arguments passed after the subcommand
|
|
108
|
+
- execution options (e.g., `timeout:`, `out:`, `err:`, `env:`)
|
|
109
|
+
- the return value and any post-processing (`.stdout`, parsing, regex matching)
|
|
110
|
+
3. Document the method's current **public contract**: signature, return type, and
|
|
111
|
+
return-value format (String, Array, Hash, Boolean, etc.)
|
|
112
|
+
4. Run linters and rubocop to confirm a clean baseline:
|
|
113
|
+
|
|
114
|
+
```bash
|
|
115
|
+
bundle exec rubocop
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
Fix any issues before continuing.
|
|
119
|
+
|
|
120
|
+
### Step 2 — Plan the migration and get approval
|
|
121
|
+
|
|
122
|
+
Before writing or changing any code, present a migration plan and **wait for
|
|
123
|
+
explicit confirmation** from the user. Do not proceed until they approve.
|
|
124
|
+
|
|
125
|
+
The plan must cover every `#command` call identified above. For each one, state:
|
|
126
|
+
|
|
127
|
+
| `Git::Lib` method | `#command` call | Target `Git::Commands` class | Class exists? | Notes |
|
|
128
|
+
| --- | --- | --- | --- | --- |
|
|
129
|
+
| `some_method` | `command('sub', '--flag', arg)` | `Git::Commands::Sub` (new) or existing | ✅ / 🆕 | any mapping decisions |
|
|
130
|
+
|
|
131
|
+
Also state:
|
|
132
|
+
|
|
133
|
+
- Which (if any) new `Git::Commands::*` classes need to be created
|
|
134
|
+
- How optional or empty arguments will be handled (e.g., nil vs `''` operands)
|
|
135
|
+
- Any return-value post-processing that stays in `Git::Lib`
|
|
136
|
+
|
|
137
|
+
Then ask:
|
|
138
|
+
|
|
139
|
+
> Does this mapping look correct? Any changes before I start implementing?
|
|
140
|
+
|
|
141
|
+
**Do not move to Step 3 until the user confirms the plan.**
|
|
142
|
+
|
|
143
|
+
### Step 3 — Ensure adequate legacy tests
|
|
144
|
+
|
|
145
|
+
Before making any changes, verify that `tests/units/` has adequate tests for the
|
|
146
|
+
`Git::Lib` method being migrated.
|
|
147
|
+
|
|
148
|
+
1. Search existing legacy tests for coverage of the method:
|
|
149
|
+
|
|
150
|
+
```bash
|
|
151
|
+
grep -rn '<method_name>' tests/units/
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
2. If coverage is insufficient, add **minimal new tests** to the legacy test suite
|
|
155
|
+
that exercise the method's current behavior. These tests ensure the refactor does
|
|
156
|
+
not break backward compatibility.
|
|
157
|
+
- Do **not** change existing tests.
|
|
158
|
+
- Follow existing legacy test conventions (`Test::Unit::TestCase`,
|
|
159
|
+
`assert_command_line_eq`, `in_temp_dir`, etc.).
|
|
160
|
+
- Verify new tests pass:
|
|
161
|
+
|
|
162
|
+
```bash
|
|
163
|
+
bundle exec bin/test <test-file-basename>
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
- Run rubocop against the new test file:
|
|
167
|
+
|
|
168
|
+
```bash
|
|
169
|
+
bundle exec rubocop tests/units/<test-file>
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
- Fix any issues before continuing.
|
|
173
|
+
3. Commit the new legacy tests:
|
|
174
|
+
|
|
175
|
+
```bash
|
|
176
|
+
git add tests/units/<test-file>
|
|
177
|
+
git commit -m "refactor(test): add legacy tests for <method_name>"
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
### Step 4 — Ensure the `Git::Commands::*` class exists
|
|
181
|
+
|
|
182
|
+
1. Search `lib/git/commands/` for an existing command class that matches the git
|
|
183
|
+
subcommand:
|
|
184
|
+
|
|
185
|
+
```bash
|
|
186
|
+
find lib/git/commands -name '*.rb' | sort
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
Also check the class contents to confirm the existing class covers the same
|
|
190
|
+
subcommand variation (e.g., `branch --show-current` vs. `branch --list`).
|
|
191
|
+
|
|
192
|
+
2. **If the command class already exists**, skip to Step 5.
|
|
193
|
+
|
|
194
|
+
3. **If the command class does not exist**, scaffold it using the
|
|
195
|
+
[Command Implementation](../command-implementation/SKILL.md) skill. This produces:
|
|
196
|
+
|
|
197
|
+
- `lib/git/commands/<command>.rb` (or `lib/git/commands/<family>/<action>.rb`)
|
|
198
|
+
- `spec/unit/git/commands/<command>_spec.rb`
|
|
199
|
+
- `spec/integration/git/commands/<command>_spec.rb`
|
|
200
|
+
|
|
201
|
+
4. Verify the new command class:
|
|
202
|
+
|
|
203
|
+
```bash
|
|
204
|
+
bundle exec rspec spec/unit/git/commands/<command>_spec.rb
|
|
205
|
+
bundle exec rspec spec/integration/git/commands/<command>_spec.rb
|
|
206
|
+
bundle exec rubocop lib/git/commands/<command>.rb
|
|
207
|
+
bundle exec rake yard
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
Fix any issues before continuing.
|
|
211
|
+
|
|
212
|
+
5. Commit the new command class and its tests:
|
|
213
|
+
|
|
214
|
+
```bash
|
|
215
|
+
git add lib/git/commands/<command>*.rb spec/
|
|
216
|
+
git commit -m "refactor(command): add Git::Commands::<Command> class"
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
### Step 5 — Update `Git::Lib` to delegate to the command class
|
|
220
|
+
|
|
221
|
+
1. Replace the `command(...)` call with a call to the `Git::Commands::*` class:
|
|
222
|
+
|
|
223
|
+
```ruby
|
|
224
|
+
# Before
|
|
225
|
+
def some_method(args)
|
|
226
|
+
command('some-command', '--flag', args).stdout
|
|
227
|
+
end
|
|
228
|
+
|
|
229
|
+
# After
|
|
230
|
+
def some_method(args)
|
|
231
|
+
Git::Commands::SomeCommand.new(self).call(args, flag: true).stdout
|
|
232
|
+
end
|
|
233
|
+
```
|
|
234
|
+
|
|
235
|
+
2. Preserve the method's **exact return value contract** — apply any parsing or
|
|
236
|
+
transformation after `.stdout` / `.stderr` / `.status` to match the original
|
|
237
|
+
return type.
|
|
238
|
+
|
|
239
|
+
3. **Prevent API expansion** — the command class may accept many more options than
|
|
240
|
+
the legacy `Git::Lib` method ever exposed. Only forward the options that were
|
|
241
|
+
part of the original `Git::Lib` method's public API. Use a `<COMMAND>_ALLOWED_OPTS`
|
|
242
|
+
constant to whitelist permitted option keys, call `assert_valid_opts` to raise
|
|
243
|
+
on unknown keys, then filter with `opts.slice` before forwarding:
|
|
244
|
+
|
|
245
|
+
```ruby
|
|
246
|
+
PULL_ALLOWED_OPTS = %i[allow_unrelated_histories].freeze
|
|
247
|
+
|
|
248
|
+
def pull(remote = nil, branch = nil, opts = {})
|
|
249
|
+
assert_valid_opts(opts, PULL_ALLOWED_OPTS)
|
|
250
|
+
allowed_opts = opts.slice(*PULL_ALLOWED_OPTS)
|
|
251
|
+
Git::Commands::Pull.new(self).call(remote, branch, **allowed_opts).stdout
|
|
252
|
+
end
|
|
253
|
+
```
|
|
254
|
+
|
|
255
|
+
`assert_valid_opts` raises `ArgumentError` for any unrecognised key, giving
|
|
256
|
+
callers a clear error instead of silently ignoring unknown options. This
|
|
257
|
+
ensures that callers cannot accidentally pass options that happen to match
|
|
258
|
+
command DSL option names but were never part of the public contract.
|
|
259
|
+
|
|
260
|
+
4. Add the appropriate `require_relative` at the top of `lib/git/lib.rb` if not
|
|
261
|
+
already present.
|
|
262
|
+
|
|
263
|
+
4. Verify:
|
|
264
|
+
|
|
265
|
+
```bash
|
|
266
|
+
bundle exec bin/test <legacy-test-file-basename>
|
|
267
|
+
bundle exec rspec
|
|
268
|
+
bundle exec rubocop
|
|
269
|
+
bundle exec rake yard
|
|
270
|
+
```
|
|
271
|
+
|
|
272
|
+
Fix any issues before continuing.
|
|
273
|
+
|
|
274
|
+
5. Commit the `Git::Lib` change:
|
|
275
|
+
|
|
276
|
+
```bash
|
|
277
|
+
git add lib/git/lib.rb
|
|
278
|
+
git commit -m "refactor(lib): delegate <method_name> to Git::Commands::<Command>"
|
|
279
|
+
```
|
|
280
|
+
|
|
281
|
+
## Commit discipline
|
|
282
|
+
|
|
283
|
+
Keep work organized into **three logical commit categories** (each optional if no
|
|
284
|
+
changes were needed for that step):
|
|
285
|
+
|
|
286
|
+
1. `refactor(test): add legacy tests for <method_name>` — new tests in
|
|
287
|
+
`tests/units/`
|
|
288
|
+
2. `refactor(command): add Git::Commands::<Command> class` — new command class,
|
|
289
|
+
unit specs, and integration specs
|
|
290
|
+
3. `refactor(lib): delegate <method_name> to Git::Commands::<Command>` — `Git::Lib`
|
|
291
|
+
changes only
|
|
292
|
+
|
|
293
|
+
During implementation, you may use multiple task-level commits. Before opening a
|
|
294
|
+
PR, follow the repository finalize workflow (see
|
|
295
|
+
[Development Workflow](../development-workflow/SKILL.md)) and squash commits as
|
|
296
|
+
required.
|
|
297
|
+
|
|
298
|
+
**Issue and PR references in commit bodies:** Do not use `#<number>` in the
|
|
299
|
+
commit body — write `issue 1000` not `issue #1000`. A commitlint parser flaw
|
|
300
|
+
treats any line containing `#<number>` as a footer token, breaking the
|
|
301
|
+
body/footer split. To close an issue/PR, use `Closes`/`Fixes`/`Resolves #<number>`
|
|
302
|
+
in the footer. To merely mention one for context, omit the `#` and no footer line
|
|
303
|
+
is needed.
|
|
304
|
+
|
|
305
|
+
If further changes are needed after task commits are created:
|
|
306
|
+
|
|
307
|
+
- Amend the change to the **appropriate commit** (e.g., a command class fix goes
|
|
308
|
+
into the `refactor(command)` commit).
|
|
309
|
+
- Rebase the later commits on top:
|
|
310
|
+
|
|
311
|
+
```bash
|
|
312
|
+
git rebase -i <base-commit>
|
|
313
|
+
```
|
|
314
|
+
|
|
315
|
+
- After rebasing, verify all quality gates still pass:
|
|
316
|
+
|
|
317
|
+
```bash
|
|
318
|
+
bundle exec rspec && bundle exec rake test && bundle exec rubocop && bundle exec rake yard
|
|
319
|
+
```
|
|
320
|
+
|
|
321
|
+
## Create a pull request
|
|
322
|
+
|
|
323
|
+
Once all commits are clean and quality gates pass, create a PR for the branch.
|
|
324
|
+
|
|
325
|
+
If changes are made after the PR is created:
|
|
326
|
+
|
|
327
|
+
- Amend the change to the appropriate commit.
|
|
328
|
+
- Rebase later commits on top.
|
|
329
|
+
- Force-push the branch:
|
|
330
|
+
|
|
331
|
+
```bash
|
|
332
|
+
git push --force-with-lease
|
|
333
|
+
```
|
|
334
|
+
|
|
335
|
+
## Quality gates (run at every step)
|
|
336
|
+
|
|
337
|
+
```bash
|
|
338
|
+
bundle exec rspec
|
|
339
|
+
bundle exec rake test
|
|
340
|
+
bundle exec rubocop
|
|
341
|
+
bundle exec rake yard
|
|
342
|
+
```
|
|
343
|
+
|
|
344
|
+
All four must pass before committing at each step. If errors are found, fix them
|
|
345
|
+
before continuing.
|
|
346
|
+
|
|
347
|
+
## Common patterns
|
|
348
|
+
|
|
349
|
+
### Simple delegation (stdout passthrough)
|
|
350
|
+
|
|
351
|
+
```ruby
|
|
352
|
+
# Before
|
|
353
|
+
def symbolic_ref(branch_name)
|
|
354
|
+
command('symbolic-ref', 'HEAD', "refs/heads/#{branch_name}")
|
|
355
|
+
end
|
|
356
|
+
|
|
357
|
+
# After
|
|
358
|
+
def symbolic_ref(branch_name)
|
|
359
|
+
Git::Commands::SymbolicRef.new(self).call(branch_name).stdout
|
|
360
|
+
end
|
|
361
|
+
```
|
|
362
|
+
|
|
363
|
+
### Delegation with post-processing
|
|
364
|
+
|
|
365
|
+
```ruby
|
|
366
|
+
# Before
|
|
367
|
+
def cat_file_type(object)
|
|
368
|
+
command('cat-file', '-t', object).stdout
|
|
369
|
+
end
|
|
370
|
+
|
|
371
|
+
# After
|
|
372
|
+
def cat_file_type(object)
|
|
373
|
+
Git::Commands::CatFile::Type.new(self).call(object).stdout
|
|
374
|
+
end
|
|
375
|
+
```
|
|
376
|
+
|
|
377
|
+
### Delegation with parsed return value
|
|
378
|
+
|
|
379
|
+
```ruby
|
|
380
|
+
# Before
|
|
381
|
+
def worktree_list
|
|
382
|
+
worktrees = {}
|
|
383
|
+
command('worktree', 'list', '--porcelain').stdout.split("\n").each do |w|
|
|
384
|
+
# ... parsing ...
|
|
385
|
+
end
|
|
386
|
+
worktrees
|
|
387
|
+
end
|
|
388
|
+
|
|
389
|
+
# After
|
|
390
|
+
def worktree_list
|
|
391
|
+
result = Git::Commands::Worktree::List.new(self).call
|
|
392
|
+
worktrees = {}
|
|
393
|
+
result.stdout.split("\n").each do |w|
|
|
394
|
+
# ... parsing stays in Git::Lib ...
|
|
395
|
+
end
|
|
396
|
+
worktrees
|
|
397
|
+
end
|
|
398
|
+
```
|
|
399
|
+
|
|
400
|
+
### Delegation with opts-hash key normalization
|
|
401
|
+
|
|
402
|
+
When the legacy method accepted a flat `opts` hash and uses a `KEY_NORMALIZATIONS`
|
|
403
|
+
constant to rename option keys before forwarding them, the constant's keys must be
|
|
404
|
+
the same type as the keys callers actually pass.
|
|
405
|
+
|
|
406
|
+
**Ruby's `'key':` symbol-literal syntax creates a *symbol* key** — `{ 'update-head-ok': :x }`
|
|
407
|
+
stores the key `:'update-head-ok'`, not the string `'update-head-ok'`. If legacy
|
|
408
|
+
callers pass string keys, the lookup misses and the raw key is forwarded unchanged,
|
|
409
|
+
causing a git "unsupported option" error at runtime.
|
|
410
|
+
|
|
411
|
+
Always symbolize keys before the normalization lookup:
|
|
412
|
+
|
|
413
|
+
```ruby
|
|
414
|
+
# ❌ Bug — string key 'update-head-ok' misses the symbol key :'update-head-ok'
|
|
415
|
+
opts = opts.transform_keys { |k| KEY_NORMALIZATIONS.fetch(k, k) }
|
|
416
|
+
|
|
417
|
+
# ✅ Correct — symbolize first so both string and symbol callers match
|
|
418
|
+
opts = opts.transform_keys do |k|
|
|
419
|
+
sym = k.is_a?(Symbol) ? k : k.to_sym
|
|
420
|
+
KEY_NORMALIZATIONS.fetch(sym, sym)
|
|
421
|
+
end
|
|
422
|
+
```
|
|
423
|
+
|
|
424
|
+
**When to apply this pattern:** Whenever `transform_keys` is combined with a
|
|
425
|
+
normalization constant whose keys use the `'hyphenated-name':` symbol-literal
|
|
426
|
+
syntax. Scan the constant's definition to confirm its keys are symbols, then
|
|
427
|
+
confirm whether existing callers pass strings or symbols. If callers are a mix —
|
|
428
|
+
or if callers are `Git::Base` or `Git::Lib` methods that forward user-supplied
|
|
429
|
+
hashes — add the symbolization guard.
|
|
430
|
+
|
|
431
|
+
### Delegation with option filtering (preventing API expansion)
|
|
432
|
+
|
|
433
|
+
A command class may expose many more options than the legacy `Git::Lib` method
|
|
434
|
+
ever accepted. Without filtering, callers could accidentally pass options that
|
|
435
|
+
happen to match command DSL names but were never part of the public contract.
|
|
436
|
+
|
|
437
|
+
The facade is also where **policy options** are set as safe defaults — options
|
|
438
|
+
that support non-interactive execution, control output format for parsing, or
|
|
439
|
+
set other command-level defaults. The command class stays neutral; the facade
|
|
440
|
+
makes the defaults explicit. Some defaults are **fixed** (not in `ALLOWED_OPTS` —
|
|
441
|
+
`assert_valid_opts` rejects them if a caller supplies them); others are
|
|
442
|
+
**overridable** (in `ALLOWED_OPTS`, placed before `**opts` so the caller's value
|
|
443
|
+
wins on collision). Examples: `no_edit: true`, `verbose: true`,
|
|
444
|
+
`no_progress: true`, `no_color: true`.
|
|
445
|
+
See "Command-layer neutrality" in CONTRIBUTING.md.
|
|
446
|
+
|
|
447
|
+
Declare an `<COMMAND>_ALLOWED_OPTS` constant listing only the options that were
|
|
448
|
+
present in the original method. Call `assert_valid_opts` first to raise
|
|
449
|
+
`ArgumentError` on unrecognised keys, then use `opts.slice` to filter before
|
|
450
|
+
forwarding:
|
|
451
|
+
|
|
452
|
+
```ruby
|
|
453
|
+
# Only :allow_unrelated_histories was accepted by the original Git::Lib#pull
|
|
454
|
+
PULL_ALLOWED_OPTS = %i[allow_unrelated_histories].freeze
|
|
455
|
+
|
|
456
|
+
def pull(remote = nil, branch = nil, opts = {})
|
|
457
|
+
raise ArgumentError, 'You must specify a remote if a branch is specified' if remote.nil? && !branch.nil?
|
|
458
|
+
|
|
459
|
+
assert_valid_opts(opts, PULL_ALLOWED_OPTS)
|
|
460
|
+
allowed_opts = opts.slice(*PULL_ALLOWED_OPTS)
|
|
461
|
+
positional_args = [remote, branch].compact
|
|
462
|
+
# no_edit: true is the non-interactive default (see CONTRIBUTING.md)
|
|
463
|
+
Git::Commands::Pull.new(self).call(*positional_args, no_edit: true, **allowed_opts).stdout
|
|
464
|
+
end
|
|
465
|
+
```
|
|
466
|
+
|
|
467
|
+
`assert_valid_opts` is a private helper already defined in `Git::Lib` — no extra
|
|
468
|
+
require is needed. It raises `ArgumentError: Unknown options: <key>` when any
|
|
469
|
+
unrecognised key is present, giving callers a clear error rather than silently
|
|
470
|
+
dropping the option.
|
|
471
|
+
|
|
472
|
+
Name the constant after the git subcommand (`PULL_ALLOWED_OPTS`, `FETCH_ALLOWED_OPTS`,
|
|
473
|
+
etc.) and place it immediately before the method definition.
|
|
474
|
+
|
|
475
|
+
## What stays in `Git::Lib`
|
|
476
|
+
|
|
477
|
+
- Output parsing and transformation (until a parser class is created)
|
|
478
|
+
- Return-value adaptation to preserve backward compatibility
|
|
479
|
+
- Option validation and filtering to prevent API expansion (see `<COMMAND>_ALLOWED_OPTS` + `assert_valid_opts` pattern)
|
|
480
|
+
- Deprecation shims (e.g., option renames)
|
|
481
|
+
- Method signatures and public API surface
|
|
482
|
+
|
|
483
|
+
## What moves to `Git::Commands::*`
|
|
484
|
+
|
|
485
|
+
- Argument building and CLI flag generation
|
|
486
|
+
- `#command` invocation
|
|
487
|
+
- Exit-status handling via `allow_exit_status`
|