Git hooks

Recently I found the benefits of using git hooks. I want you explain in this article more about git hooks. What are git hooks in the first place? What for hooks are there? (no I’m not talking about a fish hook). And how to use git hooks in combination in my favorite open-source git server: GitLab.

git_hookGit hooks

A git hook is a way to fire off your own custom scripts (perl, python or bash it doesn’t really matter). These scripts may contain important actions or other things you would like to happen. These git hooks and scripts can get triggered in different moments during the git process: like before a git push command.

Server & Client hooks

There are exists two types of hooks. Server-side hooks and client-side hooks.
Client-side hooks could be useful, but be-ware of the limitations. Let’s say you want to enforce some kind of policies on the users you should not use client-side hooks. But server-side hooks instead. Since client-side hooks are NOT copied during a clone of a git repository. And that is exactly one of the down-sides of using client-side git hooks. Client-side hooks are the easier to setup, but also limited.

Client-side

Client-side scripts can be placed into the local hidden folder called: .git within your git repository. There are different types of client-hooks as well. In order of execution time: first the pre-commit hook is run first, even before a commit message. Then you got prepare-commit-msg, this hook is run before the commit message editor, but after the default message is already in place. The commit-msg hook will be triggered after the developer enters some commit message. Besides these hooks there exist more client-side hooks, like:  pre-rebase, post-checkout, post-merge, post-merge and pre-push hooks.

A git init or git clone will use the default templates located in /usr/share/git-core/templates.By default the hooks are disabled, so remove the .sample extension to enable them. Or provide your own template directory, using: --template=<template_directory>. Or in your git config file (.git/config):

[init]
      templatedir = /path/to/templates

Use git config --list to see the new configuration.

So let’s say again you want to enforce some policy to the users of the git repository, you are thinking: pre-push sounds good. This hook is triggered just before the files gets transferred to the server. In fact you can make  such a client-side hook script and block the user whenever you want. But be-aware you need to copy this hook to other people (it won’t be part of the git repo). And people can still edit the scripts themselves, thus removing the policy. That is why server-side scripts are so much better.

Server-side

As a system administrator you can setup server-side hooks. This is the way to go when you want to enforce any kind of policy for your project. There are three type of server-side hooks:

  • pre-receive
  • update
  • post-receive

The pre-receive hook is triggered when the server receives a push request from a client. If you return a non-zero in this script, the push gets rejected. You can get information about this push via stdin (Standard input stream) within your script.

The update hook is very similar to the pre-receive script, except this hook is triggered for each branch while the pre-receive is triggered once. So if you push to multiple branches, you can reject a push to a specific branch by returning a non-zero value in the update hook script.

Finally the post-receive hook is run after the entire process is completed. You can also use the stdin for the push data. Examples where you can use this hook for are: notifying users or sending mails, trigger the continuous integration server or for example updating a ticket in your Issue tracking system.

Gitlab + Server-side Hook

gitlab_hook

I’m using Gitlab (Community Edition), which is a free and open-source git server implementation. I highly recommend you looking at Gitlab yourself if you want to setup your own git server.

Gitlab supports server-side hooks, they call it ‘Custom Git Hooks’. In my situation I store the Gitlab data on a separated RAID disk mounted on /media/data under GNU/Linux. So in my case I have a folder called gitlab, which stores all the data of Gitlab on my disk. Within this folder the git-data is located together with all the git repositories. You can store your custom hooks in a folder called ‘custom_hooks‘ within the project’s repository directory. The custom_hooks directory needs to be created by you, and give it the git user/group rights. My location for custom_hook directory could be:

/media/data/gitlab/git-data/repositories/melroy/<My Project>.git/custom_hooks

I created a post-receive hook within the ‘custom_hooks‘ folder, which does a git check-out of the git repository to another location. My script deploys the latest version directly (after a successful push) to a self-defined location, wonderful! I created a new file called ‘post-receive‘ (noticed the ‘-‘ and not ‘_’ in the name). I added a bash shebang on top of the script, so Linux knows it’s a bash script. Finally give it execute rights and again be sure it has the rights as the ‘git’ user and group:

sudo chmod a+x ./post-receive
sudo chown git:git ./post-receive

The full content of my script is:

#!/usr/bin/env bash
# Author: Melroy van den Berg
# This script gets executed by the git user on the Gitlab server.
# The script is triggered after every successful push command, which sends new data to the server.
# Then this scripts deploys the latest version to the "$DEPLOY_ROOT" folder.
read oldrev newrev refname
DEPLOY_ROOT="/var/www/your_location"
BRANCH=`echo $refname | sed -n 's/^refs\/heads\///p'`
GIT_BRANCH="master"
if [ $BRANCH=$GIT_BRANCH ]; then
    # Make dir if not exist yet
    mkdir -p ${DEPLOY_ROOT}
    
    cd "${DEPLOY_ROOT}"  || exit
    # Unset the GIT_DIR otherwise the git location is wrong!
    unset GIT_DIR
    
    echo "githook: Deploying Project to: ${DEPLOY_ROOT}"
    echo 
    
    if [ "$(ls -A $DEPLOY_ROOT)" ]; then
        git fetch origin && \
        git reset --hard origin/$GIT_BRANCH
    else
        git clone git@gitlab.yourdomain/user/project.git ${DEPLOY_ROOT} -b $GIT_BRANCH
    fi
    echo "Roll-out master. Releasing: ${newrev}!" | mail -s "Gitlab Push" "your@email.com"
    # Install depends
    /usr/bin/npm install --production
else
    echo "githook: Skipped. Not the master branch."
fi
echo "githook: Hook finished"
exit 0

The script first reads in the stdin via: read oldrev newrev refname.  Then it checks if branch is equal to the master branch, it continues. It creates the deployment folder if it didn’t exists yet and ‘cd’ (change directory) to the folder. Then a very important step is to unset the GIT_DIR environment variable. Otherwise the change directory command won’t work and the git fetch/clone will use the $GIT_DIR instead.  If the project already exists the script updates the folder via a git fetch && git reset --hard. Otherwise it will execute git clone for the first time usage.

I even setup a mail trigger, which gives me an e-mail notification. Finally as you can see my project is using NodeJS with NPM packages, that is why npm install is also executed within my script to install any missing dependencies. The script always return zero (exit 0), meaning the push won’t be blocked in any case. Furthermore I use Nodemon tool on my NodeJS server, and nodemon will auto-detect any file changes and restart the NodeJS server if required.
To summarize, if a successful push is done to the master branch, the script keeps the live website server directly in sync with the latest version of the git project. The NodeJS server will restart and the latest version of the website is directly live to the public!

Conclusion

Git hooks, especially server-side hooks, are very powerful extensions to your git projects. The possibilities of git hooks are infinite. In my article I explained an example about deploying the latest version of a git project to roll-out location plus sending an e-mail. Of-source you could do much more things with git hooks. But be-aware of the limitations of client-side hooks and the advantages of server-side hooks.

Did you like the article? Please share!