mrkeyoor.com_
Tue 22 Sept 22:31 UTC
PyPITestingupdated 22 Sept 2026

diff-cover review

diff-cover 10.5.1 compares the lines in a Git diff with Cobertura, Clover, JaCoCo XML, or LCOV coverage data. That produces a coverage percentage for changed code while leaving old untouched lines outside the calculation. The same distribution includes `diff-quality`, which limits supported lint results to changed lines. The current release indexes Clover paths instead of scanning the report again for each source file and turns a missing Git executable into a readable error. Our clean Python 3.12 installation was small and free of known vulnerabilities, but the tool still depends on a correctly fetched base branch and matching file paths.

Verdict

diff-cover is a practical ratchet for repositories carrying old coverage debt, provided CI makes the base ref and source paths deterministic. It should sit beside a whole-project report because changed-line coverage cannot expose untouched gaps.

We installed it

Lab card: what happened when we installed diff-coverScreenshot of diff-cover documentation
Install✓ · 0.3s6 packages on disk · 10 MB
Importimport diff_cover in 0.22s · pure Python · requires Python >=3.10
Known vulns0(pip-audit)

Answers from our run

Does diff-cover install cleanly?

Yes. In a fresh container with an empty cache, pip install diff-cover finished in 0.3s, leaving 6 packages and 10 MB on disk. pip-audit reported no known vulnerabilities.

What does diff-cover need to run?

Python >=3.10, and nothing compiled: it is pure Python. In our run import diff_cover succeeded in 0.22s.

diff-cover or coverage: which should you use?

coverage: Use it to collect Python coverage, combine data files, and enforce a whole-project threshold without Git diff analysis. diff-cover is a practical ratchet for repositories carrying old coverage debt, provided CI makes the base ref and source paths deterministic.

When should you not use diff-cover?

Your repository does not use Git. The README lists Git as a requirement, and 10.5.1 only improves the error shown when the executable is missing.

API stability4/5The central command remains a list of coverage reports followed by switches for the base branch, threshold, output, exclusions, and path mapping. Recent additions extend that model: 10.4.0 added `--branch-coverage`, 10.4.2 fixed config-file format handling, and 10.5.1 changed Clover lookup performance and the missing-Git error. Long option names make CI readable, though corrected path and report handling can change results without any workflow edit, so the tool should be pinned.
Docs4/5The repository README explains accepted report formats, default branch behavior, thresholds, combined inputs, saved diffs, range notation, working-tree states, source roots, multiline expansion, TOML, output formats, and diff-quality adapters. It also documents the common empty-report symptom and its path-matching cause. There is no separate manual with focused navigation; current guidance is one long GitHub page, and some older examples receive the same visual weight as newer options.
Maintenance5/5PyPI and GitHub published 10.5.1 on 2026-08-16, the same date as the repository's latest recorded push. GitHub lists 841 stars, 61 open issues and pull requests, and an unarchived main branch. Three releases within nine days added Clover input support, repaired config and plugin behavior, indexed Clover file lookup, and improved the missing-Git failure. That release cadence and the specificity of the fixes show direct attention to user-facing paths.
Ecosystem4/5The package receives about 6.7 million weekly downloads in the supplied data and accepts coverage formats used across Python, Java, JavaScript, C, and C++ toolchains. Its companion command understands results from Ruff, Flake8, Pylint, Checkstyle, Clang, and other named checkers. The integration is intentionally file and CLI based: it creates local reports and exit codes, leaving pull-request comments, historical charts, permissions, and artifact hosting to CI or a hosted coverage service.

Use it if

  • A legacy repository cannot pass a sensible whole-project coverage floor yet, but every new change should carry tests.
  • CI already produces Cobertura, Clover, JaCoCo XML, or LCOV and can fetch the branch used for comparison.
  • Reviewers need a local HTML, JSON, or Markdown artifact listing uncovered changed lines.
  • The same changed-line policy should apply to a supported checker such as Ruff, Flake8, Pylint, Checkstyle, or Clang.
Skip it if

Setup reality

