C.W.K.
Stream
Lesson 02 of 04 · published

Refs, HEAD, and the Index File

~20 min · refs, head, index

Level 0Untracked Rookie
0 XP0/47 lessons0/14 achievements
0/100 XP to next level100 XP to go0% complete

Refs, HEAD, and the index file

Objects are content. Refs are names. The reason commits get human-readable names like main or v2.4.0 is the refs/ hierarchy under .git/: small files containing one hash each, plus packed-refs for older entries. The reason Git knows where you currently are is HEAD, a special pointer pointing at a ref (or, when detached, directly at a commit). Together these three pieces — refs, HEAD, and the index — are the bridge between the object database and your daily commands.

Refs live in three sub-trees. refs/heads/<name> are local branches. refs/remotes/<remote>/<branch> are remote-tracking branches — read-only-from-your-perspective shadows of what the remote had at last fetch. refs/tags/<name> are tags (lightweight tags are direct hashes; annotated tags point at tag objects). git for-each-ref walks all of them in one query, useful for housekeeping.

HEAD is the only pointer Git modifies on every operation. Its file content is usually ref: refs/heads/main — a symbolic ref to a branch. When you commit, Git updates the commit object, then updates the branch ref to point at the new commit, leaving HEAD pointing at the same branch (now at a new tip). When you switch branches, HEAD's symbolic-ref content changes. When you check out a specific commit, HEAD becomes a direct hash — that is detached HEAD.

The index is the third structure. Stored at .git/index, it is a binary file describing what the next commit's tree will look like — every path Git tracks, with mode, blob hash, timestamps, and stage information for in-progress merges. git ls-files --stage reads it. git diff compares working tree to index; git diff --staged compares index to HEAD's tree. The index exists because of git add — without it, every commit would have to be all-or-nothing.

Code

Inspect refs, HEAD, and the index·bash
# All refs at a glance:
git for-each-ref --format='%(refname:short) -> %(objectname:short)'

# What HEAD points at:
cat .git/HEAD
# Usually: ref: refs/heads/main

# What the current branch points at:
cat .git/refs/heads/main

# Or via plumbing:
git symbolic-ref HEAD                  # returns refs/heads/main
git rev-parse HEAD                     # returns the commit hash

# Look at the index (the next-commit blueprint):
git ls-files --stage | head -10
Manipulate refs at the plumbing level·bash
# Move a branch ref to a different commit (low-level):
git update-ref refs/heads/feature-x abc1234

# Read any ref's value:
git rev-parse refs/heads/feature-x

# Delete a ref:
git update-ref -d refs/heads/feature-x

# Packed refs file (older refs are stored together for efficiency):
cat .git/packed-refs | head

External links

Exercise

On a real repo: walk .git/refs/ with find .git/refs -type f. Pick one ref file, cat it, and confirm the hash matches git rev-parse refs/heads/<name>. Run cat .git/HEAD and identify whether you are on a branch or detached. Run git ls-files --stage and read three rows. Note in writing what happens to HEAD and the relevant branch ref when you make a single commit.

Progress

Progress is local-only — sign in to sync across devices.
Spotted a bug or have feedback on this page?Report an Issue

Comments 0

🔔 Reply notifications (sign in)
Sign inPlease sign in to comment.

No comments yet — be the first.