A List Apart

Menu
Issue № 317

Get Started with Git

by Published in Project Management, Workflow & Tools17 Comments

If you’re a designer or developer, you’ve probably heard about Git, and you might know that it has become immensely popular, especially among the open source community. Though it may seem cryptic at first, this version control system could change the way you work with text, whether you’re writing code, or a novel.

This article covers why version control is important, how to install the Git version control system, and how to get started with your first repository. Once you start using Git, you’ll want to throw everything into it, from full-blown apps to blog post drafts, because it’s so easy and versatile.

Why do I need version control?

While it may be obvious that large development teams should have sophisticated code-management systems to track releases and bugs, and to avoid stepping on each others’ toes, it might not be immediately clear why individuals would need version control—especially designers or writers.

But take a look at a site like Wikipedia, which is built around collaborative user content editing. One of Wikipedia’s best features is the ability to compare two versions of an article. When you do this, you’re actually performing a diff, one of version control’s central concepts. And when you decide to add or change content, you’re committing a revision. With Git, you can add this Wikipedia-like functionality to any folder, and it will automatically start looking for changes in files contained within it, even if they’re not text files. In Git parlance, that folder becomes your repository.

With Git, though, you can do much more. Branching and merging are powerful tools for integrating changes without compromising more stable work that might, for example, be running on a production website. Remotes are copies of whole repositories that are transferred over a network, and are really helpful for collaborating on projects with people both in your office and over the internet.

No more circus file naming

At the very least, Git can save you the tedium of file name versioning (e.g., avoiding untitled-1-new-v2.html). At best, it can help you work faster by saving your changes, managing different ideas and features in your project, and even serving as a backup strategy. Because Git unobtrusively operates on whole folders, it won’t interfere in your day-to-day work until you explicitly ask it to save a project snapshot at any given time. And permanently removing Git from a folder is just one command away.

Git changes the way you work by making risk cheaper. Unlike haphazard “multiple undo” and “autosave” features common in many editors and graphics packages, Git expects you to control how and when to commit changes to a project, and, in doing so, allows your project to evolve from any one of these changes. Plus, it does this without having any extra administrative debris around versioning (think labels, markers, and extra files) in your project. Any of these changes can be merged automatically, so you never have to repeat yourself or try to figure out how to redo something you overwrote. For this reason, Joel Spolsky called Git a “change control system” instead of a version control system because rather than presenting a project linearly (the “undo” mentality), Git just sees a handful of commits always ready to be reshuffled or squashed together:

When you manage changes instead of managing versions, merging works better, and therefore, you can branch any time your organizational goals require it, because merging back will be a piece of cake.

In the beginning was the command line

Git is unlike most modern OS-native applications in that there is (mostly) no graphical user interface; it is totally controlled via the command line. This may seem daunting at first, but the few common commands will quickly become second nature. If you’re new to the command line, I highly recommend Dan Benjamin’s PeepCode screencast, Meet the Command Line, as a quick crash course.

For the more visually inclined, there are a few graphical tools to browse and manipulate Git repositories. Git comes with two programs, one called gitk for browsing your project history, and another called git-gui for adding files and creating commits. With these two mini apps, you won’t need to use the command line interface much; but you may be surprised to find that it is actually quicker to use the Git commands than git-gui or gitk, especially if you upload your project to GitHub, which has an excellent source browser. Mac OS X users should check out GitX, a prettier version of gitk/git-gui to visualize and edit repositories. The official wiki has a comprehensive list of graphical interfaces for Git.

Housekeeping, before we get started

When referring to the terminal, I’ll use prompt> to represent the command prompt and what to type after it. Everything under the prompt> line is what will be displayed after you hit return. The Unix shell is terse, meaning, it won’t tell you if something went right—only if something goes wrong. Git is pretty good about telling you what it’s doing, but don’t be alarmed if not every command gives you a response.

Installing Git

