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.
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
- - [Command layer responsibilities](#command-layer-responsibilities)
27
- - [Wrapping a git command](#wrapping-a-git-command)
28
- - [Method placement](#method-placement)
29
- - [Method naming](#method-naming)
30
- - [Result class naming](#result-class-naming)
31
- - [Parameter naming](#parameter-naming)
32
- - [Parameter values](#parameter-values)
33
- - [Options](#options)
34
- - [Positional arguments](#positional-arguments)
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
- - [Unit tests](#unit-tests)
44
- - [RSpec best practices](#rspec-best-practices)
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 is designed as a lightweight wrapper around the `git` command-line
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
- By following this philosophy, the `git` gem allows users to leverage their existing
269
- knowledge of Git while benefiting from the expressiveness and power of Ruby's syntax
270
- and paradigms.
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 three architectural layers each play a distinct role:
298
+ The `git` gem is organized into three architectural layers:
275
299
 
276
300
  | Layer | Responsibility | Mechanism |
277
301
  | --- | --- | --- |
278
- | **Facade** (`Git::Repository::*` and `Git` module) | Public API and policy | Normalizes Ruby arguments, sets safe defaults, calls one or more `Git::Commands::*` classes, and may parse output into public Ruby objects |
279
- | **Command** (`Git::Commands::*`) | Neutral git CLI interface | Declares CLI arguments via the [Arguments DSL](lib/git/commands/arguments.rb), executes git, and returns `Git::CommandLine::Result` |
280
- | **Execution** (`Git::ExecutionContext`) | Execution context and subprocess defaults | Carries repository/global execution settings such as working directory, environment, timeout, binary path, and logging; runs the git CLI with subprocess defaults such as `GIT_EDITOR='true'` |
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 every CLI argument/option via the [Arguments DSL](lib/git/commands/arguments.rb)
288
- - Binds `#call` parameters with the [Arguments DSL](lib/git/commands/arguments.rb) to
289
- build the git argv
290
- - Executes a git CLI command via `Git::ExecutionContext`
291
- - Returns the raw git CLI result as a `Git::CommandLine::Result` object
292
-
293
- Command classes should not embed policy choices such as output-control flags, editor
294
- suppression, progress output, or verbose mode. These policy decisions belong to the
295
- `Git::Repository::*` facade methods, which set safe defaults at each call site when
296
- policy is needed. In most cases, the facade gives callers the choice to override those
297
- defaults when they have a legitimate reason (e.g., running in a TTY-attached
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:** `literal '--no-edit'`, `literal '--verbose'`, or
303
- `literal '--no-progress'` inside a command class — embeds policy in the wrong layer
304
- - **Correct pattern:** `flag_option :edit, negatable: true` in the command; `edit:
305
- false` passed from the facade call site
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
- ## Wrapping a git command
340
+ ## Implementing a git command
315
341
 
316
- This section guides you through wrapping a git command. The first subsections focus
317
- on **API design**: where methods belong, how to name them, and how to handle
318
- parameters and output. These describe the public interface that gem users will see.
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
- [From design to implementation](#from-design-to-implementation) then shows how to
321
- structure your code using the gem's three-layer architecture. The public API is
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
- > **Note:** When adding new git command wrappers, **always use the architecture**
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
- ### Method placement
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
- When implementing a git command, first determine what type of command it is. This
332
- determines where to implement it in the Ruby API:
356
+ #### Method placement
333
357
 
334
- > **Note:** These placement guidelines define the **public API**. Always add public
335
- > methods to the `Git` module or `Git::Repository` (the facade), even though the
336
- > implementation will be in a `Git::Commands::*` class.
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 methods** are implemented on the `Git` module. Use these to
339
- obtain a repository object for subsequent operations:
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** operate within a repository context. Implement these
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
- **Non-repository commands** do not require a repository context. Implement these as
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`, can operate in multiple contexts:
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
- - **On the `Git` module**: A scope parameter (`global: true`, `system: true`) or
367
- `file:` parameter is required. The `local:` and `worktree:` options are not allowed
368
- since they require a repository.
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
- ### Method naming
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
- ### Result class naming
429
+ #### Result class naming
401
430
 
402
- Parsed result objects returned from facade methods follow a reserved suffix convention:
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::` namespace.
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
- ### Parameter naming
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
- When multiple options are mutually exclusive (like `--global`, `--local`,
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
- #### Options
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
- #### Positional arguments
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
- ### Output processing
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
- ### From design to implementation
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
- # @!method call(*, **)
646
+ # @overload call(*pathspec, **options)
618
647
  #
619
- # @overload call(*pathspec, **options)
648
+ # Execute the `git add` command
620
649
  #
621
- # Execute the `git add` command
650
+ # @param pathspec [Array<String>] files to be added to the repository
651
+ # (relative to the worktree root)
622
652
  #
623
- # @param pathspec [Array<String>] files to be added to the repository
624
- # (relative to the worktree root)
653
+ # @param options [Hash] command options
625
654
  #
626
- # @param options [Hash] command options
655
+ # @option options [Boolean, nil] :verbose (nil) be verbose
627
656
  #
628
- # @option options [Boolean, nil] :verbose (nil) be verbose
657
+ # Alias: :v
629
658
  #
630
- # Alias: :v
659
+ # @option options [Boolean, nil] :force (nil) allow adding otherwise ignored
660
+ # files
631
661
  #
632
- # @option options [Boolean, nil] :force (nil) allow adding otherwise ignored
633
- # files
662
+ # Alias: :f
634
663
  #
635
- # Alias: :f
664
+ # @return [Git::CommandLine::Result] the result of calling `git add`
636
665
  #
637
- # @return [Git::CommandLine::Result] the result of calling `git add`
666
+ # @raise [ArgumentError] if unsupported options are provided
638
667
  #
639
- # @raise [ArgumentError] if unsupported options are provided
668
+ # @raise [Git::FailedError] if git exits with a non-zero exit status
640
669
  #
641
- # @raise [Git::FailedError] if git exits with a non-zero exit status
670
+ # @api public
642
671
  #
643
- # @api public
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 `@execution_context`:
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. Module-level
747
- > methods such as `Git.clone` construct a `Git::ExecutionContext::Global` instead.
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
- ### Unit tests
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
- - All changes must be accompanied by new or modified unit tests.
890
- - The entire test suite must pass when `bundle exec rake default` is run from the
891
- project's local working copy.
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
- This project uses **RSpec** (`spec/`) as its sole test framework.
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
- #### RSpec best practices
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
- - **Public methods**: Use a separate `describe '#method_name'` block for each public
898
- method.
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.