Git hooks
By Melroy van den Berg
- 7 minutes read - 1310 wordsRecently 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 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
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.
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.