Git path · The data model · Topic 2 of 13

Refs, branches and HEAD: a branch is a pointer

Not started

Commit hashes are hard to remember, so Git gives them names called refs. A branch is a ref under refs/heads/ that holds the hash of one commit, its tip. A tag is a ref under refs/tags/. HEAD is a special ref that says what you have checked out: normally it names a branch, and that branch names a commit. Committing makes a new commit whose parent is the current tip, then moves the branch to it.

Why it matters: Once a branch is just a 41-byte pointer, creating, moving and deleting branches stops feeling risky, and detached HEAD stops being scary.

  1. 1A ref is a name for a hash

    refs/heads/main holds one commit hash. That is the whole branch.

  2. 2Committing moves the branch

    A new commit's parent is the commit the branch pointed at; then the branch is moved to the new commit.

  3. 3HEAD says which branch you are on

    HEAD normally names a branch (ref: refs/heads/main). Detached HEAD holds a commit hash directly.

  4. 4Commits don't belong to branches

    A commit is on a branch only because it can be reached from the branch's tip by following parents.

1

How it works

The explanation, with examples you can run.

See the pointers

Make two commits and a second branch:

git init -q -b main demo && cd demoecho "hello" > greeting.txt && git add . && git commit -q -m "Add greeting"echo "bye" > farewell.txt && git add . && git commit -q -m "Add farewell"git branch featurecat .git/HEADgit rev-parse main feature
ref: refs/heads/main128af6f8019138b890d8453f93b6b40bdc3b0e51128af6f8019138b890d8453f93b6b40bdc3b0e51

git branch feature wrote one pointer to the commit you are on. Both names now point at the same commit; nothing was copied. In a fresh repository a ref is a small file such as .git/refs/heads/feature; after git gc, many refs are packed into .git/packed-refs, and newer repositories can use the reftable format instead. Use git rev-parse or git show-ref rather than reading the files.

Committing moves only the current branch

git switch -q featureecho "more" >> greeting.txt && git commit -q -am "Extend greeting"cat .git/HEADgit log --oneline --graph --all
ref: refs/heads/feature* ee97a7b Extend greeting* 128af6f Add farewell* c15e99d Add greeting

HEAD names feature, so the commit moved feature and left main where it was. git log --oneline --decorate would show the labels; the graph shows one line because main is simply an ancestor of feature. Neither branch "owns" a commit: 128af6f is on both, because both tips can reach it by following parents.

Detached HEAD

Check out a commit instead of a branch and HEAD holds the hash itself:

git switch -q --detach maincat .git/HEADgit symbolic-ref -q HEAD || echo "detached"git switch -q main
128af6f8019138b890d8453f93b6b40bdc3b0e51detached

Detached HEAD is useful for looking at an old version or running a bisect. Commits made there are real, but no branch moves to follow them. When you switch away, Git warns that you are leaving commits behind; git switch -c rescue <hash> keeps them.

Names you can use

Anywhere Git wants a commit you can write a hash prefix, a branch or tag name, HEAD, or an expression: HEAD~2 (two parents back), main^2 (the second parent of a merge), feature@{yesterday} (where the branch pointed a day ago, from the reflog) or main..feature (commits reachable from feature but not main). Remote-tracking refs such as origin/main live under refs/remotes/ and are covered in the remotes topic.

git switch (for branches) and git restore (for files) arrived in Git 2.23 to split up the overloaded git checkout, which still works.

2

Check yourself

3 questions. Get them all right to finish this topic.

  1. 1.You run git branch feature. What does Git write?
  2. 2.What does .git/HEAD contain while you are on the main branch?
  3. 3.You make two commits in detached HEAD state and then switch to main. What happens to them?

0 of 3 right so far. Get all three to finish this topic.