use anyhow::{Context, Result};
use clap::Subcommand;
use std::fs;
use crate::utils::{confirm, find_repo_root};
const PRESET_LF: &str = "* text=auto eol=lf\n";
const PRESET_BINARY: &str = "\
*.png binary\n\
*.jpg binary\n\
*.jpeg binary\n\
*.gif binary\n\
*.ico binary\n\
*.pdf binary\n\
*.zip binary\n\
*.tar binary\n\
*.gz binary\n\
*.wasm binary\n\
";
#[derive(Subcommand)]
pub enum AttributesCommand {
Init {
#[arg(short, long)]
yes: bool,
#[arg(short, long)]
force: bool,
#[arg(long)]
dry_run: bool,
},
}
pub fn run(cmd: AttributesCommand) -> Result<()> {
let AttributesCommand::Init {
yes,
force,
dry_run,
} = cmd;
let root = find_repo_root()?;
let path = root.join(".gitattributes");
if path.exists() && !force {
if !confirm(".gitattributes already exists. Overwrite?", yes) {
println!("Aborted.");
return Ok(());
}
if !dry_run {
let backup = root.join(".gitattributes.bak");
std::fs::copy(&path, &backup).context("Failed to backup .gitattributes")?;
println!("Backed up to {}", backup.display());
}
}
if dry_run {
println!("[dry-run] Would write .gitattributes:\n{PRESET_LF}");
return Ok(());
}
fs::write(&path, PRESET_LF).context("Failed to write .gitattributes")?;
println!("Applied line endings preset to .gitattributes.");
Ok(())
}
pub(crate) fn apply_presets(labels: &[&str]) -> Result<()> {
let root = find_repo_root()?;
let path = root.join(".gitattributes");
let existing = if path.exists() {
fs::read_to_string(&path).unwrap_or_default()
} else {
String::new()
};
let mut content = existing;
for label in labels {
let preset = match *label {
"line-endings" => PRESET_LF,
"binary-files" => PRESET_BINARY,
_ => continue,
};
if !content.contains(preset.lines().next().unwrap_or("")) {
if !content.ends_with('\n') && !content.is_empty() {
content.push('\n');
}
content.push_str(preset);
}
}
fs::write(&path, content).context("Failed to write .gitattributes")?;
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
use tempfile::TempDir;
fn make_git_repo() -> TempDir {
let dir = TempDir::new().unwrap();
std::fs::create_dir(dir.path().join(".git")).unwrap();
dir
}
#[test]
fn attributes_init_dry_run_does_not_write_file() {
let dir = make_git_repo();
let path = dir.path().join(".gitattributes");
assert_eq!(PRESET_LF, "* text=auto eol=lf\n");
assert!(!path.exists());
}
#[test]
fn attributes_preset_contains_lf_rule() {
assert!(PRESET_LF.contains("eol=lf"));
assert!(PRESET_LF.contains("text=auto"));
}
#[test]
fn attributes_binary_preset_marks_png() {
assert!(PRESET_BINARY.contains("*.png binary"));
}
}