How to do releases

This article describes how I think releases in a git project should be done.

The following sections do not talk about major releases (increasing “x” in a “x.y.z” version number) but only about the “y” and “z” (commonly known as “minor” and “patch” releases) part.

Preface

The goal of the workflow described in this chapter is to have as little impact as possible on the general development of a project. Releases should never stop, prevent or even just slow down the normal development of a project. For example, halting development on the master branch and forbidding contributors and maintainers to merge changes to it even for as little as one hour just so that a new release can be published is not acceptable. Also should “patch” releases have no impact on the project pace or even concern developers – they should just happen in parallel without any influence on the normal development activities.

With these requirements, I propose the following workflow.

Semver conformity and API coverage

We adhere to semver, with the exceptions implied by the cargo implementation of the standard (as I am mostly concerned with Rust these days) and with the exception that updating the rustc toolchain is not considered a semver-breaking change.

Parts of the API that are not covered by semver are explicitly marked. The public API is extensively tested in integration tests. If changes to the public API of the project are necessary, these changes are documented (in the commit message as well as) in the CHANGELOG.md file of the project, with reasoning why the change was necessary and how users can upgrade to the new interface(s).

We also define the following rules for our release process:

Release cycle

Until a minor release is published, the previous minor release can receive patch releases (accordingly to semver).

Strategy

We use the release-branch strategy, where development happens on the master branch and releases are done on dedicated release-X.Y.Z branches.

Because we employ the “evergreen master branch” strategy and all of our merge commits on the master branch are guaranteed to successfully build and pass CI, a release can potentially be made starting from every commit on the master branch ( That is not as in “every commit that is reachable from master”, but rather “every commit that gets returned from git log --first-parent master).

For a description of the release workflow, read below.

Release maintainer

For every release, one or more “release maintainers” are appointed by the core team.

The responsibilities of the release maintainers are:

It is explicitly allowed for everyone to open bugfix-backport pull requests to a release branch, but the release maintainer(s) decide when and how to apply them to the release branch.

Release branches

Release branches MUST be named with a pattern. This pattern is

"release" dash <major version number> dot <minor version number> dot "x"

The "x" is used instead of the “patch version number”, because patch releases are published from the release branches (read below).

A release branch gets the following rules enforced via GitHub (or your code forge):

Workflow for minor releases

The following steps happen when a minor release is made:

  1. One release maintainer creates a branch, starting from a recent commit on master, named after the aforementioned naming rule.
  2. It is ensured that this branch cannot be pushed to by anyone except the bors merge bot (Refer to the “Merge strategies” point in the “Repository maintenance” chapter for more information) (When I initially wrote this article, “bors” was still a thing. There are most likely other solutions for this today).
  3. The release maintainer crafts the CHANGELOG.md entries for this release and opens a pull request for it, targeting the aforementioned release branch.
  4. Once agreed upon with everyone involved, they merge the PR for the CHANGELOG.md to the release branch
  5. The release maintainer publishes the release on crates.io and creates a new tag for the release. They might add a tag message as they seem appropriate. They then push the tag to the GitHub repository of the project. They also make sure that release artifacts are published on GitHub for the new release (or make sure that a github-actions pipeline does this)
  6. After the release is done, the CHANGELOG.md changes are cherry-picked to a pull request to the master branch by the release maintainer
  7. After the release is done, the release maintainer opens a pull request to the master branch to update all version numbers of all crates to the next minor version

Note that development on master can continue without interruption.

Workflow for patch releases

Patch releases are published when appropriate, there is no fixed release cycle.

Backporting

Bugfixes that are added to a patch release must have been applied to the master branch, before they are backported to the appropriate release-x.y.z branch. If a bugfix that is applied to master cannot be backported, due to either conflicts or because the feature that is about to be fixed does not exist in the release-x.y.z branch anymore, the patch may be adapted to apply cleanly or a special patch may be crafted. The release maintainer is encouraged to reach out to the developer of the patch, as it is not the responsibility of the release maintainer to adapt patches.

In any case, fixes that are backported to a release-x.y.z branch, MUST pass CI and thus MUST be submitted to the release branch via a pull request.

Publishing of patch releases

A patch release is published as soon as the release maintainer thinks it is appropriate.

The steps the release maintainer follows are almost the same as the steps of the minor release workflow:

More

The CHANGELOG.md is added on the release branch rather than on the master branch. The reason for this is the workflow: If the changelog is adapted on master, the branchoff for the release branch can only happen directly after the changelog was merged, because other merges may introduce new changes that need to be in the changelog. Also, the changelog-adding pull request must be up to date all the time, if patches get merged to master while the changelog is written, the changelog would need further fixes. If the changelog is added on the release branch, no synchronization is necessary, as the branchoff for the release already happened. Instead, the changelog entries can be “backported” to master, which is trivial.

In a similar fashion are patch-level CHANGELOG.md entries ported to the master branch.

Version number bumps happen right after branchoff for a release. Doing the version number bump before the release would mean that the release mastertainers would have to wait for the version-bump-pull-request, which is not acceptable under the preassumption that every commit from master can potentially be released. By bumping the numbers right after the release, but for the next release, we automatically get a peace-of-mind state for that next release, where the release maintainers can again just take any commit on master and start their release process.

The implication of the patch-level release workflow is that the master branch does never see changes to the version strings of the crates in the “patch” level position. This is intentional, as there is no need for that.