Git commit messages for the bold and the daring
Backlog Staff
November 15, 2023
Good Git commit messages are like healthy eating habits: most people know they’re good for them, but few actually manage to follow through on a daily basis. Good Git commit messages take practice and discipline to get used to, but once you’re in the habit of writing them, you will learn to appreciate the many benefits that come along with them.
This article will discuss why proper Git commit messages are important, what kind of problems they solve, and how you can automate your client-side commit workflow to help contributors follow your guidelines.
If you are new to Git, here’s a great Git tutorial to get you started.
The benefits of good Git commit messages
Good commit guidelines provide more benefits than might first come to mind. These benefits are not just for yourself as a maintainer but for your entire team as well as your future team members. A good Git commit message guideline will:
- give context about the “what” and, most importantly, the “why” of your commits
- make working with your commit history easier and more systematic(i.e., readability, search-ability, debugging, investigating issues, etc.)
- help you spot commits that lack separation of concerns
- lower the entry level for new contributors by making the commit history consistent and easy to follow
- make you a more disciplined engineer and a more empathetic project maintainer
- help you generate change logs
Choosing the right commit guidelines
The developer community has plenty of great best practices in place when it comes to what makes a good Git commit message and what doesn’t. Ultimately, it is up to you to decide if they work for your project or if you’d rather define your own set of rules. Whatever you decide, just remember that choosing a set of guidelines and announcing them to the world won’t guarantee that contributors will actually abide by them. This is not to say that people are ill-intentioned, but sometimes people forget or are unaware that such guidelines even exist. If you want to make sure that what ends up in the repository is exactly how you want, then you might want to consider automation.
There are a couple of ways you can automate your commit workflow to better support your guidelines. Let’s look at a few of them and analyze what problems they solve, how restrictive they are, and what advantages and disadvantages they come with.
BTW, did you know there’s currently an ongoing effort by a few folks in the dev community to create a standardized specification for Git commit messages?
What is a Git commit template?
Git gives you a LOT of options out of the box to configure your Git environment. The one that’s particularly interesting for the scope of this article is commit.template(*).
commit.template
allows you to “specify the pathname of a file to use as the template for new commit messages“, which basically means that you can save your Git commit message template as a file in a project and then configure Git to use it every time you git commit
your changes.
Let’s look at how we can set this up and the benefits of using such a strategy.
If you’re unsure whether you already use a Git template in your project, try running git config --get commit.template
in your terminal. If no value is returned, then you’re good to go.
Setting up a commit message template
Start off by creating the template file in your project. Keep in mind that Git will pre-populate the editor you’ve configured for your commit messages (see Git’s core.editor
setting, which defaults to Vim) with the contents of this template. That content is what committers will see when prompted for the commit message at commit time. One example of such a template could look something like this:
# Hey there o/! # # We just wanted to let you know that we care a great deal about # making our Git history clean, maintainable and easy to access for # all our contributors. Commit messages are very important to us, # which is why we have a strict commit message policy in place. # Please use the following guidelines to format all your commit # messages: # # <type>(<scope>): <subject> # <BLANK LINE> # <body> # <BLANK LINE> # <footer> # # Please note that: # - The HEADER is a single line of max. 50 characters that # contains a succinct description of the change. It contains a # type, an optional scope, and a subject # + <type> describes the kind of change that this commit is # providing. Allowed types are: # * feat (feature) # * fix (bug fix) # * docs (documentation) # * style (formatting, missing semicolons, …) # * refactor # * test (when adding missing tests) # * chore (maintain) # + <scope> can be anything specifying the place of the commit # change # + <subject> is a very short description of the change, in # the following format: # * imperative, present tense: “change” not # “changed”/“changes” # * no capitalized first letter # * no dot (.) at the end # - The BODY should include the motivation for the change and # contrast this with previous behavior and must be phrased in # imperative present tense # - The FOOTER should contain any information about Breaking # Changes and is also the place to reference GitHub issues that # this commit closes # # Thank you <3
Your template’s path and name are up to you as long as you link the commit.template
value to the correct file. Some folks seem to prefer .gitmessage
for the name of the file, but really anything ranging from .git-message-template
to gitmessage.txt
will work.
Once you have the template in place, you’ll need to configure Git to use it
git config --local commit.template "path_to_template_file/filename"
and voilà! You’re ready to git commit
with style:
Considerations when using commit message templates
commit.template
shines the most in that it brings your commit formatting rules right in the context of editing the message. This saves contributors the hassle of changing context to figure out what the commit guidelines were.
On the downside, commit.template
heavily relies on customizing one’s local Git environment, which contributors may or may not do. Good documentation can help, but it will ultimately leave you at the odds of contributors following that documentation. Furthermore, you need to keep in mind that the template applies to commits done via git commit
ONLY. This means any changes committed using git commit -m
or via any other Git user interface or tool will not prompt the user with the commit message template.
The other important thing to remember is that commit.template
is not a means of enforcing a certain commit etiquette on contributors but rather acts as a friendly, informative reminder of what that etiquette is for the given project. The commit.template
setting alone will not abort a commit if the Git commit message ignores the guidelines and is, therefore, invalid.
If enforcing commit conventions is the kind of behavior you’re looking for, then Git hooks might be what you’re looking for.
If you want to take it one step further and get the ULTIMATE commit experience with commit.template
, consider spicing up your Git core.editor
editor, too. Harry Roberts wrote a great post about how to do that for VIM.
What are Git hooks?
Git hooks are custom scripts that Git executes when certain important actions occur (think commit
, push
, merge
, a.s.o.). Git supports two types of hooks: client-side and server-side hooks. For the scope of this article, we’ll focus only on the client-side ones.
Considerations when using Git hooks
The reason why client-side Git hooks are so powerful is that they can catch invalid commits and provide feedback about them before changes are pushed to the remote repository.
The disadvantage, however, is that they cannot be version-controlled. This is because they are stored in the .git/hooks
folder, and, per definition, the .git
folder is not subject to versioning. This not only means that your Git hooks can reside strictly on your local machine but that sharing them across a team is a bit of a more involved business.
One way around this is to create your Git hooks in a folder that can be versioned and then use custom scripts to ensure those hooks are installed in the .git
directory. Alternatively, you could set the Git core.hooksPath
to point to your custom hooks folder and have the hooks served directly from that folder.
The Angular project is an excellent example of a project that uses custom scripts to validate Git commit messages. I highly recommend checking it out and learning from their experience.
Husky and Git hooks
Another, probably more elegant, way in which we’ll go into more detail is husky. husky is an npm package that allows you to declaratively configure Git hooks either via a configuration file or using package.json
configuration and takes care of installing them for you in the .git/hooks
folder.
The beauty of husky is that it comes with zero setup and maintenance overhead for both maintainers and contributors. Maintainers have their life made easier because all hook configuration is centralized in one place and shared across the team with version control. No need for extra scripts to have the hooks locally installed or special instructions to get everyone set up, and no more relying on whether team members have their local Git environments configured in a specific way. As for contributors, they only have to install husky, and everything else will work out of the box.
// npm npm install --save-dev husky
// yarn yarn add husky --dev
That said, let’s have a look at how we can set up a Git commit-msg
hook with husky to enforce good commit messages.
Start by configuring husky and the hooks you want it to tap into. In this article, we’re using a .huskyrc
configuration file, but you could just as well use package.json
for your configuration.
{ "hooks": { "commit-msg": "node ./my_commit_validation_script.js" } }
With that in place, the next step is to figure out what script you want to run to validate the Git commit messages. I personally prefer using commitlint for its simple and declarative commit rules configuration, but you can also write your own custom script based on the needs of your project. Whichever you choose, the end workflow will look something like this:
Considerations when using husky
There are not many disadvantages I can think of for this approach. One thing that might be inconvenient when you use husky is the extra third-party dependency(ies) you are adding to your project, including possibly the dependency on npm itself if you are working on non-Javascript projects and are relying on different package managers. Another downside is that a Git commit message hook can only validate the message after the fact, which can introduce a considerable overhead in the feedback loop. If you want to ensure that contributors don’t have to wait until the hook passes or fails their commit but rather give them feedback as they write the commit message, then Commitizen might be a good tool of choice.
What is Commitizen?
Commitizen is an npm package that gives instant feedback on your Git commit message formatting and prompts you for the required fields at commit time.
Like husky, Commitizen is configuration-driven and incredibly easy to set up. Another great thing about it is its extensible and pluggable architecture. Rather than enforcing a certain commit message guideline, Commitizen acknowledges that each project has different requirements, making it possible for you to bring your own set of rules and plug them into its core through what is called adapters.
Setting up Commitizen
To get the complete Commitizen experience, all you need to do is make your repository Commitizen friendly, set up a commit script in package.json
{ "scripts": { "commit": "git-cz" } }
and run that script every time you want to commit your changes — npm run commit
or yarn commit
.
The most important thing you must remember about Commitizen is that the adapters, not Commitizen itself, define all the prompting, message formatting, and validation. Therefore, you need to pay extra attention when choosing the right adaptor for the job. For example, if you’re committing with cz-conventional-changelog
, you’ll notice that Git commit messages such as “feat:” will be considered valid due to some defaults the adapter is using.
On the other hand, if you’re committing with @commitlint/prompt
, you’ll notice that not only is this adapter much more restrictive, but you’ll get step-by-step guidance and validation of your input. At the end of the day, it all boils down to what your needs are as a maintainer and what kind of user experience you want to provide your contributors with.
Considerations when using Commitizen
The main downside of this approach is that one can easily bypass Commitizenby using any other means of committing changes other than the npm custom script, be it git commit
or other Git UI tools. The other thing is that — just like with using husky — Commitizen introduces extra third-party dependencies, including a dependency on the npm package manager for non-Javascript projects.
Despite all that, Commitizen can be the ‘cherry on top’ of the commit user experience cake. Focusing on things like keeping all validation and validation feedback within the commit context or auto-” magically” formatting the Git commit message for you provides a great user experience that none of the other alternatives we discussed in this article does. On top of that, Commitizen is a fantastic fit for Git hooks. If you care about both compliant Git commit messages AND contributor experience, using the two together is worth looking into.
One last thing
Remember that at the end of the day, the person who knows best what your project needs is you and your team. There is no one-size-fits-all solution that comes without trade-offs. The alternatives discussed in this article merely serve as examples of more established practices. Still, they are not an exhaustive list of ways to automate and enforce your Git commit message guidelines.
Another important thing you should remember is that all practices discussed here are client-side oriented and, therefore, won’t give you a 100% good commit guarantee. I personally see these as a way to enhance the contributor experience and to ensure that there’s some message validation in place on the client side before code gets pushed further to the remote repository. But ultimately, if you want absolute certainty that no random Git commit messages end up in your codebase, you’ll want to look into server-side solutions. But that’s a story for another time.
And on a more personal note, I realize that Git commit message guidelines are not for everyone. The Angular community’s best practices inspired me, and I feel I’ve become a much more disciplined committer since adopting them. I’ve lived the benefits of good commit messages to tell the tale 😉
If you want to learn more about the subject, check out the resources section below, and if you have any questions, you can ping me @CarmenPopoviciu on Twitter.
✧(◕‿◕✿)✧
If you’re still there
You can find a demo repository on Github that walks you through all the solutions described in this article.
Resources
- Derek Prior — Implementing a Strong Code-Review Culture
- Tim Pope — A Note About Git Commit Messages
- Mat Sumner — Better Commit Messages with a .gitmessage Template
- Caleb Hearth — 5 Useful Tips For A Better Commit Message
- Kent C. Dodds —Writing conventional commits with commitizen
- Kent C. Dodds — Committing a new feature with commitizen
- JetBrains commit message template plugin
This post was originally published on November 30, 2018, and updated most recently on November 15, 2023.