The git merge command is the instruction responsible for recombining two independent lines of development into a single branch.
Imagine you have already created your branch. You’ve worked on it isolated from the world and your new feature is ready and tested.
The time has come to bring those changes back to the main branch. The operation responsible for unifying two divergent lines of development is merge.
In this entry, we will look at this command, and how Git has two very different ways of behaving depending on how the history has evolved: Fast-Forward and the Merge Commit.
Unlike old systems where merging code was a manual process, Git is designed to perform merges automatically in the vast majority of cases.
Who Merges with Whom
First, before continuing, let’s talk about who merges. You always merge into where you currently are.
That is, if you want to put the changes from the feature branch into main:
First, go to the destination: git switch main
From there, call the external branch: git merge feature
Never the other way around. HEAD is the destination, always. Now, let’s see the two possible types of merge. 👇
Scenario 1: Fast-forward
This is the simplest scenario. A branch has advanced, but they haven’t truly diverged. Imagine this situation:
- You are at commit
Conmain. - You create a branch
feature. - You advance two commits on
feature(DandE). - Meanwhile, no one has touched
main. It’s still at commitC.
When you tell Git “merge feature into main”, Git looks and says: “Wait, there’s no divergence. The history is a straight line.”
git merge feature
So Git doesn’t complicate things. It simply takes the main tag and slides it forward until it reaches feature.
Characteristics of Fast-forward:
- Does not create a merge commit.
- The history remains completely linear.
- It’s as if you had worked directly on
mainthe whole time.
Sometimes fast-forward is not as good an idea as it seems. We’ll see this below with --no-ff
Scenario 2: Merge Commit
This is the most common case, where the branches have actually diverged. Imagine this scenario:
- You create your
featurebranch and advance. - But while you were working, someone has pushed changes to
main(or you yourself made an urgent hotfix).
Now the branches have diverged. main has advanced on one side and feature on the other.
git merge feature
What does Git do? It performs a “three-way merge”. It uses:
- The last commit of
main. - The last commit of
feature. - The most recent common ancestor of both.
With these three ingredients, Git calculates the blend and automatically creates a NEW commit (the Merge Commit) that has two parents.
Preventing Fast-forward (--no-ff)
Sometimes, Fast-forward is great because it leaves the history very clean (a straight line). But it has a huge flaw: you lose the notion that those commits belonged to a specific “feature”.
If you look at the history a month from now, you’ll see 5 consecutive commits, but you won’t know that those 5 were part of the “New Login” task.
That’s why many teams (and methodologies like Git Flow) prefer to prohibit Fast-forward when integrating complete features.
You can force Git to create a merge commit, even if it could do a Fast-forward, by using:
git merge --no-ff feature
<lll-ui-git-timeline config=’{ “scenarios”: [ { “title”: “Before (FF Situation)”, “commits”: [ {“id”:“A”, “col”:0}, {“id”:“B”, “parent”:“A”, “col”:1}, {“id”:“C”, “parent”:“B”, “col”:2}, {“id”:“D”, “parent”:“C”, “col”:3, “row”:-1}, {“id”:“E”, “parent”:“D”, “col”:4, “row”:-1} ], “branches”: { “main”: “C”, “feature”: “E” }, “head”: { “target”: “main”, “type”: “branch” }, “terminal”: { “cmd”: ”
Using --no-ff visually groups the commits of a task. It allows you to revert the entire feature by undoing just one commit (the merge one).
