use anyhow::Result;
use std::path::Path;
use std::process::Command;
const AID_EXCLUDE_ENTRIES: &[&str] =
&[".aid-*", "aid-batch-*", "result-t-*.md", "result-t-*.json", ".aid/state.toml", ".aid/batches/"];
const AID_EXCLUDE_HEADER: &str = "# aid: runtime files written into this repo by ai-dispatch";
pub fn ensure_aid_paths_excluded(dir: &Path) {
if let Err(err) = write_exclude_entries(dir) {
aid_warn!("[aid] could not add aid's runtime files to {}'s git exclude: {err}", dir.display());
}
}
fn write_exclude_entries(dir: &Path) -> Result<()> {
let common_dir = git_common_dir(dir)?;
let exclude_path = common_dir.join("info").join("exclude");
let existing = std::fs::read_to_string(&exclude_path).unwrap_or_default();
let missing: Vec<&str> = AID_EXCLUDE_ENTRIES
.iter()
.filter(|entry| !existing.lines().any(|line| line.trim() == **entry))
.copied()
.collect();
if missing.is_empty() {
return Ok(());
}
if let Some(parent) = exclude_path.parent() {
std::fs::create_dir_all(parent)?;
}
let mut out = existing;
if !out.is_empty() && !out.ends_with('\n') {
out.push('\n');
}
if !out.contains(AID_EXCLUDE_HEADER) {
out.push_str(AID_EXCLUDE_HEADER);
out.push('\n');
}
for entry in missing {
out.push_str(entry);
out.push('\n');
}
std::fs::write(&exclude_path, out)?;
Ok(())
}
fn git_common_dir(dir: &Path) -> Result<std::path::PathBuf> {
let output = Command::new("git")
.args(["-C", &dir.to_string_lossy(), "rev-parse", "--git-common-dir"])
.output()?;
anyhow::ensure!(output.status.success(), "git rev-parse --git-common-dir failed");
let raw = String::from_utf8_lossy(&output.stdout).trim().to_string();
anyhow::ensure!(!raw.is_empty(), "git reported no common dir");
let path = std::path::PathBuf::from(&raw);
Ok(if path.is_absolute() { path } else { dir.join(path) })
}
#[cfg(test)]
#[path = "exclude_tests.rs"]
mod tests;