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; 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 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 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}