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,993 @@
|
|
|
1
|
+
# Command Implementation — Reference
|
|
2
|
+
|
|
3
|
+
Detailed reference for `Git::Commands::Base` command classes. This file is loaded
|
|
4
|
+
by subagents during the [Command Implementation](SKILL.md) workflow.
|
|
5
|
+
|
|
6
|
+
## Contents
|
|
7
|
+
|
|
8
|
+
- [Contents](#contents)
|
|
9
|
+
- [Files to generate](#files-to-generate)
|
|
10
|
+
- [Single class vs. sub-command namespace](#single-class-vs-sub-command-namespace)
|
|
11
|
+
- [When to use sub-commands](#when-to-use-sub-commands)
|
|
12
|
+
- [Do NOT split by output format / output mode](#do-not-split-by-output-format--output-mode)
|
|
13
|
+
- [When to keep a single class](#when-to-keep-a-single-class)
|
|
14
|
+
- [Naming sub-command classes](#naming-sub-command-classes)
|
|
15
|
+
- [Namespace module template](#namespace-module-template)
|
|
16
|
+
- [Architecture contract](#architecture-contract)
|
|
17
|
+
- [Command template (Base pattern)](#command-template-base-pattern)
|
|
18
|
+
- [`#call` override guidance](#call-override-guidance)
|
|
19
|
+
- [Overriding `call` — inline example](#overriding-call--inline-example)
|
|
20
|
+
- [Action-option-with-optional-value commands](#action-option-with-optional-value-commands)
|
|
21
|
+
- [When to use `skip_cli` on `operand`](#when-to-use-skip_cli-on-operand)
|
|
22
|
+
- [`Base#with_stdin` mechanics](#basewith_stdin-mechanics)
|
|
23
|
+
- [Options completeness — consult the latest-version docs first](#options-completeness--consult-the-latest-version-docs-first)
|
|
24
|
+
- [`requires_git_version` convention](#requires_git_version-convention)
|
|
25
|
+
- [Scoping options to sub-command classes](#scoping-options-to-sub-command-classes)
|
|
26
|
+
- [Execution-model conflicts](#execution-model-conflicts)
|
|
27
|
+
- [`end_of_options` placement](#end_of_options-placement)
|
|
28
|
+
- [Rule 1 — SYNOPSIS shows `--`: mirror the SYNOPSIS](#rule-1--synopsis-shows----mirror-the-synopsis)
|
|
29
|
+
- [Rule 2 — SYNOPSIS does NOT show `--`: protect operands from flag misinterpretation](#rule-2--synopsis-does-not-show----protect-operands-from-flag-misinterpretation)
|
|
30
|
+
- [Exit status guidance](#exit-status-guidance)
|
|
31
|
+
- [Facade delegation and policy options](#facade-delegation-and-policy-options)
|
|
32
|
+
- [Internal compatibility contract](#internal-compatibility-contract)
|
|
33
|
+
- [Phased rollout requirements](#phased-rollout-requirements)
|
|
34
|
+
- [Common failures](#common-failures)
|
|
35
|
+
- [Policy/output-control flag hardcoded as `literal` (neutrality violation)](#policyoutput-control-flag-hardcoded-as-literal-neutrality-violation)
|
|
36
|
+
- [Unnecessary `def call` override](#unnecessary-def-call-override)
|
|
37
|
+
- [`execution_option` for fixed kwargs](#execution_option-for-fixed-kwargs)
|
|
38
|
+
- [Unnecessary `require` statements](#unnecessary-require-statements)
|
|
39
|
+
- [Other common failures](#other-common-failures)
|
|
40
|
+
|
|
41
|
+
## Files to generate
|
|
42
|
+
|
|
43
|
+
For `Git::Commands::Foo::Bar`, **all three files are required and must be created**:
|
|
44
|
+
|
|
45
|
+
- `lib/git/commands/foo/bar.rb` — the command class
|
|
46
|
+
- `spec/unit/git/commands/foo/bar_spec.rb` — unit tests
|
|
47
|
+
- `spec/integration/git/commands/foo/bar_spec.rb` — integration tests (mandatory, not
|
|
48
|
+
optional)
|
|
49
|
+
|
|
50
|
+
Optional (first command in module):
|
|
51
|
+
|
|
52
|
+
- `lib/git/commands/foo.rb`
|
|
53
|
+
|
|
54
|
+
## Single class vs. sub-command namespace
|
|
55
|
+
|
|
56
|
+
Most git commands map to a single class. Split into a namespace module with multiple
|
|
57
|
+
sub-command classes when the git command surfaces **meaningfully different
|
|
58
|
+
operations** that have distinct call shapes or protocols.
|
|
59
|
+
|
|
60
|
+
### When to use sub-commands
|
|
61
|
+
|
|
62
|
+
**Split by operation** — when the git command has named sub-actions whose option sets
|
|
63
|
+
have little overlap (each sub-action would have mostly dead options if they shared
|
|
64
|
+
one class):
|
|
65
|
+
|
|
66
|
+
```
|
|
67
|
+
git stash push / pop / apply / drop / list / show
|
|
68
|
+
git tag --create / --delete / --list
|
|
69
|
+
git worktree add / list / remove / move
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
**Split by stdin protocol** — when one variant reads from stdin and another does not
|
|
73
|
+
(even if the git command is the same). The stdin variant needs a `call` override that
|
|
74
|
+
uses `Base#with_stdin`; mixing that with a no-stdin path in one class produces an
|
|
75
|
+
awkward interface.
|
|
76
|
+
|
|
77
|
+
### Do NOT split by output format / output mode
|
|
78
|
+
|
|
79
|
+
**Output-mode flags are NOT a reason to create separate subclasses.** When a git
|
|
80
|
+
command supports multiple output formats via flags (`--patch`, `--numstat`, `--raw`,
|
|
81
|
+
`--format=…`, etc.), express them as `flag_option` or `value_option` entries in a
|
|
82
|
+
**single class**. The facade passes the desired format flags explicitly at call time:
|
|
83
|
+
|
|
84
|
+
```ruby
|
|
85
|
+
# ❌ Anti-pattern: one class per output format
|
|
86
|
+
class Diff::Patch < Git::Commands::Base; end # literal '--patch'
|
|
87
|
+
class Diff::Numstat < Git::Commands::Base; end # literal '--numstat'
|
|
88
|
+
class Diff::Raw < Git::Commands::Base; end # literal '--raw'
|
|
89
|
+
|
|
90
|
+
# ✅ Correct: one class, output mode as options
|
|
91
|
+
class Diff < Git::Commands::Base
|
|
92
|
+
arguments do
|
|
93
|
+
literal 'diff'
|
|
94
|
+
flag_option :patch
|
|
95
|
+
flag_option :numstat
|
|
96
|
+
flag_option :raw
|
|
97
|
+
...
|
|
98
|
+
end
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
# lib/git/lib.rb — parser contract is visible and auditable:
|
|
102
|
+
Git::Commands::Diff.new(self).call(patch: true, numstat: true, ...)
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
The same applies for `--format=<string>`, `--pretty=<fmt>`, `--no-color`, and all
|
|
106
|
+
other parser-contract options. Declare them in the DSL; the facade passes them.
|
|
107
|
+
|
|
108
|
+
Remember: **`literal` entries are only for operation selectors** — fixed flags that
|
|
109
|
+
define which git sub-operation the class represents (e.g., `literal 'stash'`,
|
|
110
|
+
`literal 'show'`, `literal '--delete'`). Output-format flags are not operation
|
|
111
|
+
selectors.
|
|
112
|
+
|
|
113
|
+
### When to keep a single class
|
|
114
|
+
|
|
115
|
+
- Different output modes (`--patch`, `--numstat`, `--raw`): **always** use a single
|
|
116
|
+
class; expose modes as DSL options.
|
|
117
|
+
- Minor option variations that share the same argument set.
|
|
118
|
+
- When the "special mode" is just 1–2 flags — use `flag_option`/`value_option`.
|
|
119
|
+
- When callers would always need multiple modes together (the facade composes them).
|
|
120
|
+
|
|
121
|
+
### Naming sub-command classes
|
|
122
|
+
|
|
123
|
+
Prefer **user-oriented names** (what the caller gets back) over flag names
|
|
124
|
+
(implementation detail the caller shouldn't need to know):
|
|
125
|
+
|
|
126
|
+
```
|
|
127
|
+
# Avoid — leaks implementation detail
|
|
128
|
+
CatFile::BatchCheck / CatFile::Batch
|
|
129
|
+
|
|
130
|
+
# Prefer — describes the result from the caller's perspective
|
|
131
|
+
CatFile::ObjectMeta / CatFile::ObjectContent
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
Two hard constraints:
|
|
135
|
+
|
|
136
|
+
- **Never name a sub-command class `Object`** — it shadows Ruby's `::Object` base
|
|
137
|
+
class anywhere that constant is looked up inside the namespace.
|
|
138
|
+
- **Never use the `*Info` or `*Result` suffix** on command classes — those suffixes
|
|
139
|
+
are reserved for parsed result structs (`BranchInfo`, `TagInfo`,
|
|
140
|
+
`BranchDeleteResult`) which live in the top-level `Git::` namespace, not in
|
|
141
|
+
`Git::Commands::*`. A reader seeing `CommandFoo::BarInfo` expects a data struct,
|
|
142
|
+
not a class that runs a subprocess.
|
|
143
|
+
|
|
144
|
+
### Namespace module template
|
|
145
|
+
|
|
146
|
+
When splitting, create a bare namespace module file (`foo.rb`) — no class — matching
|
|
147
|
+
the pattern of `cat_file.rb`. The file has three required sections in this order:
|
|
148
|
+
`require_relative` lines → module body with YARD → empty `module Foo` block.
|
|
149
|
+
|
|
150
|
+
**Required elements (all mandatory):**
|
|
151
|
+
|
|
152
|
+
1. `# frozen_string_literal: true` magic comment
|
|
153
|
+
2. One `require_relative` line per sub-command file, in the order the sub-commands
|
|
154
|
+
appear in the `@see` bullet list
|
|
155
|
+
3. A one-line summary (what `git foo` does overall)
|
|
156
|
+
4. A "This module contains command classes split by…" paragraph with a bullet for
|
|
157
|
+
every sub-command class using `{Foo::Bar}` YARD links followed by ` — ` and a
|
|
158
|
+
short description
|
|
159
|
+
5. `@api private`
|
|
160
|
+
6. `@see https://git-scm.com/docs/git-foo git-foo documentation`
|
|
161
|
+
7. At least two `@example` blocks — one per sub-command class; each example should
|
|
162
|
+
demonstrate the most common (non-error-path) call using a local variable named
|
|
163
|
+
`cmd` and `lib` as the constructor argument
|
|
164
|
+
8. Empty `module Foo` + `end` block (no methods, no constants)
|
|
165
|
+
|
|
166
|
+
**Tag ordering inside the YARD comment block:**
|
|
167
|
+
|
|
168
|
+
```
|
|
169
|
+
# One-line summary.
|
|
170
|
+
#
|
|
171
|
+
# This module contains command classes split by ...:
|
|
172
|
+
#
|
|
173
|
+
# - {Foo::Bar} — short description
|
|
174
|
+
# - {Foo::Baz} — short description
|
|
175
|
+
#
|
|
176
|
+
# @api private
|
|
177
|
+
#
|
|
178
|
+
# @see https://git-scm.com/docs/git-foo git-foo documentation
|
|
179
|
+
#
|
|
180
|
+
# @example <Short description of the Bar use case>
|
|
181
|
+
# cmd = Git::Commands::Foo::Bar.new(lib)
|
|
182
|
+
# cmd.call(...)
|
|
183
|
+
#
|
|
184
|
+
# @example <Short description of the Baz use case>
|
|
185
|
+
# cmd = Git::Commands::Foo::Baz.new(lib)
|
|
186
|
+
# cmd.call(...)
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
**Full template:**
|
|
190
|
+
|
|
191
|
+
```ruby
|
|
192
|
+
# frozen_string_literal: true
|
|
193
|
+
|
|
194
|
+
require_relative 'foo/bar'
|
|
195
|
+
require_relative 'foo/baz'
|
|
196
|
+
|
|
197
|
+
module Git
|
|
198
|
+
module Commands
|
|
199
|
+
# One-line summary of what `git foo` does.
|
|
200
|
+
#
|
|
201
|
+
# This module contains command classes split by [reason for split]:
|
|
202
|
+
#
|
|
203
|
+
# - {Foo::Bar} — what Bar does
|
|
204
|
+
# - {Foo::Baz} — what Baz does
|
|
205
|
+
#
|
|
206
|
+
# @api private
|
|
207
|
+
#
|
|
208
|
+
# @see https://git-scm.com/docs/git-foo git-foo documentation
|
|
209
|
+
#
|
|
210
|
+
# @example <Short description for bar>
|
|
211
|
+
# cmd = Git::Commands::Foo::Bar.new(lib)
|
|
212
|
+
# cmd.call(...)
|
|
213
|
+
#
|
|
214
|
+
# @example <Short description for baz>
|
|
215
|
+
# cmd = Git::Commands::Foo::Baz.new(lib)
|
|
216
|
+
# cmd.call(...)
|
|
217
|
+
#
|
|
218
|
+
module Foo
|
|
219
|
+
end
|
|
220
|
+
end
|
|
221
|
+
end
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
Each sub-command file adds `@see Git::Commands::Foo` to link back to the parent
|
|
225
|
+
module's overview.
|
|
226
|
+
|
|
227
|
+
**Checklist for reviewing an existing namespace module:**
|
|
228
|
+
|
|
229
|
+
- [ ] `# frozen_string_literal: true` is present
|
|
230
|
+
- [ ] All sub-command files are `require_relative`'d (no `require 'git/commands/...'`)
|
|
231
|
+
- [ ] Bullet list covers every sub-command class in the namespace with `{Foo::Bar}` YARD links
|
|
232
|
+
- [ ] `@api private` is present
|
|
233
|
+
- [ ] `@see` link points to `git-scm.com/docs/git-foo` documentation
|
|
234
|
+
- [ ] At least one `@example` block per sub-command class
|
|
235
|
+
- [ ] Each example uses `cmd = Git::Commands::Foo::Bar.new(lib)` form (variable `cmd`, arg `lib`)
|
|
236
|
+
- [ ] Tag order: summary → bullet list → `@api private` → `@see` → `@examples`
|
|
237
|
+
- [ ] No class is defined inside the module file; the `module Foo` block is empty
|
|
238
|
+
|
|
239
|
+
## Architecture contract
|
|
240
|
+
|
|
241
|
+
For migrated commands, the expected structure is:
|
|
242
|
+
|
|
243
|
+
```ruby
|
|
244
|
+
require 'git/commands/base'
|
|
245
|
+
|
|
246
|
+
class SomeCommand < Git::Commands::Base
|
|
247
|
+
arguments do
|
|
248
|
+
...
|
|
249
|
+
end
|
|
250
|
+
|
|
251
|
+
# optional — only when introduced after Git::MINIMUM_GIT_VERSION
|
|
252
|
+
requires_git_version '2.29.0'
|
|
253
|
+
|
|
254
|
+
# optional for non-zero successful exits
|
|
255
|
+
# reason comment
|
|
256
|
+
allow_exit_status 0..1
|
|
257
|
+
|
|
258
|
+
# @!method call(*, **)
|
|
259
|
+
#
|
|
260
|
+
# @overload call(**options)
|
|
261
|
+
#
|
|
262
|
+
# YARD docs for this command's call signature.
|
|
263
|
+
#
|
|
264
|
+
# @return [Git::CommandLineResult]
|
|
265
|
+
end
|
|
266
|
+
```
|
|
267
|
+
|
|
268
|
+
The `@!method` directive is the correct YARD form when the class contains **no
|
|
269
|
+
explicit `def call`** — YARD uses it to render per-command docs on the inherited
|
|
270
|
+
`call` method. When the class **does** define `def call` explicitly, place YARD
|
|
271
|
+
docs directly above `def call` and omit the `@!method` directive.
|
|
272
|
+
|
|
273
|
+
Shared behavior lives in `Base`:
|
|
274
|
+
|
|
275
|
+
- binds arguments
|
|
276
|
+
- calls `@execution_context.command_capturing(*args, **args.execution_options, raise_on_failure: false)`
|
|
277
|
+
- raises `Git::FailedError` unless exit status is in allowed range (`0..0` default)
|
|
278
|
+
|
|
279
|
+
Structural requirements:
|
|
280
|
+
|
|
281
|
+
- Class inherits from `Git::Commands::Base`
|
|
282
|
+
- File requires `git/commands/base` (not `git/commands/arguments`)
|
|
283
|
+
- Has exactly one `arguments do` declaration
|
|
284
|
+
- Does not define command-specific `initialize` that only assigns
|
|
285
|
+
`@execution_context`
|
|
286
|
+
- `require` statements are limited to files actually used within the command
|
|
287
|
+
class file itself — do not carry over `require` entries that belong only to
|
|
288
|
+
the facade (`Git::Lib`) or parser layer
|
|
289
|
+
|
|
290
|
+
## Command template (Base pattern)
|
|
291
|
+
|
|
292
|
+
The `@note` annotation in the class-level docs must record the **latest git release**
|
|
293
|
+
at the time of audit, not the "last updated in" version shown in the git-scm.com page
|
|
294
|
+
footer (which only tracks when that command's docs last changed and can be much older
|
|
295
|
+
than the current release). Determine the correct version by running:
|
|
296
|
+
|
|
297
|
+
```sh
|
|
298
|
+
bin/latest-git-version # e.g. 2.53.0
|
|
299
|
+
```
|
|
300
|
+
|
|
301
|
+
Substitute the output for `2.XX.0` in the template below. The URL will resolve to
|
|
302
|
+
the last docs update for that command even if the command docs did not change in that
|
|
303
|
+
exact release.
|
|
304
|
+
|
|
305
|
+
```ruby
|
|
306
|
+
# frozen_string_literal: true
|
|
307
|
+
|
|
308
|
+
require 'git/commands/base'
|
|
309
|
+
|
|
310
|
+
module Git
|
|
311
|
+
module Commands
|
|
312
|
+
module Foo
|
|
313
|
+
# Summary...
|
|
314
|
+
#
|
|
315
|
+
# @example Typical usage
|
|
316
|
+
# bar = Git::Commands::Foo::Bar.new(execution_context)
|
|
317
|
+
# bar.call(...)
|
|
318
|
+
#
|
|
319
|
+
# @note `arguments` block audited against https://git-scm.com/docs/git-{command}/2.XX.0
|
|
320
|
+
#
|
|
321
|
+
# @see Git::Commands::Foo
|
|
322
|
+
#
|
|
323
|
+
# @see https://git-scm.com/docs/git-{command} git-{command}
|
|
324
|
+
#
|
|
325
|
+
# @api private
|
|
326
|
+
class Bar < Git::Commands::Base # never name the class Object
|
|
327
|
+
arguments do
|
|
328
|
+
# Group related options with section comments (e.g. # Output, # Safety)
|
|
329
|
+
# NEVER add trailing inline comments (e.g. `# --verbose`) to DSL entries.
|
|
330
|
+
# The DSL is self-documenting; inline comments duplicate YARD docs and
|
|
331
|
+
# were removed project-wide in commit 370dffb.
|
|
332
|
+
end
|
|
333
|
+
|
|
334
|
+
# Optional: for commands where non-zero exits are valid
|
|
335
|
+
# rationale comment
|
|
336
|
+
# allow_exit_status 0..1
|
|
337
|
+
|
|
338
|
+
# @!method call(*, **)
|
|
339
|
+
#
|
|
340
|
+
# @overload call(operand = nil, *rest, **options)
|
|
341
|
+
#
|
|
342
|
+
# Execute the git ... command.
|
|
343
|
+
#
|
|
344
|
+
# @param operand [String, nil] (nil) short description without trailing period
|
|
345
|
+
#
|
|
346
|
+
# Continuation paragraph separated by a blank comment line. Only needed
|
|
347
|
+
# when the short description alone is insufficient.
|
|
348
|
+
#
|
|
349
|
+
# @param options [Hash] command options
|
|
350
|
+
#
|
|
351
|
+
# @option options [Boolean, nil] :simple_flag (nil) one-sentence description without period
|
|
352
|
+
#
|
|
353
|
+
# @option options [Boolean, Integer, nil] :force (nil) short description without period
|
|
354
|
+
#
|
|
355
|
+
# When an integer is given, the flag is repeated that many times (up to the
|
|
356
|
+
# configured `max_times:` limit).
|
|
357
|
+
#
|
|
358
|
+
# Alias: :f
|
|
359
|
+
#
|
|
360
|
+
# @option options [Boolean, String, nil] :complex_flag (nil) short description without period
|
|
361
|
+
#
|
|
362
|
+
# Continuation: explain the `true`/`false`/string forms here, each separated by
|
|
363
|
+
# a blank comment line from the short description above.
|
|
364
|
+
#
|
|
365
|
+
# Alias: :f
|
|
366
|
+
#
|
|
367
|
+
# @return [Git::CommandLineResult] the result of calling `git ...`
|
|
368
|
+
#
|
|
369
|
+
# @raise [ArgumentError] if unsupported options are provided
|
|
370
|
+
#
|
|
371
|
+
# @raise [Git::FailedError] if git exits with a non-zero exit status
|
|
372
|
+
end
|
|
373
|
+
end
|
|
374
|
+
end
|
|
375
|
+
end
|
|
376
|
+
```
|
|
377
|
+
|
|
378
|
+
This template uses no explicit `def call` — the `@!method` YARD directive attaches
|
|
379
|
+
per-command docs to the inherited `call` method. Use this form for simple commands
|
|
380
|
+
where no pre-call logic is needed.
|
|
381
|
+
|
|
382
|
+
> **`@raise` wording** — always use the canonical generic form that matches the
|
|
383
|
+
> command's declared exit-status range. **Never** enumerate specific failure causes
|
|
384
|
+
> (e.g. "if the branch doesn't exist"). Use:
|
|
385
|
+
>
|
|
386
|
+
> | `allow_exit_status` | Canonical `@raise` wording |
|
|
387
|
+
> | --- | --- |
|
|
388
|
+
> | none declared (default `0..0`) | `if git exits with a non-zero exit status` |
|
|
389
|
+
> | `allow_exit_status 0..1` | `if git exits outside the allowed range (exit code > 1)` |
|
|
390
|
+
> | `allow_exit_status 0..N` | `if git exits outside the allowed range (exit code > N)` |
|
|
391
|
+
|
|
392
|
+
YARD tag formatting rules (short descriptions, continuation paragraphs, punctuation)
|
|
393
|
+
are defined in the [YARD Documentation](../yard-documentation/SKILL.md) skill. The
|
|
394
|
+
template above demonstrates the correct form.
|
|
395
|
+
|
|
396
|
+
## `#call` override guidance
|
|
397
|
+
|
|
398
|
+
Most commands use only a `# @!method call(*, **)` YARD directive with no
|
|
399
|
+
explicit `def call` — the inherited `Base#call` handles binding, execution,
|
|
400
|
+
and exit-status validation automatically. Do **not** add `def call(*, **) = super`
|
|
401
|
+
or `def call(*, **) / super / end` for commands that need no custom logic; it
|
|
402
|
+
adds no behavior and conflicts with the `@!method` directive.
|
|
403
|
+
|
|
404
|
+
**Override `call` only when the command needs:**
|
|
405
|
+
|
|
406
|
+
1. **Input validation the DSL cannot express** — per-argument validation parameters
|
|
407
|
+
(`required:`, `type:`, `allow_nil:`, etc.) and operand format validation belong
|
|
408
|
+
in `arguments do`. Cross-argument constraint methods are generally **not** declared;
|
|
409
|
+
git validates its own option semantics. The narrow exception is **arguments git
|
|
410
|
+
cannot observe in its argv**: if an argument is `skip_cli: true` and never
|
|
411
|
+
reaches git's argv, git cannot detect incompatibilities — use `conflicts` and/or
|
|
412
|
+
`requires_one_of` in the DSL (e.g., `cat-file --batch` uses both because
|
|
413
|
+
`:objects` is `skip_cli: true`). Do not raise `ArgumentError` manually for things
|
|
414
|
+
the DSL can express via a constraint declaration.
|
|
415
|
+
2. **stdin feeding** — batch protocols (`--batch`, `--batch-check`) via
|
|
416
|
+
`Base#with_stdin`
|
|
417
|
+
3. **Non-trivial option routing** — build different argument sets based on
|
|
418
|
+
which options are present
|
|
419
|
+
4. **Action-option-with-optional-value** — when the command's primary action is
|
|
420
|
+
expressed as an option with an optional value (man-page notation:
|
|
421
|
+
`--flag[=<value>]`). The DSL entry uses `flag_or_value_option :name, inline:
|
|
422
|
+
true, type: [TrueClass, String]` and the override maps a positional `call` API
|
|
423
|
+
onto the keyword:
|
|
424
|
+
|
|
425
|
+
```ruby
|
|
426
|
+
def call(value = true, *, **)
|
|
427
|
+
super(*, **, option_name: value)
|
|
428
|
+
end
|
|
429
|
+
```
|
|
430
|
+
|
|
431
|
+
**When overriding:**
|
|
432
|
+
|
|
433
|
+
- Bind arguments via `args_definition.bind(...)` — do not reimplement binding
|
|
434
|
+
- Delegate exit-status handling to `validate_exit_status!` — do not reimplement
|
|
435
|
+
- Do not call `super` after manual binding; use `@execution_context.command_capturing` directly
|
|
436
|
+
|
|
437
|
+
**DSL defaults:**
|
|
438
|
+
|
|
439
|
+
Defaults defined in the DSL (e.g., `operand :paths, default: ['.']`) are applied
|
|
440
|
+
automatically during `args_definition.bind(...)` — do not set defaults manually in
|
|
441
|
+
`call`.
|
|
442
|
+
|
|
443
|
+
When the command requires an explicit `def call` override, place YARD doc comments
|
|
444
|
+
**directly above** `def call` — do **not** use `# @!method call(*, **)` alongside
|
|
445
|
+
an explicit override.
|
|
446
|
+
|
|
447
|
+
### Overriding `call` — inline example
|
|
448
|
+
|
|
449
|
+
```ruby
|
|
450
|
+
# @overload call(*objects, **options)
|
|
451
|
+
#
|
|
452
|
+
# Execute the `git cat-file --batch` command.
|
|
453
|
+
#
|
|
454
|
+
# @param objects [Array<String>] one or more object names
|
|
455
|
+
#
|
|
456
|
+
# @param options [Hash] command options
|
|
457
|
+
#
|
|
458
|
+
# @option options [Boolean, nil] :unordered (nil) unordered output
|
|
459
|
+
#
|
|
460
|
+
# @return [Git::CommandLineResult] the result of calling `git cat-file --batch`
|
|
461
|
+
#
|
|
462
|
+
# @raise [ArgumentError] if unsupported options are provided
|
|
463
|
+
#
|
|
464
|
+
# @raise [Git::FailedError] if git exits with a non-zero exit status
|
|
465
|
+
def call(*objects, **options)
|
|
466
|
+
bound = args_definition.bind(*objects, **options)
|
|
467
|
+
with_stdin(Array(bound.objects).map { |o| "#{o}\n" }.join) do |reader|
|
|
468
|
+
run_batch(bound, reader)
|
|
469
|
+
end
|
|
470
|
+
end
|
|
471
|
+
|
|
472
|
+
def run_batch(bound, reader)
|
|
473
|
+
result = @execution_context.command_capturing(*bound, in: reader, **bound.execution_options, raise_on_failure: false)
|
|
474
|
+
validate_exit_status!(result)
|
|
475
|
+
result
|
|
476
|
+
end
|
|
477
|
+
```
|
|
478
|
+
|
|
479
|
+
For stdin-driven commands where objects are domain inputs but not CLI argv, prefer
|
|
480
|
+
modeling that contract in the DSL:
|
|
481
|
+
|
|
482
|
+
```ruby
|
|
483
|
+
arguments do
|
|
484
|
+
literal 'cat-file'
|
|
485
|
+
literal '--batch-check'
|
|
486
|
+
flag_option :batch_all_objects
|
|
487
|
+
operand :objects, repeatable: true, skip_cli: true
|
|
488
|
+
|
|
489
|
+
conflicts :objects, :batch_all_objects
|
|
490
|
+
requires_one_of :objects, :batch_all_objects
|
|
491
|
+
end
|
|
492
|
+
```
|
|
493
|
+
|
|
494
|
+
### Action-option-with-optional-value commands
|
|
495
|
+
|
|
496
|
+
When a git command's primary action is an option with an optional value (man-page
|
|
497
|
+
notation: `--flag[=<value>]`, e.g. `git am --show-current-patch[=(diff|raw)]`), use
|
|
498
|
+
this pattern:
|
|
499
|
+
|
|
500
|
+
**DSL:**
|
|
501
|
+
```ruby
|
|
502
|
+
arguments do
|
|
503
|
+
literal 'am'
|
|
504
|
+
flag_or_value_option :show_current_patch, inline: true, type: [TrueClass, String]
|
|
505
|
+
end
|
|
506
|
+
```
|
|
507
|
+
|
|
508
|
+
**`#call` override** — required to give callers a natural positional API:
|
|
509
|
+
```ruby
|
|
510
|
+
# Show the patch currently being applied by `git am`
|
|
511
|
+
#
|
|
512
|
+
# @param value [true, String] when +true+ (default), emits +--show-current-patch+
|
|
513
|
+
# (git's default behavior). Pass +"diff"+ or +"raw"+ to emit
|
|
514
|
+
# +--show-current-patch=diff+ / +--show-current-patch=raw+.
|
|
515
|
+
#
|
|
516
|
+
# @return [Git::CommandLineResult] the result of the command
|
|
517
|
+
#
|
|
518
|
+
# @raise [Git::FailedError] if no am session is in progress
|
|
519
|
+
#
|
|
520
|
+
def call(value = true, *, **)
|
|
521
|
+
super(*, **, option_name: value)
|
|
522
|
+
end
|
|
523
|
+
```
|
|
524
|
+
|
|
525
|
+
Where:
|
|
526
|
+
- `value = true` — positional default; `true` emits `--flag`; a String emits
|
|
527
|
+
`--flag=value`
|
|
528
|
+
- `*` — forwards positional operands declared in the DSL (omit when the command has
|
|
529
|
+
none)
|
|
530
|
+
- `**` — forwards keyword options to the DSL binder; unknown keywords raise
|
|
531
|
+
`ArgumentError`
|
|
532
|
+
- `option_name: value` placed last so the positional arg always takes precedence
|
|
533
|
+
|
|
534
|
+
The `type: [TrueClass, String]` on the DSL entry rejects `false` at bind time,
|
|
535
|
+
removing the need for manual validation in the override.
|
|
536
|
+
|
|
537
|
+
### When to use `skip_cli` on `operand`
|
|
538
|
+
|
|
539
|
+
Use `operand ..., skip_cli: true` when all of the following are true:
|
|
540
|
+
|
|
541
|
+
- The value is part of the Ruby `#call` interface and should be bound/validated by
|
|
542
|
+
the DSL
|
|
543
|
+
- The value should remain accessible on `bound` (for cross-field constraints, helper
|
|
544
|
+
methods, and documentation)
|
|
545
|
+
- The value must **not** be emitted into CLI argv (for example, it is sent via stdin
|
|
546
|
+
protocol)
|
|
547
|
+
|
|
548
|
+
Do **not** use `skip_cli` for execution-engine kwargs (`timeout:`, `chdir:`, etc.) —
|
|
549
|
+
those belong to `execution_option`.
|
|
550
|
+
|
|
551
|
+
Key points:
|
|
552
|
+
|
|
553
|
+
- **`in:` requires a real IO object.** `Process.spawn` only accepts objects with a
|
|
554
|
+
file descriptor; `StringIO` does not work. `Base#with_stdin` handles this by
|
|
555
|
+
opening an `IO.pipe` and spawning a background `Thread` that writes the content to
|
|
556
|
+
the write end (then closes it). The threaded write prevents deadlocks when content
|
|
557
|
+
exceeds the OS pipe buffer — the subprocess can drain the pipe concurrently. The
|
|
558
|
+
thread rescues `Errno::EPIPE` / `IOError` so it exits cleanly if the subprocess
|
|
559
|
+
closes stdin early. Pass an empty string when the process should receive no input
|
|
560
|
+
(e.g. when a `--batch-all-objects`-style flag makes git enumerate objects itself).
|
|
561
|
+
- **Extract helpers** like `run_batch` to stay within Rubocop `Metrics/MethodLength`
|
|
562
|
+
and `Metrics/AbcSize` thresholds. Aim to keep `call` under ~10 lines.
|
|
563
|
+
|
|
564
|
+
## `Base#with_stdin` mechanics
|
|
565
|
+
|
|
566
|
+
`Base#with_stdin(content)` opens an `IO.pipe`, spawns a background `Thread` that
|
|
567
|
+
writes `content` to the write end (then closes it), and yields the read end as
|
|
568
|
+
`in:` to the execution context. The threaded write prevents deadlocks when
|
|
569
|
+
`content` exceeds the OS pipe buffer — the subprocess can drain the pipe
|
|
570
|
+
concurrently. The thread also rescues `Errno::EPIPE` / `IOError` so it exits
|
|
571
|
+
cleanly if the subprocess closes stdin early.
|
|
572
|
+
|
|
573
|
+
Use `with_stdin` instead of manual pipe management. `StringIO` cannot be used
|
|
574
|
+
because `Process.spawn` requires a real file descriptor.
|
|
575
|
+
|
|
576
|
+
Example — batch stdin protocol (as used by `git cat-file --batch`):
|
|
577
|
+
|
|
578
|
+
```ruby
|
|
579
|
+
def call(*, **)
|
|
580
|
+
bound = args_definition.bind(*, **)
|
|
581
|
+
with_stdin(Array(bound.objects).map { |object| "#{object}\n" }.join) do |stdin_r|
|
|
582
|
+
run_batch(bound, stdin_r)
|
|
583
|
+
end
|
|
584
|
+
end
|
|
585
|
+
```
|
|
586
|
+
|
|
587
|
+
## Options completeness — consult the latest-version docs first
|
|
588
|
+
|
|
589
|
+
**Before writing any DSL entries**, use the documentation fetched during the
|
|
590
|
+
[Input](SKILL.md#git-documentation-for-the-git-command) phase and enumerate every
|
|
591
|
+
option the latest-version docs describe.
|
|
592
|
+
|
|
593
|
+
### `requires_git_version` convention
|
|
594
|
+
|
|
595
|
+
`requires_git_version` is a **class-level** declaration only. Individual options do
|
|
596
|
+
**not** carry version annotations. The declaration must use a `'major.minor.patch'`
|
|
597
|
+
string (e.g., `'2.29.0'`), not a `Git::Version` or `Range` — pre-release versions
|
|
598
|
+
are not supported.
|
|
599
|
+
|
|
600
|
+
| Scenario | Action |
|
|
601
|
+
| --- | --- |
|
|
602
|
+
| Command exists in `Git::MINIMUM_GIT_VERSION` | Do **not** add `requires_git_version` |
|
|
603
|
+
| Command was introduced after `Git::MINIMUM_GIT_VERSION` | Add `requires_git_version '<version>'` at the version the command was introduced |
|
|
604
|
+
|
|
605
|
+
Options that were added to a command after `Git::MINIMUM_GIT_VERSION` are still
|
|
606
|
+
scaffolded in the DSL — they are **not** omitted. When a caller passes such an option
|
|
607
|
+
on an older git installation, git itself will produce its native "unknown option"
|
|
608
|
+
error. This is acceptable and expected; the ruby-git library does not gate individual
|
|
609
|
+
options by version.
|
|
610
|
+
|
|
611
|
+
For each option, make one of three decisions:
|
|
612
|
+
|
|
613
|
+
| Decision | Reason | Action |
|
|
614
|
+
| --- | --- | --- |
|
|
615
|
+
| **Include** | All behavioral options — including output-format flags (`--pretty=`, `--patch`, `--numstat`, `--name-only`, etc.) and filtering/selection flags | Add to `arguments do` |
|
|
616
|
+
| **Exclude (wrong sub-action)** | Option belongs to a different sub-action than the one this class implements | Omit — see [Scoping options to sub-command classes](#scoping-options-to-sub-command-classes) below |
|
|
617
|
+
| **Exclude (execution-model conflict)** | Requires TTY input or otherwise makes the command incompatible with non-interactive subprocess execution | Omit — see [Execution-model conflicts](#execution-model-conflicts) below |
|
|
618
|
+
|
|
619
|
+
Group related options with a comment in the DSL (e.g. `# Ref inclusion`, `# Date
|
|
620
|
+
filtering`, `# Commit ordering`). Follow the section groupings from the
|
|
621
|
+
latest-version documentation — this makes it easy for a reviewer to cross-check
|
|
622
|
+
against the docs.
|
|
623
|
+
|
|
624
|
+
**Pairs and opposites:** when the latest-version docs document `--foo` / `--no-foo`
|
|
625
|
+
as explicit flags, model them as a single `flag_option :foo, negatable: true` rather
|
|
626
|
+
than two separate DSL declarations. This registers both `:foo` (positive) and `:no_foo`
|
|
627
|
+
(negative) as independent boolean keys that follow standard boolean semantics:
|
|
628
|
+
`true` emits the flag, `false`/`nil` emits nothing. Use `no_foo: true` at the call
|
|
629
|
+
site to emit `--no-foo`.
|
|
630
|
+
|
|
631
|
+
**Short-flag aliases — cross-check the latest-version docs/source:** before adding
|
|
632
|
+
any short-flag alias (e.g. `%i[dry_run n]`), verify the alias character appears on
|
|
633
|
+
the same option heading in the documentation or parser for the latest supported
|
|
634
|
+
version (`-n, --dry-run`). Do **not** invent an alias that the latest-version sources
|
|
635
|
+
do not document — in this DSL, short aliases are Ruby-keyword aliases for ergonomics
|
|
636
|
+
and documentation parity, while CLI emission still follows the primary option name's
|
|
637
|
+
flag spec. Symmetrically, every option the latest-version docs document with a short
|
|
638
|
+
form **must** have an alias in the DSL (long name first: `%i[dry_run n]`).
|
|
639
|
+
|
|
640
|
+
**`as:` is an escape hatch, not a default tool:** treat `as:` as suspicious by
|
|
641
|
+
default and use it only when the required argv cannot be expressed by the normal DSL
|
|
642
|
+
mapping plus existing modifiers (`negatable:`, `inline:`, `as_operand:`,
|
|
643
|
+
`max_times:`, etc.). If a plain symbol, alias, or first-class modifier can express
|
|
644
|
+
the same behavior, prefer that. In particular, do not use `as:` to encode repeated
|
|
645
|
+
flags now that `max_times:` exists.
|
|
646
|
+
|
|
647
|
+
**`inline: true` for `=<value>` options:** when the latest-version docs show
|
|
648
|
+
`--option=<value>` (with an `=`), the DSL entry must include `inline: true`
|
|
649
|
+
regardless of whether the DSL method is `value_option` or `flag_or_value_option`.
|
|
650
|
+
Without it, the value is emitted as a separate token (`--option value`) instead of
|
|
651
|
+
the expected inline form (`--option=value`). Check every `value_option` and
|
|
652
|
+
`flag_or_value_option` entry against the latest-version signature.
|
|
653
|
+
|
|
654
|
+
**Constraint declarations are generally not used in command classes.** Do not add
|
|
655
|
+
`conflicts`, `requires`, `requires_one_of`, `requires_exactly_one_of`,
|
|
656
|
+
`forbid_values`, or `allowed_values` declarations to command classes. Git is the
|
|
657
|
+
single source of truth for its own option semantics. There are two narrow exceptions:
|
|
658
|
+
|
|
659
|
+
1. **`skip_cli: true` arguments** — the argument never reaches git's argv, so git
|
|
660
|
+
cannot detect incompatibilities and constraint declarations are appropriate (see
|
|
661
|
+
the `cat-file --batch` example above: `:objects` is `skip_cli: true`, so git never
|
|
662
|
+
sees it and cannot detect the conflict or the absent-both case).
|
|
663
|
+
2. **Git-visible arguments that cause silent data loss** — if a combination of
|
|
664
|
+
git-visible arguments causes git to silently discard data (no error, wrong
|
|
665
|
+
result), a `conflicts` declaration MAY be added with: a code comment explaining
|
|
666
|
+
why, a reference to the git version(s) where the behavior was verified, and a
|
|
667
|
+
test. As of this writing, no such case has been identified.
|
|
668
|
+
3. **Mode-scoped flags explicitly constrained by the git docs** — if the git
|
|
669
|
+
documentation explicitly states that a flag only applies to certain modes or
|
|
670
|
+
option combinations (e.g., `--allow-unknown-type` is documented as "Allow
|
|
671
|
+
`-s` or `-t` to query broken/corrupt objects of unknown type"), a
|
|
672
|
+
`requires_one_of :mode_a, :mode_b, when: :flag` declaration is appropriate. Add
|
|
673
|
+
a DSL comment noting the constraint and a unit test asserting the ArgumentError.
|
|
674
|
+
|
|
675
|
+
```ruby
|
|
676
|
+
# Allow -t and -s to query broken or corrupt objects of unknown type;
|
|
677
|
+
# rejected by git in any other mode — enforced by constraint below
|
|
678
|
+
flag_option :allow_unknown_type
|
|
679
|
+
# ...
|
|
680
|
+
requires_one_of :t, :s, when: :allow_unknown_type
|
|
681
|
+
```
|
|
682
|
+
|
|
683
|
+
See `redesign/3_architecture_implementation.md` Insight 6 for the full policy.
|
|
684
|
+
|
|
685
|
+
This step is required. A command class that only exposes the options that happen to
|
|
686
|
+
be used today in `Git::Lib` is incomplete — callers of the future API should not need
|
|
687
|
+
to re-open the docs just because the scaffold only covered current usage.
|
|
688
|
+
|
|
689
|
+
### Scoping options to sub-command classes
|
|
690
|
+
|
|
691
|
+
When a git command is split into sub-command classes (e.g., `Branch::Create`,
|
|
692
|
+
`Branch::List`, `Branch::Delete`), each class must include **only** the options that
|
|
693
|
+
apply to the sub-action it implements. Do **not** enumerate every option on the man
|
|
694
|
+
page — most git commands document options for all modes on a single page, and adding
|
|
695
|
+
options that belong to a different mode produces a class that accepts arguments git
|
|
696
|
+
will reject or misinterpret.
|
|
697
|
+
|
|
698
|
+
**How to determine which options belong to a sub-action:**
|
|
699
|
+
|
|
700
|
+
1. **Read the SYNOPSIS** — git man pages list separate SYNOPSIS lines per mode
|
|
701
|
+
(e.g., `git branch [--list]`, `git branch -d`, `git branch -m`). Only options
|
|
702
|
+
shown on the SYNOPSIS line for the target sub-action are candidates.
|
|
703
|
+
|
|
704
|
+
2. **Cross-reference the DESCRIPTION and OPTIONS sections** — some options are
|
|
705
|
+
described generally but only apply to specific modes. Check each option's
|
|
706
|
+
description for phrases like "only useful with `--list`" or "when used with
|
|
707
|
+
`-d`". If the docs explicitly tie an option to a different mode, exclude it.
|
|
708
|
+
|
|
709
|
+
3. **Common/shared options** — options that appear on every SYNOPSIS line or are
|
|
710
|
+
described as applying to the command as a whole (e.g., `--quiet`, `--verbose`)
|
|
711
|
+
should be included in every sub-command class where they are meaningful.
|
|
712
|
+
|
|
713
|
+
**Example — `git branch`:**
|
|
714
|
+
|
|
715
|
+
| Option | Create | List | Delete | Move/Copy |
|
|
716
|
+
| --- | --- | --- | --- | --- |
|
|
717
|
+
| `--track` | Yes | — | — | — |
|
|
718
|
+
| `--force` | Yes | — | Yes | Yes |
|
|
719
|
+
| `--sort` | — | Yes | — | — |
|
|
720
|
+
| `--format` | — | Yes | — | — |
|
|
721
|
+
| `--merged` | — | Yes | — | — |
|
|
722
|
+
| `--quiet` | Yes | — | Yes | — |
|
|
723
|
+
| `--color` | — | Yes | — | — |
|
|
724
|
+
|
|
725
|
+
This rule applies **only** when the command is split into sub-command classes. For
|
|
726
|
+
single-class commands, include all options as described in the decision table above.
|
|
727
|
+
|
|
728
|
+
### Execution-model conflicts
|
|
729
|
+
|
|
730
|
+
Command classes are neutral — they never hardcode policy choices. Policy defaults
|
|
731
|
+
(`no_edit: true`, `no_progress: true`, etc.) belong to the facade (`Git::Lib`).
|
|
732
|
+
|
|
733
|
+
> **Anti-pattern:** `literal '--no-edit'` inside a command class.
|
|
734
|
+
>
|
|
735
|
+
> **Correct pattern:** `flag_option :edit, negatable: true` in the command; `no_edit:
|
|
736
|
+
> true` passed from the facade call site.
|
|
737
|
+
|
|
738
|
+
The **only** options to exclude from the DSL are those that conflict with
|
|
739
|
+
non-interactive subprocess execution:
|
|
740
|
+
|
|
741
|
+
- `--interactive` / `-i` — requires a TTY
|
|
742
|
+
- `--patch` (interactive form, e.g. `git add -p`) — requires TTY prompts
|
|
743
|
+
- Any option requiring stdin/TTY interaction the library cannot provide
|
|
744
|
+
|
|
745
|
+
> **Note on `--patch`:** In `git add -p` it opens an interactive session (exclude).
|
|
746
|
+
> In `git diff --patch` it selects a non-interactive output format (include).
|
|
747
|
+
> Evaluate per-command, not globally.
|
|
748
|
+
|
|
749
|
+
**`--edit` / `--no-edit`:** Model as `flag_option :edit, negatable: true`. Do
|
|
750
|
+
**not** hardcode `literal '--no-edit'`. Pass `no_edit: true` from the facade call
|
|
751
|
+
site. See "Command-layer neutrality" in CONTRIBUTING.md.
|
|
752
|
+
|
|
753
|
+
**`--verbose`/`-v` and `--quiet`/`-q`:** include these unless their git
|
|
754
|
+
implementation requires interactive I/O.
|
|
755
|
+
|
|
756
|
+
## `end_of_options` placement
|
|
757
|
+
|
|
758
|
+
Determine placement based on whether the SYNOPSIS explicitly shows `--`. See the
|
|
759
|
+
Review Arguments DSL checklist for the full decision tree.
|
|
760
|
+
|
|
761
|
+
### Rule 1 — SYNOPSIS shows `--`: mirror the SYNOPSIS
|
|
762
|
+
|
|
763
|
+
When the SYNOPSIS explicitly shows `--` between operand groups (e.g., `[<tree-ish>]
|
|
764
|
+
[--] [<pathspec>...]`), place `end_of_options` in the same position the SYNOPSIS
|
|
765
|
+
shows it — after the pre-`--` operands, before the post-`--` group. See the Review
|
|
766
|
+
Arguments DSL checklist ("Choosing the correct pathspec form") for how to model the
|
|
767
|
+
post-`--` group (`value_option ... as_operand: true`).
|
|
768
|
+
|
|
769
|
+
**Do not apply Rule 2** when Rule 1 applies.
|
|
770
|
+
|
|
771
|
+
```ruby
|
|
772
|
+
# git diff [<tree-ish>] [--] [<pathspec>...]
|
|
773
|
+
operand :tree_ish # BEFORE end_of_options
|
|
774
|
+
end_of_options # mirrors SYNOPSIS position
|
|
775
|
+
value_option :pathspec, as_operand: true, repeatable: true # AFTER end_of_options
|
|
776
|
+
```
|
|
777
|
+
|
|
778
|
+
### Rule 2 — SYNOPSIS does NOT show `--`: protect operands from flag misinterpretation
|
|
779
|
+
|
|
780
|
+
Insert `end_of_options` immediately before the first `operand` whenever any
|
|
781
|
+
`flag_option`, `value_option`, `flag_or_value_option`, `key_value_option`, or
|
|
782
|
+
`custom_option` appears earlier in the `arguments do` block. This prevents operands
|
|
783
|
+
from being misinterpreted as flags when a caller passes a value that starts with `-`.
|
|
784
|
+
|
|
785
|
+
`literal` entries are **never** the trigger — regardless of whether their value is
|
|
786
|
+
option-style (e.g. `literal '--delete'`) or a plain subcommand word (e.g. `literal
|
|
787
|
+
'remove'`). Only the five DSL option methods above matter.
|
|
788
|
+
|
|
789
|
+
```ruby
|
|
790
|
+
# ✅ Correct — flag_option triggers Rule 2; end_of_options inserted before first operand
|
|
791
|
+
arguments do
|
|
792
|
+
literal 'remote'
|
|
793
|
+
literal 'rename'
|
|
794
|
+
flag_option :progress, negatable: true # ← this triggers Rule 2
|
|
795
|
+
|
|
796
|
+
end_of_options
|
|
797
|
+
|
|
798
|
+
operand :old, required: true
|
|
799
|
+
operand :new, required: true
|
|
800
|
+
end
|
|
801
|
+
|
|
802
|
+
# ✅ Not needed — only literal entries precede the operand; no DSL option-flag methods
|
|
803
|
+
arguments do
|
|
804
|
+
literal 'remote'
|
|
805
|
+
literal 'remove'
|
|
806
|
+
operand :name, required: true # no DSL option methods → not required
|
|
807
|
+
end
|
|
808
|
+
```
|
|
809
|
+
|
|
810
|
+
`end_of_options` is always safe to add even when not strictly required. Omit it by
|
|
811
|
+
convention when neither rule applies: it adds no defensive value and produces
|
|
812
|
+
unnecessarily verbose command lines (e.g. `git remote remove -- origin` instead of
|
|
813
|
+
`git remote remove origin`). When in doubt, add it — the Review Arguments DSL skill
|
|
814
|
+
flags a missing `end_of_options` as an error when options appear before operands.
|
|
815
|
+
|
|
816
|
+
## Exit status guidance
|
|
817
|
+
|
|
818
|
+
- Default: no declaration needed (`0..0` from `Base`)
|
|
819
|
+
- Non-default: declare `allow_exit_status <range>` and add rationale comment
|
|
820
|
+
|
|
821
|
+
Examples:
|
|
822
|
+
|
|
823
|
+
```ruby
|
|
824
|
+
# git diff exits 1 when differences are found (not an error)
|
|
825
|
+
allow_exit_status 0..1
|
|
826
|
+
```
|
|
827
|
+
|
|
828
|
+
```ruby
|
|
829
|
+
# fsck uses exit codes 0-7 as bit flags for findings
|
|
830
|
+
allow_exit_status 0..7
|
|
831
|
+
```
|
|
832
|
+
|
|
833
|
+
## Facade delegation and policy options
|
|
834
|
+
|
|
835
|
+
The command class is only half the story. After scaffolding the command, you must
|
|
836
|
+
also write (or update) the `Git::Lib` method that **delegates** to it. The facade
|
|
837
|
+
sets safe policy defaults at each call site — `no_edit: true`, `no_progress: true`,
|
|
838
|
+
etc. — not as `literal` entries inside the command class. See "Command-layer
|
|
839
|
+
neutrality" in CONTRIBUTING.md.
|
|
840
|
+
|
|
841
|
+
Policy defaults fall into two categories (see also
|
|
842
|
+
[facade-implementation/REFERENCE.md](../facade-implementation/REFERENCE.md)):
|
|
843
|
+
|
|
844
|
+
- **Fixed policy defaults** (`no_edit: true`, `no_progress: true`, `no_color: true`,
|
|
845
|
+
format strings): set unconditionally and **not** included in `ALLOWED_OPTS`.
|
|
846
|
+
`assert_valid_opts` rejects any caller-supplied value for these keys, enforcing
|
|
847
|
+
the policy. They are not part of the public API.
|
|
848
|
+
- **Overridable policy defaults** (e.g., `verbose: false`): included in `ALLOWED_OPTS`.
|
|
849
|
+
The facade sets a sensible default but callers may override it by passing a value
|
|
850
|
+
that goes through `**opts`.
|
|
851
|
+
|
|
852
|
+
```ruby
|
|
853
|
+
# lib/git/lib.rb — facade method for `git pull`
|
|
854
|
+
|
|
855
|
+
PULL_ALLOWED_OPTS = %i[allow_unrelated_histories].freeze
|
|
856
|
+
|
|
857
|
+
def pull(remote = nil, branch = nil, opts = {})
|
|
858
|
+
raise ArgumentError, 'You must specify a remote if a branch is specified' if remote.nil? && !branch.nil?
|
|
859
|
+
|
|
860
|
+
assert_valid_opts(opts, PULL_ALLOWED_OPTS)
|
|
861
|
+
allowed_opts = opts.slice(*PULL_ALLOWED_OPTS)
|
|
862
|
+
positional_args = [remote, branch].compact
|
|
863
|
+
# no_edit: true is the non-interactive default (see CONTRIBUTING.md)
|
|
864
|
+
Git::Commands::Pull.new(self).call(*positional_args, no_edit: true, **allowed_opts).stdout
|
|
865
|
+
end
|
|
866
|
+
```
|
|
867
|
+
|
|
868
|
+
Key points for the facade method:
|
|
869
|
+
|
|
870
|
+
- **Filter options** — declare an `ALLOWED_OPTS` constant listing only the options
|
|
871
|
+
the public API accepted at v4.3.0. Use `assert_valid_opts` + `opts.slice` to
|
|
872
|
+
prevent accidental API expansion.
|
|
873
|
+
- **Pass policy options as safe defaults** — `no_edit: true`, `no_progress: true`, etc.
|
|
874
|
+
Fixed policy defaults go directly in the command call (not in `ALLOWED_OPTS`).
|
|
875
|
+
Overridable policy defaults are placed before `**opts` in the command call so the
|
|
876
|
+
caller's value wins on key collision, and are included in `ALLOWED_OPTS`. Add a
|
|
877
|
+
comment explaining *why* (e.g., `# non-interactive default`).
|
|
878
|
+
- **Return the legacy type** — typically `.stdout` or a parsed struct, not
|
|
879
|
+
`CommandLineResult`.
|
|
880
|
+
|
|
881
|
+
See [Extract Command from Lib](../extract-command-from-lib/SKILL.md) for the complete
|
|
882
|
+
delegation workflow and additional patterns (stdout passthrough, parsed return
|
|
883
|
+
values, opts-hash normalization).
|
|
884
|
+
|
|
885
|
+
## Internal compatibility contract
|
|
886
|
+
|
|
887
|
+
This is the canonical location for the internal compatibility contract. Other
|
|
888
|
+
skills reference this section rather than duplicating it.
|
|
889
|
+
|
|
890
|
+
Ensure refactors preserve these contract expectations:
|
|
891
|
+
|
|
892
|
+
- constructor shape remains `initialize(execution_context)` (inherited from `Base`)
|
|
893
|
+
- command entrypoint remains `call(*, **)` at runtime (via `Base#call`)
|
|
894
|
+
- argument-definition metadata remains available via `args_definition`
|
|
895
|
+
|
|
896
|
+
If an intentional deviation exists, require migration notes/changelog documentation.
|
|
897
|
+
|
|
898
|
+
## Phased rollout requirements
|
|
899
|
+
|
|
900
|
+
This is the canonical location for phased rollout requirements. Other skills
|
|
901
|
+
reference this section rather than duplicating the full checklist.
|
|
902
|
+
|
|
903
|
+
For migration PRs, verify process constraints:
|
|
904
|
+
|
|
905
|
+
- changes are on a feature branch — **never commit or push directly to `main`**
|
|
906
|
+
- migration slice is scoped (pilot or one family), not all commands at once
|
|
907
|
+
- each slice is independently revertible
|
|
908
|
+
- refactor-only changes are not mixed with unrelated behavior changes
|
|
909
|
+
- quality gates pass for the slice — discover tasks via
|
|
910
|
+
`bundle exec ruby -e "require 'rake'; load 'Rakefile'; puts Rake::Task['default:parallel'].prerequisites"`
|
|
911
|
+
and run each individually via `bundle exec rake <task>`, fixing failures before
|
|
912
|
+
advancing
|
|
913
|
+
|
|
914
|
+
## Common failures
|
|
915
|
+
|
|
916
|
+
### Policy/output-control flag hardcoded as `literal` (neutrality violation)
|
|
917
|
+
|
|
918
|
+
`literal` entries for output-control, editor-suppression, progress, or verbose
|
|
919
|
+
flags inside a command class violate the neutrality principle. The command class
|
|
920
|
+
must model the git CLI faithfully; the facade sets safe defaults and callers may
|
|
921
|
+
override them.
|
|
922
|
+
|
|
923
|
+
Symptom: the command class contains one or more of:
|
|
924
|
+
|
|
925
|
+
```ruby
|
|
926
|
+
# ❌ Any of these are neutrality violations
|
|
927
|
+
literal '--no-edit'
|
|
928
|
+
literal '--verbose'
|
|
929
|
+
literal '--no-progress'
|
|
930
|
+
literal '--no-color'
|
|
931
|
+
literal '--porcelain'
|
|
932
|
+
```
|
|
933
|
+
|
|
934
|
+
Fix: convert each to a DSL option and pass the policy value from the facade:
|
|
935
|
+
|
|
936
|
+
```ruby
|
|
937
|
+
# ✅ In the command class — neutral DSL declaration
|
|
938
|
+
flag_option :edit, negatable: true
|
|
939
|
+
flag_option :progress, negatable: true
|
|
940
|
+
flag_option :verbose
|
|
941
|
+
value_option :format
|
|
942
|
+
|
|
943
|
+
# ✅ In Git::Lib — facade passes the policy value explicitly
|
|
944
|
+
Git::Commands::Pull.new(self).call(no_edit: true, no_progress: true)
|
|
945
|
+
Git::Commands::Mv.new(self).call(*args, verbose: true)
|
|
946
|
+
Git::Commands::Fsck.new(self).call(no_progress: true)
|
|
947
|
+
```
|
|
948
|
+
|
|
949
|
+
See "Command-layer neutrality" in CONTRIBUTING.md for the full policy.
|
|
950
|
+
|
|
951
|
+
### Unnecessary `def call` override
|
|
952
|
+
|
|
953
|
+
Do **not** add `def call(*, **) = super` or `def call(*, **) / super / end` for
|
|
954
|
+
commands that need no custom logic; it adds no behavior and conflicts with the
|
|
955
|
+
`@!method` directive.
|
|
956
|
+
|
|
957
|
+
### `execution_option` for fixed kwargs
|
|
958
|
+
|
|
959
|
+
`execution_option` must **not** be used for kwargs whose value must be
|
|
960
|
+
unconditionally fixed regardless of caller input. If a kwarg always has a specific
|
|
961
|
+
required value (e.g. `chomp: false` for commands returning raw content where trailing
|
|
962
|
+
newlines are data), hardcode it in a `def call` override instead — exposing it via
|
|
963
|
+
`execution_option` would allow callers to override a value that must never change.
|
|
964
|
+
|
|
965
|
+
### Unnecessary `require` statements
|
|
966
|
+
|
|
967
|
+
A command class file should only `require` what it actually uses. The canonical
|
|
968
|
+
example is parser requires: `require 'git/parsers/foo'` is needed by the facade
|
|
969
|
+
(`Git::Lib`) but not by the command class itself — the command class just runs git
|
|
970
|
+
and returns `CommandLineResult`.
|
|
971
|
+
|
|
972
|
+
```ruby
|
|
973
|
+
# ❌ Command class does not use Git::Parsers::Branch
|
|
974
|
+
require 'git/parsers/branch'
|
|
975
|
+
require 'git/commands/base'
|
|
976
|
+
|
|
977
|
+
# ✅ Only what the file actually uses
|
|
978
|
+
require 'git/commands/base'
|
|
979
|
+
```
|
|
980
|
+
|
|
981
|
+
**Review mode:** flag any `require` beyond `git/commands/base` (and `git/commands/branch` for sub-command files) unless a constant from that file is referenced in the command class body.
|
|
982
|
+
|
|
983
|
+
**Update mode:** remove flagged `require` statements.
|
|
984
|
+
|
|
985
|
+
### Other common failures
|
|
986
|
+
|
|
987
|
+
- lingering `ARGS = Arguments.define` constant and custom `#call`
|
|
988
|
+
- command-specific duplicated exit-status checks instead of `allow_exit_status`
|
|
989
|
+
- missing rationale comment for `allow_exit_status`
|
|
990
|
+
- missing YARD directive (`# @!method call(*, **)`)
|
|
991
|
+
- `call` override that reimplements `Base#call` logic instead of delegating to `validate_exit_status!`
|
|
992
|
+
- using a manual `IO.pipe` inline instead of `Base#with_stdin` for stdin-feeding commands
|
|
993
|
+
- migration PR scope too broad (not phased)
|