There are many ways to get and install Git: The quickest way is to download and and compile it from source—here is a great walkthrough on how to do that in Mac OS X. Alternatively, Mac users can also download the graphical Git installer and use it to install Git like any other Mac program. On Ubuntu Linux, run apt-get install git-core; Windows users should check out msysgit.

The free book Pro Git also has a great section about installing Git on different platforms.

Creating a repository

Now that Git is installed, we’ll get started with a repository (or repo, for short). First, let’s make sure it’s installed properly:

prompt> git --version
git version 1.7.2.2

It’s working! (If you got something different, try re-installing.) Now, we’re ready to create our first repo. First, make a new folder anywhere in the Finder, then navigate to that folder from the terminal:

prompt> cd /Users/alshaw/testrepo

To make this folder a Git repo, just type (Line wraps marked » —Ed.):

prompt> git init
Initialized empty Git repository in /Users/alshaw/»
testrepo/.git/prompt> git status  
# On branch master
#
# Initial commit
#

Behind the scenes, this creates a hidden .git directory inside the folder that will keep track of your changes. Just to make sure, we’ll test it:

prompt> ls -a 
.       ..      .git

Git won’t ever touch your files, or do anything outside the .git directory, unless you tell it to. If you ever decide you don’t want the folder to be a Git repo anymore, just delete the .git folder:

prompt> rm -rf .git

And a quick test that we have really de-Gitted “testrepo”:

prompt> git status
fatal: Not a git repository (or any of the parent »
directories): .git

Another way to start working with a repository is to git-clone an existing repository. Let’s say we’re writing a new web app, and we want to make use of Paul Irish’s shiny new HTML5 Boilerplate as a jumping-off point. Since Paul has his source code in a Git repository on GitHub, we can grab it and start modifying it without leaving the command line:

prompt> git clone git://github.com/paulirish/»
html5-boilerplate.git
Cloning into html5-boilerplate…
remote: Counting objects: 932, done.
remote: Compressing objects: 100% (820/820), done.
remote: Total 932 (delta 477), reused 196 »
(delta 88)
Receiving objects: 100% (932/932), 1.54 MiB | 349 »
KiB/s, done.
Resolving deltas: 100% (477/477), done.prompt> cd html5-boilerplate

Sweet, now we have the whole history of the project! Let’s see what he’s been working on with git-log:

prompt> git log
commit 75b34e5962b155238bcb711e87b34a6409787e78
Author: paulirish 
Date:   Tue Aug 24 22:12:59 2010 -0700    minified dd_belated png… you probably dont »
    need the full one.
[snip]

With git-log you’ll get a list of every commit, the commit’s SHA1 hash (a unique string assigned to each commit), the author, and a message he or she wrote to describe the change.

If we want to see specifically what changed in this version, we can use git-diff to compare it to previous changes. Let’s look at the difference between the current version and an earlier commit. To do that, we’ll use git-diff with the prior commit’s hash id as its argument:

prompt> git diff b59c1cc00e1f6a25a12a224080a70287fa33e4da
[snip]
diff --git a/.htaccess b/.htaccess
index 9879e5e..5451611 100644
--- a/.htaccess
+++ b/.htaccess
@@ -68,7 +68,7 @@ AddType text/x-component htc
 # Disabled by default. # 
-#         Options +IncludesNOEXEC
+#         Options +Includes
 #         SetOutputFilter INCLUDES
 # 
 [snip]
 

This is just an excerpt of that diff, which shows a change in this sample .htaccess file. The author added one line (marked with +) and deleted another (marked with -).

Adding and committing

Now that we’ve taken a look at this repo, why don’t we make some changes of our own to make it work in our app? Let’s say we’re going to be using Google Ads, so we really shouldn’t redefine [removed](). We’ll take that function out of plugins.js. Also, let’s kill the hot pink text selection rule in style.css; not sure what they were thinking with that.

I fire up my text editor, delete those two hunks of code, and save the files. Back at the command line, I type:

