Skip to content
Jared Steiner

The key šŸ”‘ to easy commit messages

ā€” 4 min read

Are you bored of typing things? Do you enjoy automation? Do you always have to check on your current ticket's key for your commit message? Well have I got a present for you šŸŽ

Introducing Git Hooks

Hooks are programs you can place in a git repo's hooks directory to trigger actions at certain points in gitā€™s execution

You can write code to help you write code ( and if youā€™re writing tests then youā€™d be writing tests to help your code help your code šŸ¤Ŗ). By default your git hooks folder is in the $REPO/.git/hooks folder and due to this, isnā€™t apart of the repoā€™s version control so you can mess around with it without worrying about affecting your repo.

Weā€™re going to be looking at the prepare-commit-msg hook which you might have guessed helps you prepare you commit messages.

Commit Message

prepare-commit-msg

This hook is invoked by git-commit[1] right after preparing the default log message, and before the editor is started.

So this is going to allow us to modify the commit message before it gets set in our repo log.

Heres the present (script). Copy it and follow the two steps below to use it per repo)

1#!/bin/bash
2
3ISSUE_KEY=$(git branch 2>/dev/null| \
4 sed -nE "s/^\* ([A-Z]{2,}-[0-9]+).*$/\1 /p")
5echo "$ISSUE_KEY$(cat $1)" > $1

Put the script into the following file

$REPO/.git/hooks/prepare-commit-msg

And to make sure it will run properly weā€™ll just make sure it has the right permissions

chmod 755 prepare-commit-msg (thats rwxr-xr-x for those playing at home) (different from rawr XD)

Note: if the script exits with a non-zero exit status the git commit will abort

The hash bang #!/bin/bash at the top of the script can be subbed out for any other language you have with a respective script if you like it more. For example #!/usr/bin/env python followed by your favourite python script. Git hooks will happily oblige.


Keen to learn more about what we just covered? šŸ‘‡

Prepare Commit Message

prepare-commit-msg docs: https://git-scm.com/docs/githooks#_prepare_commit_msg

The way we actually modify the message only actually happens on the last line where we prepend the issue key to the pre-exisiting message. The first parameter given to the hook ($1) is the file where our message currently is. The next two parameters are optional and not needed for this. Echoing the Issue key to the beginning of the file will let us see whatever key we get earlier in our message

So weā€™re about to get all the repo branches and extract a Jira issue key if the current branch contains a valid Jira issues key from the branch name before we pop it in the commit message

Git Branch

Now onto the fun. Using /dev/null is like setting up email rules. I love them but misuse it and you can miss some important stuff. Trust me on this one now ā€¦. THROW CAUTION TO THE WIND ā€¦ Weā€™re going to yeet that stderr output straight into the void.

1$COMMAND 2>/dev/null

This is going to let you ignore those pesky errors. It feels good not to care right?? šŸ¤Øā€‹ this is technically not necessary but helps when testing out the script outside of the a git repo as you can avoid the fatal: not a git repository (or any of the parent directories): .git message youā€™ll get. Donā€™t worry, this will end up failing the script anyway due to the absence of branches so this error skipping isnā€™t so bad but feel free to remove the 2>/dev/null if youā€™re worried.

He sed she sed by the stream shore

Sed (Stream Editor) helps us weed out what we donā€™t need from the output we do. If you think it looks like gibberish donā€™t worry. It Is!! but so is latin and they still teach that so šŸ¤·ā€ā™‚ļø.

To break it down quickly it consists of the command sed the flags -nE and the regex expression s/^\* ([A-Z]{2,}-[0-9]+).*$/\1 /p

Flags

-n ā€œsuppress automatic printing of pattern spaceā€ Donā€™t print anything out at the end

-E ā€œuse extended regular expressionsā€ I wanna use that fancy regex

Regex

Basic format of replacement regex is s/find/replace/

^\* The current checked out branch in git is highlighted by an asterisks and space at the beginning of the line. For example * ABC-123-my-special-feature

[A-Z]{2,} Two or more capitals for the project key (Constraint set by Jira)

-[0-9]+ One or more digits for the issue number prefixed with a dash to seperate the key and number

( ) brackets to capture them all (https://www.youtube.com/watch?v=wrCUQuJsDYI)

.*$ match anything else to the end of the line

We need to match the entire line so that the replace removes everything we donā€™t want (like the branch description after the key and number)

\1 back reference to the stuff in the brackets (i choose you CAPTURE GROUP)

p this tells us to print out each line that matches the find portion of our sed

To put all that together

We want to (by default) suppress output to ignore other branches. Sed will run on each line of stdin which will be every branch available locally (which can get pretty big sometimes) so we donā€™t want it to printout out anything unless it matches exactly what we want. We want to run it with extended regex to we can use the fancy {2,} which is a nice way of saying two or more but wasnā€™t introduced initially.

We match our right branch which looks like * ABC-12345-branch-info-and-stuff on the entire line but the brackets let us match on just the part we want ABC-12345. The replace section consists of \1 which puts the contents of the first capture group ABC-12345 as well as a static space. Since we found an exact match on this line the p at the end of the regex will let us output it! If we donā€™t find any matches, no output will come out at all.

This allows us to see the $ISSUE_KEY in the echo as optional if there are not matches leaving so side effects if no branch is found.


Fun Fact! The echo in the last line opens up a sub-shell to cat the contents of the file with the $( $COMMAND ) syntax because of how I/O is handled. This can be done using inline editing in sed but I find this is a little easier to understand.

Imagine a file in your home directory called all_my_secrets.txt that had your diary in it šŸ¤«

Can you guess what would happen if you were to run the following command

1cat todays_new_secret.txt all_my_secrets.txt > all_my_secrets.txt

If you guessed that it would prepend todays_new_secrets.txt to all_my_secrets.txt, I wouldnā€™t blame you. Unfortunately for you though, youā€™ve just erased all of your secrets in your diary except for todays one šŸ˜±

The > all_my_secrets.txt part of your script will run before the output of the cat and as > is for overwriting files, a new file called all_my_secrets.txt is created nice and fresh destroying all old data


šŸ’¾ Join me next time on Jared turns two lines of code into an essay

Ā© 2021 by Jared Steiner. All rights reserved.
Theme by LekoArts