CelinQ Insights · No. 45

Can Enterprise Architect Models Merge Like Source Code Without Treating Models as Text?

Software teams solved concurrent editing decades ago with three-way merge. Architecture teams can borrow the same idea, but only once they stop trying to borrow the text-diff mechanics that go with it.

A NILUS perspective on collaborative modelling for Sparx Enterprise Architect

Every architect who has ever worked alongside a software engineering team has, at some point, watched with a certain envy as a developer resolves a merge conflict in about ninety seconds: two branches diverged, a tool shows exactly which lines differ from a common ancestor, the developer picks the right combination, and the codebase moves on. It looks almost effortless, and it raises an obvious question — why can't a shared architecture model work the same way? Why does merging two people's changes to an Enterprise Architect repository still feel, for most teams, like a manual archaeology project rather than a solved problem?

The honest answer is not that architecture tooling is behind software tooling by some number of years and will eventually catch up to the same techniques. It is that the technique software teams rely on — line-based three-way merge — is a solution shaped precisely to fit the structure of source code, and a model does not have that structure. Borrowing the idea of three-way merge is exactly right. Borrowing the mechanics that make it work for text is exactly wrong, and understanding why is the difference between a merge tool that actually helps and one that produces confident-looking nonsense.

Why line-based merge fits source code

A source file is, structurally, a sequence: line one, then line two, then line three, in a fixed and meaningful order, where two adjacent lines are next to each other because a human put them there in that order for a reason a compiler or interpreter will respect. This sequential structure is precisely what makes line-based diff and merge work as well as it does. Given a base version and two divergent copies, a line-based tool can compare each copy against the base line by line, identify which contiguous stretches changed on which side, and — when two changes touch different stretches of the file — combine them by simply keeping both stretches in their respective positions. It works because "the file" genuinely is decomposable into an ordered sequence of independent-ish chunks, and most of the time, two developers editing different functions in the same file really are doing independent things that a line-based comparison correctly recognises as non-overlapping.

It is worth being honest that even in source code, line-based merge is a leaky abstraction — it occasionally combines two independently sensible edits into code that compiles but is subtly wrong, because "different lines" is not a perfect proxy for "unrelated changes." Software teams accept this because the alternative, no automatic merging at all, is worse, and because tests and code review catch a meaningful share of what the merge itself cannot. The point is not that line-based merge is flawless even in its home territory. It is that it is at least a coherent fit for what a source file actually is: a sequence of lines.

Why a model has no lines

An Enterprise Architect model has no equivalent sequential structure to exploit, and pretending it does is where naive attempts at "just diff it like a text file" go wrong. A model is fundamentally a graph: elements connected to other elements by relationships, contained within a package hierarchy that is itself a tree, decorated with tagged values that form independent key-value maps, and rendered across any number of diagrams where the same element can appear multiple times in different visual contexts, each with its own position and size. There is no single meaningful "line one, then line two" ordering that captures this structure, and any attempt to force one — by, say, serialising the model to some canonical XML or text representation and diffing that — immediately runs into the identity problem discussed in the article on offline consistency: a rename or a move can shift where an entity's serialised representation falls in that artificial ordering, and a purely textual diff has no way to know that the thing that moved is still the same thing, semantically, that it always was.

This is not a minor technical inconvenience that a cleverer serialisation format would fix. It reflects something true about what a model actually is, as distinct from what a text file is. A text file's meaning is largely determined by sequence and adjacency — this line follows that one, this block is nested inside that one. A model's meaning is determined by identity and relationship — this element is the same element it was yesterday regardless of where it currently sits in a package tree or a diagram, and this connector means what it means because of which two specific elements it joins, not because of where its row happens to fall in some incidental ordering.

What actually transfers from source-code merge, and what does not

The idea worth keeping from source-code version control is the three-way comparison itself: given a common ancestor and two divergent sets of changes, compare each side against the ancestor rather than against each other, so that each side's actual intent can be isolated and reasoned about independently, and combine the two sets of changes wherever they do not genuinely overlap. That structural idea is sound regardless of what kind of content is being merged. What does not transfer is the unit of comparison. Where source-code merge compares lines, model merge has to compare the things a model actually consists of: stably identified entities, their individual scalar fields, their tagged-value entries, their tree position, their relationship endpoints, and their diagram geometry — the merge atoms discussed in detail in the companion piece on merge atoms. Get this substitution right and the three-way idea works beautifully on a model. Get it wrong — keep the line-based unit and simply point it at a model's serialised text — and you get a tool that looks superficially similar to a code merge tool while actually reasoning about something with no relationship to what the model means.