prompt> git status
# On branch master
# Changed but not updated:
#   (use "git add ..." to update what »
will be committed)
#   (use "git checkout -- ..." to »
discard changes in working directory)
#
#       modified:   css/style.css
#       modified:   js/plugins.js
#

Cool, Git knows I made changes to those two files. Now we’ll want to commit the changes. But first, just for fun, we can see what Git knows about what we changed. In this commit, we’ve deleted several lines:

prompt> git diff
diff --git a/css/style.css b/css/style.css
index daf8d54..d47b608 100644
--- a/css/style.css
+++ b/css/style.css
@@ -155,11 +155,6 @@ input:invalid {
 .no-boxshadow input:invalid { background-color: »
 #f0dddd; }
-/* These selection declarations have to be separate.
-   No text-shadow: twitter.com/miketaylr/status/12228805301 
-   Also: hot pink. */
-::-moz-selection{ background: #FF5E99; color:#fff; »
text-shadow: none; }
-::selection { background:#FF5E99; color:#fff; »
text-shadow: none; } 
[snip]

Hot pink, be gone!

The staging area

Before we make our first commit, we note that the staging area (also known as the index), is an important quirk about Git that we should know about. (It isn’t in many other version control systems.) If we were using Subversion, we would now svn commit and record our change. In Git, there is one more step, and it’s there for a good reason. Before you can commit a change, you need to git-add the file to what is called the “staging area” so that it can be committed. We could skip this step altogether by typing:

prompt> git commit -am "my message"

(where the -a flag means add). But the staging area is designed so you can craft your commits regardless of when you edit the actual code that composes them. In this round of editing, we changed two totally separate parts of the project—a CSS block and a JavaScript function. Logically, that should be submitted as two separate commits, since the edits are not dependent on one another. The staging area helps you break down your commits by features rather than by when you edited the text files containing them.

Let’s just add the CSS file first:

prompt> git add css/style.cssprompt> git status
# On branch master
# Changes to be committed:
#   (use "git reset HEAD ..." to unstage)
#
#       modified:   css/style.css
#
# Changed but not updated:
#   (use "git add ..." to update what »
will be committed)
#   (use "git checkout -- ..." to »
discard changes in working directory)
#
#       modified:   js/plugins.js
#

Now, since we just added style.css, Git is telling us we can commit it:

prompt> git commit -m "killed the hot pink »
selector"
[master e54357d] killed the hot pink selector
 1 files changed, 0 insertions(+), 5 deletions(-)

And a quick sanity check:

prompt> git log
commit e54357d66498870072c4348168f16cafcd0496e7
Author: Al Shaw 
Date:   Wed Aug 25 22:48:29 2010 -0400    killed the hot pink selector
[snip]

Now you’ll notice that if you run git-status, only plugins.js remains modified. We can then commit it separately with its own message. Because Git is really a change tracker rather than a version tracker, organizing commits by feature allows you to move those features around easily later on. For example, if you want to bring a whole set of features from one branch to another (more on branching below), it’s much easier to have them within their own commits. For this reason, many developers adopt a “commit often” philosophy and go back to organize the commits later.

The staging area as a solution to the “tangled working copy problem” just scratches the surface of Git’s efforts to help you keep your code organized by feature. It gets even better: Despite how it appears, Git doesn’t actually see your code as discrete files; instead, it sees everything in your repository as just one big hunk of text—that’s how it can keep track of diffs (and renames) across files within a repository. Git’s “stupid” nature is part of its good design. This comes in handy when you’re working on two different features that may happen to be in the same file, and you want to break them into separate commits. There is a special option called git add—patch for that, which allows you to “stage” code by “hunk” rather than by whole file. Here’s a good overview from Ryan Tomayko on all the possibilities of the git index, and how to use add—patch.

One more thing about git-add I didn’t mention: If you create a brand new file that Git has never seen before, you’ll also need to use git-add so Git can start keeping track of it. This use of git-add, however, won’t put it directly in the staging area. Here I created a new file in the html5-boilerplate directory, filled in some text, and then saved it in my text editor. Git noticed there’s a new file in the directory that isn’t part of the repo yet:

