HOW TO REDUCE THE SUFFERING OF LIFE with Git Branches

A beginner's guide to Git Branching and Merging

- Do you ever feel like you are not utilizing the full potential of Git?

- Do you like climbing trees?

- Has the topic of Git Branches always confused you?

If your answer is yes to any of the above, then this tutorial is something you must not miss.

Else if your answer is no, then also keep reading, otherwise you risk missing out all the puns and the learns…

Introduction

If you haven’t already read the previous tutorial on git basics, it is highly recommended you go through it before continuing with this one.

The idea of branching completely changes the way most people think about and write code. So if you think the title is crazy, read the full tutorial and judge for yourself.

But before we get into branching, it’s worth knowing what problems it solves for us.

The Problems

1. Adding a new or experimental feature

Suppose your project calls for a new feature OR you just want to try out an experiment on the existing code.

But, assume your repository is also tied to a production environment.

Therefore, if you’re sane, you won’t want to make any commits that could affect or break the stable production environment.

How can you try out the idea for the new feature?

meme_thinking-with-cup

2. Conflicts in a team project

When multiple people make changes to the same file, there is a very high chance of them editing the same parts of the file. This is what we are referring to as a conflict.

If you recall the last tutorial carefully, you’ll notice that we didn’t discuss how to solve this problem. How sneaky of us…

meme_angery

If that makes you angry or resentful, I’m sorry.

You can calm down because we’re going to learn about branching in git now, which forms the basis of how most teams work on projects together, and how new features are usually incorporated.

The Solution

Branching

Here’s how we defined branches in the last tutorial.

A Branch is a (moveable) pointer to a commit.

Visually, a branch represents a sequence of commits, with the tip of the branch pointing to a single commit.

By default, git creates a branch named master. Every time you commit from the master branch, the pointer automatically moves forward to the new commit.

We’re going to integrate hands on practice to better understand the solution.

We’ll use the repository from our previous tutorial, which had 3 commits. If you already have that repository in your computer with 3 commits, you may use that. Otherwise, to stay consistent with this tutorial, let’s clone it.

git clone https://github.com/AluBhorta/git-demo-groceries.git

This will clone the repository to a folder named git-demo-groceries. Remember that cloning is another way of getting a repository, instead of initializing a new one with git init.

Now change directory to that folder and view the commits made.

cd git-demo-groceries
git log

NOTE: if you ever find yourself stuck in the git log screen, press q to return back to your terminal.

Notice we’re on master, as pointed by HEAD. The origin/master and origin/HEAD are denoting that the master pointer and the HEAD of the remote repository (named origin) are also pointing at this commit. We won’t focus on the remote references for now.

With the 3 commits, our version history looks like this:

git-vh-diag1.png

As we can see, each commit points to its parent (previous) commit, except for the initial one. Because of this, a branch pointer in Git only has to point to a single commit, as its history can easily be traced by following its consecutive parent commits.

Makes sense right?

This lightweight branch implementation is different, from that in other Version Control Systems (VCSs) - as opposed to copying the whole version histories for each branch. It’s is one of the reasons that makes Git efficient and blazing fast!

Now let’s tackle problem 1 - adding a new feature.

To mock the idea of a production environment, we’ll assume that the contents of our master branch are hosted on some website and are used by our family for buying groceries. So, we don’t want to make any commits to master that we are unsure about.

NOTE: I’m just making stuff up to minimize the dependencies to any particular language/runtime, hoping everyone can follow along. :P

To make commits that don’t affect our master branch, we need to make another branch (to no ones surprise). You can check that we’re on master by running:

git branch

We’ll name our new branch develop following standard practice.

git branch develop
git checkout develop

The first command makes the new branch named develop, while the second one switches or checks-you-out to that branch.

Tip: git checkout -b develop is shorthand for both in succession.

When you make a new branch using the syntaxes above, the tip of the new branch points to the commit you are in (i.e. your HEAD is in). Running git log will reveal that.

This is our updated version history:

git-vh-diag2.png

We can now make commits that will progress develop and leave master untouched.

Open up your favourite editor/IDE and let’s add a new change and commit. I recommend VS Code which is a fantastic, customizable and open source editor from Microsoft.

code .

I added 12 eggs to my groceries.

sc_add-eggs

Now add the new changes to index and commit.

git add to_buy.txt
git commit -m "add eggs"

This is what the version history looks like now:

git-vh-diag3.png

As we see, develop automatically moves to the new commit. This allows us to just keep track of the branch we’re on, and forget about the commit id - which is hard to remember.

Now assume that we got confirmation that the eggs can be added to the main grocery list i.e. in master.

How do we add the new change (commit) to master?

Thinking about - switching to master, copying the new line and making a new commit might be tempting. But that’s a painful recipe for disaster.

Because our change is so small, we could get away with this. But, if our change was on tens of files and hundreds of lines of code, or more than a commit, that solution quickly falls apart.

Merging

Git’s answer is the aptly named git merge command, which can join two or more development histories or branches together. We will strictly be working with merging two branches, as that is sufficient for most use cases.

Think of merging two branches as, adding the changes of one onto another. Here, we want to add the changes of develop onto master.

In our case, master can be thought of as the receiving branch, while develop can be thought of as the target branch.

Running git merge branch_name will merge branch_name onto the receiving branch. But we have to switch to the receiving branch first before this command.

