Home Automatic Semantic Versioning for GitLab Projects using CI/CD Pipeline
Post
Cancel

Automatic Semantic Versioning for GitLab Projects using CI/CD Pipeline

Semantic Versioning

As software evolves the need arises to come up with some useful versioning scheme. One of those is Semantic Versioning (also called SemVer1) which is currently used in a lot of software projects. The idea is simple: the versioning should follow a specific meaning. For that the version string is usually split into three parts:

1
[Major].[Minor].[Patch]

So for example:

1
2.1.17

These parts are in order of decreasing importance from left to right and are separated by a simple dot (“.”).

Major

The Major part usually changed only when big changes happened. For example a completely revised software (aka Version 2) would justify a change in the Major version. This is usually tied to the highest risk of being incompatible with previous version. So for example a 2.x.x version of a software might not be able to read the files stored by a 1.x.x version of that same software.

Minor

When a new feature is added or something was changed that might impact backwards compatibility without completely breaking it (aka semi-breaking change), then typically the Minor version is changed. So for example a x.15.x version should have at least one feature/change more than the preceding x.14.x version.

Patch

Patch or sometimes also called Build denotes the smallest change possible (that is still tracked). Most of the time this is used when something was fixed (so no breaking or semi-breaking changes). In some project this is also used as a build counter and increased with every build.

This versioning scheme, when consequently used, dictates that the version is changed each time when something changes. This usually means that something gets merged into the main branch of the repository.

As you can imagine keeping track of that manually can be a tedious task, but thankfully we can automate this using GitLab CI:

GitLab CI/CD Integration

To automate the semantic versioning based on git commit messages there is a handy bundle of JavaScript tools that can handle that for us: semantic-release. This works, of course, regardless of what language you’re using for your software as this runs only as part of the CI pipeline.

The first thing you need is a .releaserc.json file in the root of your repository that contains alls the configuration for the semantic-release package. An example looks like this:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
{
  "branches": [
    "main"
  ],
  "plugins": [
    ["@semantic-release/commit-analyzer", {
      "preset": "conventionalcommits",
      "presetConfig": {
        "types": [
          {"type": "feat", "section": "Features"},
          {"type": "fix", "section": "Bug Fixes"},
          {"type": "chore", "hidden": true},
          {"type": "docs", "hidden": true},
          {"type": "style", "hidden": true},
          {"type": "refactor", "hidden": true},
          {"type": "perf", "hidden": true},
          {"type": "test", "hidden": true}
        ],
        "issueUrlFormat": "https://gitlab.com/mobiuscode/semantic-versioning-demo/-/issues/"
      }
    }],
    "@semantic-release/release-notes-generator",
    ["@semantic-release/gitlab", {
      "gitlabUrl": "https://gitlab.com/mobiuscode/semantic-versioning-demo"
    }]
  ]
}

This configures the basic stuff like where your repository resides but also what commit message prefixes will lead to changes in what part of the version. In this example we have mostly only two prefixes that matter:

Prefix Explanation Example
feat A new feature was implemented as part of the commit,
so the Minor part of the version will be increased once
this is merged to the main branch
feat: add button to let user shuffle
their playlist
fix A bug was fixed, so the Patch part of the version will be
increased once this is merged to the main branch
fix: fix a bug that causes the user to not
be properly informed when a new album
is released

Along with that I also want to mention the current best practice of always writing commit messages in imperative form. This comes from the fact that a commit is actually just a diff between two states of the repository - so stating what it does in imperative form describes what the computer does when this commit is applied. It also makes sense when you read the git log: every entry tells you in natural language what is actually changed as part of that commit, and not what was changed, as that very commit is the actual change (so no past-tense!).

But back to our semantic versioning set-up. Now that we have the .releaserc.json, we also need to change our .gitlab-ci.yml to run the semantic versioning job once something was merged to the main branch. This can be done by adding the following step:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
[...]

stages:
  [...]
  - release

[...]

semantic-versioning:
  image: node:lts
  stage: release
  needs:
    - job: compile-linux-amd64
  variables:
    GITLAB_TOKEN: $GITLAB_TOKEN_SEMANTIC_VERSIONING
  script:
    - npx -y
      -p @semantic-release/commit-analyzer@9.0.2
      -p @semantic-release/git@10.0.1
      -p @semantic-release/gitlab@9.5.0
      -p @semantic-release/release-notes-generator@10.0.3
      -p @semantic-release/exec@6.0.3
      -p @semantic-release/changelog@6.0.2
      -p conventional-changelog-conventionalcommits@5.0.0
      -p semantic-release@19.0.5
      semantic-release
  only:
    - main

Once this is done, the only thing missing is to set up the GITLAB_TOKEN_SEMANTIC_VERSIONING environment variable for the CI pipeline. This is, as the name suggests, a GitLab access token which is needed so that the new tag can be automatically pushed.

The easiest (and most secure way) of obtaining this token is to simply create a project specific access token in GitLab (under Settings > Access Tokens) with the following scopes: api, read_api, read_repository, write_repository. Then just set it in the CI/CD Variables (Settings > CI/CD > Variables):

set ci variables

Once all of that is set up, the only thing you need is to open a merge request and get it merged. Make sure that the commit message that is added to the main branch matches one of the prefixes (feature: or fix:), then the semantic versioning should come to life and create a new release:

releases link

releases

Note that the merge commit message will be automatically added to the respective section (without the prefix).

You can the full example project here: Semantic Versioning Demo on GitLab

This post is licensed under CC BY-NC-SA by the author.