Managing Websites with Git

November 16, 2008

Let’s say you have a local Git repository on your workstation containing the source for a statically-generated website. You also have the origin repository stored on a server somewhere that also serves the website. The goal is to be able to edit files on the workstation and have the website update automatically once changes are pushed to the server.

For the sake of concreteness, I’ll describe the setup I currently use for maintaining this site (let’s call it $SITENAME). I have a content repository which contains (mostly) plain-text Markdown formatted source files. There is also a Perl script which reads some basic metadata from the Markdown files, applies Markdown and any other required text filters, and applies templates to generate XHTML files (lets call this step $BUILD). The site is hosted by a server, $SERVERNAME, which also serves as the origin remote for the Git repository. I edit the Markdown files on my workstation or laptop, commit the changes, and push them to the server which then rebuilds the site.

If the content repository for the site exists already and you want to create a bare repository on the server you can do the following:

$ ssh $SERVERNAME
$ cd /srv/git/$SITENAME
$ mkdir content.git
$ cd content.git
$ git init --bare

Then, on the workstation, edit content/.git/config and add the origin remote:

[remote "origin"]
    url = ssh://$SERVERNAME/srv/git/$SITENAME/content.git
    fetch = +refs/heads/*:refs/remotes/origin/*

You probably also want to configure Git to pull from the remote and merge with the master branch:

[branch "master"]
    remote = origin
    merge = refs/heads/master

Now, push everything to the server with

$ git push --all origin

To check to see if you can pull from it, run

$ git pull

The server now has a bare repository with all of your revisions but there is no working copy containing the source files that will be needed to build the site. Create a clone of the repository with a working copy as follows:

$ cd /var/www/$SITENAME
$ git clone /srv/git/$SITENAME/content.git

The directory /var/www/$SITENAME/content now contains the source files. This working copy will be used to rebuild the site. There is however, a new problem: in addition to triggering a website rebuild, we need to make sure the working copy is kept up to date. We’ll tackle both of these simultaneously.

I’m assuming there is some command you can run that will build the website. It may be a make rule or a Perl script, for example. Let’s call this command $BUILD. I actually have more of a $REFRESH and $REBUILD system but the general idea is the same: $BUILD needs to be called on the server when something in the content repository changes.

Git provides several hooks that run before or after various events. Each hook is a shell script in the .git/hooks/ directory:

$ ls .git/hooks
applypatch-msg  post-commit   post-update     pre-commit          pre-rebase
commit-msg      post-receive  pre-applypatch  prepare-commit-msg  update

Git’s post-update hook is exactly what we’re looking for. On the server, do the following:

$ cd /srv/git/$SITENAME/content.git/hooks
$ $EDITOR post-update

By default, the file contains only a call to git-update-server-info:

#!/bin/sh
#
# An example hook script to prepare a packed repository for use over
# dumb transports.
#
# To enable this hook, make this file executable by "chmod +x post-update".

exec git-update-server-info

We want to add commands to update the working copy in /var/www/$SITENAME/content and rebuild the site. Before the exec line, add something like the following (modify to suite your situation):

# Update the working copy
WORKDIR="/var/www/$SITENAME/content"
export GIT_DIR=$WORKDIR/.git
pushd $WORKDIR > /dev/null
git pull
popd > /dev/null

# Build the website
$BUILD

The first section changes to the $WORKDIR containing your working copy and pulls the new changes. The second section simply runs your $BUILD command.

It is important that you be careful if you make changes to the working copy on the server. If you do, make sure you push them to the bare origin repository. Otherwise, when you push from the workstation there may be merge conflicts.

To active the hook, simply make it executable:

$ chmod +x post-update

Now, when you run git push on the workstation, your changes are pushed to the repository on the server which in turn updates the working copy and triggers a rebuild.