gochanged
gochanged lists Go packages that have untested changes compared to a base git
branch, tag, or commit. It is intended for use in CI pipelines and local
development workflows where running the full test suite is expensive and only
the affected packages need to be tested.
Installing
go install github.com/hpidcock/gochanged@latest
Usage
gochanged [flags] [packages...]
The packages argument accepts standard Go package patterns (e.g. ./...,
./cmd/...). If no packages are specified, it defaults to ./....
gochanged prints one import path per line to stdout, making it suitable for
use as input to go test or other tools via command substitution.
Flags
| Flag |
Description |
-b, --branch |
Git ref (branch, tag, or commit SHA) to diff against. Required. |
--why |
Print an explanation to stderr for each package describing why it was selected. |
-v, --version |
Print version and exit. |
Examples
Run tests for all packages that changed compared to main:
go test $(gochanged -b main ./...)
Run tests against a specific remote branch:
go test $(gochanged -b origin/main ./...)
Run tests for changes in the last 5 commits:
go test $(gochanged -b HEAD~5 ./...)
Scope to a subtree of the module:
go test $(gochanged -b main ./cmd/...)
See why each package was selected:
gochanged -b main -why ./...
How It Works
gochanged determines which packages need testing through the following steps:
-
Resolve packages. The requested package patterns are expanded using
go list to obtain the full set of packages and their import graphs.
-
Check for workspace mode. If a go.work file is active, all requested
packages are printed unconditionally because dependency information across
workspace modules is not tracked.
-
Compare go.mod. The current go.mod is compared against the version at
the base ref. If the Go language version (go directive) changed, all
packages are printed. Otherwise, individual require and replace
directives are compared to detect changed module dependencies.
-
Diff files with git. git diff --name-status is run against the base ref
to obtain the list of added, modified, deleted, and renamed files. Each
changed file is classified as either a source change or a test-only change
(_test.go files and anything under a testdata directory).
-
Map files to packages. Changed directories are matched to their
corresponding Go packages.
-
Walk the import graph. A directed acyclic graph of package imports is
built. For every directly changed package, gochanged walks the graph in
reverse (from dependency to dependents) to find all transitively affected
packages. Packages whose test imports (TestGoFiles or XTestGoFiles)
reference an affected package are also marked.
-
Print results. The final set of packages that need testing is printed to
stdout, one per line. When --why is specified, the reason each package was
selected is printed to stderr.
What counts as a change
- A file was added, modified, deleted, or renamed compared to the base ref.
- A module dependency version changed in
go.mod.
- A
replace directive was added, removed, or modified in go.mod.
- The
go directive version changed in go.mod (causes all packages to be
selected).
- The module did not exist at the base ref (causes all packages to be selected).
Sub-packages
gochanged is composed of two internal library packages:
git — Wraps git commands for finding the repository root, reading files
at a given ref, and computing file-level diffs.
packages — Wraps go list and go env to enumerate Go packages,
resolve their imports, and detect workspace mode. Also defines the Package
struct that mirrors the output of go list -json.
Requirements
- Go toolchain (for
go list and optionally running tests)
- Git (the working directory must be inside a git repository)
- All requested packages must belong to the same Go module
License
MIT — see LICENSE.