Git: commit vs branch vs tag vs HEAD¶
Mental model¶
Git's data model is a DAG of immutable snapshots (commits) with named pointers (branches, tags, HEAD) floating on top. Everything in Git is either a snapshot or a pointer to a snapshot.
What it looks like¶
"Branches contain commits." "Tags mark releases." "HEAD is where I am." People picture branches as folders or containers that hold work.
What it really is¶
- Commit: a snapshot of the entire tree + metadata + pointer(s) to parent commit(s). Immutable. Identified by a 40-char SHA-1 hash (newer Git versions support SHA-256 / 64 chars).
- Branch: a mutable pointer to a commit. Literally a 41-byte file
in
.git/refs/heads/containing one SHA. - Tag: an immutable pointer to a commit. Lightweight tags are bare refs; annotated tags are objects that point to a commit with extra metadata (tagger, message, signature).
- HEAD: a pointer to the current branch ref (symref). In detached HEAD state, it points directly to a commit SHA instead.
Why it seems confusing¶
Branches feel like containers, but they are just movable labels. When you "delete a branch," you delete one pointer — the commits still exist (until garbage collection). The word "branch" implies a physical fork, but it is just a name taped to a node in the graph.
What actually matters¶
- Creating a branch is O(1). It writes one 41-byte file. No copies.
- A commit records the full tree state, not a diff (diffs are computed on the fly).
- Moving HEAD is how Git tracks "where you are." Checkout = move HEAD.
- The DAG structure means every commit knows its parent(s), so Git can walk history backward from any pointer.
Common mistakes¶
- Thinking deleting a branch deletes commits (it only removes the pointer; commits persist until GC prunes unreachable objects).
- Confusing HEAD (current position) with
main(a branch name). - Treating branches as heavyweight — avoid creating them "to save resources." They cost essentially nothing.
- Using lightweight tags for releases (annotated tags carry author, date, message — prefer them for releases).
Small examples¶
# See the DAG
git log --oneline --graph --all
# A branch is just a file with a SHA
cat .git/refs/heads/main
# Output: a1b2c3d4e5f6... (40-char SHA)
# HEAD usually points to a branch
cat .git/HEAD
# Output: ref: refs/heads/main
# Detached HEAD points directly to a commit
git checkout a1b2c3d
cat .git/HEAD
# Output: a1b2c3d4e5f6... (raw SHA, no ref:)
# Creating a branch = writing one pointer
git branch feature # instant, no data copied
One-line summary¶
Commits are immutable snapshots in a DAG; branches, tags, and HEAD are just named pointers to those snapshots.