use std::path::Path;
use crate::cli::commands::{GitAction, GitCmd};
use crate::ops::git_setup::{self, SetupReport, StepStatus};
pub fn cmd_git(args: GitCmd, json: bool) -> Result<(), Box<dyn std::error::Error>> {
match args.action {
GitAction::Setup(setup_args) => {
let root = super::discover_project_root()?;
let frame_rel = frame_rel_for(&root);
let report = git_setup::run(&root, &frame_rel, setup_args.dry_run);
if json {
print_json(&report, setup_args.dry_run);
} else {
print_human(&report, setup_args.dry_run);
}
if report.failed() {
return Err("some steps could not be applied".into());
}
Ok(())
}
}
}
fn frame_rel_for(root: &Path) -> String {
let frame_dir = root.join("frame");
let Some(paths) = crate::io::git::repo_paths(&frame_dir) else {
return "frame".to_string();
};
frame_dir
.canonicalize()
.ok()
.and_then(|abs| {
abs.strip_prefix(&paths.toplevel)
.ok()
.map(|p| p.to_path_buf())
})
.map(|rel| rel.to_string_lossy().replace('\\', "/"))
.unwrap_or_else(|| "frame".to_string())
}
fn print_human(report: &SetupReport, dry_run: bool) {
if !report.in_git {
println!("not a git repository — nothing to configure");
return;
}
for step in &report.steps {
match &step.status {
StepStatus::AlreadyCorrect => println!(" ok {}", step.name),
StepStatus::Changed => {
println!(
" {} {}",
if dry_run { "would " } else { "set " },
step.name
);
for line in &step.detail {
println!(" {line}");
}
}
StepStatus::Failed(why) => println!(" failed {} — {}", step.name, why),
}
}
if report.fr_not_on_path {
println!();
println!("warning: `fr` is not on PATH, so git cannot run the merge driver.");
println!(" install it, or set merge.frame.driver to an absolute path.");
}
println!();
if dry_run {
if report.changed_anything() {
println!("dry run — nothing was written. Re-run without --dry-run to apply.");
} else {
println!("already configured — nothing to do.");
}
} else if report.changed_anything() {
println!("configured. Commit .gitignore and .gitattributes;");
println!("the merge driver is per-clone, so each clone runs `fr git setup` once.");
} else {
println!("already configured — nothing to do.");
}
}
fn print_json(report: &SetupReport, dry_run: bool) {
let steps: Vec<serde_json::Value> = report
.steps
.iter()
.map(|step| {
let (status, error) = match &step.status {
StepStatus::AlreadyCorrect => ("already_correct", None),
StepStatus::Changed => ("changed", None),
StepStatus::Failed(why) => ("failed", Some(why.clone())),
};
serde_json::json!({
"name": step.name,
"status": status,
"detail": step.detail,
"error": error,
})
})
.collect();
let out = serde_json::json!({
"in_git": report.in_git,
"dry_run": dry_run,
"changed": report.changed_anything(),
"fr_on_path": !report.fr_not_on_path,
"steps": steps,
});
println!("{}", serde_json::to_string_pretty(&out).unwrap_or_default());
}