24/7/365 Support

Automating common tasks with Git hooks

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.

Getting ready

We will need a local web server installed. I have used an Apache installation; feel free to use your favorite server:

Set up a new virtual host under Apache and enable it:

$ cd /var/www/

$ sudo mkdir git-hooks-demo

$ sudo chown ubuntu:ubuntu git-hooks-demo

$ cd git-hooks-demo

Create 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>

Create the virtual host configuration:

$ cd /etc/apache2/sites-available

$ sudo cp 000-default.conf git-hooks-demo.conf

Open the virtual host configuration, git-hooks-demo.conf, and replace its contents with following:

<VirtualHost *:80>

DocumentRoot /var/www/git-hooks-demo/html

</VirtualHost>

Check the initial version by visiting your IP address in your browser.

Next, initialize a Git repository under the home directory:

$ cd ~/

$ mkdir git-hooks-repo

$ cd git-hooks-repo

$ git init

Copy 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.

How to do it…

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:

Create a new file under the .git/hooks directory of your repository:

$ touch .git/hooks/post-commit

Add the following contents to our 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

We need to set executable permissions to a post-commit file so that Git can execute it:

$ chmod +x .git/hooks/post-commit

Now, update the index.html content. Change the line <h2>Deployed Manually </h2> to <h2>Deployed using Git Hooks </h2>.

Commit the changes as usual. We have edited the existing file, so staging and committing can be done in a single command, as follows:

$ 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:

How it works…

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.

See also

Read more about Git hooks at https://git-scm.com/docs/githooks

Customizing Git hooks at https://git-scm.com/book/en/v2/Customizing-Git-Git-Hooks

Help Category:

What Our Clients Say