The conflict is not always where the disagreement is.
A plain text merge can treat two safe edits as a collision because both edits happened near the same lines. That is painful in generated-ish metadata, unpacked solution files, YAML manifests, templated files, and other source formats where order, identity, and nesting carry meaning.
Meridian changes the question. Instead of asking whether nearby lines changed, it asks which tree node changed, how that node is identified, and whether sibling order is actually semantic for that parent.
<<<<<<< ours (current change)<Product sku="atlas" status="preview"> <Name>Atlas Sensor Pro</Name></Product><Product sku="beacon" status="active"> ...||||||| base<Product sku="atlas" status="active"> ...<Product sku="beacon" status="active"> ...=======<Product sku="atlas" status="active"> ...<Product sku="beacon" status="active"> <Label languagecode="en">Beacon Control Hub</Label></Product><Product sku="caldera" status="active"> ...>>>>>>> theirs (incoming change)Small at the center, semantic at the edges.
Format adapters own physical syntax. Schemas own merge-relevant semantics. The merge engine works over trees and refuses to invent identity when the repository has not supplied enough information.
The merge above was made possible by this schema.
No model guesses what sku means. A repository commits one small, readable schema next to catalog.xml, and every merge and diff reads it.
$schema: …/schemas/meridian.schema.jsonschemaVersion: 0.1name: catalog defaults: globalDiscriminatorFields: - id - languagecode files: - match: catalog.xml root: Catalog discriminators: - path: Catalog/Products/Product key: attribute: sku - path: Catalog/DisplayOrder/ProductRef key: attribute: sku orderedChildren: - Catalog/DisplayOrderSibling Label nodes are told apart by languagecode, so the en and fr labels merge as separate slots.
atlas, beacon, and caldera are matched by sku — one side's edit to atlas can never collide with the other's edit to beacon.
Display references are aligned by the product they point at, not by their position in the list.
Order matters here only. The one-sided append of caldera is accepted; two sides reordering it differently would be reported as a conflict, never silently sorted.
Every format is a plugin.
The core knows nothing about syntax. Each format is an adapter that implements one small contract — recognise a file, parse its text into a structural tree, and render a tree back to text, preserving the original formatting wherever it can.
Adapters are discovered at runtime, so support is additive. Drop in an adapter for a proprietary or in-house format and it takes part in structural merge, semantic diff, and recursive nesting exactly like the formats that ship in the box — no changes to the engine.
Built-in adapters ship as grouped provider packages — MeridianGit.Formats.Markup, .Web, .Images, .PowerPlatform, and .Binary. Your own adapter just implements MeridianGit.Abstractions.
Shipped as provider bundles
GroupedMeridianGit.Formats.* packages — install only what a repo needs.
Markup
Structured config and data, matched by element id, member name, or a schema discriminator.
Web
Front-end source merged by element, by selector and property, and by top-level declaration.
Power Platform
ProPower Platform solution formats, and ready-made Meridian schemas for the platform. A paid provider bundle.
Contact for access & pricingBinary
Byte-safe whole-file providers. Compared by exact content — a both-sides change is a conflict, never a structural merge.
Bring your own
Implement the adapter contract and your format merges like everything else.
A value can be a whole document.
Real source files smuggle one format inside another — a JSON payload in an XML attribute, an HTML fragment in JSON, an escaped JSON string nested inside that. To a line merge it is one opaque blob. To Meridian it is a tree with more trees inside it.
When an adapter recognises a scalar as another format, it parses that payload as its own document — under its own schema — and merges it at that depth. The clean result is re-embedded through the parent adapter, which owns the quoting and escaping, so encoding stays correct all the way back up.
Schemas compose the same way. An outer schema points an inner payload at the schema that governs it, and Meridian traverses that chain — each level keeping its own identity and ordering rules.
Constraints that keep the merge honest.
The tool is intentionally conservative. It exists to preserve semantic intent across source-controlled files, not to smooth over uncertainty by guessing.
Identity is local
Identity means "inside this parent, siblings with this node name are matched by this value." It is not global uniqueness, and ambiguity is a configuration error.
Order is opt-in
Sibling order is ignored by default once nodes have identities. Declare orderedChildren only where sequence changes meaning.
Nested content stays nested
JSON inside XML, HTML inside JSON, and similar scalar payloads can be parsed as their own tree and re-embedded with the parent adapter handling escaping.
Consumers own domain semantics
Power Platform, product metadata, and repository-specific rules belong in *.meridian.yaml files or wrapper tooling, not in Meridian core.
Install it and wire it into Git.
Install the meridian CLI, register it as a Git merge and diff driver, and commit your schema files alongside the source they describe.
1 · Install the CLI
Add Meridian as a global .NET tool — the meridian command lands on your PATH.
dotnet tool install --global MeridianGitmeridian --version2 · Run a merge by hand
Invoke a three-way structural merge directly — handy for trying a schema before wiring it into Git.
meridian merge \ --base base.xml \ --ours ours.xml \ --theirs theirs.xml \ --path catalog.xml3 · Wire it into Git
Register the merge and diff driver, then opt files in so every clone merges and diffs structurally.
[merge "meridian"] name = Meridian structural merge driver = meridian merge \ --base %O --ours %A --theirs %B --path %P *.config merge=meridian diff=meridian*.xml merge=meridian diff=meridian*.json merge=meridian diff=meridian4 · Minimal schema shape
Schemas describe merge-relevant facts: identity, ordering, nested content, companion payloads, and aliases.
schemaVersion: 0.1name: dotnet-config defaults: globalDiscriminatorFields: - key - name files: - match: "*.config" discriminators: - path: configuration/appSettings/add key: {attribute: key} orderedChildren: - configuration/system.webServer/handlers