Learn Git progressively - Day 3
You’re reading Part Three of the “Learn Git progressively” four-part tutorial.
- Basics - Setup,
- Daily usage -
- Branching & Merging -
- Collaborative coding -
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.
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 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 * 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:
- Make a test repository (new folder,
git init), create a file or two, check them all in (
git add .,
git commit -m 'initial commit')
- Create a new branch (
git branch newBranch) & check it out (
git checkout newBranch)
- While on the new branch, create a file or two, make some changes to the existing files, and then add and commit the changes
- Open a Finder/Explorer window and navigate to your test repository
- 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.
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:
- Check out the branch you want to receive the changes. In this case, we’re going to check out the “master” branch.
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:
- Edit the file so that it looks the way you want it to. This includes removing the lines beginning with
>>>>>>as well as removing the code you don’t want stored in the file.
addthe fixed file, and
committo resolve the conflict.