Menu for Per-Arne's Git Good!
Courses and project Per-Arne's Git Good!

Branches and HEAD: pointers, not copies

Module 2: Branching and merging · 12 min

The interactive lesson needs JavaScript. The text below is the complete lesson.

After this lesson you can

  • explain that a branch is a movable pointer to one commit, not a copy of the files understand
  • create and switch branches with git branch, git switch and git switch -c apply
  • predict which branch moves on a commit, from where HEAD is understand
  • keep a commit made on a detached HEAD by giving it a branch apply

Understand: A name that moves

Many people think a branch is a copy of the whole project folder. It is not. A branch is just a name that points to one commit. Here, main points to B.

git branch feature creates a new name on the same commit. No files are copied and no commit is made. HEAD stays on main.

HEAD tells you which branch you are on. git switch feature moves HEAD over to feature. Both names still point to B, so the files are the same.

The new commit C gets B as its parent. feature goes to C, and main stays at B.

HEAD can also point straight at a commit instead of at a branch. That is called a detached HEAD. Here HEAD points straight at A. git switch --detach does this, and it is useful when you want to look at an old version.

You can still commit. The new commit D gets A as its parent, but no branch moves. Only HEAD follows. If you switch away now, no name holds on to D.

The fix is to give the work a name while you are there. git switch -c experiment creates a branch at D and switches to it. Now D is easy to find again, and HEAD follows a branch again.

Watch: Watch the pointers move

We have two commits on main. First we create a new branch. Watch the graph: a new name appears on the same commit, and HEAD stays on main.

$ git branch feature

Now we switch. Only HEAD moves. The files are the same, because both names point to the same commit.

$ git switch feature

We create a new file and stage it, just as before.

$ echo "Login form" > login.txt && git add login.txt

Now we commit. Watch the graph: only feature moves to the new commit, and main stays put.

$ git commit -m "Add login form"

We switch back to main. Look at the file areas: login.txt disappears, because the snapshot of main does not have it. The file is not gone. It is safe in the commit on feature.

$ git switch main

Now we want to look at the very first version. HEAD~1 means the commit before the one HEAD is on. Watch the graph: HEAD now points straight at a commit, without any branch.

$ git switch --detach HEAD~1

We try an idea and commit it here. Watch the graph: the new commit gets its own line, and only HEAD came along. No branch holds on to it.

$ echo "Idea" > idea.txt && git add idea.txt && git commit -m "Try an idea"

We rescue the work by giving it a name. git switch -c creates the branch and switches to it in one step. Now HEAD is attached to a branch again.

$ git switch -c experiment

Tasks

  1. Create a branch called feature without switching to it.
  2. Switch to feature.
  3. Make a commit on feature.
  4. Go back to main.
  5. Look at the commit before the one main points to, without being on any branch.
  6. Keep the idea commit with a branch called experiment.

Practise: Transfer: a new line of work

A new repository with one commit on main. No demo this time — use what you just saw.

Tasks

  1. Create a branch docs with one commit on it. main must not move.
  2. Use a detached HEAD to go to the commit docs points to. Make a commit there, and keep it with a branch spike. docs must not move.

Check yourself: Recall it

You are on main and run git branch feature. Where is HEAD afterwards?

  • On feature
  • Still on main
  • Detached, straight on the commit

main, fix and docs all point to C, and HEAD is on fix. You commit. Which names point to the new commit?

  • All three, because they pointed to the same commit
  • fix, and HEAD comes along
  • None. Only HEAD goes forward.

You commit on a detached HEAD and then switch to main. What happens to the new commit?

  • It is deleted immediately
  • It still exists, but no branch points to it, so it is hard to find
  • main automatically moves to it

Review: You switch from feature to main, and login.txt disappears from the folder. Where does Git get the files of main from?

  • It replays every change from the first commit up to main
  • From a separate copy of the folder that main has stored
  • From the snapshot in the commit main points to

Common misconceptions

  • “A branch is a copy of the project folder.” It is a name that points to one commit. The files live in the commits.
  • “git branch feature switches to the new branch.” It only creates the name. git switch is what moves HEAD.
  • “A detached HEAD means something is broken.” HEAD just points straight at a commit instead of at a branch. Create a branch if you want to keep what you commit there.

Read more

What a branch really is

A branch is a tiny file that holds one commit ID. HEAD normally holds the name of a branch, not an ID:

$ cat .git/HEAD
ref: refs/heads/main
$ cat .git/refs/heads/main
ff0d3edfd4234d9bbfec609f679a5274202d42a0

When you commit, Git stores the new commit and then moves the branch that HEAD names to it. That is the whole mechanism behind "the branch I am on moves". A new branch costs a few bytes, however large the project is.

Git may pack many refs into .git/packed-refs, so do not build scripts on those files. Ask Git instead: git rev-parse main prints the ID, git symbolic-ref HEAD prints the branch HEAD is attached to.

Creating, switching, listing

Command Effect
git branch feature new name on HEAD's commit; HEAD stays where it is
git branch hotfix v1.0 new name on another commit (the start point)
git switch feature HEAD follows feature; staging area and working tree are updated
git switch -c feature create and switch in one step
git switch - back to the branch you were on before
git branch -v list branches with the newest commit of each
git branch -m old new rename a branch
$ git branch feature
$ git branch -v
  feature ff0d3ed Add contact info
* main    ff0d3ed Add contact info

The star marks the branch HEAD is on. Both names show the same ID: nothing was copied.

git checkout is the older command that both switches branches and restores files. git switch only switches, which makes it harder to misuse. git checkout -b feature is the same as git switch -c feature, and you will meet both in tutorials.

What switching does to your files

Switching fills the staging area and the working tree with the snapshot of the target commit. A file that exists only on the other branch disappears from the folder, but it is safe inside that branch's commits.

Uncommitted changes are handled carefully:

  • If the changed file is identical on both branches, Git carries your change along and prints M README.md after the switch.
  • If switching would overwrite your change, Git refuses and does nothing:
error: Your local changes to the following files would be overwritten by checkout:
    README.md
Please commit your changes or stash them before you switch branches.
Aborting

Commit the change, put it aside with git stash (a later lesson), or undo it, and then switch again. Do not reach for --discard-changes or -f: they throw your edits away.

Detached HEAD

git switch --detach <commit> makes HEAD hold a commit ID instead of a branch name. That is useful for looking at an old version, running old tests or hunting for a bug. The older git checkout <commit> does the same and prints a long note that begins "You are in 'detached HEAD' state". git switch refuses a tag or ID without the flag, so you cannot detach by accident:

fatal: a branch is expected, got tag 'v1.0'
hint: If you want to detach HEAD at the commit, try again with the --detach option.

You may commit while detached. Each new commit moves only HEAD. When you switch away, Git warns you:

Warning: you are leaving 1 commit behind, not connected to
any of your branches:
  9bc0be7 Try an idea

Rescuing a commit you left behind

The commit is not deleted, only unnamed. In order of convenience:

  1. Name it before you leave: git switch -c experiment.
  2. Use the ID from the warning: git branch experiment 9bc0be7.
  3. If the warning has scrolled away, git reflog finds it again. The reflog lesson shows how.

Commits that no name reaches are eventually cleaned up, so rescue them early. The fast-forward lesson shows how to delete a branch safely.

Naming branches

Names may contain slashes, and many teams use prefixes such as feature/login or fix/42-typo. Spaces, .., ~, ^, : and ? are not allowed, because those characters have meanings in revision syntax: HEAD~1 is the commit before HEAD.

Official documentation