No history yet

Git Internal Plumbing

The Object Database

At its core, Git is a content-addressable filesystem. This isn't just a clever analogy; it's the literal implementation. Every piece of data, from file contents to directory structures and commit metadata, is stored as an object in a key-value store. The key is a hash of the object's content, and the value is the compressed content itself. This design ensures data integrity and enables massive deduplication, since identical content is stored only once.

This entire collection of objects lives inside the .git/objects directory. If you look inside, you'll see a series of two-character directory names and a pack directory. Git stores objects by taking the first two characters of the hash for the directory name and the remaining 38 characters for the filename. This prevents having too many files in a single directory. Later, for efficiency, Git packs these loose objects into single, highly compressed packfiles.

The Four Object Types

Git's universe is composed of four fundamental object types: blobs, trees, commits, and tags.

Blob: Stores the raw content of a file. It has no metadata, not even a filename. A blob is pure data.

You can manually create a blob from any content using the plumbing command git hash-object. This command calculates the hash and, with the -w flag, writes the object to the database.

# Create a blob object from a string
$ echo 'hello world' | git hash-object -w --stdin

# The command outputs the object's SHA-1 hash
95d09f2b10159347eece71399a7e2e907ea3df4f

You can inspect any Git object using git cat-file. The -p flag pretty-prints the content, while -t shows the object's type.

# Check the object's type
$ git cat-file -t 95d09f2b
type: blob

# Print the object's content
$ git cat-file -p 95d09f2b
hello world

Tree: Represents a directory. It contains pointers (SHA-1 hashes) to blobs and other trees, along with file modes, types, and filenames.

Trees are how Git reconstructs a working directory's snapshot. Unlike blobs, trees are typically created from the index (staging area). We first add a file to the index with git update-index, a low-level command that manipulates the staging area directly. Then, we write the index's state to a new tree object with git write-tree.

# First, create a file and its corresponding blob
$ echo 'version 1' > file.txt
$ git hash-object -w file.txt
# Let's assume the hash is 3b18e512dba79e4c8300dd08aeb37f8e728b8dad

# Now, add it to the index (staging area)
$ git update-index --add --cacheinfo 100644 3b18e512dba79e4c8300dd08aeb37f8e728b8dad file.txt

# Write the index to a tree object
$ git write-tree
# This outputs the tree's hash
d8329fc1cc938780ffdd9f94e0d364e0ea74f579

Inspecting this tree object reveals its contents: a list of file modes, object types, hashes, and filenames.

$ git cat-file -p d8329f
100644 blob 3b18e5... 	file.txt

Commit: Points to a single tree, capturing the state of the project at a moment in time. It also contains metadata: parent commit(s), author, committer, and a commit message.

A commit is a snapshot. It links snapshots together to form a history. The git commit-tree command creates a commit object. It requires a tree hash and, usually, a parent commit hash using the -p flag.

# Create a commit from the tree we just made
# The -m flag provides the commit message
$ echo 'Initial commit' | git commit-tree d8329f

# The command outputs the commit's hash
f0678b4a2b1613b5677b63f6848259838c645479

A commit object's content includes the tree it points to, author/committer info, and the message. When a commit has a parent, it creates a historical link. This chain of commits forms the project's history.

References and Tags

Hashes are powerful but impossible for humans to remember. Git uses references (refs) as friendly pointers to commit hashes. These are simple files located in the .git/refs directory. A branch, for instance, is just a file in .git/refs/heads that contains the 40-character SHA-1 hash of the latest commit on that branch. When you switch branches, Git simply updates the special HEAD file to point to the new branch ref.

When you checkout a commit hash directly instead of a branch name, Git warns you that you are in a 'Detached HEAD' state. This means HEAD is pointing directly to a commit object's hash instead of a symbolic reference like a branch. Any new commits you make will not belong to any branch and can be lost once you switch away.

Tag: A reference that marks a specific commit, typically used for releases. There are two types: lightweight and annotated.

A lightweight tag is just a ref pointing directly to a commit, similar to a branch that doesn't move. An annotated tag, however, is a full-fledged Git object. It contains the tagger's information, a date, a tagging message, and an optional GPG signature. It points to a commit object, adding a stable, verifiable layer of metadata.

This structure of immutable, interlinked objects forms a directed acyclic graph (DAG). Understanding this model demystifies Git's behavior. Commands like merge, rebase, and cherry-pick are simply different strategies for creating new commit objects and manipulating the pointers (refs) that give them meaning.

Now, let's test your understanding of Git's internal plumbing.

Quiz Questions 1/6

What is the fundamental storage model that Git is built upon?

Quiz Questions 2/6

Which of the following is NOT one of the four fundamental object types in Git?