Drew's Blog - Home - Archive

Drew's Guide to Git, Part 2

I frequently find myself needing to teach my friends git. This is the second in a series of n posts to quickly get you up to speed with git.

Ok, so now you can add and commit things. This is great when you only have one thing to work on at a time. But what happens if you want to try something out and you're not sure if it'll work or not? Well, my friend, this is what branches are for.

To create a new branch, type git branch new_branch_name. This will create a new branch. (To see a list of all your branches, just type git branch. The one with the * next to it is your current branch.) To switch between branches, you must git checkout branch_to_switch_to. Now, you can add and commit new changes to each branch in parallel.

At some time, you will want to incorporate the changes you've made back in to your "main" branch. (Called "master" in git by default.) to do this, you need to understand two tools: git merge and git rebase.

git merge functions a lot like svn merge. You can use it to merge different revisions between branches. If you have two branches going, say, "foo" and "bar", and want to merge your changes from foo into bar, here's what you need to do:

  1. git checkout foo - switch to foo (not necessary if you're already working on foo)
  2. git merge bar - merge bar into foo

Easy, huh? Now, let's backtrack a bit. There are two types of merges: fast-forward merges, and merges that create merge commits. If you'll remember from Part 1, every commit has a parent commit (the commit that came right before it). A fast-forward merge happens when the last commit on foo is the parent of a commit in bar. It's called a fast-forward merge because foo is fast-forwarded to where bar is. Fast-forward merges are generally desirable because you retain the commit history of bar in an easy-to-follow way.

Merge commits happen when the last commit of foo is not a parent of any commit in bar. In this case, git will create a commit that is essentially equal to the diff of foo and bar. However, this is undesirable because the commit history has been messed up, because this commit doesn't reflect the work that was actually done, just the diff of two branches at any given time. How do we fix this? With git rebase.

Generally, foo and bar will have a commit that is a common "ancestor", that is, a commit that is the parent of commits in both foo and bar. (It might be theoretically possible for this not to happen, but you'd have to try really hard and do things like branch from an empty repository, so it won't happen by accident.) What git rebase will do is replay all of the commits on bar since the common ancestor on to the end of foo. (This is also called "reparenting", since the first commit since the common ancestor on bar is changed to have its parent be the latest commit on foo.) Here's how to rebase:

  1. git checkout bar - switch to bar (not necessary if you're already working on bar)
  2. git rebase foo - reparent bar on to foo

Now, at this point, you may have some conflicts. You can use git status to see which files have conflicts, and then you'll need to edit them so they don't anymore. Then use git add to mark them for add, and git rebase --continue to continue with the rebase. If you get to a situation where your rebase is stuck and you don't know what to do, you can always git rebase --abort to stop rebasing and go back to where you were.

Now, you have a bar that is reparented on to foo, so you can git checkout foo and git merge bar to have a nice, clean fast-forward merge!

Up next: remote branches and labels.

If you've read this far, you should probably follow me on Twitter.

See more posts

Drew writes code for fun and (sometimes) profit. He's currently studying Computer Science at Carnegie Mellon University. He has previously worked at Facebook, Amazon, and a startup called Intersect.