Step 1 of 5 · Reading · ~3 min
Read
Object Storage
cat-file: The Object-Store Swiss Army Knife
git cat-file is the plumbing command for looking at any object by name. After this lesson your program stores a handful of real objects, finds them by an abbreviated hash the way git does, and answers -t, -s, -e and -p for them, including the one case that needs real work: pretty-printing a tree.
The four questions
git cat-file -t <name> the type: blob, tree, commit or tag
git cat-file -s <name> the payload size in bytes (the number in the header)
git cat-file -e <name> print nothing; exit status 0 if the object exists, 1 if not
git cat-file -p <name> pretty-print the payload
In this exercise -e prints the exit status (0 or 1) so it can be tested. For a blob, a commit or a tag, -p writes the payload unchanged, because those are already readable. For a tree it cannot, and that is the interesting part.
Names can be abbreviated
You almost never type 40 digits. Git accepts any prefix of at least 4 hex digits and looks for objects whose hash starts with it:
- no object matches:
fatal: Not a valid object name <name> - exactly one matches: that is the object
- two or more match: the name is ambiguous and git refuses to guess
The 4-digit floor matters. With few objects four digits are almost always enough, but with a million objects collisions are common, which is why git log --abbrev grows its output as a repository grows. Uppercase digits, fewer than four, and non-hex characters are simply not names.
Pretty-printing a tree
A tree's payload is a run of entries with nothing between them:
<mode as octal text> SP <name> NUL <20 raw hash bytes>
For example a payload holding a file and a directory looks like this, with the raw bytes in angle brackets:
100644 a.txt NUL <20 bytes> 40000 lib NUL <20 bytes>
-p turns each entry into one line, <mode> <type> <hash>, a tab, then the name:
100644 blob 3b18e512dba79e4c8300dd08aeb37f8e728b8dad a.txt
040000 tree 4b825dc642cb6eb9a060e54bf8d69288fbee4904 lib
Four details decide whether you match git:
- The mode is padded with zeros to six digits. On disk a directory is
40000; printed it is040000. - The type column is derived from the mode, not stored:
40000is a tree,160000is a commit (a submodule), everything else (100644,100755,120000) is a blob. - The hash is 20 raw bytes in the payload and 40 lowercase hex digits in the output.
- Names are raw bytes. A name with a space or an accented letter is printed as it is.
An entry cannot be found by searching for a delimiter after the name: the 20 hash bytes may contain a zero, a space or anything. You read the mode up to the space, the name up to the next zero byte, then take exactly 20 bytes.
Common mistakes
- Printing the mode as stored (
40000) instead of six digits. - Treating a prefix that matches two objects as a match for the first one.
- Printing the hash bytes as text or forgetting to convert them to hex.
- Adding a trailing newline to an empty payload, or forgetting one when a blob does not end in a newline. The exercise says exactly when.
- Resolving a prefix against all objects ever seen instead of the distinct ones: storing the same object twice is still one object.
Your exercise
The starter stores objects by their real hash and answers -t, -s and -e. You write the two missing pieces: resolve, which turns a name into one stored object or one of the two failures, and pretty_tree, which decodes a tree payload. The objects in the tests were written by real git, including a tree with a directory, an executable file, a name with a space and a name with a non-ASCII letter.
Discussion
Ask a question, share an insight, or help someone who’s stuck.
Sign in to post a comment or reply.
Loading…