gn_cli/commands/
validate.rs1use anyhow::Result;
2use clap::Args;
3use gn_core::{DataValidator, Namespace};
4use std::env;
5
6#[derive(Args, Debug)]
7pub struct ValidateArgs {
8 #[arg(short, long)]
10 pub namespace: Option<String>,
11
12 #[arg(long, default_value_t = false)]
14 pub strict: bool,
15
16 #[arg(long, default_value_t = false)]
18 pub json: bool,
19}
20
21pub fn run(args: &ValidateArgs) -> Result<()> {
22 let repo_path = env::current_dir()?;
23 let validator = DataValidator::new(&repo_path);
24
25 let namespaces = if let Some(ns_str) = &args.namespace {
26 vec![Namespace::from_str(ns_str)]
27 } else {
28 vec![
29 Namespace::Comments,
30 Namespace::Review,
31 Namespace::Todos,
32 ]
33 };
34
35 let report = validator.validate_all(&namespaces)?;
36
37 if args.json {
38 println!("{}", serde_json::to_string_pretty(&report)?);
39 if !report.healthy || (args.strict && report.total_orphans > 0) {
40 std::process::exit(1);
41 }
42 return Ok(());
43 }
44
45 println!("\x1b[1;36m═══════════════════════════════════════════════════════════════════════\x1b[0m");
46 println!("\x1b[1;36m git-notes Data & Refspec Integrity Validator (fsck) \x1b[0m");
47 println!("\x1b[1;36m═══════════════════════════════════════════════════════════════════════\x1b[0m");
48 println!("Repository: \x1b[1m{}\x1b[0m\n", report.repo_root);
49
50 for ref_rep in &report.ref_reports {
51 if !ref_rep.exists {
52 println!(" \x1b[90m○ {:<24} (ref not yet created / 0 notes)\x1b[0m", ref_rep.ref_path);
53 continue;
54 }
55
56 let status_badge = if ref_rep.corrupt_blobs == 0 && ref_rep.unparseable_notes == 0 {
57 if ref_rep.orphan_notes == 0 {
58 "\x1b[32m✔ OK\x1b[0m"
59 } else {
60 "\x1b[33m⚠ ORPHANS\x1b[0m"
61 }
62 } else {
63 "\x1b[31m✗ CORRUPT\x1b[0m"
64 };
65
66 println!(
67 " [{}] \x1b[1m{:<24}\x1b[0m (commit: \x1b[90m{}\x1b[0m | {} note{})",
68 status_badge,
69 ref_rep.ref_path,
70 ref_rep.commit_sha.as_deref().unwrap_or("none").chars().take(8).collect::<String>(),
71 ref_rep.note_count,
72 if ref_rep.note_count == 1 { "" } else { "s" }
73 );
74
75 for issue in &ref_rep.issues {
76 if issue.contains("unreachable") || issue.contains("missing commit") {
77 println!(" \x1b[33m⚠ {}\x1b[0m", issue);
78 } else {
79 println!(" \x1b[31m✗ {}\x1b[0m", issue);
80 }
81 }
82 }
83
84 println!("\n\x1b[1;36m───────────────────────────────────────────────────────────────────────\x1b[0m");
85 println!(
86 "Summary: {} note(s) scanned across {} ref(s). Corrupt: {}, Orphans: {}",
87 report.total_notes,
88 report.total_refs,
89 report.total_corrupt,
90 report.total_orphans
91 );
92
93 if !report.healthy {
94 println!("\x1b[1;31m✗ Data corruption detected in git note references!\x1b[0m");
95 anyhow::bail!("Data validation failed: {} corrupt blob(s) detected.", report.total_corrupt);
96 } else if report.total_orphans > 0 {
97 println!("\x1b[33m💡 Tip: Run 'gn heal' to automatically re-anchor orphan notes after rebases.\x1b[0m");
98 if args.strict {
99 anyhow::bail!("Strict mode: {} orphan note(s) found.", report.total_orphans);
100 }
101 } else {
102 println!("\x1b[1;32m✔ All git-notes refs and JSON schemas are 100% valid and verified.\x1b[0m");
103 }
104
105 Ok(())
106}