Friday, November 11, 2011

Learn Git progressively - Day 3

You’re reading Part Three of the “Learn Git progressively” four-part tutorial.

  1. Basics - Setup, init, add, and commit
  2. Daily usage - status, diff & difftool, and log
  3. Branching & Merging - branch, checkout, and merge
  4. Collaborative coding - remote and Github.com

This is a follow-up to my previous post, Learn Git progressively - Day 2. I suggest reading that post before continuing here.


At this point, you’re familiar with the basic usage of Git; you’ve created a new repository and stored a bunch of stuff in it, you can determine what’s going on in your repository, and you know how to access history. Now let’s get into one of the most useful benefits of using a good version control system; branching & merging.

Branches in Git
Git branches, visualized. Image from Vincent Driessen’s Git branching tutorial.

One of the programming paradoxes is that in order to add new features to existing code you have to “break” the program, such that the program won’t run properly until the new code is complete. Wouldn’t it be great if there was a way to maintain a copy of code that was always able to run, while simultaneously go about your usual development work, without having to maintain two separate folders?

Enter branches. Recall from our Day 1 discussion that committing code involves were storing a snapshot of the entire directory. Well, the branch feature of Git allows you to have multiple versions of your directory (i.e., your entire project) existing side by side. You can have as many copies of your code like this as you want, and you can create and delete them with a single simple command.

One common pattern for using Git branches is shown in the image to the right. In this pattern, we maintain two branches — one for development and one for the current stable version. The “stable” branch (usually named “master”) can always compile and work just fine, and the “development” branch is where all your work is stored. After you complete the project in the “development” branch, you can use the merge feature — also a single simple command — to bring all changes in the development branch into “master”.

Branches in Git
Git branching for bug fixing. Image adapted from Vincent Driessen’s Git branching tutorial.

Branches also have some pretty nifty implications for debugging. Consider the following scenario: you released the 1.0 version of your software and are deep in development of the 1.1 update. You’ve added a whole bunch of new features and bug fixes. While in the middle of coding, you notice a new email; it’s a bug report for a bug you haven’t encountered yet, detailing a critical bug that exists in both the 1.0 stable version and the 1.1 development version. What do you do?

You branch, of course! As detailed in the figure to the left, you can check out the 1.0 version, create a new branch called “hot fix”, fix the bug, and push the changes into both the master and development branches. You recompile the “master” branch and push it live. Now the bug is fixed in both the old version and the new version, with just a few simple commands.

Branches are exceedingly useful, and I’ve only barely described how useful they can be in development. I strongly recommend you read Vincent Driessen’s Git branching tutorial to get a better feel for how branches should be incorporated into your project. For now, I’m going to simply describe how they are used.

branch and checkout

You can see all available branches by typing git branch:

$ git branch
* master

When you begin a project, there’s only one branch, named “master”. The * next to master indicates that you’re currently working on the “master” branch. Creating a new branch is almost stupidly simple; to create a branch named “newFeatures”, type the following into your terminal:

$ git branch newFeatures

That’s it. Now, if you type git branch, you’ll see that there are two branches:

$ git branch
* master
  newFeature

In order to do work on the “newFeature” branch, you’ll have to checkout that branch, using the git checkout branch:

$ git checkout newFeature
Switched to branch newFeature

OK, so now you have this new branch… how do you use it? Well, the interesting thing about branches is that commits made to a branch only exist on that branch. Let’s consider the two branches we talked about earlier, “master” and “development”. If you make a bunch of changes and commits to the “development” branch, those changes are only present on the “development” branch, and they won’t be present when you checkout the “master” branch. This may sound simple, but it’s often hard to visualize. To get a solid grip on how branches work, I highly recommend you try out the following on your computer:

  1. Make a test repository (new folder, git init), create a file or two, check them all in (git add ., git commit -m 'initial commit')
  2. Create a new branch (git branch newBranch) & check it out (git checkout newBranch)
  3. While on the new branch, create a file or two, make some changes to the existing files, and then add and commit the changes
  4. Open a Finder/Explorer window and navigate to your test repository
  5. Switch between the two branches (git checkout master; git checkout newBranch) and observe the files changing in the window. Examine the code in each file as you switch branches.

You can see a short video of this process on YouTube.

If you do this, you’ll see that as you check the branches out, the contents of the folder change. Git is storing all the files in it’s hidden database. When you check out a branch, Git populates the folder with the necessary files, and you don’t have to manually keep track of things.

Branches make it easy to keep new features separate until they’re complete. This simplifies debugging — fewer potential problem areas — and makes keeping track of changes much simpler.

merge

At some point, you will have completed your work in the “development” branch, and you’re going to want to bring all the changes in “development” over to the “master” line. This is a two-step process:

  1. Check out the branch you want to receive the changes. In this case, we’re going to check out the “master” branch.
  2. Run the following command:

    git merge development
    

This brings all the commits in the “development” branch in to the “master” branch. If this command runs successfully, then you’re done! All your work is now in the master line. You can now run git checkout development and begin working on your next feature set.

I say “if this command runs successfully”, because sometimes merges fail due to merge conflicts. A conflict occurs when changes have occurred in both branches to the same line, and Git can’t figure out how to combine the changes. You’ll know you have a conflict because Git will tell you explicitly when executing the merge:

$ git merge development
CONFLICT (content): Merge conflict in file.m

At this point, if you look in file.m, you’ll see something that looks like this:

<<<<<<< HEAD:file.m
if (value == TEST_CONSTANT)
=======
if (value == "test" || value == "test2")
>>>>>>> development:file.m

HEAD here means “the branch you’re currently working with”, which is “master”. This means that “in the master branch, this line looks like if (value == TEST_CONSTANT), and in the development branch, this line looks like if (value == "test" || value == "test2")”. Resolving the conflict is a two-step procedure:

  1. Edit the file so that it looks the way you want it to. This includes removing the lines beginning with <<<<<< and >>>>>> as well as removing the code you don’t want stored in the file.
  2. add the fixed file, and commit to resolve the conflict.

Continue on to Day 4 of Learn Git progressively, covering git remote and an overview of GitHub.com.