A Guide to Git: Branches and Merging

Nilay Majorwar
Programming Club, IIT Kanpur
11 min readJan 17, 2021

In Part 1, we covered initializing a Git repository, staging our changes, committing our work and checking out past versions of our project. We also took a little dive into the backstage of how the above operations happen inside Git.

This time, we’ll cover some of the more complex concepts and operations of Git — in particular, we’ll dive into how commits are organised inside Git and have a go at using branches and merging them. We’ll be focusing a bit more on theory this time, so follow closely.

Git and Collaboration

Till now, we’ve used Git simply as a personal book-keeping butler, ignoring one of the most important use cases of a powerful version control system — collaboration. Version control systems are built to allow seamless and nearly painless collaboration between members of a project. Linus Torvalds developed Git when BitKeeper failed to keep pace with the rapidly growing Linux development community and the kernel project itself (amongst some other reasons).

One of the features of version control systems that allow for easy collaboration are branches, which we’ll be covering in this part. So from now, we’ll assume that we are working with some other authors too on our novel.

Branching

Our book’s going pretty good, but we forgot something — we didn’t add headings on any of our poems! Normally, we would just add headings to our existing files and make a commit for it. But now that we are working with other people too, we need to ensure we don’t get in the way of others.

An example of a project extensively using Git branches. Different colours denote different branches, and asterisks are points where one branch merges into another. This’ll look simpler by the end of the article.

When people collaborate, it is good practice to keep your work in a separate space to avoid clash of work (imagine five people writing an article on the same piece of paper). The general workflow is to create a separate space for your task, do your task in this space and when done, merge your space into the main line. In Git, this is done via “branches”. Let’s try it.

Here, the task we want to do is to add headings to our existing poems. So we’ll name the branch as add-headings (branch names should not contain spaces). If you’re here from part 1, be sure to checkout to the current, latest version.

# Checkout to current version
git checkout master
# Create a new branch for adding headings
git branch add-headings
# Switch to the branch we just created
git checkout add-headings

Fair enough. Now we’ve created a branch and switched to it, so that our active branch is add-headings. Let's actually add our headings now and commit the changes. Open each poem file and add a heading at the top of the contents.

# DIY: Add a heading to each poem
# THEN, commit the changes:
git add .
git commit -m "Add poem headings"

Note how we staged changes from all the existing files using git add .. The character .(period) stands for the current directory in Bash and other shells, so the command basically means "stage all changes in the current directory". Also, this commit will be added on the new branch we created.

Great, so you just created a new branch and committed some work to it. Branches are simply a way to create parallel lines of work. Besides direct collaboration, they are often used to:

  • Work on and track progress of several tasks in parallel
  • Separate stable and unstable versions of a project
  • Maintain utility stuff like project websites in a separate branch

What’s underneath?

In the first part, we just dealt with commits like it’s a straight line of dominoes. But now, branches change that — now this straight line seems to split and diverge. This leads us to the million-dollar question: how are commits organised in Git?

Formally, one would say they are organised in the form of a tree, where each commit has a certain “parent” commit that roughly translates to the previous commit. But you may like a simpler analogy — let’s say we’re exploring a forest, and dirt paths magically appear wherever we walk. Whenever we commit, we set down a flag in the ground.

So, when we start out in the forest, we create the main path that represents the current state of the project. This main path represents the branch master, and on every commit we set down a flag. When we created a new branch for a side task, we basically decided to take a detour onto a dirt path on the side, called add-headings.

Actually, a branch is simply an alias, or a nickname, for a commit. So, add-headings is just a nickname for the commit "Add poem 3", and similarly for master. Maybe now you can realise that the two "variants" of the checkout command - one for checking out commits, and one for switching branches - are actually the same thing, only referring to commits in different ways.

So technically, branches are not paths but actually nicknames for the latest flag on a path (or any flag, in general). But for beginners, it’s easier to think of branches as paths instead of just commit aliases, so we’ll try to move forward with this analogy as much as possible.

Work done on the main line

Remember how we work on a personal space so that our work does not clash with others, and then at the end of our work, we merge work done on our personal space into the main line. This means that by the time we get to merge our work into the main line, it’s possible that the main line is updated with someone else’s work — and this happens all the time when collaborating with others.

Since for this guide we don’t really have actual co-authors, we’ll have to simulate “others’ work” ourselves. Let’s switch back to master (the main branch, that is) and create a commit with poem 4 that represents the work of a co-author. Also, assume that you already instructed your co-authors to start adding headings to the poems they write.

# Switch to the main branch, master
git checkout master
# DIY: Add poem 4 with a heading
# THEN,
as usual, stage and commit

git add .
git commit -m "Add poem 4"

So now, while you were working on adding headings to the first three poems of the book, one of the co-authors went ahead, added poem 4 (with heading) and merged his work into master. All that's left now is to merge our work into the main line.

Merging one branch into another

Let’s assess the commit tree as it is right now. This is what it looks like:

We want to merge our work done in the add-headings branch into the main line master, so the commit tree we need after merging should look something like this:

You might be wondering how such a situation is resolved in Git. It would’ve been much easier if no one made any changes to master, no? Then all Git would have to do is to copy our work to master, which is very easy (this is called fast-forwarding, since Git "fast-forwards" the main line to your branch).

Turns out Git does have a very nice way to resolve such situations, called a three-way merge. This involves creating a merge commit that adapts your work to the work done on the main line. If your work doesn’t clash with the work done on the main line, Git can do the merge operation all by itself. Pretty neat, right?

Let us try this. Since there’s something we need to do on this repository later, we’ll create a copy of our project directory as follows:

# Right now, you're in the poems directory
# Go to the parent directory

cd ..
# Copy the project folder
cp -r poems poems-1

