mrkeyoor.com_
Sat 08 Aug 20:58 UTC
PyPITestingupdated 08 Aug 2026

diff-cover

diff-cover is a command-line quality gate for code changed in a Git branch. It combines `git diff` with a Cobertura, Clover, JaCoCo XML, or LCOV coverage report and calculates coverage only for added or modified lines. Its companion `diff-quality` command applies supported linters to the same changed-line view. The practical aim is to demand tests and clean lint results for new work without first fixing every historical problem in a repository.

Verdict

diff-cover is a focused and effective ratchet for improving legacy repositories one change at a time. Do not mistake patch coverage for overall test health, and budget time to make Git refs and report paths deterministic in CI.

API stability4/5The core contract remains a coverage report followed by command-line switches, and the current README retains established options for comparison branches, thresholds, formats, and exclusions. Version 10.5.0 requires Python 3.10 or newer, so runtime support can move at major releases, but scripts built on documented long option names have a comparatively small surface to break.
Docs4/5The repository README is a detailed manual with copyable commands for all output formats, multiple reports, Git range notation, source roots, TOML, lint tools, and frequent failure messages. It also explains the multi-line statement blind spot. The project has no live standalone documentation URL, so navigation is a long-page experience and CI-specific recipes remain the user's job.
Maintenance5/5PyPI shows version 10.5.0 uploaded on 2026-08-08, and GitHub reports a push at 2026-08-08T17:22:51Z. The package declares support through Python 3.14 and is classified Production/Stable. GitHub's 62 open items include both issues and pull requests, but same-day code and package activity give strong evidence of active maintenance.
Ecosystem4/5Its inputs cover major language ecosystems through Cobertura, Clover, JaCoCo XML, and LCOV, while diff-quality integrates pycodestyle, pyflakes, flake8, pylint, Checkstyle, Ruff, and Clang. Weekly use is measured in millions of downloads. It remains a CLI integration point, though, without the review UI and history offered by hosted coverage platforms.

Use it if

  • You are introducing a coverage requirement to a codebase whose overall percentage is too low for an immediate global gate
  • Your CI already creates Cobertura, Clover, JaCoCo XML, or LCOV reports and checks out enough Git history to compare branches
  • You need a nonzero exit status when changed-line coverage drops below a review policy such as 80 percent
  • You want HTML, JSON, or Markdown evidence that a pull request's exact changed lines are covered
Skip it if

Setup reality

Installing `diff-cover` is the easy part: version 10.5.0 needs Python 3.10 or newer and pulls Jinja2, Pygments, chardet, and pluggy. It does not run your tests or create coverage data. CI must first generate a supported Cobertura, Clover, JaCoCo XML, or LCOV report, then invoke `diff-cover` from a Git working tree. By default it compares with `origin/main`, which fails or measures the wrong change set when the remote, base branch, shallow checkout, or pull-request merge strategy differs. Fetch the intended base ref and pass `--compare-branch` explicitly. Coverage file paths must line up with paths in `git diff`; run report generation and diff-cover from the same repository root, or normalize the producer's source paths. JaCoCo projects often need `--src-roots` because package-relative class paths do not identify the source tree by themselves. A threshold matters only when `--fail-under` is present; without it, a poor report is still informational. Multiple reports are OR-combined, meaning a line is covered if any input report covers it. TOML configuration is not part of the base install: use `pip install 'diff-cover[toml]'`, name the file with a `.toml` extension, and remember that mandatory positional inputs still stay on the command line. For pull-request services that provide a patch but little Git history, create a diff file and use `--diff-file`. Decide deliberately between the default three-dot comparison and `--diff-range-notation=..`, because they answer different Git questions.

Patterns

Generate Python coverage and check the diffcheck-python-diff-coverage

pytest --cov=src --cov-report=xml:coverage.xml
diff-cover coverage.xml --compare-branch=origin/main

Run both commands from the same repository root so source paths in coverage.xml match paths produced by Git.

Fail CI below a changed-line thresholdenforce-coverage-threshold

diff-cover coverage.xml \
  --compare-branch=origin/main \
  --fail-under=90 \
  --show-uncovered

Without --fail-under, the command reports the percentage but does not turn a low result into a failing policy check.

Create a Markdown pull-request artifactwrite-markdown-report

diff-cover coverage.xml \
  --compare-branch=origin/main \
  --format markdown:diff-coverage.md

The file is created locally. Posting it to a pull request still needs CI-provider permissions and a separate upload or comment step.

Produce HTML and JSON in one runwrite-multiple-formats

diff-cover coverage.xml \
  --format html:artifacts/diff-cover.html,json:artifacts/diff-cover.json

Create the artifacts directory before running the command; the formats are comma-separated within one --format value.

Combine results from multiple test suitescombine-coverage-reports

diff-cover unit-coverage.xml integration-coverage.xml \
  --compare-branch=origin/main \
  --fail-under=85

Combination uses OR semantics: a changed line counts as covered when any supplied report records it as covered.

Analyze an explicit patch filecheck-saved-diff

git diff origin/main...HEAD > pull-request.diff
diff-cover coverage.xml --diff-file=pull-request.diff --fail-under=90

Use this when CI supplies a patch or lacks the base ref. Do not also rely on --compare-branch to define a different change set.

Map a JaCoCo report to Java sourcesset-java-source-roots

diff-cover target/site/jacoco/jacoco.xml \
  --src-roots src/main/java generated/src/main/java \
  --compare-branch=origin/main

The README lists src/main/java as the default. Add every actual source root when report class paths do not match repository paths.

Exclude files that should not affect the gateexclude-generated-files

diff-cover coverage.xml \
  --exclude 'src/generated/*' '*/migrations/*' \
  --fail-under=90

Exclusion uses fnmatch and paths are relative to the Git root. Quote patterns so the shell does not expand them first.

Control staged, unstaged, and untracked inputsinclude-working-tree-changes

diff-cover coverage.xml \
  --ignore-staged \
  --include-untracked

Staged and unstaged files are included by default, while untracked files are excluded unless --include-untracked is passed.

Expand XML coverage across multi-line statementshandle-multiline-statements

diff-cover coverage.xml \
  --expand-coverage-report \
  --show-uncovered

This workaround is XML-only and copies the preceding reported line's hit count, so review whether that assumption fits your coverage producer.

Keep optional settings in TOMLconfigure-with-toml

# Install the optional parser first:
pip install 'diff-cover[toml]'

# pyproject-diff-cover.toml
[tool.diff_cover]
compare_branch = "origin/main"
fail_under = 90
show_uncovered = true

# The report remains positional:
diff-cover coverage.xml --config-file pyproject-diff-cover.toml

Only .toml files are parsed, and command-line values override config values. Mandatory report inputs cannot live in the file.

Run Ruff only against changed linesgate-ruff-violations

diff-quality \
  --violations=ruff.check \
  --compare-branch=origin/main \
  --fail-under=100

The selected quality tool must already be installed. diff-quality reports 'Quality tool not installed' rather than installing Ruff for you.

Alternatives

PackageRegistryPick it when
coveragePyPIYou need Python coverage collection, combination, and whole-project thresholds rather than changed-line analysis
pytest-covPyPIYour pytest suite needs to generate the XML input that diff-cover consumes
codecov-cliPyPIYou want hosted pull-request annotations, historical trends, and server-side patch coverage
pycoberturaPyPIYou want local Cobertura report inspection and comparison without tying the policy to Git diffs