use auths_sdk::ports::git::{CommitRecord, GitLogProvider, GitProviderError, SignatureStatus};
use std::path::Path;
use std::sync::Mutex;
pub struct Git2LogProvider {
repo: Mutex<git2::Repository>,
}
impl Git2LogProvider {
pub fn open(path: &Path) -> Result<Self, GitProviderError> {
let repo =
git2::Repository::open(path).map_err(|e| GitProviderError::Open(e.to_string()))?;
Ok(Self {
repo: Mutex::new(repo),
})
}
}
impl GitLogProvider for Git2LogProvider {
fn walk_commits(
&self,
range: Option<&str>,
limit: Option<usize>,
) -> Result<Vec<CommitRecord>, GitProviderError> {
let repo = self
.repo
.lock()
.map_err(|_| GitProviderError::LockPoisoned)?;
let mut revwalk = repo
.revwalk()
.map_err(|e| GitProviderError::Walk(e.to_string()))?;
revwalk
.set_sorting(git2::Sort::TOPOLOGICAL | git2::Sort::TIME)
.map_err(|e| GitProviderError::Walk(e.to_string()))?;
if let Some(range_spec) = range {
revwalk
.push_range(range_spec)
.map_err(|e| GitProviderError::Walk(e.to_string()))?;
} else {
revwalk
.push_head()
.map_err(|e| GitProviderError::Walk(e.to_string()))?;
}
let mut records = Vec::new();
let max = limit.unwrap_or(usize::MAX);
for oid_result in revwalk {
if records.len() >= max {
break;
}
let oid = oid_result.map_err(|e| GitProviderError::Walk(e.to_string()))?;
let commit = repo
.find_commit(oid)
.map_err(|e| GitProviderError::NotFound(e.to_string()))?;
let signature_status = classify_signature(&repo, &commit);
let author = commit.author();
records.push(CommitRecord {
hash: oid.to_string()[..7].to_string(),
author_name: author.name().unwrap_or("unknown").to_string(),
author_email: author.email().unwrap_or("").to_string(),
timestamp: format_commit_time(&commit),
message: commit.summary().unwrap_or("").to_string(),
signature_status,
});
}
Ok(records)
}
}
fn classify_signature(repo: &git2::Repository, commit: &git2::Commit) -> SignatureStatus {
match repo.extract_signature(&commit.id(), None) {
Ok(sig_buf) => {
let sig_bytes = sig_buf.0;
let sig_str = String::from_utf8_lossy(sig_bytes.as_ref());
if sig_str.contains("-----BEGIN SSH SIGNATURE-----") {
if sig_str.contains("auths") {
SignatureStatus::AuthsSigned {
signer_did: String::new(),
}
} else {
SignatureStatus::SshSigned
}
} else if sig_str.contains("-----BEGIN PGP SIGNATURE-----") {
SignatureStatus::GpgSigned { verified: false }
} else {
SignatureStatus::InvalidSignature {
reason: "unknown signature format".to_string(),
}
}
}
Err(_) => SignatureStatus::Unsigned,
}
}
fn format_commit_time(commit: &git2::Commit) -> String {
let time = commit.time();
let secs = time.seconds();
let offset_minutes = time.offset_minutes();
let offset_hours = offset_minutes / 60;
let offset_mins = (offset_minutes % 60).abs();
let sign = if offset_minutes >= 0 { '+' } else { '-' };
format!(
"{}{}{}:{:02}",
chrono::DateTime::from_timestamp(secs, 0)
.map(|dt| dt.format("%Y-%m-%dT%H:%M:%S").to_string())
.unwrap_or_else(|| secs.to_string()),
sign,
offset_hours.abs(),
offset_mins
)
}