Skip to main content
RunBook Academy

Git, CI/CD & GitOpsIII · Git ObjectsGit Objects

Blob objects — content only, no filename, deduplicated by hash

Intermediate⏱ ~17 mingit

What you'll learn

  • Define what a blob is and what it is not (no filename, no path, no metadata)
  • Create a blob with git hash-object -w and inspect it with git cat-file
  • Demonstrate that two files with identical bytes produce a single blob OID
  • Explain why deduplication falls out of the content-addressed model
  • Recognise that path information lives in the tree object, not the blob

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.

A blob is the simplest Git object: the raw bytes of a file plus a header that tags the object as a blob. It carries no filename, no path, no mode bits, no timestamps. None of the filesystem properties that humans associate with a file are preserved in the blob. The blob is the content, nothing more. This is what makes the content-addressed model possible: the identity of a blob is the identity of its bytes.

What a blob is and is not

A blob is:

  • The raw bytes of a file.
  • A header that says blob <byte-length>\0 before the bytes.
  • Identified by the SHA of the header + bytes.
  • Stored in the object database at a path derived from the SHA.

A blob is not:

  • A file with a name. The name lives in the tree object that references the blob.
  • A file with a path. The path is reconstructed by walking from the root tree down through sub-trees.
  • A file with mode bits. The mode (100644, 100755, 120000) lives in the tree entry, not the blob.
  • A file with permissions or ownership. Those are filesystem properties; Git does not store them.
flowchart LR
    A["README.md on disk\nname + path + mode"] --> B["blob OID\nhash of bytes"]
    B --> C["object at\n.git/objects/ab/cd1234..."]
    A2["duplicate of README.md\nat a different path"] --> B
    A3["same content in a different repo"] --> B

The deduplication is not a feature Git added later — it is a necessary consequence of “the OID is the hash of the bytes”. The same bytes must have the same OID, by definition. Two files in the same repository, or the same bytes in two different repositories, have the same OID and therefore the same identity.

Creating a blob by hand

You do not need git add or git commit to create a blob. git hash-object -w <file> reads the file, computes the OID, and writes the loose object:

# Create a file with known content
printf 'hello world\n' > hello.txt

# Compute its OID (no write)
git hash-object hello.txt
# 7d3c6c5b9c8d4f3e2a1b0c9d8e7f6a5b4c3d2e1f

# Compute its OID and write the loose object
git hash-object -w hello.txt
# 7d3c6c5b9c8d4f3e2a1b0c9d8e7f6a5b4c3d2e1f

The -w flag is the difference between asking “what OID would this file have?” and “store this file as a blob and tell me the OID”. Without -w, Git computes and prints; with -w, Git also writes the loose object under .git/objects/<2-hex>/&lt;rest&gt;.

The --stdin form reads the blob from standard input:

printf 'hello world\n' | git hash-object --stdin -w
# 7d3c6c5b9c8d4f3e2a1b0c9d8e7f6a5b4c3d2e1f

Both forms produce the same OID because the hashed input is the same: the header blob 11\0 followed by the eleven bytes hello world\n.

Inspecting a blob

git cat-file -t &lt;oid&gt; reports the type. For a blob, the answer is blob:

git cat-file -t 7d3c6c5b9c8d4f3e2a1b0c9d8e7f6a5b4c3d2e1f
# blob

git cat-file -p &lt;oid&gt; pretty-prints the payload. For a blob the payload is the file bytes, interpreted as text:

git cat-file -p 7d3c6c5b9c8d4f3e2a1b0c9d8e7f6a5b4c3d2e1f
# hello world
# (with no trailing newline indicator; the trailing newline is
# part of the bytes)

git cat-file --textconv &lt;oid&gt; is an alias for -p with textconv filters applied, useful for inspecting blobs that are configured to be transformed on checkout (for example, clean and smudge filters for line-ending normalization). For ordinary text blobs, --textconv and -p produce identical output.

Demonstrating deduplication

The cleanest demonstration is two files, one repository, one OID:

mkdir demo && cd demo
git init

echo 'shared content' > a.txt
echo 'shared content' > b.txt

OID_A=$(git hash-object -w a.txt)
OID_B=$(git hash-object -w b.txt)

echo "OID_A = $OID_A"
echo "OID_B = $OID_B"
# Both OIDs are identical.
# The loose object exists exactly once under .git/objects/.

If you inspect the object database directly:

ls .git/objects/$HEX_PREFIX/$REST
# The object exists once, referenced by neither tree yet, but
# created and addressable.

When the files are eventually committed (or added to the index via git update-index --add --cacheinfo), they will be referenced by two tree entries pointing at the same blob OID. The bytes are stored once; the names live in the tree.

Why path information does not live in the blob

If a blob carried its filename and path, renaming a file would change the blob’s bytes, which would change its OID, which would mean every commit that “renamed” a file would actually store a brand-new blob. Git’s history would inflate by the size of every file on every rename, and diff machinery that wanted to detect renames would have to look at full content rather than at OIDs.

By keeping the filename and path in the tree object, Git makes renames a tree-level operation: the blob OID is unchanged, and the rename is visible as a tree entry moving from one path to another. The diff machinery can detect renames cheaply by comparing blob OIDs across commits.

flowchart LR
    A["Commit N:\ntree points blob X at src/main.go"] --> B["Commit N+1:\ntree points blob X at cmd/main.go"]
    B --> C["Same blob OID\nonly the tree entries changed"]

This is the structural reason Git’s rename detection is fast and why git log --follow &lt;path&gt; can follow a file across renames by tracking the blob OID through tree history.

Production discipline

  1. Trust the blob OID as the unit of content identity. When you need to ask “did the file content change?” the answer is “did the blob OID change?”. Path and mode do not enter the question.
  2. Use git hash-object -w for low-level workflows. When you are building objects by hand (for custom tooling, for tests, for forensics), git hash-object -w is the entry point. git update-index --add --cacheinfo &lt;mode&gt;,&lt;oid&gt;,&lt;path&gt; then attaches the blob to a tree entry.
  3. Never try to put a filename into a blob. A blob that carries a filename is a misuse of the type system. Names belong in tree entries; if you need both, write a tree object that contains the blob with the desired name.

Cross-course references

  • Docker for Production Sysadmins - Part XI (Content addressing) draws the same distinction for image layers: each layer is a blob of bytes, identified by hash, and the manifest (the analogue of a tree) carries the layer order and filenames.
  • Terraform for Production Sysadmins - Part IX (State) shows state files stored as blobs of content-addressed JSON with no internal filename awareness — the state filename is supplied by the backend, not by the state itself.
  • Observability for Production Sysadmins - Part XII (Logs) treats log records as content-addressed blobs: each record is identified by the hash of its bytes, regardless of where in the index it lives.

Quiz

Knowledge check · 4 questions

  1. Q1. Two files in the same repository have identical bytes but live at different paths. How many blob objects does Git store for them?

  2. Q2. A blob object carries the filename and mode bits of the file it represents, because those properties are needed to reconstruct the working tree.

  3. Q3. What command writes a file's bytes into the object store as a blob and prints the OID, and what flag do you need to make the write happen?

  4. Q4. Diagnose whether a working tree change produced a new blob OID or reused an existing one, and what that means for storage and history.

    An engineer copies a 4 GiB binary from a Terraform modules directory to a new path under the same repository and commits the change. The team needs to know whether the commit doubled the repository size and whether the change is visible in `git log` as a rename.

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