One of the more interesting features of Git is hooks. With hooks, you can tie an arbitrary script to various Git events. Whenever a particular event, such as a git commit
or git push
, occurs, the script attached to that event gets executed.
Typically, an event consists of several steps, and a script can be attached to each of these steps. The most common steps are pre-event and post-event, with pre hooks executed before the event and post hooks after the event. A pre hook, such as pre-commit, is generally used to cross-check the updates and can approve or reject an actual event. A post hook is used to execute additional activities after an event, such as start a built process when a new push is received or a notification sent.
Every Git repository consists of a .git/hooks
directory with sample scripts. You can start using those hooks by removing the .sample
extension from the script name. Additionally, the hook scripts belong to a single repository instance and do not get copied with the repository clone. So, if you add some hooks to your local repository and then push changes to the remote, the hooks will not get replicated on the remote. You will need to manually copy those scripts on the remote system. Built-in sample hooks generally use the shell scripts, but you can use any scripting language, such as Python or even PHP.
In this recipe, we will learn how to use Git hooks. We will create our own post-commit hook that deploys to a local web server.
We will need a local web server installed. I have used an Apache installation; feel free to use your favorite server:
$ cd /var/www/ $ sudo mkdir git-hooks-demo $ sudo chown ubuntu:ubuntu git-hooks-demo $ cd git-hooks-demo
index.html
and add the following contents to it:$ vi index.html
<!DOCTYPE html> <html> <head><title>Git hooks demo</title></head> <body> <h2>Deployed Manually </h2> </body> </html>
$ cd /etc/apache2/sites-available $ sudo cp 000-default.conf git-hooks-demo.conf
git-hooks-demo.conf,
and replace its contents with following:<VirtualHost *:80> DocumentRoot /var/www/git-hooks-demo/html </VirtualHost>
home
directory:$ cd ~/ $ mkdir git-hooks-repo $ cd git-hooks-repo $ git init
index.html
from the web root to the repository:$ cp /var/www/git-hooks-demo/index.html .
Now we are equipped with the basic requirements to create our Git hook.
Git hooks are located under the .git/hooks
directory. We will create a new post commit hook that deploys the latest commit to our local web server. We will be using a shell script to write our hook:
.git/hooks
directory of your repository:$ touch .git/hooks/post-commit
post-commit
hook:#!/bin/bash echo "Post commit hook started" WEBROOT=/var/www/git-hooks-demo TARBALL=/tmp/myapp.tar echo "Exporting repository contents" git archive master --format=tar --output $TARBALL mkdir $WEBROOT/html_new tar -xf $TARBALL -C $WEBROOT/html_new --strip-components 1 echo "Backup existing setup" mv $WEBROOT/html $WEBROOT/backups/html-'date +%Y-%m-%d-%T' echo "Deploying latest code" mv $WEBROOT/html_new $WEBROOT/html exit 0
post-commit
file so that Git can execute it:$ chmod +x .git/hooks/post-commit
index.html
content. Change the line <h2>Deployed Manually </h2>
to <h2>Deployed using Git Hooks </h2>.
$ git commit -a -m "deployed using hooks"
This time, the git commit
result should output all echo statements from our git hook
. It should look as follows:
You can check the latest deployed index.html
by visiting the IP address of your system:
We have created a simple post commit hook that exports all files from the Git repository, backs up the existing live site, and replaces it with new contents. This is a very simple shell script, set to execute after each commit event on the local repository. A script that starts with a hash bang signature defines that the script is expecting bash runtime. Later, we defined the WEBROOT
and TARBALL
variables, which contain the full path for the web-root directory and backup location respectively. Next, we created an archive of all the files with the git archive
command. This command creates an archive of a named tree; a tree can be a specific commit ID or a branch. We have used a master branch for our export. The contents are exported in a tarball format with the export location set using the --output
parameter. Once we have the tarball in place, we need to replace the live site with contents from the tarball. We have also taken a backup of the running site, just in case anything goes wrong.
This is a very primitive script and deploys only to the local server. To deploy on a remote server, you will need to use some synchronization tools such as rsync to update the content on a remote server. Make sure you are using an SSH connection for your deployments to live servers. Many blogs advise you to have a Git instance running on a live web server and setting it to deploy the live site using a post-receive
hook. This can be an option for staging or a demo server, but on a live server I would try to avoid installing any tool other than a web server. Any additional packages will increase the effective attack surface and may compromise the security of your servers. Who knows whether Git contains some unknown shocks (remember shell shock?)
Note that we are creating a backup on each new commit. You may end up with an out of disk space error if your deployment is big or if you are doing frequent commits. That is not a big problem, though. The script can be easily modified to delete any directories created X days before. You can even choose to keep the last, say, 10 backups and delete others.
As we are deploying to a local web server, we have set the script to be a post-commit
hook. If you choose to deploy it on a remote server, then make sure you set the script as a post receive or update script. We commit on a local repository and push updates to the remote server.
As we have seen, this is a plain shell script, and you can easily use any bash command in this script. Additionally, you can execute the script manually using the sh script.sh
command or the short hand notation, ./script.sh
. This will help in debugging the script and monitoring the output without the need to create any Git commits. Also make sure that the script file is set as executable and that all directories you are working with are writable by your user account.
If you are using remote repositories hosted with GitHub or GitLab, they provide a webhook feature which works similar to Git hooks. You will need to set a script accessible over the Web through a URL. When a particular event happens, GitLab will make a POST
request to a given URL with the relevant event data.