Table of Contents

Introduction

A merge is how work done on a branch gets back to where everyone else can see it. Most of the time it's uneventful: Git combines the two histories, makes one commit, and you carry on. The interesting part is that one commit, because it's the only kind of commit in Git with more than one parent, and that shape is what lets Git remember that two lines of work were ever separate.

In this article, we'll:

  1. Watch git merge feature create a merge commit on a small repository
  2. Look at what a merge commit contains and why it has two parents
  3. Cover what a conflict is, how to undo a merge, and when a merge doesn't make a merge commit at all

What is git merge?

git merge <branch> takes the commits on <branch> that your current branch doesn't have and combines them with your current branch. When both branches have moved since they split, Git finds the last commit they share (the merge base), works out what each side changed since then, applies both sets of changes to one tree, and records the result as a merge commit with two parents: your previous tip and the tip of <branch>.

If only one side has moved, there's nothing to combine and Git just moves your branch forward. That's a fast-forward, and it has its own page.

Watch it happen

Our sample repo has two commits on main and three on feature since the branches split. Here's git merge feature with main checked out:

  1. Before: main and HEAD on 8c02d5b, and feature on 1117a34 at the end of its own three commits. The two branches have diverged.
  2. Git creates a merge commit, "Merge branch 'feature'", with two parents: 8c02d5b on main and 1117a34 on feature.
  3. main and HEAD move to the merge commit.
  4. feature stays where it was. Merging into main doesn't move the branch you merged from.

Before and after

Before, the two branches share 96c4fc2 and then go their own ways. After, they're joined by a commit that reaches both. Git's own report of the merge lists the files it brought in:

The raw git output, if you want to read along in text

git log --oneline --graph --all

before

* 1117a34 (feature) Add search tests
* e5869f0 Fix typo in search box
* fc19889 Add search box
| * 8c02d5b (HEAD -> main) Update dependencies
| * a0b2db3 Add user settings page
|/  
* 96c4fc2 Fix header layout
* ae65976 Add login page
* 3e1ffe4 Add project skeleton
* 4114b2c Initial commit

after

*   ee672dd (HEAD -> main) Merge branch 'feature'
|\  
| * 1117a34 (feature) Add search tests
| * e5869f0 Fix typo in search box
| * fc19889 Add search box
* | 8c02d5b Update dependencies
* | a0b2db3 Add user settings page
|/  
* 96c4fc2 Fix header layout
* ae65976 Add login page
* 3e1ffe4 Add project skeleton
* 4114b2c Initial commit

what git printed

Merge made by the 'ort' strategy.
 search.html    | 1 +
 test_search.py | 2 ++
 2 files changed, 3 insertions(+)
 create mode 100644 search.html
 create mode 100644 test_search.py

What a merge commit contains

Run git cat-file -p HEAD after the merge and you'll see two parent lines:

tree ...
parent 8c02d5b...
parent 1117a34...

The first parent is the branch you were on. The second is the one you merged. Everything else, git log --first-parent, git revert -m 1, the shape of --graph output, hangs off that ordering. The tree is the combined result, so checking out the merge commit gives you both sets of changes.

When two sides change the same lines

Git can combine changes to different parts of a file, or different files, on its own. When both branches changed the same lines of the same file, it stops with a conflict, writes both versions into the file between <<<<<<< and >>>>>>> markers (the git merge documentation shows the other marker styles), and waits. You edit the file to what it should be, git add it, and git commit to finish the merge. git merge --abort puts everything back to before you started.

Our sample merge had no conflicts, and git-sim checked that ahead of time with git merge-tree, which is what the "No conflicts detected" line in the report below comes from.

The mental model that made merges stop feeling risky for me was realizing that a merge is a commit like any other. It has a tree and parents and a hash, and undoing it is the same as undoing a normal commit: move the branch back one. The two parents are the only unusual thing about it.

Is it safe?

Safe git-sim pre-flight

Brings in 3 commit(s) from feature (merge commit).

The way back

  • Undo the merge commit with: git reset --hard ORIG_HEAD (back to 8c02d5b)

A merge adds a commit and moves your branch forward. Nothing existing is rewritten. The only thing to know in advance is whether it will conflict, which git-sim tells you.

How to undo it

If you haven't pushed:

git reset --hard HEAD~1

main goes back to 8c02d5b, and since feature still points at its commits, nothing is lost. If the merge has been pushed, git revert -m 1 <merge-sha> adds a commit that undoes the merged changes while keeping history intact.

Try it on your repository

pip install git-sim
git-sim merge feature

git-sim shows the merge commit and both parents on your own graph, and runs Git's conflict check first, so you know whether the merge will go through cleanly before you start it.

Common questions

What is a merge commit?

A commit with two parents: the tip of the branch you were on and the tip of the branch you merged. Its tree contains the combined changes from both.

What is a merge base?

The most recent commit that both branches have in their history. Git compares each side against it to work out what changed.

How do I undo a git merge?

Before pushing, git reset --hard HEAD~1 moves your branch back to the commit before the merge. After pushing, git revert -m 1 <merge-commit> adds a new commit that reverses the merge.

What is the difference between git merge and git rebase?

Merge joins two histories with a merge commit and keeps both intact. Rebase reapplies your commits onto the other branch as new commits, producing a straight line and rewriting your branch's history.

Summary

In this article, we watched git merge create a two-parent commit joining main and feature, looked at what that commit contains, and covered conflicts and how to undo a merge.

Next steps

Fast-forward merges are the case where no merge commit is needed, and git rebase is the alternative that avoids one on purpose.