Contact
Site: US UK AU |
Nexcess Blog

Source Control with Git

September 11, 2011 2 Comments RSS Feed

Source Control with Git
While there are numerous version control systems available these days, Git (created by Linus Torvalds) has become very popular recently. In this post, I’m going to cover the basic usage of Git, along with other helpful tools.

Git is cross-platform, grab the latest version of Git. Once installed, we can start our first repository. For this example, I’ll be using a Magento installation as the initial files, but you can just as easily start the repo in a bare directory and add the files later. So I’ve unpacked Magento in magento/:

$ ls magento/
app       downloader   get.php    index.php.sample  lib              LICENSE.txt  php.ini.sample     shell
cron.php  errors       includes   install.php       LICENSE_AFL.txt  mage         pkginfo            skin
cron.sh   favicon.ico  index.php  js                LICENSE.html     media        RELEASE_NOTES.txt  var

Now we’ll initialize the repo with:

$ cd magento/
$ git init .
Initialized empty Git repository in /chroot/home/aheadley/tmp-blog/magento/.git/

Now that we have the repo, we need to tell Git what files we want it to keep track of (and which ones we never will).

$ git status
# On branch master
#
# Initial commit
#
# Untracked files:
#   (use "git add <file>..." to include in what will be committed)
#
#       .htaccess
#       .htaccess.sample
#       LICENSE.html
#       LICENSE.txt
#       LICENSE_AFL.txt
#       RELEASE_NOTES.txt
#       app/
#       cron.php
#       cron.sh
#       downloader/
#       errors/
#       favicon.ico
#       get.php
#       includes/
#       index.php
#       index.php.sample
#       install.php
#       js/
#       lib/
#       mage
#       media/
#       php.ini.sample
#       pkginfo/
#       shell/
#       skin/
#       var/
nothing added to commit but untracked files present (use "git add" to track)

To start with, we’ll tell it what files we’ll never care about. Create a file in the root of the repo named “.gitignore” and shell patterns to it to tell it what files we want to ignore. For the example, we’ll put these patterns (note that you want to be very careful and never add configuration files to git (or any VCS) if your repo will ever be publicly available):

app/etc/local.xml
downloader/*
media/*
pkginfo/*
var/*

You can read more about the .gitignore file format here. Now that we’ve told Git what files we don’t want it to track, we can go ahead and tell it to track the rest with the “git add” command:

$ git add .

Note that you may get a bunch of warnings about the file line endings, you can ignore these. If you do a “git status”, you’ll see that it’s listing a whole bunch of new files, all the ones we just added (except for the files that match the patterns in our .gitignore file). Now we need to make our first commit to the repo. It’s important to remember that changes aren’t really saved until you commit. Every commit needs a commit message (you can force a commit without a message but that’s not a good idea), which is just a short message for what the commit is about. Generally, a single commit should represent a single logical change in the repo, but there are no hard and fast rules about this.

$ git commit -m 'initial commit'
...(messages about the files and their permissions)...
$ git status
# On branch master
nothing to commit (working directory clean)

Now we’re ready to make some changes (almost)! Before we start, we should be familiar with the idea of “branching” and “merging”. You can imagine the repo (which is the files with the entire set of changes) as a big tree of nodes, with the original files being down at the very bottom (the base), each node representing a change (commit/branch/merge), and the very tip (HEAD) representing the set of files after all the changes below it have been applied. Something like this:

 HEAD
       |
       *  *-HEAD
       |/
       *
       |
       *
       |
       *(the base, or original files)

Branching let’s you head off in a different direction with your changes, without affecting other branches, and is very “cheap” in Git (basically meaning it’s a quick and lightweight operation) so Git users are encouraged to use branches more than with other VCSs. Merging means taking a set of changes in a branch and applying them to another branch. You can read more about them here. We’re going to create a new template for our example, so we’ll create a “new_template” branch (note that the default branch is named “master” and is created when you create the repo):

$ git checkout -b new_template
Switched to a new branch 'new_template'

Now we’ll really make some changes:

# create a new directory for the template
$ cd app/design/frontend/default/
$ cp -a modern/ new_template
$ git status
# On branch new_template
# Untracked files:
#   (use "git add <file>..." to include in what will be committed)
#
#       new_template/
nothing added to commit but untracked files present (use "git add" to track)
#tell git that it should track the new directory we made
$ git add new_template/
$ git commit -m 'created new template based on modern template'
[new_template a9f00c1] created new template based on modern template
 29 files changed, 3779 insertions(+), 0 deletions(-)

Then you can go on to make changes to the template files and commit them, and as a shortcut around adding the files you make changes to before each commit, you can use the -a option:

$ git status
# On branch new_template
# Changes not staged for commit:
#   (use "git add <file>..." to update what will be committed)
#   (use "git checkout -- <file>..." to discard changes in working directory)
#
#       modified:   new_template/template/page/html/footer.phtml
#       modified:   new_template/template/page/html/header.phtml
#
no changes added to commit (use "git add" and/or "git commit -a")
$ git commit -am 'added logo to header and footer'
[new_template f3bb566] added logo to header and footer
 2 files changed, 2 insertions(+), 2 deletions(-)

Ok, so now we’re happy with our new template, let’s merge the changes back into the “main” (master) branch:

$ git checkout master
Switched to branch 'master'
$ ls
blank  default  iphone  modern
$ git merge new_template
Updating 596825e..f3bb566
Fast-forward
Total 14830 (delta 6084), reused 0 (delta 0)
...
 29 files changed, 3779 insertions(+), 0 deletions(-)
...
$ ls
blank  default  iphone  modern  new_template

And that takes care of the most basic operations! Because of Git’s distributed nature, you can also have separate repositories located on different machines (i.e. one each for the live site, dev site, and each developer working on the sites), and branches can be merged between them as well, so you could test your changes on your dev site, then merge them into the live site when you’re happy with them. This lets you keep a complete history of all changes to your site, and quickly and easily revert them in case there is a problem (more can be read about remote repos here. It’s important to note that Git is not a replacement for a backup solution (although it can function in that capacity); it’s also not good for large blobs of binary data like images, compiled files, compressed archives, etc as these bloat the repo and Git (and many other VCSs) are not designed to handle them.

Additionally, there are many tools to work with Git repos, such as web frontends like cgit which let you browse the repo from your web browser and project management solutions like Redmine and Trac for documentation and issue tracking. There are also community sites like GitHub which specialize in hosting open source projects that are using Git.

Posted in: Nexcess