git 5.1.0 → 5.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (101) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +45 -0
  3. data/CONTRIBUTING.md +185 -165
  4. data/LICENSE +1 -1
  5. data/README.md +151 -151
  6. data/git.gemspec +35 -3
  7. data/lib/git/command_line/base.rb +1 -2
  8. data/lib/git/commands/base.rb +1 -1
  9. data/lib/git/commands/cat_file/raw.rb +8 -7
  10. data/lib/git/repository/branching.rb +79 -5
  11. data/lib/git/version.rb +1 -1
  12. metadata +5 -94
  13. data/.claude/commands/address-copilot-reviews.md +0 -14
  14. data/.claude/settings.json +0 -16
  15. data/.claude/skills +0 -1
  16. data/.commitlintrc.yml +0 -38
  17. data/.dockerignore +0 -27
  18. data/.github/copilot-instructions.md +0 -30
  19. data/.github/hooks/bin-setup-on-worktree.json +0 -11
  20. data/.github/hooks/run-bin-setup-once.sh +0 -20
  21. data/.github/issue_template.md +0 -15
  22. data/.github/prompts/iteratively-address-copilot-reviews.prompt.md +0 -188
  23. data/.github/pull_request_template.md +0 -21
  24. data/.github/skills/breaking-change-analysis/SKILL.md +0 -99
  25. data/.github/skills/ci-cd-troubleshooting/SKILL.md +0 -264
  26. data/.github/skills/command-implementation/REFERENCE.md +0 -994
  27. data/.github/skills/command-implementation/SKILL.md +0 -230
  28. data/.github/skills/command-test-conventions/SKILL.md +0 -664
  29. data/.github/skills/command-yard-documentation/SKILL.md +0 -434
  30. data/.github/skills/dependency-management/SKILL.md +0 -72
  31. data/.github/skills/development-workflow/SKILL.md +0 -512
  32. data/.github/skills/facade-implementation/REFERENCE.md +0 -837
  33. data/.github/skills/facade-implementation/SKILL.md +0 -269
  34. data/.github/skills/facade-test-conventions/SKILL.md +0 -391
  35. data/.github/skills/facade-yard-documentation/SKILL.md +0 -435
  36. data/.github/skills/make-skill-template/SKILL.md +0 -226
  37. data/.github/skills/pr-readiness-review/SKILL.md +0 -205
  38. data/.github/skills/project-context/SKILL.md +0 -306
  39. data/.github/skills/pull-request-review/SKILL.md +0 -168
  40. data/.github/skills/rebase/SKILL.md +0 -148
  41. data/.github/skills/refactor-command-to-commandlineresult/SKILL.md +0 -131
  42. data/.github/skills/release-management/SKILL.md +0 -125
  43. data/.github/skills/resolve-feedback/SKILL.md +0 -288
  44. data/.github/skills/review-arguments-dsl/CHECKLIST.md +0 -788
  45. data/.github/skills/review-arguments-dsl/SKILL.md +0 -214
  46. data/.github/skills/review-cross-command-consistency/SKILL.md +0 -139
  47. data/.github/skills/reviewing-skills/SKILL.md +0 -214
  48. data/.github/skills/rspec-unit-testing-standards/SKILL.md +0 -685
  49. data/.github/skills/tdd-refactor-step/SKILL.md +0 -236
  50. data/.github/skills/test-debugging/SKILL.md +0 -161
  51. data/.github/skills/yard-documentation/SKILL.md +0 -981
  52. data/.github/skills/yard-documentation/element-rules.md +0 -162
  53. data/.github/skills-deprecated/README.md +0 -21
  54. data/.github/skills-deprecated/extract-command-from-lib/SKILL.md +0 -487
  55. data/.github/skills-deprecated/extract-facade-from-base-lib/KEYWORD_ARG_REMEDIATION.md +0 -22
  56. data/.github/skills-deprecated/extract-facade-from-base-lib/SKILL.md +0 -600
  57. data/.github/skills-deprecated/review-backward-compatibility/SKILL.md +0 -275
  58. data/.github/workflows/continuous_integration.yml +0 -358
  59. data/.github/workflows/enforce_conventional_commits.yml +0 -35
  60. data/.github/workflows/experimental_continuous_integration.yml +0 -59
  61. data/.github/workflows/release.yml +0 -52
  62. data/.github/workflows/warm_bundler_caches.yml +0 -82
  63. data/.gitignore +0 -30
  64. data/.husky/commit-msg +0 -1
  65. data/.husky/pre-commit +0 -13
  66. data/.release-please-config.json +0 -36
  67. data/.release-please-manifest.json +0 -3
  68. data/.rspec +0 -2
  69. data/.rubocop.yml +0 -44
  70. data/.rubocop_todo.yml +0 -30
  71. data/.yard-lint.yml +0 -75
  72. data/CLAUDE.md +0 -11
  73. data/Gemfile +0 -22
  74. data/Rakefile +0 -41
  75. data/docker/test/Dockerfile +0 -32
  76. data/docker/test/docker-compose.yml +0 -0
  77. data/package.json +0 -10
  78. data/redesign/1_architecture_existing.md +0 -102
  79. data/redesign/2_architecture_redesign.md +0 -449
  80. data/redesign/3_architecture_implementation.md +0 -1623
  81. data/redesign/Phase 4 - Step A.md +0 -366
  82. data/redesign/Phase 4 - Step B.md +0 -921
  83. data/redesign/Phase 4 - Step C.md +0 -833
  84. data/redesign/beta_release.md +0 -107
  85. data/redesign/branch_parse_refactor_plan.md +0 -163
  86. data/redesign/c1a-public-api-scope.tsv +0 -256
  87. data/redesign/c1c2_audit.md +0 -566
  88. data/redesign/c1c2_bucket6_lib_orphans.md +0 -626
  89. data/redesign/config_design.rb +0 -501
  90. data/redesign/index.md +0 -34
  91. data/redesign/info_object_migration_plan.md +0 -126
  92. data/redesign/integration_test_analysis.md +0 -521
  93. data/redesign/phase-4-step-b-test-audit.tsv +0 -485
  94. data/redesign/remote_refactor_plan.md +0 -164
  95. data/redesign/reverse_dependencies.sql +0 -44
  96. data/tasks/gem_tasks.rake +0 -14
  97. data/tasks/npm_tasks.rake +0 -7
  98. data/tasks/rspec.rake +0 -111
  99. data/tasks/rubocop.rake +0 -5
  100. data/tasks/test_gem.rake +0 -12
  101. data/tasks/yard.rake +0 -57
