Semantic versioning — as detailed by the Semantic Versioning specification and Milestones — explains how to version and manage your dependencies without getting stuck with complicated upgrades. Briefly, this means:
-
Major (X.y.z): Incremented for backwards incompatible public API changes.
-
Minor (x.Y.z): Incremented for new, backwards compatible, public API enhancements/fixes.
-
Patch (x.y.Z): Incremented for small, backwards compatible, bug fixes.
The above is strict semantic versioning — as implemented via the Versionaire gem — where you only use a version that consists of major, minor, and patch information. Unfortunately, the specification allows for additional information so we need to dive into why this is allowed in the specification while also highlighting why adhering to strict semantic versioning is better for you in the long run.
Problem
The problem with the semantic versioning specification is it’s too loose. Beyond what is presented above, you can have:
-
Pre-Releases: Allowed but not required. Denoted by appending a hyphen immediately after the patch version followed by a series of dots and/or alphanumeric characters. Examples:
-
1.0.0-alpha
-
1.0.0-alpha.1
-
1.0.0-0.3.7
-
1.0.0-x.7.z.92
-
1.0.0-x-y-z.--
-
-
Metadata: Allowed but not required. Denoted by appending a plus sign immediately after the patch or pre-release version followed by a series of dots and/or alphanumeric/hyphen characters. Examples:
-
1.0.0-alpha+001
-
1.0.0+20130313144700
-
1.0.0-beta+exp.sha.5114f85
-
1.0.0+21AF26D3----117B344092BD
-
Metadata doesn’t have any precedence but pre-releases do. This means you always strip metadata when calculating version precedence to get the following (listed in descending order):
1.0.0 1.0.0-rc.1 1.0.0-beta.11 1.0.0-beta.2 1.0.0-beta 1.0.0-alpha.beta 1.0.0-alpha.1 1.0.0-alpha
Lastly — and this is a common antipattern — nowhere in the specification does it say you can use v as a prefix. Example: v1.2.3. Please don’t do this because it’s redundant, wrecks havoc on sorting, and doesn’t adhere to the specification.
When you put all of the above together (including the prefix), you can end up with this monstrosity:
v1.0.0-x.7.z.92+21AF26D3----117B344092BD
The above is not only ugly but unnecessarily complicated.
Solution
The solution to the above problem is to avoid using v as a prefix, pre-releases, and metadata. You only need strict semantic versioning where you use major, minor, and patch information. Example (listed in descending order):
1.0.0 0.1.1 0.1.0 0.0.0
Sortable. Consistent. Elegant.
We don’t need to make versioning any more complex than it already is. To elaborate — and even with the simplicity of strict semantic versioning — you still need to keep up-to-date with your own projects dependencies, sub-dependencies, and versioning of your own code. None of this is hard with rigorous Machine Upkeep but so many teams and projects fail to do that which is the bare minimum of a high functioning team. When you add pre-releases and metadata then you, unnecessarily, increase the complexity further. Let’s avoid that nonsense.
At this point you might be thinking:
-
"What if folks want to experiment with my upcoming release before official release?"
-
"What if I want to embed additional metadata in my version?"
We’ll tackle each question, in order listed, next.
Pre-Releases
Pre-releases are a poor person’s way of forcing the entire community to test a version of your code that you lack confidence in. If you are unsure about your work, there are multiple ways you can have folks kick the tires so-to-speak without using pre-release versions. For example, let the curious clone, import, and/or build from:
-
Your main branch.
-
Any feature branch.
-
A specific commit SHA.
-
A fully built download of your implementation.
Don’t forget, with the power of semantic versioning, you can quickly publish a new version with patches if people detect issues. Even better, with full coverage test suite, you increase confidence in releasing early and often. That said, mistakes can happen so being able to quickly respond shows your support to the community you are building whether that be fellow engineers and/or your customers.
Metadata
When it comes to metadata, Git has a couple solutions: Git Trailers and Git Notes. With trailers, all you need to do is add the appropriate metadata to the end of your commit message. Example:
Milestone: patch
With the above, we know that the commit is a patch so only the patch version will be bumped. That’s one example, but you could add additional metadata that is specific to the work you are doing, the project you are on, the deploy you’ll eventually make, and so forth. Due to each commit having additional metadata, it’s not hard to build reports out of this metadata since each trailer is a key/value pair. This rich source of information is a lot easier to parse than the highly compact and cryptic information tacked onto your version.
If Git Trailers are not to you liking, you can also use Git Notes which allows you to add additional details to your tags. Since notes are mutable, this means you can make multiple updates to your notes if desired.
Gems
Should you need additional firepower that adheres to this workflow, you can find a few here:
-
Versionaire: A Ruby gem that provides version types you can use in your own Ruby code that adheres to strict semantic versioning.
-
Milestoner: A Ruby gem that helps you tag, release, and generate release notes using strict semantic versioning.
Avoidances
Now that you understand the importance of strict semantic versioning be aware of the following antipatterns:
-
Break Versioning: Makes the incorrect claim that Semantic Versioning is too complex. The worst aspect of this document is the claim that major versions shouldn’t increment when they clearly help identify a breaking change. Hiding breaking changes in minor or patch versions is much worse than bumping a major version.
-
Calendar Versioning: Carries absolutely no meaning other than date/time stamps which come for free with Semantic Versioning.
-
Pragmatic Versioning: Overly complicates Semantic Versioning by adding a fourth part to the version while still pulling in pre-release and metadata information.
-
Keep A Changelog: Close in spirit but isn’t consistent with commit prefixes and also encourages maintenance of Unreleased details when Milestoner automates all of this for you.
Conclusion
By thinking about your software versions, you can focus on simplicity and avoid complexity. You’ll save yourself time and reduce the strain on your user base. Enjoy the extra time saved.