February 12, 2010

Everday Git

This tutorial walks you through a normal session with git. In particular we are going to build the structfun3.c example from scratch in the order that you would actually construct it as a C project. This tutorial doesn’t require a GitHub account. It just shows you how you can use git in your day to day projects. In order to get through this tutorial you should grab a copy of GitX the OS X Git repository viewer here.

Often when you code you’ll find yourself wishing you had a way to move back and forth between the many ideas about you have about a project- that is, you know you have a working stable version and you have a couple ideas about how to approach the next step. Usually this requires a very haphazard amount file duplication and moving. This is generally very error prone (“Was it foobar7.c or foobar9_2.c?”).

Revision control was specifically designed to handle this problem. Subversion is now gaining a lot of popularity because of it’s widespread adoption (Google Code, Sourceforge) and the emergence of decent UI clients for various platforms. Subversion works well for projects that are centralized- a project that has very good division of labor or a project involving one person. So it’s good for projects involving a LOT of people and projects involving one person. It is actually quite deficient for anything inbetween.

One reason it is inadequate is because of this centralization. If the repository is remote, you can only commit changes to your project if you have network access, and there’s a lot of reasons that you might not. Also, Subversion by this design actively discourages frequent commits, this is because frequent commits might “break the build” for other people working on the project.

Git solves this in two ways. First, everyone has the entire repository. That’s what is meant by cloning in git. You are cloning an entire copy of the repository. This also means that if the main server for some reason should go down, many people have the entire history of the project on their computers and it can easily be restored. Git is decentralized.

Another benefit also results from everyone having a full clone of the repository- you can commit work even if you’re not online. Git also has something called branching (which works radically differently from Subversion branches). This allows you to work on experimental ideas without affecting other parts of the project. Git also includes many commands so that when you’re ready to commit your work, your commit history integrates seemlessly into the main line of development.

Creating a Repository and Committing Changes

This probably sounds very abstract, so let’s get started. Create a directory on your Desktop called structgit. In this directory create a file called structgit.c, and paste the following into that file.

#include
#include 
 
int main(int argc, char** argv)
{
  printf("Hello, world!\n");
  return 0;
}

Open up Terminal.app and type the following lines onto the command line.

> cd ~/Desktop/structgit/
> git init
> git add structgit.c
> git commit -m "My first git commit"

You should see some relevant output after git init and git commit. You just told git that you want to create a repository out of the current directory with git init. Then you added structgit.c to the newly created repository, saying that you want this file to be added with the next commit. Finally git commit actually committed all of your changes.

Now let’s change a line of code and see what happens. Add a line following the “Hello, world!” line:

printf("A new line!\n");

Launch GitX. Click on the GitX application menu. You should see a menu item that says Enable Terminal Usage. Select this item. It’ll ask you for your admin password. Go ahead and type it in. Switch back to Terminal.app and type the following on the command line followed by enter:

> gitx

GitX will open up the directory into it’s repository viewer. It’ll look something like this:

gitrepo

This shows you all the commits you’ve made to this repository thus far. This is the History View. Let’s look at the Commit View. The top left most segmented button toggles between these two states. Click on the Commit View button now. This view is split into 4 main areas. The large top area shows the changes in the currently selected file. All the files in repository are shown the lower left panel, including ones that git doesn’t know about yet (files that you haven’t added to the repository). In the center is where you type in your commit messages, this is exactly the same as passing the -m flag followed by a string to the git command line tool. The right panel lists all of the changes you’ve said you want to be included into the commit.

If you’re coming from Subversion this should come as a little bit of shock. Git lets you selectively commit your commit changes as you see fit. This allows you to control how your history looks to the rest of the work. structgit.c should be green in the lower left pane, this means that it is a file that git knows has changed. Click on it. Git will show the changes in the large view. In this view each discernible modification gets its own stage button. This means you can actually cherry pick which modifications from within the file you want to commit!

Lets stage this trivial change. Click the stage button. The file will now appear in the Staged Changes panel. Type in a commit message like “My second commit” and click the commit button. Click back to the History View. Your repo should now look something like this:

gitrepo2

So far, if you’re a Subversion user, this seems mostly like normally stuff. Go ahead and compile this C program with the following lines to make sure everything works:

> gcc -o structgit structgit.c
> ./structgit

Hopefully everything is OK. We now have a stable version of our project. We could keep on working but now’s about the time in which we should introduce some radically new concepts about Git’s workflow.

Branching

Let’s create a branch. Unlike SVN this really isn’t a very scary process. And we also don’t need to worry as much about bringing in changes because Git has an amazing feature called rebase which we’ll talk about later. In terminal app type the following commands:

> git branch develop
> git checkout develop

You are now on a different branch. So far nothing seems to have happened. Let’s edit your file. Add the following line after the second printf:

printf("Added on the develop branch!\n");

Lets do everything from the command line this time:

> git add structgit.c
> git commit -m "Committed on my new branch."

You’ve committed changes to the develop branch. You master branch has in fact not changed one bit. Let’s switch back to it:

> git checkout master
> more structgit.c

Wow the changes are gone.

> git checkout develop
> more structgit.c

No they’re back. Hopefully it’s starting to become clear that branches allow you to have multiple simultaneous lines of development. master can hold your current stable work, while develop holds your thought processes. You can even create branches from branches. For example you could create a branch from develop called experimental where you hash out really crazy ideas.

