
Time to Get Hands-on ππ»
Time to Get Hands-on ππ» κ΄λ ¨
OK, so let's say I have this simple repository here, with a branch called main
, and a few commits with the commit messages of "Commit 1", "Commit 2" and "Commit 3":

<Source: Brief>
Next, create a feature branch by typing git branch new_feature
:

git branch
<Source: Brief>
And switch HEAD
to point to this new branch, by using git checkout new_feature
. You can look at the outcome by using git log
:

git log
after using git checkout new_feature
<Source: Brief>
As a reminder, you could also write git checkout -b new_feature
, which would both create a new branch and change HEAD
to point to this new branch.
If you need a reminder about branches and how they're implemented under the hood, please check out a previous post on the subject. Yes, check out. Pun intended π
Now, on the new_feature
branch, implement a new feature. In this example I will edit an existing file that looks like this before the edit:

code.py
before editing it<Source: Brief>
And I will now edit it to include a new function:

new_feature
<Source: Brief>
And thankfully, this is not a programming tutorial, so this function is legit π
Next, stage and commit this change:

<Source: Brief>
Looking at the history, you have the branch new_feature
, now pointing to "Commit 4", which points to its parent, "Commit 3". The branch main
is also pointing to "Commit 3".
Time to merge the new feature! That is, merge these two branches, main
and new_feature
. Or, in Git's lingo, merge new_feature
into main
. This means merging "Commit 4" and "Commit 3". This is pretty trivial, as after all, "Commit 3" is an ancestor of "Commit 4".
Check out the main branch (with git checkout main
), and perform the merge by using git merge new_feature
:

new_feature
into main
<Source: Brief>
Since new_feature
never really diverged from main
, Git could just perform a fast-forward merge. So what happened here? Consider the history:

<Source: Brief>
Even though you used git merge
, there was no actual merging here. Actually, Git did something very simple β it reset the main
branch to point to the same commit as the branch new_feature
.
In case you don't want that to happen, but rather you want Git to really perform a merge, you could either change Git's configuration, or run the merge
command with the --no-ff
flag.
First, undo the last commit:
git reset --hard HEAD~1
If this way of using reset is not clear to you, feel free to check out a post where I covered git reset
in depth. It is not crucial for this introduction of merge
, though. For now, it's important to understand that it basically undoes the merge operation.
Just to clarify, now if you checked out new_feature
again:
git checkout new_feature
The history would look just like before the merge:

git reset --hard HEAD~1
<Source: Brief>
Next, perform the merge with the --no-fast-forward
flag (--no-ff for short
):
git checkout main
git merge new_feature --no-ff
Now, if we look at the history using git log
:

--no-ff
flag<Source: Brief>
(git log
is an alias I added to Git to visibly see the history in a graphical manner. You can find it here).
Considering this history, you can see Git created a new commit, a merge commit.
If you consider this commit a bit closer:
git log -n1

<Source: Brief>
You will see that this commit actually has two parents β "Commit 4", which was the commit that new_feature
pointed to when you ran git merge
, and "Commit 3", which was the commit that main
pointed to. So a merge commit has two parents: the two commits it merged.
The merge commit shows us the concept of merge quite well. Git takes two commits, usually referenced by two different branches, and merges them together.
After the merge, as you started the process from main
, you are still on main
, and the history from new_feature
has been merged into this branch. Since you started with main
, then "Commit 3", which main
pointed to, is the first parent of the merge commit, whereas "Commit 4", which you merged into main
, is the second parent of the merge commit.
Notice that you started on main
when it pointed to "Commit 3", and Git went quite a long way for you. It changed the working tree, the index, and also HEAD
and created a new commit object. At least when you use git merge
without the --no-commit
flag and when it's not a fast-forward merge, Git does all of that.
This was a super simple case, where the branches you merged didn't diverge at all.
By the way, you can use git merge
to merge more than two commits β actually, any number of commits. This is rarely done and I don't see a good reason to elaborate on it here.
Another way to think of git merge
is by joining two or more development histories together. That is, when you merge, you incorporate changes from the named commits, since the time their histories diverged from the current branch, into the current branch. I used the term branch
here, but I am stressing this again β we are actually merging commits.