Handbooks / Git / Chapter 9

Git Internals: The Object Model

7 pages · ~5 min✓ Reviewed

Builds on Git Hooks & Automation.

GitPage 1

The Object Database Mental Model

1. Content-addressable store

  • Key = SHA-1 of the content (40 hex chars)
  • Value = the object, kept in .git/objects/ab/cdef...
  • ID = SHA-1 of type size\0 + content
  • Same content = same hash, so it is stored once
bash
echo 'test content' | git hash-object -w --stdin

-w writes the blob into .git/objects

output
d670460b4b4aece5915caf5c68d12f560a9fe3e4

2. Four object types

TypeHolds
blobfile bytes, no name
treedirectory listing
commitsnapshot pointer + metadata
tagannotated, named pointer

3. How they link

  1. Committree + parent commits
  2. Treeblobs + sub-trees
  3. Blobfile bytes
Objects are immutable

Change one byte and the hash changes. 'Editing' always creates a new object; the old one stays.

GitPage 2

Blobs, Trees, Commits and Tags

1. Blob and tree entry

  • Blob = file contents only, no name
  • Name and mode live in the parent tree entry
  • Entry format: mode type hash name
  • Rename = new tree entry, no new blob
ModeTypeMeans
100644blobfile
100755blobexecutable
120000blobsymlink
040000treedirectory

2. Commit and tag

  • Commit: tree hash, parent hash(es), author, committer, message
  • Root commit: no parent
  • Merge commit: two or more parents
  • Lightweight tag: just a ref to a commit
  • Annotated tag: real object with tagger, date, message
bash
git tag -a v1.0 -m 'msg'

annotated tag

3. Reuse by hash

  • Unchanged subtree keeps its hash, so it is reused
  • A new commit adds objects only for what changed
Remember

A commit points to one root tree plus its parents.

GitPage 3

Refs, HEAD and the Index

1. Branches and HEAD

  • Branch = file .git/refs/heads/<name> with one commit hash
  • Creating a branch writes 41 bytes: 40 hex + newline
  • HEAD is normally a symbolic ref: ref: refs/heads/main
  • Detached HEAD = HEAD holds a raw commit hash
  • Risk: commits made detached are lost once you switch away
  • Recover: git reflog → git branch <name> <sha>

2. The Index

  • Index = staging area, binary file .git/index
  • Lists path, mode, blob hash and stat info for the next commit
  1. git addhash the file
  2. Write blobobject store
  3. Update entryin the index

3. Status and Reflog

CommandComparesShows
git statusHEAD vs indexstaged
git statusindex vs working treeunstaged
git reflogHEAD and refs historylost commits
  • Why can a detached commit be lost? Nothing points to it, so gc can prune it; reflog still finds it
  • What does git add write? A blob in the object store, then the index entry
GitPage 4

How a Commit Is Built and Packed

1. Building a commit

  1. hash-object + update-index
  2. write-tree
  3. commit-tree
  4. update-ref
  • git add = hash-object + update-index
  • git commit = write-tree, commit-tree, update-ref
bash
sha=$(echo hi | git hash-object -w --stdin)
git update-index --add --cacheinfo 100644,$sha,a.txt
tree=$(git write-tree)
c=$(git commit-tree $tree -m msg)
git update-ref refs/heads/main $c

2. Hashes chain history

  • Hash covers tree, parents, author, dates, message
  • Change an ancestor: every descendant hash changes
  • Amend: makes a new commit
  • Children of the old commit still point to the old one

3. Packing

LoosePack
Layoutone zlib file each.pack + .idx
Made byeach writegit gc / git repack
  • Similar objects stored as deltas vs a base: repo grows far slower than its snapshots
  • Unreachable = no branch, tag, HEAD or reflog entry reaches it
  • git gc prunes unreachable objects after the 2-week expiry
GitPage 5

Plumbing Commands and Common Mistakes

1. Inspect and Write Objects

CommandDoes
git cat-file -t <sha>prints the type
git cat-file -s <sha>prints the size
git cat-file -p HEADpretty-prints content (the commit)
git hash-object <file>prints the hash only
git ls-tree -r HEADroot tree; -r recurses subtrees
git ls-files -sindex entries
git rev-parse HEADname to full hash
git show-reflists all refs
git verify-pack -v <idx>inspects a pack
  • hash-object alone writes nothing; add -w to store the blob in the object database

2. Common Mistakes

MythTruth
Branch contains commitsIt points to one tip; commits live in the object database
Deleted branch = commits goneThey survive until gc prunes them after gc.pruneExpire (2 weeks)
SHA-1 is a signatureIt identifies content; only signed commits and tags prove the author
Losing commits for good
  1. 1reflog expire--expire=now --all
  2. 2gc --prune=nowdeletes unreachable
  3. 3Goneno recovery path
Never

Run gc --prune=now or delete files in .git/objects by hand: it destroys the reflog recovery path.

GitPage 6

Git Internals Cheat Sheet

1. Objects and refs

TypeHolds
blobfile bytes
treedirectory
commitsnapshot + parents
tagannotated name
  • Stored at .git/objects/xx/yyyy
  • .git/refs/heads/*, .git/refs/tags/*
  • HEAD = symbolic ref or raw hash
  • Reflog: .git/logs

2. Inspect and write

InspectCommand
Type / size / contentcat-file -t / -s / -p <sha>
Tree of HEADls-tree -r HEAD
Indexls-files -s
Name to hashrev-parse HEAD
All refsshow-ref
  1. hash-object -wblob
  2. update-index --add --cacheinfoindex
  3. write-treetree
  4. commit-treecommit
  5. update-refbranch moves

3. Storage

  • Loose: one zlib file per object
  • git gc / repack → .pack + .idx
  • Packs use delta compression
  • Plain git gc prunes unreachable objects only past gc.pruneExpire (2 weeks); --prune=now skips it
  • Amend adds one commit, old children stay; rebase rewrites every descendant hash
Branch = pointer

One commit hash, 41 bytes. Commits outlive branch deletion until gc.

GitPage 7

Check yourself

History is A←B←C. You amend B. What happens to C?
  • A new commit B' replaces B; C still points to the old B and keeps its hash
  • The branch tip now reaches B', so C is orphaned
  • C gets a new hash only if replayed, e.g. rebased onto B'
After git branch -D feat, are its commits gone?
  • Usually not gone: reflog entries still reference them, so gc skips them
  • Lost only after the reflog entries expire and gc prunes, or if objects are deleted by hand
Two repos each run echo hi | git hash-object --stdin. Same hash?
  • Yes: the hash depends only on the header blob size plus the content
  • Without -w, nothing is written to .git/objects
In detached HEAD you commit, then run git switch main. Where is that commit?
  • Still in the object store, but only the reflog reaches it
  • Recover it: find the hash in git reflog, then git branch rescue <sha>