Git Detailed Course: Internals & Architecture#

Git is fundamentally a content-addressable key-value datastore with a Directed Acyclic Graph (DAG) filesystem layered on top. In this in-depth guide, we demystify the internal plumbing of Git, from low-level objects in .git/objects to interactive rebasing, automated conflict reuse (rerere), and automated bisect regression hunting.

Table of Contents#

  1. 1. Content-Addressable Storage & Hash Keys
  2. 2. The 4 Core Git Objects: Blobs, Trees, Commits, Tags
  3. 3. Anatomical Tour of the .git Directory
  4. 4. The Index (Staging) Binary Format & Hashing
  5. 5. Packfiles, Delta Compression & git gc
  6. 6. Interactive Rebase Masterclass
  7. 7. The Reflog & Dangling Object Recovery
  8. 8. Reusing Merge Resolutions (git rerere)
  9. 9. Binary Search Bug Hunting with git bisect
  10. 10. Low-Level Plumbing Command Reference

1. Content-Addressable Storage & Hash Keys#

Git does not store files by filename or directory path in its object database. Instead, it computes the cryptographic SHA-1 (or SHA-256) checksum of the object type, size, null byte, and payload content.

The hash is a 40-character hexadecimal string. Git splits the hash into:

  1. First 2 characters: directory name under .git/objects/
  2. Remaining 38 characters: filename inside that directory (compressed with zlib)
# Calculate Git object hash manually from standard input
echo "hello world" | git hash-object --stdin
# Output: 3b18e512dba79e4c8300dd08aeb37f8e728b8dad

# Store the object directly into .git/objects
echo "hello world" | git hash-object -w --stdin

2. The 4 Core Git Objects: Blobs, Trees, Commits, Tags#

Every commit in a Git repository decomposes into four fundamental immutable object types:

  1. Blob (Binary Large Object): Stores pure file content byte-for-byte. A blob contains no filename, directory path, or permissions. Identical file contents across different folders or commits share the exact same blob.
  2. Tree: Represents a directory listing. Maps filenames and permissions (e.g. 100644 for files, 040000 for subdirectories) to their corresponding child blob or tree SHA-1 hashes.
  3. Commit: Stores metadata pointing to a top-level root tree hash, parent commit hash(es), author timestamp, committer timestamp, and the commit log message.
  4. Annotated Tag: An immutable reference object pointing to a specific commit with a message, tagger identity, and optional GPG signature.
# Inspect the type of any Git object
git cat-file -t <object-sha>

# Inspect the raw contents of any Git object
git cat-file -p <object-sha>

# View tree contents formatting
git ls-tree HEAD

3. Anatomical Tour of the .git Directory#

.git/
├── HEAD            # Symbolically points to the currently checked-out ref (e.g., ref: refs/heads/main)
├── config          # Repository-specific configuration options
├── description     # Used by GitWeb or Git daemon
├── hooks/          # Client-side and server-side automation hook scripts
├── info/exclude    # Local ignore patterns not committed to shared repository
├── index           # Binary file containing the staging area cache and file stat signatures
├── objects/        # Content-addressable object store (blobs, trees, commits, packfiles)
└── refs/
    ├── heads/      # Local branch pointers (each file contains a commit SHA)
    ├── tags/       # Tag pointers
    └── remotes/    # Remote-tracking branch pointers (e.g., origin/main)

4. The Index (Staging) Binary Format & Hashing#

The .git/index file is a high-performance binary cache that acts as a bridge between your working filesystem and the commit history. It contains sorted records of every tracked file:

  1. Filesystem metadata: ctime, mtime, device, inode, file mode (permissions), UID, GID, and file size.
  2. The SHA-1 object ID of the file content currently staged.
  3. Conflict stage markers (0 = normal, 1 = ancestor, 2 = target HEAD, 3 = incoming branch).

When running git status, Git compares the file's disk mtime and size against the index cache. If they match, Git skips reading the file content entirely, allowing sub-millisecond status checks on repositories with hundreds of thousands of files.

5. Packfiles, Delta Compression & git gc#

Loose objects (individual compressed files in .git/objects/xx/) consume inodes and storage. Periodically, or during git push / git gc, Git packs loose objects into a single .pack file accompanied by a .idx index file.

Git uses sliding-window delta compression: instead of storing whole files repeatedly, it stores a baseline version and compressed binary deltas (insertions, deletions, copies) between related versions. This frequently reduces repository size by 80% to 95%.

# Run aggressive garbage collection and packfile optimization
git gc --aggressive --prune=now

# Verify object database integrity
git fsck --full

6. Interactive Rebase Masterclass#

Interactive rebase (git rebase -i) allows reshaping local commits before sharing them upstream. Git generates an instruction sheet in a temporary file and replays commits in sequence:

git rebase -i HEAD~4

Commands available in the todo list:

  1. pick (p): Keep commit as is.
  2. reword (r): Keep commit content but rewrite commit message.
  3. edit (e): Pause rebase at this commit to amend files or split into smaller commits.
  4. squash (s): Meld this commit into the previous commit and combine commit logs.
  5. fixup (f): Meld into previous commit, discarding this commit's message.
  6. drop (d): Delete this commit entirely.

7. The Reflog & Dangling Object Recovery#

The Reference Log (reflog) records every time HEAD or any local branch pointer changes position (e.g. commit, checkout, rebase, reset). Commits discarded by git reset --hard are not deleted immediately; they remain in the object store for at least 30 to 90 days.

# Inspect HEAD movement history
git reflog

# Recover from an accidental hard reset
git reset --hard HEAD@{1}

# Find unreachable commits that are no longer referenced by any branch
git fsck --lost-found

8. Reusing Merge Resolutions (git rerere)#

git rerere stands for "Reuse Recorded Resolution". When enabled, Git records conflict pre-images and your manual resolution. If you rebase frequently or maintain long-lived integration branches, Git will automatically resolve repeated conflicts identically without asking you again.

# Enable rerere globally
git config --global rerere.enabled true

# Automatically stage resolved conflicts when recognized
git config --global rerere.autoUpdate true

9. Binary Search Bug Hunting with git bisect#

When a bug is introduced across hundreds of commits, git bisect uses binary search in O(log N) steps to isolate the exact commit that introduced the regression.

# Start bisect session
git bisect start
git bisect bad                 # Current commit is broken
git bisect good v1.4.0         # v1.4.0 is known good

# Git automatically checks out middle commit. Run test:
# If broken: git bisect bad
# If working: git bisect good

# Automated bisect with a test script or command:
git bisect run npm test

# Reset back to original branch when complete
git bisect reset

10. Low-Level Plumbing Command Reference#

# Write index to a tree object
git write-tree

# Create commit object from tree
git commit-tree <tree-sha> -m "Manual commit" -p <parent-sha>

# Update branch pointer ref directly
git update-ref refs/heads/main <commit-sha>

# View raw object payload
git cat-file -p <object-sha>