use lazy_static::lazy_static; use sal::git::{GitRepo, GitTree}; use std::collections::HashMap; use std::path::{Path, PathBuf}; use std::sync::{Arc, Mutex}; use std::time::{ SystemTime}; use crate::error::{Result, WebBuilderError}; // Cache entry for Git repositories struct CacheEntry { path: PathBuf, last_updated: SystemTime, } // Global cache for Git repositories lazy_static! { static ref REPO_CACHE: Arc>> = Arc::new(Mutex::new(HashMap::new())); } // Cache timeout in seconds (default: 1 hour) const CACHE_TIMEOUT: u64 = 3600; /// Clone a Git repository /// /// # Arguments /// /// * `url` - URL of the repository to clone /// * `destination` - Destination directory /// /// # Returns /// /// The path to the cloned repository or an error pub fn clone_repository>(url: &str, destination: P) -> Result { let destination = destination.as_ref(); let destination_str = destination.to_str().unwrap(); // Create a GitTree for the parent directory let parent_dir = destination.parent().ok_or_else(|| { WebBuilderError::InvalidConfiguration(format!( "Invalid destination path: {}", destination_str )) })?; let git_tree = GitTree::new(parent_dir.to_str().unwrap()) .map_err(|e| WebBuilderError::GitError(format!("Failed to create GitTree: {}", e)))?; // Use the GitTree to get (clone) the repository let repos = git_tree .get(url) .map_err(|e| WebBuilderError::GitError(format!("Failed to clone repository: {}", e)))?; if repos.is_empty() { return Err(WebBuilderError::GitError(format!( "Failed to clone repository: No repository was created" ))); } // Return the path of the first repository Ok(PathBuf::from(repos[0].path())) } /// Pull the latest changes from a Git repository /// /// # Arguments /// /// * `path` - Path to the repository /// /// # Returns /// /// Ok(()) on success or an error pub fn pull_repository>(path: P) -> Result<()> { let path = path.as_ref(); let path_str = path.to_str().unwrap(); // Create a GitRepo directly let repo = GitRepo::new(path_str.to_string()); // Pull the repository repo.pull() .map_err(|e| WebBuilderError::GitError(format!("Failed to pull repository: {}", e)))?; Ok(()) } /// Clone or pull a Git repository with caching /// /// # Arguments /// /// * `url` - URL of the repository to clone /// * `destination` - Destination directory /// /// # Returns /// /// The path to the repository or an error pub fn clone_or_pull>(url: &str, destination: P) -> Result { let destination = destination.as_ref(); // Check the cache first let mut cache = REPO_CACHE.lock().unwrap(); let now = SystemTime::now(); if let Some(entry) = cache.get(url) { // Check if the cache entry is still valid if let Ok(elapsed) = now.duration_since(entry.last_updated) { if elapsed.as_secs() < CACHE_TIMEOUT { // Cache is still valid, return the cached path log::info!("Using cached repository for {}", url); return Ok(entry.path.clone()); } } } // Cache miss or expired, clone or pull the repository let result = if destination.exists() { // Pull the repository pull_repository(destination)?; Ok(destination.to_path_buf()) } else { // Clone the repository clone_repository(url, destination) }; // Update the cache if let Ok(path) = &result { cache.insert( url.to_string(), CacheEntry { path: path.clone(), last_updated: now, }, ); } result } /// Force update a Git repository, bypassing the cache /// /// # Arguments /// /// * `url` - URL of the repository to clone /// * `destination` - Destination directory /// /// # Returns /// /// The path to the repository or an error pub fn force_update>(url: &str, destination: P) -> Result { let destination = destination.as_ref(); // Clone or pull the repository let result = if destination.exists() { // Pull the repository pull_repository(destination)?; Ok(destination.to_path_buf()) } else { // Clone the repository clone_repository(url, destination) }; // Update the cache if let Ok(path) = &result { let mut cache = REPO_CACHE.lock().unwrap(); cache.insert( url.to_string(), CacheEntry { path: path.clone(), last_updated: SystemTime::now(), }, ); } result } /// Clear the Git repository cache pub fn clear_cache() { let mut cache = REPO_CACHE.lock().unwrap(); cache.clear(); }