#![allow(dead_code)]
use std::path::{Path, PathBuf};
use std::process::{Command, Output, Stdio};
pub struct Repo {
pub dir: PathBuf,
}
fn unique() -> String {
use std::sync::atomic::{AtomicU64, Ordering};
static N: AtomicU64 = AtomicU64::new(0);
format!(
"amont-test-{}-{}",
std::process::id(),
N.fetch_add(1, Ordering::Relaxed)
)
}
impl Repo {
pub fn new() -> Self {
let dir = std::env::temp_dir().join(unique());
let _ = std::fs::remove_dir_all(&dir);
std::fs::create_dir_all(&dir).expect("create temp repo");
let r = Repo { dir };
r.git(&["init", "-q", "--template=", "--initial-branch=main", "."]);
r.git(&["config", "user.email", "test@example.com"]);
r.git(&["config", "user.name", "test"]);
r.git(&["config", "commit.gpgsign", "false"]);
r.git(&["config", "core.autocrlf", "false"]);
r
}
pub fn git(&self, args: &[&str]) -> Output {
let mut cmd = Command::new("git");
cmd.args(args).current_dir(&self.dir).stdin(Stdio::null());
Self::strip_git_env_impl(&mut cmd);
cmd.output().expect("run git")
}
pub fn stage(&self, path: &str, content: &str) {
let full = self.dir.join(path);
if let Some(p) = full.parent() {
std::fs::create_dir_all(p).expect("create parent");
}
std::fs::write(&full, content).expect("write file");
self.git(&["add", "--", path]);
}
pub fn write(&self, path: &str, content: &str) {
let full = self.dir.join(path);
if let Some(p) = full.parent() {
std::fs::create_dir_all(p).expect("create parent");
}
std::fs::write(&full, content).expect("write file");
}
pub fn commit(&self, msg: &str) {
self.git(&["commit", "-q", "--no-verify", "-m", msg]);
}
pub fn strip_git_env_impl(cmd: &mut Command) {
for (k, _) in std::env::vars_os() {
if k.to_string_lossy().starts_with("GIT_") {
cmd.env_remove(&k);
}
}
}
pub fn hook(&self, name: &str, args: &[&str]) -> HookRun {
self.hook_at(&self.dir, name, args)
}
pub fn run(&self, args: &[&str]) -> HookRun {
let mut cmd = Command::new(bin());
cmd.args(args).current_dir(&self.dir).stdin(Stdio::null());
Self::strip_git_env_impl(&mut cmd);
cmd.env("GIT_CONFIG_GLOBAL", self.dir.join("fake-gitconfig"));
let out = cmd.output().expect("run amont");
HookRun {
code: out.status.code().unwrap_or(-1),
stdout: String::from_utf8_lossy(&out.stdout).into_owned(),
stderr: String::from_utf8_lossy(&out.stderr).into_owned(),
}
}
pub fn hook_at(&self, cwd: &Path, name: &str, args: &[&str]) -> HookRun {
let mut cmd = Command::new(bin());
cmd.arg("--hooks-dir")
.arg(self.dir.join(".git/hooks"))
.arg(name)
.args(args)
.current_dir(cwd)
.stdin(Stdio::null());
Self::strip_git_env_impl(&mut cmd);
let out = cmd.output().expect("run amont");
HookRun {
code: out.status.code().unwrap_or(-1),
stdout: String::from_utf8_lossy(&out.stdout).into_owned(),
stderr: String::from_utf8_lossy(&out.stderr).into_owned(),
}
}
pub fn worktree(&self, name: &str) -> PathBuf {
let path = self.dir.join(name);
self.git(&[
"worktree",
"add",
"-q",
path.to_str().expect("utf8 path"),
"-b",
name,
]);
path
}
pub fn path(&self, rel: &str) -> PathBuf {
self.dir.join(rel)
}
}
impl Drop for Repo {
fn drop(&mut self) {
let _ = std::fs::remove_dir_all(&self.dir);
}
}
pub struct HookRun {
pub code: i32,
pub stdout: String,
pub stderr: String,
}
impl HookRun {
pub fn passed(&self) -> bool {
self.code == 0
}
pub fn output(&self) -> String {
format!("{}{}", self.stdout, self.stderr)
}
pub fn says(&self, needle: &str) -> bool {
self.output().contains(needle)
}
pub fn silent(&self) -> bool {
self.output().trim().is_empty()
}
}
#[cfg(unix)]
fn make_executable(p: &Path) {
use std::os::unix::fs::PermissionsExt;
let _ = std::fs::set_permissions(p, std::fs::Permissions::from_mode(0o755));
}
#[cfg(not(unix))]
fn make_executable(_p: &Path) {}
fn bin() -> &'static str {
env!("CARGO_BIN_EXE_amont")
}
pub fn missing(tool: &str) -> bool {
let found = std::env::var_os("PATH")
.map(|path| {
std::env::split_paths(&path).any(|d| {
d.join(tool).is_file()
|| (cfg!(windows)
&& [".exe", ".cmd", ".bat"]
.iter()
.any(|e| d.join(format!("{tool}{e}")).is_file()))
})
})
.unwrap_or(false);
if !found {
println!(" ! {tool} unavailable — skipping");
}
!found
}
pub fn template_hook(name: &str) -> PathBuf {
Path::new(env!("CARGO_MANIFEST_DIR"))
.join("../../templates/hooks")
.join(name)
}