GitGit Objects (blob, tree, commit, tag)

Git Objects: Blobs, Trees, Commits, Tags

Git stores everything as one of four object types. Understanding exactly what each object contains — and seeing the actual bytes — demystifies every Git operation. This page walks through all four types with real terminal output, showing exactly what Git stores when you create a file, add it, and commit it.

Object Storage Mechanics
Every object is stored in `/.git/objects/` as a zlib-compressed file. The path is derived from the SHA-1 hash: the first two characters form the directory name, the remaining 38 characters form the filename. The hash is computed over a header plus the raw content:`<type> <size><content>`.

Object storage layout

Text
.git/objects/
├── a3/
│   └── f1c2d83e4f5a6b7c8d9e0f1a2b3c4d5e6f7a8b   ← a commit
├── 7c/
│   └── 4d8a3b5c6d7e8f9a0b1c2d3e4f5a6b7c8d9e0f   ← a tree
├── 8f/
│   └── 14e0b9bbd2c8fc72c99b35d0a5b61e07b19b3c   ← a blob
└── pack/
    └── *.pack  ← packed objects (many loose objects combined)
The blob Object

A blob stores the raw content of a file. Critically, it does NOT store the filename or permissions — those are stored in the tree that references the blob. Two files with identical content in any directory share one blob.

Create a blob manually with git hash-object

Bash
# Create a file
echo "# My Project" > README.md

# Store it in Git's object database and print its hash
git hash-object -w README.md
# 8f14e0b9bbd2c8fc72c99b35d0a5b61e07b19b3c

Inspect a blob with git cat-file

Bash
# Check type of object
git cat-file -t 8f14e0b9bbd2c8fc72c99b35d0a5b61e07b19b3c
# blob

# Check size (in bytes)
git cat-file -s 8f14e0b9bbd2c8fc72c99b35d0a5b61e07b19b3c
# 13

# Print contents
git cat-file -p 8f14e0b9bbd2c8fc72c99b35d0a5b61e07b19b3c
# # My Project
Note
The blob content is literally just `# My Project ` — 13 bytes. No filename, no permissions, no path. Pure content.
The tree Object
A tree is a directory listing. It maps filenames and permissions to the hashes of blobs (for files) and other trees (for subdirectories). When you look at `git ls-tree` or`git cat-file -p <tree>`, you see this mapping directly.

Inspect a tree object

Bash
# Get the tree hash of the current HEAD commit
TREE=$(git rev-parse HEAD^{tree})

# Print the tree contents
git cat-file -p $TREE

Tree object contents

Text
100644 blob 8f14e0b9bbd2c8fc72c99b35d0a5b61e07b19b3c    README.md
100644 blob 1a2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d7e8f9a0b    package.json
040000 tree 9a0b1c2d3e4f5a6b7c8d9e0f1a2b3c4d5e6f7a8b    src
040000 tree 2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d7e8f9a0b1c    tests

Tree entry format explained

Text
<mode> <type> <hash>                                  <name>
100644 blob   8f14e0b9bbd2c8fc72c99b35d0a5b61e07b19b3c  README.md

Mode values:
  100644 = regular file
  100755 = executable file
  120000 = symbolic link
  040000 = subdirectory (tree)
  160000 = gitlink (submodule)

Recursively list a tree

Bash
# ls-tree shows the tree nicely formatted
git ls-tree HEAD
git ls-tree -r HEAD   # recursive, shows all files

# Drill into a subtree
git cat-file -p 9a0b1c2d3e4f5a6b7c8d9e0f1a2b3c4d5e6f7a8b
# 100644 blob 2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d7e8f9a0b1c    index.ts
# 100644 blob 6e7f8a9b0c1d2e3f4a5b6c7d8e9f0a1b2c3d4e5f    auth.ts
The commit Object

A commit is a snapshot of the project at a point in time, plus metadata about when and why that snapshot was taken. It points to exactly one tree (the full project state) and zero or more parent commits (the previous state). A merge commit has two parents.

Inspect a commit object

Bash
git cat-file -p HEAD

Full commit object contents

Text
tree 7c4d8a3b5c6d7e8f9a0b1c2d3e4f5a6b7c8d9e0f
parent 9e2b0f1c2d3e4f5a6b7c8d9e0f1a2b3c4d5e6f7a
author Jane Developer <jane@example.com> 1716220991 +0000
committer Jane Developer <jane@example.com> 1716220991 +0000

Add authentication module

This commit introduces the JWT-based auth module with
refresh token support and rate limiting middleware.

Commit object for a merge (two parents)

Text
tree 4d5e6f7a8b9c0d1e2f3a4b5c6d7e8f9a0b1c2d3e
parent a3f1c2d83e4f5a6b7c8d9e0f1a2b3c4d5e6f7a8b
parent 1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0c
author Jane Developer <jane@example.com> 1716221000 +0000
committer Jane Developer <jane@example.com> 1716221000 +0000

