Initial commit: Compliance Scanner Agent
Autonomous security and compliance scanning agent for git repositories. Features: SAST (Semgrep), SBOM (Syft), CVE monitoring (OSV.dev/NVD), GDPR/OAuth pattern detection, LLM triage, issue creation (GitHub/GitLab/Jira), PR reviews, and Dioxus fullstack dashboard. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
100
compliance-agent/src/pipeline/git.rs
Normal file
100
compliance-agent/src/pipeline/git.rs
Normal file
@@ -0,0 +1,100 @@
|
||||
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),
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user