use std::path::{Path, PathBuf};
use std::process::Stdio;
use async_trait::async_trait;
use crate::orchestration::execution_facts::ExecutionFactsStore;
#[derive(Debug, Clone, Default, PartialEq, Eq)]
pub struct ApplyCommitEvidence {
pub present: Option<bool>,
pub oid: Option<String>,
}
impl ApplyCommitEvidence {
pub fn unknown() -> Self {
Self::default()
}
pub fn proven(oid: impl Into<String>) -> Self {
Self {
present: Some(true),
oid: Some(oid.into()),
}
}
}
#[async_trait]
pub trait ApplyCommitEvidencePort: Send + Sync {
async fn observe(&self, change_id: &str, oid: &str) -> ApplyCommitEvidence;
}
pub struct GitApplyCommitEvidence {
repo_root: PathBuf,
}
impl GitApplyCommitEvidence {
pub fn new(repo_root: PathBuf) -> Self {
Self { repo_root }
}
async fn is_ancestor_of_head(worktree: &Path, oid: &str) -> bool {
let Ok(output) = tokio::process::Command::new("git")
.args(["merge-base", "--is-ancestor", oid, "HEAD"])
.current_dir(worktree)
.stdin(Stdio::null())
.output()
.await
else {
return false;
};
output.status.success()
}
}
#[async_trait]
impl ApplyCommitEvidencePort for GitApplyCommitEvidence {
async fn observe(&self, change_id: &str, oid: &str) -> ApplyCommitEvidence {
if oid.trim().is_empty() {
return ApplyCommitEvidence::unknown();
}
let worktree =
match crate::vcs::git::get_worktree_path_for_change(&self.repo_root, change_id).await {
Ok(Some(path)) => path,
Ok(None) | Err(_) => return ApplyCommitEvidence::unknown(),
};
if !worktree.is_dir() {
return ApplyCommitEvidence::unknown();
}
if Self::is_ancestor_of_head(&worktree, oid.trim()).await {
ApplyCommitEvidence::proven(oid.trim())
} else {
ApplyCommitEvidence::unknown()
}
}
}
pub async fn observe_apply_commit(
facts: Option<&ExecutionFactsStore>,
port: Option<&dyn ApplyCommitEvidencePort>,
change_id: &str,
) -> ApplyCommitEvidence {
let (Some(facts), Some(port)) = (facts, port) else {
return ApplyCommitEvidence::unknown();
};
let Some(oid) = facts.apply_commit_oid(change_id) else {
return ApplyCommitEvidence::unknown();
};
port.observe(change_id, &oid).await
}
#[cfg(test)]
mod tests;