Exact flags, config keys, output fields, exit codes and environment variables. For how to
do a task see usage; for the reasoning behind the numbers see
explanation. patchcov <command> --help prints the same flags from the
binary you have.
- Commands
patchcov diffflagspatchcov mergeflagspatchcov lint-markersflags- Exit codes
- Error output (JSON on stderr)
- Output schema (JSON and YAML)
.patchcov/config.yaml- Environment variables
| Command | Purpose |
|---|---|
patchcov diff |
Attribute a coverage report to a git diff; report and gate on it |
patchcov merge |
Merge the per-shard reports of a sharded run into one lcov file |
patchcov lint-markers |
Check patchcov: coverage source markers without a coverage report |
-C, --repo <PATH> is accepted by every command, before or after the subcommand. It runs
patchcov as if started in <PATH>, like git -C. --error-format <text|json> is accepted
the same way and chooses how a failure is printed; see error output.
-h/--help and -V/--version work everywhere.
diff discovers the nearest enclosing git repository from the current directory or -C
(including a subdirectory). Relative --report and --baseline-report paths are resolved
from that invocation directory, not the discovered repository root. For example, from
<repo>/sub, use patchcov diff --report ../head.lcov --base-sha HEAD~1 to read
<repo>/head.lcov. Default config discovery, report path stripping and Go module detection
use the repository working directory.
| Flag | Purpose |
|---|---|
--report <PATH> |
Head coverage report (required); repeat once per shard to merge a sharded run |
--report-format <FMT> |
Format of every --report: auto (default) | lcov | llvm-cov-json | cobertura | jacoco | go-coverprofile |
--base-ref <REV> |
Base revision (default: merge base of the default branch and HEAD). An explicit revision is compared directly with the head, not via a merge base; see choosing the base |
--head-ref <REV> |
Head revision the report was measured at (default: HEAD) |
--baseline-report <PATH> |
Base-side report; enables project deltas and indirect changes. Takes one report |
--baseline-report-format <FMT> |
Format of --baseline-report (auto-detected by default); same values as --report-format |
-o, --output <FMT> |
markdown (default) | yaml | json |
--no-explanation |
Omit the explanation block from JSON/YAML output; no effect on markdown |
--fail-under-patch <PCT> |
Exit non-zero when patch coverage is below <PCT>, a finite number of at least 0 (nan, inf and negative values are usage errors). No added lines passes |
--fail-under-lines <PCT> |
Exit non-zero when overall line coverage is below <PCT>, a finite number of at least 0 (nan, inf and negative values are usage errors), or the report has no executable lines |
--fail-on-unmeasured <GLOB> |
Exit non-zero for matching touched files absent from every report (repeatable; unioned with diff.require-measured) |
--branch-coverage |
Score lines with missed lcov/Cobertura branches as uncovered, in head and baseline |
--collapse-ranges |
Collapse consecutive uncovered new lines into ranges (9-11) in the markdown per-file table; JSON and YAML always list single lines |
--all-files |
Report deltas and indirect changes for all files, not just touched ones |
--allow-path-mismatch |
Warn, instead of failing, when a nonempty head, shard or baseline report has no path matching a tracked file after normalization. Off by default: such a report fails. Unioned with diff.allow-path-mismatch |
--strip-prefix <PATH> |
Prefix stripped from report paths to make them repo-relative (default: the repository working directory) |
--ignore-filename-regex <REGEX> |
Exclude matching files from both reports (repeatable or comma-separated); unioned with diff.ignore-filename-regex |
--config-dir <PATH> |
Directory searched for config.yaml (default: the discovered .patchcov/, honouring PATCHCOV_CONFIG_DIR) |
--artifact-url <URL> |
Link to the full coverage-summary artifact, in the markdown footer |
--run-url <URL> |
Link to the CI run, in the markdown footer, independently of --artifact-url |
--commit-url <URL> |
Commit-URL prefix for linking SHAs |
--base-sha <SHA> / --head-sha <SHA> |
SHAs shown in the markdown Comparing line (both are needed for the line to appear) |
Markdown reports omit artifact guidance when no nonempty artifact URL is supplied. An explicit
--artifact-url (or COVERAGE_ARTIFACT_URL) is labeled as a full per-file coverage summary;
only use it for an artifact containing that summary. A run URL renders even without an
artifact URL. Without --baseline-report, per-file deltas and indirect coverage changes
are unavailable; the report makes no assumptions about CI baseline publication.
For both --fail-under-* flags, space-separated negative float values such as -inf,
-nan and -.5 receive the same explanatory usage error as the --flag=value spelling.
A following option instead of a value reports a value is required.
The five footer and link flags fall back to environment variables; see
Environment variables. --format is a hidden, deprecated alias
of -o/--output that prints a warning. --fail-on-path-mismatch is a hidden, deprecated no-op
(failing is now the default) that prints a warning; --allow-path-mismatch still wins over it.
| Flag | Purpose |
|---|---|
<REPORT>... |
Reports to merge, one per shard (required); lcov, llvm-cov JSON, Cobertura, JaCoCo or Go coverprofile |
--report-format <FMT> |
Format of every report: auto (default) | lcov | llvm-cov-json | cobertura | jacoco | go-coverprofile |
-o, --output <PATH> |
File to write the merged lcov report to (required); a path, not a format |
--strip-prefix <PATH> |
Prefix stripped from report paths (default: the repository working directory, if there is one) |
| Flag | Purpose |
|---|---|
[PATH]... |
Files to check (default: every tracked text file); an explicit path ignores --include and the config list |
--include <GLOB> |
Check only tracked files matching this glob (repeatable); replaces lint-markers.include |
Each class of failure has its own exit code, so a script can tell a failed gate from an
unreadable report without parsing stderr. The message on stderr is unchanged (its last line
starts with Error:) unless you ask for JSON. The report itself, on stdout,
is still printed when a gate fails, so a CI job can post it first.
| Code | Meaning | Examples (stderr) |
|---|---|---|
0 |
Success. The report was printed and no gate failed. Warnings on stderr (a path mismatch you allowed, a lint glob that matched nothing) do not change it | |
1 |
A gate failed (diff) |
patch coverage 61.11% is below the --fail-under-patch threshold of 80.00%line coverage 75.00% is below the --fail-under-lines threshold of 100.00%the report has no executable lines, so the --fail-under-lines threshold of 50.00% cannot be mettouched files absent from every coverage report (--fail-on-unmeasured / diff.require-measured): src/a.rs. Every failed gate is named, joined by ; |
2 |
Usage error: reported by the argument parser before anything runs, or a combination of flags it cannot check | the following required arguments were not provided: --report <PATH>invalid value 'abc' for '--fail-under-patch <PCT>' (also nan, inf, a negative value such as -5, and any value that overflows to infinity)unrecognized subcommand--branch-coverage supports only lcov and Cobertura reports: ./go.cover-o/--output is the file to write, but json looks like an output format ... (merge) |
3 |
A report is unreadable, empty or unparseable (diff, merge) |
could not read coverage report ./missing.lcov: No such file or directorycould not parse coverage report ./empty.lcov: coverage report format auto-detection failed: coverage report is empty; cannot detect formatcoverage shard <path> has no executable lines (several --report, or any merge input) |
4 |
A source marker is malformed (lint-markers, and diff for files in the report) |
src/m.rs:1: `patchcov: coverage ignore` needs a reason (write ...)coverage marker lint failed |
5 |
Config is bad | could not parse coverage config ./.patchcov/config.yaml: ... (malformed YAML, wrong type, a misspelled path-mappings field)coverage path-mappings destination must be repo-relative without '..': ...invalid ignore-filename-regex patterninvalid glob ... in --fail-on-unmeasured / diff.require-measured |
6 |
Git cannot answer | could not open git repository at ...could not resolve base ref ...could not resolve a default base ref (tried `refs/remotes/origin/HEAD`, `origin/main`, `main`, `origin/master`, `master`); pass --base-refcould not compute merge-base of `<ref>` and HEADcould not read `<path>` from the tree to scan for coverage markers (with --head-ref or a baseline) |
7 |
A path mismatch (diff): a nonempty report has no path matching a tracked file. With --allow-path-mismatch or diff.allow-path-mismatch it is only a warning |
coverage report <path>: none of its N file path(s) matches a tracked file in the repository; unmatched normalized paths: p1, p2, p3[, and M more]; use --strip-prefix or diff.path-mappings to make paths repo-relative (or pass --allow-path-mismatch / set diff.allow-path-mismatch to warn instead) |
8 |
Any other runtime error | could not write merged report to ./out/merged.lcov: No such file or directorycould not read <path> to scan for coverage markers (a file in the working tree) |
When one failure fits two rows, the most specific one wins: a config file that cannot be read
is 5, not 8. A failure that no row names also exits 8. A process killed by a signal
exits with the shell's 128 + signal, as usual.
Up to and including 0.3.0, every runtime error, gate or not, exited 1. A script that treated 1 as "a
gate failed" keeps working. One that tested for 1 to mean "anything went wrong" should test
for a non-zero status instead.
When upgrading from 0.3.0, a path mismatch also changes from a warning to an error by default:
a nonempty head, shard or baseline report with no normalized path matching a tracked file
now exits 7. A CI run that previously passed with exit 0 and a warning can therefore
fail after upgrading. Pass --allow-path-mismatch or set diff.allow-path-mismatch: true
in .patchcov/config.yaml to restore the warning-only behavior while
fixing the report paths.
Either option is enough; other errors and failed gates still fail the run.
patchcov diff --report head.lcov --fail-under-patch 80 > coverage-comment.md
case $? in
0) ;; # passed
1) echo "coverage gate failed" ;; # post the comment, then fail the job
*) echo "patchcov could not run" >&2 ;; # no usable report; do not post
esacBy default a failure ends stderr with one Error: ... line. A wrapper that wants the class
and cause without parsing that line can ask for a JSON object instead, with
--error-format json or PATCHCOV_ERROR_FORMAT=json. The flag wins over the variable, and an
empty variable counts as unset. The values are text (the default) and json; a variable with
any other value is ignored with a warning, so a typo cannot stop a run.
With json, a failing run prints one object on a single line, as the last line of stderr,
instead of the Error: line. Stdout and the exit code are unchanged, and so is each warning's
message. Lines printed earlier are JSON lines too, see Warnings and
Findings and summary below.
{"level":"error","code":3,"kind":"report","message":"could not read coverage report ./missing.lcov","chain":["No such file or directory (os error 2)"]}| Field | Type | Meaning |
|---|---|---|
level |
string | Always "error"; a warning line has "warning", and the other lines have "error" (a finding) or "info" |
code |
number | The process exit code, from the table above |
kind |
string | The name of the class of failure, from the table below; stable like the code |
message |
string | The outermost error message |
chain |
array of strings | The causes beneath message, outermost first; empty when there are none |
gates |
array of objects | Only when kind is gate: the failed gates as data, described after the table of kind values |
message and then chain, joined with : , is the text that follows Error: in the default
format. A message can contain newlines (a regex parse error does), escaped as usual in JSON.
kind |
code |
|---|---|
gate |
1 |
usage |
2 |
report |
3 |
marker |
4 |
config |
5 |
git |
6 |
path-mismatch |
7 |
other |
8 |
The argument parser's own errors (a missing required flag, an unknown subcommand) are usage
with an empty chain, and message is the parser's error text, with any tips, without its
usage block. Because parsing had failed, patchcov finds the format by looking for
--error-format among the arguments (the last one wins, and -- ends the options) and in
PATCHCOV_ERROR_FORMAT. --help and --version are not failures and print as usual. A bad
value for --error-format itself is a parser error in the default format.
A failed gate is a gate object. Its message names every failed gate, with the threshold and
the measured value, as in the text. It also has a gates field with the same facts as data, so
a wrapper can show which gate failed, or compare the measured value with the threshold, without
parsing the message. gates is present only when kind is gate. It has one entry per failed
gate, in the order below, and each entry's gate names the gate and decides its other fields:
{"level":"error","code":1,"kind":"gate","message":"patch coverage 61.11% is below the --fail-under-patch threshold of 80.00%","chain":[],"gates":[{"gate":"fail-under-patch","threshold":80.0,"measured":61.11111111111111}]}gate |
Fields | Meaning |
|---|---|---|
fail-under-patch |
threshold, measured (numbers) |
Patch coverage measured is below --fail-under-patch |
fail-under-lines |
threshold (number), measured (number or null) |
Overall line coverage measured is below --fail-under-lines. measured is null when the report has no executable lines, which fails the gate because nothing can be measured |
fail-on-unmeasured |
files (array of strings) |
These touched files match --fail-on-unmeasured or diff.require-measured and are absent from every report |
threshold is the value you gave. measured is a percentage on a 0 to 100 scale, not
rounded (the message rounds it to two decimal places), so measured < threshold is the
comparison the gate made. Round it yourself for display. A gate that passed is not listed.
Consumers should ignore keys they do not know: new fields, and new gate values, may be added
without a change to the exit codes or to the existing fields.
With --error-format json, each warning is printed as one JSON line on stderr instead of a
warning: ... line. The text of the message is the same as in the default format.
{"level":"warning","kind":"path-mismatch","message":"coverage report head.lcov: none of its 1 file path(s) matches a tracked file in the repository; unmatched normalized paths: `nowhere/else.rs`; use --strip-prefix or diff.path-mappings to make paths repo-relative","report":"head.lcov","file_count":1,"unmatched":["nowhere/else.rs"]}This is patchcov diff --report head.lcov --allow-path-mismatch: report is the
argument head.lcov, including when -C is supplied (details).
| Field | Type | Meaning |
|---|---|---|
level |
string | Always "warning" |
kind |
string | The class of warning, from the table below; stable like the error kind |
message |
string | The warning, as it follows warning: in the default format |
Each kind adds fields after message, listed below.
kind |
When | Fields besides level, kind and message |
|---|---|---|
deprecated |
A deprecated flag was used (--format, --fail-on-path-mismatch) |
flag (string), the flag with its dashes; replacement (string or null), what to use instead, or null if nothing replaces it |
path-mismatch |
diff: a report's paths match no tracked file, and --allow-path-mismatch or diff.allow-path-mismatch let the run continue |
report (string), the report as the message names it (a display string); file_count (number), how many file paths it has; unmatched (array of strings), the first three of its normalized paths (the ones the message samples). Every path in the report is unmatched, so the list is truncated exactly when it is shorter than file_count (more than three paths), and the message then ends the sample with , and N more |
shard-root |
merge: a shard was measured under a different workspace root |
shard (string), the shard as the message names it (a display string); strip_prefix (string), the prefix it was expected under, without a trailing / (a display string too); absolute_paths (number), how many absolute file paths it has, none of them under the prefix |
glob-no-match |
lint-markers: its globs matched no tracked file, so nothing was checked |
globs (array of strings), the globs; origin (string), where they came from: "--include" or "lint-markers.include" (the config key) |
Another example, a shard under another root, from patchcov merge --strip-prefix /ci/workspace -o merged.lcov two.lcov
(without -C, merge names the shard as typed):
{"level":"warning","kind":"shard-root","message":"coverage shard two.lcov: none of its 1 absolute file path(s) is under `/ci/workspace`, so ...","shard":"two.lcov","strip_prefix":"/ci/workspace","absolute_paths":1}The fields follow message and are the data it is written from, so a wrapper can attach a warning
to a file or a flag without parsing the message. Consumers should ignore keys they do not know:
fields and kinds may be added without a change to the existing ones.
To tell the lines apart, read level: a warning is "warning", a finding or summary
is "error" or "info", and the failure is "error" with a code. The failure is still the last
line of stderr. The warning about an invalid PATCHCOV_ERROR_FORMAT is
the one line that is not JSON, since the format it asks for was not accepted; parse only the lines
that start with { and keep the objects that have a level.
report, shard and strip_prefix are filesystem paths written as display strings: the text the
message prints for them (strip_prefix without its trailing /), not path identifiers. JSON strings
must be UTF-8, so in a path that is not valid UTF-8 each invalid sequence of bytes is replaced with
one U+FFFD (the replacement character). The value may then name no file that exists, and two
different paths can give the same value.
report and shard preserve the input argument as typed, including a leading ./, for both
commands. This applies to head reports, baseline reports and shards. Wrappers can match these
fields against their UTF-8 input arguments. -C affects where a relative file is read, but does
not change its diagnostic label:
| Run | A relative head.lcov is named |
Example |
|---|---|---|
diff without -C |
as typed | head.lcov |
diff -C <dir> |
as typed | head.lcov |
merge without -C |
as typed | head.lcov |
merge -C <dir> |
as typed | head.lcov |
An absolute input path is also kept as typed. Relative inputs are still read relative to -C
(or the current directory when it is omitted). Path-mismatch errors use the same report label
as warnings. strip_prefix describes the effective prefix, which can be inferred from the
repository; it is not necessarily an argument as typed.
Earlier versions named diff reports joined to -C (or .) and merge shards joined to -C
when supplied. Consumers that expected those anchored labels should match input arguments instead.
No lossless form of these paths is emitted today: non-UTF-8 report or shard paths are
rare, and a field for them can be added later without changing the existing ones. unmatched is
not affected: it holds the file paths read from inside the report, which are already strings.
Two commands print more than warnings and a failure. With --error-format json each of these is a
JSON line too, with the same text in message as the default format prints.
patchcov lint-markers prints one object per file with a malformed marker (the first one in the file),
before the failure. A finding is level "error" because it makes the run
fail, but its kind is "marker-finding"; the failure that follows it has kind "marker"
and a code, and is the last line. To find the failure, take the last line, not the first
"error".
{"level":"error","kind":"marker-finding","path":"src/a.rs","line":12,"message":"unterminated `reason=\"…\"` (missing closing quote)"}| Field | Type | Meaning |
|---|---|---|
level |
string | Always "error" |
kind |
string | Always "marker-finding" |
path |
string | The file, as the default format prints it: repo-relative, or as given on the command line |
line |
number | The 1-based line of the marker (of the opening marker, for an unterminated region) |
message |
string | What is wrong, without the path:line: that the default format starts the line with |
patchcov merge prints its summary line as an info object, after any warnings:
{"level":"info","kind":"merge-summary","message":"merged 3 report(s) into merged.lcov: 12 file(s), 840 of 1000 lines covered (84.00%)","inputs":3,"output":"merged.lcov","files":12,"total_lines":1000,"covered_lines":840,"percent":84.0}| Field | Type | Meaning |
|---|---|---|
level |
string | Always "info" |
kind |
string | Always "merge-summary" |
message |
string | The summary line, as the default format prints it |
inputs |
number | The number of reports merged |
output |
string | The file written, as the default format prints it |
files |
number | The files in the merged report |
total_lines |
number | Executable lines in the merged report |
covered_lines |
number | Executable lines hit at least once |
percent |
number or null |
Line coverage on a 0 to 100 scale, not rounded (message rounds it); null when there are no executable lines |
A line stays a JSON object even if patchcov cannot serialize its full record (a bug, not
something a command line can cause), so stderr never has a blank line where a diagnostic
belonged. A warning or summary line then has only level, kind and message, the first two
being the record's own. An error line keeps level, code, kind and message;
message is the outermost message alone. Its chain and gates are serialized independently
and kept when each succeeds; only a component that cannot serialize is omitted. As in a
normal error line, gates is absent when there are no failed gates. A finding's message
is then the default format's whole line, path:line: included.
patchcov diff -o json and -o yaml emit the same structure. Percentages are on a 0 to 100
scale and rounded to two decimal places. null (~ in YAML) means "not measurable", for
example a percentage over zero lines. Field order follows the table below.
The example is the output of docs/examples/sample.sh json, a small
repository where a patch adds 18 executable lines and 7 are uncovered, trimmed to the
interesting parts:
{
"patch_coverage": {
"percent": 61.11,
"covered": 11,
"total": 18,
"files": [
{
"path": "src/cache.rs",
"percent": 66.67,
"covered": 4,
"total": 6,
"uncovered_lines": [5, 6]
}
]
},
"uncovered_new_lines": ["src/cache.rs:5", "src/cache.rs:6"],
"unmeasured_files": [],
"project_delta": {
"total_before": 100.0,
"total_after": 85.42,
"files": [
{ "path": "src/cache.rs", "before": null, "after": 66.67, "delta": null }
]
},
"indirect_changes": { "newly_covered": 0, "newly_uncovered": 0, "lines": [] }
}(The real output lists both files and all seven uncovered lines, and spreads arrays over several lines.)
| Field | Type | Present | Meaning |
|---|---|---|---|
explanation |
object | by default; omitted by --no-explanation |
A description of each field, and whether it is populated in this document. See explanation |
patch_coverage |
object | always | Coverage of the lines this diff added. See patch_coverage |
uncovered_new_lines |
string[] | when non-empty | The added lines that are not covered, as path:line, in path then line order |
unmeasured_files |
string[] | always (may be empty) | Sorted repo-relative paths of non-deleted files the diff touched that appear under neither name in any head or baseline report. Files excluded by --ignore-filename-regex are omitted. May be documentation or uninstrumented code; only --fail-on-unmeasured / diff.require-measured globs turn a match into a failure |
project_delta |
object | only with --baseline-report |
Project coverage before and after. See project_delta |
indirect_changes |
object | only with --baseline-report |
Lines whose coverage flipped without their content changing. See indirect_changes |
markers |
object[] | when a source marker applied | The silenced regions. See markers |
excluded_files |
object | when --ignore-filename-regex removed a file |
What the filter removed. See excluded_files |
| Field | Type | Meaning |
|---|---|---|
percent |
number or null | Covered over total added executable lines. null when total is 0 (the diff added no instrumented code) |
covered |
integer | Added executable lines that are covered |
total |
integer | Added executable lines: the patch-coverage denominator |
files |
object[] | One entry per file that added executable lines, in path order |
files[].path |
string | Repo-relative path |
files[].percent / covered / total |
as above | The same three figures for this file |
files[].uncovered_lines |
integer[] | Uncovered added line numbers; omitted when the file has none |
| Field | Type | Meaning |
|---|---|---|
total_before |
number or null | Overall line coverage of the baseline |
total_after |
number or null | Overall line coverage of the head: the real, measured value. This is what --fail-under-lines gates on |
total_after_effective |
number | Head coverage with tolerate masking applied. Present only when masking changed the total |
files |
object[] | Touched files (all files with --all-files) whose coverage is listed |
files[].path |
string | Repo-relative path |
files[].before |
number or null | The file's baseline coverage; null for a new file |
files[].after |
number or null | The file's real head coverage |
files[].after_effective |
number | Masked head coverage; present only when masking changed it, in which case delta is after_effective - before |
files[].delta |
number or null | Change in percentage points; null for a new file |
notable_unchanged |
object[] | Files the diff did not touch that still moved by at least 10 covered lines, with the same fields as files[]. Not attributable to this diff. Omitted when empty |
| Field | Type | Meaning |
|---|---|---|
newly_covered |
integer | Unchanged lines that went from uncovered to covered |
newly_uncovered |
integer | Unchanged lines that went from covered to uncovered |
lines |
object[] | One entry per flipped line |
lines[].path |
string | Repo-relative path (at head) |
lines[].head_line |
integer | The line number at head |
lines[].base_line |
integer | The same line's number at base |
lines[].transition |
string | uncovered_to_covered or covered_to_uncovered |
One object per region a patchcov: coverage marker silenced, so a reviewer can see what was
silenced and why. See source markers.
| Field | Type | Meaning |
|---|---|---|
path |
string | Repo-relative path of the marked file |
kind |
string | ignore (lines removed from both reports) or tolerate (lines kept, flips masked) |
side |
string | Where the region was observed: both, head or base |
start, end |
integer | First and last line of the region, 1-based and inclusive, as observed on that side |
reason |
string | The marker's mandatory reason="…" text |
| Field | Type | Meaning |
|---|---|---|
count |
integer | Files removed by --ignore-filename-regex / diff.ignore-filename-regex; only files that were in a head or baseline report count, once each |
touched_count |
integer | How many of them this diff touched |
new_executable_lines |
integer | Executable lines the diff added to excluded files: patch lines the filter took out of the denominator |
paths |
string[] | Every excluded path |
touched |
string[] | The subset of paths this diff touched |
| Field | Type | Meaning |
|---|---|---|
text |
string | A paragraph describing the document |
fields |
object[] | One entry per documented field |
fields[].name |
string | Path of the field, e.g. patch_coverage.files[].path |
fields[].text |
string | What it contains |
fields[].present |
boolean | Whether this document populates it |
Repository settings live in .patchcov/config.yaml, in version control. There are no
user-level or machine-level settings, so what a gate reports is visible to a reviewer.
Discovery, the same for every command, first match wins:
--config-dir <PATH>(diffonly).- The
PATCHCOV_CONFIG_DIRenvironment variable, when set and non-empty. - The nearest
.patchcov/directory walking up from the repository root, never past the repository boundary (a.gitentry). <repo root>/.patchcov, which need not exist: a missing directory or file is an empty config, and behaviour is unchanged.
A file that is present but malformed (bad YAML, a value of the wrong type) is a hard error
rather than a silent default. Unknown keys are ignored, so a newer config stays readable by an
older binary, with one exception: a misspelled field inside a path-mappings entry fails.
| Key | Type | Default | Used by | Combines with the CLI flag |
|---|---|---|---|---|
diff.ignore-filename-regex |
list of regex strings | [] |
diff |
Union with --ignore-filename-regex: the flag only adds |
diff.path-mappings |
list of {from, to} |
[] |
diff |
No flag; config only. merge does not read it |
diff.require-measured |
list of glob strings | [] |
diff |
Union with --fail-on-unmeasured |
diff.allow-path-mismatch |
boolean | false |
diff |
Either one enables it: set here or pass --allow-path-mismatch |
lint-markers.include |
list of glob strings | [] (every tracked text file) |
lint-markers |
Replaced by --include when the flag is given |
# .patchcov/config.yaml
diff:
ignore-filename-regex:
- 'src/bits/popcount\.rs'
path-mappings:
- from: com/example
to: services/api/src/main/java/com/example
require-measured:
- 'src/**/*.rs'
allow-path-mismatch: false
lint-markers:
include:
- '**/*.rs'Regexes (Rust regex syntax) matched against the
repo-relative path, unanchored (a partial match is a match), after --strip-prefix. A
matching file is removed from both the head and baseline reports. Empty patterns are dropped,
because an empty regex would match every path. An invalid regex is an error. See
excluding files.
Each entry has exactly two string fields, from and to; any other field is an error.
| Field | Meaning |
|---|---|
from |
A report path prefix. Matches whole directory components (src does not match src2). '' matches every relative path |
to |
The repo-relative replacement. '' removes the matched prefix. Must not start with /, contain :, or contain .. |
The longest matching from wins, each path is mapped once (no chaining), and two entries with
the same normalised from are an error. Backslashes become / and a leading ./ and
trailing / are dropped before comparison. Aliases that map to one path combine by maximum
line hits. Mappings run before --strip-prefix. See
package-relative paths and
the path pipeline.
Globs matched against whole repo-relative paths: * stays within one directory, ** crosses
directories (src/**/*.rs also matches src/a.rs). A touched file that matches and is absent
from every report fails the run. An empty or invalid glob is an error. See
requiring touched files to be measured.
A boolean, false by default. By default diff fails when a nonempty head, shard or baseline
report has no path matching a tracked file, because such a report cannot be joined to the diff
and a gate over it would pass without measuring anything. true turns that error into the same
message as a warning on stderr, exactly as --allow-path-mismatch does; either one is enough.
Set it only for a repository where a report legitimately matches nothing, since a gate cannot
see a patch it cannot join. A report with at least one tracked path never triggers the check.
See absolute paths from another runner.
Globs with the same syntax, selecting which tracked files patchcov lint-markers scans when
given no paths. A file is scanned when it matches any glob. A glob list that matches no tracked
file scans nothing and prints a warning. See
checking marker syntax.
| Variable | Used by | Meaning |
|---|---|---|
PATCHCOV_ERROR_FORMAT |
every command | Fallback for --error-format: text or json. Empty is ignored. See error output |
PATCHCOV_CONFIG_DIR |
every command that reads config | Directory holding config.yaml; overrides discovery, overridden by --config-dir. Empty is ignored |
COVERAGE_ARTIFACT_URL |
diff |
Fallback for --artifact-url |
COVERAGE_RUN_URL |
diff |
Fallback for --run-url |
COVERAGE_COMMIT_URL |
diff |
Fallback for --commit-url |
COVERAGE_BASE_SHA |
diff |
Fallback for --base-sha |
COVERAGE_HEAD_SHA |
diff |
Fallback for --head-sha |
A flag wins over its variable, and an empty variable counts as unset. These only change the links and SHAs in the markdown footer.