Concept · How Git works
Branches, refs and HEAD
A branch is a file holding one commit hash; HEAD says which branch you’re on.
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.
$ 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
| Ref | Example | Moves? |
|---|---|---|
| Local branch | refs/heads/feature-x | Yes, forward to each new commit you make on it |
| Remote-tracking branch | refs/remotes/origin/main | Only when you fetch or pull |
| Tag | refs/tags/v1.4.0 | No, 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
HEAD is the ref that says where you are. Normally it points to a branch, not directly to a commit:
$ cat .git/HEAD
ref: refs/heads/mainWhen 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:
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,mergeandpullmove 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 pushasks 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.