use std::path::{Path, PathBuf}; use git2::{FetchOptions, Repository}; use crate::error::AgentError; pub struct GitOps { base_path: PathBuf, } impl GitOps { pub fn new(base_path: &str) -> Self { Self { base_path: PathBuf::from(base_path), } } pub fn clone_or_fetch(&self, git_url: &str, repo_name: &str) -> Result { let repo_path = self.base_path.join(repo_name); if repo_path.exists() { self.fetch(&repo_path)?; } else { std::fs::create_dir_all(&repo_path)?; Repository::clone(git_url, &repo_path)?; tracing::info!("Cloned {git_url} to {}", repo_path.display()); } Ok(repo_path) } fn fetch(&self, repo_path: &Path) -> Result<(), AgentError> { let repo = Repository::open(repo_path)?; let mut remote = repo.find_remote("origin")?; let mut fetch_opts = FetchOptions::new(); remote.fetch(&[] as &[&str], Some(&mut fetch_opts), None)?; // Fast-forward to origin/HEAD let fetch_head = repo.find_reference("FETCH_HEAD")?; let fetch_commit = repo.reference_to_annotated_commit(&fetch_head)?; let head_ref = repo.head()?; let head_name = head_ref.name().unwrap_or("HEAD"); repo.reference(head_name, fetch_commit.id(), true, "fast-forward")?; repo.checkout_head(Some(git2::build::CheckoutBuilder::default().force()))?; tracing::info!("Fetched and fast-forwarded {}", repo_path.display()); Ok(()) } pub fn get_head_sha(repo_path: &Path) -> Result { let repo = Repository::open(repo_path)?; let head = repo.head()?; let commit = head.peel_to_commit()?; Ok(commit.id().to_string()) } pub fn has_new_commits(repo_path: &Path, last_sha: Option<&str>) -> Result { let current_sha = Self::get_head_sha(repo_path)?; match last_sha { Some(sha) if sha == current_sha => Ok(false), _ => Ok(true), } } #[allow(dead_code)] pub fn get_changed_files( repo_path: &Path, old_sha: &str, new_sha: &str, ) -> Result, AgentError> { let repo = Repository::open(repo_path)?; let old_commit = repo.find_commit(git2::Oid::from_str(old_sha)?)?; let new_commit = repo.find_commit(git2::Oid::from_str(new_sha)?)?; let old_tree = old_commit.tree()?; let new_tree = new_commit.tree()?; let diff = repo.diff_tree_to_tree(Some(&old_tree), Some(&new_tree), None)?; let mut files = Vec::new(); diff.foreach( &mut |delta, _| { if let Some(path) = delta.new_file().path() { files.push(path.to_string_lossy().to_string()); } true }, None, None, None, )?; Ok(files) } }