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