Many projects decide to standardize their commit messages with conventions, one way or the other. This practice isn’t new, but increasingly applied in the last few years. Most likely you’ve already encountered such commit messages in certain projects.
One of the first specifications that have come up belongs to the AngularJS project. The team created a detailed document which specifies the goals and way they’re supposed to commit.
These commit conventions are pretty popular, and some of you maybe reached them through Karma guidelines.
And yet, there are also different convention variations as of jQuery, JSHint, Ember, Angular (an enhanced version that’s inspired by the AngularJS commit specification) and even more variations:
It’s plain to see the variety of commit conventions above, which definitely constitutes a decent reason to standardize an official specification. Conventional Commits is such a specification, which in practice, simplifies the Angular conventions and lightly specifies the essentials of commit message conventions.
During this article, we’ll introduce the idea behind "Semantic Commits" and demonstrate concrete examples, using Git and the Angular commit conventions. For the record, we use them only to clarify the concept – which obviously means the version control tool and the specification is up to you.
Here we go! 👨🏻🏫
Let’s start by defining the term in general:
Semantic Commits are commit messages with human and machine readable meaning, which follow particular conventions
This means, it’s is merely guidelines for commit messages, so that:
Further to that, semantic commits might come in handy when we typically need to:
To wrap up, semantic commits are dedicated to achieving better readability, velocity and automation.
Having said that, it definitely makes sense that some of us might not accept these message conventions as readable or informative.
In case we also don’t need the additional benefits that arrive with them – it’s apparently senseless to enforce such a specification within the project.
Alright, it’s about time to understand how we practically follow the conventions.
Disclaimer: From this moment on, we’re going to refer the Angular commit message conventions and their benefits.
The Angular conventions demand to shape the commit message according to the following structure:
The diagram above illustrates to us that the commit message consists of three parts – header, body and footer.
Let’s elaborate on each part.
The header is a mandatory line that simply describes the purpose of the change (up to 100 characters).
Better yet, it consists of three parts in itself:
Practically, in terms of Git, it’s merely the first line of the commit message:
git commit -m "fix(core): remove deprecated and defunct wtf* apis"
We insert a single-line message which is separated by :
. The left partition is what we hypothetically named "prefix" – when fix
and core
(the affected package) are the type and scope respectively. On the other hand, the right partition obviously constitutes the subject.
Simply put, the above message meaning is – "This change fixes a bug that belongs to the Core package, by removing deprecated and defunct wtf* apis".
The body is optional lines that introduce the motivation behind the change or just describing slightly more detailed information.
Let’s take the recent example and add a body:
git commit -m "fix(core): remove deprecated and defunct wtf* apis" -m "These apis have been deprecated in v8, so they should stick around till v10, but since they are defunct we are removing them early so that they don't take up payload size."
Now we attach to the message a couple of sentences that explain the purpose in detail.
Notice the following:
-m
in order to concatenate paragraphs instead of simple linesNote: Although we could use other ways to break the message into lines – we’ll keep using multiple -m
in the next examples for the simplicity (and also to show a shell-agnostic solution).
The footer is optional lines that mention consequences which stems from the change – such as announcing a breaking change, linking closed issues, mentioning contributors and so on.
Here’s the recent commit message with a footer:
git commit -m "fix(core): remove deprecated and defunct wtf* apis" -m "These apis have been deprecated in v8, so they should stick around till v10, but since they are defunct we are removing them early so that they don't take up payload size." -m "PR Close #33949"
In this case, we plainly add a reference to the relevant pull request and nothing else.
To finish, let’s view the complete commit log:
As you might infer, this commit was actually made in the Angular repository.
On top of defining the commit message format, the Angular commit message conventions specify a list of useful types that cover various sorts of changes.
Before we begin, we should distinguish between two categories of types:
Development – sort of maintenance types which classify changes, intended the developers, that don’t actually affect the production code but rather the development environment and workflow internally
Production – sort of enhancement types which classify changes, intended the end users, that solely affect the production code
Now, let’s introduce and explain these handy types.
Note: The examples below are taken directly from the commit log of the Angular repository.
The build
type (formerly known as chore
) is used to identify development changes related to the build system (involving scripts, configurations or tools) and package dependencies.
Examples:
build
typeThe ci
type is used to identify development changes related to the continuous integration and deployment system – involving scripts, configurations or tools.
Examples:
ci
typeThe docs
type is used to identify documentation changes related to the project – whether intended externally for the end users (in case of a library) or internally for the developers.
Examples:
docs
typeThe feat
type is used to identify production changes related to new backward-compatible abilities or functionality.
Examples:
feat
typeThe fix
type is used to identify production changes related to backward-compatible bug fixes.
Examples:
fix
typeThe perf
type is used to identify production changes related to backward-compatible performance improvements.
Examples:
perf
typeThe refactor
type is used to identify development changes related to modifying the codebase, which neither adds a feature nor fixes a bug – such as removing redundant code, simplifying the code, renaming variables, etc.
Examples:
refactor
typeThe style
type is used to identify development changes related to styling the codebase, regardless of the meaning – such as indentations, semi-colons, quotes, trailing commas and so on.
Examples:
style
typeThe test
type is used to identify development changes related to tests – such as refactoring existing tests or adding new tests.
Examples:
test
typeNow that we’re familiar with the conventions – let’s see two ways to benefit from them.
Git provides us the power to browse the repository commit history – so we’re able to figure out what actually happened, who contributed and so on.
Let’s see how the conventions might ease up the browsing:
git log --oneline --grep "^feat|^fix|^perf"
We use the commit message type to filter out and so showing only the production changes (all of the messages that start with feat
, fix
or perf
).
Another example:
git log --oneline --grep "^feat" | wc -l
We just print the total amount of feat
changes.
The point is – the commit message format is very structured, what effectively allows us relying on that when scanning or filtering the commit history.
Namely, a better velocity! 💪🏻
The commit message format is either useful for automating steps of the release process.
In fact, this is possible due to tools like Standard Version and Semantic Release that strictly following the Semantic Versioning specification beside certain commit message conventions (Conventional Commits and Angular conventions respectively). The main difference between them is the approach, but let’s focus on Semantic Release.
So, based on the commit message (and especially the type) – Semantic Release is able to:
fix
causes to patch, feat
& perf
to minor, and obviously – breaking change to major)That’s pretty cool, right?
Ionic’s angular-toolkit project, for instance, integrates Semantic Release to automate the release process (hereby follows the Angular commit conventions):
As we notice, a release version was generated with the correct tag and notes – but the thing is, that was done automatically. 🤖
Let’s see a couple of stuff in order to make the most of semantic commits.
Attaching Emojis to the commit message might improve the readability even more, so that we can identify them pretty quickly and easily while browsing the commit history. 💯
Check out the following links:
Commitizen is a tool that enables to enforce a commit message format using the command line:
commitlint is a tool that guarantees which the commit message format is aligned with the conventions:
If you’d like to use a customisable VS Code extension, then the following might interest you:
Honestly, it was developed by myself. 😊
We introduced today the term of "Semantic Commits" and explained the structure of such a message, through concrete examples that follow the Angular commit message conventions.
Recapping the top points:
And finally, whether you decide to adopt such conventions or not – you might keep encountering them occasionally, so just keep in mind the points above. 😉