GIT - Semantic versioning and conventional commits

Automatic versioning of your software projects with Gitlab CI/CD Pipelines, Conventional Commit Messages and Semantic Versioning.

GIT - Semantic versioning and conventional commits

GIT is an open source versioning tool and today belongs in every computer scientist’s toolbox. Today’s blog post is about automatic versioning of software projects.

Imagine you create a git commit with the message: “fix: fix issue in login form”
Your CICD pipeline automatically recognizes that it is a bug fix (“fix:”) and therefore increases the software version from 1.3.5 to 1.3.6 and creates a release in the GIT repository accordingly. An entry is automatically made in the changelog. Everything is correctly versioned. Today’s blog post is all about this automatism.
opensight.ch – roman huesler

If you are looking for general GIT training, I can recommend Mosh Hammedani’s online course. Today’s blog post is not about the explanation and operation of the GIT tool, but more about the best practices and design in relation to the commit messages and the version tags of a GIT repository.

A large part of the community relies on “conventional commits” and “semantic releases” for versioning. This results in the following advantages, which we will now discuss in more detail:

  • Automatic generation of CHANGELOGs
  • Automatic generation of the next semantic version number
    (based on commit type “fix”, “feat”, “chore”)
  • Teammates instantly recognize the change type (“fix”, “feat”, “docs”, etc)
  • Automatic build triggering and better integration with CICD pipelines
  • Better understanding of the version history and thus easier collaboration

Contents

Example: Bad Version History

A look at my older GIT repositories and commits made me take a closer look at the standards and best practices. A comprehensible version history / comprehensible commit messages serve for better understanding, traceability and facilitate cooperation with other developers.
opensight.ch – roman hüsler
Image – Bad Practice in GIT Version History

To further demonstrate what today’s blog post is about, here is an example of a bad GIT version history. The commit messages give little information about what has changed in each case.

When to create a commit

When should a commit be created at all?
A commit should be created whenever a unit of work is complete. For example, integrating a new feature, updating the documentation, fixing a bug. Also, different changes that are not directly related to each other should not be combined in one commit.

In the picture (above) you can see two commit messages:

  • added google maps feature
  • bugfix in google maps feature

These actions are part of the same “unit of work” and should therefore be squashed together.

Semantic Releases

# Creation of an annotated tag
git tag -a v2.0.0 -m "Release v2.0.0 (2021-12-12)"

With “Annotated GIT Tags” you can tag your commits in GIT and provide them with a message. A Semantic Release consists of an annotated tag whose formatting has been defined according to the Semantic Versioning Specification. The message usually also mentions all changes since the last release.

Image – Example Semantic Release of GIT Repository “semantic-release/semantic-release”
Image - semantic release version format

A semantic version consists of at least 3 components:

  • Major Release
    If a “breaking change” is introduced, the major release number must be increased
  • Minor Release
    New features have been introduced, which are backwards compatible (no “breaking changes”)
  • Patch Release
    Bugfixes (no “breaking changes”)

So let’s create a semantic release as an example: We currently have version “1.7.45” and have made “breaking changes”. So we have to create a new “major release” for the next version (2.0.0).

First, we generate a list of all commits made since the last release:

git log --pretty=format:%s v1.7.45...HEAD --no-merges
Image – Changes / Commits from GIT Repository since last release

We now copy the list of changes since the last release (picture above) and create a new release accordingly.

git tag -a v2.0.0

We note all changes in the tag message. In addition, we enter the same changes in the CHANGELOG.md file.

Release v2.0.0 (2021-12-12)

- feat!: upgrade to node 17
- fix: fix the bug with logfile generation

Everything we have now done manually – calculating the next version number, formatting the annotated GIT tag, keeping the changelog – all this can also be done automatically (automatic versioning) using semantic release. This is described in more detail in the “Versioning in CI/CD Pipelines” chapter.

Conventional Commits

Image – GIT Version History formatted according to “conventional commit

In order to make the GIT version history more understandable, the community has agreed on a standard (“conventional commits”), which can be found in many repositories today. Automatic versioning of the software projects is made possible by the fact that the type of change (patch, minor release, major release) can be derived directly from the commit message.

