patchcov 0.2.0

Patch coverage for git diffs: attribute lcov, llvm-cov, Cobertura, Go and JaCoCo reports to a diff and gate on it.
Documentation
<p align="center">
  <img src="docs/assets/patch.svg" alt="Patch, the patchcov hermit crab, mending a hole in its quilted shell" width="200">
</p>

# patchcov

Patch coverage for git diffs. `patchcov` attributes a per-line coverage report to a
git diff and tells you what share of the lines a change *added* are covered, which
new lines are not, and whether anything moved on code the change did not touch.

- **Reads** lcov, llvm-cov JSON, Cobertura, JaCoCo XML and Go coverprofiles, detected
  from the content.
- **Reports** patch coverage, the uncovered new lines, per-file project deltas and
  indirect changes, as a markdown PR comment, YAML or JSON.
- **Gates** a branch with `--fail-under-patch` and `--fail-under-lines`.
- **Merges** the reports of a sharded CI run into one file, or takes them directly.
- **Silences** known-flaky regions with source markers, and files with a repo-level
  ignore list, without hiding real coverage.

## Why patchcov

patchcov does one job: it tells you, precisely and repeatably, whether a change
is covered by tests. It is built to be a clear signal, not a dashboard.

- **A precise signal.** The headline number is the share of *added* lines that are
  covered, with the uncovered ones listed as `file:line`. Whoever or whatever reads it
  (a reviewer, a script, an AI agent) knows what to do next without interpreting
  charts or trends.
- **Machine-readable and gateable.** JSON and YAML output, `--collapse-ranges` for a
  compact list, and exit codes from `--fail-under-patch` and `--fail-under-lines`
  make it easy to automate, including in a loop where an agent adds tests until the
  gate passes.
- **No service in the loop, so no outage to block you.** It reads report files from
  disk and sends nothing anywhere: no account, upload token or server to be down, slow
  or rate-limited. It runs on the same runners as your other CI jobs, so the coverage
  gate is available whenever your CI is, and a hosted quality server going down for
  days cannot block merges. It runs the same on a laptop or in a sandbox, so a coverage
  check is a command you can run before you push, not something you learn about
  afterwards.
- **Quiet by construction.** Coverage that flaps between runs (a region gated on a
  runtime CPU feature, say) is the usual source of phantom regressions. Per-file
  scoping, a tolerance on the headline delta and `tolerate` source markers keep that
  noise from reading as a regression while the reported numbers stay honest.
- **Fails loudly.** An empty report, a failed shard or a malformed marker is an error,
  not a quietly lower number. Everything that silences coverage needs a stated
  reason and is listed in the PR comment, and settings live in `.patchcov/config.yaml`
  in version control, so a reviewer can see what a gate does and what was excluded.
- **One tool across languages.** Five report formats are detected from content, so the
  same command works for Rust, Go, Java and Kotlin, JavaScript and TypeScript,
  Python, C and C++ and more.

## When to choose patchcov

Choose patchcov when:

- the question you need answered is "did this change add untested code, and where?";
- coverage is checked by automation, including AI agents, as much as by people;
- you want the coverage gate to depend on nothing but your own CI runners, so an
  outage of an external quality server or coverage service cannot block your work;
- you want the check to run locally and in CI without sending code coverage to an
  external service;
- your coverage is noisy and you need regressions you can trust;
- you run a sharded CI job and want a single combined result and gate.

Choose something else, or use patchcov alongside it, when you need:

- a hosted dashboard with coverage history, trend graphs or cross-repository views;
- branch coverage (patchcov reads line coverage only);
- a broader code-quality platform that covers coverage along with static analysis and
  security scanning.

patchcov measures whether lines ran, not whether the tests assert anything useful about
them. Treat it as a regression signal, and pair it with review, or mutation testing,
for confidence in the tests themselves.

## Install

```bash
cargo install patchcov
```

Prebuilt binaries for Linux (glibc 2.35 or newer), macOS and Windows (x86_64 MSVC)
are attached to each [GitHub release](https://github.com/rust-works/patchcov/releases),
along with SHA-256 checksums. Linux and macOS builds use `.tar.gz` archives; Windows
builds use `patchcov-v<version>-x86_64-pc-windows-msvc.zip`. Extract the Windows ZIP
and add the directory containing `patchcov.exe` to your `PATH`.

## Use

```bash
# Produce a per-line report (any tool that writes one of the supported formats).
cargo llvm-cov --no-report
cargo llvm-cov report --lcov --output-path head.lcov

# Patch coverage against the merge base with origin/main.
patchcov diff --report head.lcov

# Fail the job if patch coverage is under 80% or overall coverage under 70%.
patchcov diff --report head.lcov --fail-under-patch 80 --fail-under-lines 70

# Merge the shards of a sharded run, then gate as usual.
patchcov merge shard-1.lcov shard-2.lcov -o merged.lcov

# Check source markers without a coverage report.
patchcov lint-markers
```

The full guide is in [docs/usage.md](docs/usage.md): input formats and path mapping
for non-Rust languages, gating semantics, sharded runs, the ignore list, source
markers and the flag reference.

## GitHub Action

[action-works/patchcov-action](https://github.com/action-works/patchcov-action) runs
`patchcov` in a pull-request workflow. It installs a cached `patchcov` binary, runs
`cargo-llvm-cov` (or takes a report you produced in any language), posts a sticky PR
comment with patch coverage and the uncovered new lines, publishes the baseline on
`main` and applies the gates after the comment posts. It also combines the reports
of sharded runs.

```yaml
jobs:
  coverage:
    runs-on: ubuntu-latest
    permissions:
      contents: read
      pull-requests: write        # to post the coverage comment
    steps:
      - uses: actions/checkout@v4
        with:
          fetch-depth: 0          # full history so the merge base resolves
      - uses: action-works/patchcov-action@v1
        with:
          fail-under-patch: 80
```

See the action's README for thin mode, sharded runs and its inputs.

## Configuration

Settings that belong to a repository live in `.patchcov/config.yaml`, found by
walking up from the repository root. `--config-dir` and the `PATCHCOV_CONFIG_DIR`
environment variable override the location. There are no user-level or
machine-level settings, so what a gate reports is visible in version control.

## Library

The analysis is a library as well as a command: `patchcov::parse` reads a report,
`patchcov::DiffModel` builds the added-line sets from `git2`, `patchcov::analyze`
attributes coverage to the diff and `patchcov::render` formats the result.

## Contributing

See [CONTRIBUTING.md](CONTRIBUTING.md) for local checks and the merge queue flow.

## Releasing

See [docs/RELEASE.md](docs/RELEASE.md).

## License

BSD-3-Clause. See [LICENSE](LICENSE).