Skip to content

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 is 040000.
  • The type column is derived from the mode, not stored: 40000 is a tree, 160000 is 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.

Up nextPackfile Header: Reading the BytesObject Storage

Discussion

Ask a question, share an insight, or help someone who’s stuck.

Sign in to post a comment or reply.

Loading…