Skip to content

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.