feat: Migrate SAL to Cargo workspace
- Migrate individual modules to independent crates - Refactor dependencies for improved modularity - Update build system and testing infrastructure - Update documentation to reflect new structure
This commit is contained in:
		
							
								
								
									
										140
									
								
								git/src/git.rs
									
									
									
									
									
								
							
							
						
						
									
										140
									
								
								git/src/git.rs
									
									
									
									
									
								
							| @@ -1,9 +1,9 @@ | ||||
| use std::process::Command; | ||||
| use std::path::Path; | ||||
| use std::fs; | ||||
| use regex::Regex; | ||||
| use std::fmt; | ||||
| use std::error::Error; | ||||
| use std::fmt; | ||||
| use std::fs; | ||||
| use std::path::Path; | ||||
| use std::process::Command; | ||||
|  | ||||
| // Define a custom error type for git operations | ||||
| #[derive(Debug)] | ||||
| @@ -35,7 +35,7 @@ impl fmt::Display for GitError { | ||||
|             GitError::CommandExecutionError(e) => write!(f, "Error executing command: {}", e), | ||||
|             GitError::NoRepositoriesFound => write!(f, "No repositories found"), | ||||
|             GitError::RepositoryNotFound(pattern) => write!(f, "No repositories found matching '{}'", pattern), | ||||
|             GitError::MultipleRepositoriesFound(pattern, count) =>  | ||||
|             GitError::MultipleRepositoriesFound(pattern, count) => | ||||
|                 write!(f, "Multiple repositories ({}) found matching '{}'. Use '*' suffix for multiple matches.", count, pattern), | ||||
|             GitError::NotAGitRepository(path) => write!(f, "Not a git repository at {}", path), | ||||
|             GitError::LocalChangesExist(path) => write!(f, "Repository at {} has local changes", path), | ||||
| @@ -57,48 +57,48 @@ impl Error for GitError { | ||||
| } | ||||
|  | ||||
| /// Parses a git URL to extract the server, account, and repository name. | ||||
| ///  | ||||
| /// | ||||
| /// # Arguments | ||||
| ///  | ||||
| /// * `url` - The URL of the git repository to parse. Can be in HTTPS format  | ||||
| /// | ||||
| /// * `url` - The URL of the git repository to parse. Can be in HTTPS format | ||||
| ///   (https://github.com/username/repo.git) or SSH format (git@github.com:username/repo.git). | ||||
| ///  | ||||
| /// | ||||
| /// # Returns | ||||
| ///  | ||||
| /// | ||||
| /// A tuple containing: | ||||
| /// * `server` - The server name (e.g., "github.com") | ||||
| /// * `account` - The account or organization name (e.g., "username") | ||||
| /// * `repo` - The repository name (e.g., "repo") | ||||
| ///  | ||||
| /// | ||||
| /// If the URL cannot be parsed, all three values will be empty strings. | ||||
| pub fn parse_git_url(url: &str) -> (String, String, String) { | ||||
|     // HTTP(S) URL format: https://github.com/username/repo.git | ||||
|     let https_re = Regex::new(r"https?://([^/]+)/([^/]+)/([^/\.]+)(?:\.git)?").unwrap(); | ||||
|      | ||||
|  | ||||
|     // SSH URL format: git@github.com:username/repo.git | ||||
|     let ssh_re = Regex::new(r"git@([^:]+):([^/]+)/([^/\.]+)(?:\.git)?").unwrap(); | ||||
|      | ||||
|  | ||||
|     if let Some(caps) = https_re.captures(url) { | ||||
|         let server = caps.get(1).map_or("", |m| m.as_str()).to_string(); | ||||
|         let account = caps.get(2).map_or("", |m| m.as_str()).to_string(); | ||||
|         let repo = caps.get(3).map_or("", |m| m.as_str()).to_string(); | ||||
|          | ||||
|  | ||||
|         return (server, account, repo); | ||||
|     } else if let Some(caps) = ssh_re.captures(url) { | ||||
|         let server = caps.get(1).map_or("", |m| m.as_str()).to_string(); | ||||
|         let account = caps.get(2).map_or("", |m| m.as_str()).to_string(); | ||||
|         let repo = caps.get(3).map_or("", |m| m.as_str()).to_string(); | ||||
|          | ||||
|  | ||||
|         return (server, account, repo); | ||||
|     } | ||||
|      | ||||
|  | ||||
|     (String::new(), String::new(), String::new()) | ||||
| } | ||||
|  | ||||
| /// Checks if git is installed on the system. | ||||
| ///  | ||||
| /// | ||||
| /// # Returns | ||||
| ///  | ||||
| /// | ||||
| /// * `Ok(())` - If git is installed | ||||
| /// * `Err(GitError)` - If git is not installed | ||||
| fn check_git_installed() -> Result<(), GitError> { | ||||
| @@ -117,55 +117,53 @@ pub struct GitTree { | ||||
|  | ||||
| impl GitTree { | ||||
|     /// Creates a new GitTree with the specified base path. | ||||
|     ///  | ||||
|     /// | ||||
|     /// # Arguments | ||||
|     ///  | ||||
|     /// | ||||
|     /// * `base_path` - The base path where all git repositories are located | ||||
|     ///  | ||||
|     /// | ||||
|     /// # Returns | ||||
|     ///  | ||||
|     /// | ||||
|     /// * `Ok(GitTree)` - A new GitTree instance | ||||
|     /// * `Err(GitError)` - If the base path is invalid or cannot be created | ||||
|     pub fn new(base_path: &str) -> Result<Self, GitError> { | ||||
|         // Check if git is installed | ||||
|         check_git_installed()?; | ||||
|          | ||||
|  | ||||
|         // Validate the base path | ||||
|         let path = Path::new(base_path); | ||||
|         if !path.exists() { | ||||
|             fs::create_dir_all(path).map_err(|e| { | ||||
|                 GitError::FileSystemError(e) | ||||
|             })?; | ||||
|             fs::create_dir_all(path).map_err(|e| GitError::FileSystemError(e))?; | ||||
|         } else if !path.is_dir() { | ||||
|             return Err(GitError::InvalidBasePath(base_path.to_string())); | ||||
|         } | ||||
|          | ||||
|  | ||||
|         Ok(GitTree { | ||||
|             base_path: base_path.to_string(), | ||||
|         }) | ||||
|     } | ||||
|      | ||||
|  | ||||
|     /// Lists all git repositories under the base path. | ||||
|     ///  | ||||
|     /// | ||||
|     /// # Returns | ||||
|     ///  | ||||
|     /// | ||||
|     /// * `Ok(Vec<String>)` - A vector of paths to git repositories | ||||
|     /// * `Err(GitError)` - If the operation failed | ||||
|     pub fn list(&self) -> Result<Vec<String>, GitError> { | ||||
|         let base_path = Path::new(&self.base_path); | ||||
|          | ||||
|  | ||||
|         if !base_path.exists() || !base_path.is_dir() { | ||||
|             return Ok(Vec::new()); | ||||
|         } | ||||
|          | ||||
|  | ||||
|         let mut repos = Vec::new(); | ||||
|          | ||||
|  | ||||
|         // Find all directories with .git subdirectories | ||||
|         let output = Command::new("find") | ||||
|             .args(&[&self.base_path, "-type", "d", "-name", ".git"]) | ||||
|             .output() | ||||
|             .map_err(GitError::CommandExecutionError)?; | ||||
|              | ||||
|  | ||||
|         if output.status.success() { | ||||
|             let stdout = String::from_utf8_lossy(&output.stdout); | ||||
|             for line in stdout.lines() { | ||||
| @@ -178,22 +176,25 @@ impl GitTree { | ||||
|             } | ||||
|         } else { | ||||
|             let error = String::from_utf8_lossy(&output.stderr); | ||||
|             return Err(GitError::GitCommandFailed(format!("Failed to find git repositories: {}", error))); | ||||
|             return Err(GitError::GitCommandFailed(format!( | ||||
|                 "Failed to find git repositories: {}", | ||||
|                 error | ||||
|             ))); | ||||
|         } | ||||
|          | ||||
|  | ||||
|         Ok(repos) | ||||
|     } | ||||
|      | ||||
|  | ||||
|     /// Finds repositories matching a pattern or partial path. | ||||
|     ///  | ||||
|     /// | ||||
|     /// # Arguments | ||||
|     ///  | ||||
|     /// | ||||
|     /// * `pattern` - The pattern to match against repository paths | ||||
|     ///   - If the pattern ends with '*', all matching repositories are returned | ||||
|     ///   - Otherwise, exactly one matching repository must be found | ||||
|     ///  | ||||
|     /// | ||||
|     /// # Returns | ||||
|     ///  | ||||
|     /// | ||||
|     /// * `Ok(Vec<String>)` - A vector of paths to matching repositories | ||||
|     /// * `Err(GitError)` - If no matching repositories are found, | ||||
|     ///   or if multiple repositories match a non-wildcard pattern | ||||
| @@ -212,7 +213,7 @@ impl GitTree { | ||||
|                 matched_repos.push(GitRepo::new(full_path)); | ||||
|             } | ||||
|         } else if pattern.ends_with('*') { | ||||
|             let prefix = &pattern[0..pattern.len()-1]; | ||||
|             let prefix = &pattern[0..pattern.len() - 1]; | ||||
|             for name in repo_names { | ||||
|                 if name.starts_with(prefix) { | ||||
|                     let full_path = format!("{}/{}", self.base_path, name); | ||||
| @@ -233,17 +234,17 @@ impl GitTree { | ||||
|  | ||||
|         Ok(matched_repos) | ||||
|     } | ||||
|      | ||||
|  | ||||
|     /// Gets one or more GitRepo objects based on a path pattern or URL. | ||||
|     ///  | ||||
|     /// | ||||
|     /// # Arguments | ||||
|     ///  | ||||
|     /// | ||||
|     /// * `path_or_url` - The path pattern to match against repository paths or a git URL | ||||
|     ///   - If it's a URL, the repository will be cloned if it doesn't exist | ||||
|     ///   - If it's a path pattern, it will find matching repositories | ||||
|     ///  | ||||
|     /// | ||||
|     /// # Returns | ||||
|     ///  | ||||
|     /// | ||||
|     /// * `Ok(Vec<GitRepo>)` - A vector of GitRepo objects | ||||
|     /// * `Err(GitError)` - If no matching repositories are found or the clone operation failed | ||||
|     pub fn get(&self, path_or_url: &str) -> Result<Vec<GitRepo>, GitError> { | ||||
| @@ -254,32 +255,35 @@ impl GitTree { | ||||
|             if server.is_empty() || account.is_empty() || repo.is_empty() { | ||||
|                 return Err(GitError::InvalidUrl(path_or_url.to_string())); | ||||
|             } | ||||
|              | ||||
|  | ||||
|             // Create the target directory | ||||
|             let clone_path = format!("{}/{}/{}/{}", self.base_path, server, account, repo); | ||||
|             let clone_dir = Path::new(&clone_path); | ||||
|              | ||||
|  | ||||
|             // Check if repo already exists | ||||
|             if clone_dir.exists() { | ||||
|                 return Ok(vec![GitRepo::new(clone_path)]); | ||||
|             } | ||||
|              | ||||
|  | ||||
|             // Create parent directory | ||||
|             if let Some(parent) = clone_dir.parent() { | ||||
|                 fs::create_dir_all(parent).map_err(GitError::FileSystemError)?; | ||||
|             } | ||||
|              | ||||
|  | ||||
|             // Clone the repository | ||||
|             let output = Command::new("git") | ||||
|                 .args(&["clone", "--depth", "1", path_or_url, &clone_path]) | ||||
|                 .output() | ||||
|                 .map_err(GitError::CommandExecutionError)?; | ||||
|                  | ||||
|  | ||||
|             if output.status.success() { | ||||
|                 Ok(vec![GitRepo::new(clone_path)]) | ||||
|             } else { | ||||
|                 let error = String::from_utf8_lossy(&output.stderr); | ||||
|                 Err(GitError::GitCommandFailed(format!("Git clone error: {}", error))) | ||||
|                 Err(GitError::GitCommandFailed(format!( | ||||
|                     "Git clone error: {}", | ||||
|                     error | ||||
|                 ))) | ||||
|             } | ||||
|         } else { | ||||
|             // It's a path pattern, find matching repositories using the updated self.find() | ||||
| @@ -357,7 +361,10 @@ impl GitRepo { | ||||
|             Ok(self.clone()) | ||||
|         } else { | ||||
|             let error = String::from_utf8_lossy(&output.stderr); | ||||
|             Err(GitError::GitCommandFailed(format!("Git pull error: {}", error))) | ||||
|             Err(GitError::GitCommandFailed(format!( | ||||
|                 "Git pull error: {}", | ||||
|                 error | ||||
|             ))) | ||||
|         } | ||||
|     } | ||||
|  | ||||
| @@ -382,7 +389,10 @@ impl GitRepo { | ||||
|  | ||||
|         if !reset_output.status.success() { | ||||
|             let error = String::from_utf8_lossy(&reset_output.stderr); | ||||
|             return Err(GitError::GitCommandFailed(format!("Git reset error: {}", error))); | ||||
|             return Err(GitError::GitCommandFailed(format!( | ||||
|                 "Git reset error: {}", | ||||
|                 error | ||||
|             ))); | ||||
|         } | ||||
|  | ||||
|         // Clean untracked files | ||||
| @@ -393,7 +403,10 @@ impl GitRepo { | ||||
|  | ||||
|         if !clean_output.status.success() { | ||||
|             let error = String::from_utf8_lossy(&clean_output.stderr); | ||||
|             return Err(GitError::GitCommandFailed(format!("Git clean error: {}", error))); | ||||
|             return Err(GitError::GitCommandFailed(format!( | ||||
|                 "Git clean error: {}", | ||||
|                 error | ||||
|             ))); | ||||
|         } | ||||
|  | ||||
|         Ok(self.clone()) | ||||
| @@ -429,7 +442,10 @@ impl GitRepo { | ||||
|  | ||||
|         if !add_output.status.success() { | ||||
|             let error = String::from_utf8_lossy(&add_output.stderr); | ||||
|             return Err(GitError::GitCommandFailed(format!("Git add error: {}", error))); | ||||
|             return Err(GitError::GitCommandFailed(format!( | ||||
|                 "Git add error: {}", | ||||
|                 error | ||||
|             ))); | ||||
|         } | ||||
|  | ||||
|         // Commit the changes | ||||
| @@ -440,7 +456,10 @@ impl GitRepo { | ||||
|  | ||||
|         if !commit_output.status.success() { | ||||
|             let error = String::from_utf8_lossy(&commit_output.stderr); | ||||
|             return Err(GitError::GitCommandFailed(format!("Git commit error: {}", error))); | ||||
|             return Err(GitError::GitCommandFailed(format!( | ||||
|                 "Git commit error: {}", | ||||
|                 error | ||||
|             ))); | ||||
|         } | ||||
|  | ||||
|         Ok(self.clone()) | ||||
| @@ -469,7 +488,10 @@ impl GitRepo { | ||||
|             Ok(self.clone()) | ||||
|         } else { | ||||
|             let error = String::from_utf8_lossy(&push_output.stderr); | ||||
|             Err(GitError::GitCommandFailed(format!("Git push error: {}", error))) | ||||
|             Err(GitError::GitCommandFailed(format!( | ||||
|                 "Git push error: {}", | ||||
|                 error | ||||
|             ))) | ||||
|         } | ||||
|     } | ||||
| } | ||||
|   | ||||
		Reference in New Issue
	
	Block a user