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>isblob,tree,commit, ortag,<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.100644for a file,40000for a subtree,100755executable,120000symlink), sorted by name. A commit payload is text:tree <sha>, zero or moreparent <sha>lines,author …/committer …lines with name/email/epoch/timezone, a blank line, then the message.
Commands to build
- The object store —
init,hash-object,cat-file.initlays out a.gitdirectory (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 yourhash-object -wwrote must read back correctly under realgit cat-file. - Reading trees and commits —
ls-tree,log. Parse tree entries (mode, name, hash) and commit fields (tree, parents, author, message).logwalks the parent chain from a given commit. - Writing trees and commits —
write-tree,commit-tree.write-treeserializes a directory into tree object(s) recursively and returns the top tree's id.commit-treebuilds a commit object pointing at a tree, with optional parent(s) and a message. The commit you write must be one realgitcancheckout. (Anadd/commitpair over a simple index is an acceptable substitute for this milestone.) - Refs and HEAD — ref read/write,
checkout. Read and write refs under.git/refs/, resolveHEADin both symbolic (ref: refs/heads/main) and detached (raw sha) forms, andcheckouta commit by materializing its tree into the working directory. - Interop proof. Create a history with your tool and inspect/continue it with stock
git; create one withgitand 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>\0payloadheader, 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
gitfor 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 -wreads correctly under realgit cat-file -p, and yourcat-filereads a blob thatgit hash-objectproduced — same 40-hex id both ways. - Milestone 2 (reading):
ls-treelists a real Git tree's entries (mode, name, sha) correctly, andlogwalks the parent chain of a real commit. - Milestone 3 (writing): a commit produced by your
write-tree/commit-tree(oradd/commit) can begit checkout-ed by stock Git with the expected working tree. - Milestone 4 (refs + HEAD): refs and both symbolic/detached
HEADresolve correctly;checkoutof 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
- Write Yourself a Git — the exercise note this is the subject of.
- Source: Thibault Polge's Write Yourself a Git tutorial and the Git object-format documentation, adapted into this assignment.