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 build_status(git_ops: &dyn GitOps) -> Result<StatusOutput> {
16 let annotated =
17 git_ops
18 .list_annotated_commits(10000)
19 .map_err(|e| crate::error::ChronicleError::Git {
20 source: e,
21 location: snafu::Location::default(),
22 })?;
23 let annotated_set: std::collections::HashSet<_> = annotated.iter().collect();
24
25 let recent_shas = get_recent_commits_dyn(git_ops, 20)?;
26 let recent_count = recent_shas.len();
27
28 let mut annotated_count = 0;
29 let mut unannotated = Vec::new();
30 for sha in &recent_shas {
31 if annotated_set.contains(sha) {
32 annotated_count += 1;
33 } else {
34 unannotated.push(sha.clone());
35 }
36 }
37
38 let coverage = if recent_count > 0 {
39 (annotated_count as f64 / recent_count as f64) * 100.0
40 } else {
41 0.0
42 };
43
44 Ok(StatusOutput {
45 total_annotations: annotated.len(),
46 recent_commits: recent_count,
47 recent_annotated: annotated_count,
48 coverage_pct: (coverage * 10.0).round() / 10.0,
49 unannotated_commits: unannotated,
50 })
51}
52
53pub fn run(format: String) -> Result<()> {
54 let _ = format; let repo_dir = std::env::current_dir().map_err(|e| crate::error::ChronicleError::Io {
56 source: e,
57 location: snafu::Location::default(),
58 })?;
59 let git_ops = CliOps::new(repo_dir);
60
61 let output = build_status(&git_ops)?;
62
63 let json =
64 serde_json::to_string_pretty(&output).map_err(|e| crate::error::ChronicleError::Json {
65 source: e,
66 location: snafu::Location::default(),
67 })?;
68 println!("{json}");
69
70 Ok(())
71}
72
73fn get_recent_commits_dyn(git_ops: &dyn GitOps, count: usize) -> Result<Vec<String>> {
76 let repo_dir = std::env::current_dir().map_err(|e| crate::error::ChronicleError::Io {
82 source: e,
83 location: snafu::Location::default(),
84 })?;
85
86 let _head = git_ops
88 .resolve_ref("HEAD")
89 .map_err(|e| crate::error::ChronicleError::Git {
90 source: e,
91 location: snafu::Location::default(),
92 })?;
93
94 let output = std::process::Command::new("git")
95 .args(["log", "--format=%H", &format!("-{count}")])
96 .current_dir(&repo_dir)
97 .output()
98 .map_err(|e| crate::error::ChronicleError::Io {
99 source: e,
100 location: snafu::Location::default(),
101 })?;
102
103 let stdout = String::from_utf8_lossy(&output.stdout);
104 Ok(stdout
105 .lines()
106 .filter(|l| !l.is_empty())
107 .map(|s| s.to_string())
108 .collect())
109}