Skip to main content
    Courses/Git/Branching and Merging

    Lesson 3 • Intermediate

    Branching and Merging 🌿

    By the end of this lesson you'll be able to spin up an isolated branch to build a feature, switch between lines of work, merge them back, and clean up — the core skill that turns Git from "save button" into real team collaboration.

    What You'll Learn

    • Create, list, and switch branches with git branch and git switch (and the git checkout -b classic)
    • Understand why branches exist — isolated, throwaway lines of work that keep main safe
    • Merge a branch back with git merge, and tell a fast-forward from a merge commit
    • Delete branches safely with -d (and force-delete with -D) once they're merged
    • Read what HEAD means — Git's 'you are here' pointer
    • Visualise your whole history with git log --graph --oneline --all

    1️⃣ Why Branches Exist

    A branch is just a movable label that points at a commit. That's it — Git copies no files when you branch, it simply writes one new pointer, so creating a branch is instant even in a huge project. The point of a branch is isolation: you can build a half-finished feature, break things, and experiment freely without ever putting the shared main branch at risk. When the work is good you merge it back; if it's a dead end you throw the branch away and lose nothing else.

    Worked example: create, switch, and list branches
    # A branch is just a movable label pointing at a commit.
    # Creating one is instant — Git copies NO files, it writes one pointer.
    
    # List your branches (the current one is marked with * and shown green)
    git branch
    
    # Create a branch (a new pointer at the commit you're sitting on)
    git branch feature-login
    
    # Switch to it — your working files now follow that branch (modern syntax)
    git switch feature-login
    
    # Create AND switch in one step (the everyday shortcut)
    git switch -c feature-login
    
    # Classic equivalents you'll still see everywhere:
    git checkout feature-login      # same as: git switch feature-login
    git checkout -b feature-login   # same as: git switch -c feature-login
    Output
    * main
      experiment
      feature-login
      fix-navbar
    Run these commands in a real Git repository in your own terminal. The * marks the branch you're currently on.

    Your turn. Start a new feature on its own branch, prove you're on it, and make a commit. Fill in the three blanks marked ___ using the hints in the comments.

    🎯 Your turn: branch off for a new feature
    # 🎯 YOUR TURN — replace each ___ then run it in your terminal.
    # Goal: start a new feature without touching main.
    
    # 1) Create AND switch to a branch called "add-search"
    git switch ___ add-search      # 👉 the flag that means "create" is -c
    
    # 2) Prove you're on it — list branches and look for the *
    git ___                        # 👉 the command that lists branches
    
    # 3) Make a change and commit it on this branch
    echo "search box" > search.txt
    git add search.txt
    git commit -m "Add search box"
    
    # ✅ Expected output of step 2 (the * is now on your new branch):
    #    * add-search
    #      main
    Output
    Switched to a new branch 'add-search'
    * add-search
      main
    [add-search 7f3a1c2] Add search box
     1 file changed, 1 insertion(+)
     create mode 100644 search.txt
    Fill in the ___ blanks, run them in your terminal, and check step 2's output shows the * on add-search.

    2️⃣ HEAD — "You Are Here"

    HEAD is Git's "you are here" marker. Nearly always it points at the branch you're currently on, and that branch points at your latest commit — so HEAD → main → a1b2c3d. When you switch branches, HEAD simply moves to point at the other branch. It's a label tracking your position, not a separate copy of your code.

    Worked example: HEAD follows you between branches
    # HEAD is Git's "you are here" pointer. It normally points at the
    # branch you're on, and that branch points at your latest commit:
    #
    #   HEAD -> main -> a1b2c3d (the commit you'd build the next one on top of)
    #
    # When you switch branches, HEAD just moves to point at the other branch.
    
    # See where HEAD is pointing right now
    git branch --show-current        # prints the current branch name, e.g. main
    
    # Switching moves HEAD from one branch to another
    git switch feature-login         # HEAD -> feature-login
    git switch main                  # HEAD -> main again
    
    # These are the SAME commit — HEAD is a label, not a separate copy:
    git log -1 HEAD --oneline        # newest commit on the current branch
    Output
    main
    Switched to branch 'feature-login'
    Switched to branch 'main'
    a1b2c3d Add navbar links
    Run these in your terminal. HEAD moves with you; your files always match wherever HEAD points.

    3️⃣ Merging Branches

    Merging brings the commits from one branch into another. First switch to the branch you want to receive the work, then run git merge with the branch you want to pull in. Git picks one of two strategies automatically. A fast-forward happens when main hasn't moved since the branch split off — Git just slides the main label forward and history stays perfectly linear. A three-way merge happens when both branches gained commits — Git can't slide the label, so it builds a new merge commit with two parents to tie the lines of history together.

    Worked example: merge a feature into main
    # Merging brings the commits from one branch INTO another.
    # Rule of thumb: switch to the branch you want to RECEIVE the work first.
    
    # 1) Switch to the branch you want to merge INTO
    git switch main
    
    # 2) Merge the feature branch in
    git merge feature-login
    
    # FAST-FORWARD merge: main has had no new commits since the branch
    # split off, so Git just slides the main label forward. No merge
    # commit is created — the history stays perfectly linear.
    
    # THREE-WAY (merge-commit) merge: both branches gained commits, so Git
    # can't just slide the label. It builds a NEW merge commit with TWO
    # parents to tie the two lines of history back together.
    Output
    Updating a1b2c3d..d4e5f6g
    Fast-forward
     login.js | 24 ++++++++++++++++++++++++
     1 file changed, 24 insertions(+)
     create mode 100644 login.js
    Run these in your terminal. Here main hadn't moved, so Git did a fast-forward (no merge commit).

    4️⃣ Deleting Merged Branches

    Once a branch is merged, its commits live safely on the branch you merged into, so the branch label has done its job. Delete it with git branch -d name — the lower-case -d is the safe delete: Git refuses if the branch isn't fully merged, protecting you from losing commits. The capital -D is the force delete — it throws the branch away even if its work was never merged, so reach for it only when you genuinely want to discard that experiment.

    Worked example: safe (-d) vs force (-D) delete
    # Once a branch is merged, its work lives safely on main, so the
    # branch label has done its job and can go.
    
    # Safe delete — Git REFUSES if the branch isn't fully merged yet,
    # which protects you from losing commits by accident.
    git branch -d feature-login
    
    # Force delete — throws the branch away even if it was never merged.
    # Use the capital -D only when you truly want to discard that work.
    git branch -D experiment
    
    # Tip: list branches already merged into your current branch, so you
    # know which ones are safe to clean up with -d.
    git branch --merged
    Output
    Deleted branch feature-login (was d4e5f6g).
    Deleted branch experiment (was 9z8y7x6).
    Run these in your terminal. Try -d on an unmerged branch and watch Git refuse — that's the safety net working.

    Now put the whole loop together: switch to main, merge your finished feature, and safely delete it. Fill in the three blanks.

    🎯 Your turn: merge and clean up
    # 🎯 YOUR TURN — you finished "add-search" and want it on main.
    # Replace each ___ then run it in your terminal.
    
    # 1) Move to the branch that should RECEIVE the work
    git switch ___                 # 👉 the main branch's name
    
    # 2) Merge your finished feature branch in
    git merge ___                  # 👉 the branch you built: add-search
    
    # 3) The merge worked, so safely delete the merged branch
    git branch ___ add-search      # 👉 the lower-case "safe delete" flag
    
    # ✅ Expected output (a fast-forward, then the delete confirmation):
    #    Updating a1b2c3d..7f3a1c2
    #    Fast-forward
    #     search.txt | 1 +
    #     1 file changed, 1 insertion(+)
    #    Deleted branch add-search (was 7f3a1c2).
    Output
    Switched to branch 'main'
    Updating a1b2c3d..7f3a1c2
    Fast-forward
     search.txt | 1 +
     1 file changed, 1 insertion(+)
    Deleted branch add-search (was 7f3a1c2).
    Fill in the ___ blanks and run them. Your output should match the fast-forward summary and the "Deleted branch" line.

    5️⃣ Visualising Your History

    Reading branches as plain text gets confusing fast. One command draws your entire repository as an ASCII graph — every branch, every merge, every commit — right in the terminal. Learn this early; it's the fastest way to build a mental picture of what your branches are doing.

    Worked example: the whole repo as a graph
    # Reading branches as text is hard. This one command draws the whole
    # repository as an ASCII graph — every branch, every merge, every commit.
    
    git log --graph --oneline --all
    
    #   --graph    draw the branch/merge lines on the left
    #   --oneline  one short line per commit (hash + message)
    #   --all      include ALL branches, not just the current one
    #
    # Tip: add --decorate to label which branch each commit belongs to
    # (most modern Git versions show those labels automatically).
    Output
    *   d4e5f6g (HEAD -> main) Merge branch 'feature-login'
    |\
    | * c3d4e5f (feature-login) Add login validation
    | * b2c3d4e Add login form
    |/
    * a1b2c3d Add navbar links
    * 9z8y7x6 Initial commit
    Run this in your terminal. The lines on the left show where branches split and merge back; HEAD -> main marks where you are.

    6️⃣ Rebase vs Merge

    git rebase replays your commits on top of another branch for a clean, linear history instead of a merge commit. It's powerful but dangerous on shared branches. Follow the golden rule: never rebase commits you've already pushed, because rebasing rewrites history and breaks anyone who already pulled it.

    Worked example: rebase for a linear history
    # git rebase replays your commits on top of another branch,
    # producing a clean, linear history instead of a merge commit.
    
    # On your feature branch, replay its commits onto the latest main
    git switch feature
    git rebase main
    
    # Then fast-forward main onto the rebased commits
    git switch main
    git merge feature
    
    # GOLDEN RULE: never rebase commits you have already pushed and
    # shared — rebasing rewrites history and breaks everyone else's work.
    # Safe:      rebase YOUR local, unpushed feature branch onto main.
    # Dangerous: rebase main or any branch others have pulled.
    Output
    Successfully rebased and updated refs/heads/feature.
    Run these in your terminal. Only ever rebase your own local, unpushed branches.

    7️⃣ git stash — Quick Save

    git stash temporarily shelves your uncommitted changes so you can switch branches with a clean working directory. When you come back, git stash pop restores everything exactly as it was — perfect for when an urgent bug interrupts your half-finished feature.

    Worked example: shelve work with git stash
    # Scenario: you're mid-change on feature-login but must fix a bug on main.
    
    # 1. Shelve your uncommitted changes and clean the working directory
    git stash
    
    # 2. Switch to main and fix the bug
    git switch main
    git commit -am "Fix critical bug"
    
    # 3. Go back to your feature and restore the shelved work
    git switch feature-login
    git stash pop
    
    # Other stash commands
    git stash list            # show all stashes (a last-in, first-out stack)
    git stash apply           # restore the latest stash but KEEP it in the list
    git stash drop            # delete the latest stash
    git stash clear           # delete ALL stashes
    Output
    Saved working directory and index state WIP on feature-login: a1b2c3d Add login form
    
    stash@{0}: WIP on feature-login: a1b2c3d Add login form
    stash@{1}: WIP on main: 9z8y7x6 Experiment with dark mode
    Run these in your terminal. Stashes form a stack — the most recent is stash@{0}.

    Common Errors (and the fix)

    • "error: Your local changes to the following files would be overwritten by checkout": you tried to switch branches with uncommitted changes that clash. Either git commit the work first, or shelve it with git stash, switch, and git stash pop later.
    • "error: The branch 'experiment' is not fully merged": git branch -d is protecting you — that branch has commits not on your current branch. Merge it first, or if you really want to discard the work use the capital git branch -D experiment.
    • Confusing a branch with a commit: a branch is a moving label; a commit is a fixed snapshot. git branch x doesn't save your files — only git commit does. The branch label just moves to whatever you commit next.
    • "fatal: A branch named 'feature' already exists": you tried to create a branch that's already there. Use git switch feature to move to it instead of recreating it.
    • Merging in the wrong direction: git merge pulls the named branch into the one you're on. Switch to main first, then git merge feature — not the other way around.

    Pro Tips

    • 💡 One branch per task: name it for the work — fix-navbar, add-search — so the branch list reads like a to-do list.
    • 💡 Visualise often: git log --graph --oneline --all is your map. Alias it to something short like git lg.
    • 💡 Delete merged branches: run git branch --merged now and then and clean up with -d so the list stays meaningful.
    • 💡 Never rebase shared history: rebase only your own local, unpushed branches.

    📋 Quick Reference

    CommandPurpose
    git branchList branches (current marked *)
    git branch nameCreate a branch
    git switch -c nameCreate and switch (≡ checkout -b)
    git switch nameSwitch to an existing branch
    git merge branchMerge branch into the current one
    git branch -d nameSafe-delete a merged branch
    git branch -D nameForce-delete an unmerged branch
    git log --graph --oneline --allDraw the whole history as a graph
    git stash / popShelve / restore uncommitted work

    Frequently Asked Questions

    Q: What's the difference between a branch and a commit?

    A commit is a permanent saved snapshot of your files with its own ID (hash). A branch is just a lightweight, movable label that points at one commit — usually the latest one on that line of work. Making a new commit moves the branch label forward; making a new branch does not create any snapshots, it only adds another pointer.

    Q: Should I use git switch or git checkout?

    Prefer git switch for changing branches and git switch -c to create-and-switch — it was added precisely to make branch work clearer and safer. git checkout still works and you'll see it in older tutorials, but it also does unrelated jobs (like restoring files), which is why Git split those duties out into switch and restore.

    Q: When does Git fast-forward instead of making a merge commit?

    Git fast-forwards when the branch you're merging into has had no new commits since the other branch split off. There's nothing to reconcile, so Git just slides the label forward and history stays linear. If both branches gained commits, Git can't slide the label and instead creates a merge commit with two parents (a three-way merge).

    Q: What is HEAD?

    HEAD is Git's 'you are here' pointer. Almost always it points at the branch you're currently on, which in turn points at your latest commit. When you switch branches, HEAD moves to the new branch — it's a label that tracks your position, not a separate copy of your code.

    Q: Is it safe to delete a branch after merging?

    Yes. Once a branch is merged, its commits live on the branch you merged into, so deleting the original label loses nothing. Use git branch -d (lower-case) — it refuses to delete a branch that isn't fully merged, protecting you from accidental data loss. Reserve the capital -D for deliberately discarding unmerged work.

    Mini-Challenge: Ship a Feature Branch

    No commands given this time — just the plan and a blank canvas. Work through the whole branch lifecycle in your own terminal, then check your graph against the expected result in the comments.

    🎯 Mini-Challenge: branch, commit, merge, delete
    # 🎯 MINI-CHALLENGE: ship a feature on its own branch
    # No commands given — just the plan. Run each step in your terminal.
    #
    # 1. From main, create AND switch to a branch called "about-page"
    # 2. Create a file (e.g. about.html), then add it and commit it
    # 3. Draw the graph and confirm about-page is ahead of main
    # 4. Switch back to main and merge about-page in
    # 5. Delete the merged branch with the SAFE (lower-case) delete flag
    #
    # ✅ Expected: after the merge, "git log --graph --oneline --all" shows
    #    about-page's commit sitting on the same line as main (a fast-forward),
    #    and "git branch" no longer lists about-page.
    
    # your commands here
    Run each step in your terminal. Use git log --graph --oneline --all to confirm the result.

    🎉 Lesson Complete!

    • ✅ A branch is a movable label pointing at a commit — branching copies no files
    • git switch -c name (or git checkout -b name) creates and switches in one step
    • HEAD is "you are here" — it follows you as you switch branches
    • git merge fast-forwards when possible, else makes a two-parent merge commit
    • -d safe-deletes merged branches; -D force-deletes unmerged ones
    • git log --graph --oneline --all draws your whole history
    • Next lesson: push your branches to GitHub and learn remote repository workflows

    Sign up for free to track which lessons you've completed and get learning reminders.

    Previous

    Cookie & Privacy Settings

    We use cookies to improve your experience, analyze traffic, and show personalized ads. You can manage your preferences below.

    By clicking "Accept All", you consent to our use of cookies for analytics and personalized advertising. You can customize your preferences or reject non-essential cookies.

    Privacy PolicyTerms of Service