Andrew Mercer
on this page

09 · Internals

← Overview · Prev: Hooks · Next: Resources →

Understanding the object model makes every command less magical.

The object database

Everything git stores is a content-addressed object, named by a hash of its content (SHA-1 by default; SHA-256 in repos created with --object-format=sha256).

Object Contains
blob The bytes of one file (no name, no permissions)
tree A directory: list of (mode, name, hash) entries pointing at blobs and other trees
commit Pointer to a root tree, parent commit hash(es), author + committer (name, email, timestamp), message
tag (annotated) Pointer to an object, tagger, message, optional signature

A commit is a snapshot of the whole tree, not a diff. Diffs are computed on demand. Identical content is stored once, so unchanged files cost nothing per commit.

Objects live in .git/objects/xx/yyyy… as zlib-compressed data ("loose" objects) or inside packfiles (.git/objects/pack/) after git gc, where similar objects are delta-compressed.

Inspect objects (don't decompress by hand)

git cat-file -t <sha>            # type: commit | tree | blob | tag
git cat-file -p <sha>            # pretty-print contents
git cat-file -p HEAD^{tree}      # the root tree of HEAD
git ls-tree -r HEAD              # all files in HEAD with their blob hashes
git rev-parse HEAD               # resolve a name to a full SHA
git count-objects -vH            # loose/packed sizes
git verify-pack -v .git/objects/pack/*.idx | head

Refs: names for commits

  • Branches: .git/refs/heads/<name> — a file containing a commit SHA. A branch is just a movable pointer.
  • Remote-tracking: .git/refs/remotes/origin/<name> — where the remote's branch was at your last fetch.
  • Tags: .git/refs/tags/<name>.
  • HEAD: usually ref: refs/heads/<current-branch>; when it holds a raw SHA you are in detached HEAD.
  • Special refs: ORIG_HEAD (where HEAD was before a risky operation), MERGE_HEAD, FETCH_HEAD, REBASE_HEAD.

Relative names: HEAD~1 (first-parent grandparent chain), HEAD^2 (second parent of a merge), main@{yesterday}, @{u} (upstream), A..B (in B not A), A...B (in either, not both).

Anatomy of .git

.git/
├── HEAD            current branch pointer
├── config          repo-local config
├── index           the staging area (binary)
├── objects/        the object database
├── refs/           heads/, remotes/, tags/
├── logs/           reflogs
├── hooks/          hook scripts (samples end in .sample)
├── info/exclude    personal ignore rules
└── packed-refs     refs compacted by gc

A bare repo has this content at its top level and no working tree.

The three trees

Area Holds Commands that move data
Working tree Files on disk edit; git restore (from index/HEAD)
Index (staging area) The proposed next commit git add, git restore --staged
HEAD (last commit) The last committed snapshot git commit

git status compares HEAD↔index (staged) and index↔working tree (unstaged). git reset moves the branch pointer (--soft), then the index (--mixed), then the working tree (--hard).

Garbage collection and the reflog

Unreachable objects (e.g. commits dropped by a rebase) stay until they expire from the reflog (default ~90 days for reachable-in-reflog, 30 for unreachable) and git gc prunes them. That's why reflog recovery works.

git gc                     # pack objects, prune expired ones
git fsck                   # verify integrity, list dangling objects

Plumbing vs porcelain

Porcelain commands (commit, checkout, merge) are the friendly interface. Plumbing commands (hash-object, write-tree, commit-tree, update-ref) are the building blocks. Scripts should prefer stable, machine-readable forms: git status --porcelain, git for-each-ref, git rev-list.

Hand-building a commit shows how simple the model is:

blob=$(echo 'hello' | git hash-object -w --stdin)
git update-index --add --cacheinfo 100644,$blob,hello.txt
tree=$(git write-tree)
commit=$(echo 'first' | git commit-tree $tree)
git update-ref refs/heads/demo $commit

Reading: https://matthew-brett.github.io/curious-git/index.html · https://jwiegley.github.io/git-from-the-bottom-up