Skip to main content

A Case for Conventional Commits in Git

About a year ago, I started to use conventional commits for my personal projects and at work and haven’t regretted it so far. It led me to more clearly communicate what changes are about. My commit history is a lot more manageable as a result.

Conventional commits are a specification for formatting commit messages that’s lightweight and readable by both humans and machines. Even without using tools that can parse them, it’s still useful for developers alone.

Each commit message has a type classifying changes into features, fixes, tests, etc. It also has an optional scope for which part of the project is affected by the change and a short explanation of what has been done. Then comes the body with more details and the context in which it all happened. Finally, the footer would contain information such as the ticket number from the issue tracker and similar metadata.

The easiest way to get an idea of what it looks like is by seeing some examples. Here’s a minimal commit message satisfying the rules.

feat: add an endpoint to get contents recommendation

GET /contents/:topics/recommendation returns a JSON with some
recommendations for the user.

The following is a longer commit message with more details. It has a scope (api) and a footer with metadata.

test(api): assess test suite quality with mutation analysis

Add a new executable `mutation-analysis` that analyses the API's
test suite and returns a score that we can use to keep track of
how good our tests are at catching defects. This is a better
approach than code coverage.

It works by running the tests against mutants of the API
(slightly different versions of the code, e.g. a < could be
replaced with a <=) and counting the proportion of mutants that
the tests catch. This is relying on the assumption that most
mutants are invalid and should make the tests fail.

Issue: ABC-456
Approved-By: Tom Feron

You can learn more about how to format them by reading the specification. My goal for the rest of this piece is to share my experience with them and how they provided me with value.


Lightweight

As you can see, they’re very simple and hardly need any commitment. There’s nothing to set up and no dependencies on any tool. If you change your mind, you can simply stop using them.

The cost of starting with conventional commits is therefore virtually null.

It might take a little longer for you to write commit messages, but your teammates — or your future self — will thank you for it.

The only requirement is to decide on some commit types (or steal some) and scopes if you decide to use them. In the beginning, some improvisation is perfectly fine, though. Just use whatever feels sensible and adjust later.


Easier to Go Through

An important part of software development is communicating with other human beings rather than computers. This includes commenting on your code, keeping things simple (even at the cost of performance sometimes), and also making good commit messages.

Clear communication implies conveying meaning rapidly and accurately. With that goal in mind, standard formatting helps others quickly parse a commit message to get a sense of what the change is about and highlights the key information.

What I noticed after using conventional commits for a while was how it pushes me to give more explanation and context. When I would have previously written commit messages such as “Fix tests” — come on, who hasn’t? — I now would write commit messages like the following:

test(api): fix comment's JSON in endpoint test

The key `actorId` was deprecated a few months ago and removed
recently but the test suite hadn't been updated.

Note that I’m using test for test fixes here except if it implies a bug in the application code. fix should be reserved to bug fixes. Indeed, it makes debugging regressions and release tracking easier. Speaking of which …​


1 commit = 1 change

Something else I noticed when using conventional commits is how it forces me to commit one change at a time. When you have to start your commit with feat(task-runner): something, you think twice before committing some unrelated refactoring at the same time.

It’s sometimes acceptable to put some unrelated function renaming — or reformatting of some piece of code — in the same commit as a new feature you’re working on and mention it in the commit message’s body.

While this is sometimes true, there’s a right balance to find between too many commits and the noise that comes with it. You don’t necessarily want to bury changes into other commits, thus obfuscating the commit history.

You don’t need conventional commits to clearly split changes into separate commits, but, from my experience, it helps to get the necessary discipline to do so.


Release Tracking

In the days of Agile development, it’s hard to keep track of what has changed and when it did. This is particularly problematic when debugging issues in production. Conventional commits tell you the kind of change and its scope for every commit in a parsable way for both humans and machines.

Another situation when tracking releases is useful is for traffic analysis. Knowing that the spike in conversion of your website happened right after the deployment of 3d0e862dacf9655236bd73c31676170037e8748a, or even fix spacing, is not exactly helpful.

With conventional commits, you can filter out irrelevant commits based on the type and scope and only show the changes susceptible to be the root cause of what you can observe.


Changelog

In preparation for a sprint review, have you ever been asked (or asked your colleagues yourself), "What did we ship this week?" Your task manager or kanban board might not be very helpful in showing what has actually been merged. Some changes aren’t captured by such tools, and others are only parts of a Jira issue but still deliver value and are worth mentioning.

Once again, conventional commits can help in automatically generating a changelog that accurately reflects what’s actually happened.

There are some tools available to automate that process. For example, conventional-changelog is a set of tools designed for that very purpose.


Conclusion

Considering how easy it is to start using conventional commits and the almost nonexistent cost, you’d encourage anyone to at least give it a shot and see for themselves how it plays out.

There’s no need to automate anything in the beginning. Conventional commits are already useful when only used by people.