mod common;
use common::Repo;
use std::process::Command;
fn add_worktree(r: &Repo, branch: &str) -> std::path::PathBuf {
let path = r.dir.parent().expect("parent").join(format!(
"{}-{branch}",
r.dir.file_name().expect("name").to_string_lossy()
));
let _ = std::fs::remove_dir_all(&path);
let out = r.git(&[
"worktree",
"add",
"-q",
"-b",
branch,
path.to_str().expect("utf8"),
]);
assert!(
out.status.success(),
"git worktree add failed: {}",
String::from_utf8_lossy(&out.stderr)
);
path
}
fn worktree_git_dir(wt: &std::path::Path) -> std::path::PathBuf {
let mut cmd = Command::new("git");
cmd.arg("-C")
.arg(wt)
.args(["rev-parse", "--path-format=absolute", "--git-dir"]);
Repo::strip_git_env_impl(&mut cmd);
let out = cmd.output().expect("git rev-parse --git-dir");
std::path::PathBuf::from(String::from_utf8_lossy(&out.stdout).trim())
}
fn git_add(dir: &std::path::Path, path: &str) -> bool {
let mut cmd = Command::new("git");
cmd.arg("-C").arg(dir).args(["add", path]);
Repo::strip_git_env_impl(&mut cmd);
cmd.output().expect("git add").status.success()
}
fn hook_from_worktree(
main_hooks_dir: &std::path::Path,
worktree: &std::path::Path,
) -> (i32, String) {
let mut cmd = Command::new(env!("CARGO_BIN_EXE_amont"));
cmd.arg("--hooks-dir")
.arg(main_hooks_dir)
.arg("pre-commit")
.current_dir(worktree);
Repo::strip_git_env_impl(&mut cmd);
let out = cmd.output().expect("run amont pre-commit");
(
out.status.code().unwrap_or(-1),
format!(
"{}{}",
String::from_utf8_lossy(&out.stdout),
String::from_utf8_lossy(&out.stderr)
),
)
}
#[test]
fn a_merge_in_progress_in_a_worktree_still_pauses_content_checks() {
let r = Repo::new();
r.stage("x.json", "{\n \"a\": 1\n}\n");
r.commit("chore: seed");
let wt = add_worktree(&r, "feature");
std::fs::write(wt.join("x.json"), "{ BROKEN\n").expect("write");
assert!(git_add(&wt, "x.json"));
std::fs::write(worktree_git_dir(&wt).join("MERGE_HEAD"), "deadbeef\n").expect("write");
let (code, out) = hook_from_worktree(&r.path(".git/hooks"), &wt);
assert_eq!(code, 0, "should have paused, not judged the merge:\n{out}");
assert!(out.contains("paused during a merge"), "{out}");
let _ = std::fs::remove_dir_all(&wt);
}
#[test]
fn unstaged_work_in_a_worktree_survives_the_stash_and_restore() {
let r = Repo::new();
r.stage("x.json", "{\n \"a\": 1\n}\n");
r.commit("chore: seed");
let wt = add_worktree(&r, "feature");
std::fs::write(wt.join("x.json"), "{\n \"a\": 2\n}\n").expect("write");
assert!(git_add(&wt, "x.json"));
std::fs::write(wt.join("x.json"), "{ THIS IS NOT JSON\n").expect("write");
let (code, out) = hook_from_worktree(&r.path(".git/hooks"), &wt);
assert_eq!(code, 0, "judged the working tree, not the commit:\n{out}");
assert_eq!(
std::fs::read_to_string(wt.join("x.json")).expect("read"),
"{ THIS IS NOT JSON\n",
"unstaged work in the worktree was not restored"
);
assert!(
!worktree_git_dir(&wt).join("amont-held").exists(),
"held files left behind in the worktree-private gitdir"
);
assert!(
!r.path(".git/amont-held").exists(),
"held files stranded in the COMMON gitdir — the exact incident this guards against"
);
let _ = std::fs::remove_dir_all(&wt);
}