- Run cargo fmt on all crates - Fix regex patterns using unsupported lookahead in patterns.rs - Replace unwrap() calls with compile_regex() helper - Fix never type fallback in GitHub tracker - Fix redundant field name in findings page - Allow enum_variant_names for Dioxus Route enum - Fix &mut Vec -> &mut [T] clippy lint in sbom.rs - Mark unused-but-intended APIs with #[allow(dead_code)] Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
97 lines
2.9 KiB
Rust
97 lines
2.9 KiB
Rust
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<PathBuf, AgentError> {
|
|
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<String, AgentError> {
|
|
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<bool, AgentError> {
|
|
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<Vec<String>, 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)
|
|
}
|
|
}
|