use std::io::Write;
use std::path::Path;
use anyhow::{Result, anyhow};
use crate::diff;
const ENTRY: &str = "drep.toml";
const COMMENT: &str = "# drep's local config: provider and model choice, no secrets.";
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum Outcome {
Added,
Created,
AlreadyIgnored,
Tracked,
}
impl Outcome {
pub fn message(&self) -> String {
match self {
Self::Added => format!("✓ Added {ENTRY} to .gitignore"),
Self::Created => format!("✓ Created .gitignore with {ENTRY}"),
Self::AlreadyIgnored => format!("· {ENTRY} is already ignored"),
Self::Tracked => format!(
"! {ENTRY} is tracked by git, so .gitignore will not affect it.\n \
Run `git rm --cached {ENTRY}` to stop tracking it, keeping the file."
),
}
}
}
pub async fn ensure(root: &Path) -> Result<Outcome> {
if is_tracked(root).await? {
return Ok(Outcome::Tracked);
}
if is_ignored(root).await? {
return Ok(Outcome::AlreadyIgnored);
}
append(root)
}
pub async fn ensure_to<W: Write>(out: &mut W, root: &Path) -> Result<Outcome> {
let outcome = ensure(root).await?;
writeln!(out, "{}", outcome.message())?;
Ok(outcome)
}
async fn git_says_yes(root: &Path, args: &[&str], question: &str) -> Result<bool> {
diff::git_query(root, args)
.await
.map(|answer| answer.is_some())
.map_err(|err| anyhow!("could not ask git whether {ENTRY} is {question}: {err}"))
}
async fn is_ignored(root: &Path) -> Result<bool> {
git_says_yes(root, &["check-ignore", "--quiet", "--", ENTRY], "ignored").await
}
async fn is_tracked(root: &Path) -> Result<bool> {
git_says_yes(
root,
&["ls-files", "--error-unmatch", "--", ENTRY],
"tracked",
)
.await
}
fn append(root: &Path) -> Result<Outcome> {
let path = root.join(".gitignore");
let existing = match std::fs::read_to_string(&path) {
Ok(content) => Some(content),
Err(err) if err.kind() == std::io::ErrorKind::NotFound => None,
Err(err) => return Err(anyhow!("could not read {}: {err}", path.display())),
};
let created = existing.is_none();
let mut body = existing.unwrap_or_default();
if !body.is_empty() {
if !body.ends_with('\n') {
body.push('\n');
}
body.push('\n');
}
body.push_str(COMMENT);
body.push('\n');
body.push_str(ENTRY);
body.push('\n');
std::fs::write(&path, body)
.map_err(|err| anyhow!("could not write {}: {err}", path.display()))?;
Ok(if created {
Outcome::Created
} else {
Outcome::Added
})
}