05 · Merging Basics¶
Once work on a branch is ready, you bring it back into another branch
(usually main) with git merge. There are two outcomes depending on
whether the branches have diverged: a fast-forward merge, or a true
merge commit.
Fast-forward merge¶
If main hasn't moved since you branched off it, Git can simply slide the
main pointer forward to match your feature branch — no new commit needed,
because there's nothing to reconcile.
git switch main
git branch feature/greeting # branch off main
git switch feature/greeting
echo "feature work" > feature.txt
git add feature.txt
git commit -m "Add feature file"
git switch main
git merge feature/greeting
Updating 4752e7c..71c4aec
Fast-forward
feature.txt | 1 +
1 file changed, 1 insertion(+)
create mode 100644 feature.txt
* 71c4aec (HEAD -> main, feature/greeting) Add feature file
* 4752e7c Add second line
* 07246b8 Add notes.txt
main and feature/greeting now point at the exact same commit — history
stays perfectly linear.
True merge (three-way merge)¶
If main has also moved forward since the branch point (someone else
committed, or you did on main directly), Git can't just fast-forward. It
creates a new merge commit with two parents, combining both histories:
git switch -c feature/tagline
echo "tagline: ship it" >> notes.txt
git add -A
git commit -m "Add tagline to notes"
git switch main
echo "main update" >> feature.txt
git add -A
git commit -m "Update feature.txt from main"
git merge feature/tagline -m "Merge branch 'feature/tagline'"
* 48f9b4e (HEAD -> main) Merge branch 'feature/tagline'
|\
| * 24bd52a (feature/tagline) Add tagline to notes
* | 30e5d21 Update feature.txt from main
|/
* 71c4aec Add feature file
* 4752e7c Add second line
* 07246b8 Add notes.txt
Git automatically combined both lines of work because they touched
different files (notes.txt vs. feature.txt). This automatic combination
is the normal case — most merges need zero human intervention.
When Git can't merge automatically: conflicts¶
If both branches changed the same lines of the same file, Git can't guess which version is correct and stops, marking the file as conflicted:
Auto-merging notes.txt
CONFLICT (content): Merge conflict in notes.txt
Automatic merge failed; fix conflicts and then commit the result.
At this point Git has written both versions into the file, marked with conflict markers:
You'd edit the file to keep the correct content, remove the markers, then
git add the file and git commit to complete the merge. Full conflict
resolution is covered in depth in Level 2 — for now, just recognize this
output if you see it, and know it means "two branches touched the same
lines; Git needs a human decision."
--no-ff: forcing a merge commit¶
Sometimes you want a merge commit even when a fast-forward is possible — to keep an explicit record that a feature branch existed, useful for tracking what shipped together:
Many teams standardize on --no-ff for all feature-branch merges so the
history always shows branch/merge points clearly in git log --graph.
Aborting a merge¶
If a merge goes wrong (conflicts you don't want to resolve right now), back out cleanly:
This restores the working directory to exactly how it was before you ran
git merge.
Cleaning up after a merge¶
Once a branch's work has been merged into main and you don't need it
anymore:
-d (lowercase) refuses to delete a branch with unmerged commits, which is
a good safety net — if it refuses, double-check the branch really is fully
merged before switching to -D.
How It Actually Works¶
A merge is a three-way comparison and a specific rule for choosing which kind of commit to write:
- Git finds the merge base — the most recent commit that is an ancestor
of both branch tips — by walking each branch's parent chain backward
and finding where the two histories first diverge. This is what makes it
a "three-way" merge: Git compares the merge base's tree,
HEAD's tree, and the other branch's tree, for every file. - Fast-forward: if the merge base is the current branch's tip (i.e.
mainhasn't moved sincefeature/xbranched off), there's nothing to actually merge —feature/x's commits already contain everythingmainhas, plus more. Git just rewritesrefs/heads/mainto point straight atfeature/x's tip commit. No new commit object is created; history stays a straight line, which is why--no-ffexists for teams who want the branch/merge event visible anyway. - Three-way merge commit: if both branches have new commits since the
merge base, Git computes, per file, whether only one side changed it
(take that side's version automatically) or both sides changed the same
lines (emit conflict markers and let you decide). Once resolved, Git
builds one new tree object representing the combined result and writes a
commit object with two parents — the previous tips of both branches.
That's the entire structural difference between a merge commit and a
normal commit: two
parentlines instead of one, which is what makesgit log --graphrender a join in the DAG. git merge --abortworks because nothing has been committed yet during a conflicted merge — it just resets the index and working directory back to matchHEAD's tree and deletes the in-progress merge state (.git/MERGE_HEAD), which is the file that's tracking "you're mid-merge against this other commit" in the first place.
Exercise¶
Create two branches, feature/a and feature/b, both starting from main.
On feature/a, add a new file a.txt. On feature/b, add a new file
b.txt. Merge both into main (in either order) and confirm the second
merge is a true merge commit (not a fast-forward) by checking
git log --oneline --graph --all. Delete both feature branches once
merged.