gitwatch_rs/
commit_message.rs1use anyhow::{bail, Context, Result};
2use log::debug;
3use std::path::Path;
4use std::process::Command;
5
6pub fn generate_commit_message(script_path: &Path, repo_path: &Path) -> Result<String> {
7 debug!(
8 "Executing commit message script '{}'",
9 script_path.display()
10 );
11 let output = Command::new(script_path)
12 .current_dir(repo_path)
13 .output()
14 .with_context(|| {
15 format!(
16 "Failed to execute commit message script '{}'",
17 script_path.display()
18 )
19 })?;
20
21 if !output.status.success() {
22 let stderr = String::from_utf8_lossy(&output.stderr).trim().to_string();
23 bail!(
24 "Commit message script failed with exit code {}.\nError: {}",
25 output.status,
26 stderr
27 );
28 }
29
30 let commit_message = String::from_utf8(output.stdout)
31 .context("Commit message script output is not valid UTF-8")?;
32
33 let trimmed_message = commit_message.trim();
34 if trimmed_message.is_empty() {
35 bail!("Commit message script output is empty");
36 }
37
38 if let Some(first_line) = trimmed_message.lines().next() {
39 debug!("Generated commit message: '{}'", first_line);
40 }
41
42 Ok(commit_message)
43}
44
45#[cfg(test)]
46mod tests {
47 use super::*;
48 use std::fs::OpenOptions;
49 use std::io::Write;
50 use std::os::unix::fs::OpenOptionsExt;
51 use std::thread;
52 use std::time::Duration;
53 use tempfile::TempDir;
54 use testresult::TestResult;
55
56 #[test]
57 #[cfg(unix)]
58 fn test_successful_script_execution() -> TestResult {
59 let temp_dir = TempDir::new()?;
60 let script_content = "echo 'Test commit message'";
61 let script_path = create_test_script(&temp_dir, script_content)?;
62
63 let result = generate_commit_message(&script_path, &temp_dir.path())?;
64 assert_eq!(result.trim(), "Test commit message");
65 Ok(())
66 }
67
68 #[test]
69 #[cfg(unix)]
70 fn test_working_directory() -> TestResult {
71 let temp_dir = TempDir::new()?;
72 let script_content = "echo $PWD";
73 let script_path = create_test_script(&temp_dir, script_content)?;
74
75 let result = generate_commit_message(&script_path, &temp_dir.path())?;
76 assert_eq!(
77 result.trim(),
78 temp_dir.path().canonicalize()?.display().to_string()
79 );
80 Ok(())
81 }
82
83 #[test]
84 #[cfg(unix)]
85 fn test_script_execution_failure() -> TestResult {
86 let temp_dir = TempDir::new()?;
87 let script_content = "exit 1";
88 let script_path = create_test_script(&temp_dir, script_content)?;
89
90 let result = generate_commit_message(&script_path, &temp_dir.path());
91 assert!(result
92 .unwrap_err()
93 .to_string()
94 .contains("failed with exit code"));
95 Ok(())
96 }
97
98 #[test]
99 fn test_nonexistent_script() {
100 let result = generate_commit_message(Path::new("/nonexistent/script/path"), Path::new(""));
101 assert!(result
102 .unwrap_err()
103 .to_string()
104 .contains("Failed to execute commit message script"));
105 }
106
107 #[test]
108 #[cfg(unix)]
109 fn test_empty_output() -> TestResult {
110 let temp_dir = TempDir::new()?;
111 let script_content = "echo ''";
112 let script_path = create_test_script(&temp_dir, script_content)?;
113
114 let result = generate_commit_message(&script_path, &temp_dir.path());
115 assert!(result
116 .unwrap_err()
117 .to_string()
118 .contains("Commit message script output is empty"));
119 Ok(())
120 }
121
122 #[test]
123 #[cfg(unix)]
124 fn test_whitespace_only_output() -> TestResult {
125 let temp_dir = TempDir::new()?;
126 let script_content = "echo ' \n \t '";
127 let script_path = create_test_script(&temp_dir, script_content)?;
128
129 let result = generate_commit_message(&script_path, &temp_dir.path());
130 assert!(result
131 .unwrap_err()
132 .to_string()
133 .contains("Commit message script output is empty"));
134 Ok(())
135 }
136
137 fn create_test_script(dir: &TempDir, content: &str) -> Result<std::path::PathBuf> {
138 let script_path = dir.path().join("test_script.sh");
139 OpenOptions::new()
140 .write(true)
141 .create(true)
142 .mode(0o755)
143 .open(&script_path)?
144 .write_all(format!("#!/bin/sh\n{content}").as_bytes())?;
145 thread::sleep(Duration::from_millis(50));
146 Ok(script_path)
147 }
148}