We installed diff-cover 10.5.1 in a fresh Python 3.12 Bookworm container. pip finished in 0.3 seconds, leaving 6 packages and 10 MB on disk. The package has 5 direct dependencies, requires Python 3.10 or newer, and is pure Python without py.typed. import diff_cover worked in 0.22 seconds. pip-audit reported zero known vulnerabilities.

diff-cover does not execute tests or create coverage files. Generate a supported report first, enter the Git worktree, and run the command from a directory where report paths match repository paths. Its default base is origin/main. Shallow CI clones often lack that ref, while repositories with another default branch measure the wrong range unless the workflow fetches the base and passes --compare-branch. Version 10.5.1 reports missing Git cleanly instead of leaking a FileNotFoundError.

A low percentage fails the job only when --fail-under is set. With several coverage files, a line counts as covered if any input covers it. JaCoCo users may need --src-roots to connect package paths to Java files. Multiline expansion works only for XML and assumes an omitted line has the preceding reported line's hit count. Review that assumption before turning it into a gate.

Optional settings can live in a .toml file after installing diff-cover[toml]; positional report files remain on the command line. The default three-dot Git range means changes since the branches diverged, while --diff-range-notation=.. compares the two tips. A saved patch can replace branch lookup through --diff-file. diff-quality needs its selected linter installed separately and may need --report-root-path when a prebuilt report came from another directory.

Patterns

Generate XML and inspect changed lines measure-pytest-changes

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

Run both steps from the repository root, and fetch `origin/main` in CI before asking Git to compare against it.

Fail below the review policy enforce-diff-threshold

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

Reporting alone does not fail on a poor percentage. `--fail-under` supplies the exit-code policy.

Count partial branches check-branch-coverage

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

Branch-aware handling arrived in 10.4.0. The input report must contain branch coverage data for this option to add useful information.

Merge coverage evidence from several jobs combine-test-suites

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

Inputs use OR semantics. A changed line is covered when at least one supplied report marks it covered.

Write HTML and Markdown reports publish-local-formats

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

Create the destination directory first. Uploading or commenting with these files remains the CI provider's job.

Use an explicit Git patch analyze-saved-patch

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

This is useful when a CI system supplies a patch or keeps little branch history. Ensure the patch and report refer to the same checkout.

Tell JaCoCo where Java lives map-jacoco-sources

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

The documented defaults are `src/main/java` and `src/test/java`. Add source roots when JaCoCo package paths do not resolve to repository files.

Remove generated files from the gate exclude-generated-paths

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

Exclusions use fnmatch against paths relative to the Git directory. Quote patterns to prevent shell expansion.

Choose local change states control-worktree-inputs

diff-cover coverage.xml --ignore-unstaged --include-untracked

Staged and unstaged changes are included by default. Untracked files require `--include-untracked`.

Approximate omitted statement lines expand-multiline-xml

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

This works only with XML. It assigns an absent line the hit count of the preceding reported line, which may not match every producer's semantics.

Move optional flags into TOML load-toml-policy

# install
pip install 'diff-cover[toml]==10.5.1'

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

# run
diff-cover coverage.xml -c diff-cover.toml

The filename must end in `.toml`. Coverage report paths are mandatory command arguments, and command-line options win over file values.

Score Ruff violations on changed lines gate-ruff-on-diff

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

Install Ruff in the job first. diff-quality invokes the selected checker but does not add that checker as a dependency.

Alternatives

PackageRegistryPick it when
coveragePyPIUse it to collect Python coverage, combine data files, and enforce a whole-project threshold without Git diff analysis.
pytest-covPyPIUse it when pytest should collect coverage and write the XML file that a later diff-cover step consumes.
pycoberturaPyPIUse it to inspect and compare Cobertura reports locally when a changed-line Git policy is unnecessary.

More testing guides

pytest · chai · jsdom · vitest · playwright · coverage · the whole shelf →

How this guide is made: grounded in the library's documentation, release notes, changelog, and issue history, on a fixed rubric — not a hands-on install of every release. The 50 most-downloaded entries are additionally install-verified in clean containers. Corrections: contact the desk.