use anyhow::{anyhow, Result};
pub const MANAGED_BLOCK_BEGIN: &str = "# >>> git-hook-installer managed block >>>";
pub const MANAGED_BLOCK_END: &str = "# <<< git-hook-installer managed block <<<";
pub fn ensure_shebang(contents: &str) -> String {
let first_line = contents.lines().next().unwrap_or_default();
if first_line.starts_with("#!") {
return contents.to_string();
}
format!("#!/usr/bin/env bash\n{contents}")
}
pub fn upsert_managed_block(existing: &str, block: &str) -> String {
let mut lines: Vec<&str> = existing.lines().collect();
let mut start_idx: Option<usize> = None;
let mut end_idx: Option<usize> = None;
for (idx, line) in lines.iter().enumerate() {
if *line == MANAGED_BLOCK_BEGIN {
start_idx = Some(idx);
continue;
}
if *line == MANAGED_BLOCK_END {
end_idx = Some(idx);
break;
}
}
let block_lines: Vec<&str> = block.lines().collect();
match (start_idx, end_idx) {
(Some(start), Some(end)) if start <= end => {
lines.splice(start..=end, block_lines);
ensure_shebang(&normalize_newline_join(&lines))
}
_ => {
let insert_at = if !lines.is_empty() && lines[0].starts_with("#!") {
1
} else {
0
};
let mut out: Vec<&str> = Vec::with_capacity(lines.len() + block_lines.len() + 2);
out.extend_from_slice(&lines[..insert_at]);
if insert_at > 0 {
out.push("");
}
out.extend_from_slice(&block_lines);
out.push("");
out.extend_from_slice(&lines[insert_at..]);
ensure_shebang(&normalize_newline_join(&out))
}
}
}
pub fn uninstall_managed_block(existing: &str) -> Result<String> {
let lines: Vec<&str> = existing.lines().collect();
let mut start_idx: Option<usize> = None;
let mut end_idx: Option<usize> = None;
for (idx, line) in lines.iter().enumerate() {
if *line == MANAGED_BLOCK_BEGIN {
start_idx = Some(idx);
continue;
}
if *line == MANAGED_BLOCK_END {
end_idx = Some(idx);
break;
}
}
let (Some(start), Some(end)) = (start_idx, end_idx) else {
return Err(anyhow!(
"No managed git-hook-installer block found in pre-commit hook"
));
};
if start > end {
return Err(anyhow!("Invalid managed block markers in pre-commit hook"));
}
let mut out = lines;
out.splice(start..=end, []);
Ok(normalize_newline_join(&out))
}
pub fn disable_managed_block(existing: &str) -> Result<String> {
let lines: Vec<&str> = existing.lines().collect();
let mut start_idx: Option<usize> = None;
let mut end_idx: Option<usize> = None;
for (idx, line) in lines.iter().enumerate() {
if *line == MANAGED_BLOCK_BEGIN {
start_idx = Some(idx);
continue;
}
if *line == MANAGED_BLOCK_END {
end_idx = Some(idx);
break;
}
}
let (Some(start), Some(end)) = (start_idx, end_idx) else {
return Err(anyhow!(
"No managed git-hook-installer block found in pre-commit hook"
));
};
if start > end {
return Err(anyhow!("Invalid managed block markers in pre-commit hook"));
}
let mut did_change = false;
let mut out = Vec::with_capacity(lines.len());
for (idx, line) in lines.iter().enumerate() {
if idx < start || idx > end {
out.push(*line);
continue;
}
if line.trim_start().starts_with("GHI_ENABLED=") {
out.push("GHI_ENABLED=0");
did_change = true;
continue;
}
out.push(*line);
}
if !did_change {
return Err(anyhow!(
"Managed block found, but no GHI_ENABLED setting line was found"
));
}
Ok(normalize_newline_join(&out))
}
fn normalize_newline_join(lines: &[&str]) -> String {
let mut out = lines.join("\n");
if !out.ends_with('\n') {
out.push('\n');
}
out
}
#[cfg(test)]
mod tests {
use super::*;
use anyhow::Result;
#[test]
fn ensure_shebang_prefers_env_bash_when_missing() -> Result<()> {
let contents = "echo hi\n";
let updated = ensure_shebang(contents);
assert!(updated.starts_with("#!/usr/bin/env bash\n"));
Ok(())
}
#[test]
fn upsert_managed_block_inserts_after_shebang_when_missing() -> Result<()> {
let existing = "#!/bin/sh\necho hi\n";
let block = format!("{MANAGED_BLOCK_BEGIN}\nGHI_ENABLED=1\n{MANAGED_BLOCK_END}\n");
let updated = upsert_managed_block(existing, &block);
assert!(updated.starts_with("#!/bin/sh\n\n# >>> git-hook-installer managed block >>>"));
Ok(())
}
#[test]
fn uninstall_managed_block_removes_only_the_managed_section() -> Result<()> {
let existing = "#!/bin/sh\n# >>> git-hook-installer managed block >>>\nGHI_ENABLED=1\n# <<< git-hook-installer managed block <<<\necho hi\n";
let updated = uninstall_managed_block(existing)?;
assert!(updated.contains("echo hi"));
assert!(!updated.contains(MANAGED_BLOCK_BEGIN));
Ok(())
}
#[test]
fn disable_managed_block_sets_enabled_to_zero() -> Result<()> {
let existing = "#!/bin/sh\n# >>> git-hook-installer managed block >>>\nGHI_ENABLED=1\n# <<< git-hook-installer managed block <<<\n";
let updated = disable_managed_block(existing)?;
assert!(updated.contains("GHI_ENABLED=0\n"));
Ok(())
}
}