Branches and Merging

A Brief Intro to Git


Git has two main functions. We've already covered the first, which is to create a record of all the changes we make to our code. Git's second function is allowing several people to work on the code at once without getting in each other's way. Branches let us do that.

1   Branches

A branch is a special label that points to a commit.

Let's use our special git shortlog command to remind ourselves what our repository history looks like.

$ git shortlog
* 7d6a299 -  (HEAD -> master) Added a variable to hello.py
* 823459f -  Add hello.py

The tag master is the name of the branch, which is the default name Git uses for the first branch when a repository is created. So for now, the master branch points to commit 7d6a299. The HEAD tag points to the branch we are actively working on; in Git parlance, we say this is the branch we have "checked out".

Traditionally, the master branch is the "final draft" branch. When we try something new with the code that may not work right away, we want to keep a working copy of the code we can fall back to if necessary (or if someone else wants to try a different innovation at the same time you are trying yours). To keep the master branch safe, we create a new branch where we can work on our new feature.

Let's add a subroutine to hello.py on a new branch.

1.1   Create and checkout a new branch

First, list all the branches in the repo:

$ git branch
* master

Just one, master. The asterisk marks the currently active branch (the one that's checked out).

Now, create a new branch called add-subroutine.

$ git branch add-subroutine

and list all the branches again.

$ git branch
  add-subroutine
* master

So now we have our new branch. Next, check it out so it's the active branch.

$ git checkout add-subroutine

And now we see that Git says we're on the add-subroutine branch.

$ git branch
* add-subroutine
  master

$ git status
On branch add-subroutine
nothing to commit, working tree clean

If we check shortlog, we see that HEAD is pointing to add-subroutine.

$ git shortlog
* 7d6a299 -  (HEAD -> add-subroutine, master) Added a variable to hello.py
* 823459f -  Add hello.py

Note: There's a shortcut that lets you create and checkout a branch all in one command: git checkout -b <name-of-branch>.

1.2   Work on the new branch

Adding to the branch is nothing we haven't already done. After you've checked out a branch, any commits you make are added to that branch and that branch alone. Let's use this branch to add a subroutine to our code.

Create a file called superprinter.py that looks like this:

def print_thrice(input_string):
    """ Prints `input_string` three times """
    print(input_string)
    print(input_string)
    print(input_string)

Add superprinter.py to the repo and commit it:

$ git status
On branch add-subroutine
Untracked files:
  (use "git add <file>..." to include in what will be committed)

        superprinter.py

nothing added to commit but untracked files present (use "git add" to track)

$ git add superprinter.py

$ git status
On branch add-subroutine
Changes to be committed:
  (use "git reset HEAD <file>..." to unstage)

        new file:   superprinter.py

$ git commit -m "Add superprinter"
[add-subroutine eb198f3] Add superprinter
 1 file changed, 5 insertions(+)
 create mode 100644 superprinter.py

If you look at the log, you can see that branch add-subroutine points to our new commit and that master does not.

$ git shortlog
* eb198f3 -  (HEAD -> add-subroutine) Add superprinter
* 7d6a299 -  (master) Added a variable to hello.py
* 823459f -  Add hello.py

Let's make one more change so that hello.py uses our subroutine:

to_print = "Hello, world!"
print_thrice(to_print)

Commit the change:

$ git add hello.py
$ git commit -m "Use print_thrice"
[add-subroutine fed0858] Use print_thrice
 1 file changed, 3 insertions(+), 1 deletion(-)

$ git shortlog
* fed0858 -  (HEAD -> add-subroutine) Use print_thrice
* eb198f3 -  Add superprinter
* 7d6a299 -  (master) Added a variable to hello.py
* 823459f -  Add hello.py

Our two branches have diverged further. But, we can go back to master any time we want.

1.3   Checking out a different branch

Before we do anything, let's run our code to make sure we're using print_thrice:

$ python hello.py
Hello, world!
Hello, world!
Hello, world!

Looks good. Now let's go back to the master branch for a bit, just to check that we can.

$ git checkout master
Switched to branch 'master'

$ git shortlog
* fed0858 -  (add-subroutine) Use print_thrice
* eb198f3 -  Add superprinter
* 7d6a299 -  (HEAD -> master) Added a variable to hello.py
* 823459f -  Add hello.py

Recall that HEAD points to whatever we're currently working on (what have checked out).

By checking out master, Git has reverted our code directory to be exactly like it was before made our add-subroutine commits. The new file superprinter.py is gone:

$ ls
hello.py

and hello.py looks like it did before:

$ cat hello.py
to_print = "Hello, world!"
print(to_print)

$ python hello.py
Hello, world!

Of course, we can recover our add-subroutine commits by checking out that branch again.

1.4   Making a second branch

Let's pretend that someone else on our team is the one working on add-subroutine and that our job is to come up with a quote more inspiring than "Hello, world!".

$ git checkout -b new-quote
Switched to a new branch 'new-quote'

$ git branch
  add-subroutine
  master
* new-quote

$ git shortlog
* fed0858 -  (add-subroutine) Use print_thrice
* eb198f3 -  Add superprinter
* 7d6a299 -  (HEAD -> new-quote, master) Added a variable to hello.py
* 823459f -  Add hello.py

Now let's change the to_print variable in hello.py:

to_print = "That rug really tied the room together, did it not?"
print(to_print)

and commit the changes:

$ git add hello.py

$ git commit -m "Change to_print string"
[new-quote 44425b9] Change to_print string
 1 file changed, 1 insertion(+), 1 deletion(-)

Now, if we look at our log, we can see why branches are called "branches":

$ git lg
* 44425b9 -  (HEAD -> new-quote) Change to_print string
| * fed0858 -  (add-subroutine) Use print_thrice
| * eb198f3 -  Add superprinter
|/
* 7d6a299 -  (master) Added a variable to hello.py
* 823459f -  Add hello.py

The branching is even more evident if we use a graphical Git interface like Sourcetree:

Git graph

We now have three named versions of our code, master, add-subroutine, and new-quote. The next step is bringing these branches back together.

2   Merging

Being able to create multiple versions of our code is not very helpful if we can't reconcile the multiple versions and combine them somehow. Git handles this with the merge command.

First, check out the "older" branch that needs to be updated.

$ git checkout master

Then merge the branch you want to keep. In our research group, we will also use the "no fast forward" option to make it more apparent where merges occur. Let's merge add-subroutine first.

$ git merge --no-ff add-subroutine
Merge made by the 'recursive' strategy.
 hello.py        | 4 +++-
 superprinter.py | 5 +++++
 2 files changed, 8 insertions(+), 1 deletion(-)
 create mode 100644 superprinter.py

$ git subroutine
*   fdebe3f -  (HEAD -> master) Merge branch 'add-subroutine'
|\
| * fed0858 -  (add-subroutine) Use print_thrice
| * eb198f3 -  Add superprinter
|/
| * 44425b9 -  (new-quote) Change to_print string
|/
* 7d6a299 -  Added a variable to hello.py
* 823459f -  Add hello.py

2.1   Resolving conflicts in merges

Now merge new-quote to master. Because our two branches both changed hello.py, Git can't easily merge the changes.

$ git merge --no-ff new-quote
Auto-merging hello.py
CONFLICT (content): Merge conflict in hello.py
Automatic merge failed; fix conflicts and then commit the result.

$ git status
On branch master
You have unmerged paths.
  (fix conflicts and run "git commit")
  (use "git merge --abort" to abort the merge)

Unmerged paths:
  (use "git add <file>..." to mark resolution)

        both modified:   hello.py

no changes added to commit (use "git add" and/or "git commit -a")

Despite the alarming CONFLICT warning, merge conflicts are nothing to be scared of. Git will create a version of the file with the conflicts marked with <<<, ===, and >>>.

$ cat hello.py
<<<<<<< HEAD
from superprinter import print_thrice

to_print = "Hello, world!"
print_thrice(to_print)
=======
to_print = "That rug really tied the room together, did it not?"
print(to_print)
>>>>>>> new-quote

Now you can pick and choose which parts of each version you want.

Combine them so that hello.py looks like this

from superprinter import print_thrice

to_print = "That rug really tied the room together, did it not?"
print_thrice(to_print)

This implements both major changes we made. Once we're done editing the file, we finish the merge:

$ git add hello.py

$ git merge --continue
[master 2f40bfd] Merge branch 'new-quote'

Looking at the log, we can see that all of our changes have been incorporated into master.

$ git shortlog
*   2f40bfd -  (HEAD -> master) Merge branch 'new-quote'
|\
| * 44425b9 -  (new-quote) Change to_print string
* |   fdebe3f -  Merge branch 'add-subroutine'
|\ \
| |/
|/|
| * fed0858 -  (add-subroutine) Use print_thrice
| * eb198f3 -  Add superprinter
|/
* 7d6a299 -  Added a variable to hello.py
* 823459f -  Add hello.py

At this point, we don't need the add-subroutine and new-quote branch labels, so we can delete them.

$ git branch -d add-subroutine
Deleted branch add-subroutine (was fed0858).

$ git branch -d new-quote
Deleted branch new-quote (was 44425b9).

$ git shortlog
*   2f40bfd -  (HEAD -> master) Merge branch 'new-quote'
|\
| * 44425b9 -  Change to_print string
* |   fdebe3f -  Merge branch 'add-subroutine'
|\ \
| |/
|/|
| * fed0858 -  Use print_thrice
| * eb198f3 -  Add superprinter
|/
* 7d6a299 -  Added a variable to hello.py
* 823459f -  Add hello.py

Next in "A Brief Intro to Git": Remotes: Sharing Code Online