WordPress Development - Version Control with Git

WordPress lead developer, Mark Jaquith, recently gave a talk at WordCamp San Francisco on Scaling, Servers, and Deploys that including some great advice on professional WordPress development using Git.  Git is a free & open source, distributed version control system designed to handle everything from small to very large projects with speed and efficiency.

The session from WordCamp San Francisco:

The purpose of this guide is to show you how you can add Git to your WordPress development workflow and will include the basics of securely installing Git on a remote server, using your local machine for development and deploying your changes to a live production server using SSH.

This article assumes:

  • You have SSH access to a remote server running Debian or Ubuntu (The shell commands will be slightly different for other distros)
  • Your using a Mac with a local development environment already set up.
  • Your comfortable using the command line and able to access your remote server using SSH keys.

Step 1: Installing Git on the remote server

Install Git:

apt-get install git-core

Adding the git user. (You will be asked to enter a password for the git user. We won’t be using it but remember what it is just in case.)

adduser git

Setup SSH keys for the new git user. If you still have access to the public key you use to access your server you can add it to /home/git/.ssh/authorized_keys. If not you will need to generate a new public/private key pair and add the public key to your server. See my previous article WordPress Performance Server – Debian “squeeze” with Nginx, APC and PHP from the Dotdeb repos for more info on setting up SSH keys on your server. You can also use the ssh-keygen tutorial on GitHub.

su git
cd /home/git
mkdir .ssh
chmod 700 .ssh
cd .ssh
touch authorized_keys
cat id_dsa.pub >> authorized_keys
exit

If you need to switch back to your normal user to add your public key make sure you chown the /home/git/.ssh back to the git user by running:

chown git:git /home/git/ -R

Now lets test our SSH connection using the git username on the remote server. If you use lots of different SSH keys for different servers it helps if you add instructions to your config file to identify the right key to use when logging in.

On your local machine create if needed and open the file ~/.ssh/config

Host remote_server_domain.com
Hostname remote_server_domain.com
IdentityFile ~/.ssh/id_dsa //Your private key that matches the public key we installed on the server
User git

Now lets test our connection to the remote server

ssh git@remote_server_domain.com

If your able to connect let’s go ahead and create and initialize our remote Git repository. We’re calling this repo “project1”.

cd /home/git
mkdir project1
cd project1
git --bare init
exit

You just created your first remote Git repository!

For security we are going to restrict shell access for the git user. This will allow you to share development with other users by giving them SSH access to the repo without giving them shell access to your server.
SSH back into your server with the user with su privileges.

 
nano /etc/passwd

When you open the file find the line for the new git user we just added and change the end of the line from /bin/bash to /usr/bin/git-shell. (Do not change the user Id or group ID). It should look like this when your done:

git:x:1001:1001:Git,,,:/home/git:/usr/bin/git-shell

Test the new config by exiting and trying again to SSH into the server with the git user:

ssh git@remote_server_domain.com
fatal: What do you think I am? A shell?
connection to [remote_server_domain.com] closed.

Now we have a secure remote repository.

Step 2: Installing Git and setting up your local machine

Download and install the latest version for Mac (For Lion or Snow Leopard choose: Git Installer 1.7.6 - OS X - Snow Leopard - x86_64)

Making your first commit.
Now that we have our remote repo setup and Git installed on our local machine let’s make our first commit.

mkdir project1
cd project1
git init
touch readme.txt
git add .
git commit -m "Initial commit - added readme.txt"

The first time we use our new repo we need to tell Git where to find it.

git remote add origin ssh://git@remote_server_domain.com/home/git/project1
git push origin master

Now we are going to delete the project1 directory we created on our local machine and check out the repo so we can start doing some WordPress development. I use the ~/sites directory for my local development so lets check the new project out into that directory. If you haven’t set up your local environment using hostname aliases in the ~/sites directory check out this tutorial on WPCandy: How to improve local WordPress development on a Mac

rm -rf project1
mkdir ~/sites/wp.dev
cd wp.dev
git clone ssh://git@remote_server_domain.com/home/git/project1

We now have checked out the latest version of our repo (currently only containing the readme.txt file) and are ready to start working but first we need to decide our version control strategy.

Setting up your repo for WordPress theme and plugin development

The power of Git besides being able to track your development changes is being able to quickly deploy a complete site to your production server including the latest version of WordPress. The WordPress team uses Subversion to track WordPress core changes but luckily there is a Git WordPress clone that updates every 30 minutes.

There are couple different routes you can take when setting up your repo with WordPress. Some developers prefer to keep the wp-content directory under version control separate from the core files then sym link to the version controlled wp-content dir that contains your theme and or plugin files that you will be working on.

Keeping things separate is probably best if you want to use one master repo for all the themes and plugins you work on but for this article we are going to clone the 3.2 branch from the WordPress Git repo and merge it with our remote repo. This will make it easier to deploy until we master more advanced deploy techniques like using Capistrano to automate the deploy process.

The git pull command allows us to clone the WordPress repo and merge it with our remote repo with one command. We can also specify the branch (currently WordPress is on 3.2-branch) we want to work with.

