A library of immutable snapshots: Git objects are like a library of sealed time capsules — each commit, file, and directory is stored as an immutable object identified by its content hash. Nothing is ever truly deleted; old versions remain accessible through the object graph.
Why it matters: Understanding Git objects explains why git checkout is instant (it just moves a pointer), why git rebase rewrites history (it creates new objects), and why .git can grow large (old objects accumulate).
The key insight: Git stores snapshots, not diffs — each commit points to a complete tree of files. Diffs are computed on demand, not stored. This makes operations like checkout and branch switching O(1) regardless of history size.
At its core, Git is a content-addressable filesystem . It stores data as objects, each identified by the SHA-1 hash of its content. This is not a version control feature — it is the fundamental storage mechanism. Version control is built on top of it.
There are four types of Git objects:
Type Purpose Contains blob File content Raw file bytes (no filename, no metadata) tree Directory listing List of (mode, name, SHA-1) entries (blobs or subtrees) commit Snapshot metadata Tree SHA-1, parent commit(s), author, committer, message, timestamp tag Annotated tag Tag name, tagger, message, target commit SHA-1, optional GPG signature
Every object is stored as a compressed file under .git/objects/Named by its SHA-1 hash. For example, an object with hash a3f2b1c... is stored at .git/objects/a3/f2b1c....
flowchart LR
A["File content<br/>or tree structure"] --> B["git hash-object -w<br/>or git write-tree"]
B --> C["Compressed object<br/>.git/objects/XX/YY..."]
C --> D["SHA-1 hash returned"]
D --> E["Referenced by<br/>tree or commit"]
E --> F["Reachable from<br/>a branch or tag"]
style A fill:#fff3e0
style C fill:#e8f5e9
style F fill:#e3f2fd A blob is the simplest Git object. It stores the raw content of a file — nothing more. It does not store the filename, permissions, or any metadata. Two files with identical content at different paths produce the same blob object.
## Create a blob from a file"s content and print its hash
$ echo " Hello, World " | git hash-object -w --stdin
ce013625030ba8dba906f756967f9e9ca394464a
## Verify the object exists
$ git cat-file -t ce013625030ba8dba906f756967f9e9ca394464a
# Print the blob's content
$ git cat-file -p ce013625030ba8dba906f756967f9e9ca394464a
The hash is computed over the concatenation of the object header and the content:
blob <content-length>\0<content>
For "Hello, World\n" (13 bytes):
The SHA-1 of this byte sequence is ce013625030ba8dba906f756967f9e9ca394464a.
The trailing newline matters. `echo "Hello, World"` produces `Hello, World\n` (13 bytes), while
`echo -n "Hello, World"` produces `Hello, World` (12 bytes). These produce different blob hashes. This is a common source of confusion when scripting Git operations.Because blob identity is based purely on content, Git automatically deduplicates identical files across commits and directories:
# Two files with identical content
$ echo " same content " > a.txt
$ echo " same content " > b.txt
# Both produce the same hash: 7f1bfd55bd05ed5e4e1e8e6f91f639f9700c4c4b
# Only one blob object is stored in .git/objects/
This is why Git is efficient at storing projects with many similar files (e.g., renamed files, copied configurations) — identical content is stored exactly once.
A tree object represents a directory listing . Each entry in a tree is a triple:
Field Description mode File type and permissions (e.g., 100644 = regular file, 100755 = executable, 040000 = directory, 120000 = symlink) name Filename or directory name SHA-1 Hash of the blob (for files) or subtree (for directories)
Consider this directory:
Git stores this as two tree objects:
100644 blob <hash-readme> README.md
040000 tree <hash-src> src
100644 blob <hash-main> main.c
100644 blob <hash-utils> utils.c
# Show the tree object for the current commit
$ git cat-file -p HEAD^{tree}
100644 blob a3f2b1c... README.md
040000 tree b7e9d4f... src
$ git cat-file -p HEAD:src
100644 blob c1d2e3f... main.c
100644 blob d4e5f6a... utils.c
Like blobs, trees are hashed with a header:
tree <content-length>\0<entries>
Each entry is encoded as <mode> <name>\0<20-byte-sha1> (binary SHA-1, not hex). The entries are sorted lexicographically by name, which is critical for canonical hashing — the same directory must always produce the same tree hash.
means `src` sorts as `src/`Which places it before `src-file` but after `src0`. This detail matters if you are manually constructing tree objects.A commit object is a snapshot of the project at a point in time , plus metadata. It contains:
Field Description tree SHA-1 of the root tree object (the directory listing) parent(s) SHA-1 of the parent commit(s). Zero parents = initial commit. Multiple parents = merge commit author Name, email, timestamp of the person who wrote the changes committer Name, email, timestamp of the person who created the commit (may differ from author during git rebase or git am) message Commit message (includes optional trailers like Co-authored-by:)
tree a3f2b1c0d1e2f3a4b5c6d7e8f9a0b1c2d3e4f5a6
parent b7e9d4f5a6b7c8d9e0f1a2b3c4d5e6f7a8b9c0d1
author Wyatt <wyatt@example.com> 1717300000 +0800
committer Wyatt <wyatt@example.com> 1717300000 +0800
Add authentication module
Implemented JWT-based authentication with refresh token rotation.
Commits form a directed acyclic graph (DAG). Each commit points to its parent(s), creating a chain of history. A merge commit has two or more parents, creating a diamond-shaped topology.
gitGraph
commit id: "A"
commit id: "B"
branch feature
checkout feature
commit id: "C"
commit id: "D"
checkout main
commit id: "E"
merge feature id: "F"
commit id: "G" In this graph:
A is the root commit (no parent).B and E are linear commits (one parent each).F is a merge commit (parents: E and D).main points to G``feature points to D.The distinction between author and committer is important in workflows where commits are rewritten:
Scenario Author Committer Normal commit Original developer Original developer git rebaseOriginal developer Person who rebased git am (apply mailbox)Patch sender Person who applied git commit --amendOriginal developer Person who amended
This separation preserves attribution while allowing history to be rewritten. git log shows both fields.
An object is reachable if there exists a path from at least one reference (branch, tag, HEAD, stash, reflog entry) to that object. Unreachable objects are candidates for garbage collection (see Packing and Garbage Collection ).
# Show all objects reachable from HEAD
$ git rev-list --objects HEAD
# Show unreachable objects
Git supports two types of tags:
A lightweight tag is a reference pointing to a commit. It is stored as a file in .git/refs/tags/ containing the commit SHA-1. No additional metadata is stored.
# Creates .git/refs/tags/v1.0 containing a commit SHA-1
An annotated tag is a full Git object (type tag) that contains:
The tag name The tagger (name, email, timestamp) A message The SHA-1 of the target commit (or other object) An optional GPG signature $ git tag -a v1.0 -m " Release version 1.0 "
object a3f2b1c0d1e2f3a4b5c6d7e8f9a0b1c2d3e4f5a6
tagger Wyatt <wyatt@example.com> 1717300000 +0800
Use lightweight Use annotated Private bookmarks Public releases Temporary pointers Signed releases (GPG) Personal workflow Semantic versioning milestones When you need metadata (date, tagger)
or message, which makes them unsuitable for audit trails. Use `git tag -a` or configure `tag.forceSignAnnotated` for GPG signing.Newly created objects are stored as individual loose objects — compressed (zlib deflate) files under .git/objects/. The filename is the first 2 characters of the SHA-1 hash, and the file contains the remaining 38 characters as a suffix:
│ └── f2b1c0d1e2f3a4b5c6d7e8f9a0b1c2d3e4f5a6 (zlib-compressed)
│ └── e9d4f5a6b7c8d9e0f1a2b3c4d5e6f7a8b9c0d1
When the number of loose objects exceeds a threshold (configurable via gc.autoDefault 6700), Git packs them into a packfile (.git/objects/pack/pack-<hash>.pack) with delta compression. See Packing and Garbage Collection for details.
The content-addressable nature of Git objects enables powerful workflows:
# Check if a file's content has been seen before
$ git hash-object file.txt
# If this hash matches an existing blob, the content is already in the object store
# Find all commits that contain a specific file content
$ git log --all --find-object=<blob-hash>
# Verify the integrity of the entire object database
# 1. Every referenced object exists
# 2. Every object's hash matches its content
# 3. The commit graph is acyclic
# 4. Tree entries point to valid objects
Git allows using a prefix of the SHA-1 hash as long as it is unambiguous within the repository:
# Full hash: a3f2b1c0d1e2f3a4b5c6d7e8f9a0b1c2d3e4f5a6
# Can be abbreviated to the shortest unambiguous prefix
$ git log a3f2b1c # Works if no other object starts with a3f2b1c
The minimum safe prefix length depends on the number of objects in the repository. For a project with N N N objects, you need approximately ⌈ log 16 N ⌉ \lceil \log_{16} N \rceil ⌈ log 16 N ⌉ hex characters. Git will warn you if a prefix is ambiguous:
warning: ambiguous argument 'a3f2': unknown revision or path not in the working tree.
Use '--' to separate paths from revisions, like this:
'git <command> [<revision>...] -- [<file>...]'
Confusing git reset and git revert. Reset moves the branch pointer; revert creates a new commit that undoes changes.
Forgetting to pull before pushing when working collaboratively, leading to merge conflicts.
Forgetting edge cases in algorithm design (e.g., empty input, single element, already sorted data).
Misunderstanding the difference between a stack (LIFO) and a queue (FIFO) in data structure applications.
Writing pseudocode that is too language-specific rather than using standard algorithmic constructs.
Confusing authentication (who you are) with authorisation (what you can do) in security contexts.
The key principles covered in this topic are linked in the sub-pages above. Focus on understanding the definitions, applying the formulas or frameworks, and evaluating strengths and limitations of each approach.
Worked examples demonstrating the application of key concepts are covered in the detailed sub-pages linked above.
References : Covers Git refs including branches and tags that point to commit objects.The Three Trees : Explains the working directory, staging area, and repository where objects are stored.Branching : Branch creation and management that relies on the commit object model.