GitHunt
NO

Semantic Versioning with git tags

git-semver: Semantic Versioning with git tags

What is this used for?

  • CI/CD pipeline: Continuously version your artifacts and uniquely identify your dev-builds.
    git-semver will generate unique SemVer compliant versions for each commit you add to your
    project. Let's say you were using APP_VERSION in your build-pipeline you could simply replace
    it with
    $ APP_VERSION=$(git-semver)
  • Tag releases: Automate your workflow for tagging releases of your software. Automatically select
    the next patch/minor/major version when creating a new release tag:
    $ git tag $(git-semver -target minor)
    or create an alias
    alias gtg-min="git tag -s -a $(git-semver -target minor)"  

Why is this useful?

Software should be versioned in order to be able to identify a certain feature set or to know when
a specific bug has been fixed. It is a good practice to use Semantic Versioning
(SemVer) in order to attach a meaning to a version number or the change thereof.

git allows you to conveniently reference a certain state of your code
through the usage of tags. Tags can have an arbitrary identifier, so that it seems a natural choice
to use them for versioning.


Version tags

A semantic version consists of three dot-separated parts <major>.<minor>.<patch>
and this should be the name that you give to a tag. Optionally you can prepend
the letter v if your language specific tooling requires it. It is also possible
to attach a pre-release identifier to a version e.g. for a release candidate. This
identifier is separated with hyphen from the core version component. A valid version
tag would be, e.g. 1.2.3, v2.3.0, 1.1.0-rc3.

$ git tag v2.0.0-rc1

So for a tagged commit we would know which version to assign to our software, but
which version should we use for not tagged commits? We can use git describe to
get a unique identifier based on the last tagged commit.

$ git describe --tags
3.5.1-22-gbaf822dd5

This is the 22nd commit after the tag 3.5.1 with the abbreviated commit hash gbaf822dd5.
Sadly this identifier has two drawbacks.

  1. It's not compliant to SemVer, because there are multiple hyphens after the core version.
    See the BNF specification

  2. It doesn't allow proper sorting of versions, because the pre-release identifier would
    make the version smaller than the tagged version, even though it has several commits build
    on top of that version.

Usage

git-semver collects information about the head commit of a repo similar to how
git describe would do it and derives a SemVer compliant version from it. E.g.:

git describe git-semver
3.5.1-22-gbaf822d 3.5.2-dev.22+baf822dd
4.2.0-rc.3-5-gfcf2c8f 4.2.0-rc.3.dev.5+fcf2c8fd
1.0.1 1.0.1

It will attach a pre-release tag of the form dev.N, where N is the number of commits
since the last commit, and the commit hash as build-metadata. Additionally the patch level
component will be incremented in case of a pre-release-version. If the last tag itself
contains a pre-release-identifier the dev.N suffix will be appended but all other parts
will be left untouched. This complies with the precedence rules
defined in the SemVer spec. So that

0.9.9 < 1.0.0-rc.1 < 1.0.0-rc1.dev.3+fcf2c8fd < 1.0.0-rc.2 < 1.0.0

Formatting

The output of git-semver can be controlled with the -format option or one of it shorthand
companions as described here. The format string can include the following
characters

Format char Description
x Major version
y Minor version
z Patch version
p Pre-release version
m Metadata

The format chars x, y and z are separted with a dot, p with a hyphen and m with a
plus character. A valid format string is e.g.: x.y+m

Command line options

The output and parsing of git-semver can be controlled with the following options.

Name Description
-format Format string as described here
-no-minor Exclude minor version and all following components
-no-patch Exclude patch version and all following components
-no-pre Exclude pre-release version and all following components
-no-meta/-no-hash Exclude build metadata
-prefix Prefix string for version e.g.: v
-set-meta Set buildmeta to this value
-guard Ignore shorthand formats for pre-release versions
-target Set target release dev(default), patch, minor or major

Examples

$ git-semver
3.5.2-dev.22+8eaec5d3

# Exclude build metadata
$ git-semver -no-meta
3.5.2-dev.22

# Only major and minor version
$ git-semver -no-patch
3.5

$ git-semver -prefix v -no-hash
v3.5.2

$ git-semver -set-meta custom
3.5.2+custom

$ git-semver -target minor
3.6.0

$ git-semver -target major
4.0.0

Bumping versions

A common application of git-semver is to create new

Release safeguard

If you use git-semver to automatically derive versions for your application (e.g. in a CI/CD
environment), and you want to provide convenient shorthand versions (e.g. 1.2), so that it is
easier to follow non-breaking updates, you might run into the problem that a pre-release version
accidentally overwrites a production version. This is because

# tag of HEAD commit: 1.2.2
$ git-semver -no-patch
1.2

# tag of HEAD commit: 1.2.3-dev.1"
$ git-semver -no-patch
1.2

result in the same shorthand version. To mitigate this problem you can use the -guard option
that will ignore any output format that doesn't contain the pre-release identifier if the current
version is a pre-release version. E.g.

# tag of HEAD commit: 1.2.3-dev.1"
$ git-semver -guard -no-patch
1.2.3-dev.1+8eaec5d3

Caveats

If you create multiple annotated tags on the same commit (e.g. you want to promote a release candidate
to be the final release without adding any further commits), git-semver will pick the tag that was
created last, which is usually what you want. E.g.

$ git tag -a -m "Release candidate" 1.1.0-rc.1 
$ git-semver
1.1.0-rc.1
$ git tag -a -m "Final release" 1.1.0
$ git-semver
1.1.0

Installation

Currently, git-semver can be installed with go install

$ go install github.com/mdomke/git-semver/v6@latest

There is also a Homebrew formula that can be installed with

$ brew install mdomke/git-semver/git-semver

Docker usage

You can also use git-semver as a docker-container. Images are available from DockerHub and
GitHub Container Registry

$ docker run --rm -v `pwd`:/git-semver mdomke/git-semver

or

$ docker run --rm -v `pwd`:/git-semver ghcr.io/mdomke/git-semver
norman-abramovitz/git-semver | GitHunt