use std::io::Write;
use std::path::Path;
use weave_cli::{gitscan, repo_scope, worktree};
type R<T> = Result<T, Box<dyn std::error::Error>>;
pub(crate) struct Args<'a> {
pub base: Option<&'a str>,
pub ours: Option<&'a str>,
pub theirs: Option<&'a str>,
pub base_dir: Option<&'a str>,
pub ours_dir: Option<&'a str>,
pub theirs_dir: Option<&'a str>,
pub json: bool,
}
pub(crate) fn run(args: Args<'_>) -> R<()> {
let dir_mode = args.base_dir.is_some() || args.ours_dir.is_some() || args.theirs_dir.is_some();
let rev_mode = args.base.is_some() || args.ours.is_some() || args.theirs.is_some();
if !dir_mode && !rev_mode {
return working_tree(args.json);
}
let (base, ours, theirs) = if dir_mode {
match (args.base_dir, args.ours_dir, args.theirs_dir) {
(Some(b), Some(o), Some(t)) => (
repo_scope::read_dir_tree(Path::new(b))?,
repo_scope::read_dir_tree(Path::new(o))?,
repo_scope::read_dir_tree(Path::new(t))?,
),
_ => {
return Err(
"directory mode needs all three of --base-dir, --ours-dir, --theirs-dir".into(),
)
}
}
} else {
gitscan::trees(Path::new("."), args.base, args.ours, args.theirs)?
};
let docs = repo_scope::check(&base, &ours, &theirs);
let n = repo_scope::total_findings(&docs);
let mut out = std::io::stdout();
writeln!(out, "{}", serde_json::to_string_pretty(&docs)?)?;
if docs.is_empty() {
eprintln!(
"weave check (repo scope): no cross-file binding findings between these two \
revisions. That is a statement about DANGLING and SHADOW only — it is not a \
verification of any file's contents. For that, run `weave check` with no \
arguments against the working tree."
);
}
out.flush()?;
if n > 0 {
eprintln!(
"weave check: {} finding(s) across {} file(s)",
n,
docs.len()
);
std::process::exit(1);
}
Ok(())
}
fn working_tree(json: bool) -> R<()> {
let dir = Path::new(".");
let Some(scope) = gitscan::merge_scope(dir)? else {
println!(
"weave check: no merge in progress (no MERGE_HEAD) and HEAD is not a merge \
commit, so there is no three-way context to verify a resolution against. \
NOTHING WAS CHECKED — this is not a clean bill of health."
);
return Ok(());
};
let verdicts = worktree::check(
&scope.base,
&scope.ours,
&scope.theirs,
&scope.work,
&scope.subjects,
);
let report = worktree::Report {
scope: scope.scope,
verdicts,
};
if json {
let payload = serde_json::json!({
"scope": report.scope,
"files": report.verdicts.iter().map(|v| serde_json::json!({
"file": v.file,
"ok": v.ok(),
"verdict": v.line(),
"findings": v.findings.iter().map(|f| serde_json::json!({
"class": f.class,
"detail": f.detail,
"suggestion": f.suggestion,
})).collect::<Vec<_>>(),
})).collect::<Vec<_>>(),
});
println!("{}", serde_json::to_string_pretty(&payload)?);
} else {
print!("{}", report.render());
}
if report.tally().2 > 0 {
std::process::exit(1);
}
Ok(())
}