Learn/Semantic · 8 min · updated 2026-08-03

Semantic diff vs git diff: what changes when you compare structure

A git diff compares lines. A semantic diff compares the parsed structure of the code, so a rename is a rename and a move is a move. What that changes in practice, and what it costs.

A Git diff compares lines of text. A semantic diff parses both versions into syntax trees and compares those, so it can say a function was renamed, moved or had its body changed, rather than reporting that forty lines vanished and forty appeared. The difference is small on a two-line fix and large on anything an agent writes.

The same change, two ways

Move a function from the bottom of a file to the top and rename it. Git reports the whole block as deleted and the whole block as added: two hunks, sixty lines, no relationship between them. A structural comparison reports one node, moved, renamed, body unchanged. The second description is both shorter and more accurate, and it is the one a reviewer needs.

EditGit diffSemantic diff
Rename a functionEvery line of it, twiceOne rename, plus the call sites
Reformat a fileThe whole fileNothing changed
Wrap a block in a conditionEvery line re-indentedOne node gained a parent
Delete a functionA deletion among manyA named symbol removed, with its callers listed
Change one booleanOne lineOne line, and everything that depends on it

The last row is the one that matters most and gets the least attention. Git can tell you what changed. It cannot tell you what the change reaches.

How it works

Both versions of the file are parsed into an abstract syntax tree, usually with tree-sitter, which is fast enough to run on every save and tolerant of code that does not compile. Each node is then reduced to a stable identity: a function is identified by its position in the tree and its contents, not by its line number. Comparing the two trees gives added, removed, changed and moved nodes. Adding a hash per node, computed from the node and its children, makes the comparison cheap: identical subtrees have identical hashes and are skipped whole. That is a Merkle graph.

What it costs

  • A parser per language. Support is per-grammar, so an unsupported language falls back to text.
  • Parse time on first index. Incremental after that, since only edited files are re-parsed.
  • Ambiguity in edge cases: a function deleted and a similar one added can be reported as a change or as both, and different tools pick differently.

Where it earns its keep

Agent-written changes, mostly. A 4,000-line diff from an autonomous session is unreadable line by line, and most of those lines are moves, renames and reformatting. Removing that noise leaves the twenty or thirty places where a decision was made, which is a review a person can actually do. It is also what makes an automatic check against the goal possible: you cannot ask whether a goal was delivered without a structure to ask about.

Related tools worth knowing: difftastic for a structural diff on the command line, and GumTree for the tree-differencing research this all descends from. Aura keeps the same structure as a live index so the diff, the impact analysis and the goal proof all read from one graph.

Questions

What is a semantic diff?

A comparison of two versions of a program by their parsed structure rather than their text. Where a line diff reports characters that changed, a semantic diff reports that a function was renamed, a parameter added, a branch removed — the units the language actually has.

What is the difference between a semantic diff and git diff?

git diff compares lines and knows nothing about the language, so reformatting looks like change and a moved function looks like a deletion plus an addition. A semantic diff parses both sides first, so a move is a move, a rename is a rename, and formatting is silent.

Why does git diff show changes when only formatting changed?

Because it compares text. Re-wrapping a line, changing indentation or reordering imports alters the characters without altering the program, and a line-based comparison has no way to tell those apart from a real edit.

Is a semantic diff better than a text diff?

For review, usually — it separates what changed from how it was typed. For everything else, not necessarily: it needs a working parser for the language, it is slower, and it cannot show you edits inside strings, comments or config that a text diff shows plainly.

How does a semantic diff work?

Parse both versions into syntax trees, hash each node from its children upward, then compare the hashes. Identical subtrees match immediately and are skipped, so the comparison only descends into the parts that genuinely differ, and matches survive a node moving.