Lecture 3: Branches in Git

Basic Branching

  • We have encountered branches a few times so far but we haven't really said much about what they are or why they're important. They are very important. In fact, they form a core piece of the Git workflow.

  • A Git branch is a "Sticky Note" on the graph. All branch work takes place within the same folder within your file system. When you switch branches you are moving the "Sticky Note".

  • Suppose you have a newly initialized repository. Your first commit is represented by the A block in the figure below.

A default branch is created and Git named it master. The name master has no special meaning to Git.

Now suppose we make a set of two commits (B and C). The master branch (and our pointer) moves along.

So far so good. Suddenly we find a bug! We could work on the bug in master but that's not really a good idea. It would make a lot more sense to branch off of master and fix the bug on its own branch. That way, we don't interfere with things on master. We'll discuss the details of how to create branches in the lecture exercises. For now, suppose we create a new branch called bug1.

The new branch is a pointer to the same commit as the master branch (commit C) but the pointer moved from the master branch to the bug1 branch.

We do some work on the bug1 branch and make two more commits. The pointer and branch now move to commit E.

  • Now you decide that the bug you found has been fixed.
  • You've modified a file and maybe even added a new file.
  • You can switch back to the master branch.

  • What you'll see is that none of the files that you just fixed and/or created are in your working directory!

  • The first couple of times you see this, it feels really uncomfortable.
  • However, this is the correct Git workflow.
  • How do we get the bug fix into our master branch?

  • We already know the command. From the master branch, just do git merge bug1.

This looks really nice! The merge brought the two change histories together prefectly.

  • The only thing left to do is to delete the bug1 branch.
  • We don't need it anymore and so we really don't want it floating around.
  • To delete a branch you simply write git branch -d bug1.

This looks like a nice clean tree now. If only things were always this simple.

Nonlinear Histories: Workflow Choices

Another common scenario is as follows:

  • We created our "story branch" off of commit C to address some bug (not the same bug as before!). Call this branch bug2.
  • However, some changes have happened in master since we branched off of C. For example, bug1 has been merged into master.
  • We have made a couple of commits on bug2.

Here's the current graph:

Once we're ready to merge our bug fix, we switch back to master.

Now we attempt to merge. Our attempted merge should connect the new version H to both E and G (H came from E and G).

This merge can be quite unpleasant if there are conflicts.

Now we delete the bug2 branch since the bug fix has been successfully merged into master.

  • The graph is now a bit of a mess.
  • The history is nonlinear.
  • There's nothing particularly wrong about this.
  • However, such a history makes it hard to see the changes independently.
  • Things can get very messy over time. What if another branch came off of G? You could have multiple loops!

There is another way to do merges that helps "linearize" the graph. Let's pick up with our bug2 branch just before we switched to the master branch for a merge.

This time, instead of starting the merge process right away, we'll first "rebase": git rebase master.

What does rebase do?

  • Take the changes we made off of C and undo them, but remember what they were
  • Re-apply those changes on E instead

Now we proceed as usual:

  • git checkout master
  • git merge bug2

  • Now we get a nice linear flow.

  • Moreover, the actual change set ordering in the repo mirrors what actually happened. That is, F' and G' came after E rather than in parallel to it.
  • Of course, we have re-written history; this is controversial.