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,788 @@
|
|
|
1
|
+
# Arguments DSL Checklist
|
|
2
|
+
|
|
3
|
+
- [1. Determine scope and exclusions](#1-determine-scope-and-exclusions)
|
|
4
|
+
- [Options excluded because they belong to a different sub-action](#options-excluded-because-they-belong-to-a-different-sub-action)
|
|
5
|
+
- [Options excluded due to execution-model conflicts](#options-excluded-due-to-execution-model-conflicts)
|
|
6
|
+
- [2. Verify DSL method per option type](#2-verify-dsl-method-per-option-type)
|
|
7
|
+
- [Recognizing `flag_or_value_option` from the git docs](#recognizing-flag_or_value_option-from-the-git-docs)
|
|
8
|
+
- [Action-option-with-optional-value commands](#action-option-with-optional-value-commands)
|
|
9
|
+
- [Choosing the correct pathspec form](#choosing-the-correct-pathspec-form)
|
|
10
|
+
- [Quick reference](#quick-reference)
|
|
11
|
+
- [3. Verify alias and `as:` usage](#3-verify-alias-and-as-usage)
|
|
12
|
+
- [The `as:` escape hatch](#the-as-escape-hatch)
|
|
13
|
+
- [Prefer first-class DSL features over `as:`](#prefer-first-class-dsl-features-over-as)
|
|
14
|
+
- [Single-char flags never need `as:`](#single-char-flags-never-need-as)
|
|
15
|
+
- [Short-flag alias completeness](#short-flag-alias-completeness)
|
|
16
|
+
- [Spurious aliases](#spurious-aliases)
|
|
17
|
+
- [4. Verify ordering](#4-verify-ordering)
|
|
18
|
+
- [Comments in the DSL block](#comments-in-the-dsl-block)
|
|
19
|
+
- [`end_of_options` placement](#end_of_options-placement)
|
|
20
|
+
- [Rule 1 — SYNOPSIS shows `--`: mirror the SYNOPSIS](#rule-1--synopsis-shows----mirror-the-synopsis)
|
|
21
|
+
- [Rule 2 — SYNOPSIS does NOT show `--`: protect operands from flag misinterpretation](#rule-2--synopsis-does-not-show----protect-operands-from-flag-misinterpretation)
|
|
22
|
+
- [Choosing the `as:` token](#choosing-the-as-token)
|
|
23
|
+
- [5. Verify modifiers](#5-verify-modifiers)
|
|
24
|
+
- [`execution_option` usage](#execution_option-usage)
|
|
25
|
+
- [6. Check completeness](#6-check-completeness)
|
|
26
|
+
- [YARD documentation ↔ DSL parity](#yard-documentation--dsl-parity)
|
|
27
|
+
- [Repeatable boolean flags](#repeatable-boolean-flags)
|
|
28
|
+
- [Operand naming](#operand-naming)
|
|
29
|
+
- [Per-argument validation completeness](#per-argument-validation-completeness)
|
|
30
|
+
- [7. Check class-level declarations](#7-check-class-level-declarations)
|
|
31
|
+
|
|
32
|
+
## 1. Determine scope and exclusions
|
|
33
|
+
|
|
34
|
+
Before auditing individual DSL entries, determine which git options are in scope.
|
|
35
|
+
Reference documents and source files are loaded during the [Input phase](SKILL.md#input).
|
|
36
|
+
|
|
37
|
+
### Options excluded because they belong to a different sub-action
|
|
38
|
+
|
|
39
|
+
When a command is split into sub-command classes (e.g., `Branch::Create` vs.
|
|
40
|
+
`Branch::List`), each class includes **only** the options that apply to its
|
|
41
|
+
sub-action. Do **not** add every option from the man page — git documents all modes
|
|
42
|
+
on a single page.
|
|
43
|
+
|
|
44
|
+
To determine which options belong to a sub-action:
|
|
45
|
+
|
|
46
|
+
1. **Read the SYNOPSIS** — git man pages list separate SYNOPSIS lines per mode.
|
|
47
|
+
Only options shown on the SYNOPSIS line for the target sub-action are candidates.
|
|
48
|
+
2. **Cross-reference DESCRIPTION and OPTIONS sections** — check each option's
|
|
49
|
+
description for phrases like "only useful with `--list`" or "when used with
|
|
50
|
+
`-d`". If the docs explicitly tie an option to a different mode, exclude it.
|
|
51
|
+
3. **Common/shared options** — options on every SYNOPSIS line or described as
|
|
52
|
+
applying to the command as a whole (e.g., `--quiet`, `--verbose`) belong in
|
|
53
|
+
every sub-command class where they are meaningful.
|
|
54
|
+
|
|
55
|
+
This rule applies **only** to split commands. For single-class commands, include all
|
|
56
|
+
options (subject to execution-model exclusions below).
|
|
57
|
+
|
|
58
|
+
### Options excluded due to execution-model conflicts
|
|
59
|
+
|
|
60
|
+
Include ALL git options in the DSL by default — including output-format flags such as
|
|
61
|
+
`--patch`, `--numstat`, `--raw`, `--format=…`, `--pretty=…`, `--no-color`, etc.
|
|
62
|
+
|
|
63
|
+
The only options that should be **excluded** are those that conflict with the
|
|
64
|
+
subprocess execution model: options that require TTY input or otherwise make the
|
|
65
|
+
command incompatible with non-interactive subprocess execution:
|
|
66
|
+
|
|
67
|
+
Examples of options to **exclude** (execution-model conflicts):
|
|
68
|
+
|
|
69
|
+
- `--interactive` / `-i` — opens an interactive menu; requires a TTY
|
|
70
|
+
- `--patch` (interactive form, e.g. `git add -p`) — requires TTY prompts
|
|
71
|
+
- Any option whose git implementation requires stdin/TTY interaction the library
|
|
72
|
+
cannot provide
|
|
73
|
+
|
|
74
|
+
Examples of options to **include** (no execution-model conflict):
|
|
75
|
+
|
|
76
|
+
- `--format=<fmt>`, `--pretty=<fmt>`, `--porcelain` — output format flags; the facade
|
|
77
|
+
passes these explicitly when the parser requires a specific format
|
|
78
|
+
- `--patch` (diff output mode, e.g. `git diff --patch`), `--numstat`, `--shortstat`,
|
|
79
|
+
`--raw` — output mode flags used by the facade to select a parseable format
|
|
80
|
+
|
|
81
|
+
> **Note on `--patch`:** it appears in both lists because the flag has two different
|
|
82
|
+
> git behaviors depending on the command. In `git add -p` it opens an interactive
|
|
83
|
+
> session (exclude). In `git diff --patch` it selects a non-interactive output format
|
|
84
|
+
> (include). Evaluate per-command, not globally.
|
|
85
|
+
|
|
86
|
+
- `--no-color` — facade passes this to prevent ANSI escape codes from breaking
|
|
87
|
+
parsing
|
|
88
|
+
- `--verbose` / `-v`, `--quiet` / `-q` — include these unless they open a TTY
|
|
89
|
+
|
|
90
|
+
**Default assumption for `--verbose` and `--quiet`:** declare as `flag_option`
|
|
91
|
+
(not `literal`) unless their git implementation requires interactive I/O.
|
|
92
|
+
|
|
93
|
+
Command classes are neutral — they never hardcode `literal` entries for
|
|
94
|
+
output-control, editor-suppression, or progress flags. Declare these as
|
|
95
|
+
`flag_option` / `value_option` so the facade can pass the policy value.
|
|
96
|
+
|
|
97
|
+
> **Anti-pattern:** `literal '--no-edit'`, `literal '--verbose'`,
|
|
98
|
+
> `literal '--no-progress'` inside a command class.
|
|
99
|
+
>
|
|
100
|
+
> **Correct pattern:** `flag_option :edit, negatable: true` in the command;
|
|
101
|
+
> `no_edit: true` passed from the facade call site.
|
|
102
|
+
|
|
103
|
+
**The `--edit` / `--no-edit` pair:** Model as `flag_option :edit, negatable: true`.
|
|
104
|
+
The facade (`Git::Lib`) passes `no_edit: true` at each call site. Do **not** hardcode
|
|
105
|
+
`literal '--no-edit'` — that prevents the facade from controlling the option — and do
|
|
106
|
+
**not** exclude `--edit` from the DSL.
|
|
107
|
+
|
|
108
|
+
**Output-format options belong at the facade call site, not as `literal` entries:**
|
|
109
|
+
When a parser requires specific output flags (e.g. `--pretty=raw`, `--numstat`),
|
|
110
|
+
declare those flags in the DSL with `flag_option` or `value_option`, and pass them
|
|
111
|
+
explicitly from `Git::Lib`. Never hardcode them as `literal` entries inside the
|
|
112
|
+
command class — that hides the parser contract and prevents the facade from choosing
|
|
113
|
+
the format. See Insight 16 in `redesign/3_architecture_implementation.md`.
|
|
114
|
+
|
|
115
|
+
## 2. Verify DSL method per option type
|
|
116
|
+
|
|
117
|
+
| Git behavior | DSL method | Example |
|
|
118
|
+
| --- | --- | --- |
|
|
119
|
+
| fixed flag always present | `literal` | `literal 'stash'` — **only** for operation selectors (subcommand names, mode flags like `--delete` that define what the class does) |
|
|
120
|
+
| boolean flag | `flag_option` | `flag_option :cached` |
|
|
121
|
+
| repeatable boolean flag | `flag_option ..., max_times: N` | `flag_option %i[force f], max_times: 2` |
|
|
122
|
+
| boolean-or-value | `flag_or_value_option` | `flag_or_value_option :dirstat, inline: true` |
|
|
123
|
+
| value option | `value_option` | `value_option :message` |
|
|
124
|
+
| key-value pair option (inherently repeatable via Hash/Array input) | `key_value_option` | `key_value_option :trailer, key_separator: ': '` — caller passes `trailer: { 'Signed-off-by' => 'Name' }` or `trailer: [['key', 'val']]` |
|
|
125
|
+
| option requiring custom builder logic | `custom_option` | `custom_option :pattern, required: true do |val| ... end` — builder block receives the value and returns CLI args |
|
|
126
|
+
| execution kwarg (not a CLI arg) | `execution_option` | `execution_option :timeout` |
|
|
127
|
+
| positional argument | `operand` | `operand :commit1` |
|
|
128
|
+
| pathspec-style operands (independently reachable after `--`) | `end_of_options` + `value_option ... as_operand: true` | `end_of_options; value_option :pathspec, as_operand: true, repeatable: true` — caller passes `pathspec: ['f1', 'f2']` |
|
|
129
|
+
| pathspec-style operands (only positional group, no earlier positional ambiguity) | `operand ...` | `operand :pathspec, repeatable: true` — caller passes positionals `cmd.call('f1', 'f2')` |
|
|
130
|
+
|
|
131
|
+
### Recognizing `flag_or_value_option` from the git docs
|
|
132
|
+
|
|
133
|
+
git command documentation uses **`[=<value>]`** (square-bracketed
|
|
134
|
+
`=<value>`) to mark an option's value as optional. That notation maps directly
|
|
135
|
+
to `flag_or_value_option`:
|
|
136
|
+
|
|
137
|
+
| Man-page signature | DSL method |
|
|
138
|
+
| --- | --- |
|
|
139
|
+
| `--foo` | `flag_option :foo` |
|
|
140
|
+
| `--foo` / `--no-foo` | `flag_option :foo, negatable: true` |
|
|
141
|
+
| `--foo=<value>` | `value_option :foo, inline: true` |
|
|
142
|
+
| `--foo[=<value>]` | `flag_or_value_option :foo, inline: true` |
|
|
143
|
+
| `--foo[=<value>]` / `--no-foo` | `flag_or_value_option :foo, negatable: true, inline: true` |
|
|
144
|
+
| `--foo <value>` | `value_option :foo` |
|
|
145
|
+
|
|
146
|
+
**Why `inline: true` appears in every `=` row:** The `=` in man-page notation
|
|
147
|
+
(`--foo=<value>`, `--foo[=<value>]`) means git expects the value joined to the
|
|
148
|
+
flag as a single argv token (`--foo=bar`). The `inline: true` modifier tells the
|
|
149
|
+
DSL builder to emit that joined form. Without it, the value is emitted as a
|
|
150
|
+
**separate** argv token (`--foo bar`), which is the correct behavior when the git
|
|
151
|
+
docs show a space between the flag and value (`--foo <value>`). Match the
|
|
152
|
+
man-page notation: `=` → `inline: true`; space → omit `inline:`.
|
|
153
|
+
|
|
154
|
+
Common examples: `--branches[=<pattern>]`, `--tags[=<pattern>]`,
|
|
155
|
+
`--remotes[=<pattern>]`, `--dirstat[=<param>...]`.
|
|
156
|
+
|
|
157
|
+
Negatable value-option examples: `--track[=direct|inherit]` / `--no-track`,
|
|
158
|
+
`--recurse-submodules[=yes|on-demand|no]` / `--no-recurse-submodules`.
|
|
159
|
+
|
|
160
|
+
**Do not** use `flag_option` for these — it silently drops the value when one is
|
|
161
|
+
supplied.
|
|
162
|
+
|
|
163
|
+
### Action-option-with-optional-value commands
|
|
164
|
+
|
|
165
|
+
Some git commands express their **primary action** as an option with an optional
|
|
166
|
+
value (man-page notation: `--flag[=<value>]`). The canonical example is
|
|
167
|
+
`git am --show-current-patch[=(diff|raw)]` — there is no mode where the flag is
|
|
168
|
+
*not* passed; the optional `=<value>` just refines the behavior.
|
|
169
|
+
|
|
170
|
+
This situation is distinct from a normal `flag_or_value_option` used as a
|
|
171
|
+
modifier: the option IS the command. Do **not** model this as `literal
|
|
172
|
+
'--show-current-patch'` — that precludes passing the optional value.
|
|
173
|
+
|
|
174
|
+
**DSL entry:**
|
|
175
|
+
|
|
176
|
+
```ruby
|
|
177
|
+
flag_or_value_option :show_current_patch, inline: true, type: [TrueClass, String]
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
**Required `#call` override** — the class must provide a positional `#call`
|
|
181
|
+
override that maps the positional API onto the option keyword:
|
|
182
|
+
|
|
183
|
+
```ruby
|
|
184
|
+
def call(value = true, *, **)
|
|
185
|
+
super(*, **, show_current_patch: value)
|
|
186
|
+
end
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
Where:
|
|
190
|
+
|
|
191
|
+
- `value = true` — `true` emits `--flag` alone; a String emits `--flag=value`
|
|
192
|
+
- `*` — forwards any positional operands declared in the DSL (omit when none)
|
|
193
|
+
- `**` — forwards keyword options; unknown keywords raise `ArgumentError`
|
|
194
|
+
- `show_current_patch: value` — uses the actual option keyword name; placed last
|
|
195
|
+
so the positional arg always wins
|
|
196
|
+
|
|
197
|
+
**Flag these as errors:**
|
|
198
|
+
|
|
199
|
+
- Using `literal '--flag'` when the man page shows `--flag[=<value>]`
|
|
200
|
+
- Omitting `type: [TrueClass, String]` — allows `false` to silently pass, which
|
|
201
|
+
emits nothing and produces no error
|
|
202
|
+
- Omitting the `#call` override — forces callers to use the awkward keyword form
|
|
203
|
+
`.call(option_name: true)` instead of the natural `.call` or `.call('diff')`
|
|
204
|
+
- Using `nil` as the default in the override instead of `true` (use
|
|
205
|
+
`value = true`, not `value = nil` with `|| true`)
|
|
206
|
+
|
|
207
|
+
### Choosing the correct pathspec form
|
|
208
|
+
|
|
209
|
+
Choose the pathspec form by answering one question from the git command doc
|
|
210
|
+
SYNOPSIS: **can the pathspec group be supplied independently of earlier positional
|
|
211
|
+
operands?**
|
|
212
|
+
|
|
213
|
+
If **yes**, use `end_of_options` plus `value_option … as_operand: true` so the
|
|
214
|
+
caller can supply the pathspec group without accidentally binding it to an earlier
|
|
215
|
+
operand.
|
|
216
|
+
|
|
217
|
+
If **no**, use plain `operand` entries so left-to-right positional binding mirrors
|
|
218
|
+
the SYNOPSIS.
|
|
219
|
+
|
|
220
|
+
**Independently reachable pathspec group → `value_option … as_operand: true`**
|
|
221
|
+
|
|
222
|
+
When git explicitly separates two optional groups with `--` (e.g., `git diff
|
|
223
|
+
[<tree-ish>] [--] [<pathspec>...]`), the post-`--` group is *independently reachable*
|
|
224
|
+
— a caller must be able to supply pathspecs without also providing a tree-ish. Use
|
|
225
|
+
the `value_option … as_operand: true` form so positional binding is unambiguous:
|
|
226
|
+
|
|
227
|
+
```ruby
|
|
228
|
+
operand :tree_ish # positional
|
|
229
|
+
end_of_options # options/operands boundary
|
|
230
|
+
value_option :pathspec, as_operand: true, repeatable: true # as_operand
|
|
231
|
+
|
|
232
|
+
# cmd.call → git diff
|
|
233
|
+
# cmd.call('HEAD~3') → git diff HEAD~3
|
|
234
|
+
# cmd.call(pathspec: ['file.rb']) → git diff -- file.rb
|
|
235
|
+
# cmd.call('HEAD~3', pathspec: ['f.rb']) → git diff HEAD~3 -- f.rb
|
|
236
|
+
```
|
|
237
|
+
|
|
238
|
+
Without the `value_option … as_operand: true` form, `cmd.call('file.rb')` would silently bind `'file.rb'` to
|
|
239
|
+
`:tree_ish` instead of treating it as a pathspec.
|
|
240
|
+
|
|
241
|
+
The same rule applies when the SYNOPSIS has only a post-`--` pathspec group and no
|
|
242
|
+
earlier operands:
|
|
243
|
+
|
|
244
|
+
```ruby
|
|
245
|
+
end_of_options
|
|
246
|
+
value_option :pathspec, as_operand: true, repeatable: true, required: true
|
|
247
|
+
|
|
248
|
+
# cmd.call('file.rb') → git <cmd> -- file.rb
|
|
249
|
+
# cmd.call('f1', 'f2') → git <cmd> -- f1 f2
|
|
250
|
+
```
|
|
251
|
+
|
|
252
|
+
**Pure positional nesting → plain `operand` entries**
|
|
253
|
+
|
|
254
|
+
When the SYNOPSIS shows nested brackets — `[<commit1> [<commit2>]]` — the second
|
|
255
|
+
operand is only meaningful when the first is also present. No caller would ever
|
|
256
|
+
supply `commit2` without `commit1`. Left-to-right binding is correct:
|
|
257
|
+
|
|
258
|
+
```ruby
|
|
259
|
+
operand :commit1 # optional
|
|
260
|
+
operand :commit2 # optional — only meaningful when commit1 is also given
|
|
261
|
+
|
|
262
|
+
# cmd.call → git diff
|
|
263
|
+
# cmd.call('HEAD~3') → git diff HEAD~3
|
|
264
|
+
# cmd.call('HEAD~3', 'HEAD') → git diff HEAD~3 HEAD
|
|
265
|
+
```
|
|
266
|
+
|
|
267
|
+
### Quick reference
|
|
268
|
+
|
|
269
|
+
| git SYNOPSIS shape | Meaning | DSL shape |
|
|
270
|
+
| --- | --- | --- |
|
|
271
|
+
| `[<a>] [--] [<b>...]` | `<b>` is independently reachable | `operand :a` + `end_of_options` + `value_option :b, as_operand: true, repeatable: true` |
|
|
272
|
+
| `[--] <pathspec>...` | required pathspec group after `--` | `end_of_options` + `value_option :pathspec, as_operand: true, repeatable: true, required: true` |
|
|
273
|
+
| `[--] [<pathspec>...]` | optional pathspec group after `--` | `end_of_options` + `value_option :pathspec, as_operand: true, repeatable: true` |
|
|
274
|
+
| `<pathspec>...` | only positional group; no earlier positional ambiguity | `operand :pathspec, repeatable: true, required: true` |
|
|
275
|
+
| `[<a> [<b>]]` | pure left-to-right nesting | `operand :a` + `operand :b` |
|
|
276
|
+
|
|
277
|
+
Use `value_option … as_operand: true` whenever the post-`--` group must be
|
|
278
|
+
addressable without binding through earlier positional operands. Use plain
|
|
279
|
+
`operand` entries only when left-to-right positional binding is unambiguous and
|
|
280
|
+
matches the SYNOPSIS.
|
|
281
|
+
|
|
282
|
+
## 3. Verify alias and `as:` usage
|
|
283
|
+
|
|
284
|
+
- Prefer aliases for long/short pairs (`%i[force f]`, `%i[all A]`, `%i[intent_to_add
|
|
285
|
+
N]`)
|
|
286
|
+
- Ensure long name is first in alias arrays
|
|
287
|
+
|
|
288
|
+
- **Do not** flag uppercase short-flag aliases (e.g. `:A`, `:N`) as needing `as:` —
|
|
289
|
+
the DSL preserves symbol case, so `:A` correctly produces `-A` without any override
|
|
290
|
+
|
|
291
|
+
### The `as:` escape hatch
|
|
292
|
+
|
|
293
|
+
`as:` bypasses the DSL's automatic name-to-flag mapping and emits its value verbatim.
|
|
294
|
+
This is intentional power — but it carries a cost: a reviewer can no longer verify
|
|
295
|
+
the flag by reading the symbol name alone. The `as:` string must be audited
|
|
296
|
+
separately, making it harder to spot typos and drift.
|
|
297
|
+
|
|
298
|
+
Flag any use of `as:` unless one of these conditions applies:
|
|
299
|
+
|
|
300
|
+
1. **Ruby keyword conflict** — the git flag's natural name is a Ruby keyword and
|
|
301
|
+
cannot be used as a symbol literal. The alias is renamed, and `as:` supplies the
|
|
302
|
+
real flag:
|
|
303
|
+
|
|
304
|
+
```ruby
|
|
305
|
+
flag_option %i[begin_rev], as: '--begin'
|
|
306
|
+
```
|
|
307
|
+
|
|
308
|
+
2. **The required argv cannot be expressed by the normal DSL mapping** — the
|
|
309
|
+
symbol name, aliases, and existing modifiers (`negatable:`, `inline:`,
|
|
310
|
+
`as_operand:`, `max_times:`, etc.) cannot produce the required token sequence,
|
|
311
|
+
so `as:` is the narrowest accurate escape hatch. Example:
|
|
312
|
+
|
|
313
|
+
```ruby
|
|
314
|
+
# :three_way auto-maps to --three-way, but git expects --3way
|
|
315
|
+
flag_option :three_way, as: '--3way'
|
|
316
|
+
```
|
|
317
|
+
|
|
318
|
+
Outside these cases, `as:` is a red flag. A DSL entry that uses `as:` where a
|
|
319
|
+
plain symbol, alias, or existing modifier would suffice should be corrected.
|
|
320
|
+
|
|
321
|
+
#### Prefer first-class DSL features over `as:`
|
|
322
|
+
|
|
323
|
+
When the DSL now has a first-class way to express the behavior, `as:` is no longer
|
|
324
|
+
justified. Repeated flags are the canonical example: use `max_times:` instead of
|
|
325
|
+
encoding repetition manually.
|
|
326
|
+
|
|
327
|
+
The following patterns should be flagged as errors because `max_times:` expresses
|
|
328
|
+
them directly:
|
|
329
|
+
|
|
330
|
+
- **Combined short flag used to emulate repetition** (e.g. `flag_option %i[force_force ff], as: '-ff'`) —
|
|
331
|
+
replace with `flag_option %i[force f], max_times: 2`
|
|
332
|
+
- **Repeated identical tokens encoded as an array** (e.g. `flag_option :double_force, as: ['--force', '--force']`) —
|
|
333
|
+
replace with `flag_option %i[force f], max_times: 2`
|
|
334
|
+
|
|
335
|
+
#### Single-char flags never need `as:`
|
|
336
|
+
|
|
337
|
+
When git documents a flag as a bare short flag (e.g. `-p`, `-v`, `-q`), name the
|
|
338
|
+
symbol after the flag character directly — do **not** invent a descriptive name and
|
|
339
|
+
compensate with `as:`:
|
|
340
|
+
|
|
341
|
+
```ruby
|
|
342
|
+
# ❌ Wrong — descriptive name masking the real flag
|
|
343
|
+
flag_option :pretty, as: '-p'
|
|
344
|
+
flag_option :verbose, as: '-v'
|
|
345
|
+
|
|
346
|
+
# ✅ Correct — symbol IS the flag; no as: needed
|
|
347
|
+
flag_option :p
|
|
348
|
+
flag_option :v
|
|
349
|
+
```
|
|
350
|
+
|
|
351
|
+
The caller passes `p: true` or `v: true`. The symbol name is the single source of
|
|
352
|
+
truth and can be verified at a glance without auditing the `as:` string.
|
|
353
|
+
|
|
354
|
+
### Short-flag alias completeness
|
|
355
|
+
|
|
356
|
+
Every option that the git documentation documents with a short form must have an
|
|
357
|
+
alias with the long name first:
|
|
358
|
+
|
|
359
|
+
```ruby
|
|
360
|
+
flag_option %i[regexp_ignore_case i]
|
|
361
|
+
flag_option %i[extended_regexp E]
|
|
362
|
+
flag_option %i[fixed_strings F]
|
|
363
|
+
```
|
|
364
|
+
|
|
365
|
+
When reviewing, scan the git command doc's option headings for lines of the form
|
|
366
|
+
`-X` / `--long-name` and verify each has a corresponding `%i[long_name X]` alias in
|
|
367
|
+
the DSL. Missing short aliases are a completeness defect, not just a convenience
|
|
368
|
+
omission — callers who pass the short key `:E` will get an `ArgumentError` rather
|
|
369
|
+
than the expected flag.
|
|
370
|
+
|
|
371
|
+
### Spurious aliases
|
|
372
|
+
|
|
373
|
+
**Never invent an alias that the git command document does not document.** Check the
|
|
374
|
+
git command document before adding any alias entry. The canonical audit is: does the
|
|
375
|
+
git command document show the alias on the same option heading as the primary name?
|
|
376
|
+
If not, do not add it to the alias list.
|
|
377
|
+
|
|
378
|
+
Flag any alias that cannot be found in the git command document's option headings as
|
|
379
|
+
an error (not just a style issue).
|
|
380
|
+
|
|
381
|
+
## 4. Verify ordering
|
|
382
|
+
|
|
383
|
+
literal options should always come first.
|
|
384
|
+
|
|
385
|
+
For other options mirror the order those options appear in the git command document's
|
|
386
|
+
SYNOPSIS section for the subcommand being implemented.
|
|
387
|
+
|
|
388
|
+
Options that appear only in the OPTIONS section (not the SYNOPSIS) go after all
|
|
389
|
+
SYNOPSIS-ordered options but before `execution_option` declarations. Among
|
|
390
|
+
themselves, mirror the order found in the OPTIONS section.
|
|
391
|
+
|
|
392
|
+
`execution_option` declarations go after all CLI-producing options (`flag_option`,
|
|
393
|
+
`value_option`, `flag_or_value_option`, `key_value_option`, `custom_option`) and
|
|
394
|
+
before `end_of_options` and `operand` declarations. Since `execution_option` never
|
|
395
|
+
emits CLI arguments, its position does not affect the generated command line, but
|
|
396
|
+
consistent placement keeps the DSL block readable.
|
|
397
|
+
|
|
398
|
+
operands should go last.
|
|
399
|
+
|
|
400
|
+
### Comments in the DSL block
|
|
401
|
+
|
|
402
|
+
**Section comments** (e.g. `# Output format`, `# Whitespace handling`) are encouraged
|
|
403
|
+
to group related options within large DSL blocks. Place them on the line immediately
|
|
404
|
+
before the first option in the group.
|
|
405
|
+
|
|
406
|
+
**NEVER add trailing inline comments to DSL entries.** This is a hard rule — not a
|
|
407
|
+
style preference. Comments like `flag_option :verbose # --verbose` or
|
|
408
|
+
`flag_option :full, negatable: true # --[no-]full` are **always wrong** in this
|
|
409
|
+
project. They were removed project-wide in commit 370dffb because they:
|
|
410
|
+
|
|
411
|
+
1. Duplicate information the DSL already encodes deterministically
|
|
412
|
+
2. Duplicate the YARD `@option` documentation
|
|
413
|
+
3. Create a false verification layer (reviewer checks comment ↔ DSL without
|
|
414
|
+
catching errors in either)
|
|
415
|
+
4. Create line-length pressure and alignment maintenance burden
|
|
416
|
+
|
|
417
|
+
Flag any trailing inline comment on a DSL entry as an error. This applies when
|
|
418
|
+
scaffolding new commands, updating existing commands, and reviewing commands.
|
|
419
|
+
|
|
420
|
+
### `end_of_options` placement
|
|
421
|
+
|
|
422
|
+
Determine placement based on whether the SYNOPSIS explicitly shows `--`:
|
|
423
|
+
|
|
424
|
+
#### Rule 1 — SYNOPSIS shows `--`: mirror the SYNOPSIS
|
|
425
|
+
|
|
426
|
+
When the SYNOPSIS explicitly shows `--`, place `end_of_options` in
|
|
427
|
+
the same position the SYNOPSIS shows it. See [Choosing the correct pathspec
|
|
428
|
+
form](#choosing-the-correct-pathspec-form) for how to model the operands that come
|
|
429
|
+
after `--`.
|
|
430
|
+
|
|
431
|
+
**Do not apply Rule 2** when Rule 1 applies.
|
|
432
|
+
|
|
433
|
+
```ruby
|
|
434
|
+
# git diff [<tree-ish>] [--] [<pathspec>...]
|
|
435
|
+
operand :tree_ish # BEFORE end_of_options
|
|
436
|
+
end_of_options # mirrors SYNOPSIS position
|
|
437
|
+
value_option :pathspec, as_operand: true, repeatable: true # AFTER end_of_options
|
|
438
|
+
```
|
|
439
|
+
|
|
440
|
+
#### Rule 2 — SYNOPSIS does NOT show `--`: protect operands from flag misinterpretation
|
|
441
|
+
|
|
442
|
+
**Insert `end_of_options` immediately before the first operand when any
|
|
443
|
+
`flag_option`, `value_option`, `flag_or_value_option`, `key_value_option`, or
|
|
444
|
+
`custom_option` appears earlier in the same `arguments do` block.** This prevents
|
|
445
|
+
operand values that start with `-` from being misinterpreted as flags.
|
|
446
|
+
|
|
447
|
+
This applies even when the operand is unlikely to start with `-` in practice.
|
|
448
|
+
Defending against pathological inputs is the correct default.
|
|
449
|
+
|
|
450
|
+
`literal` entries are **never** the trigger for Rule 2 — regardless of whether their
|
|
451
|
+
value is option-style (e.g. `literal '--delete'`) or a plain subcommand word
|
|
452
|
+
(e.g. `literal 'remove'`). Only the five DSL option methods matter:
|
|
453
|
+
`flag_option`, `value_option`, `flag_or_value_option`, `key_value_option`, and
|
|
454
|
+
`custom_option`.
|
|
455
|
+
|
|
456
|
+
```ruby
|
|
457
|
+
# ✅ Correct — end_of_options guards the operand
|
|
458
|
+
arguments do
|
|
459
|
+
literal 'remote'
|
|
460
|
+
literal 'prune'
|
|
461
|
+
flag_option %i[dry_run n] # ← flag_option triggers Rule 2
|
|
462
|
+
|
|
463
|
+
end_of_options
|
|
464
|
+
|
|
465
|
+
operand :name, repeatable: true, required: true
|
|
466
|
+
end
|
|
467
|
+
|
|
468
|
+
# ❌ Missing end_of_options — flag as an error
|
|
469
|
+
arguments do
|
|
470
|
+
literal 'remote'
|
|
471
|
+
literal 'prune'
|
|
472
|
+
flag_option %i[dry_run n]
|
|
473
|
+
operand :name, repeatable: true, required: true # ← end_of_options required here
|
|
474
|
+
end
|
|
475
|
+
|
|
476
|
+
# ✅ Not needed — only literal entries precede the operand; no DSL option methods
|
|
477
|
+
arguments do
|
|
478
|
+
literal 'remote'
|
|
479
|
+
literal 'remove'
|
|
480
|
+
operand :name, required: true # no option methods → not required
|
|
481
|
+
end
|
|
482
|
+
```
|
|
483
|
+
|
|
484
|
+
`end_of_options` is always safe to add even when not strictly required — it is harmless
|
|
485
|
+
when no operand can plausibly start with `-`. Omit it by convention when neither rule
|
|
486
|
+
applies: it adds no defensive value and produces unnecessarily verbose command lines
|
|
487
|
+
(e.g. `git remote remove -- origin` instead of `git remote remove origin`).
|
|
488
|
+
|
|
489
|
+
#### Choosing the `as:` token
|
|
490
|
+
|
|
491
|
+
`end_of_options` defaults to emitting `--` as the options terminator, which is
|
|
492
|
+
correct for the vast majority of git commands. However, some commands use a
|
|
493
|
+
different terminator token. The canonical example is `git rev-parse`, which uses
|
|
494
|
+
`--end-of-options` instead of `--` because `--` is a **meaningful argument** to
|
|
495
|
+
`rev-parse` (it separates revisions from file paths in the output), not an
|
|
496
|
+
options terminator.
|
|
497
|
+
|
|
498
|
+
Use `end_of_options as: '<token>'` when the git documentation for the command
|
|
499
|
+
explicitly documents a different terminator. Check the command's SYNOPSIS and
|
|
500
|
+
options section for language like "use `--end-of-options` to separate options
|
|
501
|
+
from arguments".
|
|
502
|
+
|
|
503
|
+
| Git documentation says | DSL form |
|
|
504
|
+
| --- | --- |
|
|
505
|
+
| `[--] <pathspec>...` or generic `--` usage | `end_of_options` (default `as: '--'`) |
|
|
506
|
+
| `--end-of-options` explicitly documented | `end_of_options as: '--end-of-options'` |
|
|
507
|
+
|
|
508
|
+
**Flag these as errors:**
|
|
509
|
+
|
|
510
|
+
- Using bare `end_of_options` (emitting `--`) on a command that documents
|
|
511
|
+
`--end-of-options` as its terminator — `--` has a different meaning for that
|
|
512
|
+
command and will produce incorrect behavior
|
|
513
|
+
- Using `end_of_options as: '--end-of-options'` on a command that does not
|
|
514
|
+
document it — the default `--` is correct for nearly all commands
|
|
515
|
+
|
|
516
|
+
```ruby
|
|
517
|
+
# ✅ Correct — git rev-parse documents --end-of-options
|
|
518
|
+
end_of_options as: '--end-of-options'
|
|
519
|
+
operand :args, repeatable: true
|
|
520
|
+
|
|
521
|
+
# ❌ Wrong — bare -- has a different meaning in rev-parse
|
|
522
|
+
end_of_options
|
|
523
|
+
operand :args, repeatable: true
|
|
524
|
+
```
|
|
525
|
+
|
|
526
|
+
## 5. Verify modifiers
|
|
527
|
+
|
|
528
|
+
Derive `required:` and `repeatable:` directly from the SYNOPSIS notation for
|
|
529
|
+
operands:
|
|
530
|
+
|
|
531
|
+
| SYNOPSIS notation | `required:` | `repeatable:` |
|
|
532
|
+
| --- | --- | --- |
|
|
533
|
+
| `<arg>` | `true` | — |
|
|
534
|
+
| `[<arg>]` | `false` (default) | — |
|
|
535
|
+
| `<arg>…` | `true` | `true` |
|
|
536
|
+
| `[<arg>…]` | `false` | `true` |
|
|
537
|
+
|
|
538
|
+
Square brackets `[…]` → optional (`required: false`). Ellipsis `…` → repeatable
|
|
539
|
+
(`repeatable: true`).
|
|
540
|
+
|
|
541
|
+
All valid `operand` modifiers:
|
|
542
|
+
|
|
543
|
+
| Modifier | Default | Purpose |
|
|
544
|
+
| --- | --- | --- |
|
|
545
|
+
| `required:` | `false` | Operand must be supplied by the caller |
|
|
546
|
+
| `repeatable:` | `false` | Operand accepts multiple values |
|
|
547
|
+
| `default:` | `nil` | Value emitted when the operand is absent — see note below |
|
|
548
|
+
| `allow_nil:` | `false` | Permits an explicit `nil` to be passed without raising |
|
|
549
|
+
| `skip_cli:` | `false` | Binds/validates/accesses the operand but suppresses argv emission |
|
|
550
|
+
|
|
551
|
+
**When to use `default:`**: omit it unless the explicit default value produces
|
|
552
|
+
different output than `nil`. For a repeatable operand, both `nil` and `[]` are
|
|
553
|
+
treated as absent — no args are emitted — so `default: []` is redundant and should be
|
|
554
|
+
left off. Only supply `default:` when you need a non-empty fallback value to be
|
|
555
|
+
emitted automatically (e.g. `default: 'HEAD'` on an optional commit operand).
|
|
556
|
+
|
|
557
|
+
**When to use `skip_cli:`**: use it only when an operand is part of the Ruby call
|
|
558
|
+
contract and should be bound/validated and available on `Bound`, but must not be
|
|
559
|
+
emitted to CLI argv (for example, values passed via stdin protocol). Do not use
|
|
560
|
+
`skip_cli:` for execution kwargs; use `execution_option` for those.
|
|
561
|
+
|
|
562
|
+
### `execution_option` usage
|
|
563
|
+
|
|
564
|
+
`execution_option` declares a Ruby keyword argument that controls subprocess
|
|
565
|
+
execution rather than producing a git CLI flag. It accepts **only** a name (or array
|
|
566
|
+
of alias names) — no modifiers (`required:`, `as:`, `validator:`, `repeatable:`,
|
|
567
|
+
etc.) are supported.
|
|
568
|
+
|
|
569
|
+
Values are forwarded as Ruby kwargs to the underlying command runner (e.g.,
|
|
570
|
+
`command_capturing` or `command_streaming`). They never appear in the generated argv.
|
|
571
|
+
|
|
572
|
+
The authoritative set of accepted execution option names is defined by
|
|
573
|
+
`COMMAND_CAPTURING_ARG_DEFAULTS` and `COMMAND_STREAMING_ARG_DEFAULTS` in
|
|
574
|
+
`lib/git/lib.rb`. The complete set of accepted names is:
|
|
575
|
+
|
|
576
|
+
| Name | Purpose | Capturing | Streaming | Notes |
|
|
577
|
+
| --- | --- | --- | --- | --- |
|
|
578
|
+
| `:timeout` | Maximum seconds to wait for the subprocess to complete; `nil` falls back to `Git.config.timeout`, `0` disables | yes | yes | Most commonly exposed execution option |
|
|
579
|
+
| `:chdir` | Working directory for the subprocess | yes | yes | |
|
|
580
|
+
| `:out` | Output destination; when present, `Base#execute_command` selects the streaming path | yes | yes | Presence triggers streaming vs. capturing path selection |
|
|
581
|
+
| `:in` | IO object to use as stdin for the subprocess; must be a real IO with a file descriptor | yes | yes | |
|
|
582
|
+
| `:merge` | Merge stdout and stderr into a single captured string | yes | — | |
|
|
583
|
+
| `:env` | Additional environment variable overrides (Hash); merged with the command's own `env` by `Base#execution_opts` | yes | yes | |
|
|
584
|
+
| `:normalize` | Normalize captured output encoding to UTF-8 (via `rchardet` detection) | yes | — | |
|
|
585
|
+
| `:chomp` | Chomp trailing newlines from captured stdout and stderr | yes | — | |
|
|
586
|
+
| `:err` | Additional destination for stderr output; stderr is always captured internally and available via `result.stderr` — when `:err` is provided, writes are teed to both the internal buffer and this destination | yes | yes | `result.stderr` remains available even when stderr is teed to another destination; safe to expose but rarely needed |
|
|
587
|
+
| `:raise_on_failure` | Whether to raise `Git::FailedError` on non-zero exit status | yes | yes | `Base#execute_command` hardcodes this to `false` and uses `validate_exit_status!` instead, so exposing it via the DSL has no effect — flag as unnecessary if encountered |
|
|
588
|
+
|
|
589
|
+
An `execution_option` whose name does not appear in either defaults hash will raise
|
|
590
|
+
`ArgumentError` at runtime — flag it as a likely error (either a misunderstanding of
|
|
591
|
+
the DSL or an option that should be a `value_option` or `flag_option` instead).
|
|
592
|
+
|
|
593
|
+
Also validate that these modifiers (which do **not** apply to `operand`) are
|
|
594
|
+
correctly placed on their respective DSL methods:
|
|
595
|
+
|
|
596
|
+
| Modifier | Applies to |
|
|
597
|
+
| --- | --- |
|
|
598
|
+
| `as:` | `flag_option`, `value_option`, `flag_or_value_option`, `key_value_option` — escape hatch that emits the given string verbatim; see [Section 3](#3-verify-alias-and-as-usage) for when use is justified |
|
|
599
|
+
| `type:` | `value_option`, `flag_or_value_option` — restrict accepted Ruby types; see [Section 2](#action-option-with-optional-value-commands) for the one valid use case (`type: [TrueClass, String]`) |
|
|
600
|
+
| `required:` | `flag_option`, `value_option`, `flag_or_value_option`, `key_value_option`, `custom_option`, `operand` — see [Section 6](#6-check-completeness) for when to flag its absence |
|
|
601
|
+
| `allow_nil:` | `flag_option`, `value_option`, `flag_or_value_option`, `key_value_option`, `custom_option` (default `true`), `operand` (default `false`) — see [Section 6](#6-check-completeness) |
|
|
602
|
+
| `inline:` | `value_option`, `flag_or_value_option`, `key_value_option` |
|
|
603
|
+
| `negatable:` | `flag_option`, `flag_or_value_option` |
|
|
604
|
+
| `repeatable:` | `value_option`, `flag_or_value_option` — accepts an array of values (note: `operand` also accepts `repeatable:` — see operand modifier table above) |
|
|
605
|
+
| `allow_empty:` | `value_option` — use when git distinguishes an empty-string value (`--option ''`) from the option being absent; without it, passing `''` raises `ArgumentError` |
|
|
606
|
+
| `as_operand:` | `value_option` only — see pathspec table above |
|
|
607
|
+
| `max_times:` | `flag_option` — limits how many times the flag is emitted; caller passes an integer up to N (e.g. `force: 2` emits `--force --force`) |
|
|
608
|
+
| `key_separator:` | `key_value_option` — separator between key and value (default: `'='`) |
|
|
609
|
+
|
|
610
|
+
**Do not use `type:` for general type validation.** The DSL accepts any object with
|
|
611
|
+
a meaningful `#to_s` implementation — `String`, `Integer`, `Float`, `Pathname`,
|
|
612
|
+
`Symbol`, etc. — and stringifies it automatically during the build phase. Adding
|
|
613
|
+
`type: String` to a `value_option` rejects valid inputs like `Pathname` or `Integer`
|
|
614
|
+
that would produce correct CLI arguments. Git validates the actual string value; the
|
|
615
|
+
DSL does not need to duplicate that.
|
|
616
|
+
|
|
617
|
+
The one exception is action-option-with-optional-value commands (see
|
|
618
|
+
[Section 2](#action-option-with-optional-value-commands)), where
|
|
619
|
+
`type: [TrueClass, String]` prevents `false` from silently emitting nothing.
|
|
620
|
+
|
|
621
|
+
## 6. Check completeness
|
|
622
|
+
|
|
623
|
+
### YARD documentation ↔ DSL parity
|
|
624
|
+
|
|
625
|
+
Every keyword/positional parameter documented for `call` must correspond to a DSL
|
|
626
|
+
entry and vice versa — mismatches indicate either a missing DSL entry or stale
|
|
627
|
+
documentation.
|
|
628
|
+
|
|
629
|
+
**`negatable:` options require two `@option` tags.** When the DSL declares
|
|
630
|
+
`flag_option :foo, negatable: true` or `flag_or_value_option :foo, negatable: true`,
|
|
631
|
+
two separate `@option` entries are required: one for the positive key (`:foo`) and
|
|
632
|
+
one for the negative companion key (`:no_foo`). Both follow standard boolean
|
|
633
|
+
semantics (`true` emits the flag, `false`/`nil`/omitted emits nothing); both use
|
|
634
|
+
`(nil)` as the default value. A single merged tag or "Pass `false` for `--no-foo`" prose
|
|
635
|
+
does not satisfy this requirement.
|
|
636
|
+
|
|
637
|
+
```ruby
|
|
638
|
+
# ❌ Missing negative companion tag
|
|
639
|
+
# @option options [Boolean, nil] :create_reflog (nil) create the branch's reflog
|
|
640
|
+
|
|
641
|
+
# ✅ Both forms documented with separate tags
|
|
642
|
+
# @option options [Boolean, nil] :create_reflog (nil) create the branch's reflog
|
|
643
|
+
#
|
|
644
|
+
# @option options [Boolean, nil] :no_create_reflog (nil) suppress branch reflog
|
|
645
|
+
# creation (`--no-create-reflog`)
|
|
646
|
+
```
|
|
647
|
+
|
|
648
|
+
**`@option` descriptions must use the emitted long flag form.** The DSL emits the
|
|
649
|
+
primary (long) flag regardless of which alias the caller uses. Any `@option`
|
|
650
|
+
prose that references a short flag (e.g. `-v`, `-f`, `-a`) as if it is emitted
|
|
651
|
+
is misleading and must be corrected to the long form:
|
|
652
|
+
|
|
653
|
+
```ruby
|
|
654
|
+
# ❌ Misleading — describes -v as emitted
|
|
655
|
+
# @option options [Boolean, Integer, nil] :verbose (nil) ...
|
|
656
|
+
# Pass `true` for `-v`; pass `2` for `-v -v`.
|
|
657
|
+
|
|
658
|
+
# ✅ Correct — describes the emitted flag
|
|
659
|
+
# @option options [Boolean, Integer, nil] :verbose (nil) ...
|
|
660
|
+
# Pass `true` for `--verbose`; pass `2` for `--verbose --verbose`.
|
|
661
|
+
```
|
|
662
|
+
|
|
663
|
+
- If the class defines an explicit `def call`, check the YARD docs directly above
|
|
664
|
+
that method.
|
|
665
|
+
- If the class does **not** define `def call`, check the `@overload` blocks in the
|
|
666
|
+
class's `@!method call` YARD directive.
|
|
667
|
+
|
|
668
|
+
Flag any parameter present in an `@overload` but absent from `arguments do` (or
|
|
669
|
+
vice versa) as a mismatch that must be resolved.
|
|
670
|
+
|
|
671
|
+
**Example mismatch — documented `call` parameter `force:` is missing from the DSL:**
|
|
672
|
+
|
|
673
|
+
```ruby
|
|
674
|
+
# @!method call
|
|
675
|
+
# @overload call(force: false)
|
|
676
|
+
# @param force [Boolean] force the operation
|
|
677
|
+
arguments do
|
|
678
|
+
# ← missing: flag_option %i[force f]
|
|
679
|
+
end
|
|
680
|
+
```
|
|
681
|
+
|
|
682
|
+
Every option documented for the command should be represented in `arguments do`
|
|
683
|
+
(except those excluded due to execution-model conflicts — see [§1](#1-determine-scope-and-exclusions)),
|
|
684
|
+
and every DSL entry should be covered by tests.
|
|
685
|
+
|
|
686
|
+
### Repeatable boolean flags
|
|
687
|
+
|
|
688
|
+
When the git command documentation describes a flag that can be given multiple
|
|
689
|
+
times to increase its effect (e.g. `--force` can be given twice for `git clean`), use
|
|
690
|
+
`flag_option ..., max_times: N` where N is the documented maximum repetition count.
|
|
691
|
+
The caller can then pass `true` (emit once) or an integer up to N (emit that many
|
|
692
|
+
times).
|
|
693
|
+
|
|
694
|
+
**When the docs don't state a specific maximum:** Some git docs say "can be given
|
|
695
|
+
more than once" without specifying a maximum. Do **not** invent a bound such as
|
|
696
|
+
`max_times: 2`. Instead, treat the repetition limit as requiring manual
|
|
697
|
+
verification against the latest-version upstream documentation and, if still
|
|
698
|
+
ambiguous, the latest-version upstream source. Only use `max_times: N` when that
|
|
699
|
+
verification establishes an explicit bound. If no explicit bound can be verified,
|
|
700
|
+
flag the DSL entry for manual/source verification rather than hard-coding an
|
|
701
|
+
arbitrary limit.
|
|
702
|
+
|
|
703
|
+
Flag these as errors:
|
|
704
|
+
|
|
705
|
+
- Using `as:` to emulate repeated flags (e.g. `as: '-ff'` or
|
|
706
|
+
`as: ['--force', '--force']`) instead of `max_times:`
|
|
707
|
+
- Using a separate symbol name for the repeated form (e.g. `:force_force`) instead of
|
|
708
|
+
`max_times:` on the same symbol
|
|
709
|
+
- Missing `max_times:` when the latest-version docs explicitly describe repeatable
|
|
710
|
+
flag behavior
|
|
711
|
+
|
|
712
|
+
### Operand naming
|
|
713
|
+
|
|
714
|
+
Verify that each `operand` name matches the `<parameter>` name from the git
|
|
715
|
+
documentation in singular form. For example, if the git docs say
|
|
716
|
+
`<pathspec>...`, the operand should be named `:pathspec` (not `:pathspecs` or
|
|
717
|
+
`:paths`). If the docs say `<commit>`, the operand should be named `:commit`.
|
|
718
|
+
|
|
719
|
+
**Name-collision avoidance:** if an operand name conflicts with a keyword option name
|
|
720
|
+
in the same command (e.g., both `flag_option :commit` and `operand :commit` exist),
|
|
721
|
+
the **option keeps its name** and the **operand is renamed** to something close but
|
|
722
|
+
distinct. Prefer the plural form for repeatable operands (e.g., `:commit` →
|
|
723
|
+
`:commits`) or another unambiguous variant for non-repeatable ones. Flag this as an
|
|
724
|
+
issue if the existing operand and option share a name.
|
|
725
|
+
|
|
726
|
+
### Per-argument validation completeness
|
|
727
|
+
|
|
728
|
+
For every `flag_option`, `value_option`, `flag_or_value_option`, and `operand`
|
|
729
|
+
declaration, check whether per-argument validation parameters have been considered:
|
|
730
|
+
|
|
731
|
+
| Parameter | Flag it missing if… |
|
|
732
|
+
| --- | --- |
|
|
733
|
+
| `required: true` | The command always fails without this argument, making the Ruby caller's error clearer before spawning a process |
|
|
734
|
+
| `allow_nil: false` | The argument is optional in Ruby (no `required: true`), but `nil` should be rejected rather than treated as "not provided" — for example, passing `nil` would produce an invalid CLI argument or ambiguous behavior |
|
|
735
|
+
|
|
736
|
+
All option DSL methods (`flag_option`, `value_option`, `flag_or_value_option`,
|
|
737
|
+
`key_value_option`, `custom_option`) default to `allow_nil: true`. `operand`
|
|
738
|
+
defaults to `allow_nil: false`. Specify `allow_nil: false` on options when nil must
|
|
739
|
+
be rejected explicitly. Only specify `allow_nil: true` on operands when
|
|
740
|
+
distinguishing an explicit `nil` from "not provided" is semantically important; do
|
|
741
|
+
not flag its absence when the default is correct.
|
|
742
|
+
|
|
743
|
+
**`allow_nil: true` on `required:` arguments:** flag as suspicious — allowing nil on
|
|
744
|
+
a required argument is rarely correct.
|
|
745
|
+
|
|
746
|
+
Do **not** flag the absence of these parameters as issues when no meaningful
|
|
747
|
+
constraint exists for that argument — omitting them is correct in that case.
|
|
748
|
+
|
|
749
|
+
**Cross-argument constraint methods are generally not used in command classes.** Do
|
|
750
|
+
not flag the absence of `conflicts`, `requires`, `requires_one_of`,
|
|
751
|
+
`requires_exactly_one_of`, `forbid_values`, or `allowed_values` as a completeness
|
|
752
|
+
issue. Command classes use per-argument validation parameters (`required:`,
|
|
753
|
+
`allow_nil:`, etc.) and operand format validation. Git validates its own option
|
|
754
|
+
semantics. There are two narrow exceptions:
|
|
755
|
+
|
|
756
|
+
1. **Arguments git cannot observe in its argv** — the test is: does this argument
|
|
757
|
+
appear in git's argv? If no (e.g., `skip_cli: true` operands routed via stdin),
|
|
758
|
+
git cannot detect incompatibilities and constraint declarations are appropriate
|
|
759
|
+
and should not be flagged as policy violations. Example: `cat-file --batch`
|
|
760
|
+
declares `conflicts :objects, :batch_all_objects` and `requires_one_of :objects,
|
|
761
|
+
:batch_all_objects` because `:objects` is `skip_cli: true`.
|
|
762
|
+
2. **Git-visible arguments that cause silent data loss** — if a combination of
|
|
763
|
+
git-visible arguments causes git to silently discard data (no error, wrong
|
|
764
|
+
result), a `conflicts` declaration MAY be added with: a code comment explaining
|
|
765
|
+
why, a reference to the git version(s) where the behavior was verified, and a
|
|
766
|
+
test. As of this writing, no such case has been identified.
|
|
767
|
+
|
|
768
|
+
## 7. Check class-level declarations
|
|
769
|
+
|
|
770
|
+
The following class-level declarations are **not** part of `arguments do` but should
|
|
771
|
+
be verified alongside DSL entries. The canonical rules live in [Command
|
|
772
|
+
Implementation](../command-implementation/REFERENCE.md) — see
|
|
773
|
+
[Exit status guidance](../command-implementation/REFERENCE.md#exit-status-guidance)
|
|
774
|
+
and [`requires_git_version` convention](../command-implementation/REFERENCE.md#requires_git_version-convention). Briefly:
|
|
775
|
+
|
|
776
|
+
- **`allow_exit_status`** — present with a `Range` and rationale comment when the
|
|
777
|
+
command has non-zero successful exits.
|
|
778
|
+
- **`requires_git_version`** — present only when the command was introduced after
|
|
779
|
+
`Git::MINIMUM_GIT_VERSION`; uses a `'major.minor.patch'` string.
|
|
780
|
+
- **`` @note `arguments` block audited against https://git-scm.com/docs/git-{command}/<version> ``** —
|
|
781
|
+
present in the class-level YARD doc block. Flag as an error if: (1) the note is
|
|
782
|
+
missing, (2) the version in the URL is not the current latest git version, or
|
|
783
|
+
(3) the note appears in the wrong position (it must appear after all `@example`
|
|
784
|
+
blocks and before any `@see` tags — i.e. the canonical tag order is description
|
|
785
|
+
→ `@example` → `@note` → `@see` → `@api private`).
|
|
786
|
+
To get the current latest version, run `bin/latest-git-version` from the repo root.
|
|
787
|
+
A stale version means the DSL may be missing options added in subsequent git
|
|
788
|
+
releases.
|