rustic_git/commands/
commit.rs1use crate::utils::git;
2use crate::{Hash, Repository, Result};
3
4impl Repository {
5 pub fn commit(&self, message: &str) -> Result<Hash> {
15 Self::ensure_git()?;
16
17 if message.trim().is_empty() {
18 return Err(crate::error::GitError::CommandFailed(
19 "Commit message cannot be empty".to_string(),
20 ));
21 }
22
23 let status = self.status()?;
25 let has_staged = status.staged_files().count() > 0;
26
27 if !has_staged {
28 return Err(crate::error::GitError::CommandFailed(
29 "No changes staged for commit".to_string(),
30 ));
31 }
32
33 let _stdout =
34 git(&["commit", "-m", message], Some(self.repo_path())).map_err(|e| match e {
35 crate::error::GitError::CommandFailed(msg) => {
36 crate::error::GitError::CommandFailed(format!(
37 "Commit failed: {}. Ensure git user.name and user.email are configured.",
38 msg
39 ))
40 }
41 other => other,
42 })?;
43
44 let hash_output = git(&["rev-parse", "HEAD"], Some(self.repo_path()))?;
46 let commit_hash = hash_output.trim().to_string();
47
48 Ok(Hash(commit_hash))
49 }
50
51 pub fn commit_with_author(&self, message: &str, author: &str) -> Result<Hash> {
62 Self::ensure_git()?;
63
64 if message.trim().is_empty() {
65 return Err(crate::error::GitError::CommandFailed(
66 "Commit message cannot be empty".to_string(),
67 ));
68 }
69
70 if author.trim().is_empty() {
71 return Err(crate::error::GitError::CommandFailed(
72 "Author cannot be empty".to_string(),
73 ));
74 }
75
76 let status = self.status()?;
78 let has_staged = status.staged_files().count() > 0;
79
80 if !has_staged {
81 return Err(crate::error::GitError::CommandFailed(
82 "No changes staged for commit".to_string(),
83 ));
84 }
85
86 let _stdout = git(&["commit", "-m", message, "--author", author], Some(self.repo_path()))
87 .map_err(|e| match e {
88 crate::error::GitError::CommandFailed(msg) => {
89 crate::error::GitError::CommandFailed(format!(
90 "Commit with author failed: {}. Ensure git user.name and user.email are configured.",
91 msg
92 ))
93 }
94 other => other,
95 })?;
96
97 let hash_output = git(&["rev-parse", "HEAD"], Some(self.repo_path()))?;
99 let commit_hash = hash_output.trim().to_string();
100
101 Ok(Hash(commit_hash))
102 }
103}
104
105#[cfg(test)]
106mod tests {
107 use super::*;
108 use std::fs;
109 use std::path::Path;
110
111 fn create_test_repo(path: &str) -> Repository {
112 if Path::new(path).exists() {
114 fs::remove_dir_all(path).unwrap();
115 }
116
117 let repo = Repository::init(path, false).unwrap();
118
119 repo.config()
121 .set_user("Test User", "test@example.com")
122 .unwrap();
123
124 repo
125 }
126
127 fn create_and_stage_file(repo: &Repository, repo_path: &str, filename: &str, content: &str) {
128 let file_path = format!("{}/{}", repo_path, filename);
129 fs::write(file_path, content).unwrap();
130 repo.add(&[filename]).unwrap();
131 }
132
133 #[test]
134 fn test_commit_basic() {
135 let test_path = "/tmp/test_commit_repo";
136 let repo = create_test_repo(test_path);
137
138 create_and_stage_file(&repo, test_path, "test.txt", "test content");
140
141 let result = repo.commit("Initial commit");
143 assert!(result.is_ok());
144
145 let hash = result.unwrap();
146 assert!(!hash.as_str().is_empty());
147 assert_eq!(hash.short().len(), 7);
148
149 let status = repo.status().unwrap();
151 assert!(status.is_clean());
152
153 fs::remove_dir_all(test_path).unwrap();
155 }
156
157 #[test]
158 fn test_commit_with_author() {
159 let test_path = "/tmp/test_commit_author_repo";
160 let repo = create_test_repo(test_path);
161
162 create_and_stage_file(&repo, test_path, "test.txt", "test content");
164
165 let result = repo.commit_with_author("Test commit", "Test User <test@example.com>");
167 assert!(result.is_ok());
168
169 let hash = result.unwrap();
170 assert!(!hash.as_str().is_empty());
171
172 fs::remove_dir_all(test_path).unwrap();
174 }
175
176 #[test]
177 fn test_commit_empty_message() {
178 let test_path = "/tmp/test_commit_empty_msg_repo";
179 let repo = create_test_repo(test_path);
180
181 create_and_stage_file(&repo, test_path, "test.txt", "test content");
183
184 let result = repo.commit("");
186 assert!(result.is_err());
187
188 if let Err(crate::error::GitError::CommandFailed(msg)) = result {
189 assert!(msg.contains("empty"));
190 } else {
191 panic!("Expected CommandFailed error");
192 }
193
194 fs::remove_dir_all(test_path).unwrap();
196 }
197
198 #[test]
199 fn test_commit_no_staged_changes() {
200 let test_path = "/tmp/test_commit_no_changes_repo";
201 let repo = create_test_repo(test_path);
202
203 let result = repo.commit("Test commit");
205 assert!(result.is_err());
206
207 if let Err(crate::error::GitError::CommandFailed(msg)) = result {
208 assert!(msg.contains("No changes staged"));
209 } else {
210 panic!("Expected CommandFailed error");
211 }
212
213 fs::remove_dir_all(test_path).unwrap();
215 }
216
217 #[test]
218 fn test_hash_display() {
219 let hash = Hash("abc123def456".to_string());
220 assert_eq!(hash.as_str(), "abc123def456");
221 assert_eq!(hash.short(), "abc123d");
222 assert_eq!(format!("{}", hash), "abc123def456");
223 }
224
225 #[test]
226 fn test_hash_short_hash() {
227 let hash = Hash("abc".to_string());
228 assert_eq!(hash.short(), "abc"); }
230
231 #[test]
232 fn test_commit_with_author_empty_author() {
233 let test_path = "/tmp/test_commit_empty_author_repo";
234 let repo = create_test_repo(test_path);
235
236 create_and_stage_file(&repo, test_path, "test.txt", "test content");
238
239 let result = repo.commit_with_author("Test commit", "");
241 assert!(result.is_err());
242
243 fs::remove_dir_all(test_path).unwrap();
245 }
246
247 #[test]
248 fn test_git_config_is_set_in_test_repo() {
249 let test_path = "/tmp/test_git_config_repo";
250 let repo = create_test_repo(test_path);
251
252 let (name, email) = repo.config().get_user().unwrap();
254 assert_eq!(name, "Test User");
255 assert_eq!(email, "test@example.com");
256
257 fs::remove_dir_all(test_path).unwrap();
259 }
260}