use std::fs;
use std::path::Path;
use std::process::Command;
use colored::Colorize;
const SUPPORTED_EXTENSIONS: &[&str] = &[
"*.ts", "*.tsx", "*.js", "*.mjs", "*.cjs", "*.jsx", "*.py", "*.go", "*.rs",
"*.java", "*.c", "*.h", "*.cpp", "*.cc", "*.cxx", "*.hpp", "*.hh", "*.hxx",
"*.rb", "*.cs", "*.php", "*.swift", "*.ex", "*.exs", "*.sh",
"*.f90", "*.f95", "*.f03", "*.f08",
"*.xml", "*.plist", "*.svg", "*.csproj", "*.fsproj", "*.vbproj",
"*.json", "*.yaml", "*.yml", "*.toml", "*.md",
];
pub fn run(driver_path: Option<&str>) -> Result<(), Box<dyn std::error::Error>> {
let git_dir = Path::new(".git");
if !git_dir.exists() {
return Err("Not in a git repository. Run `weave setup` from the repo root.".into());
}
let driver = if let Some(p) = driver_path {
p.to_string()
} else {
which_driver()?
};
if !Path::new(&driver).exists() && Command::new("which").arg(&driver).output()
.map(|o| !o.status.success())
.unwrap_or(true)
{
eprintln!(
"{} Driver binary '{}' not found. Build with `cargo build --release` first.",
"warning:".yellow().bold(),
driver
);
}
let driver_cmd = format!("{} %O %A %B %L %P", driver);
let status = Command::new("git")
.args(["config", "merge.weave.name", "Entity-level semantic merge"])
.status()?;
if !status.success() {
return Err("Failed to set merge.weave.name".into());
}
let status = Command::new("git")
.args(["config", "merge.weave.driver", &driver_cmd])
.status()?;
if !status.success() {
return Err("Failed to set merge.weave.driver".into());
}
println!(
"{} Configured git merge driver: {}",
"✓".green().bold(),
driver_cmd
);
let gitattributes_path = Path::new(".gitattributes");
let mut existing = if gitattributes_path.exists() {
fs::read_to_string(gitattributes_path)?
} else {
String::new()
};
let mut added = 0;
for ext in SUPPORTED_EXTENSIONS {
let pattern = format!("{} merge=weave", ext);
if !existing.contains(&pattern) {
if !existing.is_empty() && !existing.ends_with('\n') {
existing.push('\n');
}
existing.push_str(&pattern);
existing.push('\n');
added += 1;
}
}
if added > 0 {
fs::write(gitattributes_path, &existing)?;
println!(
"{} Updated .gitattributes ({} patterns added)",
"✓".green().bold(),
added
);
} else {
println!(
"{} .gitattributes already configured",
"✓".green().bold(),
);
}
println!(
"\n{} Weave is ready. Merge conflicts will now be resolved at the entity level.",
"Done!".green().bold()
);
Ok(())
}
pub fn unsetup() -> Result<(), Box<dyn std::error::Error>> {
let git_dir = Path::new(".git");
if !git_dir.exists() {
return Err("Not in a git repository. Run `weave unsetup` from the repo root.".into());
}
let _ = Command::new("git")
.args(["config", "--remove-section", "merge.weave"])
.status();
println!(
"{} Removed weave merge driver from git config",
"✓".green().bold()
);
let gitattributes_path = Path::new(".gitattributes");
if gitattributes_path.exists() {
let content = fs::read_to_string(gitattributes_path)?;
let filtered: Vec<&str> = content
.lines()
.filter(|line| !line.contains("merge=weave"))
.collect();
let new_content = filtered.join("\n");
if filtered.is_empty() || new_content.trim().is_empty() {
fs::remove_file(gitattributes_path)?;
println!(
"{} Removed .gitattributes (was only weave patterns)",
"✓".green().bold()
);
} else {
let mut out = new_content;
if !out.ends_with('\n') {
out.push('\n');
}
fs::write(gitattributes_path, out)?;
println!(
"{} Cleaned weave patterns from .gitattributes",
"✓".green().bold()
);
}
}
println!(
"\n{} Weave has been removed. Git will use its default merge strategy.",
"Done!".green().bold()
);
Ok(())
}
fn which_driver() -> Result<String, Box<dyn std::error::Error>> {
if let Ok(exe) = std::env::current_exe() {
if let Some(dir) = exe.parent() {
let candidate = dir.join("weave-driver");
if candidate.exists() {
return Ok(candidate.to_string_lossy().to_string());
}
}
}
if let Ok(output) = Command::new("which").arg("weave-driver").output() {
if output.status.success() {
let path = String::from_utf8_lossy(&output.stdout).trim().to_string();
if !path.is_empty() {
return Ok(path);
}
}
}
Ok("weave-driver".to_string())
}