Skip to main content

chronicle/cli/
status.rs

1use crate::error::Result;
2use crate::git::{CliOps, GitOps};
3
4#[derive(serde::Serialize)]
5pub struct StatusOutput {
6    pub total_annotations: usize,
7    pub recent_commits: usize,
8    pub recent_annotated: usize,
9    pub coverage_pct: f64,
10    pub unannotated_commits: Vec<String>,
11}
12
13pub fn run(format: String) -> Result<()> {
14    let _ = format; // reserved for future pretty-print support
15    let repo_dir = std::env::current_dir().map_err(|e| crate::error::ChronicleError::Io {
16        source: e,
17        location: snafu::Location::default(),
18    })?;
19    let git_ops = CliOps::new(repo_dir);
20
21    // Get all annotated commits
22    let annotated =
23        git_ops
24            .list_annotated_commits(10000)
25            .map_err(|e| crate::error::ChronicleError::Git {
26                source: e,
27                location: snafu::Location::default(),
28            })?;
29    let annotated_set: std::collections::HashSet<_> = annotated.iter().collect();
30
31    // Get recent commits (last 20 SHAs)
32    let recent_shas = get_recent_commits(&git_ops, 20)?;
33    let recent_count = recent_shas.len();
34
35    let mut annotated_count = 0;
36    let mut unannotated = Vec::new();
37    for sha in &recent_shas {
38        if annotated_set.contains(sha) {
39            annotated_count += 1;
40        } else {
41            unannotated.push(sha.clone());
42        }
43    }
44
45    let coverage = if recent_count > 0 {
46        (annotated_count as f64 / recent_count as f64) * 100.0
47    } else {
48        0.0
49    };
50
51    let output = StatusOutput {
52        total_annotations: annotated.len(),
53        recent_commits: recent_count,
54        recent_annotated: annotated_count,
55        coverage_pct: (coverage * 10.0).round() / 10.0,
56        unannotated_commits: unannotated,
57    };
58
59    let json =
60        serde_json::to_string_pretty(&output).map_err(|e| crate::error::ChronicleError::Json {
61            source: e,
62            location: snafu::Location::default(),
63        })?;
64    println!("{json}");
65
66    Ok(())
67}
68
69fn get_recent_commits(git_ops: &CliOps, count: usize) -> Result<Vec<String>> {
70    let output = std::process::Command::new("git")
71        .args(["log", "--format=%H", &format!("-{count}")])
72        .current_dir(&git_ops.repo_dir)
73        .output()
74        .map_err(|e| crate::error::ChronicleError::Io {
75            source: e,
76            location: snafu::Location::default(),
77        })?;
78
79    let stdout = String::from_utf8_lossy(&output.stdout);
80    Ok(stdout
81        .lines()
82        .filter(|l| !l.is_empty())
83        .map(|s| s.to_string())
84        .collect())
85}