Inside Git: How It Works and the Role of the .git Folder

What Actually Happens When You Type Those Commands?
Ever typed git add . followed by git commit -m "fixed stuff" without really knowing what's happening behind the scenes? Yeah, me too. For the longest time, Git felt like magic — sometimes it worked, sometimes it yelled at me with cryptic error messages.
Then I decided to peek under the hood. Turns out, Git is surprisingly elegant once you understand its internals. Let me walk you through what I learned.
The Mysterious .git Folder
Every time you run git init, Git creates a hidden .git folder in your project. This folder is your repository. Delete it, and your entire Git history vanishes. Keep it, and Git remembers everything.
Here's what's inside:
.git/
├── HEAD
├── config
├── objects/
├── refs/
│ ├── heads/
│ └── tags/
├── index
└── hooks/
The important players here:
objects/ — This is Git's database. Every file, every commit, everything lives here.
refs/ — Stores pointers to commits (branches and tags are just pointers!)
HEAD — Points to your current branch
index — The staging area (what gets committed next)
Git Objects: The Building Blocks
Here's where it gets interesting. Git stores everything as objects, and there are only three types you need to care about:
1. Blob (Binary Large Object)
A blob is just file content. Not the filename, not the path — just the raw content. If two files have identical content, Git stores only one blob. Efficient, right?
2. Tree
A tree is like a directory snapshot. It maps filenames to blobs (and other trees for subdirectories). Think of it as Git's way of saying "at this moment, index.js pointed to blob ABC and utils/helper.js pointed to blob XYZ."
3. Commit
A commit ties everything together. It contains:
A pointer to a tree (the project snapshot)
Author and timestamp
Commit message
Pointer(s) to parent commit(s)
Here's a mental picture:
┌──────────────┐
│ COMMIT │
│ (abc123) │
│ │
│ tree: def456 │
│ parent: ... │
│ msg: "init" │
└──────┬───────┘
│
▼
┌──────────────┐
│ TREE │
│ (def456) │
│ │
│ index.js → blob1 │
│ style.css → blob2│
└──────────────┘
│
┌─────┴─────┐
▼ ▼
┌───────┐ ┌───────┐
│ BLOB │ │ BLOB │
│(blob1)│ │(blob2)│
│ │ │ │
│<code> │ │<css> │
└───────┘ └───────┘
The Magic of Hashes
Every Git object gets a SHA-1 hash based on its content. That 40-character string like e83c5163316f89bfbde7d9ab23ca2e25604af290 isn't random — it's a fingerprint.
This gives Git two superpowers:
Integrity — If even one byte changes, the hash changes. Corruption is instantly detectable.
Deduplication — Same content = same hash = stored only once.
When you see commit hashes like a1b2c3d, you're looking at a unique identifier that's mathematically derived from the commit's actual content.
What Really Happens During git add and git commit
Let's trace through what actually occurs:
git add index.js
Git reads
index.jscontentCompresses it and calculates its SHA-1 hash
Stores it as a blob in
.git/objects/Updates the index (staging area) to track this file-blob mapping
Working Directory Index (Staging) .git/objects/
│ │ │
index.js ──── git add ────► entry added ──────► blob created
(file → hash) (compressed)
git commit -m "Add homepage"
Git reads the index and creates a tree object (snapshot of staged files)
Creates a commit object pointing to that tree, plus metadata
Updates the current branch (in
refs/heads/) to point to this new commitMoves HEAD along with the branch
Index .git/objects/ refs/heads/main
│ │ │
└──► tree created ───►│ │
│ │
commit created ──┘ │
(points to tree) │
│
refs/heads/main updated ◄───────────────────┘
(now points to new commit)
Building Your Mental Model
Forget memorizing commands for a second. Here's the mental model that actually helped me:
Git is essentially a key-value store where the key is a hash and the value is compressed data. Branches are just text files containing a commit hash. When you commit, you're creating a new snapshot and moving a pointer forward.
That's it. Everything else — merging, rebasing, cherry-picking — is just manipulating these pointers and objects.
Try It Yourself
Want to see this in action? Run these in any Git repo:
# See what's in .git
ls -la .git/
# Look at a commit's raw content
git cat-file -p HEAD
# See the tree it points to
git cat-file -p HEAD^{tree}
# Check what branch HEAD points to
cat .git/HEAD
Playing with these commands turned Git from a black box into something I actually understand. Hope it does the same for you!
This article was written as part of the Web Dev Cohort 2026. If you're also trying to understand Git beyond just copy-pasting commands, I'd love to connect!




