Skip to main content

Command Palette

Search for a command to run...

Git Under the Hood: Exploring the .git Directory

Updated
5 min readView as Markdown
Git Under the Hood: Exploring the .git Directory
S

Just a dev trying to write code that doesn’t crash (most of the time).

Git, the most popular distributed version control system, often seems like magic. Commands like git add, git commit, or git checkout usually work well, but few developers ask what really happens inside.

The truth is, Git isn’t mysterious or too complex. At its core, Git is a content-addressable file system that tracks changes in your code using cryptographic hashing. Instead of thinking about files and folders, Git thinks in terms of snapshots, objects, and references—all stored inside the .git directory.

In this article, we’ll look deeper into how Git works internally. You’ll learn what the .git folder contains, how Git stores data using blobs, trees, and commits, and what really happens when you run commands like git add and git commit.

Initializing a Git Repository

When we start a Git repository using:

git init

Git creates a hidden folder named .git inside the project directory. This folder is hidden by default, but you can see it by running:

ls -a

This command shows all files, including hidden ones, and you'll see the .git folder.

How Git Stores Data Internally

Git doesn't track files in the usual way we think about them. Instead, Git saves snapshots of content using objects. These objects are stored inside:

.git/objects

Git uses SHA-1 hashing to name these objects. A SHA-1 hash is a 40-character hexadecimal string.

  • The first 2 characters are used as the folder name

  • The remaining 38 characters are used as the file name

This makes storing objects efficient.

1. Blobs (File Content)

A blob represents the content of a file, not the file name.

  • If two files have the same content, Git stores only one blob. This optimization helps save space.

  • Git creates the blob by hashing the file’s content using SHA-1

  • Blobs do not store metadata like file name or folder location

So, blobs are simply raw snapshots of file data.

2. Trees (Directory Structure)

A tree represents a directory.

  • Trees store:

    • File names

    • Pointers to blobs (for files)

    • Pointers to other trees (for subdirectories)

  • Trees define how files and folders are organized

3. Commits (Snapshots of the Project)

A commit represents a complete snapshot of your project at a specific point in time.

A commit object contains:

  • A pointer to a root tree

  • The parent commit (or commits)

  • Author information

  • Timestamp

  • Commit message

When you run:

git commit

Git creates a new commit object that points to the current tree, which in turn points to blobs and subtrees.

The .git directory :

Git Internals: Why It Is a DAG, Not Just a Tree

Git stores data using objects:

  • Blob → file content

  • Tree → directory structure

  • Commit → snapshot + history

Individually, tree objects look like trees, but the entire Git object graph is a Directed Acyclic Graph (DAG).

Example Project

project/
├── README.md
└── src/
    └── main.c

Step 1: git add README.md

When you run:

git add README.md

Git does only one thing internally:

Creates a blob object

Blob A (hash: a1b2)
Content: "Hello Git"

Important :

  • The blob stores only file content

  • No filename

  • No directory structure

  • No tree is created yet

Step 2 : git add src/main.c

git add src/main.c

Git again creates only a blob:

Blob B (hash: b2c3)
Content: "int main() {}"

Still:

  • No tree objects

  • No commit

  • Just blobs in .git/objects

git add creates blobs, NOT trees.
Tree objects are created only when a commit is made.

Step 3 : git commit -m "Initial commit"

This is the first time Git creates tree objects.

What happens internally

  1. Git looks at the staging area (index)

  2. It groups blobs into directories

  3. It creates tree objects bottom-up

Created objects

Tree T1 (src/)
 └── main.c → Blob B

Tree T0 (root)
 ├── README.md → Blob A
 └── src → Tree T1

Then Git creates the commit:

Commit C1
 └── points to Tree T0

Commit points to the root tree, not directly to blobs.

At this stage, the structure looks like a tree.

Step 4 : Modify README.md and commit again

Change:

README.md → "Hello Git v2"

Run:

git add README.md
git commit -m "Update README"

What Git Does Internally (Second Commit)

New blob is created (content changed)

Blob C (new hash)
Content: "Hello Git v2"

Reuse unchanged objects

Blob B (main.c) ← reused
Tree T1 (src/) ← reused

New trees are created (only where needed)

Tree T2 (root)
 ├── README.md → Blob C
 └── src → Tree T1

New commit is created

Commit C2
 ├── points to Tree T2
 └── parent → Commit C1

Why This Becomes a DAG not a Tree

Now observe:

  • Tree T1 is referenced by both commits

  • Blob B is referenced by multiple trees

This means:

  • Nodes can have multiple parents

  • Objects are shared

  • No cycles exist

This structure is a Directed Acyclic Graph (DAG)

CommandWhat is created
git addBlob objects only
git commitTree objects + Commit object

Where DAG Appears

Now look carefully:

Same objects are shared by multiple parents
That cannot happen in a pure tree

This is a Directed Acyclic Graph (DAG):

  • Directed → pointers go one way (commit → parent)

  • Acyclic → no loops

  • Graph → nodes can have multiple parents

Final Thoughts

Understanding Git internals can really change how you think about version control. Once you see that Git doesn't track files or folders directly but instead stores unchangeable objects (like blobs, trees, and commits) connected through hashes, you'll have a much clearer picture of how Git works inside.

What looks like a simple directory tree is actually a Directed Acyclic Graph (DAG). By reusing objects, keeping them unchangeable, and linking them with hashes, Git becomes:

  • Fast (no unnecessary duplication)

  • Space-efficient (shared blobs and trees)

  • Reliable (history integrity is guaranteed)

This perspective helps explain many Git behaviors that often puzzle newcomers, like why commits are cheap, why branches are lightweight, and why merges are so powerful.