Chapter 18Lesson 01~110 minutes

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.

PlumbingIndex internalsObjectsState layers

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

From bytes to a named commit
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.

Next

Build a commit manually without losing the live index

Lesson 2 compares normal add/commit with the low-level object/index/tree/commit sequence, then repeats index work through GIT_INDEX_FILE.

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.

Ethereum / ERC-20
0x716c4Ab160C4B66F31a28AE2448BfF68fc3a2ef0 Send only Ethereum/ERC-20 compatible assets to this address.