//! `git yrs-merge refresh` — update stale .yrs/ sidecars after a merge. //! //! Designed to run as a post-merge hook. After git auto-merges files //! (without invoking the merge driver), the .yrs/ sidecars may be stale. //! This command reads the merged content and regenerates sidecars. use std::fs; use std::io; use std::path::Path; use std::process::Command; use crate::sidecar; /// Run `git yrs-merge refresh`. pub fn run(verbose: bool) -> io::Result<()> { // Check if .yrs/ directory exists if !Path::new(".yrs").exists() { if verbose { eprintln!("git-yrs-merge: no .yrs/ directory, nothing to refresh"); } return Ok(()); } // Get list of files changed in the merge let changed_files = get_changed_files()?; if changed_files.is_empty() { if verbose { eprintln!("git-yrs-merge: no changed files to refresh"); } return Ok(()); } let mut refreshed = 0; for file_path in &changed_files { let sidecar_path = sidecar::sidecar_path(file_path); // Only refresh files that have an existing sidecar if !sidecar_path.exists() { continue; } // Read current content let content = match fs::read_to_string(file_path) { Ok(c) => c, Err(_) => continue, // file might have been deleted }; // Check if sidecar is stale let sidecar_data = match fs::read(&sidecar_path) { Ok(d) => d, Err(_) => continue, }; if sidecar::validate_sidecar(&sidecar_data, &content) { continue; // sidecar is fine } // Regenerate sidecar from current content let new_sidecar = sidecar::create_sidecar_from_text(&content); sidecar::write_sidecar(file_path, &new_sidecar)?; if verbose { eprintln!("git-yrs-merge: refreshed sidecar for {}", file_path); } refreshed += 1; } // Stage refreshed sidecars if refreshed > 0 { let output = Command::new("git") .args(["add", ".yrs/"]) .output()?; if !output.status.success() { eprintln!( "git-yrs-merge: warning: failed to stage .yrs/: {}", String::from_utf8_lossy(&output.stderr) ); } eprintln!("git-yrs-merge: refreshed {} sidecar(s)", refreshed); } else if verbose { eprintln!("git-yrs-merge: all sidecars are up to date"); } Ok(()) } /// Get the list of files changed in the most recent merge/pull. fn get_changed_files() -> io::Result> { // Try using MERGE_HEAD first (during merge) // Fall back to comparing HEAD with HEAD@{1} (after merge) let output = Command::new("git") .args(["diff", "--name-only", "HEAD@{1}", "HEAD"]) .output()?; if output.status.success() { let files: Vec = String::from_utf8_lossy(&output.stdout) .lines() .filter(|l| !l.is_empty() && !l.starts_with(".yrs/")) .map(|l| l.to_string()) .collect(); Ok(files) } else { // Fallback: list all tracked files (less efficient but always works) let output = Command::new("git") .args(["ls-files"]) .output()?; if output.status.success() { let files: Vec = String::from_utf8_lossy(&output.stdout) .lines() .filter(|l| !l.is_empty() && !l.starts_with(".yrs/")) .map(|l| l.to_string()) .collect(); Ok(files) } else { Ok(Vec::new()) } } }