Skip to content

Versioning and Release Strategy

ColmBhandal edited this page Dec 21, 2021 · 13 revisions

Overview

This page explains how we version this project and how we perform releases.

Versioning

There are a number of types of versions that we will discuss here:

  1. Release versions - these are the versions that adhere to semantic versioning.
  2. Pre-release versions - these are versions that we want to share, but may not adhere to semantic versioning. They allow sharing of multiple subsequent breaking-change versions without an explosion in the major version number.
  3. Test versions - these are just automated versions that are stamped to intermediate builds which do not qualify for either release or pre-release.

Release Versions: Semantic Versioning

For releases, this project follows the semantic versioning specification.

Versions are in the x.y.z format where x is the major version, y is the minor version, and z is the patch version. This means that:

  • The major version is incremented when a change is introduced that breaks backwards compatibility
  • The minor version is incremented when new features are added that do not break backwards compatibility
  • The patch version is incremented when an existing bug is fixed but no other new functionality is added

Pre-Release Versions

Pre-release versioning largely follows the Pre-Release Versions in NuGet Guidance and the semantic versioning specification.

They will be of the form x.y.z-LABEL.w. The LABEL will be an indicator of stability e.g. alpha, beta etc., and the w will just be a number that differentiates subsequent pre-release versions.

Version Tracking: Release & Pre-Release

This project uses git tags to track versions for releases and pre-releases. The tags are all prefixed with 'v' i.e. they will be in the form vx.y.z (e.g. v1.0.0).

Test Versions

These versions are not released. They are simply auto-generated versions that are stamped to the published NuGet packages of various builds so that somebody can download manually & differentiate versions from one another. A test version will simply look for the most recent release or pre-release tag and then suffix that with a dot and then the number of commits between that and the HEAD of the repo. Like pre-release versions, there is no semantic versioning or meaning to this number. It's just there to differentiate different NuGet packages that are archived as part of the CI/CD build.

Examples:

  • Suppose our latest version is 1.0.1-alpha.2, and suppose we are 54 commits ahead of that tag. Then the build will stamp a version of 1.0.1-alpha.2.54.
  • Suppose our latest version is 2.5.3, and suppose we are 3 commits ahead of that tag. Then the build will stamp a version of 2.5.3.3.

Track Version Increments with the Changelog

Our CHANGELOG.md file should be used to determine the next version increment. BREAKING changes should be logged as BREAKING and will require a major version update on the next release. Otherwise, the next release will be a minor increment, except for patch releases.

Note: the GitHub release creation mechanism automatically includes notes similar to the change log - it seems to be a list of all PR titles since the last release.

When a release version is created, the changelog should be updated. This only applies to release versions, not pre-releases. In the case of major/minor releases:

  1. The [Unreleased] section should be changed to the version that's being released
  2. A new empty [Unreleased] section should be added

In the case of a patch release, a new section should be added where that patch release belongs in terms of semantic version ordering. So for example, the section patch release 1.0.4 should go between sections 1.0.3 and the next version e.g. 2.0.0. The date in the section title should be the date of the release. Note: this implies that for patch versions, the dates may not be ordered in the changelog file. Rather, the file is ordered by semantic version.

Releasing a New Version

Releases are managed by the project admins. The following steps are for admins only.

While test versions are unreleased and are applied automatically by the CI/CD build, pre-release and release versions must be added manually. To release a new version, follows these steps:

  1. First, follow the specific steps for the type or release you're creating - see below. This guidance will help you choose your tag and also will specify any actions that need to happen prior to doing the release
  2. Use the GitHub releases feature to create a new release. If it's a pre-release, then select that checkbox on the GitHub page.
    1. Click on the button to auto-generate release notes
    2. Create a tag or choose one if you've already created the tag for this release
  3. Preview the release
  4. Release the release - this will create a new tag
  5. The new tag should kick off a build - copy over the NuGet package of that build to this release. If this does not happen, then just run the workflow manually. Note: the workflow will need to run after the tag is applied so that the tag gets stamped to the NuGet package. So even if there is an existing automated-workflow build for the given commit, the workflow should be run again after tagging so that the NuGet package has the correct tag.
  6. Upload the NuGet package to Nuget
    1. In the documentation section (usually bottom of page) choose "URL" to pull the markdown description of the repo from GitHub
    2. Choose the raw README.md file as the URL: https://raw.githubusercontent.com/ColmBhandal/CsharpExtras/develop/README.md

First Pre-release After Release

If you are creating the first pre-release after an existing release, then the pre-release should always be for the next release. As of 2021-12-15, we don't see the need to create pre-releases for patches, so either the major or minor version of the semantic version should be incremented. So for example, if the existing version is 1.0.1, then our pre-release version would either be 1.1.0-alpha.0 for a targeted minor release or 2.0.0-alpha.0 for a targeted major release.

Subsequent Pre-releases

For pre-releases following a prior pre-release since the last release, then increment either the label or the version number after the label. The label should be incremented if this release represents a shift in the stability of the version. Otherwise just update the version number. So for example, suppose our previous pre-release is 2.0.0-alpha.4. Then for the next one we could choose 2.0.0-alpha.5, or we could choose 2.0.0-beta.0, if there was a substantial shift in stability. More guidance may be added in future on how we choose labels.

Subsequent Pre-release: Semantic Version Update

Sometimes, a pre-release version might leap to another pre-release version with a new semantic version. This would happen if, for example, a BREAKING change was introduced to the API, whereas up to this point no BREAKING changes were released. So for example, suppose our current released version is 2.0.0 and suppose we are targeting a minor release, so we have a pre-release tag such as 2.1.0-alpha.3. Then we can decide to skip that minor release and go straight to a new major release if some breaking changes are added either intentionally or unintentionally, and we can update our next pre-release version in that case to: 3.0.0-alpha.0.

Note: we don't expect this to happen very often. It is an exceptional case. And it will only happen if the targeted release is minor, and we need to instead go to major. Once the targeted release is major, there is no need to skip versions any more.

Major Release

A major release tag should only be created if there is an existing, stable pre-release tag there. So for example, if the tag v2.0.0-rc is there, then we are ready to release, and the release version would be v2.0.0.

Major releases should only be launched after an analysis the existing codebase and a triage of all outstanding issues. The codebase analysis should include a revision of the API package structure & could yield more issues, particularly breaking changes. For example, a package change is a breaking change, because even if the code behaves the same, it forces client code to refactor their usages to point at the new package e.g. by updating import statements.

All breaking-change issues should be closed before any major release is done. This means the issues should either be implemented or closed without implementation for some other reason.

The changelog must be updated for a major release- see above.

Minor Release

There does not need to be an existing pre-release version before releasing a minor version release. E.g. we can release 1.4.0 straight after 1.3.2 without creating any pre-releases in-between, as long we're confident that the code is stable.

As per semantic versioning, a minor release should be fully backwards compatible with the previous minor release. E.g. functionality can be added and bugfixes applied, but there should be no breaking changes. A breaking change inadvertently introduced to a minor release should be later fixed with a patch release - this should also be propagated to all downstream minor releases.

The changelog must be updated for a minor release- see above.

Releases: Patch

Patch versions should be applied to a previous released version. So in this case you need to:

  1. Checkout the previous release's tag
  2. Create a branch off that tag & make your changes
  3. Create a PR to develop (to feed the changes upstream)

The commit you want to tag with the patch version in this case is the commit that was merged into develop not develop itself (which may contain unreleased/breaking changes).

The changelog must be updated for a patch release- see above.