Introduction
As a programmer, you might develop an application, a library, a microservice, or a monorepo full of services and libraries or (heaven forbid!) a huge monolith. No matter the type of application you develop, if you “do it right” you will use some version control (90% of the time it will be with git).
The question is – how do you manage your version history? How do you report bug fixing, feature addition, a chore done on the workspace, or even a breaking change? More importantly, how easy it is to see that?
Conventional Commits is a standardized approach to version control that enhances clarity, consistency, and collaboration among developers. In this post, we'll understand what Conventional Commits are, explore how they work, and explain the three main benefits you gain by using them.
Table of Contents
What Do Git Messages Looks Like Without Standards?
The picture above illustrates a common git commit sequence. It has a singular clear line which is good but leaves a lot of unanswerable questions. What can we tell from the commit messages? What does fix path
mean? What path? In what component?
One cannot answer that just by looking at the commit history.
Nor can a machine do that. Remember that for later.
What Does It Look Like With Conventional Commit?
Comparatively, here's how commit messages look like according to the Conventional Commit standards:
By just looking at the first two pieces of information in each commit, we already learn so much. Going from bottom to top, we can see that a feature was added to the fab
component, fixed something in typography, added a feature to the date-picker, did some chore in the docs
, another feature related to accordion item
, and so on.
Wouldn't you agree that our commmits are now clearer and more descriptive about changes in each push?
But developers don’t usually spend a lot of time looking at commit messages, right? Let’s see the real power of Conventional Commits.
The Benefits of Using Conventional Commits**
There are three main benefits to conventional commits: auto-generated changelogs, auto-versioning, and improved team communication.
1. Auto-Generated Changelog
There are many tools around that can generate a change log according to conventional commits. A changelog looks like this:
Did you notice how Conventional Commit standard already turns regular commmits into an easy-to-read change log? It even splits it into versions with dates to track down any new feature and bug fixes.
How can it know the version? This is the next benefit.
2. Auto Versioning According to SemVer
The Conventional Commit standard has the following structure:
{type}({scope}): {description}
The type is what kind of change was done. There are 3 main types: features, fixes, or chores.
The scope refers to the thing being impacted by this change. This can be a component, a library, an app, or a service.
The description describes more specifically what was changed.
That’s the gist of it. I’m not going to bore you with the full specifications, but I suggest reading it. It’s short and makes sense.
So, how does this help us with versioning?
SemVer is a way of versioning our software. The version is comprised of 3 digits: X.Y.Z
.
X is called Major
, Y is called Minor
, and Z is called Patch
.
When you do a breaking change, you raise a major version. That means the interface of this scope changed.
When you add a new feature (without breaking existing ones), you raise a minor version.
When you fix some bug without a feature and without breaking anything, you raise a patch version.
See where we are getting at?
Now a machine can read our commits and decide what version our scope should receive.
And you don’t have to do it yourself. Because Conventional Commits is a standard, many tools exist that provide auto changelog, auto versioning, and usually both.
One such tool is [release-please](https://github.com/googleapis/release-please)
, which also has a handy [github action](https://github.com/google-github-actions/release-please-action)
.
Encouraging Better Communication in the Team
In addition to commit messages being more readable, defining the structure gives us something else. It gives the commit message context. Even if the description is meh
, because the type of the commit and its scope are well understood – the reader can at least anticipate what’s coming.
Take a look at this example:
Here is an attempt at standardisation. Still, a human looking at it cannot understand what type of change was done (is this a fix or a feature?) nor to what component. A change log cannot be inferred from this either.
If we change this a bit, it will look like this:
This isn’t much better, because the description isn't descriptive.
Then again, if one has to write the commit with fix(button): {description}
it is less likely this person would write such a description. It is more likely a person would describe the fix
or feat
done on the button
.
As usual, having some standards helps people do things better. It’s somewhat like the broken window theory.
Try it. See what happens.
Example From Production: Vivid’s Commit Message Standard
We are using Conventional Commits in Vonage's design system, Vivid.
It helps us generate our changelog and release log and auto-determine the release version.
In addition, our commit messages have been much better ever since.
But our conventional commit enforcement is not on each and every commit. During development, a developer can add as many commits as one likes with any message.
When someone creates a pull request (PR), though, we enforce a Conventional Commit on the pull request’s title:
A mandatory github action that verifies our PR title follows Conventional Commits
We’ve added another standard that helps us connect the commit to a JIRA ticket. Every PR title ends with (VIV-XXX) where XXX is the issue number. For example:
fix(disabled): adds a consistent cursor to disabled elements (VIV-999)
Eventually, in our release log, the VIV-XXX
turns into a link to JIRA:
Eventually, when a PR is merged, the PR title is set as the commit. We use squash + merge
so all commit history is removed from the main
branch and only the Conventional Commit remains. It looks like this:
If a pull request has more than one change (more than one fix or feature), one can always add them in the merge commit’s comments. We do it like this:
The Conventional Commit tool (in our case, release-please) will consider these comments as if they were part of the message.
This way, we take a relatively lax
approach in regard to commit messages, making sure that the last step would be to actually describe the change.
Summary
Conventional commits give us two straightforward benefits: an auto-generated changelog and auto versioning.
There are many ways to achieve these goals.
One such tool is Beachball. This tool uses a CLI that generates a JSON file instead of reading git history. The big advantage here is that it is much easier to change the history if one made a mistake (changing git history can be quite messy…).
Aside from these two clear and immediate benefits, I claim it also helps encourage people to write better commit messages.
I might be wrong 🙂 Let me know on our Vonage Community Slack or send us a message on X, formerly known as Twitter.
Additional Resources
Design Systems: Lessons From Vivid