DEV Community

Héctor de Isidro
Héctor de Isidro

Posted on • Edited on

14 10

How good are Git Hooks?

I’ve tried them and the answer will shock you

As developers we use Git (or at least we should) an uncountable number of times
a day¹ so, because not all of us are machines yet, we can sometimes make a mistake as the result of the rush (i.e pushing a commit to master when it should go to dev). To try to cut down these kinds of slips we have Git Hooks’ help.

What are Git Hooks?

Git Hooks are extensionless executable scripts² you can place in your repository’s hooks folder (by default .git/hooks)³ to trigger actions at certain points in Git’s execution. In other words: we can automate what we always want to happen before/after using a git command.

There is a huge list of hook types⁴ you can attach⁵ scripts to but here we will see just a few.

hook name invoked by client-side server-side
applypatch-msg git am x
commit-msg git commit x
post-update git push x
pre-applypatch git am x
pre-commit git commit x
prepare-commit git commit x
pre-push git push x
pre-rebase git rebase x
pre-receive git push x
update git push x

Tip: they can always be bypassed adding the--no-verify param

I hope the code snippets attached below are self-explanatory.

Pre-commit

#!/bin/sh
MASTER='refs/heads/master'
if git symbolic-ref HEAD | grep $MASTER >/dev/null; then
exec < /dev/tty
while true; do
read -p "You're about to commit to master, want some bacon? [y|n]" yn
case $yn in
[Yy]* ) exit 0;; # tells git to continue normally
[Nn]* ) exit 1;; # tells git to abort operation
esac
done
fi
exit 0 # everything is ok
view raw pre-commit hosted with ❤ by GitHub

Commit-msg

#!/bin/sh
COMMIT_MSG_FILE=$1
SEVEN_DIRTY_WORDS='shit|piss|fuck|cunt|cocksucker|motherfucker|tits' # http://en.wikipedia.org/wiki/Seven_dirty_words
# people who didn't use this hook -> http://www.commitlogsfromlastnight.com/
if grep -qE $seven_dirty_words COMMIT_MSG_FILE; then
echo "Okay, you really need to chill out"
exit 1 # tells git you will try again later
fi
exit 0 # life is beautiful
view raw commit-msg hosted with ❤ by GitHub

Pre-push

#!/bin/sh
KEYWORD='DEBUG'
matches=$(git diff-index --patch HEAD | grep '^+' | grep -Pi $KEYWORD)
if [ ! -z "$matches" ]
then
exec < /dev/tty
while true; do
read -p "We may have among us the guy who pushed his Amazon S3 keys to a public repository once, continue anyway? [y|n]" yn
case $yn in
[Yy]* ) exit 0;; # tells git life is risky
[Nn]* ) exit 1;; # tells git to abort a possible disaster
esac
done
fi
exit 0 # another job well done
view raw pre-push hosted with ❤ by GitHub

As a good practice we could run our tests at this point as well 😉

Sharing hooks with our lovely team

As the default hooks folder belongs to .git folder which isn’t versioned we can use one of these strategies depending on our Git version⁶ to share hooks among our team members:

  • version 2.9 or greater:
# as easy as set-up using git
git config core.hooksPath [project-hooks-folder]

Tip: you can apply this configuration to all current (and future) repositories adding the --global param

  • earlier version than 2.9:
# use symlinking
# run these commands from project's root
# remove old one hooks
rm -rf .git/hooks/*
# create the symlinks; remember to re-run it every time you add/remove hooks
find [project-hooks-folder] -type f -exec ln -sf ../../{} .git/hooks/ \;

Said that, you can use this script I’ve created for lazy people like me:

#!/bin/sh
if [ -z "$1" ]; then
echo "Usage: $0 [project-hooks-folder]"
exit 1
fi
mayor=$(git --version | grep -o '[0-9.]*' | awk -F \. {'print $1'})
minor=$(git --version | grep -o '[0-9.]*' | awk -F \. {'print $2'})
if [ $mayor -eq "2" ] && [ $minor -lt "9" ]; then
rm -rf .git/hooks/*
find $1 -type f -exec ln -sf ../../{} .git/hooks/ \;
else
git config core.hooksPath $1
fi
echo "Done."

Another additional benefit of using a different folder than the default one is that we can keep them updated

Introducing Git Hooks⁷ repository

In order to be able to run multiple hooks within each type avoiding the creation of a huge and cumbersome single script loaded of comments, I have created a public repository which offers a new structure to store our hooks: each type of hook (i.e. pre-commit) launches the scripts we have stored in its corresponding subfolder (i.e /pre-commit-hooks).

To disable or enable a hook you can use the git-hooks-state.sh script (which basically is a chmod alias that changes their executable permission depending on the new state indicated)

I wish I didn’t have to say it but PRs are very welcome.

PS: you will have a lot more spare time to configure your hooks if you start using LolaMarket to shop for groceries 😄

This article was originally published on Medium


[1] Remember, commit early and often.
[2] Use chmod +x script.sh from terminal to make a script executable
[3] Every Git repository has a hooks folder which contains some disabled sample hook files but IMHO they are pretty useless 🔥. You can checkout the latest version here
[4] They are usually categorized into two main types: client side and server side
[5] It supports any scripting language that your system knows how to execute
[6] Use git --version to find out your current version of Git
[7] Great naming, isn’t it? 😞


External links 👀

Billboard image

The Next Generation Developer Platform

Coherence is the first Platform-as-a-Service you can control. Unlike "black-box" platforms that are opinionated about the infra you can deploy, Coherence is powered by CNC, the open-source IaC framework, which offers limitless customization.

Learn more

Top comments (0)

A Workflow Copilot. Tailored to You.

Pieces.app image

Our desktop app, with its intelligent copilot, streamlines coding by generating snippets, extracting code from screenshots, and accelerating problem-solving.

Read the docs