Semantic Merge
What It Is
Suture's semantic merge understands the internal structure of your files. Instead of comparing files line-by-line (like Git), Suture uses format-aware drivers that parse file structure and detect changes at the logical level -- keys, elements, rows, slides, cells.
Two patches conflict only when they modify the same logical address. Changing different JSON keys, different CSV rows, or different slides in a PPTX never conflicts.
How It Works
- A driver parses the file into a structured intermediate representation
- Changes are mapped to logical addresses (e.g.,
database/host,sheet1/A3) - The patch algebra checks for touch set overlap -- only overlapping addresses cause conflicts
- When merging, the driver reconstructs the file from both sides' changes
If no driver matches a file's extension, Suture falls back to line-based merge (same behavior as Git).
Supported Formats
.jsonsuture-driver-json.yaml, .ymlsuture-driver-yaml.tomlsuture-driver-toml.csvsuture-driver-csv.xmlsuture-driver-xml.mdsuture-driver-markdown.sqlsuture-driver-sql.docxsuture-driver-docx.xlsxsuture-driver-xlsx.pptxsuture-driver-pptx.pdfsuture-driver-pdf.png .jpg .gif .bmp .webp .tiff .ico .avifsuture-driver-image.otiosuture-driver-otioExample: JSON Config Merge
Two developers edit config.json independently:
Base (committed on main):
{
"database": {
"host": "localhost",
"port": 5432
},
"logging": {
"level": "info"
}
}
Alice (on branch feature/logging) changes logging.level:
{
"database": {
"host": "localhost",
"port": 5432
},
"logging": {
"level": "debug"
}
}
Bob (on main) changes database.port:
{
"database": {
"host": "localhost",
"port": 3306
},
"logging": {
"level": "info"
}
}
suture merge feature/logging produces:
{
"database": {
"host": "localhost",
"port": 3306
},
"logging": {
"level": "debug"
}
}
Both changes applied. No conflict.
Contrast with Git
Git treats files as opaque text. When both sides modify the same region (even different keys on adjacent lines), Git inserts conflict markers:
<<<<<<< HEAD
"port": 3306
=======
"level": "debug"
>>>>>>> feature/logging
Suture knows these are different JSON keys (database/port vs logging/level) and merges them automatically.
Standalone File Merge
You can use Suture's semantic merge outside of a repository:
suture merge-file base.json ours.json theirs.json
suture merge-file --driver yaml -o merged.yaml base.yaml ours.yaml theirs.yaml
The --driver flag selects a specific driver. If omitted, Suture auto-detects by file extension.
Writing a Custom Driver
Implement the SutureDriver trait to add support for a new format:
- Create a crate:
crates/suture-driver-<format>/ - Depend on
suture-driverand implementSutureDriver - Implement
diff()to produceSemanticChangevalues between two file versions - Implement
format_diff()for human-readable output - Implement
merge()for three-way semantic merge (returnNoneon genuine conflict) - Return supported extensions from
supported_extensions()
See crates/suture-driver-example/ for a minimal working driver.
Element ID Convention
Use slash-delimited paths that reflect the document hierarchy:
database/host, logging/levelworkbook/sheet/rows/3/cols/Adoc/body/paragraphs/5/runs/2timeline/track/clip, timeline/stackCommutativity
Two patches commute when their touch sets are disjoint. The driver only needs to compute correct touch sets -- Suture's patch algebra handles the rest.
Fallback
Files without a matching driver use line-based diff and merge, identical to Git's behavior. This covers source code (.rs, .py, .js, etc.) and any other text format.