LEARN · PHASE 02 · LESSON 13

Branches Are Movable Labels, Not Copies

You can create, switch, and delete a branch without the repository noticing

Type
Learn
Prerequisites
Phase 00, Lesson 2; Phase 02, Lesson 12
Time
~35 minutes

Learning Objectives

  • Explain what a branch is in one sentence that has no "copy" in it
  • Create, switch, rename, and delete branches without fear of data loss
  • Move a branch pointer deliberately and say what that does to history
  • Use git switch instead of git checkout for day-to-day work, and say why

The Problem

You are three commits into an experiment. The experiment is wrong. You want main back and the experiment out of the way.

The instinct is git checkout main, then... something with branch -D that you are afraid of, because the last time you deleted a branch in a panic you spent an evening reconstructing work that was, at the time, unrecoverable.

Then later: a long-running feature branch finishes. The reviewer asks for the three middle commits to be squashed before merge. You have to move that branch. It feels like rearranging furniture in a building you are standing in.

Both stalls come from the same wrong picture — that a branch is a copy of the project, expensive to create and awkward to rearrange. It is not a copy. It is a bookmark.


The Concept

A branch is a file containing one commit hash. That is the entire implementation. When you see a branch pointer in git log, that is the whole truth.

flowchart LR
    subgraph refs
        HEAD[HEAD<br/>points at main]
        MAIN[main<br/>movable label]
        FEAT[feature<br/>movable label]
    end
    C1[commit A] --> C2[commit B] --> C3[commit C] --> C4[commit D]
    MAIN -.->|points at| C2
    FEAT -.->|points at| C4
    HEAD -.->|points at| MAIN
Mermaid source — renders as a diagram where mermaid.js is available

Each label points at a commit. Commits point at their parent. Nothing is copied when you create a branch — you write 41 bytes into a file.

git branch feature              # writes a label. Instant.
git switch feature              # moves HEAD. Instant.
git log --oneline --graph --all # the picture

Two facts fall out of this and they change how you work:

Moving a label changes history's shape, not its content. Commits are immutable. But which commit the label names is a choice, and you can change it at any time — git branch -f feature main, git reset --hard, git rebase. That is what makes squashing and rebasing possible at all.

Deleting a branch deletes a label, not work. The commits stay in the object store. What you lose is the name — and if the commits are unreachable from any branch, they eventually get garbage-collected. So git branch -d refuses when the branch holds unmerged commits, and that refusal is protecting something real, not being fussy.

git branch -d feature     # refuses if unmerged — this is the safe one
git branch -D feature     # deletes anyway; commits survive until gc, then may not

HEAD is itself a label, and it usually points at a branch. Detached HEAD means it points straight at a commit — you are not on any branch, and a commit made there is harder to find later.

Use switch, not checkout

git checkout does two unrelated jobs: move between branches and restore files from a commit. That dual meaning is why git checkout main -- file and git checkout main are so easily confused. git switch only changes branches; git restore only touches files. Split jobs, split commands.

The reframe: a branch is a bookmark, not a photocopy — which means

creating one is free, moving one is a decision you can always reverse, and

deleting one only ever deletes the bookmark.


Build It

1. Create a branch and time it

cd ~/git-drill
time git switch -c feature
git branch -v

✅ You pass when a branch named feature appears in git branch -v and the command returned instantly.

2. Prove it is a label

cat .git/refs/heads/feature

It is one line: a commit hash.

✅ You pass when you can read the hash out of the file and match it against git rev-parse feature.

3. Move it and see what that means

git switch feature
printf 'two\n' > d.txt && git add d.txt && git commit -m "add d"
git switch main
git branch -f feature main     # label now points where main points
git log --oneline feature -1

The commit still exists — reachable through the reflog, not through the branch.

✅ You pass when git log --oneline feature -1 now shows main's commit, and git reflog still lists the commit you moved away from.

4. Delete, then find it again

git branch -d feature
git reflog            # the commit is still listed here

✅ You pass when the delete succeeds and the reflog still shows the commit you just orphaned.

5. Meet detached HEAD

git switch --detach HEAD
git status            # says "HEAD detached at ..."

✅ You pass when git status reports a detached HEAD and you can say what would make a commit made here hard to find later.


Use It

Command / conceptWhen you reach for it
git switch -c <name>Starting isolated work
git switch <name>Moving between branches
git switch -Jumping back to where you were
git branch -vSeeing every label and what it points at
git branch -dCleaning up a merged branch
git branch -DDeleting an unmerged branch — commits survive in reflog until gc
git branch -fMoving a label deliberately
Detached HEADYou are on a commit, not a branch. Most commits made here are findable only via reflog
git reflog"Where was that branch?" — the local record of every HEAD move