Three-way merge is a strategy, not a format. The strategy — compare each side against a common ancestor, keep what does not overlap, escalate what does — is exactly right for a model. The format that strategy was originally built to compare — lines of text — is exactly wrong, and confusing the two is the single most common mistake in attempts to bring "git-style" merging to architecture tools.

Stable entity identity as the foundation

Everything downstream of the comparison strategy depends on a property that source-code merge gets almost for free and model merge has to build deliberately: knowing, with certainty, that the thing being compared on one side is the same thing being compared on the other. In a text file, this is trivial — line seventeen is line seventeen, and if it moved, a decent diff algorithm can usually still track it by content similarity. In a model, an element's identity has to be tracked explicitly and independently of everything that can change about it — its name, its position in the tree, its stereotype, its geometry on any given diagram. CelinQ assigns and preserves a stable identifier for exactly this reason: so that a rename is recognised as a rename of a persistent identity, not the disappearance of one thing and the appearance of a different one that happens to look similar. Without this, three-way comparison collapses immediately, because the comparison has no reliable way to find "the same entity" on both sides of a divergence to compare in the first place.

Causal revisions instead of arbitrary snapshots

Source-code merge tools typically work from a specific, well-defined common ancestor commit — the point where two branches diverged, tracked precisely by the version-control system itself. Model merge needs the equivalent: not just any earlier snapshot, but the specific version of each individual entity that both sides last agreed on, tracked per entity rather than per whole repository, because in a long-lived shared model different entities are edited on wildly different schedules and a single global "last common snapshot" would be a poor proxy for what actually needs comparing entity by entity. CelinQ tracks a version per entity and requires every change to declare the base version it was made against, so that the server can classify each incoming change precisely: sequential, if nothing else happened to that entity since the declared base, or genuinely concurrent, if it did — triggering an actual three-way comparison against the recorded base state rather than a guess at what the base might have been.

Trees, graphs, sets and maps: each needs its own merge logic

A further way model merge diverges from text merge is that a model is not one kind of structure but several, layered together, and each needs its own reasoning about what "compatible" means. The package hierarchy is a tree: moving an element to a new parent is a change to one field, and it combines cleanly with an unrelated property change on the same element, while two people moving the same element to two different new parents is a genuine structural disagreement about where it belongs. Relationships form a graph: a connector's two endpoints are references to other entities, and reassigning one is a topological change distinct from any scalar property change on the connector itself. Tagged values are maps: independent key-value entries where two additions under different keys are trivially compatible and only a genuine collision on the identical key is a real conflict. And a diagram is a layout of sets of positioned objects, where geometry decomposes further into translation and size components that can commute rather than collide. None of these four structures behaves like the others, and a merge approach that reduces all of them to "did the underlying text differ" throws away exactly the structural knowledge that would let it merge each one intelligently on its own terms.

A worked comparison: what each approach actually produces

It is worth tracing a single divergence through both approaches to see exactly where they part company. Suppose two architects start from the same shared package: a business capability model containing a dozen elements and a handful of connectors. One architect reorganises the package, moving three capabilities under a new sub-package to reflect a decision made in a workshop, and renames one of them to match updated terminology. The other, working from the same starting point without knowing about the reorganisation, adds two new connectors from an existing capability to a newly created supporting capability, and updates a tagged value on an unrelated element elsewhere in the package.

Serialise both sides to text and diff them, and the result is a large, tangled block of differences: the reorganisation shifts where a great many elements appear in the serialised ordering, which makes even the unrelated tagged-value change and the new connectors show up buried inside a much larger apparent rewrite of the file, because a text diff has no way to separate "this moved" from "this changed" from "everything after this point in the file shifted position as a side effect." A human presented with that diff has to do real work just to figure out what actually happened, before they can even begin deciding whether anything genuinely conflicts.

Run the same divergence through entity-aware, atom-level comparison, and the picture is completely different: a tree-parent change on three specific, stably identified elements; a name change on one of them; two new connector entities with their endpoints recorded explicitly; a single tagged-value key changed on one unrelated element. Four or five atomic, individually labelled operations, none of which touch the same atom as any of the others. Fusion applies all of them, because there is no overlap anywhere in this list — not because the changes happen to be small, but because entity-aware comparison can see, precisely, that they are independent, where a text diff could only see that a large stretch of serialised output had changed and leave the actual question of independence for a human to work out by hand.

Where human semantic disagreement still belongs

None of this machinery, however carefully built, is trying to eliminate the moments where people genuinely disagree about what a model should say. Two architects who each believe, in good faith, that an element should carry a different name reflecting a different understanding of its role are having a real conversation about the architecture, not a technical collision that better tooling could dissolve. What model-aware merge does is narrow the set of moments that reach a person down to exactly these — genuine, substantive disagreements — by correctly and automatically absorbing the much larger number of concurrent edits that were never really in tension. This distinction, and the specific mechanics of how CelinQ tells the two apart deterministically, is the subject of the pillar article on conflict resolution, and it is worth reading as the natural next step from the structural argument made here: this piece explains why models cannot be diffed as text; that one explains, case by case, what happens once you diff them correctly instead.

Model integrity checks: the guarantee text merge never had to offer

There is one more respect in which model merge has to do more than its source-code ancestor. A text merge tool that combines two non-overlapping edits produces a file that is, by construction, syntactically valid — it is still just lines of text, arranged in an order a subsequent compiler will either accept or reject on its own terms; the merge tool's job ends at producing the combined text. A model merge cannot stop there, because a model carries structural invariants a naive combination could silently violate: a relationship's endpoints have to still exist, a package cannot end up as its own ancestor, an identifier has to remain unique. CelinQ addresses this with a shadow validation pass that runs before any merged state is actually committed — checking that referenced parents and endpoints still exist, that no containment cycles have been introduced, that identity remains unique — and a proposed state that fails this check does not get applied as if it were fine. It becomes its own kind of capsule, flagged for a person, rather than quietly corrupting the model's structural integrity in the name of resolving a conflict automatically. This is a guarantee text-based merge tools were never asked to provide, because a text file has no structural invariants of its own to violate in the first place; a model does, and a trustworthy model merge has to check for them explicitly rather than assume combining two valid states always yields a third valid state.

The right way to describe what CelinQ does is not "Git for Enterprise Architect models," even though the phrase is tempting shorthand and gestures at something true. It is closer to: the same disciplined three-way comparison that made source-code collaboration trustworthy, rebuilt from scratch around what a model actually is — a graph of stably identified entities, not a sequence of lines — with a validation step text merge never needed, because text never had structural rules of its own to break.

The honest limits

This approach asks more of the underlying engineering than a line-based tool ever had to deliver, and it is worth being clear about where that additional complexity leaves real edges. Every distinct kind of model content — scalar fields, tagged-value maps, tree parentage, connector endpoints, diagram geometry — has to be deliberately modelled and merged on its own terms rather than falling out for free from one generic textual comparison, and that is genuinely more work to build and to test than pointing a diff tool at a serialised export. Data that predates a repository's connection to this machinery, with no recorded causal base for its entities, cannot be merged with the same precision as data whose full history has been captured from the start; CelinQ treats such cases conservatively, flagging them for a person rather than guessing at a base that was never recorded, which is the safer failure mode but still a real limitation worth naming rather than glossing over. And concurrent pushes are today reconciled pairwise against the current canonical head rather than through a single simultaneous many-way comparison — correctness and convergence hold at the scale this has actually been tested against, but a genuinely simultaneous N-way visual diff across a very large number of concurrently active authors remains a harder problem than the underlying merge logic itself, and is honestly future work rather than a shipped capability today.

It is also worth naming what this approach deliberately does not attempt: a generic, model-agnostic merge algorithm that works the same way regardless of what kind of tool produced the data. The specific set of atoms recognised here — scalar fields with particular names, tagged-value maps, EA's own tree and diagram conventions — reflects Enterprise Architect's actual data model, learned and encoded deliberately rather than derived automatically from some universal theory of "what a model is." A different modelling tool with a different underlying structure would need its own, similarly deliberate mapping from its own concepts to a set of atoms suited to it; nothing here transfers automatically just because the word "model" is common to both. That specificity is a cost, in the sense that it is not a one-time investment that generalises for free to every possible modelling format, but it is also exactly what makes the resulting merges trustworthy rather than approximate — a generic merge algorithm that tried to work across arbitrary structured data without understanding what any particular field actually meant would end up rediscovering the same textual-diff weaknesses this article has spent its length explaining, just with a more sophisticated-looking wrapper around them.

None of that undermines the central claim, which is narrower and more defensible than "models merge exactly like code": models can be merged with the same rigour and the same three-way discipline that source code has enjoyed for decades, provided the unit of comparison is rebuilt to match what a model actually is rather than borrowed wholesale from a format models were never structured like in the first place. Treating a model as text gets you a tool that looks like a merge tool and behaves like a coin flip with extra steps. Treating a model as a model — entities, atoms, trees, graphs, maps, and geometry, each reasoned about on its own terms — is what it actually takes to get the outcome architects envied in that developer's ninety-second merge in the first place.