Git hooks

Hey everyone ! Today, I will introduce you what’s git hooks. In this tutorial, you’ll need to have some knowledge of git to understand well what we are doing. If it’s the first time you heard this word, I will like to know where you’ve been living ! But seriously, if you want to learn more about git, I recommend you to read this e-book

After this few weeks of reading, let’s start serious stuff. git gives you the possibility to personalize his behaviour for some actions coding some scripts. It will give you to personalize the versioning of your project. If you watched the previous link, you should see a part named “git hook”, I will keep this structure adding a little example :)

You’ll have to install these hooks in the folder localized in your project at this path : .git/hooks. If you’re curious (I’m sure you are), you’ll see some sample. They give you a little idea how to develop them.

You can find two kind of hooks :

  • Client hooks
  • Server hook

We’ll watch brievely server’s hook, but the example will be on a client hook. You have to know these hooks are coded in bash, but you can use every script language, the only constraint is to have installed the execution environment of this language or you’re script will never run. So that’s mean, if you developed a client hook for a team, all the team needs to install the execution environment associated, (so that’s can be annoying) But well, let’s see what can we do with git hooks:

Server’s hook

pre-receive and post-receive hook

These hook are used when a client push some code. We can use it to notify connected clients or contributors project about a new version of the project.

update hook

This hook is used everytime a branch is updated. So the difference between the previous hook is this hook will run n branches time instead of one for the preivous one.

That’s all for the server’s hook ! :)

Client’s hook

This hook will be used during clients action. There’s 4 different kinds :

  • commit hook
  • mail hook
  • merge and rebase hook

Commit hook

With these hooks, you can configure everything around the commit, as well the message as the files commited, etc…

pre-commit hook

git will use when you’ll call the command line “git commit”. You can use it to parse some files modified in the git index. If the script return 0, the commit is accepted, not if not. You can still avoid to use a hook using the option –no-verify.

prepare-commit-msg hook

This hook will give you the possibility to write the default message.

commit-msg hook

This hook will give you the ability to refuse a commit parsing the commit message

post-commit hook

this hook will be used after the commit.

So all these hooks will be executed in this order : pre-commit -> prepare-commit-msg -> commit-msg -> post-commit. If you’re managing a team and you want to use some hook, you have to know that every developer will have to install your hook in his .git folder, so you can’t force them to use it (snif, snif).

IT’S TIME TO CODE NOW

Now, I’ll ask you to imagine a world where you’re mananging a team of 4 years old child not able to follow some rules about commit message. So you’ll decide to write a git hook to force them to write it perfectly. We’ll use the karma convention. (big up Hubert Sablonnière and Cyril Lakech)

  • First question : Which hook you’ll use?
  • Second question: How to implement it?

For the first one, the response will be simple if you read this article, we’ll use the “commit-msg” hook.

For the second one, we will watch the sample in the .git folder at this path “.git/hooks/commit-msg.sample” :

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#!/bin/sh
#
# An example hook script to check the commit log message.
# Called by "git commit" with one argument, the name of the file
# that has the commit message.  The hook should exit with non-zero
# status after issuing an appropriate message if it wants to stop the
# commit.  The hook is allowed to edit the commit message file.
#
# To enable this hook, rename this file to "commit-msg".

# Uncomment the below to add a Signed-off-by line to the message.
# Doing this in a hook is a bad idea in general, but the prepare-commit-msg
# hook is more suited to it.
#
# SOB=$(git var GIT_AUTHOR_IDENT | sed -n 's/^\\(.*>\\).*$/Signed-off-by: \\1/p')
# grep -qs "^$SOB" "$1" || echo "$SOB" >> "$1"

# This example catches duplicate Signed-off-by lines.

test "" = "$(grep '^Signed-off-by: ' "$1" |
	 sort | uniq -c | sed -e '/^[ 	]*1[ 	]/d')" || {
	echo >&2 Duplicate Signed-off-by lines.
	exit 1
}

The code is not really important here, but comments are. We’ll learn that this script take one parameter (the file containing the commit message) and has to return 0 if the message is accepted.

So we’ll create a parser which analize the commit file and detect if the message is not well formated :

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
#!/bin/sh
# Git commit-msg hook which will reject the commit if the commit message is not
# formated like :
# [feat|refactor|test|merge|docs|fix](<scope>): <subject>
# <new line>
# <body>
# <!-- Optionnal part -->
# <new line>
# <footer>
#
LINE_READ=0
COMMIT_HEADER_REGEXP="(feat|refactor|test|merge|docs|fix)\\(*\\)\\s?: .+"
COMMIT_BLANK_LINE_REGEXP="^$"
COMMIT_BODY_REGEXP="^[A-Za-z].*$"
while read line;
do
    LINE_READ=$(($LINE_READ + 1))
    if [[ "$LINE_READ" == "1" ]]; then
        if ! [[ $line =~ $COMMIT_HEADER_REGEXP ]]; then
            echo -e "The first line is wrong, it should be : [feat|refactor|test|merge|docs|fix](<scope>): <subject>. Try again."
            echo -e "Line:  $line"
            exit 1;
        fi
    fi
    if [[ "$LINE_READ" == "2" ]]; then
        if ! [[ $line =~ $COMMIT_BLANK_LINE_REGEXP ]]; then
            echo -e "You forgot to add a blank line between the first line and the body section. Try again."
            echo -e "Line: $line"
            exit 1;
        fi
    fi
    if [[ "$LINE_READ" == "3" ]]; then
        if ! [[ $line =~ $COMMIT_BODY_REGEXP ]]; then
            echo -e "You have to write something in the body. Try again."
            echo "Line: $line"
            exit 1
        fi
    fi
done < $1

so we’ll read line by line the file (while loop). For the first line, we’ll see if it’s formated as the regular expression specified in COMMIT_HEADER_REGEXP variable. After we watch if there’s a blank line and finally we see if there’s something in the body section.

So nothing really hard to code but good to know how to do it if you’ll need it ! If you have any other idea of hooks, don’t hesitate to write a little comment !

I hope this article will be usefull for you in the future ! I did a little repo with all my hooks on bitbucket, if you’re interested, here’s the link

See you next time, and code well. XOXO !