use crate::args::Args;
use crate::git;
use crate::types::{Operation, OperationResult, RepositoryOperation};
use tracing::{debug, info};
use std::path::PathBuf;
use std::sync::Arc;
use tokio::sync::Semaphore;
pub struct OperationExecutor {
max_concurrency: usize,
}
impl OperationExecutor {
pub fn new(max_concurrency: usize) -> Self {
Self { max_concurrency }
}
pub async fn execute(&self, args: Args) -> anyhow::Result<()> {
info!("Finding repositories in {}", args.source_dir.display());
let repositories = git::find_git_repositories(&args.source_dir).await?;
if repositories.is_empty() {
info!("No Git repositories found in {}", args.source_dir.display());
return Ok(());
}
info!("Found {} Git repositories", repositories.len());
let op_type = args.operation.clone();
let args_clone = args.clone();
let filter = args.filter.clone();
let semaphore = Arc::new(Semaphore::new(self.max_concurrency));
let mut handles = vec![];
for repo in repositories {
if !repo.is_git_repository() {
info!("Skipping non-Git directory: {}", repo.path.display());
continue;
}
if let Some(pattern) = &filter {
let repo_name = repo.path.file_name().unwrap().to_string_lossy();
if !repo_name.contains(pattern) {
debug!("Filtering out repository: {}", repo.path.display());
continue;
}
}
let permit = semaphore.clone().acquire_owned().await.unwrap();
let repo_path = repo.path.clone();
let op_type_clone = op_type.clone();
let args_clone2 = args_clone.clone();
let handle = tokio::spawn(async move {
let repo_path_clone = repo_path.clone();
info!("Running operation on {}", repo_path.display());
let operation = crate::operations::create_operation(&op_type_clone, &args_clone2);
let result = operation.execute(&repo_path).await;
match result {
Ok(OperationResult::Success(msg)) => {
info!("✅ Success: {}", msg);
}
Ok(OperationResult::Failure(msg)) => {
info!("❌ Failed: {}", msg);
}
Ok(OperationResult::Skipped(msg)) => {
info!("⏭️ Skipped: {}", msg);
}
Err(e) => {
info!("❌ Error: {} - {}", repo_path_clone.display(), e);
}
}
drop(permit);
});
handles.push(handle);
}
for handle in handles {
handle.await?;
}
Ok(())
}
fn _filter_repositories<'a>(&self,
repositories: &'a [git::Repository],
_operation: &Operation,
pattern: Option<&str>
) -> Vec<&'a git::Repository> {
repositories.iter()
.filter(|repo| {
if let Some(pattern) = pattern {
let repo_name = repo.path.file_name().unwrap().to_string_lossy();
if !repo_name.contains(pattern) && !regex::Regex::new(®ex::escape(pattern)).unwrap().is_match(&repo_name) {
return false;
}
}
true
})
.collect()
}
}