We only briefly describe the formatting of a conventional commit here. Check out the details on the home page (www.conventionalcommits.org).

Format

<type>[optional scope]: <description>

[optional body]

[optional footer(s)]

Example

feat(api)!: send confirmation email to the customer

BREAKING CHANGE: send an email to customer after order

Reviewed-by: Z
Refs: #123

Header
The commit header first describes the type of change: fix, feat, chore, docs, refactor, perf.
In addition, a brief description of the changes made.

Message Body
Since this is a “breaking change” in this case, an “!” attached. In addition, the message says “BREAKING CHANGE”. This later helps the “semantic version” automatism to recognize that it is a major release.

Versioning in CI/CD Pipelines

Now we want to implement automatic versioning for a Node.JS project in a CI/CD pipeline on Gitlab. The idea is that we push a commit (conventional commit) to the Gitlab repository and through it. the CI/CD pipeline is initiated. As part of the CI/CD pipeline, the next version number of the software is generated. Some files are adjusted (package.json, Changelog.md) and automatically written to the GIT repository by the bot.

First we create an NPM project and install some dependencies:

npm init --yes
npm install --save-dev @semantic-release/changelog
npm install --save-dev @semantic-release/commit-analyzer
npm install --save-dev @semantic-release/exec
npm install --save-dev @semantic-release/git
npm install --save-dev @semantic-release/gitlab
npm install --save-dev @semantic-release/npm
npm install --save-dev @semantic-release/release-notes-generator

Then we edit the package.json and add the following lines:

"private": true,
"release": {
    "plugins": [
      "@semantic-release/commit-analyzer",
      "@semantic-release/release-notes-generator",
      "@semantic-release/changelog",
      "@semantic-release/npm",
      [
        "@semantic-release/exec",
        {
          "prepareCmd": "sed 's/\"app_version\".*/\"app_version\":\"${nextRelease.version}\"/' src/configuration.json > configuration.json"
        }
      ],
      [
        "@semantic-release/git",
        {
          "assets": [
            "package.json",
            "CHANGELOG.md",
            "configuration.json"
          ],
          "message": "chore(release): ${nextRelease.version} [skip ci]\n\n${nextRelease.notes}"
        }
      ],
      "@semantic-release/gitlab"
    ]
},
"branches": [
  "master"
]

These lines determine what happens when the Semantic Versioning Tool (“npx semantic-release”) is run later.
A brief explanation of all plugins used:

  • commit-analyzer
    The commit analyzer analyzes our commit message. This should be formatted according to the “Conventional Commit” specification. Based on the message, the tool then decides on a patch release, minor release, or major release for the next version.
  • release-notes-generator
    The Release Notes Generator collects all changes since the last commit (commit log)
  • changelog
    Writes the release notes (release-notes-generator) to a file CHANGELOG.md
  • npm
    Writes the next version number to the NPM project’s package.json file
  • exec
    Execute any command. In the example (above) we write the next version number with “sed” in the file “configuration.json”
  • git
    With the GIT plugin, the changes made can be committed to the GIT repository. So we commit the customized package.json, the generated CHANGELOG.md, and the configuration.json we just generated
  • gitlab
    The Gitlab plugin now pushes all changes to the Gitlab repository.

Note – Gitlab plugin
In order for the Gitlab plugin to work, the access token should be stored in the Gitlab repository. The token should have “api” and “write_repository” permission

Image - Token to access gitlab repository

Now we add the “.gitlab-ci.yml” file to the project, i.e. the definition of the Gitlab CI/CD pipeline.

stages:
  - release

semantic_release:
  image: node:latest
  stage: release
  only:
    - master
  script:
    - npm i
    - npx semantic-release
  artifacts:
    paths:
      - configuration.json

If we now make and commit a change in the Git repository, the CI/CD pipeline is automatically executed, which generates a new version number.

git add testfile.txt
git commit -m "fix: fix bug in semantic release"
git push origin master
Image – CHANGELOG.md
Bild – Gitlab Repository Release

After checking in the code, the CI/CD pipeline was executed and the bot automatically adjusted the files (“CHANGELOG.md”, “package.json”, “configuration.json”).

Image – Automatic adjustment of the version number and Gitlab release by semantic-release-bot