use std::path::Path;
use std::process::{Command, Stdio};
#[derive(Debug, Clone, Default, PartialEq, Eq)]
pub struct GitMeta {
pub branch: Option<String>,
pub sha: Option<String>,
pub dirty: Option<bool>,
}
impl GitMeta {
#[must_use]
pub fn capture(cwd: &Path) -> Self {
let branch = git_output(cwd, &["rev-parse", "--abbrev-ref", "HEAD"]);
let branch = branch.and_then(|b| {
let trimmed = b.trim();
if trimmed.is_empty() || trimmed == "HEAD" {
None
} else {
Some(trimmed.to_string())
}
});
let sha = git_output(cwd, &["rev-parse", "HEAD"])
.map(|s| s.trim().to_string())
.filter(|s| !s.is_empty());
let dirty = git_exit_ok(cwd, &["diff", "--quiet"]).map(|clean| !clean);
Self { branch, sha, dirty }
}
}
fn git_output(cwd: &Path, args: &[&str]) -> Option<String> {
Command::new("git")
.args(args)
.current_dir(cwd)
.stderr(Stdio::null())
.output()
.ok()
.filter(|o| o.status.success())
.and_then(|o| String::from_utf8(o.stdout).ok())
}
fn git_exit_ok(cwd: &Path, args: &[&str]) -> Option<bool> {
Command::new("git")
.args(args)
.current_dir(cwd)
.stderr(Stdio::null())
.status()
.ok()
.map(|s| s.success())
}
#[cfg(test)]
mod tests {
use super::*;
use std::env;
#[test]
fn capture_in_repo_yields_metadata_or_none() {
let cwd = env::current_dir().unwrap();
let meta = GitMeta::capture(&cwd);
if let Some(sha) = &meta.sha {
assert!(!sha.is_empty());
}
if meta.sha.is_some() {
assert!(meta.dirty.is_some());
}
}
}