rgoussu@goussu: ~/library/system-administration
~/library/system-administration cat git-internals.md

Git internals

# The content-addressed object model under the porcelain — blobs, trees, commits, refs, and why the reflog means almost nothing is ever lost.

Conceptsaved 2026-08-08updated 2026-08-09 #git#internals#version-control#cli

Overview

Git stops being arcane the moment you see the data model: a content-addressed object store (blobs, trees, commits — each named by its hash) plus a thin layer of movable pointers (refs). Every command is a manipulation of those two things. The payoff is practical fearlessness — rebases, resets, and "I think I just lost my work" all become mechanical once you know the objects are immutable and the reflog remembers where the pointers were.

Key points

  • Four object types: blob (file contents, no name), tree (directory: names → blobs/ trees), commit (a tree + parent commit(s) + author/message), tag. Content-addressing means identical content stored once, and history is tamper-evident: change anything and every downstream hash changes.

  • A commit is a snapshot, not a diff: each commit points to a complete tree; diffs are computed on demand (packfiles delta-compress under the hood, invisibly). This is why checkout is fast and why "which files changed" is a comparison, not a stored fact.

  • Branches are 41-byte pointers: a ref is a file containing a commit hash; HEAD points to a ref (or directly to a commit — "detached"). Creating a branch copies nothing; merging/rebasing/resetting just build objects and move pointers.

    The whole model in one picture — a branch is nothing but the REF box, a file holding one commit hash:

    erDiagram
        COMMIT ||--|| TREE : "snapshots (complete, not a diff)"
        COMMIT }o--o{ COMMIT : "parent commit(s)"
        TREE ||--o{ BLOB : "names file contents"
        TREE ||--o{ TREE : "nests subdirectories"
        REF }o--|| COMMIT : "a branch is just a file with this hash"
        HEAD |o--o| REF : "points to"
        HEAD |o--o| COMMIT : "or directly (detached)"
    
  • The index (staging area) is a real thing: a snapshot-in-progress sitting between working tree and repository — git status is two diffs (worktree↔index, index↔HEAD); partial staging (add -p) is editing that middle snapshot.

    flowchart LR
        WT["Working tree"] -->|"add -p edits the middle snapshot"| IX["Index (staging area)"]
        IX --> RP["Repository (HEAD)"]
        WT -.-|"git status diff 1: worktree vs index"| IX
        IX -.-|"git status diff 2: index vs HEAD"| RP
    
  • The reflog is the safety net: every movement of HEAD and each ref is journaled locally (~90 days) — after a botched rebase or hard reset, git reflog + git reset --hard <entry> recovers the "lost" state, because commits aren't deleted, only unreferenced.

  • Rewriting history = writing new history: rebase/amend create new commits and move pointers; the old ones linger until GC. Hence the one social rule — never rewrite what's pushed and shared — and --force-with-lease as the seatbelt when you must.

  • Merge mechanics: three-way merge from the common ancestor; a conflict is merely both sides touching the same lines — the markers are the three versions laid bare; rerere remembers your resolutions.

  • To explore: cat-file/ls-tree spelunking (recreate a commit by hand once — it cures all fear), packfiles & GC, worktrees, sparse checkout & partial clone at scale, interactive-rebase fluency as history authorship.

Practice

  • Learn Git Branching (source) — visual, interactive practice moving pointers with branch/merge/rebase/reset; makes "refs are just pointers" obvious.
  • git katas (source) — targeted drills for reset modes, reflog recovery, and interactive rebase — the fearlessness reps.
  • Git objects by hand (source) — use hash-object, cat-file, write-tree, commit-tree to build a commit from raw objects; the "cure all fear" exercise.
  • Write Yourself a Git (exercise) — implement blobs, trees, commits, and refs from scratch until the object model is muscle memory.

Related