Automatically enrich your commit messages
I commit often, lately even more often than I did maybe a month ago. For me there are just too many benefits of doing this.
- The smaller your commits are, the more obvious they’re, and the easier it’s to spot something odd.
- The earlier you commit again, the earlier you have another safe point where your code compiled and passed all tests. Like with TDD, working in such small incremental steps is a joy and relaxing.
- It leaves a clear trail on how I got from the earlier state to the new state of the software where it has the desired feature (or lack of a bug).
At the same time, I don’t want to think about project acronyms or ticket numbers while I’m writing code. But the team might have an agreement in place to add this information to each and every commit.
With a bit of research, I learned more about git hooks and now thanks to some bash scripting, it’s done in the background while I only type in the commit message as I did before. So how does it work?
First of all, have a look, your project has a folder called .git
and in there you’ll find another folder called hooks
.
$ cd github/java_playground/.git/hooks/
In my case, there were already plenty of .sample files in that folder, but all we care is one: prepare-commit-message. If the file doesn’t exist yet, simply create it. Open the file to modify it, use vim, VSC or whatever editor you use to edit scripts.
$ code prepare-commit-message
And then copy&paste the following
#!/bin/bash
FILE=$1
MESSAGE=$(cat $FILE)
TICKET=$(git rev-parse --abbrev-ref HEAD | grep -Eo '^(\w+/)?\w+[-_][0-9]+' | grep -Eo '\w+[-][0-9]+' | tr "[:lower:]" "[:upper:]")
if [[ $TICKET == "" || $MESSAGE == "Merge"* || "$MESSAGE" == "$TICKET"* ]];then
echo 'Branch name without ticket information, keeping git message as is.'
exit 0;
fi
echo "$TICKET $MESSAGE" > $FILE
Last but not least, double check that the file is executable. If you’re not sure, you can just set it again to be executable by running
$ chmod +x prepare-commit-message
Before finishing this blog post, let’s also have a closer look at the commands used so that we understand what the script is doing when it determines the ticket-number.
git rev-parse --abbrev-ref HEAD
returns for example: feature/ABC-1234-blog-post when I’m on this branch and run the command in my terminal.
We then pipe this value (by using | ) to the next command.
grep -Eo '^(\w+/)?\w+[-_][0-9]+'
For those who don’t know yet, the grep command prints lines matching a pattern. The flag -E allows the usage of extended regular expressions and -o means we only want to print the part of the line that matches the pattern and cut off the rest. Let’s look at the pattern a bit closer:
^(\w+/)?
the ^ means we start looking at the start of the line. We’re looking for a word (“w+”) which is followed by a slash (“/”). Since we wrap everything in braces “( )” and end with a ?, there can be 0 or 1 occurrences. A few examples that are matched by this part of the regexp:
feature/
my_feature/
lars_on_tubli_poiss/
feature_priority_4/
The 2nd part of our pattern was
\w+[-_][0-9]+
meaning that after our optional part, we continue with a word again which is followed either a - or _ and then numbers. Please be aware that \w+ also allows _ to be part of the word. So this pattern matches the following examples:
Lars-1234
LARS_ECKART-1234
LARS_ON_TUBLI_POISS_1234
LARS_ON_TUBLI_POISS-1234
but doesn’t match for
TLN-TARTU-1234
So by the end of the first grep command, we’ve left feature/B2CSC-1234.
This string we pipe again to another grep, again we try to only keep what matches an optional word followed by numbers, now we’ve left ABC-1234.
One last time we will modify this resulting string, by sending it to the tr command, which will translate or delete characters.
tr "[:lower:]" "[:upper:]"
As you might have already guessed, it takes lower cased characters and will replace them with their uppercase version.
The if statement in line 5 will take care of cases when we don’t want to modify the chosen commit message:
- no ticket information could be determined from branch name
- it’s a merge commit
- the commit message already starts with the found ticket info.
So there we go, I never have to think about Jira and ticket numbers after I created the branch, I can continue doing all my micro commits and they’re all prefixed with whatever ticket information my current branch has.
One downside is that this is repository-specific, so I have to copy it to each project where I want to make use of this. But it doesn’t have to be like this.
To use global git hooks, create yourself a folder for them and add the hook files you care about. In this case, for example
touch ~/.dotfiles/githooks/prepare-commit-message
and tell your global git configuration to know about it
[core]
hooksPath = ~/.dotfiles/githooks
Oh, and it doesn’t play well with release branches if they have a format like release/7.1.
Book recommendations
Small, Sharp Software Tools