Git Internals — How Git Really Works Under the Hood
In this tutorial, you'll learn about Git Internals. We cover key concepts, practical examples, and best practices to help you understand and apply this topic effectively.
What You'll Learn
Understand Git's internal architecture — the object model (blobs, trees, commits, tags), how branches are just pointers, and how the staging area works.
Why It Matters
Understanding Git internals demystifies the commands you use daily. When you know how Git stores data, commands like reset, rebase, and merge finally make sense.
Real-World Use
Recovering "lost" commits, debugging corrupt repositories, understanding why rebase rewrites history, or explaining to your team why Git works the way it does.
Git is a Content-Addressable Filesystem
At its core, Git is a key-value store. Everything is identified by its SHA-1 hash:
Content → SHA-1 hash → stored in .git/objects/
# What's inside .git/
.git/
├── HEAD # Current branch reference
├── config # Repository config
├── objects/ # All data (commits, files, trees)
│ ├── ab/ # Object directory (first 2 chars of hash)
│ │ └── c1d2e3f4... # Object file (remaining 38 chars)
│ └── ...
├── refs/ # References (branches, tags)
│ ├── heads/ # Local branches
│ │ ├── main
│ │ └── feature
│ └── tags/ # Tags
└── index # Staging area
The Four Object Types
Git has four fundamental object types:
| Object | What It Stores | Example |
|---|---|---|
| Blob | File contents (binary) | The text inside login.py |
| Tree | Directory listing (filenames + hashes) | The src/ directory |
| Commit | Snapshot metadata (tree + parent + message) | "Add login feature" |
| Tag | A named reference to a commit | "v1.0.0" |
Blobs — File Contents
A blob stores the contents of a file. It doesn't store the filename — just the data.
# The same content always produces the same hash
echo "Hello" | git hash-object --stdin
# 8ab686e... (always the same)
# Git stores it as a blob
echo "Hello" | git hash-object -w --stdin --stdin
# Stored in .git/objects/8a/b686e...
Blob: 8ab686e...
Content: "Hello\n"
Trees — Directories
A tree maps filenames to blobs (or sub-trees):
Tree: a1b2c3d4 (root of commit)
├── login.py → blob: f1e2d3c4
├── README.md → blob: b2c3d4e5
└── src/ → tree: c3d4e5f6
├── utils.py → blob: d4e5f6a7
└── styles.css → blob: e5f6a7b8
Commits — Snapshots
A commit points to a tree, has zero or more parents, and has metadata:
Commit: a1b2c3d4
├── Tree: a1b2c3d4 (root directory snapshot)
├── Parent: e5f6a7b8 (previous commit)
├── Author: Alice <alice@example.com>
├── Date: Mon Mar 4 14:30:00 2024
└── Message: "Add login feature"
# View any object's type and content
git cat-file -t a1b2c3d4 # commit
git cat-file -p a1b2c3d4 # Pretty-print
Branches Are Pointers
A branch is just a file containing a commit hash:
# .git/refs/heads/main contains:
cat .git/refs/heads/main
# a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0
# When you commit, Git updates this file
Before commit:
main → a1b2c3
feature → d4e5f6
After commit on main:
main → g7h8i9 (new commit, parent is a1b2c3)
feature → d4e5f6 (unchanged)
HEAD — Where You Are
HEAD is a pointer to the current branch:
cat .git/HEAD
# ref: refs/heads/main
# When you checkout feature:
cat .git/HEAD
# ref: refs/heads/feature
In detached HEAD state, HEAD points directly to a commit:
git checkout a1b2c3d
cat .git/HEAD
# a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0
The Index (Staging Area)
The index is a binary file (.git/index) that contains the next commit's content:
Index (staging area)
├── login.py → blob: a1b2c3 (current state)
├── README.md → blob: d4e5f6
└── styles.css → blob: g7h8i9
When you run git add, Git updates the index. When you run git commit, Git creates a tree from the index and wraps it in a commit object.
working → git add → index → git commit → commit (with tree)
What Happens During git commit
1. Git reads the index (staged content)
2. Creates a tree object from the index
3. Creates a commit object:
- Points to the new tree
- Points to the parent commit (HEAD)
- Includes author, date, message
4. Updates the branch pointer to the new commit
What Happens During git merge
1. Git finds the merge base (common ancestor)
2. Git compares three snapshots:
- Merge base (common ancestor)
- HEAD (our branch)
- MERGE_HEAD (their branch)
3. Combines changes
4. Creates a merge commit with two parents
Exploring Git Internals
# Find all objects
find .git/objects -type f
# Show object type
git cat-file -t a1b2c3
# Show object content
git cat-file -p a1b2c3
# Create a blob manually
echo "test content" | git hash-object -w --stdin
# View tree of a commit
git ls-tree -r HEAD
# Show where HEAD points
git symbolic-ref HEAD
Why This Matters
# Recovering a "lost" commit
git reflog
# a1b2c3 HEAD@{0}: commit: Important work
# Now you know reflog is just tracking HEAD movements
# Understanding rebase
# Rebase creates new commits with new hashes
# Because Git can't change a commit (it's identified by content)
# Why commit --amend works
# It creates a NEW commit with the same parent
# Replaces the old commit
# Why force push is dangerous
# Other people's branches point to your old commits
# Those commits still exist, but HEAD moved away
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro