Managing WordPress with Git

In addition to blogging, WordPress can be a great way to manage content for a basic website: what we in the biz like to call, a CMS. I’ve recently been building more websites using WordPress for the backend, and although it’s been relatively easy to manage content, it can be a pain to manage upgrades, plugins and theme customizations over FTP, or directly on the server.

Since I’ve had to do more wordpress development recently, the need to test and backup my customizations has increased. So I decided to set up a local mirror of my blog and put it under source control. I recently read an article entitled Using Git to manage a web site, which lists the steps needed to place your web content under git source control, and deploy that content to the production server over SSH. Using this system, you get two huge benefits just by learning a few git commands.

It would also be nice to be able to separate my customizations and content from official wordpress releases, and merge them in with each new release. A tutorial I found at wp.stackexchange.com (git by example – upgrade wordpress like a ninja) outlines the steps needed to mange this process.

I’ll show you how I use these two tutorials to manage this site. First, you want to set up your local git repository:
$ wget http://wordpress.org/wordpress-3.0.4.zip
$ unzip wordpress-3.0.4.zip
$ cd wordpress/
$ git init
$ git add .
$ git commit -m'check in initial 3.0.4 upstream'
$ git status

On my dev machine, I grab the same version of version that my blog is currently running on, initialize the git repo, and add/commit the files.

Next, we need to add all of our customizations (you can skip this step and the one after if starting a new blog or site from scratch) to a separate branch in git:
$ git branch mine
$ git checkout mine
$ rsync -cuvr MY_SERVER:MY_DOCUMENT_ROOT/ .
$ rm -rf stats error phpinfo.php
$ git status
$ git add .
$ git commit -m'added my themes, config and network patch'

Here, I create a branch called ‘mine’ and rsync the live site back to my local working folder. There’s some stuff I don’t need versioned, so I get rid of it. You can also add these files to a .gitignore file. If you don’t have rsync installed, you can ftp your customized WP files instead. If you are familiar with rsync, I reccomend adding ‘n’ to the flags so that you can preview what will be added to your pristine WP install.

Now we need to merge our changes and new files with the latest WP version:
$ git checkout master
$ cd ..
$ wget http://wordpress.org/wordpress-latest.zip
$ unzip wordpress-latest.zip
$ cd -
$ git status
$ git add .
$ git commit -m'check in initial 3.1 upstream'

Here, I go back to the master branch and extract the latest WP code over the existing files. Don’t worry about losing any data since git is tracking the changes.
$ git checkout mine
$ git rebase master
$ git reset wp-content/plugins/akismet/akismet.php
$ git checkout -f wp-content/plugins/akismet/akismet.php
$ git add .
$ git rebase --continue

In order to merge in he changes from the master branch, I rebase the code on the mine branch and handle the conflicts. The rebase command complained about a conflict in an Akismet plugin file which was changed between the base 3.0.4 version and the 3.1 upgrade. Since I didn’t care about losing that version (I must have updated it through the admin panel), I ended up resetting that file on the branch back to the 3.0.4 version and committing it.  rebase –continue finishes the merge of my customizations and the new 3.1 version in the master branch. I will repeat this process (the last two steps) every time I need to upgrade WP.

The next step is to automate the deployment of any updated or new files to the server. First, we’ll set up a remote repository that will mirror our local one:
$ mkdir WEBSITE.git && cd WEBSITE.git
$ git init --bare

Next, I need to add a script that will pull all updated files from git into the document root and restart the server:

$ vi WEBSITE.git/hooks/post-receive
#!/bin/sh
sudo chown -R MY_USERNAME MY_DOCUMENT_ROOT
GIT_WORK_TREE=MY_DOCUMENT_ROOT git checkout -f mine
sudo chown -R apache:www MY_DOCUMENT_ROOT
sudo /etc/init.d/nginx reload

(Replace everything in CAPS with your info except for GIT_WORK_TREE.)

In order for those sudo commands to run, I need to add these two lines to the bottom of my /etc/sudoers file (use visudo!) as root:
MY_USERNAME ALL=(ALL) NOPASSWD: /bin/chown
MY_USERNAME ALL=(ALL) NOPASSWD: /etc/init.d/nginx

Now, back on my local machine, I can add a remote mirror and deploy the files from my branch with  the git push command:

$ git remote add web ssh://MY_SERVER/home/MY_USERNAME/WEBSITE.git
$ git push web +mine:refs/heads/mine

After the initial push, you only need to type ‘git push web’  for subsequent updates.

Mirroring installations can have some pitfalls, such as accidentally deploying debugging code or special local settings. So I’ve added these lines of code to my wp-config.php file:
if (file_exists('dev_settings.php')) {
include('dev_settings.php');
} else {
$debug_on = false;
}
define( 'WP_DEBUG', $debug_on);

So I throw whatever custom settings into dev_settings.php and then I exclude that file from git so that it doesn’t get pushed up to the live server:
$ echo dev_settings.php >> .git/info/exclude
$ git status

And now you’re a bonafide WordPress-Git-Ninja!

I recommend excluding any user upload files from git and finding a way to back them up separately. The push process won’t remove anything from your doc root, so you can pick and choose which files you need under version control.

Please feel free to correct or suggest anything in the comments below.