GIT - Semantic versioning and conventional commits
Automatic versioning of your software projects with Gitlab CI/CD Pipelines, Conventional Commit Messages and Semantic Versioning.
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
- When to create a Commit
- Semantic Releases
- Conventional Commits
- Versioning in CI/CD Pipelines
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
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.
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
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
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
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
After checking in the code, the CI/CD pipeline was executed and the bot automatically adjusted the files (“CHANGELOG.md”, “package.json”, “configuration.json”).