← Back to Git series
🔀
Git & GitHub Deep Dive
branch · switch · merge · conflict

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.

branchmergeconflict
Duration
1-2 hours
Level
📊 Beginner
Prerequisite
🎯 Chapter 2
Outcome
Branch off, merge back, resolve conflicts

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

bash
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

ScenarioMerge 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
bash
git switch main
git merge feature

3) Conflicts

When two branches edit the same line, Git pauses with `CONFLICT`. Files contain:

text
<<<<<<< HEAD
main's version
=======
feature's version
>>>>>>> feature

Edit the file to keep what you want, remove the markers, then:

bash
git add <file>
git commit          # completes the merge

4) Graph view

bash
git log --oneline --graph --all

Shows the actual topology — invaluable when something looks wrong.

Examples

Example 1 — `ex01_branch.sh`: create and switch branches

**Output**

text
=== Create branch ===
=== Switch branch ===
On branch feature

Key: `git switch -c feature` is the idiomatic create-and-switch.

Example 2 — `ex02_merge_ff.sh`: fast-forward scenario

**Output**

text
=== Fast-forward merge ===
Updating a1b2c3d..b2c3d4e
Fast-forward

Key: when `main` has not moved, the merge just advances the pointer.

Example 3 — `ex03_merge_3way.sh`: 3-way merge

**Output**

text
=== 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**

text
=== 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

  1. **Deleting an unmerged branch** with `-d` — Git refuses. Use `-D` only when you really mean to throw it away.
  2. **Forgetting to switch** before merging — you might merge `feature` into `feature` and wonder what happened.
  3. **Resolving a conflict but not staging** — Git stays in mid-merge state. `git add` first.
  4. **`git pull` causing surprise conflicts** — `pull` is `fetch + merge`; consider `--rebase` for a linear history.
  5. **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

bash
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.

ex01_branch.shcreate and switch branches
CODE
#!/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"
▶ Output
=== Create branch ===
=== Switch branch ===
On branch feature
ex02_merge_ff.shfast-forward scenario
CODE
#!/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"
▶ Output
=== Fast-forward merge ===
Updating a1b2c3d..b2c3d4e
Fast-forward
ex03_merge_3way.sh3-way merge
CODE
#!/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"
▶ Output
=== 3-way merge ===
Merge made by the 'ort' strategy.
ex04_conflict.shconflict and resolution
CODE
#!/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"
▶ Output
=== 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.

Exercise 1

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.

Requirements
  • Filename: hw01.sh
Toggle solution
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"
Exercise 2

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).

Requirements
  • Filename: hw02.sh
Toggle solution
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"
Example code / lecture materials

All lecture materials and example code are openly available on GitHub.

View on GitHub ↗