Git & GitHub track

Lesson 03 · Lessons

Branches & Merging

branch, switch, merge, fast-forward vs three-way merges and conflict resolution

Lessons5 min readFree lesson

Branching is Git's most powerful feature. You can experiment with new features or fix bugs without touching the main code. Once complete, merge them together. Flexible handling of branches allows you to work on multiple ideas simultaneously, even when developing alone.

Learning Objectives

  • You can create and switch branches with git branch·git switch.
  • Can explain the difference between fast-forward merge and 3-way merge.
  • When a conflict occurs, you can directly correct and resolve it.
  • Branches merged with git branch -d can be safely deleted.

Key concepts

1) Creating/Converting Branch

git branch feature/login        #Create branch
git switch feature/login        #Branch Switch

#Create + Convert at once
git switch -c feature/login

#Check list
git branch                      #local
git branch -a                   #local + remote

2) Fast-forward Merge

main:    A---B
               \
feature:        C---D

git merge feature → main: A---B---C---D (straight line)
  • main just moves forward (no branching points).
  • No merge commits are created, so the history is clean.
git switch main
git merge feature/login         #fast-forward (default)
git merge --no-ff feature/login #Force creation of merge commit

3) 3-way Merge

main:    A---B---E
               \   \
feature:        C---D

git merge feature → main: A---B---E---M (M = merge commit)
  • When both main and feature have made their own commits.
  • Compare and merge the common ancestor (B) + the ends of the two branches (E, D).

4) Conflict resolution

#When a conflict occurs, git status displays the conflict file.
git status
# both modified: src/app.py

#Conflict markers are inserted when opening a file
<<<<<<< HEAD
Contents of the current branch
=======
Contents of the branch to be merged
>>>>>>> feature/login

#1. Remove the marker and leave the desired content
#2. git add <file>
# 3. git commit

See as example

Example 1 — ex01_branch.sh: Branch creation/conversion/list confirmation

#!/usr/bin/env bash
REPO=$(mktemp -d); cd "$REPO"
git init -q && git config user.name "For practice" && git config user.email "[email protected]"
echo "main" > main.txt && git add . && git commit -q -m "init"

git switch -c feature/hello
echo "hello" > hello.txt && git add . && git commit -q -m "feat: hello"

echo "=== Branch List ==="
git branch

git switch main
echo "=== HEAD POSITION ==="
git log --oneline --all --graph

Execution result

=== Branch List ===
* feature/hello
  main

=== HEAD POSITION ===
* a1b2c3d (feature/hello) feat: hello
* b2c3d4e (HEAD -> main) init

Bottom line: * points to the currently checked out branch.


Example 2 — ex02_merge_ff.sh: Fast-forward Merge

#!/usr/bin/env bash
REPO=$(mktemp -d); cd "$REPO"
git init -q && git config user.name "For practice" && git config user.email "[email protected]"
echo "init" > README.md && git add . && git commit -q -m "init"

git switch -c feature/ff
echo "FF function" > feature.txt && git add . && git commit -q -m "feat: FF feature"

git switch main
git merge feature/ff

echo "=== Log after merge (straight line) ==="
git log --oneline --graph

Execution result

=== Log after merge (straight line) ===
* a1b2c3d (HEAD -> main, feature/ff) feat: FF feature
* b2c3d4e init

Bottom line: fast-forward only moves the pointer without a merge commit.


Example 3 — ex03_merge_3way.sh: 3-way Merge

#!/usr/bin/env bash
REPO=$(mktemp -d); cd "$REPO"
git init -q && git config user.name "For practice" && git config user.email "[email protected]"
echo "init" > README.md && git add . && git commit -q -m "init"

git switch -c feature/3way
echo "Function A" > feature.txt && git add . && git commit -q -m "feat: feature A"

git switch main
echo "main Additional actions" > main_extra.txt && git add . && git commit -q -m "chore: main extra"

git merge feature/3way -m "merge: feature/3way into main"

echo "=== Log after merge (with merge commit) ==="
git log --oneline --graph

Execution result

=== Log after merge (with merge commit) ===
*   c3d4e5f (HEAD -> main) merge: feature/3way into main
|\
| * a1b2c3d (feature/3way) feat: feature A
* | b2c3d4e chore: main extra
|/
* 9a8b7c6 init

Bottom line: Git automatically creates a merge commit when both parties commit separately.


Example 4 — ex04_conflict.sh: Creating and resolving conflicts

#!/usr/bin/env bash
REPO=$(mktemp -d); cd "$REPO"
git init -q && git config user.name "For practice" && git config user.email "[email protected]"

echo "Common content" > shared.txt && git add . && git commit -q -m "init"

git switch -c feature/conflict
echo "Modification of feature" > shared.txt && git add . && git commit -q -m "feat: modify shared"

git switch main
echo "Modification of main" > shared.txt && git add . && git commit -q -m "chore: modify shared on main"

echo "=== Attempt to merge conflicts ==="
if ! git merge feature/conflict -m "merge" 2>/dev/null; then
    echo "A crash occurred! shared.txt contents:"
    cat shared.txt
    #Conflict resolution: adopt main version
    echo "Conflict resolution: adopt main version" > shared.txt
    git add shared.txt
    git commit -m "merge: resolve conflict in shared.txt"
    echo ""
    echo "=== Log after conflict resolution ==="
    git log --oneline --graph
fi

rm -rf "$REPO"

Execution result

A crash occurred! shared.txt contents:
<<<<<<< HEAD
Modification of main
=======
Modification of feature
>>>>>>> feature/conflict

=== Log after conflict resolution ===
*   d4e5f6a (HEAD -> main) merge: resolve conflict in shared.txt
|\
| * c3d4e5f (feature/conflict) feat: modify shared
* | b2c3d4e chore: modify shared on main
|/
* a1b2c3d init

Key point: Edit the conflict marker directly and complete the merge from git add → git commit.


Seeing from a different perspective

Git concepts Everyday metaphor
branch parallel universe
merge Merging two universes into one
fast-forward Fast forward the timeline
conflict Same object modified differently in two universes
merge commit The intersection where two universes meet

Common Mistakes

  1. Work directly from main — Always create and work on feature branches.
  2. Commit with conflict markers left in the file — If <<<, ===, and >>> remain, the build will be broken.
  3. Delete unmerged branch as -D instead of -d — Commit will be lost.
  4. Omit git pull before merging — Remote changes are not imported, resulting in larger conflicts.
  5. Omitted when fast-forward must be forced (--no-ff) — PR history disappears, making tracking difficult.

Cleanup

  • Create and switch branches with git switch -c <branch>.
  • fast-forward creates a straight history, and 3-way creates a merge commit.
  • Conflicts are resolved in the following order: Edit marker → git add → git commit.
  • The merged branches are organized as git branch -d.

Try it yourself

cd 03_Merge_with_branch/src
chmod +x *.sh
./ex01_branch.sh
./ex02_merge_ff.sh
./ex03_merge_3way.sh
./ex04_conflict.sh

Application:

  1. Compare how the graph changes before and after merging with git log --oneline --graph --all.
  2. Use git merge --abort to create a scenario to abort conflict merging.

Example files

The runnable files used in this lesson. Copy them into your own project and experiment.

src/ex01_branch.shShell
#!/usr/bin/env bash
set -euo pipefail

REPO=$(mktemp -d)
cd "$REPO"
git init -q -b main
git config user.name "For practice" && git config user.email "[email protected]"
    git config commit.gpgsign false

echo "main content" > main.txt
git add . && git commit -q -m "init: main"

echo "=== Create branch ==="
git branch feature/hello
git branch feature/world

echo ""
echo "=== Branch List ==="
git branch

echo ""
echo "=== Switch to feature/hello ==="
git switch feature/hello
echo "hello" > hello.txt
git add . && git commit -q -m "feat: add hello"

echo ""
echo "=== Entire Graph ==="
git log --oneline --graph --all

echo ""
echo "=== Return to main ==="
git switch main
git branch

rm -rf "$REPO"
src/ex02_merge_ff.shShell
#!/usr/bin/env bash
set -euo pipefail

REPO=$(mktemp -d)
cd "$REPO"
git init -q -b main
git config user.name "For practice" && git config user.email "[email protected]"
    git config commit.gpgsign false

echo "init" > README.md
git add . && git commit -q -m "init"

git switch -c feature/fast-forward
echo "Added FF function" > feature.txt
git add . && git commit -q -m "feat: add feature"

echo "=== Log before merge ==="
git log --oneline --graph --all

git switch main
git merge feature/fast-forward

echo ""
echo "=== log after fast-forward merge (straight line) ==="
git log --oneline --graph --all

echo ""
echo "=== Force creation of merge commit with --no-ff ==="
git switch -c feature/no-ff
echo "no-ff function" > 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 "=== Log after --no-ff (with merge commit) ==="
git log --oneline --graph --all

rm -rf "$REPO"
src/ex03_merge_3way.shShell
#!/usr/bin/env bash
set -euo pipefail

REPO=$(mktemp -d)
cd "$REPO"
git init -q -b main
git config user.name "For practice" && git config user.email "[email protected]"
    git config commit.gpgsign false

echo "initial file" > README.md
git add . && git commit -q -m "init"

#Commit from feature branch
git switch -c feature/3way
echo "Function A" > feature_a.txt
git add . && git commit -q -m "feat: add feature A"
echo "Function B" > feature_b.txt
git add . && git commit -q -m "feat: add feature B"

#Separate commits from main as well (creating branch state)
git switch main
echo "Bug fix in main" > bugfix.txt
git add . && git commit -q -m "fix: bugfix on main"

echo "=== Graph before merging ==="
git log --oneline --graph --all

git merge feature/3way -m "merge: feature/3way into main"

echo ""
echo "=== Graph after 3-way merge (with merge commit) ==="
git log --oneline --graph --all

rm -rf "$REPO"
src/ex04_conflict.shShell
#!/usr/bin/env bash
set -euo pipefail

REPO=$(mktemp -d)
cd "$REPO"
git init -q -b main
git config user.name "For practice" && git config user.email "[email protected]"
    git config commit.gpgsign false

echo "Common content" > shared.txt
git add . && git commit -q -m "init"

#feature branch: Edit shared.txt
git switch -c feature/conflict
echo "Content of feature" > shared.txt
git add . && git commit -q -m "feat: update shared (feature)"

#main branch: Modify the same file with different contents
git switch main
echo "Contents of main" > shared.txt
git add . && git commit -q -m "chore: update shared (main)"

echo "=== Attempt to merge with conflict ==="
git merge feature/conflict 2>&1 || true

echo ""
echo "=== Check for conflict markers ==="
cat shared.txt

echo ""
echo "=== Conflict resolution: adopt main version ==="
echo "Conflict resolved — main version" > 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"

Assignment

Problem 1

Instead of fast-forward merging, modify different files in both branches. Implement a scenario where a 3-way merge occurs.

  • File name: hw01.sh
  • Requirements:
  1. Initialize temporary storage and commit base.txt.
  2. Create a feature/a branch, add a.txt and commit.
  3. Return to main, add b.txt and commit.
  4. Run git merge feature/a -m "merge: feature/a".
  5. Confirm that a merge commit has been created with git log --oneline --graph --all.
#Expected output (the key part)
* Merge commit hash (HEAD -> main) merge: feature/a
|\
| * hash (feature/a) ...
* | Hash...
|/
* hash init

Problem 2

Modifying the same line in the same file in two branches causes a conflict, Write a script that resolves it yourself and leaves a merge commit.

  • File name: hw02.sh
  • Requirements:
  1. Initialize temporary storage and write and commit “common content” to conflict.txt.
  2. In the feature/b branch, overwrite conflict.txt with “feature contents” and commit.
  3. In main, overwrite conflict.txt with “main content” and commit.
  4. Try git merge feature/b to induce a crash.
  5. Replace the conflict file contents with “resolved contents” and do git add → git commit.
  6. Print the final log with the --graph option.

Check the correct answer

After writing it yourself, compare it with the reference solution below.

Show the reference solution

Try the assignment yourself first — comparing your attempt with a working solution is where most of the learning happens.

hw01.shShell
#!/usr/bin/env bash
set -euo pipefail

REPO=$(mktemp -d)
cd "$REPO"
git init -q -b main
git config user.name "For practice" && git config user.email "[email protected]"
    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 "Function A" > a.txt
git add . && git commit -q -m "feat: add a.txt"

git switch main
echo "support 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 result ==="
git log --oneline --graph --all

rm -rf "$REPO"
hw02.shShell
#!/usr/bin/env bash
set -euo pipefail

REPO=$(mktemp -d)
cd "$REPO"
git init -q -b main
git config user.name "For practice" && git config user.email "[email protected]"
    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 "=== A crash occurred ==="
git merge feature/b 2>&1 || true

echo "What was resolved" > 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"

Learn this lesson with a mentor

Work through the Git & GitHub track on your own, or join a guided cohort at our Pitampura campus and online.

Call NowEnroll Now