Documentation Index
Fetch the complete documentation index at: https://resources.devweekends.com/llms.txt
Use this file to discover all available pages before exploring further.
Git Internals Deep Dive
If you love understanding how things actually work, this chapter is for you. If you just want to use Git and commit code, feel free to skip ahead. No judgment.This chapter reveals Git’s elegant internal design. We will explore the content-addressable object database, understand how commits form a directed acyclic graph, and demystify the staging area. This knowledge transforms you from a Git user into someone who truly understands version control.
Why Internals Matter
Understanding Git internals helps you:- Recover from disasters when
git reflogis your last hope - Debug merge conflicts by understanding the three-way merge algorithm
- Optimize repositories with pack files and garbage collection
- Ace interviews where Git internals are surprisingly common
- Never fear Git again because you know exactly what is happening
The Fundamental Truth: Git is a Content-Addressable Filesystem
At its core, Git is a simple key-value store. You give it content, it gives you back a unique key (SHA-1 hash). Think of it like a library where every book is shelved by a fingerprint of its contents rather than by title or author. If even one character changes, the fingerprint changes, and it goes on a different shelf. This design decision is what makes Git fast, reliable, and elegant.- Data integrity: If content changes, hash changes - corruption is detectable
- Deduplication: Same content stored once, referenced everywhere
- Fast comparisons: Compare 40-character hashes instead of file contents
The Four Git Objects
Git stores everything as one of four object types. Understanding these is understanding Git.1. Blobs - The Content
A blob (binary large object) stores file content. Just content - no filename, no permissions, no metadata.2. Trees - The Directories
A tree is like a directory listing. It contains:- Pointers to blobs (files)
- Pointers to other trees (subdirectories)
- Mode (permissions), type, hash, and filename for each entry
100644- Regular file100755- Executable file040000- Directory (tree)120000- Symbolic link160000- Gitlink (submodule)
3. Commits - The Snapshots
A commit is a snapshot in time. It contains:- Pointer to a tree (the project state)
- Pointer to parent commit(s)
- Author (who wrote the code)
- Committer (who made the commit)
- Commit message
- Timestamp
- Author: Original code writer
- Committer: Person who applied/committed (different in cherry-pick, rebase, patches)
4. Tags - The Bookmarks
Annotated tags are objects containing:- Pointer to a commit
- Tag name
- Tagger information
- Tag message
The Object Database Structure
All objects live in.git/objects/, organized by hash:
How SHA-1 Hashing Works
Git computes hashes by prepending a header to content:git init --object-format=sha256). For now, practical attacks against Git specifically remain theoretical.
The Index (Staging Area) Demystified
The index (.git/index) is a binary file that tracks:
- Which files are staged
- Their blob hashes
- Timestamps, permissions, sizes
- Stage 0: Normal, no conflict
- Stage 1: Common ancestor version
- Stage 2: Our version (HEAD)
- Stage 3: Their version (merging branch)
Why the Index is Brilliant
The index is one of Git’s most under-appreciated design decisions. Most version control systems go straight from “changed files” to “committed.” Git’s staging area gives you an editing step in between.- Speed: Comparing file modification times and sizes against the index is much faster than hashing every file’s contents on each
git status - Granularity:
git add -plets you stage individual hunks within a file — commit the bugfix on line 42 but not the debug logging you added on line 100 - Atomic commits: Build up your commit piece by piece before finalizing. Changed 10 files but only 3 are related? Stage those 3, commit, then handle the rest separately.
- Three-way merge: During conflicts, the index stores all three versions (ancestor, ours, theirs), giving merge tools everything they need for resolution
Refs - The Human-Readable Pointers
Refs are files containing SHA-1 hashes. They make Git usable.The Reflog - Your Safety Net
Every time HEAD moves, Git logs it in the reflog:Packfiles - Compression and Efficiency
As repositories grow, storing every object as a separate compressed file becomes wasteful — the Linux kernel repo has millions of objects, and individual file I/O at that scale is slow. Packfiles solve this by bundling objects together with delta compression.Delta Compression
Git stores similar objects as deltas (differences):Pack Structure
When Packing Happens
git gc- Manual garbage collectiongit push- Objects packed for transfergit fetch- Receive packfiles- Automatically when loose objects exceed threshold (~7000)
The Directed Acyclic Graph (DAG)
Commits form a DAG - a graph with no cycles where edges point backwards (to parents).Why DAG Matters
- Reachability: “Is commit X an ancestor of Y?” is fast
- Common ancestor: Three-way merge needs merge base
- History traversal:
git logwalks the DAG - Garbage collection: Unreachable commits are pruned
How Merge Actually Works
Understanding the three-way merge algorithm:Setup
The Algorithm
For each file, compare B, O, T:| Base | Ours | Theirs | Result |
|---|---|---|---|
| A | A | A | A (unchanged) |
| A | A | B | B (they changed) |
| A | B | A | B (we changed) |
| A | B | B | B (both same change) |
| A | B | C | CONFLICT |
| - | A | - | A (we added) |
| - | - | A | A (they added) |
| A | - | - | DELETE (both deleted) |
| A | - | A | DELETE (we deleted) |
| A | A | - | DELETE (they deleted) |
| - | A | B | CONFLICT (both added different) |
| A | B | - | CONFLICT (we changed, they deleted) |
Inside a Merge Conflict
Interview Deep Dive Questions
What is the Git object model?
What is the Git object model?
What is the difference between git merge and git rebase?
What is the difference between git merge and git rebase?
How does Git detect file renames?
How does Git detect file renames?
-M), Git considers it a rename. This is why renaming and modifying in the same commit can confuse detection.What happens during git checkout?
What happens during git checkout?
How does git gc work?
How does git gc work?
Explain detached HEAD state
Explain detached HEAD state
Exploring Internals Yourself
Key Takeaways
- Git is a content-addressable filesystem - content hashes are keys
- Four object types: blobs, trees, commits, annotated tags
- SHA-1 hashes ensure integrity - any change = different hash
- The index is the staging area - binary file tracking staged state
- Refs make hashes human-readable - branches and tags are just files
- Packfiles optimize storage - delta compression for similar objects
- History is a DAG - commits point to parents, forming a graph
- Three-way merge uses common ancestor - compares base, ours, theirs
Interview Deep-Dive
Explain how Git's content-addressable storage enables deduplication, and give me a practical example of where this saves significant disk space in a real repository.
Explain how Git's content-addressable storage enables deduplication, and give me a practical example of where this saves significant disk space in a real repository.
- Git hashes every piece of content (blobs, trees, commits) with SHA-1. The hash is derived purely from the content itself, which means identical content always produces the same hash, regardless of filename, location, or when it was created.
- At the blob level, if two files have identical content, Git stores a single blob and both tree entries point to the same hash. Rename a file? Same blob, different tree entry. Copy a file to a new directory? Same blob. This is automatic and invisible.
- Practical example: a monorepo with 50 microservices that all share a common
LICENSEfile,.editorconfig, and aMakefiletemplate. Without deduplication, you would store 50 copies of each file across the commit history. With Git, there is exactly one blob per unique file version, regardless of how many directories reference it. - At the packfile level, Git goes further with delta compression. When you run
git gc, Git identifies similar objects and stores only the differences. If you have a 1MB configuration file and make a 10-byte change, the packfile stores the original plus a 10-byte delta, not two 1MB copies. For large repositories with many similar files (like generated code, documentation with minor variations), this can reduce the on-disk size by 80-90%. - The Linux kernel repository demonstrates this beautifully. It has 1M+ commits and millions of file versions, but the repository is only ~4GB because of aggressive deduplication and delta compression. Without these techniques, it would be hundreds of gigabytes.
A colleague says 'Git stores diffs between files.' Correct this misconception and explain why Git's actual approach is superior for the operations Git prioritizes.
A colleague says 'Git stores diffs between files.' Correct this misconception and explain why Git's actual approach is superior for the operations Git prioritizes.
- Git stores complete snapshots, not diffs. Every commit points to a tree object that represents the full state of every file at that point in time. If you have 100 files and change 1, the commit’s tree references the same 99 unchanged blobs (by hash, so no duplication) and one new blob for the changed file.
- This is superior to diff-based systems (like SVN) for several key operations. First, branching and checkout: to check out any commit, Git just reads its tree and blobs. An SVN checkout of an old revision requires replaying every delta from the beginning to that point. Second, diffing between arbitrary commits: Git compares two trees directly. SVN must compute and combine all deltas between two revisions. Third, merge: Git’s three-way merge compares three complete snapshots (base, ours, theirs). This is a parallel operation on complete file states, not a serial replay of patches.
- The concern about disk space is addressed by packfiles. When you run
git gc, Git compresses objects using delta encoding — but this is a storage optimization, not the core data model. The delta compression in packfiles is chosen for storage efficiency (often based on similar content, not chronological order), which can actually be more space-efficient than chronological diffs. - The key insight: Git optimizes for speed of operations (checkout, branch, merge, diff) at the cost of naive storage size, then recovers the storage cost through smart compression. This is the right trade-off because operations happen millions of times more often than storage.
--window parameter). A file might be delta’d against a completely different file that happens to have similar content, or against a version from a different branch that is closer in content than the chronological predecessor. This window-based approach often produces better compression than chronological diffs because the most similar content is not always the previous version — it might be a file from a parallel branch or a similar file in a different directory.You are investigating why a Git repository has grown to 5GB despite only having 200MB of source code. Walk me through how you would diagnose this and reduce the size.
You are investigating why a Git repository has grown to 5GB despite only having 200MB of source code. Walk me through how you would diagnose this and reduce the size.
- The most common cause is large binary files committed to the repository. Even if they were deleted in a later commit, they still exist in Git history. Videos, database dumps, compiled binaries, and node_modules accidentally committed are the usual culprits.
- Diagnosis: I would run
git rev-list --objects --all | git cat-file --batch-check='%(objecttype) %(objectname) %(objectsize) %(rest)' | sort -rnk3 | head -20to find the 20 largest objects in the repository. This reveals the specific files consuming space. I would also rungit verify-pack -v .git/objects/pack/*.idx | sort -rnk3 | head -20to check packfile contents. - If the culprits are historical large files that are no longer needed, I would use
git filter-repo --strip-blobs-bigger-than 10Mto remove all objects larger than 10MB from the entire history. Alternatively,git filter-repo --path data-dump.sql --invert-pathsremoves a specific file. This rewrites history, so all team members must re-clone. - For ongoing prevention: add large file patterns to
.gitignore, and implement a pre-commit hook that rejects files above a size threshold. For large files that genuinely need versioning (design assets, test fixtures), use Git LFS, which stores large files in a separate server and keeps only lightweight pointers in the repository. - After cleanup,
git gc --aggressive --prune=nowrepacks the repository. Then force-push all branches and tags. On the hosting platform (GitHub, GitLab), you may also need to trigger a garbage collection on the server side, as some platforms cache objects independently.
git pull the rewritten history into an existing clone will cause massive conflicts because every commit hash changed. A fresh clone is always simpler and safer.Ready to master branching strategies? Next up: Git Branching where we will explore GitFlow, trunk-based development, and merge vs rebase.