Exercises

  1. Create three branches in a row from the same commit. List them. ✅ You pass when all three appear in git branch -v pointing at the same hash, and git log --oneline --graph --all shows no duplicated commits.
  1. Move a branch you have already pushed to two commits behind main, then put it back. Find every command you used afterwards. ✅ You pass when you have the branch back where it started and you can name the reflog command that would have found it if you had not.
  1. Delete a branch holding real unmerged commits with -d, then with -D. Record what each command said. ✅ You pass when you have both messages and can say why the first refused.

Key Terms

TermWhat people sayWhat it actually means
branch"a copy of the project"A file containing one commit hash. Creating it copies nothing.
HEAD"my current branch"A label that usually points at a branch. Points at a commit directly = detached HEAD.
detached HEAD"broken git"HEAD points straight at a commit. Valid, but commits made here are only reachable via reflog.
git branch -d"delete a branch"Deletes the label. Refuses if the branch holds unmerged commits.
git branch -D"force delete"Deletes the label anyway. Commits survive until garbage collection, then may not.
reflog"undo for branches"Local record of every HEAD and branch move. Your first stop when a branch "disappeared".
git switch"checkout but better"Moves between branches only. restore handles files.

What this lesson skipped

Detached HEAD workflows in depth. Checking out a specific commit to bisect a bug is real and useful — it arrives with Phase 17's operational material, not here.

Worktrees. A worktree gives a second working folder on a second branch without switching. The word contains "working tree" and that is exactly what it manipulates. Phase 16 is dedicated to it; mentioning it here would be a forward reference.

Remote-tracking branches. origin/main is a different animal from main — it is a record of what the remote had last time you fetched. Lesson 18.

Check yourself

4 questions. Answer before you reveal — recognising the right option is not the same as being able to produce it.

  1. You create a branch and immediately switch to it. What actually happened on disk?

    1. The whole project was duplicated into a new directory
    2. A file under .git/refs/heads was written containing one commit hash, and HEAD was pointed at it
    3. A second copy of every object was added to .git/objects
    4. Nothing on disk changed until the first commit on the new branch
    Show answer

    B. A file under .git/refs/heads was written containing one commit hash, and HEAD was pointed at it

    A branch is one file holding one hash, so creation and switching are both instant metadata edits — which is why creating twenty branches costs nothing. A directory copy (A) and a second object store (B) are both the copy mental model this lesson removes. Option D is wrong in the same direction: HEAD is moved at switch time, before any commit exists on the branch.

  2. A branch holds three commits nobody else has. You run git branch -D on it. Which statement is true?

    1. The three commits are deleted immediately and are unrecoverable
    2. The commits remain in the object store and are reachable through the reflog, but nothing references them as a branch any more
    3. The three commits are moved onto whichever branch you are currently on
    4. The delete fails because unmerged commits cannot be removed under any flag
    Show answer

    B. The commits remain in the object store and are reachable through the reflog, but nothing references them as a branch any more

    -D removes the label, not the commits. They stay reachable via reflog for now; garbage collection is what eventually reclaims them, so 'recoverable' is a matter of timing, not certainty. Immediate destruction (A) overstates it — that is what garbage collection does later. Nothing re-parents your commits onto your current branch (C); that is what merge and rebase do, explicitly. And -D exists precisely to override the safety refusal (D), which is why plain -d refusing is meaningful.

  3. A colleague runs git checkout main -- config.json, expecting to switch branches, and the working tree changes under them instead. Why is this confusing rather than a git bug?

    1. It is a known bug fixed in recent git versions
    2. checkout does two unrelated jobs — moving between branches and restoring files — and only the second one is a real file restore
    3. The -- separator is deprecated syntax
    4. They were in detached HEAD, so checkout ignored the branch argument
    Show answer

    B. checkout does two unrelated jobs — moving between branches and restoring files — and only the second one is a real file restore

    checkout carries two meanings, and -- plus a path selects the file-restoring one. Nothing is broken (A) and the separator is valid syntax (C). Detached HEAD (D) has nothing to do with it — the file restore happens regardless of what HEAD points at. This is exactly why the lesson recommends switch for branch moves and restore for files: split commands so the argument shape tells you which job you are doing.

  4. You are in detached HEAD after checking out a commit to inspect it. You make a commit here and switch away. How would you find that commit a week later?

    1. git branch — it will show up as a detached entry you can check out
    2. git log, which lists commits with no branch by default
    3. git reflog, which records every HEAD move including the ones made while detached
    4. There is no way; commits outside a branch are not retained
    Show answer

    C. git reflog, which records every HEAD move including the ones made while detached

    The reflog is a local record of where HEAD has pointed, and it includes detached positions — that is the whole reason it is the answer to 'where was that?'. git branch (A) lists labels, and there is no label here, which is the whole difficulty. git log (B) follows refs, so it will not volunteer your orphaned commit. And they are retained (D) — but only because of the reflog, which is a weaker guarantee than having a named branch.