auto_commit/git/
mod.rs

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        // git diff --quiet returns exit code 1 if there are changes
28        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        // Initialize git repo
71        Command::new("git")
72            .arg("init")
73            .current_dir(temp_dir.path())
74            .output()?;
75
76        // Configure git
77        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        // Create a file and stage it
97        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        // Change to test directory
104        std::env::set_current_dir(temp_dir.path())?;
105
106        // Test
107        let result = GitOperations::has_staged_changes();
108
109        // Restore original directory
110        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        // Change to test directory
124        std::env::set_current_dir(temp_dir.path())?;
125
126        // Test
127        let result = GitOperations::has_staged_changes();
128
129        // Restore original directory
130        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        // Create and stage a file
144        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        // Change to test directory
151        std::env::set_current_dir(temp_dir.path())?;
152
153        // Test
154        let diff = GitOperations::get_staged_diff();
155
156        // Restore original directory
157        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        // Create and stage a file
173        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        // Change to test directory
180        std::env::set_current_dir(temp_dir.path())?;
181
182        // Test
183        GitOperations::create_commit("test: Add test file")?;
184
185        // Verify commit was created
186        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        // Restore original directory
193        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        // Create initial commit so HEAD exists
207        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        // Change to test directory
218        std::env::set_current_dir(temp_dir.path())?;
219
220        // Test default branch
221        let branch = GitOperations::get_current_branch()?;
222        assert!(branch == "main" || branch == "master");
223
224        // Create and switch to a new branch
225        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        // Restore original directory
234        std::env::set_current_dir(original_dir)?;
235
236        Ok(())
237    }
238}