Merge branch 'feature/auth' into main
Note
The author and committer can differ. Author is who wrote the code; committer is who applied it to the repository. They differ after cherry-picks and patch application via `git am`.
The tag Object

There are two kinds of tags in Git: lightweight tags (just a ref file pointing to a commit) and annotated tags (a full object with metadata). Annotated tags have their own hash, their own author, a message, and optionally a GPG signature.

Create and inspect an annotated tag

Bash
# Create an annotated tag
git tag -a v1.0.0 -m "First production release"

# Find the tag object hash
git rev-parse v1.0.0
# 5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0c1d2e3f4a

# Inspect the tag object
git cat-file -p v1.0.0

Annotated tag object contents

Text
object a3f1c2d83e4f5a6b7c8d9e0f1a2b3c4d5e6f7a8b
type commit
tag v1.0.0
tagger Jane Developer <jane@example.com> 1716221100 +0000

First production release

This release includes:
- Authentication module
- Product catalog API
- Shopping cart service

Dereference: tag → commit → tree

Bash
# The tag object itself:
git cat-file -t v1.0.0
# tag

# The commit the tag points to:
git cat-file -t v1.0.0^{}
# commit

# The tree of that commit:
git cat-file -t v1.0.0^{tree}
# tree
Live Terminal Session: Full Object Walkthrough

Here is a complete session creating a file, staging it, committing, and then manually inspecting every object that Git created:

Complete object inspection walkthrough

Bash
# 1. Start fresh
mkdir object-demo && cd object-demo
git init

# 2. Create a file
mkdir src
echo "export const greet = (name) => `Hello, ${name}!`;" > src/greet.js
echo "# Object Demo" > README.md

# 3. Stage and commit
git add .
git commit -m "Initial commit"

# 4. Find all objects
find .git/objects -type f | grep -v pack | sort
# .git/objects/1a/2b3c4d... ← blob (greet.js content)
# .git/objects/4e/5f6a7b... ← tree (src/ directory)
# .git/objects/7c/8d9e0f... ← blob (README.md content)
# .git/objects/8a/9b0c1d... ← tree (root directory)
# .git/objects/f1/2e3d4c... ← commit

# 5. Walk the object graph
COMMIT=$(git rev-parse HEAD)
echo "=== COMMIT OBJECT ==="
git cat-file -p $COMMIT

echo "=== ROOT TREE ==="
git cat-file -p $(git rev-parse HEAD^{tree})

echo "=== src/ TREE ==="
SRC_TREE=$(git ls-tree HEAD src | awk '{print $3}')
git cat-file -p $SRC_TREE

echo "=== greet.js BLOB ==="
BLOB=$(git ls-tree HEAD src/greet.js | awk '{print $3}')
git cat-file -p $BLOB

Complete walkthrough output

Text
=== COMMIT OBJECT ===
tree 8a9b0c1d2e3f4a5b6c7d8e9f0a1b2c3d4e5f6a7b
author Dev <dev@example.com> 1716220991 +0000
committer Dev <dev@example.com> 1716220991 +0000

Initial commit

=== ROOT TREE ===
100644 blob 7c8d9e0f1a2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d  README.md
040000 tree 4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0c1d2e3f  src

=== src/ TREE ===
100644 blob 1a2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d7e8f9a0b  greet.js

=== greet.js BLOB ===
export const greet = (name) => `Hello, ${name}!`;
How Deduplication Works

Because the hash is computed from content, identical content always produces the same hash and is stored only once — regardless of filename, location, or how many times it appears in history.

Demonstrating blob deduplication across commits

Bash
# Scenario: README.md doesn't change between commits
git log --oneline
# a3f1c2d  Add auth module   ← README unchanged
# 9e2b0f1  Fix typo in greet.js
# 8c7d6e5  Initial commit

# The README blob is the SAME object in all three commits
git ls-tree a3f1c2d -- README.md
# 100644 blob 7c8d9e0f... README.md

git ls-tree 9e2b0f1 -- README.md
# 100644 blob 7c8d9e0f... README.md  ← same hash!

git ls-tree 8c7d6e5 -- README.md
# 100644 blob 7c8d9e0f... README.md  ← same hash!

# Git stores the README content exactly once, referenced by three trees
Quick Reference: cat-file Flags

Flag

Action

Example

-t

Show object type

git cat-file -t HEAD

-s

Show object size in bytes

git cat-file -s HEAD

-p

Pretty-print object contents

git cat-file -p HEAD

blob/tree/commit/tag

Print raw content of specific type

git cat-file blob HEAD:README.md

--batch

Read many hashes from stdin

git rev-list HEAD | git cat-file --batch-check

Tip
Use `git cat-file --batch-check --batch-all-objects` to list every object in the repository with its type and size. This is the lowest-level view of everything Git knows about.