Why a text diff of a .prefab file lies to you
Unity assets are YAML, which makes them technically diffable and practically unreadable. The problem is not that the format is verbose. It is that a line-based diff answers a different question from the one you asked.
A line-based diff of a Unity .prefab or .unity file is
unreliable because object identity lives in fileID and GUID values
rather than in file position. Reordering, renaming and serialization migration all
appear as large changes that changed nothing, while a single repointed reference
looks like any other line. A useful diff pairs objects by identity first, then
compares the paired objects property by property.
The question you asked
"What did this branch change about the scene?" is a question about objects. Which GameObjects were added, removed or moved. Which components changed. Which references now point somewhere else.
A text diff answers "which lines differ", and in a Unity asset those two questions come apart in at least four specific ways.
1. Identity does not live in position
Every object in a Unity file has a fileID — a local identifier used
by every reference to it. Every asset has a GUID, stored in its
.meta. Together they are what the Editor means by "this object".
A text diff has no access to that. It matches by proximity: this block of lines looks like that block of lines, so they must correspond. When Unity reorders the document — and it does, freely, because the order of blocks in the file carries no meaning — the diff produces a large, confident and entirely fictional set of changes.
The correction is to pair objects by fileID and GUID first, and only
then compare the paired objects field by field. Reordering becomes what it
actually is: nothing.
2. Renaming rewrites the block; reparenting hides the change
Rename a GameObject and one m_Name line changes — but if the rename
is accompanied by any structural churn, the diff often shows the whole object as
deleted and a new one as added. The reviewer now has to decide whether a hundred
lines of "new" content is genuinely new.
Reparenting is the opposite failure. Moving an object to a different parent
changes m_Father on one side and the m_Children list on
two others. Three small edits, scattered across the file, describing one
conceptual action that the diff never names.
3. Serialization noise is indistinguishable from intent
Open an asset in a newer Editor and save it, and Unity may rewrite fields to a newer serialized layout: added defaults, renumbered versions, reformatted scalars. Nothing about the asset's behaviour changed. The diff is enormous.
Teams learn to skim past these, which is exactly the habit that lets a real change through. A diff tool that understands the format can fold migration-only differences away and leave the reviewer with a short list they will actually read.
4. The format has corners that break naive parsers
If you are tempted to write the parser yourself — reasonable, it is only YAML — three things will bite you.
Pre-2018.3 prefabs are a different format. Older files use a
Prefab: block rather than PrefabInstance:. These are
still in circulation inside third-party packages, including some that ship with
widely used Unity tooling. A parser that only recognises the modern shape will
skip them silently.
Multi-line scalars do not respect your indentation assumptions. A quoted string that spans lines can place its closing quote at column zero. Any parser that decides block structure from leading whitespace will mis-nest the document from that point onward, and the failure surfaces far away from its cause.
fileID is namespaced per level of a variant chain.
The same conceptual object has a different fileID at each level, so
comparing across a chain by raw ID pairs the wrong objects. Inside the Editor,
PrefabUtility.GetCorrespondingObjectFromSource is the bridge; outside
it, the mapping has to be reconstructed deliberately.
What "semantic" actually has to mean
The word gets used loosely. Concretely, a diff of Unity assets is semantic when it does all of the following:
- Pairs objects by
fileIDand GUID, never by name or position. - Reports moves and reparenting as moves, not as a deletion plus an addition.
- Names properties the way the Inspector names them.
- Resolves references to the asset they point at, not the raw identifier.
- Distinguishes format migration from an actual edit.
- Treats prefab instance overrides as overrides against a specific source.
Miss any one of these and you are back to reading YAML, only with nicer colours.
Frequently asked questions
Can I diff Unity prefab files with Git?
Git will produce a diff because the files are text, but it matches blocks by position rather than by object identity. Reordering, renaming and serialization migration all appear as large changes that are not changes at all.
What makes a Unity asset diff semantic?
Pairing objects by fileID and GUID rather than by name or position, reporting moves as moves, naming properties as the Inspector names them, resolving references to their target asset, separating format migration from real edits, and treating prefab overrides as overrides against a specific source.
Why do renames produce huge diffs in Unity files?
A rename changes one line, but any accompanying structural churn breaks the diff's block matching, so the object is reported as deleted and re-added.
What breaks a hand-written Unity YAML parser?
Three things: pre-2018.3 prefabs use a Prefab: block instead of PrefabInstance:; multi-line quoted scalars can place the closing quote at column zero and defeat indentation-based parsing; and fileID values are namespaced per level of a variant chain, so raw IDs pair the wrong objects across levels.
Scene Diff implements the list above for scenes and prefabs, read-only. If you are resolving a conflict rather than reviewing a change, start with Resolving Unity scene merge conflicts in Git.