Git, CI/CD & GitOpsIII · Git ObjectsGit Objects
The three object types — blob, tree, commit, tag
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
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 <oid> 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 <oid> 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 -- <path> 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
- Always read the type tag before parsing the payload.
git cat-file -tis cheap; parsing bytes that turn out to be a different type than you expected is a bug. - 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.
- Trust the type system. A security-relevant check that
“this OID is a commit” should call
git cat-file -tand 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
Q1. Which statement about the four Git object types is correct?
Q2. Two Git objects with identical payload bytes but different type tags in their headers will have different OIDs.
Q3. Name the four Git object types and give one structural fact about each that distinguishes it from the others.
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 <oid>` and the command succeeds, so the OID is valid. The pipeline then parses the bytes returned by `git cat-file -p <oid>` 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.