Chapter 13: Git Branch Merge
Git Branch Merge — the moment when you say “this experiment/feature/bugfix is good — let’s bring it into the main story”.
git merge is Git’s way of joining two (or more) lines of development back together into one unified history. It takes all the commits from one branch and integrates them into another branch (usually main).
Think of it like this:
- You have the official book (main branch)
- Your friend wrote a new chapter in a side copy (feature/login branch)
- Merge = carefully copy-paste the new chapter into the official book → If no overlap/conflict → automatic & clean → If same paragraph edited differently → Git pauses and asks you to decide who wins each line
There are two main flavors of merge in everyday use (2026 reality):
- Fast-forward merge (clean & linear — most beautiful)
- True merge commit (creates a diamond/merge bubble — preserves full story)
We’ll do both with a real example.
Step-by-Step Hands-on Example: Merging a Feature Branch
Start fresh:
|
0 1 2 3 4 5 6 7 8 |
mkdir todo-merge-demo cd todo-merge-demo git init |
Commit 1–2 on main (base story)
|
0 1 2 3 4 5 6 7 8 9 10 |
echo "# Todo App" > README.md git add README.md && git commit -m "Initial: project start" echo "- Buy groceries" >> README.md git add . && git commit -m "Add first todo" |
Now create & work on a feature branch:
|
0 1 2 3 4 5 6 |
git switch -c feature/add-login # modern way (or git checkout -b ...) |
Make changes on the feature branch:
|
0 1 2 3 4 5 6 7 8 |
echo "- Implement user login" >> README.md echo "<form>Username: <input></form>" > login.html git add . && git commit -m "Add login page stub + todo" |
Add one more commit for realism:
|
0 1 2 3 4 5 6 7 |
echo "body { background: #eee; }" > style.css git add style.css && git commit -m "Add basic styling for login" |
Check graph:
|
0 1 2 3 4 5 6 |
git lg # or git log --graph --oneline --decorate --all |
Something like:
|
0 1 2 3 4 5 6 7 8 9 10 |
* 456defg (HEAD -> feature/add-login) Add basic styling for login * abc1234 Add login page stub + todo | * 789abcd (main) Add first todo |/ * fedcba9 Initial: project start |
Now — time to merge!
Scenario 1: Fast-forward merge (clean & linear – no extra commit)
Switch back to target branch:
|
0 1 2 3 4 5 6 |
git switch main |
Merge the feature in:
|
0 1 2 3 4 5 6 |
git merge feature/add-login |
Output you’ll likely see:
|
0 1 2 3 4 5 6 7 8 9 10 11 12 13 |
Updating 789abcd..456defg Fast-forward README.md | 1 + login.html | 1 + style.css | 1 + 3 files changed, 3 insertions(+) create mode 100644 login.html create mode 100644 style.css |
Now graph:
|
0 1 2 3 4 5 6 |
git lg |
|
0 1 2 3 4 5 6 7 8 9 |
* 456defg (HEAD -> main, feature/add-login) Add basic styling for login * abc1234 Add login page stub + todo * 789abcd Add first todo * fedcba9 Initial: project start |
→ Perfect straight line! Git just moved main pointer forward to catch up — no new commit needed. This is the cleanest history (most teams love it).
Scenario 2: True merge commit (when branches diverged)
Simulate divergence — someone else (or you) continued on main:
|
0 1 2 3 4 5 6 7 8 |
# Still on main echo "- Fix typo in groceries" >> README.md git add . && git commit -m "Hotfix: typo correction on main" |
Now graph before merge:
|
0 1 2 3 4 5 6 7 8 9 10 11 |
* 1234567 (HEAD -> main) Hotfix: typo correction on main | * 456defg (feature/add-login) Add basic styling for login | * abc1234 Add login page stub + todo |/ * 789abcd Add first todo * fedcba9 Initial: project start |
Merge again:
|
0 1 2 3 4 5 6 |
git merge feature/add-login |
Git says:
|
0 1 2 3 4 5 6 7 8 |
Merge made by the 'ort' strategy. ← default strategy in modern Git README.md | 1 + 1 file changed, 1 insertion(+) |
And creates a merge commit automatically (unless conflicts).
Graph now:
|
0 1 2 3 4 5 6 7 8 9 10 11 12 13 |
* 89abcde (HEAD -> main) Merge branch 'feature/add-login' |\ | * 456defg (feature/add-login) Add basic styling for login | * abc1234 Add login page stub + todo * | 1234567 Hotfix: typo correction on main |/ * 789abcd Add first todo * fedcba9 Initial: project start |
→ Diamond shape! The merge commit has two parents (one from main, one from feature). History tells the true story: “these lines happened in parallel”.
Merge Conflicts – When Git Needs Your Help
If both branches changed the same line differently:
Example conflict:
- main: changed line 3 to “Buy vegetables”
- feature: changed line 3 to “Buy milk & eggs”
Git stops:
|
0 1 2 3 4 5 6 7 8 |
Auto-merging README.md CONFLICT (content): Merge conflict in README.md Automatic merge failed; fix conflicts and then commit the result. |
git status shows:
|
0 1 2 3 4 5 6 |
both modified: README.md |
Open README.md — Git marks:
|
0 1 2 3 4 5 6 7 8 9 10 |
<<<<<<< HEAD - Buy vegetables ======= - Buy milk & eggs >>>>>>> feature/add-login |
You decide:
- Keep one version
- Combine both
- Rewrite
Then:
|
0 1 2 3 4 5 6 7 |
git add README.md git commit # Git pre-fills message "Merge branch 'feature/...' " |
Done!
Abort if overwhelmed:
|
0 1 2 3 4 5 6 |
git merge --abort # back to before merge attempt |
Common Merge Options / Flags (Useful in 2026)
| Flag / Option | Command Example | When / Why use it |
|---|---|---|
| Fast-forward only | git merge –ff-only feature | Fail if cannot fast-forward (clean history) |
| No fast-forward | git merge –no-ff feature | Force merge commit even if fast-forward possible |
| Squash merge | git merge –squash feature | Combine all commits into one (then commit yourself) |
| No commit (inspect first) | git merge –no-commit feature | Merge files but don’t auto-commit |
| Strategy (rare) | git merge -s ort feature | Default now — ort is fast & smart |
| Ignore whitespace | git merge -Xignore-all-space feature | Conflicts only on real code, not formatting |
Summary Table – Merge vs Alternatives
| Action | Command | History Style | Best For |
|---|---|---|---|
| Bring feature to main | git merge feature | Merge commit or FF | Integrating finished work |
| Re-write history linear | git rebase main (on feature) | Linear (no merge bubble) | Clean personal/feature branches |
| Pull + merge | git pull (default = fetch + merge) | Same as merge | Update main from remote |
| Pull + rebase | git pull –rebase | Linear | Keep history clean when pulling |
Quick workflow most people do in 2026:
|
0 1 2 3 4 5 6 7 8 9 10 11 |
git switch main git pull origin main # update git switch - # back to previous branch (feature) git rebase main # optional: clean before PR git push -u origin feature # share # → Create Pull Request → review → squash/merge on GitHub |
Got the merge feeling? Merge = “integrate this branch’s story into mine”.
Next class?
- Real merge conflict resolution step-by-step?
- Squash merge vs regular?
- Rebase vs merge deep dive?
- GitHub Pull Request merge options?
Just say — we’ll keep going. You’re mastering branches & merges now! 🚀
