How to enforce conventional commit messages using git hooks

Posted on 14 Jun 2019
git open-source linux

Conventional git commit messages are not just nice to have but great to have. In fact, once you get to know them, you’ll start feeling that they are essential in any serious programming project. Consider the difference between following two commit messages for instance:

git commit -m "added social login feature for authentication using twitter"
git commit -m "feat(authentication): added social login using twitter"

The second one is not only more readable but it also follows standards which makes it easier to integrate with build tools such as Travis CI and Appveyor. Not just that, you can easily automate CHANGELOG generation when your git log looks like this:

> git log --oneline
61c8ca9 (HEAD -> master) fix: navbar not responsive on mobile
479c48b test: prepared test cases for user authentication
a992020 chore: moved to semantic versioning
b818120 fix: button click even handler firing twice
c6e9a97 fix: login page css
dfdc715 feat(authentication): added social login using twitter

All in all, I like conventional commit messages so much that I don’t want to keep it optional but make it the default way of life. How do I do it? The answer is simple: git hooks.

You don’t need to be a git ninja or expert to work with hooks. Suffice it to know that inside your special git repository folder (named .git), there are some other special folders:

logs
hooks
objects
refs

The one we are interested in is hooks. To get the hang of it, create a test project on your machine and initialize an empty git repository by running:

git init .

Now visit the .git folder just generated and navigate to the hooks folder. There will be a bunch of scripts with .sample extension (which means they are all disabled). The one we are interested in is commit-msg. This is the hook that fires just before your commit is made and thus allows you to reject the commit by throwing an error if the message isn’t in proper format.

Create a new script in this directory named commit-msg and add the below code (you’ll need python installed) to it using your favorite editor (mine is notepad++!):

#!/usr/bin/env python
import re, sys, os

def main():
	# example:
	# feat(apikey): added the ability to add api key to configuration
	pattern = r'(build|ci|docs|feat|fix|perf|refactor|style|test|chore|revert)(\([\w\-]+\))?:\s.*'
	filename = sys.argv[1]
	ss = open(filename, 'r').read()
	m = re.match(pattern, ss)
	if m == None: raise Exception("conventional commit validation failed")

if __name__ == "__main__":
	main()

Save the script and make it executable (by running chmod u+x commit-msg on linux, not required on windows). Now head back to your source code folder where you initialized the git repo and create a source file just for testing. Then, git add and try to commit just for testing using a non-conventional message. If all goes well, it should fail like this!

> git commit -m "added a new feature for xyz"
Traceback (most recent call last):
  File "C:/Users/prahlad/Documents/scripts/check_commit.py", line 22, in <module>
	main()
  File "C:/Users/prahlad/Documents/scripts/check_commit.py", line 19, in main
	if m == None: raise Exception("conventional commit validation failed")
Exception: conventional commit validation failed	
Now test with a valid commit message and it should work!

Once you practice this a few times and get a hang over it, you’ll then want to make this behavior default for whatever projects you start using git init in future, isn’t it? Sure, you can do that by creating a global git commit hook template but that will be a post for another day. First things first!

Edit

For those really interested in enforcing and automating this thing, I’ve just published a python package called enforce-git-message. All you need to do is install that python package and it’ll automatically copy the above script to your ~/.git-templates directory and also set the value for git config init.templatedir.

pip install enforce-git-message

References: