skip to content

In Git, what is the difference between porcelain and plumbing commands?

level: middleimportance: should knowfreq 40%

answer

  1. the fixture versus the pipes underneath
  2. one is for humans, one for scripts
  3. output stability is the real contract
  4. some commands have a machine-mode flag
  5. commit is write-tree plus commit-tree plus update-ref

basics

~20 s

Porcelain commands are the human-facing UI — add, commit, log, merge — whose output is formatted for people and may change. Plumbing commands are low-level building blocks like cat-file, hash-object and rev-parse, with stable output meant for scripts.

solid answer

~50 s

Git's own documentation splits its commands into **porcelain** and **plumbing**. Porcelain is the user interface: `git add`, `git commit`, `git log`, `git status`, `git merge`. Its output is written for humans, it honours user configuration and aliases, and its wording is explicitly allowed to change between releases. Plumbing is the layer underneath: `git cat-file`, `git hash-object`, `git ls-tree`, `git ls-files`, `git write-tree`, `git commit-tree`, `git update-ref`, `git rev-parse`, `git rev-list`, `git for-each-ref`. These do one mechanical thing each, produce terse machine-readable output, and are treated as a stable interface for scripting. The practical rule: never parse porcelain output in a script. If you must use a porcelain command, use its explicit machine-readable mode — `git status --porcelain=v2`, `git blame --porcelain` — and add `-z` where paths are involved so that filenames with spaces or newlines survive.

code

bash · 3 lines
bash
git for-each-ref --format='%(objectname) %(refname)' refs/heads
git rev-parse HEAD
git status --porcelain=v2 -z

go deeper

for a junior

Recall that the commands you use daily are the porcelain layer and that Git has a lower-level set of commands underneath it that scripts use.

for a middle

Give examples on both sides, explain the output-stability contract, and describe how git commit decomposes into write-tree, commit-tree and update-ref.

for a senior

Show scripting judgement: choose for-each-ref and rev-parse over parsing human output, use -z for paths, pin --porcelain=v2, and rely on exit codes for existence checks.

for a principal

Own it as an interface-design point: tooling that depends on human-facing output is a latent break on every upgrade, so make the plumbing-only rule explicit in shared automation.

## The split, and why it exists Git was built bottom-up: first a content-addressed object database with a set of tiny tools to manipulate it, then a friendlier interface on top. The names stuck. Borrowing plumbing terminology, the low-level tools are the **plumbing** — the pipes you never look at — and the user-facing commands are the **porcelain** — the fixture you actually touch. The git(1) manual page still lists the commands in exactly these two groups. **Porcelain** commands are the ones in every tutorial: `add`, `commit`, `status`, `log`, `diff`, `branch`, `checkout`, `switch`, `merge`, `rebase`, `pull`, `push`. They are designed for a person at a terminal, so they colourise, paginate, abbreviate object IDs, translate messages, respect aliases, and print advice. All of that is a promise to the reader, not a contract with a program: the exact wording, ordering and layout may change in any release. **Plumbing** commands are the mechanical primitives. Each does one narrow job against the object database, the index or the refs: - `git cat-file -t/-s/-p` — inspect an object's type, size or contents. - `git hash-object -w` — compute an object ID and optionally write the object. - `git ls-tree` — list the entries of a tree object. - `git ls-files --stage` — list index entries with mode, object ID and stage number. - `git write-tree` / `git commit-tree` / `git mktree` — build tree and commit objects. - `git update-ref` / `git symbolic-ref` — set refs, including `HEAD`. - `git rev-parse` — resolve a revision expression or a repository path to a concrete value. - `git rev-list` / `git for-each-ref` / `git diff-tree` — enumerate commits, refs and changed paths. Their output is deliberately boring: fixed-width fields, tab or NUL separators, full object IDs, no colour, no pager, no abbreviation, and no translation. That is what makes it safe to pipe. ## Porcelain built from plumbing The relationship is not just conceptual. `git commit` does essentially what `git write-tree`, `git commit-tree` and `git update-ref` do, wrapped in message editing, hooks and safety checks. `git log` is `git rev-list` plus formatting. Knowing this is what turns Git from a set of memorised incantations into a system you can reason about, which is precisely why interviewers ask: a candidate who can name the plumbing behind a porcelain command has understood the store rather than the UI. ## The confusing `--porcelain` flag A genuine trap. Several porcelain commands accept a `--porcelain` option — `git status --porcelain`, `git push --porcelain`, `git blame --porcelain` — and it means the *opposite* of what the word suggests in the command taxonomy. There it means "output intended for consumption by porcelain scripts", i.e. a stable, machine-parseable format. `git status --porcelain` (and the richer `--porcelain=v2`) is explicitly documented as a stable format, unlike `git status`'s human output. So the guidance is not "never touch porcelain commands in scripts"; it is "never parse a format that was written for a human". ## Practical scripting rules 1. Prefer a plumbing command when one exists. Iterating refs? `git for-each-ref --format=...`, not parsing `git branch`. Getting the current commit? `git rev-parse HEAD`, not scraping `git log`. 2. When only a porcelain command will do, use its documented machine mode and pin the version of that mode: `--porcelain=v2` rather than bare `--porcelain`. 3. Use `-z` wherever paths appear (`git ls-files -z`, `git status -z`, `git diff --name-only -z`). Path names may contain spaces and, on many systems, newlines; NUL termination is the only safe delimiter. 4. Do not rely on abbreviated object IDs in scripts — plumbing gives you full IDs for a reason. 5. Check exit codes. Plumbing is designed for it: `git rev-parse --verify --quiet <rev>` and `git cat-file -e <oid>` are existence tests that report through the exit status rather than the output. ## How to phrase it in an interview A complete answer names the split, gives two or three examples on each side, states the stability contract (plumbing output is an interface; porcelain output is prose), notes that porcelain is implemented in terms of plumbing, and flags the `--porcelain` flag as the naming trap. That covers everything the question is actually probing.

  • Why does git status have a --porcelain option if status is itself a porcelain command?
    The flag means "output for consumption by porcelain scripts" — a documented, stable, machine-parseable format, unlike status's human output, which may be reworded at any release. `--porcelain=v2` pins a specific version of that format. The naming collides with the command taxonomy, which is exactly why it is worth calling out.
  • Name the plumbing commands that git commit is effectively composed of.
    `git write-tree` turns the current index into a tree object, `git commit-tree` creates a commit object pointing at that tree with the current HEAD as parent, and `git update-ref` moves the branch to the new commit. `git commit` adds message editing, hooks, identity resolution and safety checks around that core.
  • Why should scripts pass -z to commands that print paths?
    Path names may contain spaces and, on many filesystems, newlines, so a line-oriented parse can be broken by a hostile or merely unusual filename. `-z` makes Git terminate each record with a NUL byte, which cannot appear in a path, giving an unambiguous delimiter. It also disables the quoting Git otherwise applies to unusual characters.

Porcelain is the sink you use every day; plumbing is the pipework behind the wall. You would not redesign the pipes for looks, and you would not judge them by how the sink is shaped.

saying these in an interview costs you the question

  • Thinks plumbing means deprecated or internal-only commands
  • Parses git log or git branch output in scripts
  • Believes --porcelain makes output more human-readable
  • Says plumbing commands are unsafe to use directly
  • Assumes porcelain output is stable across Git versions

context