@@ -1,162 +0,0 @@
1
- # YARD Element-Specific Rules
2
-
3
- Per-element YARD requirements for classes, modules, constants, attributes,
4
- dynamically defined methods, and value objects
5
- (`Data.define` / `Struct.new`).
6
-
7
- Consult this file when documenting one of those elements. Rules that apply to
8
- **every** doc comment (formatting, length limits, tag order) and to **methods**
9
- live in [SKILL.md](SKILL.md).
10
-
11
- ## Contents
12
-
13
- - [Contents](#contents)
14
- - [Classes](#classes)
15
- - [Modules](#modules)
16
- - [Constants](#constants)
17
- - [Attributes](#attributes)
18
- - [Dynamically defined methods (`@!method`)](#dynamically-defined-methods-method)
19
- - [`Data.define` classes](#datadefine-classes)
20
- - [`Struct.new` classes](#structnew-classes)
21
-
22
- ## Classes
23
-
24
- - One-sentence class description as a **noun phrase** (or starting with "Represents…") — the description should pass
25
- the "This class is a…" litmus test (i.e. you should be able to prefix "This class
26
- is a" and produce a grammatical sentence). "Represents…" is an accepted convention.
27
- Do not start descriptions with "This class is…", "Provides…", or "Encapsulates…".
28
- - Good: `Wrapper around the git binary`, `Immutable value object for a branch
29
- delete result`, `Represents a git branch`
30
- - Bad: `This class wraps the git binary`, `Provides branch deletion`,
31
- `Encapsulates branch state`
32
- - `@api public` or `@api private` tag to declare visibility — use `@api public` for
33
- stable user-facing classes; use `@api private` for internal implementation classes
34
- (e.g., `Git::ExecutionContext::*`, `Git::Commands::*`, parsers)
35
- - At least one `@example` showing typical instantiation or primary usage — this
36
- applies to all classes including `@api private` classes
37
- - Error/exception classes must also state when the error is raised in class-level
38
- prose, using caller-facing wording such as `Raised when branch deletion fails`
39
- - Deprecated classes must include `@deprecated` explaining the migration path
40
- - When present, class-level tags must appear in this order: `@example`, `@note`,
41
- `@deprecated`, `@see`, `@api`, `@abstract`
42
-
43
- ## Modules
44
-
45
- - One-sentence description — "Namespace for…", "Provides helpers for…", or
46
- "Mixin that adds…"
47
- - `@api public` or `@api private` — use `@api public` for stable user-facing modules;
48
- use `@api private` for internal implementation modules
49
- - No `@example` required unless the module provides standalone methods
50
- - Deprecated modules must include `@deprecated` explaining the migration path
51
- - When present, module-level tags must appear in the same order as class-level
52
- tags: `@example`, `@note`, `@deprecated`, `@see`, `@api`
53
-
54
- ## Constants
55
-
56
- - A comment immediately above the constant describing its purpose and valid values
57
- - No special YARD tag is needed; YARD picks up the preceding comment automatically
58
- - Add `# @return [Type]` when the constant holds a collection, frozen structure, or
59
- domain-specific type whose shape is not immediately obvious from the value
60
-
61
- ## Attributes
62
-
63
- - For explicitly written `attr_reader`, `attr_accessor`, and `attr_writer` declarations,
64
- place the documentation directly above the attribute; do **not** use the `@!attribute`
65
- directive
66
- - For dynamically created attributes, `Data.define` members, or `Struct.new`
67
- members, you **must** use a `# @!attribute [r/rw/w] name` YARD directive
68
- - Must include `@return [Type] description` explaining the value and its units or
69
- constraints if relevant
70
- - For explicit `attr_reader`/`attr_accessor`/`attr_writer`, include a short
71
- description paragraph above the attribute in addition to `@return`
72
- - For `@!attribute` directives (in `Data.define` / `Struct.new`), the `@return`
73
- tag inside the directive block serves as the sole documentation — no separate
74
- short description is needed
75
- - Tags inside `@!attribute` (and `@!method`) directive blocks must be indented
76
- two extra spaces relative to the directive itself
77
- - Must be defined at the class level, not inside method bodies
78
-
79
- ## Dynamically defined methods (`@!method`)
80
-
81
- Use the `# @!method name(params)` directive for methods created via
82
- metaprogramming (`define_method`, method-generating DSLs, etc.) that have no
83
- literal `def`. Place the directive and its doc comment where the method would
84
- logically appear in the class body. Tags inside the directive block follow the
85
- same indentation rule as `@!attribute` — indented two extra spaces relative to
86
- the directive.
87
-
88
- If an `@!method` block uses `@overload`, follow overload tag-placement rules in
89
- [SKILL.md](SKILL.md): keep `@return` inside each overload, keep shared
90
- `@raise` at top level, and keep `@api` at top level.
91
-
92
- ## `Data.define` classes
93
-
94
- Immutable value objects defined with `Data.define` use the following conventions
95
- (see `Git::BranchDeleteFailure` for a canonical example):
96
-
97
- - Class-level doc: noun-phrase short description, `@example`, `@see`, `@api`
98
- - One `@!attribute [r]` directive per member with `@return [Type] description`
99
- - Attribute directives are placed after class-level tags and before the
100
- `Data.define` line
101
- - Custom methods defined inside the `Data.define` block follow standard method
102
- rules
103
-
104
- ```ruby
105
- # Immutable value object for a failed branch delete
106
- #
107
- # @example Create a failure object
108
- # failure = BranchDeleteFailure.new(
109
- # name: 'nonexistent',
110
- # error_message: "branch 'nonexistent' not found."
111
- # )
112
- # failure.name #=> 'nonexistent'
113
- # failure.error_message #=> "branch 'nonexistent' not found."
114
- #
115
- # @see Git::BranchDeleteResult
116
- #
117
- # @api public
118
- #
119
- # @!attribute [r] name
120
- #
121
- # @return [String] the branch name that failed to delete
122
- #
123
- # @!attribute [r] error_message
124
- #
125
- # @return [String] the git error message explaining the failure
126
- #
127
- BranchDeleteFailure = Data.define(:name, :error_message)
128
- ```
129
-
130
- ## `Struct.new` classes
131
-
132
- Document `Struct.new` classes using the same conventions as `Data.define` classes:
133
- class-level doc with a noun-phrase short description, `@example`, `@see`, `@api`,
134
- and one `@!attribute` directive per member with `@return [Type] description`.
135
-
136
- Unlike `Data.define`, a `Struct.new` class is **mutable** — it generates
137
- read-write accessors — so use `@!attribute [rw]` (not `[r]`) for its members and
138
- do not describe it as immutable.
139
-
140
- ```ruby
141
- # Value object for a diff hunk's location within a file
142
- #
143
- # @example Create and update a hunk location
144
- # loc = HunkLocation.new(start_line: 10, line_count: 3)
145
- # loc.start_line #=> 10
146
- # loc.line_count = 4
147
- # loc.line_count #=> 4
148
- #
149
- # @see Git::DiffInfo
150
- #
151
- # @api public
152
- #
153
- # @!attribute [rw] start_line
154
- #
155
- # @return [Integer] the 1-based line where the hunk begins
156
- #
157
- # @!attribute [rw] line_count
158
- #
159
- # @return [Integer] the number of lines the hunk spans
160
- #
161
- HunkLocation = Struct.new(:start_line, :line_count, keyword_init: true)
162
- ```
@@ -1,21 +0,0 @@
1
- # Deprecated skills
2
-
3
- This directory holds skills that have been **retired** from active use. They are
4
- kept on disk for historical reference only — they are intentionally located outside
5
- `.github/skills/` so they are not auto-discovered or loaded into agent context.
6
-
7
- ## Retired skills
8
-
9
- - **`extract-command-from-lib/`** — guided migrating a `Git::Lib` `#command` call
10
- into a `Git::Commands::*` class during the v5.0.0 architectural redesign.
11
- - **`extract-facade-from-base-lib/`** — guided migrating a public method from
12
- `Git::Base` / `Git::Lib` into a `Git::Repository::*` facade method during the same
13
- redesign.
14
- - **`review-backward-compatibility/`** — audited `Git::Lib` methods for backward
15
- compatibility during the migration to `Git::Commands::*`. `Git::Lib` was deleted in
16
- PR #1456 as part of Phase 4 Step A; this workflow no longer applies.
17
-
18
- The extract-* skills describe migration work that is now complete: `Git::Base` and
19
- `Git::Lib` have been removed from the codebase, so there is nothing left to extract.
20
- All three skills remain here as a record of the workflow used during the migration,
21
- in case similar work is needed again in the future.
@@ -1,487 +0,0 @@
1
- ---
2
- name: extract-command-from-lib
3
- description: "Migrates a direct #command call in Git::Lib to a Git::Commands::* class as part of the architectural redesign. Use when extracting a specific command during the Strangler Fig migration."
4
- ---
5
-
6
- # Extract Command from Lib
7
-
8
- Replace a direct `#command` call in `Git::Lib` with a call to a `Git::Commands::*`
9
- class. The git subcommand is determined by the first (or first few) arguments to the
10
- `#command` method call.
11
-
12
- ## Contents
13
-
14
- - [How to use this skill](#how-to-use-this-skill)
15
- - [Prerequisites](#prerequisites)
16
- - [Related skills](#related-skills)
17
- - [Input](#input)
18
- - [Workflow](#workflow)
19
- - [Branch setup](#branch-setup)
20
- - [Step 1 — Identify the `#command` call](#step-1-identify-the-command-call)
21
- - [Step 2 — Plan the migration and get approval](#step-2-plan-the-migration-and-get-approval)
22
- - [Step 3 — Ensure adequate legacy tests](#step-3-ensure-adequate-legacy-tests)
23
- - [Step 4 — Ensure the `Git::Commands::*` class exists](#step-4-ensure-the-gitcommands-class-exists)
24
- - [Step 5 — Update `Git::Lib` to delegate to the command class](#step-5-update-gitlib-to-delegate-to-the-command-class)
25
- - [Commit discipline](#commit-discipline)
26
- - [Create a pull request](#create-a-pull-request)
27
- - [Quality gates (run at every step)](#quality-gates-run-at-every-step)
28
- - [Common patterns](#common-patterns)
29
- - [Simple delegation (stdout passthrough)](#simple-delegation-stdout-passthrough)
30
- - [Delegation with post-processing](#delegation-with-post-processing)
31
- - [Delegation with parsed return value](#delegation-with-parsed-return-value)
32
- - [Delegation with opts-hash key normalization](#delegation-with-opts-hash-key-normalization)
33
- - [Delegation with option filtering (preventing API expansion)](#delegation-with-option-filtering-preventing-api-expansion)
34
- - [What stays in `Git::Lib`](#what-stays-in-gitlib)
35
- - [What moves to `Git::Commands::*`](#what-moves-to-gitcommands)
36
-
37
- ## How to use this skill
38
-
39
- Attach this file to your Copilot Chat context, then invoke it with a short message
40
- identifying the `Git::Lib` method or `#command` call to migrate. Examples:
41
-
42
- ```text
43
- Using the Extract Command from Lib skill, migrate Git::Lib#worktree_add —
44
- it calls command('worktree', 'add', ...).
45
- ```
46
-
47
- ```text
48
- Extract Command from Lib: command('ls-tree', ...)
49
- ```
50
-
51
- The invocation needs either the `Git::Lib` method name or the git subcommand string
52
- from the `#command` call (or both).
53
-
54
- ## Prerequisites
55
-
56
- Before starting, you **MUST** load the following skill(s) in their entirety:
57
-
58
- - [YARD Documentation](../../skills/yard-documentation/SKILL.md) — authoritative
59
- source for YARD formatting rules and writing standards;
60
-
61
- ## Related skills
62
-
63
- Run or reference these skills during the workflow:
64
-
65
- - [Command Implementation](../../skills/command-implementation/SKILL.md) — generates and reviews `Git::Commands::*`
66
- classes, unit tests, integration tests, and YARD docs (used in Step 4 if the
67
- command class does not exist yet); also the canonical class-shape checklist,
68
- phased rollout gates, and internal compatibility contracts
69
- - [Review Arguments DSL](../../skills/review-arguments-dsl/SKILL.md) — verifying DSL entries match git CLI
70
- - [Command Test Conventions](../../skills/command-test-conventions/SKILL.md) — unit/integration test conventions for command classes
71
- - [Command YARD Documentation](../../skills/command-yard-documentation/SKILL.md) — documentation completeness for command classes
72
- - [Review Cross-Command Consistency](../../skills/review-cross-command-consistency/SKILL.md) — sibling consistency within a command family
73
- - [Review Backward Compatibility](../../skills/review-backward-compatibility/SKILL.md) — preserving `Git::Lib` return-value contracts
74
- - [Extract Facade from Base/Lib](../extract-facade-from-base-lib/SKILL.md) — the
75
- follow-on extraction that moves the public method from `Git::Base` /
76
- `Git::Lib` into a `Git::Repository::*` facade method (Phase 4 deletes both
77
- `Git::Base` and `Git::Lib`)
78
-
79
- ## Input
80
-
81
- Required:
82
-
83
- 1. A `Git::Lib` method that contains one or more `command(...)` calls to replace
84
- 2. The git subcommand name (derived from the first arguments to `#command`)
85
-
86
- ## Workflow
87
-
88
- ### Branch setup
89
-
90
- All work must be done on a feature branch. **Never commit or push directly to
91
- `main`.**
92
-
93
- Before starting, create a new branch:
94
-
95
- ```bash
96
- git checkout -b <feature-branch-name>
97
- ```
98
-
99
- All commits in this workflow go on the feature branch. When work is complete,
100
- open a pull request — do not merge or push directly into `main`.
101
-
102
- ### Step 1 — Identify the `#command` call
103
-
104
- 1. Locate the `Git::Lib` method that calls `command(...)`.
105
- 2. Note:
106
- - the git subcommand (first argument(s) to `#command`)
107
- - the options/arguments passed after the subcommand
108
- - execution options (e.g., `timeout:`, `out:`, `err:`, `env:`)
109
- - the return value and any post-processing (`.stdout`, parsing, regex matching)
110
- 3. Document the method's current **public contract**: signature, return type, and
111
- return-value format (String, Array, Hash, Boolean, etc.)
112
- 4. Run linters and rubocop to confirm a clean baseline:
113
-
114
- ```bash
115
- bundle exec rubocop
116
- ```
117
-
118
- Fix any issues before continuing.
119
-
120
- ### Step 2 — Plan the migration and get approval
121
-
122
- Before writing or changing any code, present a migration plan and **wait for
123
- explicit confirmation** from the user. Do not proceed until they approve.
124
-
125
- The plan must cover every `#command` call identified above. For each one, state:
126
-
127
- | `Git::Lib` method | `#command` call | Target `Git::Commands` class | Class exists? | Notes |
128
- | --- | --- | --- | --- | --- |
129
- | `some_method` | `command('sub', '--flag', arg)` | `Git::Commands::Sub` (new) or existing | ✅ / 🆕 | any mapping decisions |
130
-
131
- Also state:
132
-
133
- - Which (if any) new `Git::Commands::*` classes need to be created
134
- - How optional or empty arguments will be handled (e.g., nil vs `''` operands)
135
- - Any return-value post-processing that stays in `Git::Lib`
136
-
137
- Then ask:
138
-
139
- > Does this mapping look correct? Any changes before I start implementing?
140
-
141
- **Do not move to Step 3 until the user confirms the plan.**
142
-
143
- ### Step 3 — Ensure adequate legacy tests
144
-
145
- Before making any changes, verify that `tests/units/` has adequate tests for the
146
- `Git::Lib` method being migrated.
147
-
148
- 1. Search existing legacy tests for coverage of the method:
149
-
150
- ```bash
151
- grep -rn '<method_name>' tests/units/
152
- ```
153
-
154
- 2. If coverage is insufficient, add **minimal new tests** to the legacy test suite
155
- that exercise the method's current behavior. These tests ensure the refactor does
156
- not break backward compatibility.
157
- - Do **not** change existing tests.
158
- - Follow existing legacy test conventions (`Test::Unit::TestCase`,
159
- `assert_command_line_eq`, `in_temp_dir`, etc.).
160
- - Verify new tests pass:
161
-
162
- ```bash
163
- bundle exec bin/test <test-file-basename>
164
- ```
165
-
166
- - Run rubocop against the new test file:
167
-
168
- ```bash
169
- bundle exec rubocop tests/units/<test-file>
170
- ```
171
-
172
- - Fix any issues before continuing.
173
- 3. Commit the new legacy tests:
174
-
175
- ```bash
176
- git add tests/units/<test-file>
177
- git commit -m "refactor(test): add legacy tests for <method_name>"
178
- ```
179
-
180
- ### Step 4 — Ensure the `Git::Commands::*` class exists
181
-
182
- 1. Search `lib/git/commands/` for an existing command class that matches the git
183
- subcommand:
184
-
185
- ```bash
186
- find lib/git/commands -name '*.rb' | sort
187
- ```
188
-
189
- Also check the class contents to confirm the existing class covers the same
190
- subcommand variation (e.g., `branch --show-current` vs. `branch --list`).
191
-
192
- 2. **If the command class already exists**, skip to Step 5.
193
-
194
- 3. **If the command class does not exist**, scaffold it using the
195
- [Command Implementation](../../skills/command-implementation/SKILL.md) skill. This produces:
196
-
197
- - `lib/git/commands/<command>.rb` (or `lib/git/commands/<family>/<action>.rb`)
198
- - `spec/unit/git/commands/<command>_spec.rb`
199
- - `spec/integration/git/commands/<command>_spec.rb`
200
-
201
- 4. Verify the new command class:
202
-
203
- ```bash
204
- bundle exec rspec spec/unit/git/commands/<command>_spec.rb
205
- bundle exec rspec spec/integration/git/commands/<command>_spec.rb
206
- bundle exec rubocop lib/git/commands/<command>.rb
207
- bundle exec rake yard
208
- ```
209
-
210
- Fix any issues before continuing.
211
-
212
- 5. Commit the new command class and its tests:
213
-
214
- ```bash
215
- git add lib/git/commands/<command>*.rb spec/
216
- git commit -m "refactor(command): add Git::Commands::<Command> class"
217
- ```
218
-
219
- ### Step 5 — Update `Git::Lib` to delegate to the command class
220
-
221
- 1. Replace the `command(...)` call with a call to the `Git::Commands::*` class:
222
-
223
- ```ruby
224
- # Before
225
- def some_method(args)
226
- command('some-command', '--flag', args).stdout
227
- end
228
-
229
- # After
230
- def some_method(args)
231
- Git::Commands::SomeCommand.new(self).call(args, flag: true).stdout
232
- end
233
- ```
234
-
235
- 2. Preserve the method's **exact return value contract** — apply any parsing or
236
- transformation after `.stdout` / `.stderr` / `.status` to match the original
237
- return type.
238
-
239
- 3. **Prevent API expansion** — the command class may accept many more options than
240
- the legacy `Git::Lib` method ever exposed. Only forward the options that were
241
- part of the original `Git::Lib` method's public API. Use a `<COMMAND>_ALLOWED_OPTS`
242
- constant to whitelist permitted option keys, call `assert_valid_opts` to raise
243
- on unknown keys, then filter with `opts.slice` before forwarding:
244
-
245
- ```ruby
246
- PULL_ALLOWED_OPTS = %i[allow_unrelated_histories].freeze
247
-
248
- def pull(remote = nil, branch = nil, opts = {})
249
- assert_valid_opts(opts, PULL_ALLOWED_OPTS)
250
- allowed_opts = opts.slice(*PULL_ALLOWED_OPTS)
251
- Git::Commands::Pull.new(self).call(remote, branch, **allowed_opts).stdout
252
- end
253
- ```
254
-
255
- `assert_valid_opts` raises `ArgumentError` for any unrecognised key, giving
256
- callers a clear error instead of silently ignoring unknown options. This
257
- ensures that callers cannot accidentally pass options that happen to match
258
- command DSL option names but were never part of the public contract.
259
-
260
- 4. Add the appropriate `require_relative` at the top of `lib/git/lib.rb` if not
261
- already present.
262
-
263
- 4. Verify:
264
-
265
- ```bash
266
- bundle exec bin/test <legacy-test-file-basename>
267
- bundle exec rspec
268
- bundle exec rubocop
269
- bundle exec rake yard
270
- ```
271
-
272
- Fix any issues before continuing.
273
-
274
- 5. Commit the `Git::Lib` change:
275
-
276
- ```bash
277
- git add lib/git/lib.rb
278
- git commit -m "refactor(lib): delegate <method_name> to Git::Commands::<Command>"
279
- ```
280
-
281
- ## Commit discipline
282
-
283
- Keep work organized into **three logical commit categories** (each optional if no
284
- changes were needed for that step):
285
-
286
- 1. `refactor(test): add legacy tests for <method_name>` — new tests in
287
- `tests/units/`
288
- 2. `refactor(command): add Git::Commands::<Command> class` — new command class,
289
- unit specs, and integration specs
290
- 3. `refactor(lib): delegate <method_name> to Git::Commands::<Command>` — `Git::Lib`
291
- changes only
292
-
293
- During implementation, you may use multiple task-level commits. Before opening a
294
- PR, follow the repository finalize workflow (see
295
- [Development Workflow](../../skills/development-workflow/SKILL.md)) and squash commits as
296
- required.
297
-
298
- **Issue and PR references in commit bodies:** Do not use `#<number>` in the
299
- commit body — write `issue 1000` not `issue #1000`. A commitlint parser flaw
300
- treats any line containing `#<number>` as a footer token, breaking the
301
- body/footer split. To close an issue/PR, use `Closes`/`Fixes`/`Resolves #<number>`
302
- in the footer. To merely mention one for context, omit the `#` and no footer line
303
- is needed.
304
-
305
- If further changes are needed after task commits are created:
306
-
307
- - Amend the change to the **appropriate commit** (e.g., a command class fix goes
308
- into the `refactor(command)` commit).
309
- - Rebase the later commits on top:
310
-
311
- ```bash
312
- git rebase -i <base-commit>
313
- ```
314
-
315
- - After rebasing, verify all quality gates still pass:
316
-
317
- ```bash
318
- bundle exec rspec && bundle exec rake test && bundle exec rubocop && bundle exec rake yard
319
- ```
320
-
321
- ## Create a pull request
322
-
323
- Once all commits are clean and quality gates pass, create a PR for the branch.
324
-
325
- If changes are made after the PR is created:
326
-
327
- - Amend the change to the appropriate commit.
328
- - Rebase later commits on top.
329
- - Force-push the branch:
330
-
331
- ```bash
332
- git push --force-with-lease
333
- ```
334
-
335
- ## Quality gates (run at every step)
336
-
337
- ```bash
338
- bundle exec rspec
339
- bundle exec rake test
340
- bundle exec rubocop
341
- bundle exec rake yard
342
- ```
343
-
344
- All four must pass before committing at each step. If errors are found, fix them
345
- before continuing.
346
-
347
- ## Common patterns
348
-
349
- ### Simple delegation (stdout passthrough)
350
-
351
- ```ruby
352
- # Before
353
- def symbolic_ref(branch_name)
354
- command('symbolic-ref', 'HEAD', "refs/heads/#{branch_name}")
355
- end
356
-
357
- # After
358
- def symbolic_ref(branch_name)
359
- Git::Commands::SymbolicRef.new(self).call(branch_name).stdout
360
- end
361
- ```
362
-
363
- ### Delegation with post-processing
364
-
365
- ```ruby
366
- # Before
367
- def cat_file_type(object)
368
- command('cat-file', '-t', object).stdout
369
- end
370
-
371
- # After
372
- def cat_file_type(object)
373
- Git::Commands::CatFile::Type.new(self).call(object).stdout
374
- end
375
- ```
376
-
377
- ### Delegation with parsed return value
378
-
379
- ```ruby
380
- # Before
381
- def worktree_list
382
- worktrees = {}
383
- command('worktree', 'list', '--porcelain').stdout.split("\n").each do |w|
384
- # ... parsing ...
385
- end
386
- worktrees
387
- end
388
-
389
- # After
390
- def worktree_list
391
- result = Git::Commands::Worktree::List.new(self).call
392
- worktrees = {}
393
- result.stdout.split("\n").each do |w|
394
- # ... parsing stays in Git::Lib ...
395
- end
396
- worktrees
397
- end
398
- ```
399
-
400
- ### Delegation with opts-hash key normalization
401
-
402
- When the legacy method accepted a flat `opts` hash and uses a `KEY_NORMALIZATIONS`
403
- constant to rename option keys before forwarding them, the constant's keys must be
404
- the same type as the keys callers actually pass.
405
-
406
- **Ruby's `'key':` symbol-literal syntax creates a *symbol* key** — `{ 'update-head-ok': :x }`
407
- stores the key `:'update-head-ok'`, not the string `'update-head-ok'`. If legacy
408
- callers pass string keys, the lookup misses and the raw key is forwarded unchanged,
409
- causing a git "unsupported option" error at runtime.
410
-
411
- Always symbolize keys before the normalization lookup:
412
-
413
- ```ruby
414
- # ❌ Bug — string key 'update-head-ok' misses the symbol key :'update-head-ok'
415
- opts = opts.transform_keys { |k| KEY_NORMALIZATIONS.fetch(k, k) }
416
-
417
- # ✅ Correct — symbolize first so both string and symbol callers match
418
- opts = opts.transform_keys do |k|
419
- sym = k.is_a?(Symbol) ? k : k.to_sym
420
- KEY_NORMALIZATIONS.fetch(sym, sym)
421
- end
422
- ```
423
-
424
- **When to apply this pattern:** Whenever `transform_keys` is combined with a
425
- normalization constant whose keys use the `'hyphenated-name':` symbol-literal
426
- syntax. Scan the constant's definition to confirm its keys are symbols, then
427
- confirm whether existing callers pass strings or symbols. If callers are a mix —
428
- or if callers are `Git::Base` or `Git::Lib` methods that forward user-supplied
429
- hashes — add the symbolization guard.
430
-
431
- ### Delegation with option filtering (preventing API expansion)
432
-
433
- A command class may expose many more options than the legacy `Git::Lib` method
434
- ever accepted. Without filtering, callers could accidentally pass options that
435
- happen to match command DSL names but were never part of the public contract.
436
-
437
- The facade is also where **policy options** are set as safe defaults — options
438
- that support non-interactive execution, control output format for parsing, or
439
- set other command-level defaults. The command class stays neutral; the facade
440
- makes the defaults explicit. Some defaults are **fixed** (not in `ALLOWED_OPTS` —
441
- `assert_valid_opts` rejects them if a caller supplies them); others are
442
- **overridable** (in `ALLOWED_OPTS`, placed before `**opts` so the caller's value
443
- wins on collision). Examples: `no_edit: true`, `verbose: true`,
444
- `no_progress: true`, `no_color: true`.
445
- See "Command-layer neutrality" in CONTRIBUTING.md.
446
-
447
- Declare an `<COMMAND>_ALLOWED_OPTS` constant listing only the options that were
448
- present in the original method. Call `assert_valid_opts` first to raise
449
- `ArgumentError` on unrecognised keys, then use `opts.slice` to filter before
450
- forwarding:
451
-
452
- ```ruby
453
- # Only :allow_unrelated_histories was accepted by the original Git::Lib#pull
454
- PULL_ALLOWED_OPTS = %i[allow_unrelated_histories].freeze
455
-
456
- def pull(remote = nil, branch = nil, opts = {})
457
- raise ArgumentError, 'You must specify a remote if a branch is specified' if remote.nil? && !branch.nil?
458
-
459
- assert_valid_opts(opts, PULL_ALLOWED_OPTS)
460
- allowed_opts = opts.slice(*PULL_ALLOWED_OPTS)
461
- positional_args = [remote, branch].compact
462
- # no_edit: true is the non-interactive default (see CONTRIBUTING.md)
463
- Git::Commands::Pull.new(self).call(*positional_args, no_edit: true, **allowed_opts).stdout
464
- end
465
- ```
466
-
467
- `assert_valid_opts` is a private helper already defined in `Git::Lib` — no extra
468
- require is needed. It raises `ArgumentError: Unknown options: <key>` when any
469
- unrecognised key is present, giving callers a clear error rather than silently
470
- dropping the option.
471
-
472
- Name the constant after the git subcommand (`PULL_ALLOWED_OPTS`, `FETCH_ALLOWED_OPTS`,
473
- etc.) and place it immediately before the method definition.
474
-
475
- ## What stays in `Git::Lib`
476
-
477
- - Output parsing and transformation (until a parser class is created)
478
- - Return-value adaptation to preserve backward compatibility
479
- - Option validation and filtering to prevent API expansion (see `<COMMAND>_ALLOWED_OPTS` + `assert_valid_opts` pattern)
480
- - Deprecation shims (e.g., option renames)
481
- - Method signatures and public API surface
482
-
483
- ## What moves to `Git::Commands::*`
484
-
485
- - Argument building and CLI flag generation
486
- - `#command` invocation
487
- - Exit-status handling via `allow_exit_status`
@@ -1,22 +0,0 @@
1
- # Keyword-arg remediation list
2
-
3
- The following facade methods are known to use `**opts`/`**` keyword-splat where
4
- the legacy `Git::Base` or `Git::Lib` predecessor used a positional options hash
5
- (`opts = {}`). Each is a candidate `legacy-contract` violation that must be
6
- resolved before `Git.open`/`.clone`/`.init`/`.bare` are changed to return
7
- `Git::Repository`: either fix the signature or
8
- record an explicit `5.x-native` justification.
9
-
10
- | Facade method | Current signature | Expected classification | Action needed |
11
- | --- | --- | --- | --- |
12
- | `Git::Repository::Staging#add` | `add(paths = '.', **)` | `legacy-contract` | Verify against 4.x `Git::Lib#add`; change to `opts = {}` if legacy |
13
- | `Git::Repository::Staging#reset` | `reset(commitish = nil, **)` | `legacy-contract` | Verify against 4.x `Git::Lib#reset`; change to `opts = {}` if legacy |
14
- | `Git::Repository::Committing#commit` | `commit(message = nil, **opts)` | `legacy-contract` | Verify against 4.x `Git::Base#commit`; change to `opts = {}` if legacy |
15
- | `Git::Repository::Committing#commit_all` | `commit_all(*, **)` | `legacy-contract` | Verify against 4.x `Git::Base#commit_all`; change to `opts = {}` if legacy |
16
- | `Git::Repository::Committing#commit_tree` | `commit_tree(tree, **opts)` | needs classification | Classify; if 5.x-native confirm; if legacy-contract fix signature |
17
- | `Git::Repository::Committing#write_and_commit_tree` | `write_and_commit_tree(**)` | needs classification | Classify; if 5.x-native confirm; if legacy-contract fix signature |
18
- | `Git::Repository::Branching#branch_delete` | `branch_delete(*branches, **options)` | needs classification | Verify against 4.x `Git::Base#branch_delete`; classify and fix or confirm |
19
- | `Git::Repository::Inspecting#fsck` | `fsck(*objects, **)` | needs classification | Verify against 4.x `Git::Lib#fsck`; classify and fix or confirm |
20
-
21
- This list is seeded from a static scan of `lib/git/repository/**/*.rb` and may be
22
- incomplete. A full public-method inventory is required before closing the sweep.