Skip to content

3-Way AST Merge Driver

symtrace merge-driver integrates directly into Git as a native 3-way AST semantic merge driver. Standard line-based git merge often flags false-positive conflicts when one branch re-formats code, reorders functions, or moves structs while another branch adds logic inside them. symtrace merge-driver parses the Concrete Syntax Trees of Base (%O), Ours (%A), and Theirs (%B) to resolve non-conflicting structural changes automatically with zero merge conflict markers and validates syntax integrity via AST scope splicing and re-parsing.

Configure symtrace as your global or repository-specific Git merge driver:

Terminal window
# Register symtrace merge driver command
git config merge.symtrace.name "symtrace 3-way AST semantic merge driver"
git config merge.symtrace.driver "symtrace merge-driver %O %A %B %P"

Add file associations to your repository’s .gitattributes:

*.rs merge=symtrace
*.js merge=symtrace
*.ts merge=symtrace
*.tsx merge=symtrace
*.py merge=symtrace
*.java merge=symtrace
*.cs merge=symtrace
*.go merge=symtrace
*.c merge=symtrace
*.cpp merge=symtrace
*.rb merge=symtrace
*.php merge=symtrace
*.json merge=symtrace

When a Git conflict occurs during git merge or git rebase, Git executes symtrace merge-driver passing four arguments:

  1. %O - Ancestor/Base blob temporary file path
  2. %A - Current branch (“Ours”) temporary file path
  3. %B - Incoming branch (“Theirs”) temporary file path
  4. %P - Target relative file path in the repository
Base AST (%O)
/ \
/ \
Ours AST (%A) Theirs AST (%B)
[Reformatted] [Added Function Logic]
\ /
\ /
3-Way AST Merge Engine
AST Scope Splicing
Tree-sitter Validation Re-parse (has_ast_errors())
Merged Clean File (%A)
(Zero Conflict Markers!)

1. Automatic Scope Splicing & Validation Re-parse

Section titled “1. Automatic Scope Splicing & Validation Re-parse”

If Branch A reformats code, renames a variable, or moves a function block, and Branch B independently adds logic inside another function, symtrace merge-driver splices the AST scopes cleanly. Before writing the output, it performs a validation re-parse (has_ast_errors()). If any syntax corruption or broken scope is detected, it automatically falls back to standard conflict markers safely.

For genuine logic conflicts (e.g., both branches modify the same AST statement differently), symtrace merge-driver writes clear, AST-annotated conflict headers:

<<<<<<< Ours: fn process_user [Renamed 'id' -> 'user_id']
let account = fetch_user(user_id);
||||||| Base
let user = fetch_user(id);
=======
let account = fetch_user_by_uuid(id);
>>>>>>> Theirs: fn process_user [Modified logic]

Configure merge resolution behavior in .symtracerc or symtrace.toml:

[merge]
auto_resolve_formatting = true # Resolve whitespace/formatting conflicts automatically
auto_resolve_reorders = true # Resolve reordered function/struct definitions
fallback_to_text = true # Fallback to standard 3-way text diff if AST parse fails
annotate_semantic_conflicts = true # Add AST entity context to conflict headers

You can test 3-way merge resolution manually on temporary files:

Terminal window
symtrace merge-driver base.rs ours.rs theirs.rs target_path.rs

Returns exit code 0 on clean merge, or exit code 1 if conflict markers were written for manual review.