Difficulty: Advanced
Explain Git's internal object model. What are blobs, trees, and commit objects? How does Git store data?
Git is fundamentally a content-addressable filesystem. Every piece of data is stored as an object identified by its SHA-1 hash. There are four types of objects:
1. Blob: Stores file contents (no filename, no metadata). Two files with identical content share the same blob. 2. Tree: Represents a directory. Contains pointers to blobs (files) and other trees (subdirectories), along with filenames and permissions. 3. Commit: Points to a tree (the project snapshot), parent commit(s), author, committer, timestamp, and message. 4. Tag: An annotated tag object pointing to a commit with additional metadata.
Refs are human-readable names that point to commit hashes. Branches are refs in .git/refs/heads/, tags in .git/refs/tags/, and remotes in .git/refs/remotes/. HEAD is a special ref that points to the current branch (or directly to a commit in detached HEAD state).
Git stores objects initially as loose objects (one file per object in .git/objects/). Periodically, Git packs them into pack files for efficiency, using delta compression to store only differences between similar objects.
# See the type of any object
git cat-file -t HEAD
# commit
# See the content of a commit object
git cat-file -p HEAD
# tree 4b825dc642cb6eb9a060e54bf899d4e769ad93a2
# parent a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0
# author John <john@example.com> 1705312200 +0000
# committer John <john@example.com> 1705312200 +0000
#
# feat: add user authentication
# See the tree object (directory listing)
git cat-file -p HEAD^{tree}
# 100644 blob abc123... .gitignore
# 100644 blob def456... package.json
# 040000 tree 789abc... src
# See a blob (file contents)
git cat-file -p abc123
# node_modules/
# .env
# dist/
# Get the hash of any content
echo "hello" | git hash-object --stdin
# ce013625030ba8dba906f756967f9e9ca394464a
git cat-file is the Swiss Army knife for inspecting objects. -t shows the type, -p shows the content. Every object is identified by its SHA-1 hash.
# HEAD points to the current branch
cat .git/HEAD
# ref: refs/heads/main
# Branch is just a file containing a commit hash
cat .git/refs/heads/main
# a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0
# Tags are also refs
cat .git/refs/tags/v1.0.0
# e4f5g6h7i8j9k0l1m2n3o4p5q6r7s8t9u0v1w2x3
# Remote tracking branches
cat .git/refs/remotes/origin/main
# a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0
# Symbolic refs
git symbolic-ref HEAD
# refs/heads/main
# Detached HEAD: HEAD points directly to a commit
git checkout a1b2c3d
cat .git/HEAD
# a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0
# (no "ref:" prefix = detached)
# Packed refs (optimization for many refs)
cat .git/packed-refs
# a1b2c3d... refs/tags/v1.0.0
# e4f5g6h... refs/remotes/origin/main
Branches are simply files containing commit hashes. This is why branching in Git is instant and cheap - it is just writing 40 characters to a file.
# See all objects in the repository
git count-objects -v
# count: 150 (loose objects)
# size: 600 (KB of loose objects)
# in-pack: 3200 (objects in pack files)
# packs: 1 (number of pack files)
# size-pack: 1200 (KB of pack files)
# Verify repository integrity
git fsck
# Checking object directories: 100% done.
# Checking objects: 100% done.
# Manually trigger garbage collection
git gc
# Compresses loose objects into pack files
# Removes unreachable objects older than 2 weeks
# See what a reference resolves to
git rev-parse HEAD
# a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0
git rev-parse HEAD~3
# m0n1o2p3q4r5s6t7u8v9w0x1y2z3a4b5c6d7e8f9
# Find objects by partial hash
git rev-parse --short HEAD
# a1b2c3d
# Show object size
git cat-file -s HEAD
# 264
Git's garbage collector packs loose objects and removes unreachable ones. git fsck verifies integrity. Understanding storage helps debug repository issues.
Git Objects, Blob, Tree, Commit Object, Refs, SHA-1, Pack Files