Skip to main content
RunBook Academy

Git, CI/CD & GitOpsIII · Git ObjectsGit Objects

The three object types — blob, tree, commit, tag

Intermediate⏱ ~18 mingit

What you'll learn

  • Name the four Git object types and the payload each one wraps
  • Read the type tag and size header that prefix every object body
  • Trace the reference edges that connect blob to tree, tree to commit, and tag to commit
  • Use git cat-file -t and git cat-file -p to inspect any object by its OID
  • Recognise why the four-type model is what makes Git a content-addressed DAG, not just a file store

Prerequisites

Verified against Git 2.55.x teaching target; 2.40+ minimum · GitHub Actions continuous service; Aug 2026 documentation baseline · Argo CD v3.5.x teaching target; v3.0+ minimum · Flux v2.9.x · Sigstore Cosign v3.1.x · SLSA v1.2 · OCI Distribution Specification v1.1 · Git LFS v3.7.1 · Kubernetes (cross-course target) 1.36.x

Not yet marked complete on this device.

Git stores four kinds of objects on disk: blob, tree, commit, and tag. Each object has a type tag in its on-disk header and is named by the SHA of its body. The four types form a directed acyclic graph, and the graph is the repository. Every ref, every branch, every working-tree file is a way of pointing into that graph. Until you can read the graph by hand, refs and signing and packfiles all look like magic.

The four object types

Every Git object — regardless of type — is a single file under .git/objects/, named by the SHA of its body, prefixed by a header that identifies its type:

<type-tag> <byte-length>\0<type-specific-payload>

The four type tags are:

  • blob — the raw bytes of a file, with no filename, no path, no metadata. The payload is the file contents only.
  • tree — a directory listing. The payload is a flat list of entries, each carrying a mode, an OID, and a name. A tree can contain blobs and other trees.
  • commit — a snapshot marker. The payload names the root tree, zero or more parent commits, an author, a committer, an optional GPG signature, and a free-form message.
  • tag — a named marker. The payload names a target OID, the target type, a tag name, a tagger, and an optional GPG signature.
flowchart LR
    A["blob\nfile bytes"] --> B["tree\ndirectory listing"]
    B --> C["commit\nsnapshot + metadata"]
    C --> D["tag\nnamed pointer"]
    D -.->|"can point at\nany object type"| A
    D -.->|"can point at\nany object type"| B
    D -.->|"can point at\nany object type"| C

The reference edges always point downward in that diagram: blobs do not reference anything, trees reference blobs and other trees, commits reference exactly one tree, and tags reference any single object. Cycles are impossible because the type system forbids back-edges and the hash function makes rewrites impossible (a cycle would require an object to reference itself by OID, but the OID is computed from the body, including any references inside the body — a fixed-point calculation that has no solution other than trivial ones Git refuses to write).

Reading the type tag with git cat-file

git cat-file -t &lt;oid&gt; prints the type of any object. This is the fastest way to confirm what a SHA points at:

git cat-file -t "$OID"
# commit

git cat-file -p &lt;oid&gt; pretty-prints the payload. For a blob it prints the file bytes (interpreted as text). For a tree it prints the directory listing. For a commit it prints the metadata. For a tag it prints the wrapped payload and the signature:

# A tree, pretty-printed
git cat-file -p "$TREE_OID"
# 100644 blob 2aae6c35...   README.md
# 040000 tree 7b3f9a01...   src

# A commit, pretty-printed
git cat-file -p "$COMMIT_OID"
# tree 4d2c8e01...
# parent 9f3c1d72...
# author Ops <ops@example.com> 1730000000 +0000
# committer Ops <ops@example.com> 1730000000 +0000
#
# bump terraform module to v1.4.0

The reference edges that make a DAG

A blob is a leaf node. It references nothing. A tree is a flat list of references to blobs and sub-trees; a tree references zero or more children. A commit references exactly one root tree and zero or more parent commits. A tag references exactly one target object of any type.

flowchart TD
    C1["commit: bump module\nparent: C0"] --> T1["tree: root"]
    C0["commit: initial"] --> T0["tree: root"]
    T1 --> B1["blob: README.md"]
    T1 --> S1["tree: src/"]
    T0 --> B0["blob: README.md"]
    T0 --> S0["tree: src/"]
    S1 --> B2["blob: main.go"]
    S0 --> B3["blob: main.go (v1)"]
    G["tag: v1.0\ntarget: C0"] -.-> C0

The edges are not bidirectional. To answer “which commits contain this blob?” you have to walk the reverse edges, which Git does not store — the git rev-list family walks the graph forward from heads, and tools like git log --all -- &lt;path&gt; do a name-based filter combined with tree traversal. The graph itself is a one-way structure: children reference parents, not the other way around.

Why the four-type model survives 20 years of Git evolution

The four-type model has not changed since 2005. Packfiles, delta encoding, partial clones, sparse checkouts, SHA-256, multi-pack indices: all of these are storage and transport optimisations on top of the same on-disk object types. A repository from 2005 is still readable by modern Git because the object types and their reference rules are unchanged. Loose objects, packed objects, and the upcoming reftable format all preserve the contract that a blob wraps bytes, a tree wraps directory entries, a commit wraps metadata + tree + parents, and a tag wraps a named reference.

Production discipline

  1. Always read the type tag before parsing the payload. git cat-file -t is cheap; parsing bytes that turn out to be a different type than you expected is a bug.
  2. Walk the graph with OIDs, never with paths. A blob has no filename, a tree has no absolute path. The path is reconstructed by accumulating names as you walk from the root tree down.
  3. Trust the type system. A security-relevant check that “this OID is a commit” should call git cat-file -t and refuse to proceed if the answer is anything else. Never assume the type from the syntax of the payload.

Cross-course references

  • Docker for Production Sysadmins - Part XI (Content addressing) draws the same distinction between blobs (image layers), trees (manifests), and the equivalent of commits (config objects). The four-type discipline applies to any content-addressed store.
  • Terraform for Production Sysadmins - Part IX (State) shows a similar DAG-of-types pattern in Terraform state: resources, outputs, and modules all have explicit type tags and reference each other by hash.
  • Observability for Production Sysadmins - Part XII (Logs) covers content-addressed log indices, where the same type-tag discipline prevents an attacker from substituting a log record for a different record type.

Quiz

Knowledge check · 4 questions

  1. Q1. Which statement about the four Git object types is correct?

  2. Q2. Two Git objects with identical payload bytes but different type tags in their headers will have different OIDs.

  3. Q3. Name the four Git object types and give one structural fact about each that distinguishes it from the others.

  4. Q4. Diagnose what the type tag of an OID is and decide whether a security-relevant check can proceed.

    An automation pipeline receives an OID from an upstream system and is supposed to verify that it points at a commit before fetching its tree and walking the graph. The pipeline runs `git cat-file -e &lt;oid&gt;` and the command succeeds, so the OID is valid. The pipeline then parses the bytes returned by `git cat-file -p &lt;oid&gt;` as if they were a commit. Identify what is wrong and what the correct check is.

Passing score: 75%. Answers are checked in this browser.