Hash Versioning
Versioning is everywhere in tech and allows us to reference a point in time for our software, hardware or anything in between.
Most use a simple dot separated list such as Python, JavaScript or NodeJS. Typically referring to Semantic Versioning for how the numbers are incremented. Others use a single number like Firefox, Chrome or Safari. The other common format is to just use the date, called Calendar Versioning, such as Ubuntu or Unity.
Some like to add a little extra with a cool name like Android or Ubuntu.
The problem is that these formats assume a rate of releases to be rather slow. That is, maybe once a month or twice a year, what if we release 2-10 times a day?
Note: Most, if not all, of the projects listed above do have some sort of nightly or early access build that is published much much more frequently. But for comparing we are only concerned with those that reach production status.
Scheme
HashVer format consists of 3 or 4 values separated by a period.
- Full year (
printf("%Y")
) - Zero padded month (
printf("%m")
) - [Optional] Zero padded day (
printf("%d")
) - 10+ characters of the current source controls commits hash
Examples:
- 2020.01.67092445a1abc
- 2019.07.21.3731a8be0f1a8
Note If you are using existing tools that rely on semver formatting, you can instead use a
+
between the day and hash. This specifies the hash as an optional metadata to semver checks.
Ordering
Because commit hashes are random the ordering is not guaranteed when versions collide on the same day or month if not including days. You should include enough granularity so you do not release more often than the accuracy allows. If you release many times a day, including more parts of the time like hours can work. Or you can include some other metadata like CI build index to make the builds sortable.
Advantages
Each version includes the date and hash commit, this gives us a few advantages over existing versioning schemes.
- Similar to CalVer we will know the month or optionally the exact day of the release
- Unlike CalVer we will also know the exact commit this release is from
- We can automatically calculate the version as part of our deployment process
When to use HashVer
HashVer will not apply to every project use the following as a rough guide on good use cases.
- Do you deploy to production after most commits?
- Are your changelog updates mostly a single change?
- Do you have an automated process to deploy?
- Full CI/CD recommended
- Do you have safety checks in place for bad changes?
- High code coverage
- Health checks
- Monitoring
- Alerting
- Can you rollback easily?
-
Do your users always use the latest version?
To describe this a little more consider a website like facebook. Whenever they update and release a new version of the core website, you do not get an option to use the new version or stay on the old one. You use whatever version is sent to you.
If you meet all or most of these, you can use hash versioning.
When not to use HashVer
There are many more reasons to not use hash versioning than there are reasons to use it. To name a few:
- Are backwards incompatable changes optional?
- Does the user need to install or update manually?
- Mobile apps probably will not work
- PWA’s will probably work
- Do you release infrequently?
- Long gaps in releases make hash versioning irrelevant use calver or semver
- Do you support more than one release at a time?
- Do you have a suite of external checks you must go through to release?
- This just slows you down at which point you will need to batch releases anyway
- Is your project used as a dependency to other projects?
- Do you have to manually deploy?
- Do you have to manually test?
- Do you need explicit approval from someone that takes a long time?
Generating Hash Versions
As part of automated CI/CD solutions you will want to generate your hash version.
- hashver-python can generate versions and handles more.
- Bash:
printf '%(%Y-%m-)T'; git rev-parse --short=12 HEAD
- Bash with day:
printf '%(%Y-%m-%d-)T'; git rev-parse --short=12 HEAD
- Bash with semver format:
printf '%(%Y-%m-%d+)T'; git rev-parse --short=12 HEAD
Related Practices
As part of your deployment pipeline when using hash versioning, here are some related practices. These are not directly part of hash version but are related enough to mention.
Documentation as Code
- As you break down the barriers of releasing services, you will run into the problem of documentation
- If you write your documentation alongside your service, than you can deploy both in parallel
- Each change along with documentation updates go together in one commit, preferably as a pull request
- There will be no more time to make wiki or PDF changes before a change is rolled out
Automated Changelog Updates
- Keeping track of every change will be very hard if they are released and deployed frequently
- Instead opt for an automated approach to changelog generation
- Specifications like keep a changelog are a good place to start
- hashver-python packages changelog management together
- changie also supports hash versioning
- Changelogs should be packaged or referenced in your documentation as well
Single Environment Deploys
- As you deploy faster and faster you may consider going from master straight to production
- With a strong CI/CD tool chain hash versioning keeps up with the speed of releases
- If one environment feels too risky consider two with a 24 hour rollover window
Feature Management
- As you deploy to a single environment, or quickly through two, managing beta or early access features changes
- Instead of holding new features out until they are “perfect” consider alternatives
- Such as, just releasing it and not announcing it, depending on your circumstance this may work
- Local feature flags that can be enabled for some users but not all
- A feature flag service to modify and toggle flags based on many factors
Hash Version Versioning
Hash version is in the early stages, all feedback is appreciated.