Git: Plumbing — Objects and Refs
Git's user-facing commands (add, commit, branch, etc.) are called "porcelain." Underneath them is a layer of lower-level "plumbing" commands that operate directly on Git's object database and ref system. Understanding plumbing reveals how Git actually works — every porcelain command is ultimately built from these primitives.
Commands Covered
git cat-file · git hash-object · git ls-tree · git read-tree · git write-tree · git commit-tree · git rev-parse · git rev-list · git for-each-ref · git show-ref · git update-ref · git symbolic-ref · git refs
The Object Model
Git stores four types of objects, each identified by a SHA-1 (or SHA-256) hash of their content:
| Object type | What it stores | Created by |
|---|---|---|
blob | File content (no filename) | git hash-object, git add |
tree | Directory: list of blobs and subtrees with names/modes | git write-tree |
commit | Snapshot: points to a tree, parent commit(s), author, message | git commit-tree, git commit |
tag | Annotated tag: points to a commit with tagger metadata | git tag -a |
git hash-object
Computes the SHA hash of content — the same hash Git would use to store it.
# Compute hash of a file (don't store it) $ git hash-object myfile.txt # Compute hash and store as a blob object in the database $ git hash-object -w myfile.txt # Returns the SHA: e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 # Hash arbitrary content from stdin $ echo "hello" | git hash-object --stdin $ echo "hello" | git hash-object -w --stdin # store it # Hash of an empty file (the famous empty blob SHA): # e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
git cat-file
Provides content or details of repository objects. The primary tool for inspecting Git's internal storage.
# Show type of an object
$ git cat-file -t abc1234
# commit
$ git cat-file -t HEAD
# commit
# Show size of an object
$ git cat-file -s abc1234
# Show content of an object
$ git cat-file -p abc1234 # pretty-print (auto-formats by type)
$ git cat-file commit abc1234 # raw content
# Pretty-print a commit:
$ git cat-file -p HEAD
# tree 4b825dc642cb6eb9a060e54bf8d69288fbee4904
# parent def5678abc9012...
# author Jason Smith <jason@dispelled.ca> 1699999999 -0500
# committer Jason Smith <jason@dispelled.ca> 1699999999 -0500
#
# Fix login bug
# Pretty-print a tree:
$ git cat-file -p HEAD^{tree}
# 100644 blob e69de29b README.md
# 100644 blob 3b18e512 src/main.c
# 040000 tree 5f9f2593 tests/
# Pretty-print a blob:
$ git cat-file -p HEAD:src/main.c
# Batch mode — process multiple objects from stdin
$ echo "HEAD\nHEAD~1" | git cat-file --batch
$ echo "HEAD" | git cat-file --batch-check # just type and size
git ls-tree
Lists the contents of a tree object — like ls for a Git tree.
# List contents of the tree at HEAD $ git ls-tree HEAD # Output format: # mode type SHA name # 100644 blob e69de29b README.md # 040000 tree 5f9f2593 src/ # Mode values: # 100644 = regular file # 100755 = executable file # 120000 = symbolic link # 040000 = directory (tree) # 160000 = submodule (gitlink) # Recursive listing (expand subtrees) $ git ls-tree -r HEAD $ git ls-tree -r HEAD --name-only # List contents of a subdirectory at HEAD $ git ls-tree HEAD src/ # List contents at a specific commit $ git ls-tree abc1234 # List with long format (includes object sizes) $ git ls-tree -l HEAD
git write-tree and git read-tree
# Write the current index (staging area) as a tree object $ git write-tree # Returns the SHA of the tree object created # Read a tree into the index (the reverse) $ git read-tree abc1234 # replace index with this tree $ git read-tree --reset abc1234 # reset index to this tree $ git read-tree -m HEAD abc1234 # three-way merge into index
git commit-tree
Creates a commit object from a tree object. The low-level version of git commit.
# Create a commit from a tree, with a parent $ git commit-tree TREE_SHA -p PARENT_SHA -m "Commit message" # Returns: new commit SHA # Create an initial commit (no parent): $ git commit-tree TREE_SHA -m "Initial commit" # Example: manually create a commit $ git write-tree # 4b825dc642cb6eb9a060e54bf8d69288fbee4904 $ git commit-tree 4b825dc -p HEAD -m "Manual commit" # abc1234def5678... $ git reset --hard abc1234def5678 # move HEAD to the new commit
git rev-parse
Translates names, refs, and expressions into raw SHA hashes. The backbone of scripting with Git.
# Get SHA of HEAD
$ git rev-parse HEAD
# Get SHA of a branch
$ git rev-parse main
$ git rev-parse origin/main
# Get SHA of a tag
$ git rev-parse v1.0.0
# Relative references
$ git rev-parse HEAD~3 # 3 commits before HEAD
$ git rev-parse HEAD^ # parent of HEAD (same as HEAD~1)
$ git rev-parse HEAD^^ # grandparent
# Get tree SHA of a commit
$ git rev-parse HEAD^{tree}
# Get blob SHA of a file
$ git rev-parse HEAD:src/main.c
# Abbreviate a SHA
$ git rev-parse --short HEAD
$ git rev-parse --short=8 HEAD
# Show top-level directory of the repo
$ git rev-parse --show-toplevel
# Show .git directory path
$ git rev-parse --git-dir
# Check if we're inside a repo (useful in scripts)
$ git rev-parse --is-inside-work-tree
$ git rev-parse --git-dir >/dev/null 2>&1 && echo "in repo" || echo "not a repo"
# Resolve ~ and symbolic refs
$ git rev-parse --symbolic-full-name HEAD
# refs/heads/main
git rev-list
Lists commit objects in reverse chronological order. The engine behind git log.
# List all commits reachable from HEAD
$ git rev-list HEAD
# List commits in a range
$ git rev-list main..feature # commits on feature not in main
# Count commits
$ git rev-list --count HEAD
$ git rev-list --count main..feature
# List commits touching a specific file
$ git rev-list HEAD -- src/auth.c
# Combine with cat-file for scripting
$ git rev-list HEAD | while read sha; do
echo "$(git log -1 --format='%s' $sha)"
done
git for-each-ref
Outputs information on each ref — the low-level version of branch and tag listing. Highly configurable for scripting.
# List all refs
$ git for-each-ref
# List branches only
$ git for-each-ref refs/heads/
# List tags only
$ git for-each-ref refs/tags/
# Custom format
$ git for-each-ref --format='%(refname:short) %(objectname:short)' refs/heads/
# main abc1234
# feature-login def5678
# Sort by committer date (newest last)
$ git for-each-ref --sort=committerdate refs/heads/
# Find branches whose last commit is older than 30 days
$ git for-each-ref --format='%(refname:short) %(committerdate:relative)' \
--sort=committerdate refs/heads/ | grep "months\|years"
# Show branch, last commit, and author
$ git for-each-ref \
--format='%(refname:short) %(objectname:short) %(authorname) %(subject)' \
refs/heads/
git show-ref
# List all refs in the local repo $ git show-ref # List only branches $ git show-ref --heads # List only tags $ git show-ref --tags # Check if a ref exists $ git show-ref --verify refs/heads/main $ git show-ref --quiet --verify refs/heads/feature # exit code 0 = exists
git update-ref and git symbolic-ref
# Move a branch to a specific commit (the low-level version of git reset) $ git update-ref refs/heads/main abc1234def5678 # Delete a ref $ git update-ref -d refs/heads/old-branch # Read HEAD (which branch it points to) $ git symbolic-ref HEAD # refs/heads/main # Set HEAD to point to a branch $ git symbolic-ref HEAD refs/heads/feature # Detach HEAD (point directly at a commit) $ git symbolic-ref -d HEAD # removes symbolic nature of HEAD
git refs (Git 2.45+)
A newer low-level interface to refs, designed for future ref-database backends (like the reftable format).
# List refs $ git refs list # Migrate to reftable format (faster for repos with many refs) $ git refs migrate --ref-format=reftable
dispelled