corpora-engine 0.1.0

Bus orchestrator and IO edges (parser, fs walk, writer, git oracle) for corpora.
Documentation
//! Git-backed [`RevisionOracle`] — the trait lives in `corpora-core`; this is the IO impl.
//! It shells out to `git` (no heavy VCS dependency); resolution is `git rev-parse`.

use std::path::PathBuf;
use std::process::Command;

use corpora_core::{OracleStatus, Rev, RevisionOracle};

pub struct GitOracle {
    root: PathBuf,
}

impl GitOracle {
    pub fn new(root: impl Into<PathBuf>) -> Self {
        GitOracle { root: root.into() }
    }

    /// Run `git -C <root> <args>`, returning trimmed stdout on success.
    fn run(&self, args: &[&str]) -> Option<String> {
        let out = Command::new("git")
            .arg("-C")
            .arg(&self.root)
            .args(args)
            .output()
            .ok()?;
        if !out.status.success() {
            return None;
        }
        let s = String::from_utf8_lossy(&out.stdout).trim().to_string();
        (!s.is_empty()).then_some(s)
    }

    /// Is the `git` binary itself runnable? Checked without `-C` so a missing repo dir
    /// doesn't masquerade as a missing binary.
    fn git_present(&self) -> bool {
        Command::new("git")
            .arg("--version")
            .output()
            .map(|o| o.status.success())
            .unwrap_or(false)
    }
}

impl RevisionOracle for GitOracle {
    fn status(&self) -> OracleStatus {
        if !self.git_present() {
            OracleStatus::Unavailable
        } else if self.run(&["rev-parse", "--git-dir"]).is_none() {
            OracleStatus::NoRepo
        } else {
            OracleStatus::Ready
        }
    }

    fn resolve(&self, r: &Rev) -> Option<Rev> {
        // `^{commit}` forces the rev to name an actual commit; `--end-of-options` stops a
        // rev that begins with `-` from being parsed as a flag.
        let spec = format!("{}^{{commit}}", r.0);
        self.run(&["rev-parse", "--verify", "--quiet", "--end-of-options", &spec])
            .map(Rev)
    }
}