prompt> git status
# Untracked files:
#   (use "git add ..." to include in »
what will be committed)
#
#       newfile.txt

If I want to commit that, I’ll first have to add it to the repo, then add it again (git add . adds every file it can find) to get it into the staging area. This time since there’s only one file, we’ll use the -a flag to add and commit at the same time:

prompt> git add .
prompt> git commit -am "added some random text"
[master d5c2ed1] added some random text
 1 files changed, 1 insertions(+), 0 deletions(-)
 create mode 100644 newfile.txt
 

With git-init, git-status, git-log, git-add, git-commit, and git-clone you have 90% of the tools you need to get stuff done with Git. But there are still a few more goodies to sweeten the pot.

Branching and merging

At some point, you’ll have an idea for an experimental new feature that is too big for a single commit. In this case, you may want to consider creating a branch. New branches are just copies of the state of a repository at any commit. In fact, we’ve been on a branch this whole time without knowing it. The default branch every Git repository starts out with is called “master.” There’s nothing special about master, but by convention, it is usually considered the “stable” branch alongside a development or experimental branch for new features or bug fixes.

To see all branches, and to create new branches, we use git-branch:

prompt> git branch
* master

There’s only one local branch in our html5-boilerplate example. Let’s make a new one and move to it:

prompt> git checkout -b development
Switched to a new branch 'development'
prompt> git branch
* development
  master

We now have two branches in the local repository. The star marks the current one. Git will always keep track of which branch you’re “sitting on” and it will stay there unless you explicitly checkout another branch. This will be important when we decide to merge.

Note that when you checkout a branch, the files in your working directory will change in place to wherever the code happens to be in that branch. By that, I mean that Git will actually “edit” your files to match the versions in the branch you are checking out, and you’ll see the changes “flip” in your text editor. Nothing will be lost as long as it’s committed (in one branch or another), just make sure you have saved files in your text editor before checking out another branch as your non Git-aware changes could be overwritten.

Let’s suppose we finished our new feature on this new development branch, and want to merge it into our master branch (because it’s ready to ship). To merge, we always checkout the branch we want to merge into and then merge the target branch into it:

prompt> git checkout master
Switched to branch 'master'prompt> git merge development
Updating d5c2ed1..9a66cbf
Fast-forward
 newfile.txt |    3 ++-
 1 files changed, 2 insertions(+), 1 deletions(-)

Now all your changes in development have been merged with master. Since that feature is done, we’ll delete the development branch:

prompt> git branch -d development
Deleted branch development (was 9a66cbf).

Developer Vincent Driessen has a good sketch of a Git branching workflow incorporating infinite master and development branches alongside mini deletable feature branches.

One more hint on branching: git-checkout has another, more controversial use that sometimes doesn’t have to do with branching at all. In fact, it’s usually thought of as a destructive command. If I made a change to newfile.txt, but did not stage it, and then ran git checkout newfile.txt, it would revert that file to the state it was in at the last commit with no undo. Like the “revert” option in Photoshop, it rewinds to the last state you told Git about the file. This can be dangerous but it can also be useful for digging yourself out of a rabbit hole (remember, Git doesn’t care about when you use an application’s save function, only when you deliberately use the commit command).

Likewise, you can use git-checkout to pluck a file out of another branch into your current branch. Say I’m on the master branch and I want the version of a file I committed on development, I could git checkout development myfile.txt and it would replace only that file in the current branch with the development version of the file. Think of git-checkout as a more destructive merge. It won’t ask questions before stomping over your work, so use it with caution.

Next steps…

There’s much more to Git that’s beyond the scope of this article. Working with remote repositories, for example, is a large and exciting aspect of the Git workflow, and the cornerstone of the popular “social coding” site GitHub. Whether you’re a programmer, blogger, or designer, Git is an excellent way to keep track of, share, and backup your work.

Additional resources

17 Reader Comments

Load Comments