Plumbing Commands, Index Internals, cat-file, hash-object, and commit-tree: Diagnostics, Failure Modes, Security, and Performance
Diagnose unreachable objects, invalid index tuples, accidental live-index replacement, wrong-repository environment overrides, unsafe filename parsing, alternate-object failures, and unstable debug-output parsing without destroying evidence.
Learning objectives
- Separate object existence from reachability and retention.
- Interpret and repair invalid manual path/mode/index operations in disposable state.
- Recover a deliberately emptied live index from HEAD without hard reset.
- Detect repository targeting errors caused by inherited Git environment variables.
- Use NUL-safe path interfaces and avoid treating debug/internal formats as stable APIs.
1. Diagnostic sequence — preserve the object/index/ref evidence first
- Preserve evidence: Git version, cwd, repository identity, environment overrides, status, index entries, refs, and candidate object IDs.
-
Inspect read-only:
rev-parse,status --porcelain=v2,ls-files --stage,cat-file,show-ref. - Identify the affected layer: object database, index, working tree, ref, environment/repository discovery, or parser.
- Choose the least destructive correction.
- Verify state from an independent interface.
2. Capture a low-level evidence bundle
git --version
pwd
git rev-parse --show-toplevel
git rev-parse --absolute-git-dir
git rev-parse --git-path index
git rev-parse --show-object-format
git status --porcelain=v2 --branch
git ls-files --stage
git show-ref
git rev-parse --local-env-vars
If repository identity is already suspect, do not run low-level mutating commands until these paths make sense.
3. Failure mode — writing an object and forgetting that no history reaches it
In a disposable repository:
printf "orphan evidence\n" > orphan.txt
ORPHAN=$(git hash-object -w orphan.txt)
git cat-file -t "$ORPHAN"
git cat-file -p "$ORPHAN"
git rev-list --objects --all | grep "$ORPHAN" || echo "not reachable from refs"
git fsck --unreachable --no-reflogs
The object exists, so cat-file succeeds. It is absent
from normal reachable history because no tree/commit/ref path
includes it. The correction is not to edit the object file; either
construct the intended reachable tree/commit/ref chain or accept
that it was temporary.
4. Unreachable does not mean permanently recoverable
5. Intentionally broken example — a manual index entry uses an invalid repository path
BLOB=$(printf "demo\n" | git hash-object -w --stdin)
git update-index --add --cacheinfo "100644,$BLOB,../outside.txt"
A current Git error is conceptually:
error: Invalid path '../outside.txt'
fatal: git update-index: --cacheinfo cannot add ../outside.txt
The path attempts to escape the repository worktree namespace. Git rejects it. The repair is to supply a valid repository-relative path, for example:
git update-index --add --cacheinfo "100644,$BLOB,inside.txt"
git ls-files --stage -- inside.txt
6. Manual mode mistakes can make index/tree semantics differ from the filesystem
A mode is part of the index/tree entry, not inferred later from file
contents. If automation registers a script blob as
100644 when the intended repository mode is executable
100755, Unix-like checkouts may not be executable. On
filesystems where executable bits are unreliable,
core.filemode affects change detection. Use
git ls-files --stage to verify the recorded mode.
7. Intentionally broken example — emptying the live index makes every tracked path look staged for deletion
read-tree --empty directly replaces the selected index.
Do not run this in valuable work.
git status --short
git ls-files --stage
git read-tree --empty
git status --short
git ls-files --stage
With a previously committed file still present in the working tree, status can show a staged deletion plus an untracked working copy. Git has not deleted the file; the live index was replaced with an empty snapshot.
Repair from the committed tree
git read-tree HEAD
git status --short
git ls-files --stage
This restores the index from HEAD without needing a
hard reset. The working-tree file was left untouched throughout.
8. Prevention — redirect dangerous index experiments before they start
ALT_INDEX="$PWD/recovery-lab.index"
GIT_INDEX_FILE="$ALT_INDEX" git read-tree --empty
GIT_INDEX_FILE="$ALT_INDEX" git ls-files --stage
git ls-files --stage
The normal index remains intact. Isolation is cheaper than recovery.
9. Failure mode — environment variables make Git operate on an unintended repository
Imagine a process inherited
GIT_DIR=/srv/repo-a/.git and then changed directory
into repo B. Its Git commands can still resolve repo A's metadata.
env | grep '^GIT_' || true
git rev-parse --absolute-git-dir
git rev-parse --show-toplevel
git rev-parse --local-env-vars
The safe correction is to clear or deliberately set the relevant
environment before mutation. Do not infer the target repository only
from pwd.
10. Cross-repository tooling should sanitize inherited repository environment
Hooks and parent Git processes may export repository-specific
variables. A tool that invokes Git against a second repository
should use an explicit process environment and repository path
rather than inheriting every GIT_* override
accidentally.
11. Failure mode — parsing filenames with whitespace splitting
This common shell pattern is unsafe:
for path in $(git ls-files); do
echo "$path"
done
Spaces, tabs, or newlines can split one pathname into several shell words. Use a NUL-delimited interface and a language/runtime API that reads NUL records. In Bash, a demonstration looks like:
git ls-files -z |
while IFS= read -r -d '' path
do
printf 'path=%q\n' "$path"
done
PowerShell and other runtimes should use their own byte/record-safe handling rather than copying Bash syntax.
12. Failure mode — treating internal/debug output as a timeless compatibility guarantee
A script may parse git ls-files --debug because it
exposes cache details. Current documentation explicitly warns that
the exact debug format may change. The repair is to identify the
actual needed fields and use documented --stage,
--format, porcelain v2, or another stable interface.
13. Failure mode — an alternate object store disappears
A repository configured to borrow objects can work until the alternate directory is moved, unmounted, or cleaned. Object lookup then fails even though local refs still name commits that conceptually belong to the repository.
git rev-parse --git-path objects/info/alternates
git fsck --full
Operational repair depends on why alternates were used: restore the required object store, repopulate local objects from an authoritative source, or migrate away from the dependency. Do not delete refs merely because objects are temporarily unavailable.
14. Failure mode — one environment has replacement refs and another does not
git replace -l
git show <object>
git --no-replace-objects show <object>
If these views differ, record that replacement refs affect interpretation. Replacement refs are advanced local/history tooling and should not silently contaminate forensic conclusions.
15. Security relevance — environment and path handling can redirect trusted automation
Low-level tooling often runs with broad filesystem access in CI
agents or developer tools. An attacker-controlled path, inherited
GIT_DIR/GIT_WORK_TREE, or unsafe filename
parser can make automation inspect or mutate unintended data.
Validate repository roots, use -- before path
arguments, keep paths NUL-safe, and avoid shell
eval around untrusted repository content.
16. Performance relevance — batch object queries and index design matter at scale
Repeatedly spawning git cat-file for hundreds of
thousands of objects is much slower than documented batch modes.
Large indexes may benefit from current index features, but changing
index format/extensions should follow measurement and ecosystem
compatibility testing. Do not optimize by bypassing Git's documented
interfaces and parsing internal files yourself.
17. Red-zone operations that reduce recovery options
18. Symptom → affected layer → verification
| Symptom | Likely layer | Verification |
|---|---|---|
| Object exists but not in history | Reachability/ref graph |
cat-file +
rev-list --objects --all
|
| All files appear staged deleted | Index replaced/emptied |
ls-files --stage + compare HEAD
|
| Git reports unexpected project | Discovery/environment |
rev-parse --absolute-git-dir --show-toplevel
|
| Script breaks on odd filename | Parser framing | Switch to -z/NUL records |
| Refs exist but objects missing | Alternate object dependency | Inspect alternates + fsck |
19. Knowledge check
Question 1. Why is an object written by
hash-object -w not automatically protected by
reachability?
Question 2. After read-tree --empty, why can a
tracked file appear both staged deleted and untracked?
Question 3. Why is pwd insufficient to prove which
repository a Git command will use?
Question 4. Why is git ls-files --debug a bad
long-term parsing contract?
Question 5. What should happen before any pruning during an unreachable-object investigation?
20. Summary
Most plumbing failures become understandable once you classify the wrong layer: unreachable object, malformed index tuple, unintended live-index mutation, wrong repository environment, unsafe path parser, or external object dependency. The safest corrections preserve evidence and repair the narrowest layer rather than applying destructive repository-wide commands.
Authoritative references
git-update-index
git-read-tree
git-ls-files
git-fsck
gitrepository-layout
git-replace
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.