cd ~/sites/wp.dev
git pull https://github.com/markjaquith/WordPress.git 3.2-branch
git add .
git commit -m "added WordPress core files"

You will notice that when you try to make this commit you will get a message that your branch is ahead of origin/master

# On branch master
# Your branch is ahead of 'origin/master' by 16688 commits.
#
nothing to commit (working directory clean)

There might be a better way to add WordPress and keep it under version control but using this method we inherit all the core svn commits that were made to the 3.2-branch (Don’t worry any new additions will not cause problems)

Now that we have the WordPress core files in our repo lets go ahead and push them to our remote master

git push origin master

Before we start working we need to make a few changes to wp-config.php to use the local version of our database by creating a local-config.php file. (See Mark Jaquith’s postWordPress local dev tips: DB & plugins for detailed instructions on how to use a local-config.php file and disable plugins you don’t want to run on your local environment.

We want to keep wp-config.php under version control but tell WordPress to use our local DB_NAME, DB_USER, and DB_PASSWORD settings when we’re doing local development.

You can now create your theme or plugin files and start working and commit (check them in) as needed:

git add .
git commit -m "Your commit message"
git push origin master

Step 4: Deploying to a live server

For this article I’m going to cover a very basic deploy strategy that uses the master and origin branches. There are more advanced methods like Capistrano and multiple branches for staging and production that I will cover in future articles. See the Git Reference for more info on working with branches.

Moving to our live server were going to clone repo. This assumes the production environment lives on the same server as the remote repo or we have generated a working SSH key pair to access the remote repo.

cd {root public dir}
git clone ssh://git@remote_server_domain.com/home/git/project1 .

Now anytime you make updates SSH into the live server and run:

git fetch origin
git merge origin master

If you ever need to rollback a change you can quickly do a git reset -hard your_sha. Your_sha represents a commit and to find the previous commit to roll back from run git log. This will output recent changes. Find the most recent “Merge remote branch” message and use the sha (long number after the commit message) to run your git reset command.

Conclusion
Hopefully this article gives you a basic understanding of how version control with Git works. This should give you a good start and keep you from wearing the “Pink Sombrero” I would love to hear how you use version control in your WordPress development workflow and welcome any comments or suggestions.

Resources used for this article:

10 Comments (Add Yours)

  1. Hi,

    Your steps work perfectly. BUT, how do I get back into the git user account to create new repos? What am I missing?

    Thanks

  2. Nvm… I figured it out. There are two ways (that I know of) to make new repos once bash shell access has been restricted.

    1. People with sudo access use sudo su -shell /bin/bash git
    2. People on shared hosting accounts can ssh into their main user account and then log into the”git” user account from another shell su -c ‘/bin/sh’ git

  3. You should be aware of the environment variable GIT_SSH, which is used by git to find your ssh-speaking client, if ssh doesn’t work for you. The git install may be using plink.exe (via GIT_SSH) to perform the authentication. If so, make sure you have pageant.exe running, and the key you created for github loaded into it. This provides the key to plink.exe; without it, the above error will occur.

  4. Speaking of cowboy coding, the audience member’s question at 29:10 in the video gets at a larger issue I’ve been grappling with lately. Specifically: you can version-control files, but what about the database?

    The audience member specifically mentions that changes to the WordPress Dashboard are recorded in the wp_options table, and he’s not able to migrate that change from dev to live with version control. I see two other cases where it’s also an issue:

    * WordPress core updates. Some of them make database modifications. Say you’re tracking your entire WordPress install in a git repo. Once you perform a core updates that includes a db migration, you can’t revert in git anymore because the database is now out of sync.

    * Many content contributors. If you have contributors adding content to your WordPress blog (comments, posts, etc.), there is not a good way to track that data in version control.

    Anyone have any thoughts or best practices on this? I know I can version control database dumps, but I’m looking for a more comprehensive solution that brings the entire ecosystem under version control (in much the same way you can do so in a Ruby on Rails environment, for example).

    • Very good question. I mainly use version control for files so I don’t really know a good solution.

    • I’ve spent all night looking for an answer to this question. I have a locally developed version of my website. However I’m adding information to the database, mostly custom post type data. Now when I commit my changes I need to export my database and import it to the new server. It’s pretty lame, I would love any suggestions. I just want a mirror version of everything I’m working on to be able to seamlessly transition between dev staging and production.

      • Does anyone have a good solution to the dbase issue?

        • This is something I’ve run into in a variety of projects and environments. Two things I’ve found that help (but don’t provide a complete solution):

          1. Script db changes and save the sql to files that you check in. Unfortunately this doesn’t work as well when you want to edit the data through admin or a plugin adds data.

          2. Make a backup of the live database and restore it to your local system. This doesn’t solve the cowboy coding problem, but at least you can debug locally with something close to live data and you don’t have to worry about accidentally changing live data.

  5. at around the 4:50 mark, he mentions his ideal is to ignore the ‘uploads’ folder. What’s wrong with checking in your images? Wouldn’t it make sense to have these sync’ed between your local/stage/productionn?

    • the uploads folder can grow enormously which can affect performance especially cloning. in addition, i sometimes prefer hosting the uploads folder in s3 or some cloud service