Changed lines only (git diff mode)
Your pull request changes three lines of a large file. An incremental run re-tests that whole file, and every mutant in it runs the package tests again. With --diff, Gooze mutates only the lines your branch changed, and the score tells you how well the tests check that change.
Before you start
- git is installed and on
PATH. Gooze checks for it first: without git,gooze run --diffexits with code1and says that--diffneeds git and git was not found onPATH. - The project is inside a git repository.
- The base ref exists in your clone, with enough history to find the commit your branch started from. Locally that's usually true; in CI see Run it in CI.
Run it on your branch
Take the calc package from the quick start. main has calc.go and the first tests, which leave two survivors on lines 10 and 13. On your branch you add the two boundary tests. Preview what diff mode will test:
gooze run --estimate --diff main ./...Diff mode: 1 file changed since main (merge base f5ff61d); counts cover changed lines only. PATH │ MUTATIONS ───────────────┼─────────── calc.go │ 13 ───────────────┼─────────── TOTAL FILES 1 │ 13
The first line says how many files changed since main and names the merge base commit. The table lists only the files in scope, and its counts cover the changed lines only. Unchanged files aren't listed. Here all 13 mutations of calc.go are in scope, because the branch changed its test file (see below). Then run it:
gooze run --diff main ./...Mutation score: 100.00% (13 killed, 0 survived; 0 cached, 13 tested; changed lines since main) Reports of this run are in .gooze-reports/run; add them to the main reports with gooze report merge.
The run ends with one score line. It covers the changed lines only and says so at the end (changed lines since main). The next line says where the reports of this run are: a diff run doesn't change the main reports (see The score of the change).
REF is anything git rev-parse accepts: a branch (origin/main), a tag (v1.2.0), a commit SHA or HEAD~3. Path arguments and -x/--exclude (or paths.exclude) still apply, and diff mode narrows the result further:
gooze run --diff origin/main -x '^mocks/' ./internal/...To use diff mode on every run, set run.diff in gooze.yaml or GOOZE_RUN_DIFF in the environment. The flag overrides both, and --diff="" turns a configured value off for one run:
# gooze.yamlrun: diff: origin/maingooze run --diff="" ./...What counts as changed
Gooze diffs from git merge-base REF HEAD, the commit your branch started from, so it sees the same changes a pull request shows. It compares that commit with your working tree:
- Committed, staged and unstaged changes all count.
- Untracked
.gofiles count, unless git ignores them. - The changed lines of a source file are its added or modified lines.
- A new file counts in full.
- Deleted lines have no mutants.
- Renames are followed: a file that moved without changes is not changed.
- Non-Go files are ignored, so a change in
testdataorgo.modputs nothing in scope.
Test changes test the whole file
A changed or new _test.go file puts the source files it tests in scope in full. New tests can kill survivors anywhere in that file, not only next to the lines you touched. Gooze pairs test and source files the same way its cache does.
That is what happens on the calc branch above: the branch changes only calc_test.go, so all of calc.go is tested, including the two bound checks the new tests kill.
The score of the change
The Mutation score line of a diff run covers the changed lines only. It is the score of your change, not of the codebase. The run writes its reports to .gooze-reports/run/, so you can read them again, survivors included:
gooze report view -o .gooze-reports/runPATH │ MUTATIONS │ KILLED │ SURVIVED │ NOT COVERED │ SCORE ───────────────┼───────────┼────────┼──────────┼─────────────┼───────── calc.go │ 13 │ 13 │ 0 │ 0 │ 100.00% ───────────────┼───────────┼────────┼──────────┼─────────────┼───────── TOTAL FILES 1 │ 13 │ 13 │ 0 │ 0 │ 100.00% No survivors. Mutation score: 100.00% (13 killed, 0 survived)
A diff threshold such as --threshold-diff-total 80 checks this score, so it gates the pull request's own code. When the branch changes no Go lines, there is nothing to mutate: the score is 100.00%, the run exits with 0, and the gate passes.
Mutation score: 100.00% (no mutations; changed lines since main) Reports of this run are in .gooze-reports/run; add them to the main reports with gooze report merge.
The main reports still hold the last full run, the one on main with the two survivors:
gooze report viewMutation score: 84.62% (11 killed, 2 survived)
For the score of the whole codebase with your change, fold the diff run into the main reports, then view them:
gooze report mergegooze report viewMutation score: 100.00% (13 killed, 0 survived)
Mutation score: 100.00% (13 killed, 0 survived)
Diff mode and the cache
A diff run is a narrowed run, like a run with --run or --mutagens, and follows the same rules (see the run directory):
- Its reports go to a fresh
.gooze-reports/run/(run/shard_Nwhen sharded). The main reports don't change. - It uses the main reports as its cache: a file unchanged since them is skipped and isn't in the score.
--no-cachetests the changed lines anyway. gooze report mergefoldsrun/into the main reports and removes it. A file that changed since the main reports gets the diff run's results in place of its own. If the run tested only some of its lines, the file is marked partial, and the next fullgooze runre-tests it in full. On thecalcbranch the whole file was in scope, so the merge leaves it complete.
Run it in CI
On a pull request, pass the target branch as REF. On main, keep a full run and push its reports as the baseline, so the cache stays complete. Each provider page has the pull request setup: GitHub Actions, GitLab CI and Cloud Build.
gooze run --diff origin/main ./...- git in the image. The official
golangimages include git. Minimal images may not; install git there, or diff mode fails. - History. CI checkouts are often shallow, so the base branch or the merge base is missing. Fetch full history or the base branch (see Errors).
- One job. A diff run is small, so run it in one job and gate in that job. Gating a sharded diff run on the score of the changed lines isn't supported yet:
gooze report mergefolds the shards into the main reports, whose score covers the whole codebase. Keep sharding for the full run onmain. - Baseline. Don't push a diff run's reports as the baseline. Push from the full run on
main. - Coverage profile.
--coverage-profilestill applies: changed lines that no test runs are reported asnot_covered. - Thresholds. A diff run checks the
threshold.diffkeys (--threshold-diff-totaland the others) on the changed lines, not the plain thresholds, and exits with code3below one.
Errors
- git missing or not a repository.
gooze run --diffexits with code1and says that--diffneeds git, or a git repository. - Unknown ref or no merge base. Gooze exits with code
1and suggests fetching the ref. This is common with shallow CI clones. Useactions/checkoutwithfetch-depth: 0, or fetch the branch yourself:
git fetch origin mainAn unknown ref:
Running with 1 worker(s) (shard 0/1) Error: diff against no-such-branch: unknown ref "no-such-branch"; fetch it first (actions/checkout: fetch-depth: 0, or git fetch origin <branch>)
Outside a git repository:
Running with 1 worker(s) (shard 0/1) Error: diff against main: --diff needs a git repository: git rev-parse: fatal: not a git repository (or any of the parent directories): .git
Without git on PATH:
Running with 1 worker(s) (shard 0/1) Error: diff against main: --diff needs git, but git was not found on PATH: exec: "git": executable file not found in $PATH
Limitations
- The score describes the change, not the codebase. Use a full run on
main, orgooze report mergeandgooze report view, for the whole picture. - Deleted lines have no mutants. A pull request that only removes code tests nothing.
- A mutant that spans several lines is placed on its first changed line.
- A sharded diff run can't be gated on the score of the changed lines yet (see Run it in CI).
- Unchanged code isn't re-tested even when its behavior depends on a line you changed in another file.
FAQ
How do I run Gooze only on the lines a pull request changed?
Run gooze run --diff REF ./... with the branch the pull request targets as REF, for example gooze run --diff origin/main ./.... Gooze compares the working tree with the merge base of REF and HEAD and mutates only the added or modified lines.
Does --diff need git?
--diff need git?Yes. git must be installed and on PATH, and the project must be inside a git repository. Without git, or outside a repository, gooze run --diff exits with code 1 and says that --diff needs git or a git repository.
Are uncommitted changes included in diff mode?
Yes. Gooze compares the merge base with the working tree, so committed, staged and unstaged changes count, and so do untracked .go files that git doesn't ignore.
Why did a test change put a whole source file in scope?
A changed or new _test.go file can kill mutants anywhere in the source file it tests, so Gooze tests that source file in full. It pairs test and source files the same way its cache does.
Which score should a CI gate read in diff mode?
The score of the diff run, which covers only the changed lines. A diff run prints one Mutation score line, and gooze report view -o .gooze-reports/run prints the same score from its reports, so the existing gate step works with either.
Does a diff run change my main reports?
No. A diff run writes its reports to .gooze-reports/run/ and leaves the main reports as they were. For the score of the whole codebase, run gooze report merge and then gooze report view.
Why does --diff fail in CI with an unknown ref or no merge base?
--diff fail in CI with an unknown ref or no merge base?The CI checkout is usually shallow, so the base branch or the merge base is missing. Fetch full history (fetch-depth: 0 in actions/checkout, GIT_DEPTH: 0 in GitLab) or fetch the base branch, for example git fetch origin main.
What happens when a pull request changes no Go code?
The run has nothing to mutate. The score for the changed lines is 100% and gooze run exits with code 0, so a score gate passes.