Thus, to do the merge, we first switch to the receiving branch (master) and run the merge command like so:

git checkout master
git merge develop

How the merge will take place will depend on the version history of the receiving branch (master).

Fast-Forward Merge

If the receiving branch (master) hasn’t had any commits since the target branch has diverged from it, a fast-forward merge occurs. And that is so, is our case now.

In fast forward merge, the tip of the receiving branch is just updated to point to the target branch tip.

Before merge

git-vh-diag4-before

After merge

git-vh-diag4-after

3 Way Merge

If on the other hand, the receiving branch (master) has had commits since the target branch has diverged from it, a 3 Way or Explicit merge occurs. In this type of merge, a new commit is created to tie two histories together. This commit is called the merge commit.

To replicate the 3 way merge, we’ll have to make changes in both develop and master before merging.

First switch to develop:

git checkout develop

Make some changes:

sc_add-milk

And commit:

git add to_buy.txt
git commit -m "add dat milk"

Now we switch to master:

git checkout master

Make some other changes elsewhere.

NOTE: Do not make changes on the same lines in both develop and master for now. We will be doing that later.

sc_update-tomatoes-2kg

Add and Commit:

git add to_buy.txt
git commit -m "update tomatoes amount to 2kg"

Make sure you are in master using git branch. Now, execute the merge with a commit message:

git merge develop -m "merge develop onto master"

This is how the merge takes place:

Before merge

git-vh-diag5-before

After merge

git-vh-diag5-after

Notice that the merge commit has two parents, instead of 0 or 1. Also notice that develop is one commit behind master due to the last merge being received by master.

Usually when the changes of a feature branch has been merged to the main branch, the feature branch is

  • either deleted
  • or fast forwarded to the main branch, if the feature is to be used later

To delete our develop branch, which is fully merged, we can run:

git branch -d develop

If we want to throw away all changes of a branch named crazy-feature before merging it with the main branch, we can run:

git branch -D crazy-feature

But we’re going to fast forward develop to master, by simply running:

git checkout develop
git merge master

Thus our version history is updated like so:

git-vh-diag6

Merge Conflicts

Alright, so far so good.

But things don’t always go as smoothly. What if both the receiving and the target branches have changes on the same part of a file or files, before merge?

That is when a conflict happens…

meme_man-sweating.png

Chill! We can work around it.

When a conflict happens, git is unable to decide which change to keep. So it stops right before making the merge commit, and let’s us decide what changes to keep, and what not to.

Conflicts are presented using these visual markers <<<<<<<, =======, and >>>>>>>. This is an example of a file with conflicts:

This line is not affected by the conflict
<<<<<<< HEAD
conflicting lines
from master
=======
conflicting line from develop
>>>>>>> develop
 
These lines
are also not affected
by the conflict

The lines between <<<<<<< and ======= are from the receiving branch, here master. And the lines between ======= and >>>>>>> are from the target branch, here develop.

To resolve the merge, we simply remove the markers and keep the changes we want to, whether that be from the receiving branch, or the target branch, or both, or a modification of them.

In modern editors like VS Code, conflicts are automatically highlighted. We are given options to choose what changes to keep (usually via buttons), and the editor does the rest of the cleanup.

After keeping the desired changes, we add it to the index to mark it as resolved and then make a commit.

Let’s make a conflict!

First make a change from develop. Before doing that, make sure both develop and master are pointing to the same commit using git log.

Now, checkout to develop:

git checkout develop

Make some changes on README.md:

sc_update-readme-from-develop

Now commit the changes made:

git add README.md
git commit -m 'update readme from develop'

After that, switch to master:

git checkout master

Make changes on the same line of the README.md file:

sc_update-readme-from-master

Commit the changes made:

git add README.md
git commit -m "update readme from master"

Now if we try to merge using:

git merge develop

Git will yell at us saying we have conflicts. We can further check which files have conflicts (unmerged paths) using git status.

If you’re using VS Code, you will be shown the conflicts in highlight like this:

sc_merge conflict marker

You can accept whichever changes you want to keep. I am going to choose Accept Current Change, to keep the changes of master.

Now you can add the changes to index and make a commit which will be the merge commit:

git add README.md
git commit -m "resolve conflicts and merge develop onto master"

Our version history is updated like so:

Before merge

git-vh-diag7-before

After merge

git-vh-diag7-after

Undoing Merge

Finally, if you are ever stuck in a conflict and want to throw away the mess, and get back to the pre merge state, run:

git reset --hard HEAD

If you have just made a merge commit, but want to throw it away and get back to the pre merge state, run:

git reset --hard HEAD~1

Conclusion

Did you follow along, understand the concepts and did the exercises?

If yes, Congratulations! You now know more branching and merging than the majority of git users.

meme_cheers-leo

It’s perfectly normal if you didn’t get everything. It took me more than a while before I got used to merging branches. Practice using branches in your everyday work for a few days, and it will feel a lot more comfortable.

I hope you could take away at least something of value from this tutorial. If you did, it would mean a lot if you shared this with others who might find it useful as well.

Regardless of that, go out there and add a new crazy-feature to your project!

Have fun branching!

Subsribe to the Newsletter

Get notifed of cool new content and interesting topics on Tech.

You won't be spammed, and you can unsubscribe whenever you wish.


See also