1use crate::command::Command;
2use crate::error::{GiError, Result};
3use crate::repo::GitRepo;
4use crate::ui;
5use std::process;
6
7pub struct Commit;
9
10impl Commit {
11 fn normalize_message(message: String) -> Option<String> {
12 (!message.trim().is_empty()).then_some(message)
13 }
14
15 fn run_hook(repo: &GitRepo, hook_name: &str) -> Result<()> {
17 let workdir = repo.workdir().unwrap_or(repo.path());
18
19 let output = process::Command::new("git")
20 .args(["hook", "run", hook_name])
21 .current_dir(workdir)
22 .output()
23 .map_err(|e| GiError::Other(e.into()))?;
24
25 let stderr = String::from_utf8_lossy(&output.stderr);
27 if stderr.contains("cannot find") || stderr.contains("not found") {
28 return Ok(());
30 }
31
32 if !output.status.success() {
33 return Err(GiError::Other(anyhow::anyhow!(
34 "{} hook failed:\n{}",
35 hook_name,
36 stderr.trim()
37 )));
38 }
39
40 Ok(())
41 }
42
43 pub fn execute_with_message(repo: &GitRepo, message: Option<String>) -> Result<()> {
45 if !repo.has_staged_changes()? {
46 println!("No staged changes to commit");
47 return Ok(());
48 }
49
50 let message = match message {
51 Some(message) => Self::normalize_message(message),
52 None => ui::required_text(
53 "Commit message:",
54 Some("Enter a descriptive commit message"),
55 )?,
56 };
57
58 let Some(message) = message else {
59 println!("Aborting commit due to empty commit message");
60 return Ok(());
61 };
62
63 Self::run_hook(repo, "pre-commit")?;
65
66 repo.commit(&message)?;
68
69 let _ = Self::run_hook(repo, "post-commit");
71
72 println!("Committed: {}", message);
73 Ok(())
74 }
75}
76
77impl Command for Commit {
78 fn execute(repo: &GitRepo) -> Result<()> {
79 Self::execute_with_message(repo, None)
80 }
81}
82
83#[cfg(test)]
84mod tests {
85 use super::Commit;
86
87 #[test]
88 fn normalize_message_keeps_non_empty_message() {
89 assert_eq!(
90 Commit::normalize_message("commit message".to_string()),
91 Some("commit message".to_string())
92 );
93 }
94
95 #[test]
96 fn normalize_message_rejects_blank_message() {
97 assert_eq!(Commit::normalize_message(" ".to_string()), None);
98 }
99}