Four gates judge a pull request on what the change adds, never on the repository's past. None of them needs an index, a model or an API key, and none stores anything.
| Gate | It fails the change when | Needs |
|---|---|---|
repowise coverage check | the tests ran too few of the changed lines | git and a coverage report |
repowise doc-drift --check | a document names a file, link, heading or command that is gone | git |
repowise security check | the change adds a secret or a risky call | git |
repowise risk --fail-above-percentile P | the change is bigger and more spread out than P% of recent commits | git (at least 8 recent commits) |
Every flag is on Coverage and CI gates. What each gate
measures is on Test intelligence,
Documentation drift,
Security signals and
repowise risk.
GitHub Actions
The repowise repository is itself an action. It installs repowise, runs the gates you pick, keeps going when one fails so the job reports all of them, and fails the step at the end.
on: pull_request
permissions:
contents: read
jobs:
repowise:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- run: pytest --cov=src --cov-report=lcov:coverage/lcov.info
- uses: repowise-dev/repowise@v0.54.0 # pin a release tag
with:
checks: coverage,doc-drift,security,risk
coverage-report: coverage/lcov.info
coverage-fail-under: 80
risk-fail-above-percentile: 95| Input | Default | Meaning |
|---|---|---|
checks | doc-drift,security | Gates to run: coverage, doc-drift, security, risk. |
version | latest | repowise version to install. |
base | the pull request's target | Revision range for coverage, security and risk. |
coverage-report | coverage.paths, else discovery | Reports, one per line. A path, a glob, or path=prefix. |
coverage-fail-under | coverage.fail_under | Minimum patch coverage percent. |
coverage-base-report, coverage-max-drop | none | Report measured at the base, and the most project coverage may fall, in points. |
doc-drift-baseline, security-baseline | none | Committed baseline files. Only findings they do not list fail. |
security-fail-on | high | Lowest severity that fails: high, med, low. |
risk-fail-above-percentile | none | Fail above this percentile. Empty reports the rank without gating. |
upload-sarif | false | Send doc drift and security findings to code scanning. Needs security-events: write. |
impacted-tests | false | Select the tests the change needs. See below. |
More inputs (coverage-min-coverable-lines, coverage-fail-under-risky,
coverage-fail-under-branches, python-version, working-directory,
impacted-tests-runner) are listed in the action's
action.yml.
Outputs coverage, doc-drift, security and risk hold each gate's exit
code.
Without the action, each gate is one step:
- run: pip install repowise
- run: repowise coverage check --report coverage/lcov.info --fail-under 80 --format github
- run: repowise doc-drift --check --format github
- run: repowise security check --format github
- run: repowise risk --fail-above-percentile 95 --format githubGitLab
Include the template and set the variables you need:
include:
- remote: https://raw.githubusercontent.com/repowise-dev/repowise/main/ci/gitlab/repowise.gitlab-ci.yml
variables:
REPOWISE_COVERAGE_REPORT: coverage/lcov.info
REPOWISE_COVERAGE_FAIL_UNDER: "80"
repowise-coverage:
needs: [test] # the job that writes the reportIt adds one job per gate on merge request pipelines. repowise-coverage
runs only when REPOWISE_COVERAGE_REPORT is set, and repowise-risk only
when REPOWISE_RISK_FAIL_ABOVE_PERCENTILE is set. Doc drift and security
also write a Code Quality report, so their findings show in the merge
request's Code Quality widget. The variable list is at the top of the
template.
Other CI systems
The gates are plain commands. Fetch the target branch, run them, and let the exit code fail the build:
git fetch --no-tags origin "+refs/heads/$CHANGE_TARGET:refs/remotes/origin/$CHANGE_TARGET"
pip install repowise
repowise coverage check "origin/$CHANGE_TARGET...HEAD" --report coverage/lcov.info --fail-under 80
repowise doc-drift --check
repowise security check "origin/$CHANGE_TARGET...HEAD"What every gate shares
- Exit codes.
0passed or had nothing to judge.1the change failed.2the gate could not evaluate (unknown revision, no merge-base, unreadable report). A setup problem never reads as a pass. - The change. Pass a range such as
origin/main...HEAD(three dots: what the branch did since it forked). Without one, the gates read the target branch from the CI's pull request variables (GitHub, GitLab, Jenkins, Bitbucket), else the remote's default branch. Doc drift checks the whole tree and takes no range. - History. Use full history (
fetch-depth: 0on GitHub,GIT_DEPTH: "0"on GitLab). A shallow clone has no merge-base, and the gate exits2. - Formats.
table,json,markdown, andgithub(annotations on changed lines plus a job summary). Doc drift and security also writesarifandgitlab.
Adopting on an existing repository
Coverage, security and risk only judge what a change adds. Doc drift checks the whole tree, so a repository that already has drift fails on the first run. Record what is there once:
repowise doc-drift --check --write-baseline .doc-drift-baseline.json
git add .doc-drift-baseline.jsonThen pass --baseline .doc-drift-baseline.json (or doc-drift-baseline on
the action). Or gate only the drift the change is answerable for, with no
baseline: repowise doc-drift --check --since auto.
To accept a security finding on purpose, record it with
repowise security check --write-baseline .security-baseline.json, or put a
repowise-security-ignore comment on its line.
Run only the tests a change needs
repowise impacted-tests --format args prints one line of runner
arguments, or :all when any part of the answer is uncertain, with the
reasons on stderr. It needs an index, so cache one from the default branch:
# .github/workflows/repowise-index.yml
on: {push: {branches: [main]}}
jobs:
index:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with: {fetch-depth: 0}
- uses: actions/cache/restore@v4
with: {path: .repowise, key: "repowise-${{ github.sha }}", restore-keys: "repowise-"}
- run: pip install repowise
- run: if [ -f .repowise/state.json ]; then repowise update --index-only; else repowise init --yes --index-only; fi
- uses: actions/cache/save@v4
with: {path: .repowise, key: "repowise-${{ github.sha }}"}Then branch on the action's run-all-tests output in the pull request job:
- uses: actions/cache/restore@v4
with: {path: .repowise, key: "repowise-${{ github.event.pull_request.base.sha }}", restore-keys: "repowise-"}
- id: select
uses: repowise-dev/repowise@v0.54.0
with: {checks: "", impacted-tests: true, impacted-tests-runner: pytest}
- if: steps.select.outputs.run-all-tests != 'false'
run: pytest
- if: steps.select.outputs.run-all-tests == 'false' && steps.select.outputs.impacted-tests != ''
env: {TESTS: "${{ steps.select.outputs.impacted-tests }}"}
run: eval "pytest $TESTS"A skipped or failed step leaves run-all-tests empty, so != 'false' runs
everything. On GitLab, set REPOWISE_IMPACTED_TESTS: "true".
It runs the whole suite whenever selection is not certain: lockfiles, build
and CI config, test data, a changed file no test reaches, or an index that
is missing or out of date. Add your own rules in .repowise/config.yaml:
tests:
full_run_on: ["migrations/**"] # changes here run everything
always_run: ["tests/test_smoke.py"] # added to every selectionThe subset is only as small as the code is loosely coupled. On tightly
coupled code the graph alone picks most tests. A per-test coverage map
(repowise coverage add .coverage from coverage run --contexts=test)
makes it more precise.
The full guide, with per-language coverage commands, monorepo reports and path-scoped gates, is Repowise in CI.