feat: Semantic diff and three-way merge for .tm7 deconfliction#17
Merged
Conversation
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
feat: semantic diff and three-way merge for .tm7 deconfliction
What & why
.tm7is DataContractSerializer XML which is verbose and order-sensitive so git's line-basedmerge corrupts it (conflict markers break deserialization) and its diffs are unreadable.
This adds a structural, identity-aware diff and merge keyed by the stable per-node GUID every
model already carries, exposed via the CLI, wired into git, and surfaced in Studio so
.tm7becomes reviewable and mergeable in normal branch/PR workflows.
Highlights
ModelSnapshot/ModelDiffandModelMerge. Non-overlapping edits combine; genuine conflicts (same-property divergence,delete/modify, dangling connector) resolve to ours and are reported, the written model is
always valid, never marker-laced. Falls back to a two-way merge when the common ancestor
isn't available.
tmforge diff <a> <b> [--json]andtmforge diff --textconv(readable git diffs);tmforge merge <base> <ours> <theirs>shaped as a git driver (writes a<path>.conflicts.jsonsidecar + non-zero exit on conflict);
tmforge git-setup [--global] [--print]wires thediff/merge drivers with no committed
.gitattributesrequired..gitattributes(*.tm7 diff=tmforge merge=tmforge) + textconv/merge-driverconfig; fully opt-in, so git behaves exactly as before until configured.
EngineService.Merge(nullable base -> two-way) behindPOST /v1/model/mergeand the in-browser WASMEngine.Merge; OpenAPIv1.jsonregenerated.survives the round-trip and pages align across files (also makes diff meaningful on
.tmforge.json).pick ours-or-theirs per conflict, then load onto the canvas or download
.tm7; app-styled file pickers.merge tests, and id-preservation coverage; validated with real
git mergeand in-browser WASM smokes.