use serde::{Deserialize, Serialize};
use std::process::Command;
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct GitInfo {
pub commit: String,
pub branch: String,
pub dirty: bool,
}
impl GitInfo {
pub fn capture() -> Self {
let mut info = Self::default();
if let Some(out) = run_git(&["rev-parse", "HEAD"]) {
info.commit = out;
}
if let Some(out) = run_git(&["symbolic-ref", "--short", "HEAD"]) {
info.branch = out;
}
if let Some(out) = run_git(&["status", "--porcelain"]) {
info.dirty = !out.is_empty();
}
info
}
pub fn changed_files(reference: &str) -> Option<Vec<String>> {
let args: Vec<&str> = if reference.is_empty() {
vec!["status", "--porcelain"]
} else {
vec!["diff", "--name-only", reference, "HEAD"]
};
let raw = run_git(&args)?;
if reference.is_empty() {
let mut files = Vec::new();
for line in raw.lines() {
if line.len() <= 3 {
continue;
}
let path = &line[3..];
files.push(path.trim().to_string());
}
Some(files)
} else {
Some(
raw
.lines()
.map(|s| s.trim().to_string())
.filter(|s| !s.is_empty())
.collect(),
)
}
}
}
fn run_git(args: &[&str]) -> Option<String> {
let out = Command::new("git").args(args).output().ok()?;
if !out.status.success() {
return None;
}
let stdout = String::from_utf8(out.stdout).ok()?;
Some(stdout.trim().to_string())
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn capture_in_a_repo_returns_a_commit_or_empty_default() {
let info = GitInfo::capture();
let _ = info.commit.len();
}
}