Automate and enforce conventional commits for .NET based projects

Yasser Shaikh
yasser.dev
Published in
4 min readJan 1, 2021

After being introduced to Conventional Commits by my colleague at Agoda, I have never gone back to my old ways of writing commit messages, and for good. In this post, I would like to walkthrough —

  1. What are Conventional Commits
  2. How to use Git Hooks to enforce it
  3. How to automate this enforcement for .NET based projects.

1. What are Conventional Commits?

The Conventional Commits specification is a lightweight convention on top of commit messages. It provides an easy set of rules for creating an explicit commit history; which makes it easier to write automated tools on top of.

For example, here is how a usual commit message (on a good day 😉) would look like…

git commit -m "Added logging for failed signups"
git commit -m "Fixed homepage image gallery not working"
git commit -m "Updated test cases"
git commit -m "Updated readme with new logging table"

Now compare it with the second option below…

git commit -m "feat(logging): added logs for failed signups"
git commit -m "fix(homepage): fixed image gallery"
git commit -m "test(homepage): updated tests"
git commit -m "docs(readme): added new logging table information"

In my opinion, the second one is more readable, follows a convention, a structure. This makes your commit messages readable not only to humans but also to bots, you can now automate releases (SemVer), automate changelogs, and more.

This specification is just a few years old and was inspired by Angular commit guidelines, and is now used by most popular projects such as Vue.js, deno, and electron.

I have been trying to follow this convention for quite some time now, but every now and then when I am not at my best I do end up having commit messages such as the one below 😆 😆 😆

Hence the need to ENFORCE it 😉

P.S: I will be using CC instead of Convention Commit hereafter.

2. Using Git Hooks to enforce Conventional Commits

Every git repository has a hidden .git/hooks folder, at its root level. These are what git calls “client-side hooks” (you can read here for server-side hooks). Inside this folder, you will find a bunch of example hooks that are disabled by default.

The hook that we are interested in is called commit-msg.This hook is fired after the user runs the git commit -m command. Meaning here we can set up a few rules, that every commit message has to satisfy, if not we can reject and throw an error message. We can enable this rule by simply renaming the file from commit-msg.sample to commit-msg.

To enforce CC there are two things we need to check —

  1. Commit message should follow CC specification (using regex)
  2. Commit message to be shorter than 50 characters. (optional)

My commit-msg file now looks like this —

#!/usr/bin/env bashif ! head -1 "$1" | grep -qE "^(feat|fix|ci|chore|docs|test|style|refactor)(\(.+?\))?: .{1,}$"; then
echo "Aborting commit. Your commit message is invalid." >&2
exit 1
fi
if ! head -1 "$1" | grep -qE "^.{1,50}$"; then
echo "Aborting commit. Your commit message is too long." >&2
exit 1
fi

Here is the regex being tested —

And now when I write a commit message that violates CC rules, voila! 🎉

Our hook validates against our regex and rejects the commit, and returns our configured error message —

But…(there is always a but🙄)

Since changes in commit-msg file is not part of my git repository (.git directory isn't versioned) and only present on my local, anyone who clones my repository and does not follow the same steps as I did, can still continue to commit with his own commit message conventions. Bummer right? 🤦‍♂️

This can be easily be resolved if you are using Github Enterprise or any Git provider that allows server-side hooks. However, Github.com (where my project lies) does not support server-side hooks (to the best of my knowledge).

3. How to automate this enforcement for .NET based projects.

So to automate this for .NET based projects, I committed this commit-msg file to my repository (outside the .git folder, can be anywhere) and then using the MS-Build Copy Task feature I copy the file to the .git/hooks folder.

<Target Name="CopyCustomContent" AfterTargets="AfterBuild">
<ItemGroup>
<_CustomFiles Include="..\automation\commit-msg" />
</ItemGroup>
<Copy SourceFiles="@(_CustomFiles)" DestinationFolder="./../.git/hooks" />
</Target>

This ensures that whenever anyone does his first build, our hook gets copied to the correct location and wham! Our commit convention is enforced.

What’s next?

As enforcing CC is a prerequisite to SemVer (Semantic Versioning), I had to set this up first. Next, I will be setting up Semantic Releases using Github Action (post on that to follow shortly 😊).

References

--

--

Yasser Shaikh
yasser.dev

Lead @ Agoda.com — Full stack Engineering. Gamer, Footballer, Bollywood Buff, Software Engineer, and @stackoverflow contributor. Mumbaikar.