Repeat after me! Commit history is intrinsic to quality documentation.
Let's be honest about the number of times we have been disappointed by a poorly maintained commit history of a project anytime we tried to make sense of it.
Good commit messages are intrinsic to meaningfully communicate changes to a repository to other team members as well as to our future selves. To be able to gloss over a commit history and get a gist of whatever progress have been made on a project over a period of time is invaluable. More interestingly the premise seems insinuatingly of even more value for an open source project where the commit history is public to the whole world.
Clearly, a convention could come to rescue, isn't it? This is what the project Conventional Commits is all about. It suggests to follow a format to maintain a consistent commit history which is easier to be read by both humans as well as machines.
The problem as I see however stays staring at our faces despite existence of such conventions is loose enforcement of such conventions in our development workflows. Its lame to expect committers to learn such conventions and always follow them. Its easier to loose track when it comes to following conventions. In this blog post I will explain how could we ensure conventional commits in a sbt project. Although this blog centers around sbt, the principles are universal.
After experimenting with a couple of conventional commit tools I came across an interesting npm tool called Commitizen that presents committer with questions, answers to which form commit messages that already follow the conventions out of the box. This way the cognitive load to learn and remember the conventions is taken off the committers while making it fun and easy to write commit messages.
Let's see how could we utilize Commitizen in linux environment for example. First we need to install commitizen
as global npm dependency as shown below:
Next we can install and configure commitizen adapter of our choice. You can read more about them here.
And we are good to start contributing. In order to make conventional commits we can now make use of commitizen
as shown below. This would present a series of questions, answers to which would create a conventional commit as shown in the picture above.
So far so good. There is however one problem still staring at our faces and that is that although we now have a tool to allow us to write conventional commits, we haven't found a way to enforce it in our development workflow i.e., there is no way to ensure that a committer would always use this tool to write a conventional commit message or even write a commit message using conventional commit convention.
This is where git hooks come into picture. In order to add commitizen
as part of our git workflow we can use prepare-commit-msg
git hook to invoke commitizen
as part of git commit
command itself. This will ensure that no commit goes without conventional commit convention. However for this to work we need to write custom prepare-commit-msg
and copy it to the .git/hooks
directory of our git repository. Say if we are maintaining all our are custom git hooks under root project directory called git-hooks
then before we start committing we must copy these hooks to .git/hooks
directory as aforementioned. There is one serious issue with this approach however and that is that everytime there are changes to the custom git hooks, committers must remember to copy them to .git/hooks
directory. Another alternative that dodges this issue would be to point core.githooks
git configuration to our custom git hooks directory itself instead of copying custom hooks to .git/hooks
directory as shown below:
Note: Ensure that your custom git hooks have execute permissions.
Now if you try executing git commit
command, you will be prompted with the same git cz
prompt to create conventional commits. One problem however still remains i.e., we are expecting committers to make manual changes. We could make one last improvement to this process (in a sbt project) by automating copying git hooks to .git/hooks
directory everytime we load sbt in a project.
To automate, we start by adding a plugin to our sbt project called sbt-git-hooks.
This plugin provides a sbt task called writeHooks
to automate copying custom git hooks from git-hooks
directory to .git/hooks
. However, for every change in custom hooks committer are expected to run this task. We can instead with the following changes to our build.sbt
invoke this task on every sbt load, ensuring we always have up-to-date custom git hook definitions in .git/hooks
:
Now every time you load sbt
in your project repository, your custom git hooks will automatically be copied to .git/hooks
directory.
Notes:
-
commitizen
is an external npm tool. Hence before you start committing make sure this dependency is already installed otherwise you would see following error message:
-
If you are maitaining your project repository as mono repo with a single
build.sbt
for all the sub-projects, the afore-mentioned scheme works with thesbt-git-hooks
plugin otherwise say if you havebuild.sbt
for every sub-project or you have multiple repositories for every project and you wish to maintain a commongit-hooks
repository to share amongst all those repositories then better gocore.gitHooks
git configuration way itself.