Plumbing Commands, Index Internals, cat-file, hash-object, and commit-tree: Concepts, Architecture, and Mental Model
Understand Git porcelain versus plumbing, index entries, object/tree/commit construction, reachability, ref naming, and repository/index environment boundaries before using low-level commands.
Learning objectives
- Distinguish porcelain workflow commands from narrower plumbing/state interfaces.
- Interpret index entries as mode, object ID, stage, and path.
- Explain the blob → index → tree → commit → ref chain without assuming porcelain implementation details.
- Use cat-file, rev-parse, and ls-files read-only inspections before mutation.
- Recognize repository/index/object environment variables as explicit trust and targeting boundaries.
1. Why look below porcelain when porcelain is what people should normally use?
Earlier chapters taught the safe, high-level Git commands that
developers normally use: add, commit,
status, switch, restore, and
history inspection. Those commands are called
porcelain: user-facing commands designed around
everyday workflows. Git also exposes lower-level commands
traditionally called plumbing. Plumbing is valuable
when you are writing tooling, diagnosing unusual repository state,
or trying to understand exactly which object/index/ref layer
changed.
The goal of this chapter is not to replace porcelain with clever low-level scripts. The goal is to make the underlying state transitions visible enough that you can troubleshoot them precisely and choose documented machine-oriented interfaces when automation needs them.
2. Start every low-level investigation with read-only repository identity checks
git --version
git status --porcelain=v2 --branch
git rev-parse --show-toplevel
git rev-parse --absolute-git-dir
git rev-parse --git-path index
git rev-parse --show-object-format
git rev-parse --show-ref-format
git ls-files --stage
git cat-file -t HEAD
git cat-file -p HEAD
If the repository has no commits yet, commands that resolve
HEAD will fail; that is expected for an unborn branch.
The other commands establish which worktree, repository metadata
directory, index path, object format, reference backend, and staged
paths Git currently sees.
3. Porcelain describes workflows; plumbing exposes narrower state transformations
A porcelain command can coordinate several concerns at once: configuration, filters, hooks, index updates, object creation, ref movement, messages, and user-friendly diagnostics. A plumbing command usually exposes a narrower mechanism such as “write this blob,” “show this object,” “write a tree from this index,” or “move this ref safely.”
| Goal | Typical porcelain | Useful lower-level view |
|---|---|---|
| Stage a file | git add |
hash-object +
update-index concepts
|
| Create a commit | git commit |
write-tree + commit-tree + ref
update concepts
|
| Inspect staged state | git status |
git ls-files --stage |
| Inspect objects | git show |
git cat-file |
| Move a named ref in automation | branch/tag commands |
git update-ref |
Do not assume that porcelain literally invokes these commands one-by-one internally. The table is a mental model of equivalent repository layers, not an implementation trace that Git promises to preserve forever.
4. The low-level commit path crosses four distinct layers
flowchart TD WT[Working-tree bytes] -->|hash-object -w| B[Blob object] B -->|update-index| I[Index entry] I -->|write-tree| T[Tree object] T -->|commit-tree| C[Commit object] C -->|update-ref| R[Named ref] R -->|resolves through refs| C
The first arrow stores file content as a blob. The second makes the index point a path and mode at that blob. The third serializes the complete stage-0 index into tree objects. The fourth creates a commit that names the tree and optional parent commits. Only the final arrow gives the new commit a durable name such as a branch ref. An object can exist before any ref can reach it.
5. The index is a structured staging database, not a folder of copied files
For a normal staged path, git ls-files --stage shows
four important fields: the recorded file mode, object ID, stage
number, and path.
100644 <object-id> 0 app.conf
100644 means a normal non-executable regular file.
100755 represents an executable regular file where
executable-bit tracking is supported. Other documented modes include
symlink and gitlink entries. The object ID names the staged content.
Stage 0 means the path is fully resolved.
6. Conflict stages explain why an index entry also has a stage number
During an unresolved three-way merge, a path may have up to three non-zero-stage entries: stage 1 for the merge-base version, stage 2 for “ours,” and stage 3 for “theirs.” A resolved path returns to a single stage-0 entry. Chapter 20 will use these stages during conflict engineering; here the important point is that the index can represent more than a simple list of filenames.
git ls-files --unmerged
git ls-files --stage
7. hash-object separates computing an object ID from
writing the object
git hash-object app.conf
git hash-object -w app.conf
Without -w, Git computes the object ID that the content
would have and prints it. With -w, Git also writes the
object into the current object database. Writing a blob does not
stage a path, create a tree, create a commit, or move a ref.
8. cat-file is the object-database microscope
git cat-file -t <object>
git cat-file -s <object>
git cat-file -p <object>
-t reports object type, -s reports size,
and -p prints a human-readable representation
appropriate for the object type. For high-volume automation, batch
modes avoid launching a new Git process for every object.
9. A tree object is a directory snapshot assembled from index entries
git write-tree creates tree objects from the current
index and prints the root tree object ID. The index must be fully
merged. A tree stores names, modes, and object IDs—not working-tree
timestamps or filesystem metadata such as arbitrary permissions.
TREE=$(git write-tree)
git cat-file -t "$TREE"
git cat-file -p "$TREE"
10. commit-tree creates a commit object but does not
publish it through a ref
COMMIT=$(printf 'plumbing demonstration\n' | git commit-tree "$TREE")
git cat-file -p "$COMMIT"
The commit object records the tree, zero or more parents,
author/committer identities and timestamps, and the message. A root
commit has no parent. A normal later commit supplies one parent with
-p; merge commits can supply multiple parents. The
command prints the new commit ID and stops there.
11. Object existence and object reachability are different
If hash-object -w writes a blob that no tree
references, the blob exists but is not reachable from a normal ref.
Similarly, a commit made by commit-tree is not part of
branch history until a ref points to it or another reachable commit
descends from it.
git cat-file -e "$COMMIT"
git rev-list --objects --all
The first command can succeed while the second omits the object. This distinction is essential when debugging “I created the object, but Git cannot find it in history.”
12. Use ref APIs rather than editing ref files manually
After inspecting a commit object, automation can attach a temporary
ref using git update-ref. Chapter 19 explores guarded
and transactional ref updates in depth. For this chapter, the
production lesson is simple: do not open files under
.git/refs and overwrite them yourself.
git update-ref refs/heads/plumbing-demo "$COMMIT"
git rev-parse refs/heads/plumbing-demo
13. Environment variables can redirect the repository, worktree, index, and object store
GIT_DIR can select repository metadata directly.
GIT_WORK_TREE can select a working-tree root.
GIT_INDEX_FILE can replace the normal
$GIT_DIR/index for a command.
GIT_OBJECT_DIRECTORY and
GIT_ALTERNATE_OBJECT_DIRECTORIES affect object
storage/lookup. These are powerful tooling mechanisms and equally
powerful ways to operate on the wrong state.
git rev-parse --local-env-vars
git rev-parse --git-dir
git rev-parse --show-toplevel
git rev-parse --git-path index
14. Repository discovery is itself a boundary that tooling should verify
When no explicit GIT_DIR is supplied, Git normally
searches upward from the current directory for repository metadata,
stopping at filesystem boundaries by default.
GIT_CEILING_DIRECTORIES can tell Git not to search
above specific directories;
GIT_DISCOVERY_ACROSS_FILESYSTEM can alter the
filesystem-boundary rule. Tools should resolve
--show-toplevel and
--absolute-git-dir before mutating state.
15. Stable automation output is not the same thing as internal file-format stability
Many plumbing commands expose documented machine-oriented output,
but not every debugging flag is a compatibility promise. For
example, git ls-files --debug is documented as
manual-inspection output whose exact format may change. Prefer
documented formats such as
git status --porcelain=v2 -z,
git ls-files -z, and explicit
cat-file --batch-check formats.
16. DevOps connection — low-level understanding improves tools only when the interface is chosen carefully
Build systems, repository analyzers, IDE integrations, release tools, and repair utilities sometimes need object/index/ref detail. The safe production pattern is to use documented commands with machine-oriented output, validate repository identity before mutation, handle paths without newline/whitespace assumptions, and isolate alternate-index experiments from the live developer index.
17. Knowledge check
Question 1. Does git hash-object -w file stage the
file?
Question 2. What does stage 0 mean in
git ls-files --stage?
Question 3. Why can git cat-file -e COMMIT succeed
while git rev-list --all cannot reach that
commit?
Question 4. Which layer does
git write-tree serialize?
Question 5. Why should automation inspect GIT_DIR/GIT_WORK_TREE/GIT_INDEX_FILE-related state?
18. Summary
Plumbing exposes the boundaries that porcelain usually coordinates for you: objects, index entries, trees, commits, and refs. The most important beginner insight is that these are separate state layers. The most important automation insight is that documented command interfaces are safer than parsing or editing Git's internal files directly.
Authoritative references
git command overview and environment
git-ls-files
git-cat-file
git-hash-object
git-write-tree
git-commit-tree
Keep the academy open
Support free, practical DevOps education.
Every lesson is designed to remain readable in a browser, downloadable from GitHub, and usable without a paid learning platform. Contributions help expand and maintain the curriculum.
0x716c4Ab160C4B66F31a28AE2448BfF68fc3a2ef0
Send only Ethereum/ERC-20 compatible assets to this
address.