Git Internals

Course 3 · Ch 1
Git Internals
Objects, refs, and the .git directory demystified — what's actually happening beneath every command from Courses 1 and 2

Courses 1 and 2 covered git at the command level — what to type, what happens. This chapter goes one layer deeper: what's actually stored inside .git, and how. Understanding this isn't required to use git well day to day, but it's exactly what turns "I memorised these commands" into "I understand why these commands work," and it's the foundation everything else in this advanced course builds on — interactive rebase, reflog recovery, and real submodules all make far more sense once the object model clicks.

Git Is a Content-Addressable Object Database

Strip away every porcelain command (commit, branch, merge) and underneath, git is fundamentally a simple key-value store: content goes in, a hash comes out, and that hash is forever the key to retrieve that exact content. This is what "content-addressable" means — the address is a hash of the content itself.

📄 Blob
The raw content of a single file, nothing else — no filename, no permissions, just bytes. Two files with identical content produce the exact same blob, even in completely unrelated commits.
🌳 Tree
A directory listing — maps filenames and permissions to blob hashes (for files) or other tree hashes (for subdirectories). This is how git represents folder structure.
📦 Commit
Points to one tree (the project's complete state at that moment), one or more parent commits, plus author, message, and timestamp metadata.
🏷️ Tag (annotated)
Points to a commit, with its own message and metadata — covered practically in Course 2, Chapter 5.
commit tree (root) blob: index.html tree: styles/
Every commit points to exactly one root tree, which can point to blobs (files) and further trees (subdirectories) — the entire project state, addressed entirely by hashes

Seeing this for real

$ # Show what type of object a hash refers to
$ git cat-file -t HEAD
commit

$ # Show a commit object's raw content
$ git cat-file -p HEAD
tree 9f8e7d6c5b4a...
parent a1b2c3d4e5f6...
author Philip Osztromok <you@example.com> 1750000000 +0100
committer Philip Osztromok <you@example.com> 1750000000 +0100

Fix login validation bug
This is why git is so fast at detecting unchanged files
Because the hash is a function of content, git can tell two files are identical just by comparing hashes — no need to actually re-read and diff full file contents. This is also why renaming a file with no content changes is essentially free for git to recognise: the blob hash stays exactly the same, only the tree entry's name changes.

Refs — Human-Readable Names for Hashes

Nobody wants to type 40-character hashes constantly. A ref is simply a small file containing a hash, with a memorable name — this is the literal mechanism behind everything Course 1, Chapter 4 explained conceptually about branches being "just a movable label."

.git/
├── refs/
│   ├── heads/
│   │   ├── main ← contains just a commit hash, nothing more
│   │   └── feature/login
│   └── tags/
│       └── v1.2.0
├── HEAD ← usually contains "ref: refs/heads/main", a pointer to a pointer
└── objects/ ← every blob, tree, commit, and tag object, content-addressed
$ # See exactly what HEAD currently contains
$ cat .git/HEAD
ref: refs/heads/main

$ # And what that ref points to
$ cat .git/refs/heads/main
a1b2c3d4e5f6789...

This is the literal, file-level answer to "what is HEAD" from Course 1, Chapter 4 — it's a text file containing either a ref name (the normal case) or, occasionally, a raw commit hash directly (a "detached HEAD" state).

Where Everything Else from Courses 1 and 2 Fits

  • git commit creates a new commit object, pointing at a new tree object built from your staged blobs, then moves the current branch's ref file to point at it.
  • git branch <name> just creates a new file in .git/refs/heads/ containing the current commit's hash.
  • git merge creates a commit object with two parent hashes instead of one.
  • git rebase (Course 2, Ch2) creates brand-new commit objects with different hashes, then moves the branch ref to point at the new chain — this is the literal mechanism behind "rebase rewrites history."
  • The broken gitlink incident (Course 1 Ch9, Course 2 Ch10) — a gitlink is a special tree entry (mode 160000) that records a commit hash from a different object database entirely, rather than a blob or tree from this one. When that other database (the nested .git) disappears, the reference becomes unresolvable.
You'll almost never touch these files directly — and you shouldn't
Everything in this chapter is read-only exploration territory. Manually editing files inside .git/ is how repositories get genuinely, sometimes unrecoverably, broken — every porcelain command (commit, branch, merge, rebase) exists specifically to make these changes safely and consistently. Use git cat-file, cat .git/HEAD, and similar read-only exploration to build understanding, never to modify.

Command Reference

CommandWhat it does
git cat-file -t <hash>Shows the object type (blob, tree, commit, tag) for a given hash
git cat-file -p <hash>Pretty-prints the object's actual content
git ls-files -sShows tracked files with their mode and blob hash (used in Course 1/2's gitlink diagnosis)
cat .git/HEADShows what HEAD currently points to — a ref name, or a raw hash if detached
cat .git/refs/heads/<branch>Shows the exact commit hash a branch currently points to

Chapter 1 Quick Reference

  • Git is content-addressable — every object's hash is a function of its own content
  • Four object types: blob (file content), tree (directory listing), commit (snapshot + metadata), tag (annotated marker)
  • A ref is just a small file holding a hash with a memorable name — the literal mechanism behind branches
  • HEAD is a file, usually containing "ref: refs/heads/<branch>" — a pointer to a pointer
  • git cat-file -p / -t — read-only tools for exploring objects directly
  • Never hand-edit files inside .git/ — porcelain commands exist to do this safely
  • Next chapter: interactive rebase — squashing, reordering, and splitting commits safely