git 5.0.1 → 5.0.3
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/.claude/commands/address-copilot-reviews.md +14 -0
- data/.claude/settings.json +16 -0
- data/.claude/skills +1 -0
- data/.github/copilot-instructions.md +0 -12
- data/.github/hooks/run-bin-setup-once.sh +9 -3
- data/.github/pull_request_template.md +2 -0
- data/.github/skills/command-implementation/REFERENCE.md +1 -1
- data/.github/skills/command-implementation/SKILL.md +2 -2
- data/.github/skills/facade-implementation/SKILL.md +2 -2
- data/.github/skills/rspec-unit-testing-standards/SKILL.md +52 -6
- data/.github/workflows/continuous_integration.yml +12 -0
- data/.gitignore +6 -0
- data/.release-please-manifest.json +1 -1
- data/.rspec +0 -1
- data/CHANGELOG.md +59 -0
- data/CLAUDE.md +11 -0
- data/CONTRIBUTING.md +295 -153
- data/README.md +8 -3
- data/Rakefile +2 -13
- data/git.gemspec +17 -5
- data/lib/git/commands/arguments.rb +2 -2
- data/lib/git/commands/remote/remove.rb +3 -5
- data/lib/git/commands/remote/rename.rb +7 -2
- data/lib/git/factories.rb +813 -0
- data/lib/git/parsers/diff.rb +2 -3
- data/lib/git/parsers/grep.rb +1 -1
- data/lib/git/path_resolver.rb +206 -0
- data/lib/git/repository.rb +0 -1
- data/lib/git/version.rb +1 -1
- data/lib/git.rb +2 -1
- data/tasks/rspec.rake +93 -30
- metadata +13 -9
- data/lib/git/repository/factories.rb +0 -814
- data/lib/git/repository/path_resolver.rb +0 -207
data/CONTRIBUTING.md
CHANGED
|
@@ -20,35 +20,33 @@
|
|
|
20
20
|
- [Before requesting review](#before-requesting-review)
|
|
21
21
|
- [Branch strategy](#branch-strategy)
|
|
22
22
|
- [AI-assisted contributions](#ai-assisted-contributions)
|
|
23
|
+
- [Agent configuration](#agent-configuration)
|
|
23
24
|
- [Agent skills](#agent-skills)
|
|
24
25
|
- [Design philosophy](#design-philosophy)
|
|
25
26
|
- [Layered architecture](#layered-architecture)
|
|
26
|
-
|
|
27
|
-
- [
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
- [
|
|
34
|
-
|
|
35
|
-
- [Output processing](#output-processing)
|
|
36
|
-
- [From design to implementation](#from-design-to-implementation)
|
|
27
|
+
- [Implementing a git command](#implementing-a-git-command)
|
|
28
|
+
- [API design](#api-design)
|
|
29
|
+
- [Method placement](#method-placement)
|
|
30
|
+
- [Method naming](#method-naming)
|
|
31
|
+
- [Result class naming](#result-class-naming)
|
|
32
|
+
- [Parameter naming](#parameter-naming)
|
|
33
|
+
- [Parameter values](#parameter-values)
|
|
34
|
+
- [Output processing](#output-processing)
|
|
35
|
+
- [Implementation](#implementation)
|
|
37
36
|
- [Example implementations](#example-implementations)
|
|
38
37
|
- [Coding standards](#coding-standards)
|
|
39
38
|
- [Commit message guidelines](#commit-message-guidelines)
|
|
40
39
|
- [What does this mean for contributors?](#what-does-this-mean-for-contributors)
|
|
41
40
|
- [What to know about Conventional Commits](#what-to-know-about-conventional-commits)
|
|
42
41
|
- [Issue and PR references](#issue-and-pr-references)
|
|
43
|
-
- [
|
|
44
|
-
- [
|
|
42
|
+
- [Testing guidelines](#testing-guidelines)
|
|
43
|
+
- [Test coverage policy](#test-coverage-policy)
|
|
45
44
|
- [Unit tests vs Integration tests](#unit-tests-vs-integration-tests)
|
|
46
45
|
- [Building a specific version of the Git command-line](#building-a-specific-version-of-the-git-command-line)
|
|
47
46
|
- [Install pre-requisites](#install-pre-requisites)
|
|
48
47
|
- [Obtain Git source code](#obtain-git-source-code)
|
|
49
48
|
- [Build git](#build-git)
|
|
50
49
|
- [Use the new Git version](#use-the-new-git-version)
|
|
51
|
-
- [Licensing](#licensing)
|
|
52
50
|
|
|
53
51
|
## Summary
|
|
54
52
|
|
|
@@ -199,10 +197,6 @@ This project maintains two active branches:
|
|
|
199
197
|
- **`4.x`**: Maintenance branch for the v4.x release series. This branch receives bug
|
|
200
198
|
fixes and backward-compatible improvements only.
|
|
201
199
|
|
|
202
|
-
**Important:** Never commit directly to `main` or `4.x`. All changes must be
|
|
203
|
-
submitted via pull requests from feature branches. This ensures proper code review,
|
|
204
|
-
CI validation, and maintains a clean commit history.
|
|
205
|
-
|
|
206
200
|
When submitting a pull request:
|
|
207
201
|
|
|
208
202
|
- **New features and breaking changes**: Target the `main` branch
|
|
@@ -223,6 +217,27 @@ and "CI is green" are not substitutes for the submitter running
|
|
|
223
217
|
[the local validation step](#contributor-validation-policy) themselves; CI is a
|
|
224
218
|
backstop, not a primary validation surface.
|
|
225
219
|
|
|
220
|
+
### Agent configuration
|
|
221
|
+
|
|
222
|
+
Agent configuration is shared: each piece of guidance is stored once and surfaced to
|
|
223
|
+
every supported agent.
|
|
224
|
+
|
|
225
|
+
| Content | Canonical location | Also read by |
|
|
226
|
+
| --- | --- | --- |
|
|
227
|
+
| Project instructions | [`.github/copilot-instructions.md`](.github/copilot-instructions.md) | Claude Code, via an import in [`CLAUDE.md`](CLAUDE.md) |
|
|
228
|
+
| 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
|
+
| Setup hook | [`.github/hooks/run-bin-setup-once.sh`](.github/hooks/run-bin-setup-once.sh) | Claude Code, via `.claude/settings.json` |
|
|
231
|
+
|
|
232
|
+
Always edit the canonical file. The Claude Code side is a pointer in every case, so
|
|
233
|
+
changes reach both agents without a sync step.
|
|
234
|
+
|
|
235
|
+
One caveat: `.claude/skills` is a committed symlink. Git for Windows only
|
|
236
|
+
materializes symlinks when `core.symlinks` is enabled (which requires Developer Mode
|
|
237
|
+
or an elevated shell). Without it, Windows contributors get a plain text file there
|
|
238
|
+
and Claude Code silently loads no skills; either enable symlinks or point your agent
|
|
239
|
+
at [`.github/skills/`](.github/skills/) directly. Copilot is unaffected.
|
|
240
|
+
|
|
226
241
|
### Agent skills
|
|
227
242
|
|
|
228
243
|
If you use an AI coding agent that understands repository skills, the
|
|
@@ -256,7 +271,11 @@ guidance that mirrors maintainer expectations:
|
|
|
256
271
|
|
|
257
272
|
## Design philosophy
|
|
258
273
|
|
|
259
|
-
The `git` gem
|
|
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.
|
|
277
|
+
|
|
278
|
+
Its public API is designed as a lightweight wrapper around the `git` command-line
|
|
260
279
|
tool, providing Ruby developers with a simple and intuitive interface for
|
|
261
280
|
programmatically interacting with Git.
|
|
262
281
|
|
|
@@ -265,44 +284,51 @@ introduce unnecessary abstraction layers or modify Git's core functionality. Ins
|
|
|
265
284
|
the gem maintains a close alignment with the existing `git` command-line interface,
|
|
266
285
|
avoiding extensions or alterations that could lead to unexpected behaviors.
|
|
267
286
|
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
287
|
+
`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
|
|
289
|
+
same order. Keyword arguments map to `git` CLI options by long OR short name.
|
|
290
|
+
|
|
291
|
+
Some examples:
|
|
292
|
+
|
|
293
|
+
- To execute `git clone <url> --depth=1`, call `Git.clone(url, depth: 1)`
|
|
294
|
+
- To execute `git add <path> --force`, call `Git::Repository#add(path, force: true)`
|
|
271
295
|
|
|
272
296
|
## Layered architecture
|
|
273
297
|
|
|
274
|
-
The
|
|
298
|
+
The `git` gem is organized into three architectural layers:
|
|
275
299
|
|
|
276
300
|
| Layer | Responsibility | Mechanism |
|
|
277
301
|
| --- | --- | --- |
|
|
278
|
-
| **Facade** (`Git::Repository
|
|
279
|
-
| **Command** (`Git::Commands::*`) | Neutral git CLI interface | Declares CLI arguments via the [Arguments DSL](lib/git/commands/arguments.rb), executes git
|
|
280
|
-
| **Execution** (`Git::ExecutionContext
|
|
281
|
-
|
|
282
|
-
### Command layer responsibilities
|
|
302
|
+
| **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
|
+
| **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 `LC_ALL=en_US.UTF-8`) |
|
|
283
305
|
|
|
284
306
|
Command classes (`Git::Commands::*`) are **faithful, neutral representations of the
|
|
285
307
|
git CLI**. Each command class does the following:
|
|
286
308
|
|
|
287
|
-
- Declares
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
-
|
|
291
|
-
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
309
|
+
- Declares acceptable CLI arguments and options via the
|
|
310
|
+
[Arguments DSL](lib/git/commands/arguments.rb)
|
|
311
|
+
- Defines a `#call` method which:
|
|
312
|
+
- Maps its parameters to the git argv using the declared arguments
|
|
313
|
+
- Executes a git CLI command via `Git::ExecutionContext`
|
|
314
|
+
- Returns the unprocessed git CLI result as a `Git::CommandLine::Result` object
|
|
315
|
+
|
|
316
|
+
Command classes should not embed choices such as output format flags, editor
|
|
317
|
+
suppression, progress output, or verbose mode. These decisions belong to the facade
|
|
318
|
+
layer which sets them as needed. The facade layer may give callers the choice to
|
|
319
|
+
override those decisions when appropriate (e.g., running in a TTY-attached
|
|
298
320
|
environment where an editor is desired).
|
|
299
321
|
|
|
300
322
|
For example:
|
|
301
323
|
|
|
302
|
-
- **Anti-pattern:**
|
|
303
|
-
`literal '--no-
|
|
304
|
-
-
|
|
305
|
-
|
|
324
|
+
- **Anti-pattern:** declaring non-overidable and non-default options in the Arguments
|
|
325
|
+
DSL to control output such as `literal '--no-edit'`, `literal '--verbose'`, or
|
|
326
|
+
`literal '--no-progress'` inside a command class. This embeds policy in the wrong
|
|
327
|
+
layer.
|
|
328
|
+
- **Correct pattern:** declaring options which allow the user of the command (often a
|
|
329
|
+
facade method) to set desired values such as: `flag_option :edit, negatable: true`.
|
|
330
|
+
This allows the facade to either accept the default or to hard code `edit: false`
|
|
331
|
+
if it is needed.
|
|
306
332
|
|
|
307
333
|
This separation keeps command classes reusable across facade methods with different
|
|
308
334
|
policy needs. For example, a facade method that parses command output may pass
|
|
@@ -311,41 +337,41 @@ a stable, parseable output shape. Those parser-contract options belong at the fa
|
|
|
311
337
|
call site, not as hard-coded literals in the command class. Other facade methods can
|
|
312
338
|
reuse the same command class with different options.
|
|
313
339
|
|
|
314
|
-
##
|
|
340
|
+
## Implementing a git command
|
|
315
341
|
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
342
|
+
Start with the official git documentation page for the command (e.g., `man git-add`
|
|
343
|
+
or the [git-scm.com](https://git-scm.com/docs) reference page). Its SYNOPSIS line
|
|
344
|
+
identifies the positional operands, and its OPTIONS section identifies the flags and
|
|
345
|
+
value options the Ruby method must expose.
|
|
319
346
|
|
|
320
|
-
[
|
|
321
|
-
|
|
322
|
-
`Git::Repository` (and the `Git` module), whose facade methods delegate directly to
|
|
323
|
-
internal `Git::Commands::*` classes.
|
|
347
|
+
Implementing the command has two major tasks: [API design](#api-design) and
|
|
348
|
+
[Implementation](#implementation).
|
|
324
349
|
|
|
325
|
-
|
|
326
|
-
> described in "From design to implementation" with `Git::Commands::*` classes and
|
|
327
|
-
> the [Arguments DSL](lib/git/commands/arguments.rb).
|
|
350
|
+
### API design
|
|
328
351
|
|
|
329
|
-
|
|
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
|
|
354
|
+
users will see.
|
|
330
355
|
|
|
331
|
-
|
|
332
|
-
determines where to implement it in the Ruby API:
|
|
356
|
+
#### Method placement
|
|
333
357
|
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
358
|
+
The public API is `Git::Repository` (and the `Git` module). These facade methods must
|
|
359
|
+
be exposed there, even when their implementation lives in private mixin modules or
|
|
360
|
+
`Git::Commands::*` classes.
|
|
337
361
|
|
|
338
|
-
**Repository factory
|
|
339
|
-
|
|
362
|
+
**Repository factory commands** are exposed via `Git` as module methods and
|
|
363
|
+
are usually implemented in the `Git::Factories` mixin. These methods return a
|
|
364
|
+
`Git::Repository` object for subsequent operations:
|
|
340
365
|
|
|
341
366
|
```ruby
|
|
342
367
|
repo = Git.clone('https://github.com/user/repo.git', 'local_path')
|
|
343
|
-
repo = Git.init('new_repo')
|
|
368
|
+
repo = Git.init('new_repo', initial_branch: 'main')
|
|
344
369
|
repo = Git.open('.')
|
|
345
370
|
```
|
|
346
371
|
|
|
347
|
-
**Repository-scoped commands**
|
|
348
|
-
`Git::Repository` instance methods
|
|
372
|
+
**Repository-scoped commands** require a repository context. These methods are
|
|
373
|
+
exposed via `Git::Repository` instance methods and are usually implemented in a
|
|
374
|
+
`Git::Repository::*` mixin.
|
|
349
375
|
|
|
350
376
|
```ruby
|
|
351
377
|
repo.add('file.txt')
|
|
@@ -353,23 +379,26 @@ repo.commit('Add file')
|
|
|
353
379
|
repo.log
|
|
354
380
|
```
|
|
355
381
|
|
|
356
|
-
**
|
|
357
|
-
methods on the `Git` module:
|
|
382
|
+
**Global commands** do not require a repository context. Expose these
|
|
383
|
+
as methods on the `Git` module:
|
|
358
384
|
|
|
359
385
|
```ruby
|
|
360
386
|
Git.config_get('user.name', global: true)
|
|
361
387
|
Git.config_set('user.email', 'user@example.com', global: true)
|
|
362
388
|
```
|
|
363
389
|
|
|
364
|
-
Some commands, like `git config
|
|
390
|
+
Some commands, like `git config` commands, can be called either in a global or
|
|
391
|
+
repository scope. Here is how that was solved for the config commands:
|
|
392
|
+
|
|
393
|
+
- When called via the `Git` module, a scope parameter such as `global: true`,
|
|
394
|
+
`system: true`, or `file: <filename>` MUST be given. `local` and `worktree`
|
|
395
|
+
scopes are not allowed.
|
|
365
396
|
|
|
366
|
-
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
- **On a `Git::Repository` instance**: The command defaults to the repository's local
|
|
370
|
-
scope. The `worktree: true` option is also available.
|
|
397
|
+
- When called via a `Git::Repository` instance, `local: true` and `worktree: true`
|
|
398
|
+
scope parameters may be given, with `local` being the default if no scope is given.
|
|
399
|
+
`global`, `system`, and `file` scopes are also allowed.
|
|
371
400
|
|
|
372
|
-
|
|
401
|
+
#### Method naming
|
|
373
402
|
|
|
374
403
|
Each method corresponds directly to a `git` command. For example, the `git add`
|
|
375
404
|
command is implemented as `Git::Repository#add`, and the `git ls-files` command is
|
|
@@ -397,12 +426,14 @@ names where appropriate.
|
|
|
397
426
|
See also [Output processing](#output-processing) for when different output formats
|
|
398
427
|
require separate methods.
|
|
399
428
|
|
|
400
|
-
|
|
429
|
+
#### Result class naming
|
|
401
430
|
|
|
402
|
-
Parsed result objects returned from facade methods follow a reserved suffix
|
|
431
|
+
Parsed result objects returned from facade methods follow a reserved suffix
|
|
432
|
+
convention:
|
|
403
433
|
|
|
404
434
|
- **`*Info`** — a parsed metadata struct returned from a query (e.g., `BranchInfo`,
|
|
405
|
-
`TagInfo`, `StashInfo`, `DiffInfo`). Always lives in the top-level `Git::`
|
|
435
|
+
`TagInfo`, `StashInfo`, `DiffInfo`). Always lives in the top-level `Git::`
|
|
436
|
+
namespace.
|
|
406
437
|
- **`*Result`** — the outcome of a mutating or destructive operation (e.g.,
|
|
407
438
|
`BranchDeleteResult`, `TagDeleteResult`). Also lives in `Git::`.
|
|
408
439
|
|
|
@@ -410,7 +441,7 @@ Do **not** use these suffixes on `Git::Commands::*` command classes — those ar
|
|
|
410
441
|
subprocess runners, not data objects. A reader seeing `Commands::Foo::BarInfo`
|
|
411
442
|
expects a parsed struct, not a class that shells out to git.
|
|
412
443
|
|
|
413
|
-
|
|
444
|
+
#### Parameter naming
|
|
414
445
|
|
|
415
446
|
Parameters within the `git` gem methods are named after their corresponding long
|
|
416
447
|
command-line options, ensuring familiarity and ease of use for developers already
|
|
@@ -424,18 +455,12 @@ This means git itself will validate option combinations and report errors. This
|
|
|
424
455
|
approach is preferred as long as the error messages returned by git are actionable
|
|
425
456
|
and understandable for users of the gem.
|
|
426
457
|
|
|
427
|
-
|
|
428
|
-
`--system`), only one may be specified. Providing more than one will raise an
|
|
429
|
-
`ArgumentError`.
|
|
430
|
-
|
|
431
|
-
Note that not all Git command options are supported.
|
|
432
|
-
|
|
433
|
-
### Parameter values
|
|
458
|
+
#### Parameter values
|
|
434
459
|
|
|
435
460
|
This section defines how git command-line options and positional arguments map to
|
|
436
461
|
Ruby method parameters. Contributors must follow these conventions:
|
|
437
462
|
|
|
438
|
-
|
|
463
|
+
##### Options
|
|
439
464
|
|
|
440
465
|
Git command-line options are passed as keyword arguments in the Ruby API. Methods
|
|
441
466
|
accept these via an options splat parameter (e.g., `def replace(object, replacement,
|
|
@@ -504,7 +529,7 @@ accept these via an options splat parameter (e.g., `def replace(object, replacem
|
|
|
504
529
|
all of them raises `ArgumentError`. The DSL enforces this via `requires_one_of`
|
|
505
530
|
declarations at bind time.
|
|
506
531
|
|
|
507
|
-
|
|
532
|
+
##### Positional arguments
|
|
508
533
|
|
|
509
534
|
Arguments that are not options (e.g., file names, branch names) are passed as method
|
|
510
535
|
arguments, not as keyword arguments.
|
|
@@ -551,7 +576,7 @@ arguments, not as keyword arguments.
|
|
|
551
576
|
These conventions ensure the API is predictable and closely aligned with the git CLI.
|
|
552
577
|
If a new option type is encountered, extend this section to document the mapping.
|
|
553
578
|
|
|
554
|
-
|
|
579
|
+
#### Output processing
|
|
555
580
|
|
|
556
581
|
The `git` gem translates the output of many Git commands into Ruby objects, making it
|
|
557
582
|
easier to work with programmatically.
|
|
@@ -578,7 +603,7 @@ repo.diff_path_status('HEAD~1', 'HEAD') # File paths and status (git diff --name
|
|
|
578
603
|
This approach ensures each method has a clear, predictable return type and allows for
|
|
579
604
|
targeted parsing logic appropriate to each output format.
|
|
580
605
|
|
|
581
|
-
###
|
|
606
|
+
### Implementation
|
|
582
607
|
|
|
583
608
|
The gem uses the three-layer architecture described in
|
|
584
609
|
[Layered architecture](#layered-architecture). When wrapping a git command, keep the
|
|
@@ -596,6 +621,10 @@ layer responsibilities separate:
|
|
|
596
621
|
facade policy, calls the command class, and parses the raw result when returning
|
|
597
622
|
structured Ruby objects.
|
|
598
623
|
|
|
624
|
+
Steps 2 and 3 correspond to the Command and Facade layers, respectively. The
|
|
625
|
+
Execution layer (`Git::ExecutionContext::*`) already exists — a command class only
|
|
626
|
+
consumes it via `@execution_context`; it is not authored per command.
|
|
627
|
+
|
|
599
628
|
Example structure for `git add`:
|
|
600
629
|
|
|
601
630
|
```ruby
|
|
@@ -614,33 +643,54 @@ module Git
|
|
|
614
643
|
operand :pathspec, repeatable: true
|
|
615
644
|
end
|
|
616
645
|
|
|
617
|
-
#
|
|
646
|
+
# @overload call(*pathspec, **options)
|
|
618
647
|
#
|
|
619
|
-
#
|
|
648
|
+
# Execute the `git add` command
|
|
620
649
|
#
|
|
621
|
-
#
|
|
650
|
+
# @param pathspec [Array<String>] files to be added to the repository
|
|
651
|
+
# (relative to the worktree root)
|
|
622
652
|
#
|
|
623
|
-
#
|
|
624
|
-
# (relative to the worktree root)
|
|
653
|
+
# @param options [Hash] command options
|
|
625
654
|
#
|
|
626
|
-
#
|
|
655
|
+
# @option options [Boolean, nil] :verbose (nil) be verbose
|
|
627
656
|
#
|
|
628
|
-
#
|
|
657
|
+
# Alias: :v
|
|
629
658
|
#
|
|
630
|
-
#
|
|
659
|
+
# @option options [Boolean, nil] :force (nil) allow adding otherwise ignored
|
|
660
|
+
# files
|
|
631
661
|
#
|
|
632
|
-
#
|
|
633
|
-
# files
|
|
662
|
+
# Alias: :f
|
|
634
663
|
#
|
|
635
|
-
#
|
|
664
|
+
# @return [Git::CommandLine::Result] the result of calling `git add`
|
|
636
665
|
#
|
|
637
|
-
#
|
|
666
|
+
# @raise [ArgumentError] if unsupported options are provided
|
|
638
667
|
#
|
|
639
|
-
#
|
|
668
|
+
# @raise [Git::FailedError] if git exits with a non-zero exit status
|
|
640
669
|
#
|
|
641
|
-
#
|
|
670
|
+
# @api public
|
|
642
671
|
#
|
|
643
|
-
|
|
672
|
+
def call(*, **)
|
|
673
|
+
super
|
|
674
|
+
end
|
|
675
|
+
end
|
|
676
|
+
end
|
|
677
|
+
end
|
|
678
|
+
```
|
|
679
|
+
|
|
680
|
+
Here is the corresponding facade method that calls it:
|
|
681
|
+
|
|
682
|
+
```ruby
|
|
683
|
+
# lib/git/repository/staging.rb (facade — a topic module included into Git::Repository)
|
|
684
|
+
module Git
|
|
685
|
+
class Repository
|
|
686
|
+
module Staging
|
|
687
|
+
ADD_ALLOWED_OPTS = %i[all force].freeze
|
|
688
|
+
private_constant :ADD_ALLOWED_OPTS
|
|
689
|
+
|
|
690
|
+
def add(paths = '.', **)
|
|
691
|
+
SharedPrivate.assert_valid_opts!(ADD_ALLOWED_OPTS, **)
|
|
692
|
+
Git::Commands::Add.new(@execution_context).call(*Array(paths), **).stdout
|
|
693
|
+
end
|
|
644
694
|
end
|
|
645
695
|
end
|
|
646
696
|
end
|
|
@@ -678,7 +728,7 @@ into private helpers to satisfy RuboCop `Metrics` thresholds:
|
|
|
678
728
|
def call(*objects, **options)
|
|
679
729
|
raise ArgumentError, '...' if objects.empty? && !options[:batch_all_objects]
|
|
680
730
|
|
|
681
|
-
bound = args_definition.bind(**options)
|
|
731
|
+
bound = args_definition.bind(*objects, **options)
|
|
682
732
|
with_stdin(objects.map { |o| "#{o}\n" }.join) { |reader| run_batch(bound, reader) }
|
|
683
733
|
end
|
|
684
734
|
|
|
@@ -711,25 +761,9 @@ also handles translation from single values or arrays to the splat format.
|
|
|
711
761
|
> testing each option to ensure clarity and isolation. See
|
|
712
762
|
> `spec/unit/git/commands/add_spec.rb` for examples of comprehensive argument testing.
|
|
713
763
|
|
|
714
|
-
```ruby
|
|
715
|
-
# lib/git/repository/staging.rb (facade — a topic module included into Git::Repository)
|
|
716
|
-
module Git
|
|
717
|
-
class Repository
|
|
718
|
-
module Staging
|
|
719
|
-
ADD_ALLOWED_OPTS = %i[all force].freeze
|
|
720
|
-
private_constant :ADD_ALLOWED_OPTS
|
|
721
|
-
|
|
722
|
-
def add(paths = '.', **)
|
|
723
|
-
SharedPrivate.assert_valid_opts!(ADD_ALLOWED_OPTS, **)
|
|
724
|
-
Git::Commands::Add.new(@execution_context).call(*Array(paths), **).stdout
|
|
725
|
-
end
|
|
726
|
-
end
|
|
727
|
-
end
|
|
728
|
-
end
|
|
729
|
-
```
|
|
730
|
-
|
|
731
764
|
For factory methods and module-level commands, the pattern is the same but
|
|
732
|
-
`Git::ExecutionContext::Global` is used instead of the repository's
|
|
765
|
+
`Git::ExecutionContext::Global` is used instead of the repository's
|
|
766
|
+
`@execution_context`:
|
|
733
767
|
|
|
734
768
|
```ruby
|
|
735
769
|
# Factory method (Git.clone) — creates a global context, runs the command, returns a repository
|
|
@@ -743,8 +777,9 @@ end
|
|
|
743
777
|
```
|
|
744
778
|
|
|
745
779
|
> **Note:** `Git::Repository` facade methods pass `@execution_context` (a
|
|
746
|
-
> `Git::ExecutionContext::Repository`) to each command class they invoke.
|
|
747
|
-
> methods such as `Git.clone` construct a
|
|
780
|
+
> `Git::ExecutionContext::Repository`) to each command class they invoke.
|
|
781
|
+
> Module-level methods such as `Git.clone` construct a
|
|
782
|
+
> `Git::ExecutionContext::Global` instead.
|
|
748
783
|
|
|
749
784
|
### Example implementations
|
|
750
785
|
|
|
@@ -884,27 +919,145 @@ process.stdin.on('end', () =>
|
|
|
884
919
|
" | jq
|
|
885
920
|
```
|
|
886
921
|
|
|
887
|
-
###
|
|
922
|
+
### Testing guidelines
|
|
923
|
+
|
|
924
|
+
- All changes must be accompanied by new or modified unit and integration tests as
|
|
925
|
+
appropriate.
|
|
926
|
+
- The entire test suite must pass when `bundle exec rake` is run from the project's
|
|
927
|
+
local working copy.
|
|
928
|
+
- Test runs are covered by SimpleCov by default. Set `COVERAGE=false` (or `0`/`no`/
|
|
929
|
+
`off`) to skip coverage, e.g. `COVERAGE=false bundle exec rake spec`.
|
|
930
|
+
`rake spec:integration` always disables coverage, regardless of `COVERAGE`:
|
|
931
|
+
integration tests aren't meant to be exhaustive, so tracking their coverage would
|
|
932
|
+
misleadingly suggest that low integration coverage is a problem to fix.
|
|
933
|
+
- `rake spec:integration` runs in parallel (via `parallel_tests`) on MRI. Set
|
|
934
|
+
`PARALLEL_TESTS=false` (or `0`/`no`/`off`) to force serial execution, e.g.
|
|
935
|
+
`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
|
|
937
|
+
workers, and serial execution gives per-example (documentation) output.
|
|
938
|
+
- Set `SPEC=<glob>` to run specific files instead of a task's whole directory, e.g.
|
|
939
|
+
`SPEC=spec/unit/git/version_spec.rb bundle exec rake spec:unit`.
|
|
940
|
+
- Each task runs only the matches that live under its own directory, so one glob
|
|
941
|
+
spanning `spec/unit/` and `spec/integration/` can drive `bundle exec rake spec` and
|
|
942
|
+
exercise both layers of an area in a single command:
|
|
943
|
+
|
|
944
|
+
```bash
|
|
945
|
+
# Unit and integration specs for the add command
|
|
946
|
+
SPEC=spec/**/git/commands/add_spec.rb bundle exec rake spec
|
|
947
|
+
|
|
948
|
+
# Unit and integration specs for every command class
|
|
949
|
+
SPEC=spec/**/git/commands bundle exec rake spec
|
|
950
|
+
```
|
|
888
951
|
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
952
|
+
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 —
|
|
954
|
+
`SPEC=spec/unit/...` on `rake spec` runs the unit specs and skips
|
|
955
|
+
`spec:integration`. A glob matching nothing anywhere fails the task outright.
|
|
956
|
+
|
|
957
|
+
This project uses **RSpec** (`spec/`) as its sole test framework. Structure,
|
|
958
|
+
naming, setup, stubbing, and coverage rules for unit specs are defined in the
|
|
959
|
+
[`rspec-unit-testing-standards`](.github/skills/rspec-unit-testing-standards/SKILL.md)
|
|
960
|
+
skill — follow it when writing or reviewing specs under `spec/unit/`.
|
|
961
|
+
|
|
962
|
+
#### Test coverage policy
|
|
963
|
+
|
|
964
|
+
**Every pull request to `main` must keep `bundle exec rake spec:unit` at 100% line
|
|
965
|
+
coverage and 100% branch coverage of `lib/`.** CI fails the build when it drops
|
|
966
|
+
below either threshold.
|
|
967
|
+
|
|
968
|
+
This is enforceable without being onerous because unit coverage in this project is
|
|
969
|
+
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.
|
|
973
|
+
|
|
974
|
+
What the policy does and does not cover:
|
|
975
|
+
|
|
976
|
+
- **Scope is the unit suite on MRI.** Integration specs are deliberately not
|
|
977
|
+
exhaustive and are not measured (`rake spec:integration` always disables coverage).
|
|
978
|
+
JRuby and TruffleRuby do not produce reliable coverage data and are not measured.
|
|
979
|
+
- **Enforcement applies to full-suite runs.** A focused run
|
|
980
|
+
(`SPEC=<glob> bundle exec rake spec:unit`, or `bundle exec rspec <file>`) still
|
|
981
|
+
reports coverage but will not fail on it, so the usual edit-test loop is unaffected.
|
|
982
|
+
Set `FAIL_ON_LOW_COVERAGE=true` to force enforcement on for a focused run.
|
|
983
|
+
- **A focused run lists gaps only in the code it is about.** The reported percentage is
|
|
984
|
+
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
|
|
986
|
+
file mirrors. So a focused run answers "is what I just changed fully covered?" without
|
|
987
|
+
waiting for CI:
|
|
988
|
+
|
|
989
|
+
```text
|
|
990
|
+
Reporting uncovered lines and branches for 1 of 225 files.
|
|
991
|
+
|
|
992
|
+
No uncovered lines and branches in this file.
|
|
993
|
+
```
|
|
892
994
|
|
|
893
|
-
|
|
995
|
+
Ignore the percentage on a focused run; it is low because `spec_helper` loads all of
|
|
996
|
+
`lib/`, not because anything is wrong. Set `LIST_UNCOVERED_FILES=all` to list gaps for
|
|
997
|
+
every file instead.
|
|
998
|
+
- **The thresholds do not move.** Do not lower `minimum_coverage` and do not add a
|
|
999
|
+
SimpleCov filter to exclude a file. The only sanctioned escape hatch is a
|
|
1000
|
+
`# simplecov:disable` directive.
|
|
1001
|
+
- **Every file under `lib/` is measured.** SimpleCov only tracks files loaded after it
|
|
1002
|
+
starts, so a file loaded earlier is silently absent from the report rather than
|
|
1003
|
+
counted as uncovered. This is why `git.gemspec` reads the version string out of
|
|
1004
|
+
`lib/git/version.rb` instead of `require`ing it: the `Gemfile` uses `gemspec`, so
|
|
1005
|
+
requiring it there would load it on every `bundle exec`, before SimpleCov starts.
|
|
1006
|
+
Keep new code out of the load path that runs ahead of `spec_helper`.
|
|
1007
|
+
|
|
1008
|
+
When a branch is hard to cover, apply these in order:
|
|
1009
|
+
|
|
1010
|
+
1. **Reach it through the public interface.** If a branch is reachable, test it.
|
|
1011
|
+
2. **Delete it.** A branch that cannot be reached through the public interface is
|
|
1012
|
+
usually dead code, and removing it is preferable to excluding it. See commit
|
|
1013
|
+
`74e919a4` ("fix: remove unreachable nil check in `Git::Parsers::Grep.parse`") for
|
|
1014
|
+
the pattern.
|
|
1015
|
+
3. **Exclude it with `# simplecov:disable`.** Reserved for defensive guards that could
|
|
1016
|
+
only be reached by breaking an OS-level invariant. Cover the smallest possible span,
|
|
1017
|
+
state why the code is unreachable, and expect a reviewer to question it. `lib/`
|
|
1018
|
+
currently contains no coverage directives.
|
|
1019
|
+
|
|
1020
|
+
Use the inline form wherever the exclusion is a single line — it applies only to the
|
|
1021
|
+
line it sits on and needs no matching `enable`, which makes it impossible to leave a
|
|
1022
|
+
region accidentally open:
|
|
1023
|
+
|
|
1024
|
+
```ruby
|
|
1025
|
+
raise 'unreachable' # simplecov:disable defensive guard; only reachable on OOM
|
|
1026
|
+
```
|
|
1027
|
+
|
|
1028
|
+
The block form covers a span and stays in effect until the matching
|
|
1029
|
+
`# simplecov:enable` (or end of file, if you forget it):
|
|
1030
|
+
|
|
1031
|
+
```ruby
|
|
1032
|
+
# simplecov:disable branch platform-specific fallback; not reachable on MRI
|
|
1033
|
+
...
|
|
1034
|
+
# simplecov:enable branch
|
|
1035
|
+
```
|
|
1036
|
+
|
|
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
|
|
1039
|
+
does not recognize is treated as free-form reason text, which silently widens the
|
|
1040
|
+
directive to all three criteria instead of failing. Write the reason after the
|
|
1041
|
+
criteria, so the required justification lives in the directive itself.
|
|
1042
|
+
|
|
1043
|
+
**Coverage is a floor on evidence, not a proof of correctness.** A test written only
|
|
1044
|
+
to execute a line, without asserting a meaningful outcome, violates Rule 24 of the
|
|
1045
|
+
[`rspec-unit-testing-standards`](.github/skills/rspec-unit-testing-standards/SKILL.md)
|
|
1046
|
+
skill and will be rejected in review even though it turns the report green. The
|
|
1047
|
+
threshold exists to surface untested behavior, not to be satisfied.
|
|
1048
|
+
|
|
1049
|
+
To see exactly what is uncovered:
|
|
894
1050
|
|
|
895
|
-
|
|
1051
|
+
```bash
|
|
1052
|
+
# Names every uncovered line and branch (printed automatically when a full run fails)
|
|
1053
|
+
$ LIST_UNCOVERED_DETAIL=true bundle exec rake spec:unit
|
|
1054
|
+
|
|
1055
|
+
# Browsable HTML report, also uploaded as a CI artifact when a build fails
|
|
1056
|
+
$ open coverage/index.html
|
|
1057
|
+
```
|
|
896
1058
|
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
- **Contexts**: Use separate `context` blocks for different scenarios.
|
|
900
|
-
- **Options**: For methods accepting options (like commands), use a separate
|
|
901
|
-
`context` for each option to ensure isolation and comprehensiveness.
|
|
902
|
-
- **One assertion per test**: Each test should verify one specific aspect of
|
|
903
|
-
behavior. Exceptions include: (a) testing that an object has expected attributes
|
|
904
|
-
after creation (e.g., verifying multiple fields of a returned object), (b)
|
|
905
|
-
verifying expected side effects of a single operation (e.g., a method that both
|
|
906
|
-
returns a value and modifies state), (c) testing that multiple related
|
|
907
|
-
assertions hold for the same setup (e.g., boundary conditions).
|
|
1059
|
+
This policy applies to `main` only. The `4.x` maintenance branch predates it and is
|
|
1060
|
+
not held to these thresholds.
|
|
908
1061
|
|
|
909
1062
|
#### Unit tests vs Integration tests
|
|
910
1063
|
|
|
@@ -959,9 +1112,6 @@ $ bundle exec rspec spec/unit/git/commands/add_spec.rb
|
|
|
959
1112
|
$ GIT_PATH=/Users/james/Downloads/git-2.30.2/bin-wrappers bundle exec rake spec
|
|
960
1113
|
```
|
|
961
1114
|
|
|
962
|
-
New and updated public-facing features should be documented in the project's
|
|
963
|
-
[README.md](README.md).
|
|
964
|
-
|
|
965
1115
|
## Building a specific version of the Git command-line
|
|
966
1116
|
|
|
967
1117
|
To test with a specific version of the Git command-line, you may need to build that
|
|
@@ -1027,11 +1177,3 @@ GIT_PATH=/Users/james/Downloads/git-2.30.2/bin-wrappers bundle exec rake spec
|
|
|
1027
1177
|
```
|
|
1028
1178
|
|
|
1029
1179
|
Note: `GIT_PATH` refers to the directory containing the `git` executable.
|
|
1030
|
-
|
|
1031
|
-
## Licensing
|
|
1032
|
-
|
|
1033
|
-
`ruby-git` uses [the MIT license](https://choosealicense.com/licenses/mit/) as
|
|
1034
|
-
declared in the [LICENSE](LICENSE) file.
|
|
1035
|
-
|
|
1036
|
-
Licensing is critical to open-source projects as it ensures the software remains
|
|
1037
|
-
available under the terms desired by the author.
|