#![cfg(unix)]
use super::tests::{project, tip};
use super::*;
use crate::delivery::Repo;
use std::os::unix::fs::PermissionsExt;
fn install_hook(root: &Path, script: &str, mode: u32) {
let hook = root.join(".git/hooks/pre-commit");
fs::write(&hook, script).unwrap();
fs::set_permissions(&hook, fs::Permissions::from_mode(mode)).unwrap();
let printed = Project::run(root, &["rev-parse", "--git-path", "hooks/pre-commit"]).unwrap();
assert_eq!(root.join(printed.trim()), hook, "the gate would run a hook this test never wrote");
}
#[test]
fn a_passing_gate_delivers_and_runs_in_the_worktree() {
let (tmp, root, p) = project();
let wt = tmp.path().join("wt");
p.materialize(&wt, "work/bl-x").unwrap();
fs::write(wt.join("feature.txt"), "shipped\n").unwrap();
install_hook(&root, "#!/bin/sh\ntest -f feature.txt\n", 0o755);
p.deliver(&wt, "work/bl-x", "main", "Add feature [bl-x]", "[bl-x]").unwrap();
assert_eq!(tip(&root), "Add feature [bl-x]");
}
#[test]
fn a_failing_gate_aborts_the_delivery_before_integration_moves() {
let (tmp, root, p) = project();
let wt = tmp.path().join("wt");
p.materialize(&wt, "work/bl-x").unwrap();
fs::write(wt.join("feature.txt"), "broken\n").unwrap();
install_hook(&root, "#!/bin/sh\nexit 1\n", 0o755);
let err = p.deliver(&wt, "work/bl-x", "main", "Add feature [bl-x]", "[bl-x]").unwrap_err();
assert!(err.to_string().contains("delivery gate"), "{err}");
assert_eq!(tip(&root), "seed"); let subject = Project::run(&root, &["log", "-1", "--format=%s", "work/bl-x"]).unwrap();
assert_eq!(subject.trim(), "Add feature [bl-x]");
}
#[test]
fn a_non_executable_hook_is_ignored_gits_rule() {
let (tmp, root, p) = project();
let wt = tmp.path().join("wt");
p.materialize(&wt, "work/bl-x").unwrap();
fs::write(wt.join("feature.txt"), "shipped\n").unwrap();
install_hook(&root, "#!/bin/sh\nexit 1\n", 0o644);
p.deliver(&wt, "work/bl-x", "main", "Add feature [bl-x]", "[bl-x]").unwrap();
assert_eq!(tip(&root), "Add feature [bl-x]");
}
#[test]
fn the_gate_checks_the_tree_that_lands_target_and_all() {
let (tmp, root, p) = project();
let wt = tmp.path().join("wt");
p.materialize(&wt, "work/bl-x").unwrap();
fs::write(wt.join("feature.txt"), "shipped\n").unwrap();
Project::run(&wt, &["add", "-A"]).unwrap();
Project::run(&wt, &["commit", "-qm", "work"]).unwrap();
fs::write(root.join("late.txt"), "landed meanwhile\n").unwrap();
Project::run(&root, &["add", "-A"]).unwrap();
Project::run(&root, &["commit", "-qm", "late main edit"]).unwrap();
Project::run(&wt, &["merge", "-q", "--no-edit", "main"]).unwrap();
install_hook(&root, "#!/bin/sh\ntest -f feature.txt && test -f late.txt\n", 0o755);
p.deliver(&wt, "work/bl-x", "main", "Add feature [bl-x]", "[bl-x]").unwrap();
assert_eq!(tip(&root), "Add feature [bl-x]");
assert_eq!(Project::run(&root, &["show", "main:late.txt"]).unwrap(), "landed meanwhile\n");
assert_eq!(Project::run(&root, &["rev-list", "--count", "main"]).unwrap().trim(), "3");
}
#[test]
fn an_incorporation_that_dissolves_the_diff_skips_gate_and_squash() {
let (tmp, root, p) = project();
let wt = tmp.path().join("wt");
p.materialize(&wt, "work/bl-x").unwrap();
fs::write(wt.join("feature.txt"), "same\n").unwrap();
Project::run(&wt, &["add", "-A"]).unwrap();
Project::run(&wt, &["commit", "-qm", "work copy"]).unwrap();
fs::write(root.join("feature.txt"), "same\n").unwrap();
fs::write(root.join("late.txt"), "more\n").unwrap();
Project::run(&root, &["add", "-A"]).unwrap();
Project::run(&root, &["commit", "-qm", "already landed"]).unwrap();
Project::run(&wt, &["merge", "-q", "--no-edit", "main"]).unwrap();
install_hook(&root, "#!/bin/sh\nexit 1\n", 0o755);
p.deliver(&wt, "work/bl-x", "main", "dup [bl-x]", "[bl-x]").unwrap();
assert_eq!(tip(&root), "already landed"); }
#[test]
fn deliver_rematerializes_an_absent_worktree_to_gate_in() {
let (tmp, root, p) = project();
let wt = tmp.path().join("wt");
p.materialize(&wt, "work/bl-x").unwrap();
fs::write(wt.join("feature.txt"), "shipped\n").unwrap();
Project::run(&wt, &["add", "-A"]).unwrap();
Project::run(&wt, &["commit", "-qm", "wip"]).unwrap();
p.release(&wt).unwrap(); install_hook(&root, "#!/bin/sh\ntest -f feature.txt\n", 0o755);
p.deliver(&wt, "work/bl-x", "main", "Add feature [bl-x]", "[bl-x]").unwrap();
assert_eq!(tip(&root), "Add feature [bl-x]");
assert!(wt.exists()); }
fn sibling_landing_hook(root: &Path, guard: &Path, file: &str, content: &str) -> String {
format!(
"#!/bin/sh\nif [ ! -e \"{g}\" ]; then\n : > \"{g}\"\n printf '%s\\n' '{content}' > \"{r}/{file}\"\n \
git -C \"{r}\" add -A\n git -C \"{r}\" commit -q --no-verify -m 'sibling landed'\nfi\n",
g = guard.display(),
r = root.display()
)
}
#[test]
fn a_mid_gate_advance_is_one_clean_cas_rejection_not_a_resurrection_abort() {
let (tmp, root, p) = project();
let wt = tmp.path().join("wt");
p.materialize(&wt, "work/bl-x").unwrap();
fs::write(wt.join("feature.txt"), "shipped\n").unwrap();
let guard = tmp.path().join("advanced");
install_hook(&root, &sibling_landing_hook(&root, &guard, "sibling.txt", "sibling"), 0o755);
let err = p.deliver(&wt, "work/bl-x", "main", "Add feature [bl-x]", "[bl-x]").unwrap_err();
let msg = err.to_string();
assert!(msg.contains("moved under the delivery"), "{msg}");
assert!(msg.contains("re-run `bl close`"), "{msg}");
assert!(!msg.contains("no-resurrection"), "{msg}");
assert_eq!(tip(&root), "sibling landed");
assert_eq!(Project::run(&root, &["show", "main:sibling.txt"]).unwrap(), "sibling\n");
assert!(!Project::ok(&root, &["cat-file", "-e", "main:feature.txt"]).unwrap());
let retry = p.deliver(&wt, "work/bl-x", "main", "Add feature [bl-x]", "[bl-x]").unwrap_err();
assert!(retry.to_string().contains("stale source"), "{retry}");
assert_eq!(tip(&root), "sibling landed");
Project::run(&wt, &["merge", "-q", "--no-edit", "main"]).unwrap();
p.deliver(&wt, "work/bl-x", "main", "Add feature [bl-x]", "[bl-x]").unwrap();
assert_eq!(tip(&root), "Add feature [bl-x]");
assert_eq!(Project::run(&root, &["show", "main:sibling.txt"]).unwrap(), "sibling\n");
assert_eq!(Project::run(&root, &["show", "main:feature.txt"]).unwrap(), "shipped\n");
}
#[test]
fn a_mid_gate_subset_advance_is_rejected_never_silently_reverted() {
let (tmp, root, p) = project();
let wt = tmp.path().join("wt");
p.materialize(&wt, "work/bl-x").unwrap();
fs::write(wt.join("seed.txt"), "work edit\n").unwrap(); let guard = tmp.path().join("advanced");
install_hook(&root, &sibling_landing_hook(&root, &guard, "seed.txt", "sibling edit"), 0o755);
let err = p.deliver(&wt, "work/bl-x", "main", "Edit seed [bl-x]", "[bl-x]").unwrap_err();
assert!(err.to_string().contains("moved under the delivery"), "{err}");
assert_eq!(Project::run(&root, &["show", "main:seed.txt"]).unwrap(), "sibling edit\n");
assert_eq!(tip(&root), "sibling landed");
let retry = p.deliver(&wt, "work/bl-x", "main", "Edit seed [bl-x]", "[bl-x]").unwrap_err();
assert!(retry.to_string().contains("stale source"), "{retry}");
assert!(retry.to_string().contains("Merge or rebase main into the work/bl-x worktree"), "{retry}");
assert_eq!(Project::run(&root, &["show", "main:seed.txt"]).unwrap(), "sibling edit\n");
}