AlgoInsight

Free lesson · Git Mastery

Internals Overview

Hard6 min readgit

Internals Overview

A note from Nikhil Joshi, Senior Software Engineer at Google Spanner.

Why the Internals Matter

Modules 1 through 13 have covered Git as a tool - the commands you run, the operations you perform, the workflows you participate in. This module covers Git as a system - what's actually happening underneath. The internals matter because they're what makes the surface operations make sense.

When you git commit, what does Git store? When you git branch, what does Git create? When you git push, what does Git send? The answers to these questions, at the data level, demystify many of Git's behaviors that otherwise seem arbitrary. The internals are simple - much simpler than the surface commands suggest. Once you understand them, advanced operations become tractable, recovery from mistakes becomes possible, and the whole tool feels coherent.

I spent eight years at Google working on distributed systems before joining the Spanner team. The internals of distributed databases are how I think about state. When I learned Git's internals seriously - what blobs, trees, commits, and refs are, how they relate, how the storage works - it clarified everything else. Git's design is similar to other distributed-data systems; the patterns transfer.

This topic frames what the rest of Module 14 covers and why each piece matters.

What Git Is, Mechanically

At the data level, Git is two things:

A content-addressed object store. Every piece of content - file contents, directory structures, commits, tag metadata - is stored as an object identified by its SHA-1 hash. The hash is computed from the content; the same content always produces the same hash. The store is a key-value database where the keys are hashes and the values are the objects.

A set of references (refs) into that object store. Branches, tags, and HEAD are pointers to specific commits. They're separate from the object store; they tell Git "this commit is special, it's main" or "that commit is special, it's v2.4.0."

Everything else - the commands, the operations, the workflows - is layered on top of these two foundations. git commit creates new objects in the object store and updates a ref. git checkout reads from the object store and updates working tree state. git push sends objects from one repository's object store to another.

flowchart TD
    subgraph "Object store (.git/objects/)"
        B1[blob: file contents]
        B2[blob: file contents]
        T1[tree: directory structure]
        C1[commit: with metadata]
        Tag[tag: signed/annotated]
    end
    
    subgraph "Refs (.git/refs/)"
        Main[main → commit hash]
        Feature[feature/x → commit hash]
        Tag1[v1.0 → commit hash]
    end
    
    Main -.-> C1
    Feature -.-> C1
    Tag1 -.-> Tag
    C1 --> T1
    T1 --> B1
    T1 --> B2
    Tag --> C1
    
    style B1 fill:#dbeafe
    style B2 fill:#dbeafe
    style T1 fill:#dcfce7
    style C1 fill:#fef3c7
    style Tag fill:#fce7f3
    style Main fill:#e0e7ff
    style Feature fill:#e0e7ff
    style Tag1 fill:#e0e7ff
The diagram shows the two-part structure. The object store holds blobs, trees, commits, and tag objects. The refs are pointers into the object store. A commit references a tree (its snapshot); a tree references blobs (file contents) and possibly other trees (subdirectories). The refs point at commits. This relationship structure is what Git fundamentally is.

What This Module Covers

Topic 14.2 - The Git Object Database. How the object store is organized. The four object types. How hashes are computed.

Topic 14.3 - Blobs. The simplest object type. File contents as content-addressed blobs.

Topic 14.4 - Commit Objects. What a commit actually is at the data level. Author, committer, message, parent pointers, tree pointer.

Topic 14.5 - Refs. Branches, tags, and HEAD as files in .git/refs/. The reference machinery that makes the object store navigable.

Topic 14.6 - Tree Objects. The directory structure representation. How Git stores hierarchical file trees efficiently.

Topic 14.7 - Garbage Collection. How Git cleans up unreachable objects. Why orphaned commits eventually disappear.

Topic 14.8 - Reflog. The local history of where refs have been. The safety net that makes some kinds of recovery possible.

Topic 14.9 - Recovering Lost Commits. Using the internals knowledge to find and restore commits that seem lost.

Why It's Worth Learning

The internals matter for several specific situations:

Recovery from mistakes. When you accidentally delete a branch, run a destructive reset, or force-push something wrong, recovery often requires understanding what's still in the object store. Topic 14.9 covers this in depth.

