Concept · How Git works

Branches, refs and HEAD

A branch is a file holding one commit hash; HEAD says which branch you’re on.

3 mindetached HEADbranch pointerrefs/heads

A branch isn't a copy of your code, and it doesn't contain commits. It's a ref: a name that points to one commit hash.

bash
$ cat .git/refs/heads/main
9f2c1e7a4b…

Commits point to their parents, so one commit hash is enough to reach a branch's whole history. That's why branches in Git are nearly free. Creating one writes 41 bytes. (Git may also keep refs together in .git/packed-refs, or in the newer reftable format, so the file isn't always there. git rev-parse main always gives you the hash.)

Kinds of refs

RefExampleMoves?
Local branchrefs/heads/feature-xYes, forward to each new commit you make on it
Remote-tracking branchrefs/remotes/origin/mainOnly when you fetch or pull
Tagrefs/tags/v1.4.0No, it's fixed to one commit

origin/main is your repository's last known position of main on the server. It doesn't update until you fetch.

HEAD is the ref that says where you are. Normally it points to a branch, not directly to a commit:

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

When you commit, Git creates the commit with the current HEAD commit as its parent, then moves the branch HEAD points to forward. HEAD comes along with it.

Detached HEAD

If you check out a commit, tag or remote branch directly (git switch --detach v1.4.0), HEAD holds a commit hash instead of a branch name:

text
HEAD → 41ab…          (detached)
main → 9f2c…

You can look around and even commit, but no branch moves to follow those commits. When you switch away, they're left with no name pointing to them. They aren't deleted right away, and you can find them in the reflog. To keep them, create a branch where you are: git switch -c keep-this.

Moving refs

  • git commit, merge and pull move the current branch forward. See fast-forward vs merge commits.
  • git reset <commit> moves the current branch anywhere, including backwards. See the three areas.
  • git branch -f name <commit> moves another branch.
  • git push asks the remote to move its ref. A push that would move it backwards or sideways is rejected unless you force it.

Everything else in Git's history is the object store. Refs are the only part that changes.

Learn it in depth