deaddabe

Automating Git commit messages with the prepare-commit-msg hook

Some commit messages can be automated. For example, I usually create a commit each time I am adding a new blog entry like this one. Instead of copy-pasting the article name in the commit message, we can detect that and fill the commit message automatically. ✨

Previous workflow

Let's have a look at the history of commits for this blog at some point in the past:

introduce scripts/new_image.py
new article: 2021-10-06_sending-templated-emails-using-python-and-msmtp.rst
new article: 2021-09-29_git-pre-commit-hook-for-rust-projects.rst
new article: 2021-09-20_status-update-september-2021.rst
new_status_update: add tags
tasks: rename livereload to live
new article: 2021-08-18_status-update-august-2021.rst

Indeed, each time I add an article, I type the same new article: prefix, and then yank and paste the name of the added file from the comment included in the commit message's default body.

In order to publish more often and with less friction, I want to automate this git commit part when adding a new article — which is the nominal use case for a blog.

Intermediate solution: use a script?

The fist naive solution was to write a scripts/commit_new_article.sh file, chmod +x it and hope that I will remember to use it instead of the mechanical git commit habit that I have built over time.

Here was the content of the script, for your eyes only:

#!/bin/sh

set -eu

P=$(git status --porcelain | awk '/^A/ { print $2; }')

if [ "$P" = "" ]
then
       >&2 echo "Error: please 'git add' first"
       exit 1
fi

NAME=$(basename "$P")

git commit -m"new article: $NAME"

But let's face it: I will never remember to use such script. What I will only remember is to use git add . && git commit to save my fresh new article into the repository.

Final solution: using the prepare-commit-msg hook

This hook is called before $EDITOR is fired up and asks you to edit the commit message. The strategy is the following: if we detect that only an article was added, then we pre-fill the commit message using a template.

This strategy is implemented in the following lines in the .git/hooks/prepare-commit-msg executable text file:

#!/bin/sh

COMMIT_MSG_FILE=$1
COMMIT_SOURCE=$2
SHA1=$3

P=$(git status --porcelain | awk '/A\s+content\/articles\// { print $2; }')
if [ "$P" != "" ]
then
        A=$(echo $P | \
                xargs basename -s .rst | \
                cut -d'_' -f2- | \
                awk '{ print "📝️ " $0; }'
        )

        if test -z "$COMMIT_SOURCE"
        then
                /usr/bin/perl -i.bak -pe "print \"$A\n\" if !\$first_line++" "$COMMIT_MSG_FILE"
        fi
fi

Note that I have changed the prefix from “new article” to just using an appropriate “📝️” emoji. This way it is easier for me to see in the log when I have added new articles. Also this blog is a pet project — and the sources are not public yet — so I can do whatever I please!

Demo

Using this very article, lets see how things work out!

$ git add .
$ git commit

And here is the result:

[master 892b1a1] 📝️ automating-git-commit-messages-with-the-prepare-commit-msg-hook
 1 file changed, 109 insertions(+)
 create mode 100644 content/articles/2022-01-21_automating-git-commit-messages-with-the-prepare-commit-msg-hook.rst

Perfect! I could use the real title inside of the file instead of its slug. This is left as an exercise for the attentive reader.

Now, whenever I am adding a new article, the commit message will be pre-written for me. When modifying scripts or other tooling — which should be a minor, part-part-time job — the commit message will be empty as expected.

One less excuse to not frequently write down my thoughts down there.