workspacer_git/
ensure_git_clean.rs1crate::ix!();
3
4#[async_trait]
5pub trait EnsureGitClean {
6 type Error;
7 async fn ensure_git_clean(&self) -> Result<(), Self::Error>;
8}
9
10#[async_trait]
11impl<P,H> EnsureGitClean for Workspace<P,H>
12where
13 for<'async_trait> P: From<PathBuf> + AsRef<Path> + Clone + Send + Sync + 'async_trait,
15 H: CrateHandleInterface<P> + Send + Sync,
16{
17 type Error = GitError;
18
19 async fn ensure_git_clean(&self) -> Result<(), Self::Error> {
23 let output = Command::new("git")
25 .args(["status", "--porcelain"])
26 .current_dir(self.as_ref()) .output()
28 .await
29 .map_err(|e|
30 GitError::IoError {
31 io: Arc::new(e),
32 context: format!("could not run git status --porcelain in current directory: {:?}", self.as_ref()),
33 }
34 )?;
35
36 if !output.status.success() {
37 return Err(GitError::FailedToRunGitStatusMakeSureGitIsInstalled);
38 }
39
40 let stdout_str = String::from_utf8_lossy(&output.stdout);
41 if !stdout_str.trim().is_empty() {
42 return Err(GitError::WorkingDirectoryIsNotCleanAborting);
43 }
44
45 Ok(())
46 }
47}
48
49#[cfg(test)]
50mod test_ensure_git_clean_for_workspace {
51 use super::*;
52
53 async fn run_in_dir(dir: &std::path::Path, cmd: &str, args: &[&str]) -> Result<(), String> {
63 let mut command = Command::new(cmd);
64 command.args(args).current_dir(dir);
65 let output = command.output().await.map_err(|e| format!("Failed to spawn {}: {}", cmd, e))?;
66 if !output.status.success() {
67 let stderr = String::from_utf8_lossy(&output.stderr);
68 return Err(format!("Command {} {:?} failed: {}", cmd, args, stderr));
69 }
70 Ok(())
71 }
72
73 #[tokio::test]
74 async fn test_clean_repo_succeeds() {
75 let tmp_dir = tempdir().expect("failed to create temp dir");
77 let path = tmp_dir.path();
78
79 run_in_dir(path, "git", &["init", "."]).await.expect("git init should succeed");
82 let file_path = path.join("hello.txt");
84 tokio::fs::write(&file_path, b"hello").await.expect("write file");
85 run_in_dir(path, "git", &["add", "."]).await.expect("git add .");
87 run_in_dir(path, "git", &["commit", "-m", "Initial commit"]).await.expect("git commit");
88
89 let ws = MockWorkspace { root: path.to_path_buf() };
92
93 let result = ws.ensure_git_clean().await;
95 assert!(result.is_ok(), "A fully committed repo is clean => Ok(())");
96 }
97
98 #[tokio::test]
99 async fn test_dirty_repo_fails() {
100 let tmp_dir = tempdir().expect("failed to create temp dir");
101 let path = tmp_dir.path();
102
103 run_in_dir(path, "git", &["init", "."]).await.expect("git init");
104 let file_path = path.join("hello.txt");
106 tokio::fs::write(&file_path, b"hello").await.expect("write file");
107 let ws = MockWorkspace { root: path.to_path_buf() };
110
111 let result = ws.ensure_git_clean().await;
112 match result {
113 Err(GitError::WorkingDirectoryIsNotCleanAborting) => {
114 }
116 other => panic!("Expected WorkingDirectoryIsNotCleanAborting, got {:?}", other),
117 }
118 }
119
120 #[tokio::test]
124 async fn test_not_git_repo_errors() {
125 let tmp_dir = tempdir().expect("failed to create temp dir");
126 let path = tmp_dir.path();
127
128 let ws = MockWorkspace { root: path.to_path_buf() };
129
130 let result = ws.ensure_git_clean().await;
131 match result {
132 Err(GitError::IoError{..})
133 | Err(GitError::FailedToRunGitStatusMakeSureGitIsInstalled) => {
134 }
136 other => panic!("Expected IoError or FailedToRunGitStatus..., got {:?}", other),
137 }
138 }
139
140 #[derive(Debug)]
143 struct MockWorkspace {
144 root: PathBuf,
145 }
146
147 impl AsRef<std::path::Path> for MockWorkspace {
148 fn as_ref(&self) -> &std::path::Path {
149 &self.root
150 }
151 }
152
153 #[async_trait]
154 impl EnsureGitClean for MockWorkspace {
155 type Error = GitError;
156
157 async fn ensure_git_clean(&self) -> Result<(), Self::Error> {
158 let output = Command::new("git")
160 .args(["status", "--porcelain"])
161 .current_dir(self.as_ref())
162 .output()
163 .await
164 .map_err(|e| GitError::IoError {
165 io: Arc::new(e),
166 context: format!("could not run git status --porcelain in current directory: {:?}", self.as_ref()),
167 })?;
168
169 if !output.status.success() {
170 return Err(GitError::FailedToRunGitStatusMakeSureGitIsInstalled);
171 }
172 let stdout_str = String::from_utf8_lossy(&output.stdout);
173 if !stdout_str.trim().is_empty() {
174 return Err(GitError::WorkingDirectoryIsNotCleanAborting);
175 }
176 Ok(())
177 }
178 }
179}