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
Now every time you load
sbt in your project repository, your custom git hooks will automatically be copied to
commitizenis 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.sbtfor all the sub-projects, the afore-mentioned scheme works with the
sbt-git-hooksplugin otherwise say if you have
build.sbtfor every sub-project or you have multiple repositories for every project and you wish to maintain a common
git-hooksrepository to share amongst all those repositories then better go
core.gitHooksgit configuration way itself.