01 — Git Object Model: Blobs, Trees, Commits & DAGs
At a glance
After completing this lesson you can:
- Explain how Git stores snapshots as a content-addressable storage engine using SHA hashes (
blob,tree,commit,tag). - Compute and verify a Git object hash from raw payload bytes using zlib and standard headers.
- Inspect and traverse Git's Directed Acyclic Graph (DAG) programmatically.
Time: ~45 minutes · Prerequisites: 03 — Shell Pipelines & Diagnostics
Reading Companion
- Book: Pro Git (2nd Edition) — Scott Chacon & Ben Straub (Free at
git-scm.com/book) - Chapter 10: Git Internals (Plumbing vs Porcelain, Git Objects, Tree Objects, Git References).
Why this matters in production
Git is not a record of line diffs; it is an immutable, content-addressable snapshot store. Developers who don't understand this get terrified by detached HEAD, accidentally force-push over colleagues' work, or believe deleted files are lost forever. Understanding the DAG turns Git into a transparent tool for history auditing, automated bisecting, and reliable CI deployments.
Deep dive
1. The Four Git Objects
Every object in .git/objects/ begins with an uncompressed header: f"{obj_type} {size_in_bytes}\x00", followed by the content, hashed with SHA-1 (or SHA-256) and compressed with zlib.
blob: Raw file contents (no filename or permissions).tree: Directory snapshot mapping mode, filename, and child SHA (blobor sub-tree).commit: Metadata pointing to a roottree, zero or moreparentcommit SHAs, author, committer, and message.tag: Annotated pointer to a specific commit.
Commit Object (SHA: a1b2c3)
├── tree: 9f8e7d ────────► Tree Object ("src/")
│ ├── 100644 blob e5d4c3 "main.py"
│ └── 040000 tree 8b7a6c "utils/"
└── parent: 010203 (Previous Commit)
Hands on
python3 phase0-foundations/02-git-and-workflow/01-git-object-model/examples/01_demo.py
python3 phase0-foundations/02-git-and-workflow/01-git-object-model/examples/02_trap.py
AI Trap & Code Review
# BAD AI-GENERATED CODE — WRONG GIT OBJECT HASHING
import hashlib
def git_hash_object(content: str) -> str:
# Fails! Git prefixes header: f"blob {len(bytes)}\0" before hashing
return hashlib.sha1(content.encode()).hexdigest()
Exercises
- Rung 1 (
hash_git_object): Compute the 40-character hex SHA-1 of a Git object givenobj_type: stranddata: bytes. Format:sha1(f"{obj_type} {len(data)}\x00".encode() + data). - Rung 2 (
parse_commit_object— Fix the AI Bug): Parse raw Git commit text into a structured dictionary with keystree,parents(list of parent commit SHAs),author,committer, andmessage. Handle multiple parent commits (merge commits) correctly. - Rung 3 (
build_tree_manifest): Given a list of entries(mode_str, name, sha_hex), produce the sorted deterministic tree representation text lines. - Rung 4 (
find_reachable_commits): Traverse a commit DAG represented asdict[str, list[str]](commit -> parents) starting from a list of tip references (e.g.["main", "feat"]) and return the set of all reachable commit IDs. - Rung 5 (
detect_dangling_commits): Given all existing commit IDs in the object database and a set of active ref pointers, return the set of dangling (unreachable) commits eligible for garbage collection (git prune).
Wrapping up
Can you explain?
- Why does renaming a file in Git not create a new
blobobject if the content is unchanged? - What actually changes on disk when you run
git branch feature-x? - What is a "detached HEAD" state at the filesystem level in
.git/HEAD?
Further Reading & References
- Pro Git Book (Chacon & Straub): Git Internals - Git Objects
- Git Plumbing and Porcelain Commands Reference
- Conventional Commits v1.0.0 Specification
- Mary Rose Cook: A Git implementation explained
Show reference solution notes
Solution & Engineering Notes — 01 Git Object Model
- Why file rename doesn't duplicate blobs: Git indexes files by the SHA-1 of their contents, not their path. If you rename
app.pytoserver.py, theblobSHA remains identical; only the containingtreeobject updates its entry name pointer. - Creating a branch: A branch in Git is literally a 41-byte text file under
.git/refs/heads/<branch_name>containing the 40-character commit hash of its tip, plus a newline. - Detached HEAD:
.git/HEADnormally containsref: refs/heads/main. In a detached HEAD state,.git/HEADcontains a raw 40-char commit SHA instead of a branch reference pointer.
Self-check
Run python3 -m unittest phase0-foundations/02-git-and-workflow/01-git-object-model/test_exercises.py -v until green, then record it here. Saved to progress/rungs.json; commit it.
Completion edits PROGRESS.md; commit it with the lesson.