How does the Git repository data get copied, you ask? Remember that everything about a Git repository is stored in Git’s personal diary for that project — the .git directory. As long as this directory is included in the copy, the Git repository gets copied too. Also, note that Git doesn't really care about the name of the project folder - it only cares about what's inside.

Three-way merge

We’ll try merging our branch into master in the copy that we created, so go to that directory.

As mentioned above, the three-way merge creates a “merge commit” in the target branch (here, master) that represents the work done in the feature branch (here, add-headings). Let's try it.

# Switch to the target branch
git checkout master
# Merge the feature branch with message
git merge add-headings -m "Add headings to existing poems"

Yes, that’s all. Git just added a “merge commit” to master that includes all the work that was done in the branch add-headings. We also specified a message for the merge commit that summarises the work done in the feature branch. Simpler than you thought?

Merge conflicts

Here’s where things get slightly tougher. I mentioned above that Git can do the merge operation by itself if your work does not clash with work on the target branch, which was the case here. Your work was done on files poem1.txt, poem2.txt and poem3.txt, while work done on master involved a new file poem4.txt being added to the project. Clearly none of the files were involved in work on both branches.

So what happens when different branches do different work on the same part of a file? That leads to the situation most dreaded by Git beginners — merge conflicts. As the name says, a merge conflict refers to a file conflict that arises while merging one branch into another. And resolving a merge conflict is seen as a tough task, but only until you do it for the first time — after that, it’s smooth sailing. So let’s go for it.

Go back to the original project directory. Since we want to trigger a merge conflict, we’ll have to change some stuff. Let’s add another commit to master that makes changes in poem 1. Say a co-author did not listen to your instruction and added his own different heading to poem 1 himself, so that the poem now reads:

The Bells JingleJingle bells, jingle bells 
Jingle all the way
Oh what fun it is to ride
In a one-horse open sleigh!

To make the above changes (on behalf of our co-author), we’ll execute:

# Switch to master
git checkout master
# DIY: Edit poem1.txt as above
# THEN, stage and commit as usual:

git commit -am "Add heading in poem 1"
# (one-liner for "stage all and commit")

So now the commit tree would be something like this:

Fair enough — let’s go for the merge:

# On branch master,
git merge add-headings

… and if you did all of the above correctly, you should be face-to-face with your first merge conflict. Although this one is probably much smaller than what you’d face in a real project, resolving it will hopefully go a good way in getting comfortable with Git. Before we start with resolving the conflict, let’s look at the output of the above command:

Auto-merging poem1.txt
CONFLICT (content): Merge conflict in poem1.txt
Automatic merge failed; fix conflicts and then commit the result.

So Git tried to merge the contents of poem1.txt from the target branch and feature branch, but ran into a conflict. So automatic merge failed, on which Git instructs you to fix the conflicts manually and commit the result.

Okay, let’s look at the contents of poem1.txt:

<<<<<<< HEAD
The Bells Jingle
=======

Jingle Bells
>>>>>>> add-headings
Jingle bells, jingle bells
Jingle all the way
Oh what fun it is to ride
In a one-horse open sleigh

This is Git’s way of showing you what the merge conflicts were. It edits the files to add conflict blocks, each of which represents a piece of conflicting contents from the two branches, and allows you to make changes accordingly.

The lines between <<<<<<< HEAD and ======= show the contents in the target branch (here, master) while the lines between ======= and >>>>>>> add-headings show the contents from the feature branch (here, add-headings). In addition, there may actually be multiple such blocks in a single file, where each block will represent a conflicting section of the file.

Here’s the golden rule to resolve merge conflicts in a three-way merge: edit the file, so that its contents represent the final content of the file you want in the main line, after the merge.

So what do you want the title of this poem to be, after the merge? Let’s go with “Jingle Bells”, because it sounds better. Edit the file so that it finally looks like this:

Jingle BellsJingle bells, jingle bells
Jingle all the way
Oh what fun it is to ride
In a one-horse open sleigh

Once that is done, let’s proceed with the merge:

# Stage changes in poem1.txt
git add .
# Commit the changes
git commit -m "Add headings to existing poems"

Boom — you just encountered and resolved your first merge conflict ever! It may seem a bit difficult when doing it the first time, but if you look closely, the concept is quite simple:

  1. Merge conflicts happen when the same part of a file is changed both in the target branch and the feature branch.
  2. When this happens, Git does not know which parts to keep so it marks the areas of merge conflict in the corresponding files.
  3. You open the files, see what the conflict is, think about it and make necessary changes to the file (and optionally other files too) so that the final version is as you want it to be.
  4. Stage, commit and done!

Note that it’s not necessary to pick one of the versions from the target or feature branch. In programming projects, you often have to read the code properly to understand the situation, and may have to mix and match stuff from the two versions so that the final code works like you want. In fact, sometimes it may also need changes in other files which don’t even have conflicts. Git’s job is only to show you conflicting blocks — your job is to edit stuff so that finally things are as expected.

Summary

Okay, so we covered one of the more difficult parts of Git in this part of the guide:

  • Branches are used to maintain parallel lines of work, and are widely used in projects with a lot of use cases, including collaboration and project management.
  • While collaborating, the general workflow is to keep your work in branches separate from the main line (generally the master branch), and merge your work branch into the main line once done.
  • If work in the target branch and the feature branch do not clash, Git can automatically merge the branches without the user’s help.
  • If work does clash, Git marks the sections of conflict in the files themselves. Then the user can fix these sections accordingly and commit the changes.

In the next part, we’ll cover the topic that will probably be the most useful to you in daily life — using GitHub, and dealing with remote repositories in general. We’ll also learn rebasing, a cleaner alternative to three-way merge.

--

--