Chapter 3 — Branches and Merging
Branches let you work on multiple ideas in parallel without touching `main`. Merging integrates a branch back. Conflicts happen when two branches edit the same line — we'll see how to resolve them.
What you'll learn
- 1Create, switch and delete branches with `git branch` / `git switch`.
- 2Distinguish fast-forward merges from 3-way merges.
- 3Resolve merge conflicts deliberately.
- 4Use `git log --graph` to read history.
Core Concepts
1) Branch basics
git branch # list
git branch feature # create
git switch feature # switch (modern) — same as git checkout
git switch -c new-branch # create + switch in one step
git branch -d feature # delete (only if merged)2) Fast-forward vs 3-way merge
| Scenario | Merge type |
|---|---|
| `main` has not changed since `feature` started | **Fast-forward** — `main` pointer moves up |
| Both branches advanced independently | **3-way** — a new merge commit is created |
git switch main
git merge feature3) Conflicts
When two branches edit the same line, Git pauses with `CONFLICT`. Files contain:
<<<<<<< HEAD
main's version
=======
feature's version
>>>>>>> featureEdit the file to keep what you want, remove the markers, then:
git add <file>
git commit # completes the merge4) Graph view
git log --oneline --graph --allShows the actual topology — invaluable when something looks wrong.
Examples
Example 1 — `ex01_branch.sh`: create and switch branches
**Output**
=== Create branch ===
=== Switch branch ===
On branch featureKey: `git switch -c feature` is the idiomatic create-and-switch.
Example 2 — `ex02_merge_ff.sh`: fast-forward scenario
**Output**
=== Fast-forward merge ===
Updating a1b2c3d..b2c3d4e
Fast-forwardKey: when `main` has not moved, the merge just advances the pointer.
Example 3 — `ex03_merge_3way.sh`: 3-way merge
**Output**
=== 3-way merge ===
Merge made by the 'ort' strategy.Key: a real merge commit appears in `git log --graph`.
Example 4 — `ex04_conflict.sh`: conflict and resolution
**Output**
=== Conflict created ===
CONFLICT (content): Merge conflict in shared.txt
=== After conflict resolution ===
Merge complete.Key: edit, `git add`, `git commit` — that's the whole conflict cycle.
Common mistakes
- **Deleting an unmerged branch** with `-d` — Git refuses. Use `-D` only when you really mean to throw it away.
- **Forgetting to switch** before merging — you might merge `feature` into `feature` and wonder what happened.
- **Resolving a conflict but not staging** — Git stays in mid-merge state. `git add` first.
- **`git pull` causing surprise conflicts** — `pull` is `fetch + merge`; consider `--rebase` for a linear history.
- **Long-lived feature branches** — drift accumulates; rebase or merge from main early and often.
Recap
- Branches are cheap; create one per topic.
- Fast-forward when `main` hasn't moved; 3-way otherwise.
- Conflicts are normal: read both sides, decide, `add` + `commit`.
- `git log --oneline --graph --all` is your map.
Try it
cd src
chmod +x ex0*.sh
./ex01_branch.sh
./ex02_merge_ff.sh
./ex03_merge_3way.sh
./ex04_conflict.sh💻 Examples
Runnable examples — see the output yourself.
#!/usr/bin/env bash
set -euo pipefail
REPO=$(mktemp -d)
cd "$REPO"
git init -q -b main
git config user.name "Demo User" && git config user.email "demo@example.com"
git config commit.gpgsign false
echo "main content" > main.txt
git add . && git commit -q -m "init: main"
echo "=== branch create ==="
git branch feature/hello
git branch feature/world
echo ""
echo "=== branch list ==="
git branch
echo ""
echo "=== feature/hello using (before) ==="
git switch feature/hello
echo "hello" > hello.txt
git add . && git commit -q -m "feat: add hello"
echo ""
echo "=== (before) graph ==="
git log --oneline --graph --all
echo ""
echo "=== main using ==="
git switch main
git branch
rm -rf "$REPO"
=== Create branch ===
=== Switch branch ===
On branch feature#!/usr/bin/env bash
set -euo pipefail
REPO=$(mktemp -d)
cd "$REPO"
git init -q -b main
git config user.name "Demo User" && git config user.email "demo@example.com"
git config commit.gpgsign false
echo "init" > README.md
git add . && git commit -q -m "init"
git switch -c feature/fast-forward
echo "FF add feature" > feature.txt
git add . && git commit -q -m "feat: add feature"
echo "=== merge (before) log ==="
git log --oneline --graph --all
git switch main
git merge feature/fast-forward
echo ""
echo "=== fast-forward merge (after) log () ==="
git log --oneline --graph --all
echo ""
echo "=== --no-ff using merge commit create ==="
git switch -c feature/no-ff
echo "no-ff feature" > noff.txt
git add . && git commit -q -m "feat: add no-ff feature"
git switch main
git merge --no-ff feature/no-ff -m "merge: feature/no-ff into main"
echo ""
echo "=== --no-ff (after) log (merge commit exists) ==="
git log --oneline --graph --all
rm -rf "$REPO"
=== Fast-forward merge ===
Updating a1b2c3d..b2c3d4e
Fast-forward#!/usr/bin/env bash
set -euo pipefail
REPO=$(mktemp -d)
cd "$REPO"
git init -q -b main
git config user.name "Demo User" && git config user.email "demo@example.com"
git config commit.gpgsign false
echo "initial file" > README.md
git add . && git commit -q -m "init"
# feature branch at commit
git switch -c feature/3way
echo "feature A" > feature_a.txt
git add . && git commit -q -m "feat: add feature A"
echo "feature B" > feature_b.txt
git add . && git commit -q -m "feat: add feature B"
# main at commit ( status )
git switch main
echo "main at edit" > bugfix.txt
git add . && git commit -q -m "fix: bugfix on main"
echo "=== merge (before) graph ==="
git log --oneline --graph --all
git merge feature/3way -m "merge: feature/3way into main"
echo ""
echo "=== 3-way merge (after) graph (merge commit exists) ==="
git log --oneline --graph --all
rm -rf "$REPO"
=== 3-way merge ===
Merge made by the 'ort' strategy.#!/usr/bin/env bash
set -euo pipefail
REPO=$(mktemp -d)
cd "$REPO"
git init -q -b main
git config user.name "Demo User" && git config user.email "demo@example.com"
git config commit.gpgsign false
echo "common content" > shared.txt
git add . && git commit -q -m "init"
# feature branch: shared.txt edit
git switch -c feature/conflict
echo "feature of content" > shared.txt
git add . && git commit -q -m "feat: update shared (feature)"
# main branch: file content using edit
git switch main
echo "main of content" > shared.txt
git add . && git commit -q -m "chore: update shared (main)"
echo "=== conflict merge ==="
git merge feature/conflict 2>&1 || true
echo ""
echo "=== conflict check ==="
cat shared.txt
echo ""
echo "=== resolve conflict: main ver (before) ==="
echo "resolve conflict done — main ver (before)" > shared.txt
git add shared.txt
git commit -m "merge: resolve conflict in shared.txt"
echo ""
echo "=== final log ==="
git log --oneline --graph --all
rm -rf "$REPO"
=== Conflict created ===
CONFLICT (content): Merge conflict in shared.txt
=== After conflict resolution ===
Merge complete.📝 Exercises
Try them yourself first, then open the solution to compare.
Problem 1 (hw01.sh)
Goal: Create `feature-a` and `feature-b` branches from `main`. Have them edit the *same* line of `shared.txt`. Merge `feature-a` into `main`, then try to merge `feature-b` — resolve the conflict and complete the merge.
- Filename: hw01.sh
▶Toggle solution
#!/usr/bin/env bash
set -euo pipefail
REPO=$(mktemp -d)
cd "$REPO"
git init -q -b main
git config user.name "Demo User" && git config user.email "demo@example.com"
git config commit.gpgsign false
echo "base" > base.txt
git add . && git commit -q -m "init: add base.txt"
git switch -c feature/a
echo "feature A" > a.txt
git add . && git commit -q -m "feat: add a.txt"
git switch main
echo " file" > b.txt
git add . && git commit -q -m "chore: add b.txt on main"
git merge feature/a -m "merge: feature/a into main"
echo "=== 3-way merge ==="
git log --oneline --graph --all
rm -rf "$REPO"
Problem 2 (hw02.sh)
Goal: Create a chain of 3 small commits on a `wip` branch, then merge into `main` with `--no-ff` so a merge commit always appears (even if fast-forward was possible).
- Filename: hw02.sh
▶Toggle solution
#!/usr/bin/env bash
set -euo pipefail
REPO=$(mktemp -d)
cd "$REPO"
git init -q -b main
git config user.name "Demo User" && git config user.email "demo@example.com"
git config commit.gpgsign false
echo "common content" > conflict.txt
git add . && git commit -q -m "init"
git switch -c feature/b
echo "feature content" > conflict.txt
git add . && git commit -q -m "feat: feature version"
git switch main
echo "main content" > conflict.txt
git add . && git commit -q -m "chore: main version"
echo "=== Conflict created ==="
git merge feature/b 2>&1 || true
echo "resolve content" > conflict.txt
git add conflict.txt
git commit -m "merge: resolve conflict in conflict.txt"
echo ""
echo "=== final graph ==="
git log --oneline --graph --all
rm -rf "$REPO"
All lecture materials and example code are openly available on GitHub.
View on GitHub ↗