Skip to main content

commit_wizard/core/usecases/commit/
push.rs

1use 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}