Git Internals Explained: Objects, Trees, Commits, Refs and HEAD

What actually happens inside .git when you commit, branch or rebase? This lesson opens the hood: Git’s object database, how branches and HEAD work, and how everyday commands map to simple internal operations.

Advertisement

📖 What You’ll Learn: the .git folder, the four object types and content addressing, exploring objects with cat-file, ls-tree and hash-object, references, branches and HEAD, detached HEAD, how commands map to internals, and packfiles and garbage collection.


🔬 Why Learn Git Internals?

You can use Git for years without looking inside. But understanding how Git stores data makes every command make sense: why branches are instant, why rebase changes hashes, why “lost” commits can be recovered, and what HEAD really is. It also answers some of the toughest interview questions.


📁 Inside the .git Folder

ls .git
ItemWhat it stores
HEADWhich branch (or commit) you are on
configRepository settings (git config)
objects/All content: files, folders and commits, as compressed objects
refs/heads/Branches: one file per branch, containing a commit hash
refs/tags/Tags
refs/remotes/Remote-tracking branches such as origin/main
indexThe staging area (staging area)
logs/The reflog (git reflog)
hooks/Hook scripts (hooks)

🧱 The Four Object Types

Git is a content-addressable database: every object is stored under the hash of its content.

ObjectRepresentsContains
blobA file’s contentThe raw bytes (no file name!)
treeA directoryNames, modes and hashes of blobs and sub-trees
commitA snapshotA tree hash, parent commit hash(es), author, committer, message
tag (annotated)A named releaseThe tagged object, tagger, date, message (tags)
commit 8b1d4e2 ──▶ tree a1b2c3 ──▶ blob 5d6e7f  (README.md)
   │                     └──────▶ tree 9a8b7c  (tests/)
   │                                 └──────▶ blob 3c4d5e  (login.spec.ts)
   ▼
parent commit 3f2a91c

Hashes are SHA-1 by default (40 hex characters); newer Git versions can also create repositories that use SHA-256.


🔎 Explore Objects Yourself

git cat-file -t HEAD              # type of an object
git cat-file -p HEAD              # pretty-print the commit
tree a1b2c3d4e5f6...
parent 3f2a91c7d8e9...
author Asha Verma <asha.verma@example.com> 1793345000 +0530
committer Asha Verma <asha.verma@example.com> 1793345000 +0530

Add learning goals and initial test plan
git cat-file -p HEAD^{tree}       # list the root tree
git ls-tree -r HEAD               # every file in the commit with its blob hash
git cat-file -p <blob-hash>       # the file content
echo "hello" | git hash-object --stdin   # the hash Git would give this content

Because the hash depends on content, two identical files anywhere in history share one blob: Git stores each unique content only once.


🏷️ References: Branches Are Just Files

cat .git/HEAD
# ref: refs/heads/main

cat .git/refs/heads/main
# 8b1d4e2c7a9f0b1e2d3c4b5a69788f7e6d5c4b3a
  • A branch is a tiny file containing one commit hash. Creating a branch writes about 41 bytes, which is why it is instant.
  • Committing creates new objects and moves the current branch file to the new commit.
  • HEAD usually contains ref: refs/heads/<branch>. When it contains a hash directly, you are in detached HEAD (for example after checking out a tag or during bisect).
  • Packed refs may live in .git/packed-refs instead of individual files.

🔗 How the Commands Map to Internals

CommandWhat happens internally
git addCreates blobs and updates the index
git commitCreates trees and a commit object; moves the branch ref
git branch xCreates refs/heads/x pointing to the current commit
git switch xUpdates HEAD and the working directory from x’s tree
git mergeCreates a commit with two parents (or moves the ref for a fast-forward)
git rebaseCreates new commits (new parents, so new hashes) and moves the ref
git reset --hard XMoves the branch ref to X and updates index and files
git gcPacks objects into packfiles and removes unreachable ones after the reflog expires

📦 Packfiles and Storage

New objects start as individual compressed files (“loose objects”). git gc (run automatically from time to time) packs them into packfiles that store similar objects as deltas, which keeps repositories small.

git count-objects -vH       # number and size of loose and packed objects
git gc                      # pack and clean up (rarely needed manually)

📝 Assignment

  1. In git-practice, run cat .git/HEAD and cat .git/refs/heads/main (or look in packed-refs).
  2. Use git cat-file -p HEAD to follow your latest commit to its tree and to one blob’s content.
  3. Create a branch and confirm a new file appeared in .git/refs/heads/.
  4. Use git hash-object on a file and find the same hash in git ls-tree -r HEAD.
  5. Check out a tag or an old commit, then cat .git/HEAD to see detached HEAD.
  6. Run git count-objects -vH before and after git gc.

❓ FAQ

🤔 How does Git store data internally?

As objects in a content-addressable database: blobs for file content, trees for directories, commits for snapshots and tags, each identified by the hash of its content.

🤔 What is HEAD in Git?

A reference to what you have checked out. Usually it points to a branch (ref: refs/heads/main); in detached HEAD it points directly to a commit.

🤔 What is a blob in Git?

The object that stores a file’s content. It does not store the file name; names live in tree objects.

🤔 Why are Git branches so cheap?

A branch is just a small file containing a commit hash, so creating one does not copy any files.

🤔 Why does git rebase change commit hashes?

A commit’s hash includes its parent. Rebasing gives commits new parents, so Git creates new commit objects with new hashes.


🎯 Next Steps

🎬 Learn Git and GitHub step by step: watch the series on YouTube!

Subscribe to SqaToolsByDY • Practice on sqatools.in

2 thoughts on “Git Internals Explained: Objects, Trees, Commits, Refs and HEAD”

Leave a Comment