git 5.1.0 → 5.3.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 (118) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +78 -0
  3. data/CONTRIBUTING.md +202 -169
  4. data/LICENSE +1 -1
  5. data/README.md +192 -151
  6. data/UPGRADING.md +287 -1
  7. data/git.gemspec +35 -3
  8. data/lib/git/author.rb +11 -0
  9. data/lib/git/author_info.rb +66 -0
  10. data/lib/git/branch.rb +210 -15
  11. data/lib/git/branch_info.rb +1 -1
  12. data/lib/git/branches.rb +35 -7
  13. data/lib/git/command_line/base.rb +1 -2
  14. data/lib/git/commands/base.rb +1 -1
  15. data/lib/git/commands/cat_file/raw.rb +62 -7
  16. data/lib/git/object.rb +13 -7
  17. data/lib/git/parsers/stash.rb +50 -17
  18. data/lib/git/parsers/tag.rb +54 -8
  19. data/lib/git/remote.rb +37 -7
  20. data/lib/git/remote_info.rb +67 -10
  21. data/lib/git/repository/branching.rb +190 -6
  22. data/lib/git/repository/merging.rb +96 -2
  23. data/lib/git/repository/remote_operations.rb +57 -0
  24. data/lib/git/repository/shared_private.rb +67 -0
  25. data/lib/git/stash_info.rb +32 -34
  26. data/lib/git/tag_info.rb +21 -29
  27. data/lib/git/version.rb +1 -1
  28. data/lib/git.rb +1 -0
  29. metadata +6 -94
  30. data/.claude/commands/address-copilot-reviews.md +0 -14
  31. data/.claude/settings.json +0 -16
  32. data/.claude/skills +0 -1
  33. data/.commitlintrc.yml +0 -38
  34. data/.dockerignore +0 -27
  35. data/.github/copilot-instructions.md +0 -30
  36. data/.github/hooks/bin-setup-on-worktree.json +0 -11
  37. data/.github/hooks/run-bin-setup-once.sh +0 -20
  38. data/.github/issue_template.md +0 -15
  39. data/.github/prompts/iteratively-address-copilot-reviews.prompt.md +0 -188
  40. data/.github/pull_request_template.md +0 -21
  41. data/.github/skills/breaking-change-analysis/SKILL.md +0 -99
  42. data/.github/skills/ci-cd-troubleshooting/SKILL.md +0 -264
  43. data/.github/skills/command-implementation/REFERENCE.md +0 -994
  44. data/.github/skills/command-implementation/SKILL.md +0 -230
  45. data/.github/skills/command-test-conventions/SKILL.md +0 -664
  46. data/.github/skills/command-yard-documentation/SKILL.md +0 -434
  47. data/.github/skills/dependency-management/SKILL.md +0 -72
  48. data/.github/skills/development-workflow/SKILL.md +0 -512
  49. data/.github/skills/facade-implementation/REFERENCE.md +0 -837
  50. data/.github/skills/facade-implementation/SKILL.md +0 -269
  51. data/.github/skills/facade-test-conventions/SKILL.md +0 -391
  52. data/.github/skills/facade-yard-documentation/SKILL.md +0 -435
  53. data/.github/skills/make-skill-template/SKILL.md +0 -226
  54. data/.github/skills/pr-readiness-review/SKILL.md +0 -205
  55. data/.github/skills/project-context/SKILL.md +0 -306
  56. data/.github/skills/pull-request-review/SKILL.md +0 -168
  57. data/.github/skills/rebase/SKILL.md +0 -148
  58. data/.github/skills/refactor-command-to-commandlineresult/SKILL.md +0 -131
  59. data/.github/skills/release-management/SKILL.md +0 -125
  60. data/.github/skills/resolve-feedback/SKILL.md +0 -288
  61. data/.github/skills/review-arguments-dsl/CHECKLIST.md +0 -788
  62. data/.github/skills/review-arguments-dsl/SKILL.md +0 -214
  63. data/.github/skills/review-cross-command-consistency/SKILL.md +0 -139
  64. data/.github/skills/reviewing-skills/SKILL.md +0 -214
  65. data/.github/skills/rspec-unit-testing-standards/SKILL.md +0 -685
  66. data/.github/skills/tdd-refactor-step/SKILL.md +0 -236
  67. data/.github/skills/test-debugging/SKILL.md +0 -161
  68. data/.github/skills/yard-documentation/SKILL.md +0 -981
  69. data/.github/skills/yard-documentation/element-rules.md +0 -162
  70. data/.github/skills-deprecated/README.md +0 -21
  71. data/.github/skills-deprecated/extract-command-from-lib/SKILL.md +0 -487
  72. data/.github/skills-deprecated/extract-facade-from-base-lib/KEYWORD_ARG_REMEDIATION.md +0 -22
  73. data/.github/skills-deprecated/extract-facade-from-base-lib/SKILL.md +0 -600
  74. data/.github/skills-deprecated/review-backward-compatibility/SKILL.md +0 -275
  75. data/.github/workflows/continuous_integration.yml +0 -358
  76. data/.github/workflows/enforce_conventional_commits.yml +0 -35
  77. data/.github/workflows/experimental_continuous_integration.yml +0 -59
  78. data/.github/workflows/release.yml +0 -52
  79. data/.github/workflows/warm_bundler_caches.yml +0 -82
  80. data/.gitignore +0 -30
  81. data/.husky/commit-msg +0 -1
  82. data/.husky/pre-commit +0 -13
  83. data/.release-please-config.json +0 -36
  84. data/.release-please-manifest.json +0 -3
  85. data/.rspec +0 -2
  86. data/.rubocop.yml +0 -44
  87. data/.rubocop_todo.yml +0 -30
  88. data/.yard-lint.yml +0 -75
  89. data/CLAUDE.md +0 -11
  90. data/Gemfile +0 -22
  91. data/Rakefile +0 -41
  92. data/docker/test/Dockerfile +0 -32
  93. data/docker/test/docker-compose.yml +0 -0
  94. data/package.json +0 -10
  95. data/redesign/1_architecture_existing.md +0 -102
  96. data/redesign/2_architecture_redesign.md +0 -449
  97. data/redesign/3_architecture_implementation.md +0 -1623
  98. data/redesign/Phase 4 - Step A.md +0 -366
  99. data/redesign/Phase 4 - Step B.md +0 -921
  100. data/redesign/Phase 4 - Step C.md +0 -833
  101. data/redesign/beta_release.md +0 -107
  102. data/redesign/branch_parse_refactor_plan.md +0 -163
  103. data/redesign/c1a-public-api-scope.tsv +0 -256
  104. data/redesign/c1c2_audit.md +0 -566
  105. data/redesign/c1c2_bucket6_lib_orphans.md +0 -626
  106. data/redesign/config_design.rb +0 -501
  107. data/redesign/index.md +0 -34
  108. data/redesign/info_object_migration_plan.md +0 -126
  109. data/redesign/integration_test_analysis.md +0 -521
  110. data/redesign/phase-4-step-b-test-audit.tsv +0 -485
  111. data/redesign/remote_refactor_plan.md +0 -164
  112. data/redesign/reverse_dependencies.sql +0 -44
  113. data/tasks/gem_tasks.rake +0 -14
  114. data/tasks/npm_tasks.rake +0 -7
  115. data/tasks/rspec.rake +0 -111
  116. data/tasks/rubocop.rake +0 -5
  117. data/tasks/test_gem.rake +0 -12
  118. data/tasks/yard.rake +0 -57
