Versioning and releases¶
Many repositories call these reusable workflows. When all of them used
@main, one bad commit here broke every one of them at once. Versioning
exists so that does not happen again.
Scheme¶
- Release tags are immutable semver tags:
v1.0.0,v1.1.0,v1.1.1, and so on. - The moving major tag (
v1) always points at the newestv1.x.yrelease. - Callers pin the major tag:
They receive fixes and new optional inputs automatically, never a breaking
change. A caller that needs to be fully reproducible can pin an exact tag
(@v1.2.0) or a commit SHA.
- @main is the development line. Its first users are the self-tests in this
repository.
What is breaking¶
Anything that can make an existing, correct caller fail:
| Change | Why it breaks |
|---|---|
| A reusable workflow removed or renamed | uses: no longer resolves |
| An input removed | Callers that pass it get startup_failure |
| An input's type changed | Callers that pass the old type get startup_failure |
| An input or secret made required, or a new required one added | Callers that do not pass it get startup_failure |
| An output removed | Callers that read it get empty values |
| A job needs a permission it did not need before | Callers that grant the old set get startup_failure |
A breaking change needs a major bump (v1 → v2). It is listed under
### Breaking in the Unreleased section of CHANGELOG.md.
On pull requests, the self-CI (.github/scripts/check_contract.py) compares
each ru-*.yml with the base branch. It fails if it finds one of these
changes and that heading is missing.
Everything else is a minor release (new workflow, new optional input, new output) or a patch release (bug fix, action bump, documentation).
Deprecating instead of removing¶
To retire an input within a major:
- Keep declaring it, so callers that pass it keep starting.
- Prefix its description with
Deprecated:and say what replaces it. - Ignore it, or map it onto the replacement. When it is set, emit a
::warning::that names the replacement. - List it under
### Deprecatedin the changelog. - Remove it in the next major, and list the removal under
### Breaking.
Automatic patch releases¶
Every push to main is released: release.yml tags the commit vX.Y.(Z+1) after the highest release, creates
the GitHub Release and moves vX. Callers pinned to @v1 therefore get each merged fix without anyone cutting a
release. Nothing is tagged when the commit is already released, or when CHANGELOG.md's Unreleased section has a
### Breaking heading: the self-CI contract check forces that heading on any breaking change, and a breaking
change needs a new major, cut by hand as below. Minor releases (new features worth a line in the notes) are also
cut by hand.
Cutting a release (maintainers)¶
- Move the Unreleased entries in
CHANGELOG.mdunder a new## [X.Y.Z] - YYYY-MM-DDheading, and merge that tomain. - Tag the merge commit and push the tag:
release.ymlthen creates the GitHub Release with generated notes. If the tag is the highestv1.*.*release, it also force-updatesv1to the same commit. A pre-release tag (v1.1.0-rc.1) gets a pre-release and leavesv1alone. A patch to an older minor does not movev1backwards.
The release workflow pushes v1 with the workflow token. If a tag ruleset
protects v*, allow GitHub Actions to update it, or move the tag by hand.
Re-runs use the old revision¶
Re-run failed jobs reruns the workflow at the revision the original run
resolved, including the revision of each reusable workflow. After a fix lands
here, or after v1 moves, start a new run instead: push a commit, use
Run workflow, or close and reopen the pull request.
Keeping callers current¶
Callers pinned to @v1 need nothing more for minor and patch releases. For
majors, and for callers that pin exact tags or SHAs, Dependabot with
package-ecosystem: github-actions proposes the update, because it also
tracks uses: references to reusable workflows.
Inside this repository¶
The workflows in this repository pin third-party actions to full commit SHAs
with a # vX.Y.Z comment. Dependabot (.github/dependabot.yml) keeps both
current.