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?
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…
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:
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:
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.
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:
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
After merge
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:
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.
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
After merge
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:
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…
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
:
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:
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:
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
After merge
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.
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!
Originally published at https://techmormo.com.