data/CONTRIBUTING.md CHANGED
@@ -42,9 +42,10 @@
42
42
  - [Issue and PR references](#issue-and-pr-references)
43
43
  - [Testing guidelines](#testing-guidelines)
44
44
  - [Test coverage policy](#test-coverage-policy)
45
- - [Unit tests vs Integration tests](#unit-tests-vs-integration-tests)
45
+ - [Unit tests vs integration tests](#unit-tests-vs-integration-tests)
46
+ - [What ships in the gem](#what-ships-in-the-gem)
46
47
  - [Building a specific version of the Git command-line](#building-a-specific-version-of-the-git-command-line)
47
- - [Install pre-requisites](#install-pre-requisites)
48
+ - [Install prerequisites](#install-prerequisites)
48
49
  - [Obtain Git source code](#obtain-git-source-code)
49
50
  - [Build git](#build-git)
50
51
  - [Use the new Git version](#use-the-new-git-version)
@@ -53,9 +54,8 @@
53
54
 
54
55
  Thank you for your interest in contributing to the `ruby-git` project.
55
56
 
56
- This document provides guidelines for contributing to the `ruby-git` project. While
57
- these guidelines may not cover every situation, we encourage you to use your best
58
- judgment when contributing.
57
+ This document provides guidelines for contributing to the `ruby-git` project. They
58
+ may not cover every situation, so use your best judgment.
59
59
 
60
60
  If you have suggestions for improving these guidelines, please propose changes via a
61
61
  pull request.
@@ -75,7 +75,7 @@ You can contribute in the following ways:
75
75
 
76
76
  ## How to report an issue or request a feature
77
77
 
78
- `ruby-git` utilizes [GitHub
78
+ `ruby-git` uses [GitHub
79
79
  Issues](https://help.github.com/en/github/managing-your-work-on-github/about-issues)
80
80
  for issue tracking and feature requests.
81
81
 
@@ -96,14 +96,15 @@ prerequisite is missing.
96
96
  | Ruby | `>= 3.2.0` (matches `required_ruby_version` in [`git.gemspec`](git.gemspec)) | A version manager such as [rbenv](https://github.com/rbenv/rbenv), [asdf](https://asdf-vm.com/), [chruby](https://github.com/postmodern/chruby), or [rvm](https://rvm.io/) is recommended so you can match the project's CI matrix. |
97
97
  | Bundler | Any 2.x or 4.x | Install with `gem install bundler`. |
98
98
  | git | `>= 2.28.0` (matches `git.gemspec` `requirements`) | Older git versions are not supported and the test suite will not pass against them. |
99
- | Node.js / npm | Optional | Required only to install the local Conventional Commit `commit-msg` hook (Husky + commitlint). If npm is missing, `bin/setup` will warn and continue CI will still validate commit messages. |
99
+ | Node.js / npm | Optional | Required only to install the local Conventional Commit `commit-msg` hook (Husky + commitlint). If npm is missing, `bin/setup` will warn and continue. CI will still validate commit messages. |
100
+ | [lychee](https://lychee.cli.rs) | `>= 0.24.0` | Runs the markdown link check (`rake markdown:links`), which is part of the default task. The floor comes from [`.lychee.toml`](.lychee.toml): older releases cannot parse the enum form of `include_fragments`. Install with `brew install lychee` (macOS), `snap install lychee` (Ubuntu), `pacman -S lychee` (Arch), `winget install --id lycheeverse.lychee` (Windows), or see the [install docs](https://github.com/lycheeverse/lychee#installation). |
100
101
 
101
102
  #### A note for Windows contributors
102
103
 
103
104
  A few unit specs create real symlinks, which on Windows requires
104
105
  `SeCreateSymbolicLinkPrivilege`. A non-elevated process only holds that privilege
105
106
  when Developer Mode is enabled (Settings → System → For developers). Without it
106
- those specs skip rather than fail, so `bundle exec rake` still passes but the
107
+ those specs skip rather than fail, so `bundle exec rake` still passes, but the
107
108
  behavior they cover goes unverified locally.
108
109
 
109
110
  The same privilege decides whether Git for Windows materializes the committed
@@ -121,7 +122,8 @@ bin/setup
121
122
  `bin/setup` will:
122
123
 
123
124
  1. Verify the prerequisites above and exit with a non-zero status if any are
124
- missing or out of date.
125
+ missing, or if Ruby, git, or lychee is out of date. (Bundler is only
126
+ checked for presence, not version.)
125
127
  2. Run `bundle install` to install Ruby gem dependencies.
126
128
  3. Run `npm install` (when npm is available) to install the Conventional Commit
127
129
  `commit-msg` hook used by this project (Husky + commitlint). A separate
@@ -129,6 +131,12 @@ bin/setup
129
131
  protected branches (`main`, `4.x`).
130
132
  4. Verify the toolchain by running `bundle exec rake --tasks`.
131
133
 
134
+ `bin/setup` checks for [lychee](https://lychee.cli.rs) alongside Ruby, git, and
135
+ Bundler, and exits non-zero when it is missing or too old. lychee is a Rust binary
136
+ rather than a gem, so `bundle install` cannot supply it and `bin/setup` cannot
137
+ install it for you. Every platform this project supports has a packaged build,
138
+ and the error message names the command for yours.
139
+
132
140
  ### Verify the toolchain
133
141
 
134
142
  Once `bin/setup` succeeds, confirm the full test and lint suite passes locally:
@@ -137,21 +145,29 @@ Once `bin/setup` succeeds, confirm the full test and lint suite passes locally:
137
145
  bundle exec rake
138
146
  ```
139
147
 
140
- This is the same default task that runs in CI and is the canonical way to
141
- validate a change before requesting review.
148
+ This runs everything CI checks: specs, RuboCop, the markdown link check, YARD,
149
+ and the gem build. It is the canonical way to validate a change before
150
+ requesting review.
151
+
152
+ One caveat on the `links` task: passing locally does not guarantee the CI job
153
+ passes, and the gap is the environment rather than the tool. A link whose
154
+ capitalization is wrong resolves on a case-insensitive filesystem such as macOS and
155
+ 404s on the Linux runner, so `](docs/README.MD)` against a file named `README.md`
156
+ looks fine locally and fails in CI. [`tasks/markdown.rake`](tasks/markdown.rake) lists
157
+ this and the other differences. CI remains the authoritative link check.
142
158
 
143
159
  ### Contributor validation policy
144
160
 
145
161
  Contributors are expected to run `bundle exec rake` locally and confirm it
146
- passes before requesting review on a pull request trivial documentation-only
162
+ passes before requesting review on a pull request. Trivial documentation-only
147
163
  fixes (e.g., typo corrections in markdown files) are excepted. "CI passed" is
148
164
  not a substitute for local validation; it is a backstop. This applies equally to
149
- human-authored and AI-assisted contributions — see
165
+ human-authored and AI-assisted contributions. See
150
166
  [AI-assisted contributions](#ai-assisted-contributions).
151
167
 
152
168
  ## How to submit a code or documentation change
153
169
 
154
- There is a three-step process for submitting code or documentation changes:
170
+ Submitting a code or documentation change has three steps:
155
171
 
156
172
  1. [Commit your changes to a fork of
157
173
  `ruby-git`](#commit-your-changes-to-a-fork-of-ruby-git) using [Conventional
@@ -179,9 +195,9 @@ feature](https://help.github.com/en/github/collaborating-with-issues-and-pull-re
179
195
  Once your pull request is ready for review, request a review from at least one
180
196
  [maintainer](MAINTAINERS.md) and any other contributors you deem necessary.
181
197
 
182
- During the review process, you may need to make additional commits, which should be
183
- squashed. Additionally, you will need to rebase your branch to the latest version of
184
- the target branch (e.g., `main` or `4.x`) before merging.
198
+ During the review process, you may need to make additional commits; squash them.
199
+ You will also need to rebase your branch onto the latest version of the target
200
+ branch (e.g., `main` or `4.x`) before merging.
185
201
 
186
202
  At least one approval from a project maintainer is required before your pull request
187
203
  can be merged. The maintainer is responsible for ensuring that the pull request meets
@@ -194,7 +210,7 @@ Before moving a pull request out of draft or requesting a review, confirm:
194
210
  - [ ] `bundle exec rake` passes locally on your branch (see
195
211
  [Local development setup](#local-development-setup)).
196
212
  - [ ] New or changed code has accompanying tests under `spec/`
197
- (see [Unit tests vs Integration tests](#unit-tests-vs-integration-tests)).
213
+ (see [Unit tests vs integration tests](#unit-tests-vs-integration-tests)).
198
214
  - [ ] Every commit message follows [Conventional Commits](#commit-message-guidelines).
199
215
  - [ ] User-facing changes are documented in `README.md` and/or YARD as appropriate.
200
216
 
@@ -205,10 +221,14 @@ first keeps the review cycle short.
205
221
 
206
222
  This project maintains two active branches:
207
223
 
208
- - **`main`**: Active development for the next major version (v5.0.0+). This branch
209
- may contain breaking changes.
210
- - **`4.x`**: Maintenance branch for the v4.x release series. This branch receives bug
211
- fixes and backward-compatible improvements only.
224
+ - **`main`**: All development. It releases the next version of the gem, including
225
+ the next major version.
226
+ - **`4.x`**: The maintenance branch for the most recent previous major series. It
227
+ receives bug fixes and security fixes, and backward-compatible features at the
228
+ maintainers' discretion.
229
+
230
+ The README's [Release support policy](README.md#release-support-policy) says how long
231
+ each major series is supported.
212
232
 
213
233
  When submitting a pull request:
214
234
 
@@ -216,6 +236,15 @@ When submitting a pull request:
216
236
  - **Bug fixes**: Target `main`, and maintainers will backport to `4.x` if applicable
217
237
  - **Security fixes**: Target both branches or `4.x` if the issue only affects v4.x
218
238
 
239
+ Removing a deprecated API follows the
240
+ [deprecation policy](.github/skills/breaking-change-analysis/SKILL.md#step-4-deprecation-policy):
241
+
242
+ A removal PR merges to main only when its deprecation warning and `UPGRADING.md` entry
243
+ are contained in a previous normal release. Once any removal has merged to main, main
244
+ becomes the release line for the next major version. If another release of the
245
+ previous major is needed, it is cut from a branch created for that major (e.g. `4.x`
246
+ or `5.x`).
247
+
219
248
  ## AI-assisted contributions
220
249
 
221
250
  AI-assisted contributions are welcome. Please review and apply our [AI
@@ -223,7 +252,7 @@ Policy](AI_POLICY.md) before submitting changes. You are responsible for
223
252
  understanding and verifying any AI-assisted work included in PRs and ensuring it
224
253
  meets our standards for quality, security, and licensing.
225
254
 
226
- The **human submitter** not the AI agent is responsible for ensuring that
255
+ The human submitter, not the AI agent, is responsible for ensuring that
227
256
  `bundle exec rake` passes locally before requesting review. This is true even
228
257
  when the change was authored end-to-end by an agent. "The agent ran the tests"
229
258
  and "CI is green" are not substitutes for the submitter running
@@ -239,7 +268,6 @@ every supported agent.
239
268
  | --- | --- | --- |
240
269
  | Project instructions | [`.github/copilot-instructions.md`](.github/copilot-instructions.md) | Claude Code, via an import in [`CLAUDE.md`](CLAUDE.md) |
241
270
  | Skills | [`.github/skills/`](.github/skills/) | Claude Code, via the `.claude/skills` symlink |
242
- | Prompts | [`.github/prompts/`](.github/prompts/) | Claude Code, via wrappers in `.claude/commands/` |
243
271
  | Setup hook | [`.github/hooks/run-bin-setup-once.sh`](.github/hooks/run-bin-setup-once.sh) | Claude Code, via `.claude/settings.json` |
244
272
 
245
273
  Always edit the canonical file. The Claude Code side is a pointer in every case, so
@@ -251,54 +279,55 @@ or an elevated shell). Without it, Windows contributors get a plain text file th
251
279
  and Claude Code silently loads no skills; either enable symlinks or point your agent
252
280
  at [`.github/skills/`](.github/skills/) directly. Copilot is unaffected.
253
281
 
282
+ The symlink stays out of the published gem, so it never reaches users. See
283
+ [What ships in the gem](#what-ships-in-the-gem).
284
+
254
285
  ### Agent skills
255
286
 
256
287
  If you use an AI coding agent that understands repository skills, the
257
288
  [`.github/skills/`](.github/skills/) directory contains optional, project-specific
258
289
  guidance that mirrors maintainer expectations:
259
290
 
260
- - [`project-context`](.github/skills/project-context/SKILL.md) architecture, coding
291
+ - [`project-context`](.github/skills/project-context/SKILL.md): architecture, coding
261
292
  standards, design philosophy, and compatibility requirements
262
- - [`development-workflow`](.github/skills/development-workflow/SKILL.md) TDD workflow
293
+ - [`development-workflow`](.github/skills/development-workflow/SKILL.md): TDD workflow
263
294
  for bug fixes, features, refactoring, and maintenance tasks
264
295
  - [`command-implementation`](.github/skills/command-implementation/SKILL.md) and
265
- [`facade-implementation`](.github/skills/facade-implementation/SKILL.md) guidance for
296
+ [`facade-implementation`](.github/skills/facade-implementation/SKILL.md): guidance for
266
297
  adding or updating command classes and `Git::Repository` facade methods
267
- - [`review-arguments-dsl`](.github/skills/review-arguments-dsl/SKILL.md) audits
298
+ - [`review-arguments-dsl`](.github/skills/review-arguments-dsl/SKILL.md): audits
268
299
  `arguments do ... end` blocks against the git CLI
269
300
  - [`rspec-unit-testing-standards`](.github/skills/rspec-unit-testing-standards/SKILL.md),
270
301
  [`command-test-conventions`](.github/skills/command-test-conventions/SKILL.md), and
271
- [`facade-test-conventions`](.github/skills/facade-test-conventions/SKILL.md) testing
302
+ [`facade-test-conventions`](.github/skills/facade-test-conventions/SKILL.md): testing
272
303
  conventions for new and updated code
273
304
  - [`yard-documentation`](.github/skills/yard-documentation/SKILL.md),
274
305
  [`command-yard-documentation`](.github/skills/command-yard-documentation/SKILL.md), and
275
- [`facade-yard-documentation`](.github/skills/facade-yard-documentation/SKILL.md)
306
+ [`facade-yard-documentation`](.github/skills/facade-yard-documentation/SKILL.md):
276
307
  documentation standards
277
308
  - [`test-debugging`](.github/skills/test-debugging/SKILL.md) and
278
- [`ci-cd-troubleshooting`](.github/skills/ci-cd-troubleshooting/SKILL.md) help for
309
+ [`ci-cd-troubleshooting`](.github/skills/ci-cd-troubleshooting/SKILL.md): help for
279
310
  failing or flaky tests and CI failures
280
- - [`breaking-change-analysis`](.github/skills/breaking-change-analysis/SKILL.md) impact
311
+ - [`breaking-change-analysis`](.github/skills/breaking-change-analysis/SKILL.md): impact
281
312
  analysis before removing methods, changing interfaces, or planning deprecations
282
- - [`pr-readiness-review`](.github/skills/pr-readiness-review/SKILL.md) final checks
313
+ - [`pr-readiness-review`](.github/skills/pr-readiness-review/SKILL.md): final checks
283
314
  before requesting review
284
315
 
285
316
  ## Design philosophy
286
317
 
287
- The `git` gem follows a design philosophy that allows users to leverage their
288
- existing knowledge of Git while benefiting from the expressiveness and power of
289
- Ruby's syntax and paradigms.
318
+ The `git` gem lets users apply what they already know about Git while working in
319
+ idiomatic Ruby.
290
320
 
291
- Its public API is designed as a lightweight wrapper around the `git` command-line
292
- tool, providing Ruby developers with a simple and intuitive interface for
293
- programmatically interacting with Git.
321
+ Its public API is a lightweight wrapper around the `git` command-line tool that
322
+ gives Ruby developers a direct way to run Git programmatically.
294
323
 
295
- This gem adheres to the "principle of least surprise," ensuring that it does not
296
- introduce unnecessary abstraction layers or modify Git's core functionality. Instead,
297
- the gem maintains a close alignment with the existing `git` command-line interface,
298
- avoiding extensions or alterations that could lead to unexpected behaviors.
324
+ This gem follows the principle of least surprise: it does not add unnecessary
325
+ abstraction layers or modify Git's core functionality. It stays close to the existing `git`
326
+ command-line interface and avoids extensions or alterations that could lead to
327
+ unexpected behavior.
299
328
 
300
329
  `git` commands generally translate to `Git::Repository` methods of the same name.
301
- Positional arguments map to the `git` CLI operands (such as paths, SHAs, etc.) in the
330
+ Positional arguments map to the `git` CLI operands (such as paths and SHAs) in the
302
331
  same order. Keyword arguments map to `git` CLI options by long OR short name.
303
332
 
304
333
  Some examples:
@@ -314,7 +343,7 @@ The `git` gem is organized into three architectural layers:
314
343
  | --- | --- | --- |
315
344
  | **Facade** (`Git::Repository` and `Git`) | Public API | Normalizes Ruby arguments, sets safe defaults, calls one or more `Git::Commands::*` classes, and may parse output into public Ruby objects |
316
345
  | **Command** (`Git::Commands::*`) | Neutral git CLI interface | Declares CLI arguments via the [Arguments DSL](lib/git/commands/arguments.rb), builds the git argv and executes git via `#call`, and returns `Git::CommandLine::Result` |
317
- | **Execution** (`Git::ExecutionContext::*`) | Execution context and subprocess defaults | Carries execution settings such as working directory, environment, timeout, binary path, and logging; runs the git CLI with default global options (such as `-c color.ui=false`) and subprocess environment variables (such as a platform-conditional `LC_ALL` `en_US.UTF-8` on macOS, `C.UTF-8` elsewhere) |
346
+ | **Execution** (`Git::ExecutionContext::*`) | Execution context and subprocess defaults | Carries execution settings such as working directory, environment, timeout, binary path, and logging; runs the git CLI with default global options (such as `-c color.ui=false`) and subprocess environment variables (such as a platform-conditional `LC_ALL`, which is `en_US.UTF-8` on macOS and `C.UTF-8` elsewhere) |
318
347
 
319
348
  Command classes (`Git::Commands::*`) are **faithful, neutral representations of the
320
349
  git CLI**. Each command class does the following:
@@ -334,7 +363,7 @@ environment where an editor is desired).
334
363
 
335
364
  For example:
336
365
 
337
- - **Anti-pattern:** declaring non-overidable and non-default options in the Arguments
366
+ - **Anti-pattern:** declaring non-overridable and non-default options in the Arguments
338
367
  DSL to control output such as `literal '--no-edit'`, `literal '--verbose'`, or
339
368
  `literal '--no-progress'` inside a command class. This embeds policy in the wrong
340
369
  layer.
@@ -362,8 +391,8 @@ Implementing the command has two major tasks: [API design](#api-design) and
362
391
 
363
392
  ### API design
364
393
 
365
- The section focuses on deciding where git command methods belong, how to name them,
366
- and how to handle parameters and output. These describe the public interface that gem
394
+ This section covers where git command methods belong, how to name them, and how to
395
+ handle parameters and output. These describe the public interface that gem
367
396
  users will see.
368
397
 
369
398
  #### Method placement
@@ -433,8 +462,7 @@ repo.config_unset('user.name') # git config --unset user.name
433
462
  repo.config_get_all('remote.origin.url') # git config --get-all remote.origin.url
434
463
  ```
435
464
 
436
- To enhance usability, aliases may be introduced to provide more user-friendly method
437
- names where appropriate.
465
+ Aliases may be added to provide friendlier method names where appropriate.
438
466
 
439
467
  See also [Output processing](#output-processing) for when different output formats
440
468
  require separate methods.
@@ -444,21 +472,20 @@ require separate methods.
444
472
  Parsed result objects returned from facade methods follow a reserved suffix
445
473
  convention:
446
474
 
447
- - **`*Info`** a parsed metadata struct returned from a query (e.g., `BranchInfo`,
475
+ - **`*Info`**: a parsed metadata struct returned from a query (e.g., `BranchInfo`,
448
476
  `TagInfo`, `StashInfo`, `DiffInfo`). Always lives in the top-level `Git::`
449
477
  namespace.
450
- - **`*Result`** the outcome of a mutating or destructive operation (e.g.,
478
+ - **`*Result`**: the outcome of a mutating or destructive operation (e.g.,
451
479
  `BranchDeleteResult`, `TagDeleteResult`). Also lives in `Git::`.
452
480
 
453
- Do **not** use these suffixes on `Git::Commands::*` command classes those are
481
+ Do not use these suffixes on `Git::Commands::*` command classes. Those are
454
482
  subprocess runners, not data objects. A reader seeing `Commands::Foo::BarInfo`
455
483
  expects a parsed struct, not a class that shells out to git.
456
484
 
457
485
  #### Parameter naming
458
486
 
459
487
  Parameters within the `git` gem methods are named after their corresponding long
460
- command-line options, ensuring familiarity and ease of use for developers already
461
- accustomed to Git.
488
+ command-line options, so developers already accustomed to Git will recognize them.
462
489
 
463
490
  For example, `git config --global` becomes `global: true`, and `git config --file`
464
491
  becomes `file: '/path/to/config'`.
@@ -506,42 +533,6 @@ accept these via an options splat parameter (e.g., `def replace(object, replacem
506
533
  => 'value' }` or as an array of pairs if multiple are allowed.
507
534
  - `git -c user.name=Scott` → `c: { 'user.name' => 'Scott' }`
508
535
 
509
- - **Mutually exclusive options**: If options are mutually exclusive (e.g.,
510
- `--global`, `--local`, `--system`), only one may be used at a time. Setting more
511
- than one raises `ArgumentError`. The DSL enforces this via `conflicts`
512
- declarations at bind time. For **negatable flag options** (`negatable: true`),
513
- passing `false` (which emits `--no-flag`) also counts as using that option in the
514
- conflict check; non-negatable `false` is treated as absent.
515
-
516
- - **Forbidden value combinations (negatable flags)**: When two negatable flags may
517
- both be present but only certain value pairings are contradictory, use
518
- `forbid_values` declarations instead of (or in addition to) `conflicts`.
519
- `conflicts` is presence-based and blocks all co-presence; `forbid_values` blocks
520
- only the exact `name: value` tuples listed, leaving semantically equivalent pairs
521
- valid. For example, `--all --no-ignore-removal` and `--no-all --ignore-removal`
522
- are equivalent and should remain allowed, while `--all --ignore-removal` and
523
- `--no-all --no-ignore-removal` are contradictory and should be rejected:
524
-
525
- ```ruby
526
- forbid_values all: true, ignore_removal: true # contradictory
527
- forbid_values all: false, ignore_removal: false # contradictory
528
- ```
529
-
530
- Unknown names raise `ArgumentError` at definition time. Alias names are
531
- canonicalized automatically.
532
-
533
- - **Exactly-one required from a mutually exclusive group**: When exactly one of a
534
- group of arguments must be provided (e.g., a command that accepts exactly one of
535
- `--mode-a`, `--mode-b`, or `--mode-c`), omitting all of them or supplying more
536
- than one raises `ArgumentError`. The DSL enforces this via
537
- `requires_exactly_one_of` declarations, which combine `requires_one_of`
538
- (at-least-one) and `conflicts` (at-most-one) in a single declaration.
539
-
540
- - **At-least-one required**: When at least one of a group of arguments (options or
541
- positional) must be provided, but the group is not mutually exclusive, omitting
542
- all of them raises `ArgumentError`. The DSL enforces this via `requires_one_of`
543
- declarations at bind time.
544
-
545
536
  ##### Positional arguments
546
537
 
547
538
  Arguments that are not options (e.g., file names, branch names) are passed as method
@@ -562,7 +553,7 @@ arguments, not as keyword arguments.
562
553
  (required).
563
554
  - `git add [<pathspec>...]` → `def add(*paths)`
564
555
 
565
- - **Mixed single-valued and multi-valued positional arguments `--` separated
556
+ - **Mixed single-valued and multi-valued positional arguments, `--` separated
566
557
  (independently reachable groups)**: When a git command separates two optional
567
558
  groups with `--` (e.g., `[<tree-ish>] [-- <pathspec>...]`), callers may want
568
559
  to supply the post-`--` group *without* supplying the first group. Use the
@@ -577,7 +568,7 @@ arguments, not as keyword arguments.
577
568
  - Callers can then do `checkout(pathspecs: ['file.rb'])` (no branch) or
578
569
  `diff('HEAD~3', pathspec: ['file.rb'])` (both), with no ambiguity.
579
570
 
580
- - **Multiple optional single-valued positional arguments pure nesting
571
+ - **Multiple optional single-valued positional arguments, pure nesting
581
572
  (second only meaningful with first)**: When the git SYNOPSIS shows nested
582
573
  optional brackets and the inner operand is only useful in the presence of the
583
574
  outer one, both arguments may be regular optional parameters in left-to-right
@@ -586,17 +577,43 @@ arguments, not as keyword arguments.
586
577
  - Callers can do `diff` (no args), `diff('HEAD~3')`, or `diff('HEAD~3', 'HEAD')`.
587
578
  There is no case where someone would pass `commit2` without `commit1`.
588
579
 
580
+ ##### Cross-argument constraints
581
+
582
+ Constraints that span arguments — mutually exclusive options, required groups,
583
+ forbidden value combinations — are not validated in Ruby. Command classes pass the
584
+ arguments through and leave the judgment to git. When git rejects a combination,
585
+ the rejection surfaces as a `Git::FailedError` carrying git's own message; a
586
+ combination git accepts, even one it silently ignores, raises nothing. The decision and its rationale are
587
+ recorded in
588
+ [ADR-0003](docs/adr/0003-validation-of-git-semantics-is-delegated-to-git.md).
589
+
590
+ The arguments DSL does provide constraint declarations (`conflicts`, `requires`,
591
+ `requires_one_of`, `requires_exactly_one_of`, `forbid_values`, `allowed_values`),
592
+ which raise `ArgumentError` at bind time. Declare one only when git cannot report
593
+ the error itself, under the two exception criteria defined in
594
+ [Validation Boundaries](.github/skills/project-context/SKILL.md#validation-boundaries):
595
+
596
+ - **The argv-invisible exception**: the argument never appears in git's argv
597
+ (`skip_cli: true` operands, `execution_option` entries), so git has no token to
598
+ object to. The two current uses are `Git::Commands::CatFile::Batch` (`conflicts`
599
+ and `requires_one_of` on its stdin-fed `:object` operand) and
600
+ `Git::Commands::Archive` (`conflicts :output, :out`, where `:out` is a Ruby IO
601
+ object).
602
+ - **The silent-wrong-result exception**: git accepts the combination but silently
603
+ discards data or produces a wrong answer. A declaration under this exception
604
+ needs a code comment explaining why, the git version(s) where the behavior was
605
+ verified, and a test.
606
+
589
607
  These conventions ensure the API is predictable and closely aligned with the git CLI.
590
608
  If a new option type is encountered, extend this section to document the mapping.
591
609
 
592
610
  #### Output processing
593
611
 
594
- The `git` gem translates the output of many Git commands into Ruby objects, making it
612
+ The `git` gem translates the output of many Git commands into Ruby objects that are
595
613
  easier to work with programmatically.
596
614
 
597
- These Ruby objects often include methods that allow for further Git operations where
598
- useful, providing additional functionality while staying true to the underlying Git
599
- behavior.
615
+ These Ruby objects often include methods for further Git operations where useful,
616
+ while staying close to the underlying Git behavior.
600
617
 
601
618
  When a single git command can produce distinctly different output types based on its
602
619
  options, implement separate methods for each output type. Follow the same naming
@@ -613,8 +630,8 @@ repo.diff_stats('HEAD~1', 'HEAD') # Statistical summary (git diff --numstat
613
630
  repo.diff_path_status('HEAD~1', 'HEAD') # File paths and status (git diff --name-status)
614
631
  ```
615
632
 
616
- This approach ensures each method has a clear, predictable return type and allows for
617
- targeted parsing logic appropriate to each output format.
633
+ This gives each method a clear, predictable return type and parsing logic targeted
634
+ to its output format.
618
635
 
619
636
  ### Implementation
620
637
 
@@ -635,7 +652,7 @@ layer responsibilities separate:
635
652
  structured Ruby objects.
636
653
 
637
654
  Steps 2 and 3 correspond to the Command and Facade layers, respectively. The
638
- Execution layer (`Git::ExecutionContext::*`) already exists a command class only
655
+ Execution layer (`Git::ExecutionContext::*`) already exists. A command class only
639
656
  consumes it via `@execution_context`; it is not authored per command.
640
657
 
641
658
  Example structure for `git add`:
@@ -693,7 +710,7 @@ end
693
710
  Here is the corresponding facade method that calls it:
694
711
 
695
712
  ```ruby
696
- # lib/git/repository/staging.rb (facade a topic module included into Git::Repository)
713
+ # lib/git/repository/staging.rb (facade, a topic module included into Git::Repository)
697
714
  module Git
698
715
  class Repository
699
716
  module Staging
@@ -717,20 +734,20 @@ status). Simple commands need only declare `arguments do … end` and inherit
717
734
  `Base#call(*, **)` unchanged. To attach command-specific YARD documentation to the
718
735
  inherited `call`, use either a `# @!method call(*, **)` directive (when there is no
719
736
  `def call` in the class) or place the YARD tags directly above an explicit
720
- `def call(*, **); super; end` both patterns produce identical runtime behavior.
737
+ `def call(*, **); super; end`. Both patterns produce identical runtime behavior.
721
738
  Only add real logic to `def call` when the command needs custom behavior beyond what
722
739
  `Base` provides.
723
740
 
724
741
  Override `call` explicitly in three situations:
725
742
 
726
- 1. **Input validation** guard `ArgumentError` for invalid option combinations that
743
+ 1. **Input validation**: guard `ArgumentError` for invalid option combinations that
727
744
  the DSL cannot express (e.g., empty operands without a compensating flag).
728
- 2. **Stdin via IO pipe** commands using the `--batch` / `--batch-check` protocol
745
+ 2. **Stdin via IO pipe**: commands using the `--batch` / `--batch-check` protocol
729
746
  must feed object names to the subprocess's stdin. Use the inherited
730
747
  `Base#with_stdin(content)`, which opens an `IO.pipe`, writes the string content,
731
- and yields the read end as `in:`. Do not open a pipe manually `StringIO` is
732
- not accepted by `Process.spawn` (it has no file descriptor).
733
- 3. **Non-trivial option routing** when multiple call shapes require different
748
+ and yields the read end as `in:`. Do not open a pipe manually. `StringIO` is
749
+ not accepted by `Process.spawn` because it has no file descriptor.
750
+ 3. **Non-trivial option routing**: when multiple call shapes require different
734
751
  argument sets built separately before dispatching.
735
752
 
736
753
  When overriding, work with `args_definition.bind(...)` directly and delegate
@@ -760,7 +777,7 @@ then the [`Arguments` DSL](lib/git/commands/arguments.rb) raises `ArgumentError`
760
777
  any keyword the command class does not recognize during argument binding. The facade
761
778
  also handles translation from single values or arrays to the splat format.
762
779
 
763
- > **YARD Documentation Note:** Because `call` uses anonymous argument forwarding
780
+ > **YARD documentation note:** Because `call` uses anonymous argument forwarding
764
781
  > (`*, **`), YARD cannot infer its signature. Document it with an `@overload` that names
765
782
  > the operands and an `**options` hash (e.g., `@overload call(*pathspec, **options)`),
766
783
  > add a `@param options [Hash]` tag, and document each supported keyword with its own
@@ -768,7 +785,7 @@ also handles translation from single values or arrays to the splat format.
768
785
  > `# @!method call(*, **)` directive (as shown above); when it defines an explicit
769
786
  > `def call` override, place them directly above that method instead.
770
787
  >
771
- > **Testing Requirement:** When defining arguments with the DSL, you must write RSpec
788
+ > **Testing requirement:** When defining arguments with the DSL, you must write RSpec
772
789
  > tests that verify each argument handles valid values correctly (booleans, strings,
773
790
  > arrays) and handles invalid values appropriately. Use a separate `context` block for
774
791
  > testing each option to ensure clarity and isolation. See
@@ -779,7 +796,7 @@ For factory methods and module-level commands, the pattern is the same but
779
796
  `@execution_context`:
780
797
 
781
798
  ```ruby
782
- # Factory method (Git.clone) creates a global context, runs the command, returns a repository
799
+ # Factory method (Git.clone): creates a global context, runs the command, returns a repository
783
800
  module Git
784
801
  def self.clone(repository_url, directory = nil, options = {})
785
802
  context = Git::ExecutionContext::Global.new
@@ -799,51 +816,42 @@ end
799
816
  The following command classes demonstrate implementation patterns.
800
817
  See `lib/git/commands/` and `spec/unit/git/commands/` for the full implementations:
801
818
 
802
- - **Simple command**: `Git::Commands::Add` straightforward argument building with
819
+ - **Simple command**: `Git::Commands::Add`, straightforward argument building with
803
820
  the [Arguments DSL](lib/git/commands/arguments.rb)
804
821
  - **Command with parser-backed facade result**: `Git::Commands::Fsck` with
805
- `Git::Parsers::Fsck` returns raw command output that the facade parses into
822
+ `Git::Parsers::Fsck`, which returns raw command output that the facade parses into
806
823
  structured Ruby objects
807
- - **Factory command**: `Git::Commands::Clone` used by `Git.clone`; returns a
824
+ - **Factory command**: `Git::Commands::Clone`, used by `Git.clone`; returns a
808
825
  `Git::CommandLine::Result` like all command classes (the factory method then builds
809
826
  and returns a `Git::Repository` from the cloned working tree)
810
- - **Multiple output modes**: `Git::Commands::Diff` declares output-mode options that
827
+ - **Multiple output modes**: `Git::Commands::Diff`, which declares output-mode options that
811
828
  facade methods choose from when building different Ruby-facing results
812
- - **Multi-context command family**: `Git::Commands::ConfigOptionSyntax::*` command
829
+ - **Multi-context command family**: `Git::Commands::ConfigOptionSyntax::*`, command
813
830
  classes shared by module-level and repository-scoped config methods
814
831
 
815
832
  ## Coding standards
816
833
 
817
- To ensure high-quality contributions, all pull requests must meet the following
818
- requirements:
834
+ All pull requests must meet the following requirements:
819
835
 
820
836
  ### Commit message guidelines
821
837
 
822
- To enhance our development workflow, enable automated changelog generation, and pave
823
- the way for Continuous Delivery, the `ruby-git` project has adopted the [Conventional
824
- Commits standard](https://www.conventionalcommits.org/en/v1.0.0/) for all commit
825
- messages.
826
-
827
- This structured approach to commit messages allows us to:
838
+ The `ruby-git` project has adopted the [Conventional Commits
839
+ standard](https://www.conventionalcommits.org/en/v1.0.0/) for all commit messages.
828
840
 
829
- - **Automate versioning and releases:** Tools can now automatically determine the
830
- semantic version bump (patch, minor, major) based on the types of commits merged.
831
- - **Generate accurate changelogs:** We can automatically create and update a
832
- `CHANGELOG.md` file, providing a clear history of changes for users and
833
- contributors.
834
- - **Improve commit history readability:** A standardized format makes it easier for
835
- everyone to understand the nature of changes at a glance.
841
+ Structured commit messages let tools determine the semantic version bump (patch,
842
+ minor, major) from the commits merged and generate an accurate `CHANGELOG.md`
843
+ automatically. A standardized format also makes the history easier to read at a
844
+ glance.
836
845
 
837
846
  #### What does this mean for contributors?
838
847
 
839
- Going forward, all commits to this repository **MUST** adhere to the [Conventional
840
- Commits standard](https://www.conventionalcommits.org/en/v1.0.0/). Commits not
841
- adhering to this standard will cause the CI build to fail. PRs will not be merged if
842
- they include non-conventional commits.
848
+ All commits to this repository must follow the [Conventional Commits
849
+ standard](https://www.conventionalcommits.org/en/v1.0.0/). Commits that do not
850
+ follow it will fail the CI build, and PRs that include them will not be merged.
843
851
 
844
852
  A git `commit-msg` hook (Husky + commitlint) that validates your Conventional
845
853
  Commit messages locally is installed automatically as part of the project
846
- bootstrap see [Local development setup](#local-development-setup). The hook
854
+ bootstrap. See [Local development setup](#local-development-setup). The hook
847
855
  depends on Node.js and npm; if those are not installed, `bin/setup` will warn
848
856
  and skip the hook, and commit-message validation will only run in CI.
849
857
 
@@ -869,12 +877,11 @@ colon:
869
877
 
870
878
  - `feat!: removed Git::Repository#commit_force`
871
879
 
872
- The commit messages will drive how the version is incremented for each release:
880
+ The commit messages drive how the version is incremented for each release:
873
881
 
874
- - a release containing a **breaking change** will do a **major** version increment
875
- - a release containing a **new feature** will do a **minor** increment
876
- - a release containing **neither a breaking change nor a new feature** will do a
877
- **patch** version increment
882
+ - a release containing a breaking change gets a major version increment
883
+ - a release containing a new feature gets a minor increment
884
+ - a release containing neither gets a patch increment
878
885
 
879
886
  The full conventional commit format is:
880
887
 
@@ -891,8 +898,8 @@ The full conventional commit format is:
891
898
  - `optional footers` only uses `BREAKING CHANGE: <description>` where description
892
899
  should describe the nature of the backward incompatibility.
893
900
 
894
- Use of the `BREAKING CHANGE:` footer flags a backward incompatible change even if it
895
- is not flagged with an exclamation mark after the `type`. Other footers are allowed
901
+ The `BREAKING CHANGE:` footer flags a backward incompatible change even if the
902
+ type is not marked with an exclamation mark. Other footers are allowed
896
903
  but not acted upon.
897
904
 
898
905
  See [the Conventional Commits
@@ -906,12 +913,12 @@ triggers a `footer-leading-blank` error.
906
913
 
907
914
  To avoid this:
908
915
 
909
- - **In the body**, omit the `#` when mentioning an issue or PR write `issue 1000`
916
+ - **In the body**, omit the `#` when mentioning an issue or PR: write `issue 1000`,
910
917
  not `issue #1000`.
911
918
  - **In the footer**, always include `#` for closing references:
912
919
  `Closes #1000`, `Fixes #1000`, or `Resolves #1000`.
913
920
  - If you only want to mention an issue for context (not close it), omit the `#` in
914
- the body no footer line is needed.
921
+ the body. No footer line is needed.
915
922
 
916
923
  To validate a commit message before committing:
917
924
 
@@ -946,7 +953,7 @@ process.stdin.on('end', () =>
946
953
  - `rake spec:integration` runs in parallel (via `parallel_tests`) on MRI. Set
947
954
  `PARALLEL_TESTS=false` (or `0`/`no`/`off`) to force serial execution, e.g.
948
955
  `PARALLEL_TESTS=false bundle exec rake spec:integration`. A run narrowed by `SPEC`
949
- to a single spec file always runs serially there is nothing to divide across
956
+ to a single spec file always runs serially. There is nothing to divide across
950
957
  workers, and serial execution gives per-example (documentation) output.
951
958
  - Set `SPEC=<glob>` to run specific files instead of a task's whole directory, e.g.
952
959
  `SPEC=spec/unit/git/version_spec.rb bundle exec rake spec:unit`.
@@ -963,14 +970,14 @@ process.stdin.on('end', () =>
963
970
  ```
964
971
 
965
972
  The glob is expanded by Rake, not the shell, so `**` works the same in any shell.
966
- A task whose directory contains none of the matches is skipped with a message
973
+ A task whose directory contains none of the matches is skipped with a message:
967
974
  `SPEC=spec/unit/...` on `rake spec` runs the unit specs and skips
968
975
  `spec:integration`. A glob matching nothing anywhere fails the task outright.
969
976
 
970
- This project uses **RSpec** (`spec/`) as its sole test framework. Structure,
977
+ This project uses RSpec (`spec/`) as its sole test framework. Structure,
971
978
  naming, setup, stubbing, and coverage rules for unit specs are defined in the
972
979
  [`rspec-unit-testing-standards`](.github/skills/rspec-unit-testing-standards/SKILL.md)
973
- skill follow it when writing or reviewing specs under `spec/unit/`.
980
+ skill. Follow it when writing or reviewing specs under `spec/unit/`.
974
981
 
975
982
  #### Test coverage policy
976
983
 
@@ -980,7 +987,7 @@ below either threshold.
980
987
 
981
988
  This is enforceable without being onerous because unit coverage in this project is
982
989
  deterministic: `lib/` has no Ruby-version, Ruby-engine, or platform conditionals, and
983
- the handful of unit specs that are conditionally skipped are redundant for coverage
990
+ the handful of unit specs that are conditionally skipped are redundant for coverage:
984
991
  every `lib/` line and branch they reach is also reached by a spec that always runs.
985
992
  Every supported MRI runtime therefore measures exactly the same lines and branches, so
986
993
  a coverage failure is always something the pull request introduced.
@@ -993,8 +1000,8 @@ failure, which is exactly what the policy exists to prevent.
993
1000
 
994
1001
  Write the guard the same way the rest of the suite does: a reusable predicate in
995
1002
  `spec/spec_helper.rb` (`unless_git`, `unless_command`, `unless_pcre`,
996
- `unless_ci_build`) used as `skip:` metadata, or for a one-off capability that the
997
- `before` block is already exercising a `rescue` in that block that calls `skip`.
1003
+ `unless_ci_build`) used as `skip:` metadata, or, for a one-off capability that the
1004
+ `before` block is already exercising, a `rescue` in that block that calls `skip`.
998
1005
 
999
1006
  What the policy does and does not cover:
1000
1007
 
@@ -1007,7 +1014,7 @@ What the policy does and does not cover:
1007
1014
  Set `FAIL_ON_LOW_COVERAGE=true` to force enforcement on for a focused run.
1008
1015
  - **A focused run lists gaps only in the code it is about.** The reported percentage is
1009
1016
  always for the whole of `lib/`, but the list of uncovered lines and branches is scoped
1010
- to the files the run tests the classes it describes, plus the `lib/` file each spec
1017
+ to the files the run tests: the classes it describes, plus the `lib/` file each spec
1011
1018
  file mirrors. So a focused run answers "is what I just changed fully covered?" without
1012
1019
  waiting for CI:
1013
1020
 
@@ -1042,7 +1049,7 @@ When a branch is hard to cover, apply these in order:
1042
1049
  state why the code is unreachable, and expect a reviewer to question it. `lib/`
1043
1050
  currently contains no coverage directives.
1044
1051
 
1045
- Use the inline form wherever the exclusion is a single line it applies only to the
1052
+ Use the inline form wherever the exclusion is a single line. It applies only to the
1046
1053
  line it sits on and needs no matching `enable`, which makes it impossible to leave a
1047
1054
  region accidentally open:
1048
1055
 
@@ -1059,8 +1066,8 @@ When a branch is hard to cover, apply these in order:
1059
1066
  # simplecov:enable branch
1060
1067
  ```
1061
1068
 
1062
- Always name the narrowest criterion that solves the problem `line`, `branch`,
1063
- `method`, or a comma-separated combination — and spell it exactly. A word SimpleCov
1069
+ Name the narrowest criterion that solves the problem and spell it exactly:
1070
+ `line`, `branch`, `method`, or a comma-separated combination. A word SimpleCov
1064
1071
  does not recognize is treated as free-form reason text, which silently widens the
1065
1072
  directive to all three criteria instead of failing. Write the reason after the
1066
1073
  criteria, so the required justification lives in the directive itself.
@@ -1084,22 +1091,22 @@ $ open coverage/index.html
1084
1091
  This policy applies to `main` only. The `4.x` maintenance branch predates it and is
1085
1092
  not held to these thresholds.
1086
1093
 
1087
- #### Unit tests vs Integration tests
1094
+ #### Unit tests vs integration tests
1088
1095
 
1089
1096
  This project uses two types of RSpec tests, organized by directory:
1090
1097
 
1091
- - **Unit tests** (`spec/unit/`) - Test individual classes and methods with mocked
1098
+ - **Unit tests** (`spec/unit/`): test individual classes and methods with mocked
1092
1099
  execution context. These verify that the gem builds correct git command arguments
1093
1100
  and properly handles git output. Unit tests should mock `@execution_context` to
1094
1101
  avoid calling real git commands.
1095
1102
 
1096
- - **Integration tests** (`spec/integration/`) - Test the gem's behavior against real
1103
+ - **Integration tests** (`spec/integration/`): test the gem's behavior against real
1097
1104
  git repositories. These verify that mocked assumptions in unit tests match actual
1098
1105
  git behavior. Integration tests create temporary repositories using `Dir.mktmpdir`
1099
1106
  and run real git commands through the gem's public API.
1100
1107
 
1101
- **Purpose of integration tests**: Integration tests validate that the gem correctly
1102
- interacts with git, not that git itself works correctly. They should verify:
1108
+ Integration tests validate that the gem correctly interacts with git, not that git
1109
+ itself works correctly. They should verify:
1103
1110
 
1104
1111
  - That the gem's mocked command expectations match real git output format
1105
1112
  - That the gem correctly handles real git behavior (e.g., unicode in branch names)
@@ -1108,9 +1115,9 @@ interacts with git, not that git itself works correctly. They should verify:
1108
1115
 
1109
1116
  **Integration test guidelines**:
1110
1117
 
1111
- - Keep tests **minimal and purposeful** - only create what's needed for the test
1112
- - Focus on **key behaviors** that unit tests can't verify
1113
- - Don't test git's functionality - test the gem's interaction with git
1118
+ - Keep tests minimal and purposeful; only create what's needed for the test
1119
+ - Focus on key behaviors that unit tests can't verify
1120
+ - Don't test git's functionality; test the gem's interaction with git
1114
1121
  - Use the shared context `'in an empty repository'` for temporary repo setup
1115
1122
  - Use `Git::IntegrationTestHelpers` methods for file operations
1116
1123
  - Each test should verify one specific git interaction pattern
@@ -1137,18 +1144,44 @@ $ bundle exec rspec spec/unit/git/commands/add_spec.rb
1137
1144
  $ GIT_PATH=/Users/james/Downloads/git-2.30.2/bin-wrappers bundle exec rake spec
1138
1145
  ```
1139
1146
 
1147
+ ### What ships in the gem
1148
+
1149
+ `spec.files` in [`git.gemspec`](git.gemspec) is an **allowlist**: the released gem
1150
+ contains `lib/`, the documents [`.yardopts`](.yardopts) names as extra files, plus
1151
+ `UPGRADING.md` and the gemspec itself. Nothing else in the repository is published.
1152
+
1153
+ It used to be a denylist, which meant every new path was published by default. That
1154
+ shipped `.github/`, `tasks/`, the Husky hooks, and the `.claude/skills` symlink to
1155
+ users. The symlink is what forced the change: extracting a symlink requires a
1156
+ privilege Windows grants only under Developer Mode or an elevated shell, so
1157
+ `gem install git` either failed there or, on RubyGems new enough to fall back to a
1158
+ copy, quietly duplicated the whole skills tree into the installed gem.
1159
+
1160
+ What this means when you add a file:
1161
+
1162
+ - **Under `lib/`**: nothing to do; it ships automatically.
1163
+ - **A new top-level document**: add it to `doc_files` in the gemspec if users should
1164
+ get it, and to `.yardopts` if rubydoc.info should render it. The two lists are
1165
+ checked against each other, so a file in `.yardopts` but not the gem fails the
1166
+ suite rather than becoming a broken documentation link.
1167
+ - **Anything else**: it stays out of the gem, which is almost always what you want.
1168
+
1169
+ [`spec/unit/gemspec_spec.rb`](spec/unit/gemspec_spec.rb) enforces all of this: every
1170
+ tracked file under `lib/` is present, no symlink is, and nothing outside `lib/` and
1171
+ the project root is.
1172
+
1140
1173
  ## Building a specific version of the Git command-line
1141
1174
 
1142
1175
  To test with a specific version of the Git command-line, you may need to build that
1143
- version from source code. The following instructions are adapted from Atlassians
1176
+ version from source code. The following instructions are adapted from Atlassian's
1144
1177
  [How to install Git](https://www.atlassian.com/git/tutorials/install-git) page for
1145
1178
  building Git on macOS.
1146
1179
 
1147
- ### Install pre-requisites
1180
+ ### Install prerequisites
1148
1181
 
1149
- Prerequisites only need to be installed if they are not already present.
1182
+ Install prerequisites only if they are not already present.
1150
1183
 
1151
- From your terminal, install Xcodes Command Line Tools:
1184
+ From your terminal, install Xcode's Command Line Tools:
1152
1185
 
1153
1186
  ```shell
1154
1187
  xcode-select --install
@@ -1171,7 +1204,7 @@ code mirror](https://mirrors.edge.kernel.org/pub/software/scm/git/).
1171
1204
  ### Build git
1172
1205
 
1173
1206
  From your terminal, change to the root directory of the extracted source code and run
1174
- the build with following command:
1207
+ the build with the following command:
1175
1208
 
1176
1209
  ```shell
1177
1210
  NO_GETTEXT=1 make CFLAGS="-I/usr/local/opt/openssl/include" LDFLAGS="-L/usr/local/opt/openssl/lib"
@@ -1182,7 +1215,7 @@ directory (e.g., `bin-wrappers/git`).
1182
1215
 
1183
1216
  ### Use the new Git version
1184
1217
 
1185
- To configure programs that use the Git gem to utilize the newly built version, do the
1218
+ To configure programs that use the Git gem to run the newly built version, do the
1186
1219
  following:
1187
1220
 
1188
1221
  ```ruby