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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +78 -0
- data/CONTRIBUTING.md +202 -169
- data/LICENSE +1 -1
- data/README.md +192 -151
- data/UPGRADING.md +287 -1
- data/git.gemspec +35 -3
- data/lib/git/author.rb +11 -0
- data/lib/git/author_info.rb +66 -0
- data/lib/git/branch.rb +210 -15
- data/lib/git/branch_info.rb +1 -1
- data/lib/git/branches.rb +35 -7
- data/lib/git/command_line/base.rb +1 -2
- data/lib/git/commands/base.rb +1 -1
- data/lib/git/commands/cat_file/raw.rb +62 -7
- data/lib/git/object.rb +13 -7
- data/lib/git/parsers/stash.rb +50 -17
- data/lib/git/parsers/tag.rb +54 -8
- data/lib/git/remote.rb +37 -7
- data/lib/git/remote_info.rb +67 -10
- data/lib/git/repository/branching.rb +190 -6
- data/lib/git/repository/merging.rb +96 -2
- data/lib/git/repository/remote_operations.rb +57 -0
- data/lib/git/repository/shared_private.rb +67 -0
- data/lib/git/stash_info.rb +32 -34
- data/lib/git/tag_info.rb +21 -29
- data/lib/git/version.rb +1 -1
- data/lib/git.rb +1 -0
- metadata +6 -94
- data/.claude/commands/address-copilot-reviews.md +0 -14
- data/.claude/settings.json +0 -16
- data/.claude/skills +0 -1
- data/.commitlintrc.yml +0 -38
- data/.dockerignore +0 -27
- data/.github/copilot-instructions.md +0 -30
- data/.github/hooks/bin-setup-on-worktree.json +0 -11
- data/.github/hooks/run-bin-setup-once.sh +0 -20
- data/.github/issue_template.md +0 -15
- data/.github/prompts/iteratively-address-copilot-reviews.prompt.md +0 -188
- data/.github/pull_request_template.md +0 -21
- data/.github/skills/breaking-change-analysis/SKILL.md +0 -99
- data/.github/skills/ci-cd-troubleshooting/SKILL.md +0 -264
- data/.github/skills/command-implementation/REFERENCE.md +0 -994
- data/.github/skills/command-implementation/SKILL.md +0 -230
- data/.github/skills/command-test-conventions/SKILL.md +0 -664
- data/.github/skills/command-yard-documentation/SKILL.md +0 -434
- data/.github/skills/dependency-management/SKILL.md +0 -72
- data/.github/skills/development-workflow/SKILL.md +0 -512
- data/.github/skills/facade-implementation/REFERENCE.md +0 -837
- data/.github/skills/facade-implementation/SKILL.md +0 -269
- data/.github/skills/facade-test-conventions/SKILL.md +0 -391
- data/.github/skills/facade-yard-documentation/SKILL.md +0 -435
- data/.github/skills/make-skill-template/SKILL.md +0 -226
- data/.github/skills/pr-readiness-review/SKILL.md +0 -205
- data/.github/skills/project-context/SKILL.md +0 -306
- data/.github/skills/pull-request-review/SKILL.md +0 -168
- data/.github/skills/rebase/SKILL.md +0 -148
- data/.github/skills/refactor-command-to-commandlineresult/SKILL.md +0 -131
- data/.github/skills/release-management/SKILL.md +0 -125
- data/.github/skills/resolve-feedback/SKILL.md +0 -288
- data/.github/skills/review-arguments-dsl/CHECKLIST.md +0 -788
- data/.github/skills/review-arguments-dsl/SKILL.md +0 -214
- data/.github/skills/review-cross-command-consistency/SKILL.md +0 -139
- data/.github/skills/reviewing-skills/SKILL.md +0 -214
- data/.github/skills/rspec-unit-testing-standards/SKILL.md +0 -685
- data/.github/skills/tdd-refactor-step/SKILL.md +0 -236
- data/.github/skills/test-debugging/SKILL.md +0 -161
- data/.github/skills/yard-documentation/SKILL.md +0 -981
- data/.github/skills/yard-documentation/element-rules.md +0 -162
- data/.github/skills-deprecated/README.md +0 -21
- data/.github/skills-deprecated/extract-command-from-lib/SKILL.md +0 -487
- data/.github/skills-deprecated/extract-facade-from-base-lib/KEYWORD_ARG_REMEDIATION.md +0 -22
- data/.github/skills-deprecated/extract-facade-from-base-lib/SKILL.md +0 -600
- data/.github/skills-deprecated/review-backward-compatibility/SKILL.md +0 -275
- data/.github/workflows/continuous_integration.yml +0 -358
- data/.github/workflows/enforce_conventional_commits.yml +0 -35
- data/.github/workflows/experimental_continuous_integration.yml +0 -59
- data/.github/workflows/release.yml +0 -52
- data/.github/workflows/warm_bundler_caches.yml +0 -82
- data/.gitignore +0 -30
- data/.husky/commit-msg +0 -1
- data/.husky/pre-commit +0 -13
- data/.release-please-config.json +0 -36
- data/.release-please-manifest.json +0 -3
- data/.rspec +0 -2
- data/.rubocop.yml +0 -44
- data/.rubocop_todo.yml +0 -30
- data/.yard-lint.yml +0 -75
- data/CLAUDE.md +0 -11
- data/Gemfile +0 -22
- data/Rakefile +0 -41
- data/docker/test/Dockerfile +0 -32
- data/docker/test/docker-compose.yml +0 -0
- data/package.json +0 -10
- data/redesign/1_architecture_existing.md +0 -102
- data/redesign/2_architecture_redesign.md +0 -449
- data/redesign/3_architecture_implementation.md +0 -1623
- data/redesign/Phase 4 - Step A.md +0 -366
- data/redesign/Phase 4 - Step B.md +0 -921
- data/redesign/Phase 4 - Step C.md +0 -833
- data/redesign/beta_release.md +0 -107
- data/redesign/branch_parse_refactor_plan.md +0 -163
- data/redesign/c1a-public-api-scope.tsv +0 -256
- data/redesign/c1c2_audit.md +0 -566
- data/redesign/c1c2_bucket6_lib_orphans.md +0 -626
- data/redesign/config_design.rb +0 -501
- data/redesign/index.md +0 -34
- data/redesign/info_object_migration_plan.md +0 -126
- data/redesign/integration_test_analysis.md +0 -521
- data/redesign/phase-4-step-b-test-audit.tsv +0 -485
- data/redesign/remote_refactor_plan.md +0 -164
- data/redesign/reverse_dependencies.sql +0 -44
- data/tasks/gem_tasks.rake +0 -14
- data/tasks/npm_tasks.rake +0 -7
- data/tasks/rspec.rake +0 -111
- data/tasks/rubocop.rake +0 -5
- data/tasks/test_gem.rake +0 -12
- 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
|
|
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
|
|
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.
|
|
57
|
-
|
|
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`
|
|
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
|
|
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
|
|
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
|
|
141
|
-
validate a change before
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
|
183
|
-
|
|
184
|
-
|
|
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
|
|
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`**:
|
|
209
|
-
|
|
210
|
-
- **`4.x`**:
|
|
211
|
-
fixes and backward-compatible
|
|
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
|
|
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)
|
|
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)
|
|
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)
|
|
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)
|
|
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)
|
|
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)
|
|
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)
|
|
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)
|
|
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
|
|
288
|
-
|
|
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
|
|
292
|
-
|
|
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
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
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
|
|
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
|
|
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-
|
|
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
|
-
|
|
366
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
478
|
+
- **`*Result`**: the outcome of a mutating or destructive operation (e.g.,
|
|
451
479
|
`BranchDeleteResult`, `TagDeleteResult`). Also lives in `Git::`.
|
|
452
480
|
|
|
453
|
-
Do
|
|
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,
|
|
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
|
|
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
|
|
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
|
|
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
|
|
598
|
-
|
|
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
|
|
617
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
732
|
-
not accepted by `Process.spawn`
|
|
733
|
-
3. **Non-trivial option routing
|
|
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
|
|
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
|
|
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)
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
818
|
-
requirements:
|
|
834
|
+
All pull requests must meet the following requirements:
|
|
819
835
|
|
|
820
836
|
### Commit message guidelines
|
|
821
837
|
|
|
822
|
-
|
|
823
|
-
|
|
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
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
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
|
-
|
|
840
|
-
|
|
841
|
-
|
|
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
|
|
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
|
|
880
|
+
The commit messages drive how the version is incremented for each release:
|
|
873
881
|
|
|
874
|
-
- a release containing a
|
|
875
|
-
- a release containing a
|
|
876
|
-
- a release containing
|
|
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
|
-
|
|
895
|
-
is not
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
997
|
-
`before` block is already exercising
|
|
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
|
|
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
|
|
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
|
-
|
|
1063
|
-
`method`, or a comma-separated combination
|
|
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
|
|
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/`)
|
|
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/`)
|
|
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
|
-
|
|
1102
|
-
|
|
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
|
|
1112
|
-
- Focus on
|
|
1113
|
-
- Don't test git's functionality
|
|
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 Atlassian
|
|
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
|
|
1180
|
+
### Install prerequisites
|
|
1148
1181
|
|
|
1149
|
-
|
|
1182
|
+
Install prerequisites only if they are not already present.
|
|
1150
1183
|
|
|
1151
|
-
From your terminal, install Xcode
|
|
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
|
|
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
|