//! Git merge driver using Yrs CRDTs. //! //! Two merge paths: //! - **Path A (lossless)**: When `.yrs/` sidecar files exist for both sides, //! load the Yrs Docs and perform a true CRDT merge. //! - **Path B (diff-based)**: When sidecars are missing or stale, reconstruct //! edits from text diffs and merge via Yrs operations. pub mod crdt_merge; pub mod diff_merge; pub mod init; pub mod refresh; pub mod sidecar; use std::fs; use std::io; /// Merge three versions of a file using Yrs CRDT. /// /// This is the main entry point called by the git merge driver. /// Writes the merged result to `ours_path` (git convention). pub fn merge_files( base_path: &str, ours_path: &str, theirs_path: &str, file_path: &str, sidecar_mode: bool, verbose: bool, ) -> io::Result<()> { // Try Path A (sidecar) if enabled if sidecar_mode && !file_path.is_empty() { match crdt_merge::try_sidecar_merge(ours_path, theirs_path, file_path, verbose) { Ok(Some(merged)) => { fs::write(ours_path, &merged)?; return Ok(()); } Ok(None) => { // Sidecars not available or stale — fall through to Path B if verbose { eprintln!("git-yrs-merge: sidecar not available, falling back to diff-based merge"); } } Err(e) => { if verbose { eprintln!("git-yrs-merge: sidecar merge failed ({}), falling back to diff-based", e); } } } } // Path B: diff-based merge let base = fs::read_to_string(base_path)?; let ours = fs::read_to_string(ours_path)?; let theirs = fs::read_to_string(theirs_path)?; if verbose { eprintln!("git-yrs-merge: using diff-based merge for {}", file_path); } let (merged, new_sidecar) = diff_merge::merge(&base, &ours, &theirs); fs::write(ours_path, &merged)?; // If sidecar mode is enabled, write the new sidecar so future merges can use Path A if sidecar_mode && !file_path.is_empty() { if let Err(e) = sidecar::write_sidecar(file_path, &new_sidecar) { if verbose { eprintln!("git-yrs-merge: failed to write sidecar: {}", e); } } } Ok(()) }