commit_wizard/core/usecases/commit/
push.rs1use std::time::Instant;
2
3use crate::{
4 core::{Context, CoreResult},
5 engine::{
6 capabilities::commit::{check::validate_commits, push::evaluate_push},
7 error::ErrorCode,
8 models::policy::commit::CommitModel,
9 },
10};
11
12pub fn run(
13 ctx: &Context,
14 from: Option<String>,
15 to: String,
16 remote: String,
17 branch: Option<String>,
18 allow_empty: bool,
19) -> CoreResult<()> {
20 let ui = ctx.ui();
21 let start = Instant::now();
22
23 let resolved_config = ctx.config().ok_or_else(|| {
24 ErrorCode::ConfigUnreadable
25 .error()
26 .with_context("context", "Config not resolved")
27 })?;
28 let policy = CommitModel::from_config(resolved_config);
29 let push_policy = ctx.policy();
30 let git = ctx.git();
31
32 let current_branch = git.current_branch()?;
33 let push_branch = branch.unwrap_or_else(|| current_branch.clone());
34
35 let raw_commits = git.list_commits(from.as_deref(), &to)?;
36 let checked = validate_commits(raw_commits, &policy);
37 let report = evaluate_push(push_policy, &push_branch, checked.commits);
38
39 let duration_ms = start.elapsed().as_millis() as u64;
40
41 let mut content = ctx
42 .ui()
43 .new_output_content()
44 .title("Push Validation")
45 .subtitle("Validate push against active rules before pushing")
46 .data("current_branch", current_branch.clone())
47 .data("push_branch", push_branch.clone())
48 .data("remote", remote.clone())
49 .data("range_to", to.clone())
50 .data("protected_branch", report.protected_branch.to_string())
51 .data("total_checked", report.total_checked.to_string())
52 .data("invalid_count", report.invalid_count.to_string())
53 .data("blocked", report.blocked.to_string())
54 .data("allow_empty", allow_empty.to_string());
55
56 if let Some(from_ref) = from.clone() {
57 content = content.data("range_from", from_ref);
58 }
59
60 if !report.block_reasons.is_empty() {
61 content = content.section(
62 "Block Reasons",
63 report.block_reasons.join("\n"),
64 "sh".to_string(),
65 );
66 }
67
68 let invalid_lines = report
69 .commits
70 .iter()
71 .filter(|c| !c.valid)
72 .map(|c| format!("{} {}", c.hash, c.summary))
73 .collect::<Vec<_>>();
74
75 if !invalid_lines.is_empty() {
76 content = content.section(
77 "Invalid Commits",
78 invalid_lines.join("\n"),
79 "sh".to_string(),
80 );
81 }
82
83 let meta = ui
84 .new_output_meta()
85 .with_duration_ms(duration_ms)
86 .with_timestamp(chrono::Utc::now().to_string())
87 .with_command("push".to_string())
88 .with_dry_run(ctx.dry_run());
89
90 if report.blocked {
91 return ui.print_with_meta(&content, Some(&meta), true);
92 }
93
94 if ctx.dry_run() {
95 content = content
96 .title("Push Preview")
97 .subtitle("Dry run: no push was performed");
98 return ui.print_with_meta(&content, Some(&meta), true);
99 }
100
101 git.push_branch(&remote, &push_branch)?;
102
103 content = content
104 .title("Push Completed")
105 .subtitle("Push completed successfully");
106
107 ui.print_with_meta(&content, Some(&meta), true)
108}