Skip to main content
  1. Learn
  2. Software development
  3. Posts
  4. Git commit messages for the bold and the daring

Git commit messages for the bold and the daring

PostsSoftware development
Backlog Staff

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.

commit.template setting check

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:

– committing with a Git commit.template –

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 commitpushmerge, 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.

– .git/hooks before and after husky install –

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:

– committing with husky and commitlint –

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 huskyCommitizen 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.

– committing with Commitizen and cz-conventional-changelog adapter –

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.

– committing with Commitizen and @commitlint/prompt adapter –

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.


Keywords

Related

Subscribe to our newsletter

Learn with Nulab to bring your best ideas to life