1use anyhow::{Context, Result};
2use std::process::Command;
3
4pub struct GitOperations;
5
6impl GitOperations {
7 pub fn get_staged_diff() -> Result<String> {
8 let output = Command::new("git")
9 .args(["diff", "--staged"])
10 .output()
11 .context("Failed to execute git diff")?;
12
13 if !output.status.success() {
14 let error = String::from_utf8_lossy(&output.stderr);
15 return Err(anyhow::anyhow!("git diff failed: {}", error));
16 }
17
18 Ok(String::from_utf8_lossy(&output.stdout).to_string())
19 }
20
21 pub fn has_staged_changes() -> Result<bool> {
22 let output = Command::new("git")
23 .args(["diff", "--staged", "--quiet"])
24 .output()
25 .context("Failed to execute git diff")?;
26
27 Ok(!output.status.success())
29 }
30
31 pub fn create_commit(message: &str) -> Result<()> {
32 let output = Command::new("git")
33 .args(["commit", "-m", message])
34 .output()
35 .context("Failed to execute git commit")?;
36
37 if !output.status.success() {
38 let error = String::from_utf8_lossy(&output.stderr);
39 return Err(anyhow::anyhow!("git commit failed: {}", error));
40 }
41
42 Ok(())
43 }
44
45 pub fn get_current_branch() -> Result<String> {
46 let output = Command::new("git")
47 .args(["rev-parse", "--abbrev-ref", "HEAD"])
48 .output()
49 .context("Failed to get current branch")?;
50
51 if !output.status.success() {
52 let error = String::from_utf8_lossy(&output.stderr);
53 return Err(anyhow::anyhow!("Failed to get current branch: {}", error));
54 }
55
56 Ok(String::from_utf8_lossy(&output.stdout).trim().to_string())
57 }
58}
59
60#[cfg(test)]
61mod tests {
62 use super::*;
63 use serial_test::serial;
64 use std::fs;
65 use tempfile::TempDir;
66
67 fn setup_test_repo() -> Result<TempDir> {
68 let temp_dir = TempDir::new()?;
69
70 Command::new("git")
72 .arg("init")
73 .current_dir(temp_dir.path())
74 .output()?;
75
76 Command::new("git")
78 .args(&["config", "user.email", "test@example.com"])
79 .current_dir(temp_dir.path())
80 .output()?;
81
82 Command::new("git")
83 .args(&["config", "user.name", "Test User"])
84 .current_dir(temp_dir.path())
85 .output()?;
86
87 Ok(temp_dir)
88 }
89
90 #[test]
91 #[serial]
92 fn test_has_staged_changes_with_changes() -> Result<()> {
93 let temp_dir = setup_test_repo()?;
94 let original_dir = std::env::current_dir()?;
95
96 fs::write(temp_dir.path().join("test.txt"), "Hello, world!")?;
98 Command::new("git")
99 .args(&["add", "test.txt"])
100 .current_dir(temp_dir.path())
101 .output()?;
102
103 std::env::set_current_dir(temp_dir.path())?;
105
106 let result = GitOperations::has_staged_changes();
108
109 std::env::set_current_dir(original_dir)?;
111
112 assert!(result?);
113
114 Ok(())
115 }
116
117 #[test]
118 #[serial]
119 fn test_has_staged_changes_without_changes() -> Result<()> {
120 let temp_dir = setup_test_repo()?;
121 let original_dir = std::env::current_dir()?;
122
123 std::env::set_current_dir(temp_dir.path())?;
125
126 let result = GitOperations::has_staged_changes();
128
129 std::env::set_current_dir(original_dir)?;
131
132 assert!(!result?);
133
134 Ok(())
135 }
136
137 #[test]
138 #[serial]
139 fn test_get_staged_diff() -> Result<()> {
140 let temp_dir = setup_test_repo()?;
141 let original_dir = std::env::current_dir()?;
142
143 fs::write(temp_dir.path().join("test.txt"), "Hello, world!")?;
145 Command::new("git")
146 .args(&["add", "test.txt"])
147 .current_dir(temp_dir.path())
148 .output()?;
149
150 std::env::set_current_dir(temp_dir.path())?;
152
153 let diff = GitOperations::get_staged_diff();
155
156 std::env::set_current_dir(original_dir)?;
158
159 let diff = diff?;
160 assert!(diff.contains("Hello, world!"));
161 assert!(diff.contains("test.txt"));
162
163 Ok(())
164 }
165
166 #[test]
167 #[serial]
168 fn test_create_commit() -> Result<()> {
169 let temp_dir = setup_test_repo()?;
170 let original_dir = std::env::current_dir()?;
171
172 fs::write(temp_dir.path().join("test.txt"), "Hello, world!")?;
174 Command::new("git")
175 .args(&["add", "test.txt"])
176 .current_dir(temp_dir.path())
177 .output()?;
178
179 std::env::set_current_dir(temp_dir.path())?;
181
182 GitOperations::create_commit("test: Add test file")?;
184
185 let output = Command::new("git")
187 .args(&["log", "--oneline"])
188 .current_dir(temp_dir.path())
189 .output()?;
190 let log = String::from_utf8(output.stdout)?;
191
192 std::env::set_current_dir(original_dir)?;
194
195 assert!(log.contains("test: Add test file"));
196
197 Ok(())
198 }
199
200 #[test]
201 #[serial]
202 fn test_get_current_branch() -> Result<()> {
203 let temp_dir = setup_test_repo()?;
204 let original_dir = std::env::current_dir()?;
205
206 fs::write(temp_dir.path().join("README.md"), "Initial commit")?;
208 Command::new("git")
209 .args(&["add", "README.md"])
210 .current_dir(temp_dir.path())
211 .output()?;
212 Command::new("git")
213 .args(&["commit", "-m", "Initial commit"])
214 .current_dir(temp_dir.path())
215 .output()?;
216
217 std::env::set_current_dir(temp_dir.path())?;
219
220 let branch = GitOperations::get_current_branch()?;
222 assert!(branch == "main" || branch == "master");
223
224 Command::new("git")
226 .args(&["checkout", "-b", "feature-branch"])
227 .current_dir(temp_dir.path())
228 .output()?;
229
230 let branch = GitOperations::get_current_branch()?;
231 assert_eq!(branch, "feature-branch");
232
233 std::env::set_current_dir(original_dir)?;
235
236 Ok(())
237 }
238}