Say you accidentally make a commit and you want to roll back the clock. There are several ways to do this. In particular it’s often useful to move backwards in time without damaging any of the other work that you’ve done. For example this new line that you’ve added is somewhat pointless. Lets go ahead imagine that we made a breaking change and we want to go back in time (we also don’t want throw away our broken work, maybe there was some good ideas there). Run the following:

> git log

You should see a log of commits. Unlike SVN git uses SHA1 hashes to identify it’s objects. Fortunately you only need to type in 6-7 characters at most (Git will tell you if you need more). Find the commit hash of the first commit. Then type the following:

> git checkout YOUR_HASH structgit.c
> git commit -m "Switched back to earlier version"

Switch over to GitX, make sure you are in the History View and type Command-R to refresh the History View. You should now see the changes you made.

Merges

Now let’s do some real work. We want to create a struct that can hold an x and y value and points to the next vector structure. Let’s define some structs, add the following after the #includes.

// Define Vector
typedef struct Vector
{
  float x;
  float y;
} Vector;
 
typedef Vector* VectorRef;
 
// Define LinkedVector
typedef struct LinkedVector
{
  Vector point;
  struct LinkedVector* next;
} LinkedVector;
 
typedef LinkedVector* LinkedVectorRef;

Let’s do a commit here. This time we can just do it from GitX. Press Command-R to refresh GitX and make sure you’re in the Commit view. Go stage your change and type in a commit message, something like “Added struct definitions”.

Now let’s modify main so that we can see if our work was good. Change main to look like the following.

int main(int argc, char** argv)
{
  Vector v1 = { 5.0f, 2.5f };
  LinkedVector vref = { v1, NULL };
 
  printf("<%f, %f>\n", v1.x, v1.y);
  printf("%p\n", vref.next);
 
  return 0;
}

We’re not going to commit right away this time. We going to add some more code. This time code for create linkable vectors, go ahead and paste the following into your code before the LinkedVectorRef typedef.

// function to create linked vectors
LinkedVectorRef CreateLinkedVector(float x, float y)
{
  LinkedVectorRef newLinkedVector = malloc(sizeof(LinkedVector));
 
  newLinkedVector->point.x = x;
  newLinkedVector->point.y = y;
 
  return newLinkedVector;
}
 
void DestroyLinkedVector(LinkedVectorRef v)
{
  v->next = NULL;
  free(v);
}

Save your file and switch to GitX. Make sure to switch to the Commit View if you aren’t already there and refresh. Note the context slider at the top right. Drag this all the way to the left. Wow. This allows you the break down the chunks of the changes. Git allows you commit only portions of your file. Lets try this now. Stage only the changes to main and set the commit message to “Fixed main” and commit. Then stage the new function declarations, set the commit message to “add linked vector functions” and click commit. Now click on your History View. You should see all of this reflected.

Just for kicks click on the branches drop down menu. Select master. You can see that history of master and develop have greatly diverged. We’ll fix that momentarily. Select the develop branch again.

In Terminal.app enter the following:

> gcc -o structgit structgit.c

You should get an error that looks like the following:

edgit

Oops looks like tried to define a return type before we actually declared that type. Lets fix that. Move the LinkedVectorRef definition before the CreateLinkedVector function. Try running the gcc command above again. This time it should work. Great. Let’s commit our changes, this time from the command line.

> git add structgit.c
> git commit -m "Fixed bug."

No let’s switch to master and merge.

> git checkout master
> git merge develop

Rebase

Let’s checkout develop again. Let’s make a minor change. Make main look like the following:

int main(int argc, char** argv)
{
  Vector v1 = { 5.0f, 2.5f };
  LinkedVector vref = { v1, NULL };
 
  // Here's a comment on the develop branch to demonstrate rebase!
 
  printf("<%f, %f>\n", v1.x, v1.y);
  printf("%p\n", vref.next);
 
  return 0;
}

Go ahead and commit your changes. Now imagine that you are collaborating with another hacker. You realize that you have agreed to leave adequate comments about the role of the various definition. Oh man you forgot and you’ve already started working on something entirely new (in this case you just added a comment to your develop branch, but pretend the change was more significat). git has a solution for this. First checkout master. This will cause all of your changes to the develop branch to disappear as expected.

Now let’s add a comment to the LinkedVectorRef definition like so:

// Get rid of that ugly C pointer
typedef LinkedVector* LinkedVectorRef;

Go ahead and commit this change, and say something like “Fixed bug in master” for the commit message. Great. You’ve fixed the problem.

Hmm, but what about develop? You could do a merge but that would be lame, your fix on master would appear on top of you work which you haven’t finished yet. Wouldn’t it be nicer if git could make it so it seemed like your comment on master was added before you started working on this new cool stuff?

Enter rebase. This is what rebase does. It allows you to bring in changes to a branch and apply these changes before the changes on the branch. git not only encourages commit often, it encourages rebase often. This allows branches to stay conflict free without too much effort. So checkout the develop branch. And type the following into Terminal.app:

> git rebase master

Now look at your repository history for the develop branch in GitX. You should see that the commit from master now comes before your last commit on develop. Nice.

That’s enough git for you to do some damage. I highly recommend reading the following resources:

Git -> SVN Crash Course
Git From the Bottom Up

Comments (1)

  1. February 15, 2010
    pholz said...

    the pdf link is broken. correct link: http://ftp.newartisans.com/pub/git.from.bottom.up.pdf

Leave a Reply