use anyhow::{bail, Context, Result};
pub fn staged_files() -> Result<Vec<String>> {
run_name_only(&["diff", "--name-only", "--diff-filter=ACMR", "--cached"])
}
pub fn changed_in_range(base: &str) -> Result<Vec<String>> {
let range = format!("{base}...HEAD");
run_name_only(&["diff", "--name-only", "--diff-filter=ACMR", &range])
}
pub fn staged_content(path: &str) -> Result<Option<String>> {
let output = std::process::Command::new("git")
.args(["show", &format!(":{path}")])
.output()
.context("cannot run git show")?;
if !output.status.success() {
return Ok(None);
}
Ok(String::from_utf8(output.stdout).ok())
}
fn run_name_only(args: &[&str]) -> Result<Vec<String>> {
let output = std::process::Command::new("git")
.args(args)
.output()
.context("cannot run git diff")?;
if !output.status.success() {
bail!(
"git diff failed: {}",
String::from_utf8_lossy(&output.stderr).trim()
);
}
Ok(String::from_utf8_lossy(&output.stdout)
.lines()
.map(str::to_owned)
.collect())
}
pub fn git_dir() -> Option<std::path::PathBuf> {
let output = std::process::Command::new("git")
.args(["rev-parse", "--absolute-git-dir"])
.output()
.ok()?;
if !output.status.success() {
return None;
}
let text = String::from_utf8(output.stdout).ok()?;
let trimmed = text.trim();
(!trimmed.is_empty()).then(|| std::path::PathBuf::from(trimmed))
}
pub fn hooks_path_override() -> Option<String> {
let output = std::process::Command::new("git")
.args(["config", "core.hooksPath"])
.output()
.ok()?;
if !output.status.success() {
return None;
}
let text = String::from_utf8(output.stdout).ok()?;
let trimmed = text.trim();
(!trimmed.is_empty()).then(|| trimmed.to_owned())
}