rgoussu@goussu: ~/library/system-administration/exercises
~/library/system-administration/exercises cat write-yourself-a-git-subject.md

Write Yourself a Git — subject

# The build spec for a Git-compatible tool — the zlib+SHA-1 object store on-disk layout, plumbing and porcelain commands to implement, and the two-way interop requirement with stock git.

Subjectsaved 2026-08-08source #git#internals#version-control#exercise#systems-programming#subject

Brief

Git feels like magic until you realize it is a small content-addressed key-value store with a thin command layer on top. You are going to build that store and enough of the command layer that real Git cannot tell your objects from its own. Write files with your tool, read them with stock git; commit with git, walk the history with your tool. When the two are byte-compatible, the "magic" is gone for good. Any language with zlib and SHA-1 available works.

Instructions

Reconstruct Git bottom-up: the object store first, then reading, then writing, then refs.

The object model (implement exactly)

  • An object is stored by the SHA-1 of its uncompressed serialized form: <type> <size>\0<payload>, where <type> is blob, tree, commit, or tag, <size> is the payload's byte length in ASCII decimal, followed by a NUL, then the raw payload.
  • The 40-hex SHA-1 of that whole byte string is the object id. It is stored zlib-deflated at .git/objects/<first-2-hex>/<remaining-38-hex> (loose object format).
  • A blob payload is the raw file bytes. A tree payload is a sequence of entries, each <mode> <name>\0<20-byte-binary-sha> (mode e.g. 100644 for a file, 40000 for a subtree, 100755 executable, 120000 symlink), sorted by name. A commit payload is text: tree <sha>, zero or more parent <sha> lines, author …/committer … lines with name/email/epoch/timezone, a blank line, then the message.

Commands to build

  1. The object store — init, hash-object, cat-file. init lays out a .git directory (objects, refs, HEAD). hash-object [-w] computes the id of a file (and, with -w, writes the compressed loose object). cat-file <type> <sha> reads, decompresses, parses, and prints an object. Round-trip: an object your hash-object -w wrote must read back correctly under real git cat-file.
  2. Reading trees and commits — ls-tree, log. Parse tree entries (mode, name, hash) and commit fields (tree, parents, author, message). log walks the parent chain from a given commit.
  3. Writing trees and commits — write-tree, commit-tree. write-tree serializes a directory into tree object(s) recursively and returns the top tree's id. commit-tree builds a commit object pointing at a tree, with optional parent(s) and a message. The commit you write must be one real git can checkout. (An add/commit pair over a simple index is an acceptable substitute for this milestone.)
  4. Refs and HEAD — ref read/write, checkout. Read and write refs under .git/refs/, resolve HEAD in both symbolic (ref: refs/heads/main) and detached (raw sha) forms, and checkout a commit by materializing its tree into the working directory.
  5. Interop proof. Create a history with your tool and inspect/continue it with stock git; create one with git and read it with your tool. Objects must be byte-identical.

Examples

Round-trip you must satisfy:

$ ./wyag init
$ echo 'hello' > a.txt
$ ./wyag hash-object -w a.txt        # writes .git/objects/ce/013625...
ce013625030ba8dba906f756967f9e9ca394464a
$ git cat-file -p ce013625030ba8dba906f756967f9e9ca394464a   # real git reads it
hello
$ ./wyag cat-file blob $(git hash-object a.txt)              # your tool reads git's
hello

(The hello-with-newline blob hashes to ce01362… in real Git — a handy fixed check that your header and compression are exactly right.)

Constraints

  • Match Git's on-disk format byte for byte: the <type> <size>\0payload header, zlib compression, SHA-1 addressing, the two-hex/38-hex object path, and binary tree entries.
  • Implement the store yourself — do not shell out to git for the object operations you are supposed to be building.
  • Trees are sorted by entry name; commit timestamps carry an epoch + timezone offset.

Acceptance

  • Milestone 1 (object store): a blob written by your hash-object -w reads correctly under real git cat-file -p, and your cat-file reads a blob that git hash-object produced — same 40-hex id both ways.
  • Milestone 2 (reading): ls-tree lists a real Git tree's entries (mode, name, sha) correctly, and log walks the parent chain of a real commit.
  • Milestone 3 (writing): a commit produced by your write-tree/commit-tree (or add/commit) can be git checkout-ed by stock Git with the expected working tree.
  • Milestone 4 (refs + HEAD): refs and both symbolic/detached HEAD resolve correctly; checkout of a commit materializes its tree into the working directory.
  • Milestone 5 (interop): a history built by your tool is inspected and extended by stock git, and vice versa, with byte-identical objects throughout.

Related