Diagnostic skill. When a Git operation produces unexpected results, the surface commands sometimes don't reveal what happened. Direct inspection of the object store often does.

Advanced operations. Some operations (graft commits, custom merges, repository surgery) require operating below the surface command layer.

Performance understanding. Git repositories can have performance issues at scale. Understanding the storage format helps diagnose them.

Trust. Knowing what Git actually does makes the tool feel less mysterious. The commands become predictable rather than incantations.

I see senior engineers who don't understand the internals struggle with cases junior engineers who do understand them handle easily. The investment in understanding pays off; the skills are durable.

What's Inside .git/

The .git directory in any repository contains the entire Git state. The structure:

.git/
├── HEAD                    # current branch reference
├── config                  # repository configuration
├── description             # repository description (rarely used)
├── hooks/                  # client-side hooks
├── info/
│   └── exclude             # local-only ignore patterns
├── objects/                # the object store
│   ├── ab/                 # first two chars of hash
│   │   └── cdef...         # rest of hash, contents
│   ├── pack/               # packed objects (for efficiency)
│   └── info/
├── refs/                   # references
│   ├── heads/              # local branches
│   ├── tags/               # tags
│   └── remotes/            # remote-tracking branches
├── logs/                   # reflog
│   ├── HEAD
│   └── refs/
└── index                   # the staging area
This structure is documented and stable. The same layout works the same way in any Git repository - the directory is the database. Tools that read .git directly (alternative Git clients, repository analyzers, IDE integrations) rely on this layout.

The topics in this module cover the parts of this structure that matter for daily understanding. The full layout is documented in Git's source code and in references like Pro Git's "Git Internals" chapter.

A Practical Demonstration

A useful exercise: trace what happens to .git when you make a commit.

# Initialize an empty repo
git init internals-demo
cd internals-demo

# Count objects (should be zero)
find .git/objects -type f | wc -l

# Make a commit
echo "hello" > file.txt
git add file.txt
git commit -m "Initial commit"

# Count objects (should be three: blob, tree, commit)
find .git/objects -type f | wc -l
The exercise shows that a single commit creates three objects: a blob for the file's contents, a tree for the directory structure, and the commit itself. The three objects are linked together via the commit's tree pointer and the tree's entries pointing at the blob. The whole structure is now in the object store.

Each subsequent commit creates at minimum one new object (the commit), plus any new blobs and trees needed if files changed. The object count grows; the refs (HEAD and refs/heads/main) update to point at the new commit.

A Pattern for Investigation

When something unexpected happens, the investigation toolkit:

# What objects exist?
find .git/objects -type f

# What's a specific object?
git cat-file -p <hash>           # pretty-print
git cat-file -t <hash>           # show type (blob/tree/commit/tag)

# What's at the tip of a branch?
cat .git/refs/heads/main

# Where has HEAD been recently?
cat .git/logs/HEAD
These commands operate directly on the .git structure. They're how I investigate when something doesn't look right. The internals knowledge makes the investigation possible; without it, you're limited to high-level commands that may not reveal the underlying state.

What Comes Next

The rest of Module 14 covers each piece of the internals in detail. The progression: object store, then specific object types (blobs, commits, trees), then refs, then the cleanup mechanisms (gc, reflog), then practical applications (recovery).

What Goes Wrong

Warning: Common internals confusions:

Treating Git as opaque. Reduces your ability to recover from mistakes or diagnose issues.

Confusing the object store with refs. Different layers with different update mechanisms.

Thinking blobs store file paths. They don't; only contents. Path is in the tree.

Assuming commits store diffs. They store snapshots; the diff is computed.

Believing branches are special objects. They're just files containing hashes.

Editing .git/ directly without understanding the structure. Easy to corrupt the repository.

Try It

Exercise: Explore the structure:

  1. In any repository, run ls -la .git/. Look at what's there.
  2. Look inside .git/refs/heads/. See the branch files.
  3. Cat one of them: cat .git/refs/heads/main. Note the hash.
  4. Look at .git/objects/. Note the structure (subdirectories by first two chars of hash).
  5. Run find .git/objects -type f | wc -l to count objects.
  6. Make a commit; count again; note the increase.

Back to Git Mastery