<!--order=14-->
<h1>Git: Plumbing — Objects and Refs</h1>
<p>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.</p>

<h2>Commands Covered</h2>
<p><code>git cat-file</code> · <code>git hash-object</code> · <code>git ls-tree</code> · <code>git read-tree</code> · <code>git write-tree</code> · <code>git commit-tree</code> · <code>git rev-parse</code> · <code>git rev-list</code> · <code>git for-each-ref</code> · <code>git show-ref</code> · <code>git update-ref</code> · <code>git symbolic-ref</code> · <code>git refs</code></p>

<h2>The Object Model</h2>
<p>Git stores four types of objects, each identified by a SHA-1 (or SHA-256) hash of their content:</p>
<table>
  <thead><tr><th>Object type</th><th>What it stores</th><th>Created by</th></tr></thead>
  <tbody>
    <tr><td><code>blob</code></td><td>File content (no filename)</td><td><code>git hash-object</code>, <code>git add</code></td></tr>
    <tr><td><code>tree</code></td><td>Directory: list of blobs and subtrees with names/modes</td><td><code>git write-tree</code></td></tr>
    <tr><td><code>commit</code></td><td>Snapshot: points to a tree, parent commit(s), author, message</td><td><code>git commit-tree</code>, <code>git commit</code></td></tr>
    <tr><td><code>tag</code></td><td>Annotated tag: points to a commit with tagger metadata</td><td><code>git tag -a</code></td></tr>
  </tbody>
</table>

<h2>git hash-object</h2>
<p>Computes the SHA hash of content — the same hash Git would use to store it.</p>
<pre>
# 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
</pre>

<h2>git cat-file</h2>
<p>Provides content or details of repository objects. The primary tool for inspecting Git's internal storage.</p>
<pre>
# 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 &lt;jason@dispelled.ca&gt; 1699999999 -0500
# committer Jason Smith &lt;jason@dispelled.ca&gt; 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
</pre>

<h2>git ls-tree</h2>
<p>Lists the contents of a tree object — like <code>ls</code> for a Git tree.</p>
<pre>
# 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
</pre>

<h2>git write-tree and git read-tree</h2>
<pre>
# 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
</pre>

<h2>git commit-tree</h2>
<p>Creates a commit object from a tree object. The low-level version of <code>git commit</code>.</p>
<pre>
# 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
</pre>

<h2>git rev-parse</h2>
<p>Translates names, refs, and expressions into raw SHA hashes. The backbone of scripting with Git.</p>
<pre>
# 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 &gt;/dev/null 2&gt;&amp;1 &amp;&amp; echo "in repo" || echo "not a repo"

# Resolve ~ and symbolic refs
$ git rev-parse --symbolic-full-name HEAD
# refs/heads/main
</pre>

<h2>git rev-list</h2>
<p>Lists commit objects in reverse chronological order. The engine behind <code>git log</code>.</p>
<pre>
# 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
</pre>

<h2>git for-each-ref</h2>
<p>Outputs information on each ref — the low-level version of branch and tag listing. Highly configurable for scripting.</p>
<pre>
# 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/
</pre>

<h2>git show-ref</h2>
<pre>
# 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
</pre>

<h2>git update-ref and git symbolic-ref</h2>
<pre>
# 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
</pre>

<h2>git refs (Git 2.45+)</h2>
<p>A newer low-level interface to refs, designed for future ref-database backends (like the reftable format).</p>
<pre>
# List refs
$ git refs list

# Migrate to reftable format (faster for repos with many refs)
$ git refs migrate --ref-format=reftable
</pre>

<h2>References</h2>
<ul>
  <li><a href="https://git-scm.com/book/en/v2/Git-Internals-Git-Objects" target="_blank" rel="noopener">Pro Git — Git Objects ↗</a></li>
  <li><a href="https://git-scm.com/docs/git-cat-file" target="_blank" rel="noopener">git-cat-file documentation ↗</a></li>
  <li><a href="https://git-scm.com/docs/git-rev-parse" target="_blank" rel="noopener">git-rev-parse documentation ↗</a></li>
  <li><a href="https://git-scm.com/docs/git-for-each-ref" target="_blank" rel="noopener">git-for-each-ref documentation ↗</a></li>
</ul>
