Git, CI/CD & GitOpsIII · Git ObjectsGit Objects
Blob objects — content only, no filename, deduplicated by hash
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
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>\0before 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>/<rest>.
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 <oid> reports the type. For a blob, the
answer is blob:
git cat-file -t 7d3c6c5b9c8d4f3e2a1b0c9d8e7f6a5b4c3d2e1f
# blob
git cat-file -p <oid> 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 <oid> 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 <path> can follow a file across
renames by tracking the blob OID through tree history.
Production discipline
- 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.
- Use
git hash-object -wfor low-level workflows. When you are building objects by hand (for custom tooling, for tests, for forensics),git hash-object -wis the entry point.git update-index --add --cacheinfo <mode>,<oid>,<path>then attaches the blob to a tree entry. - 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
Q1. Two files in the same repository have identical bytes but live at different paths. How many blob objects does Git store for them?
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.
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?
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.