4.5.7 · D5Software Engineering
Question bank — Git internals — objects (blob, tree, commit, tag), DAG structure
True or false — justify
Every answer must give the reason, never a bare true/false.
A blob knows the name of the file it came from.
False. A blob is pure content bytes with only a
"blob <size>\0" header — the name and permissions live in the tree entry that points to the blob.Two different files with byte-identical content are stored as two blobs.
False. Content-addressing means identical bytes hash to the same SHA-1, so both files point to one shared blob — automatic deduplication.
Renaming a file (content unchanged) creates a new blob.
False. Only the tree's listing changes, so a new tree is made; the blob's bytes are untouched, so its hash and the object itself are reused.
Renaming a file (content unchanged) creates a new tree object.
True. The tree stores the
(mode, type, hash, name) line; changing the name changes the tree's bytes, hence its hash, hence a new tree object.A commit stores the diff from its parent.
False. A commit references one full root tree (a complete snapshot). Deltas only appear later inside packfiles as a storage optimisation, not in the data model.
A branch is one of Git's object types.
False. The four types are blob/tree/commit/tag. A branch is just a small file in
refs/heads/ holding one commit hash — a movable pointer, discussed in Branches and HEAD.Deleting a branch deletes the commits on it.
False. It removes only the pointer; the commit objects linger (unreachable) until git gc prunes them.
The SHA-1 of a blob is computed over the file content alone.
False. It is
SHA1("<type> <size>\0" + content). Forgetting the header is the #1 reason hand-computed hashes don't match Git — see Hashing & SHA-1.A commit can have zero parents.
True. The very first (root) commit has no parent; that's the base case of the DAG, where the parent chain terminates.
A commit can have more than two parents.
True. A normal commit has 1 parent, a merge has 2, and an "octopus" merge of several branches can have 3 or more — each an extra edge out of the commit.
Git's history graph can contain a cycle if you rewrite history cleverly.
False. A parent's hash is fixed before its child is created, so a child cannot appear in its own ancestry — cycles are mathematically impossible, hence acyclic.
Editing an old commit's message leaves all later commit hashes unchanged.
False. Each descendant embeds its parent's hash, so changing an ancestor forces new hashes all the way down — this is exactly what makes history tamper-evident.
An annotated tag and a lightweight tag are the same kind of thing.
False. An annotated tag is a real object (message + pointer, possibly signed); a lightweight tag is just a ref file pointing at a commit, like a branch that doesn't move.
The empty-file blob hash e69de29b… is a special constant Git hard-codes.
False. It is simply
SHA1("blob 0\0") — the ordinary hash of empty content; it looks magic only because empty files are common.Spot the error
Each statement below has one flaw. Name it.
"To find hello.txt's content, Git looks up its blob by filename."
The flaw: blobs are keyed by hash, not name. Git first reads the tree to map
hello.txt → hash B, then fetches blob B."A tree points only to blobs."
The flaw: a tree can also point to other trees (subdirectories). That nesting of trees-of-trees is what makes Git a Merkle tree.
"Because objects are immutable, Git can never change a file."
The flaw: it never mutates an existing object; it adds a new blob/tree/commit and moves the branch pointer — change via addition, per Immutability and Persistent Data Structures.
"The commit hash depends only on its tree."
The flaw: it also depends on the parent hash(es), author/committer, and message — that parent dependency is what forms the DAG edge and the tamper-evidence.
"Arrows in the DAG point from old commits to newer ones (forward in time)."
The flaw: edges point child → parent (newer → older). You store the parent's hash inside the child, so links go backward in time.
"Content-addressing means the hash guarantees the content is safe from anyone tampering."
The flaw: it guarantees tamper-evidence (any change is detectable), not tamper-prevention; SHA-1 detects accidental or naive edits, it doesn't stop someone rewriting and re-hashing.
Why questions
Answer with the mechanism, not a restatement.
Why does Git include a "<type> <size>\0" header before hashing content?
It makes the hash type-aware and length-framed, so a 5-byte blob and a 5-byte commit fragment can't be confused and lengths are unambiguous.
Why is copying a file's content cheap in storage terms?
Identical bytes → identical SHA-1 → the same existing blob is reused; only a tiny new tree entry (name → same hash) is added.
Why can Git detect a rename automatically?
Because the blob hash is unchanged, Git spots the same content appearing under a new name in the tree and infers a rename.
Why is Git history a DAG rather than a plain tree or an arbitrary graph?
It is directed (child→parent) and acyclic (a parent exists before its child), and merges let two histories fan in — a tree forbids fan-in, an arbitrary graph would allow cycles.
Why do "history rewrites" actually create brand-new objects?
Any change to an ancestor changes its hash, which changes every descendant's embedded parent hash — so a rewritten line is a fresh chain of objects, not edited old ones.
Why does Git store full snapshots yet stay space-efficient?
Unchanged files are shared across commits via identical blob/tree hashes, and packfiles delta-compress similar objects after the fact — the model is snapshots, the storage is compressed.
Edge cases
The scenarios beginners never test — cover them all.
What object(s) does the first-ever commit in a repo create at minimum?
At least one blob (if a file exists), one root tree, and one commit with zero parents — the DAG's terminating node.
What is the tree of a commit that adds only an empty file?
A tree with one entry pointing to the empty-file blob
e69de29b…; the tree itself is non-empty even though the file is empty.Can a commit have an empty tree (no files)?
Yes — an empty root tree hashes to a valid tree object, so a commit can snapshot an empty project.
If two branches point at the exact same commit, how many commit objects exist?
One. Two ref files hold the same hash; a commit is shared, not duplicated per branch.
After you delete a branch, are its unique commits gone immediately?
No — they become unreachable but remain on disk until git gc runs (reflog can even keep them reachable for a while).
What does a merge commit look like in the DAG, and how is it the only place history fans in?
It is a commit with two (or more) parents, so two arrows leave it; every other commit has ≤1 parent, so this is the unique fan-in point.
Does an empty directory get its own tree object?
No — Git tracks content, and a directory with no tracked files produces no tree entry (this is why teams add a placeholder like
.gitkeep).If you commit the same tree twice with no changes, what happens?
The tree and blobs are reused (same hashes), but each commit is a distinct object because its committer timestamp / parent differs, giving a new commit hash.
Recall One-line self-test
Cover everything above; can you state why a rename adds a tree but not a blob, why history is acyclic, and why the header goes into the hash — each in one sentence? If yes, you own the model.
Rename
New tree entry (name changed) but the blob's bytes are identical, so the blob is reused.
Acyclic
A parent's hash is fixed before the child exists, so a child can't be its own ancestor.
Header
"<type> <size>\0" makes the hash type-aware and length-framed, preventing cross-type confusion.