commit_wizard/core/usecases/commit/
check.rs1use std::time::Instant;
2
3use crate::{
4 core::{Context, CoreResult},
5 engine::{
6 capabilities::commit::check::validate_commits,
7 constants::emoji::{ERROR, SUCCESS},
8 error::ErrorCode,
9 models::{git::CommitSummary, policy::commit::CommitModel},
10 },
11};
12
13pub fn run(
14 ctx: &Context,
15 tail: Option<u32>,
16 from: Option<String>,
17 to: Option<String>,
18 full_commit_hash: bool,
19) -> CoreResult<()> {
20 let ui = ctx.ui();
21 let start = Instant::now();
22
23 let format_hash = |hash: &str| {
24 if full_commit_hash {
25 hash.to_string()
26 } else {
27 hash[..8.min(hash.len())].to_string()
28 }
29 };
30
31 let resolved_config = ctx.config().ok_or_else(|| {
33 ErrorCode::ConfigUnreadable
34 .error()
35 .with_context("context", "Config not resolved")
36 })?;
37 let policy = CommitModel::from_config(resolved_config);
38
39 let to_ref = to.as_deref().unwrap_or("HEAD").to_string();
41 let mut raw_commits = ctx.git().list_commits(from.as_deref(), &to_ref)?;
42
43 if let Some(n) = tail {
44 let n = n as usize;
45 if raw_commits.len() > n {
46 raw_commits = raw_commits.into_iter().take(n).collect();
47 }
48 }
49
50 let commits = raw_commits
51 .into_iter()
52 .map(|c| CommitSummary {
53 full_message: c.full_message,
54 hash: c.hash,
55 summary: c.summary,
56 })
57 .collect::<Vec<_>>();
58
59 let report = validate_commits(commits, &policy);
61
62 let all_valid = report.invalid_count == 0;
63
64 if all_valid {
66 ui.logger().ok(&format!(
67 "{} All {} commit(s) are valid",
68 SUCCESS, report.total
69 ));
70 } else {
71 ui.logger().warn(&format!(
72 "{} {} of {} commit(s) are invalid",
73 ERROR, report.invalid_count, report.total
74 ));
75 }
76
77 let duration_ms = start.elapsed().as_millis() as u64;
79 let meta = ui
80 .new_output_meta()
81 .with_duration_ms(duration_ms)
82 .with_timestamp(chrono::Utc::now().to_string())
83 .with_command("check".to_string())
84 .with_dry_run(ctx.dry_run());
85
86 let mut content = ui
88 .new_output_content()
89 .title(if all_valid {
90 "Commit History Valid"
91 } else {
92 "Commit History Invalid"
93 })
94 .subtitle(if all_valid {
95 "All commits conform to the active rules"
96 } else {
97 "Some commits do not conform to the active rules"
98 })
99 .data("total", report.total.to_string())
100 .data("invalid_count", report.invalid_count.to_string())
101 .data("valid", all_valid.to_string());
102
103 if let Some(from) = from {
104 content = content.data("from", from);
105 }
106 if let Some(to) = to {
107 content = content.data("to", to);
108 }
109 if let Some(tail) = tail {
110 content = content.data("tail", tail.to_string());
111 }
112
113 let invalid_lines: Vec<String> = report
115 .commits
116 .iter()
117 .filter(|c| !c.valid)
118 .map(|c| {
119 let violations_str = c
120 .violations
121 .iter()
122 .map(|v| v.message())
123 .collect::<Vec<_>>()
124 .join(" | ");
125 format!(
126 "{} {} - {}\n {}",
127 ERROR,
128 format_hash(&c.hash),
129 c.summary,
130 violations_str
131 )
132 })
133 .collect();
134
135 if !invalid_lines.is_empty() {
136 content = content.section(
137 "Invalid Commits",
138 invalid_lines.join("\n"),
139 "sh".to_string(),
140 );
141 }
142
143 let valid_lines: Vec<String> = report
145 .commits
146 .iter()
147 .filter(|c| c.valid)
148 .map(|c| format!("{} {} {}", SUCCESS, format_hash(&c.hash), c.summary))
149 .collect();
150
151 if !valid_lines.is_empty() {
152 content = content.section("Valid Commits", valid_lines.join("\n"), "sh".to_string());
153 }
154
155 let plain = report.invalid_count.to_string();
157 content = content.plain(plain);
158
159 ui.print_with_meta(&content, Some(&meta), all_valid)
160}