Stage 2 · Tools
Git Internals & Advanced
Plumbing Commands
Explore Git's low-level object model and create commits programmatically.
Porcelain vs plumbing
Git has two layers of commands. Porcelain commands like git add, git commit, and git push are the user-friendly interface that developers use daily. Plumbing commands like git hash-object, git cat-file, and git update-index are the low-level building blocks that porcelain commands are composed of.
Understanding plumbing commands gives you insight into how Git stores data internally. Every Git operation ultimately boils down to creating objects, updating references, and manipulating the index. Plumbing commands expose these primitives directly, which is essential for building custom automation and understanding Git's internals.
Git object types
Git stores everything as objects in the .git/objects directory. There are four object types. Blob objects store file contents. Tree objects store directory structures, mapping filenames to blobs and other trees. Commit objects store a snapshot with metadata, pointing to a tree and parent commits. Tag objects store annotated tag information pointing to a commit.
- Blob: stores raw file content (no filename, no metadata)
- Tree: maps filenames to blobs and subdirectories
- Commit: stores a snapshot with author, date, message, and parent pointers
- Tag: stores annotated tag information pointing to a commit
Every object is identified by a SHA-1 hash of its content. If two files have identical content, they share the same blob object. This deduplication is why Git is so efficient at storing repositories.
Hash and inspect objects
git hash-object creates a blob object from a file or standard input. It writes the object to the object database and outputs the SHA-1 hash. This is the fundamental operation for storing content in Git. You can use it to manually add files to the object database without going through the index.
# Create a blob from a file
git hash-object hello.txt
# Output: a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6q7r8s9t0
# Write the object to the database
git hash-object -w hello.txt
# Inspect the object type
git cat-file -t a1b2c3d
# Output: blob
# Inspect the object content
git cat-file -p a1b2c3d
# Output: Hello, World!
# Show size
git cat-file -s a1b2c3d
# Output: 13Create a blob from a file, then inspect its type, content, and size.
git cat-file is the Swiss Army knife for inspecting Git objects. The -t flag shows the type, -p shows the content in a human-readable format, and -s shows the size in bytes. These commands are essential for understanding how Git stores data.
Working with trees
git ls-tree shows the contents of a tree object. Trees are Git's way of representing directory structures. Each tree contains entries that map filenames to blob or tree objects. You can use git ls-tree to explore the repository structure at any commit.
# Show tree at HEAD
git ls-tree HEAD
# 100644 blob a1b2c3d README.md
# 040000 tree e4f5g6h src
# Show tree recursively
git ls-tree -r HEAD
# Show tree at a specific commit
git ls-tree -r a1b2c3d
# Show only blob entries
git ls-tree -r --name-only HEADList tree contents at HEAD, recursively, or at specific commits.
Index and staging area
The index (staging area) is a binary file that stores the planned next commit. git update-index adds or updates entries in the index. git write-tree creates a tree object from the current index contents. This is how Git bridges the gap between your working directory and the commit history.
# Add a file to the index
git update-index --add --cacheinfo 100644,a1b2c3d,hello.txt
# Remove a file from the index
git update-index --remove hello.txt
# Show index contents
git ls-files --stage
# Write index to a tree object
git write-tree
# Output: e4f5g6h (the tree hash)Directly manipulate the staging area and write it as a tree object.
Create a commit from scratch
git commit-tree creates a commit object directly without going through the index or working directory. This is the lowest level of creating a commit. You provide a tree hash, optional parent commits, an author, a committer, and a commit message. This demonstrates exactly how commits are constructed internally.
# Step 1: Create a blob from file content
echo 'Hello, World!' > hello.txt
BLOB=$(git hash-object -w hello.txt)
# Step 2: Add to index and create a tree
git update-index --add --cacheinfo 100644,$BLOB,hello.txt
TREE=$(git write-tree)
# Step 3: Create a commit object
COMMIT=$(echo 'Initial commit' | git commit-tree $TREE)
# Step 4: Update the branch reference to point to the new commit
git update-ref refs/heads/main $COMMIT
# Verify the commit
git log --oneline
# Output: abc1234 Initial commitBuild a commit from scratch using only plumbing commands: create a blob, write a tree, create a commit, and update the branch reference.
Plumbing commands are ideal for build systems and CI/CD pipelines that need to create commits programmatically. They give you full control over every aspect of the commit without triggering hooks or modifying the index unexpectedly.
Mark this lesson complete to store local progress and unlock a cleaner resume path the next time you visit.