use crate::delivery::Repo;
use crate::delivery_repo::tests::{project, tip};
use crate::delivery_repo::Project;
use std::fs;
fn g(root: &std::path::Path, args: &[&str]) -> String {
Project::run(root, args).unwrap()
}
fn modify_delete_race() -> (tempfile::TempDir, std::path::PathBuf, Project, std::path::PathBuf) {
let (tmp, root, p) = project();
fs::write(root.join("doomed.txt"), "original\n").unwrap();
g(&root, &["add", "-A"]);
g(&root, &["commit", "-qm", "add doomed"]);
let wt = tmp.path().join("wt");
p.materialize(&wt, "work/bl-x").unwrap();
fs::write(wt.join("doomed.txt"), "work edit\n").unwrap();
g(&wt, &["commit", "-qam", "work edits doomed"]);
g(&root, &["rm", "-q", "doomed.txt"]); g(&root, &["commit", "-qm", "main deletes doomed"]);
(tmp, root, p, wt)
}
#[test]
fn the_modify_delete_race_refuses_as_a_stale_source_and_never_starts_the_merge() {
let (_tmp, root, p, wt) = modify_delete_race();
let err = p.deliver(&wt, "work/bl-x", "main", "clash [bl-x]", "[bl-x]").unwrap_err();
assert!(err.to_string().contains("stale source"), "{err}");
assert!(err.to_string().contains("is not yet in work/bl-x"), "{err}");
assert_eq!(tip(&root), "main deletes doomed"); assert!(!Project::ok(&wt, &["rev-parse", "--verify", "--quiet", "MERGE_HEAD"]).unwrap());
}
#[test]
fn deliver_never_concludes_a_half_merge_left_in_the_worktree() {
let (_tmp, root, p, wt) = modify_delete_race();
assert!(Project::run(&wt, &["merge", "main"]).is_err());
assert!(Project::ok(&wt, &["rev-parse", "--verify", "--quiet", "MERGE_HEAD"]).unwrap());
let err = p.deliver(&wt, "work/bl-x", "main", "retry [bl-x]", "[bl-x]").unwrap_err();
assert!(err.to_string().contains("merge is in progress"), "{err}");
assert!(Project::ok(&wt, &["rev-parse", "--verify", "--quiet", "MERGE_HEAD"]).unwrap());
assert_eq!(tip(&root), "main deletes doomed");
}
#[test]
fn the_squash_aborts_naming_a_path_the_work_never_authored() {
let (tmp, root, p) = project();
fs::write(root.join("doomed.txt"), "original\n").unwrap();
g(&root, &["add", "-A"]);
g(&root, &["commit", "-qm", "add doomed"]);
let wt = tmp.path().join("wt");
p.materialize(&wt, "work/bl-x").unwrap();
fs::write(wt.join("feature.txt"), "shipped\n").unwrap();
g(&wt, &["add", "-A"]);
g(&wt, &["commit", "-qm", "work feature"]);
g(&root, &["rm", "-q", "doomed.txt"]);
g(&root, &["commit", "-qm", "main deletes doomed"]);
g(&wt, &["merge", "--no-commit", "-q", "main"]); g(&wt, &["checkout", "HEAD", "--", "doomed.txt"]); g(&wt, &["commit", "-qm", "fold (evil: restores doomed)"]);
let err = p.deliver(&wt, "work/bl-x", "main", "res [bl-x]", "[bl-x]").unwrap_err();
assert!(err.to_string().contains("no-resurrection invariant"), "{err}");
assert!(err.to_string().contains("doomed.txt"), "names the path: {err}");
assert_eq!(tip(&root), "main deletes doomed"); }
#[test]
fn a_hand_resolved_fold_counts_as_authored_and_delivers() {
let (tmp, root, p) = project();
fs::write(root.join("conflict.txt"), "base\n").unwrap();
fs::write(root.join("extra.txt"), "stale\n").unwrap();
g(&root, &["add", "-A"]);
g(&root, &["commit", "-qm", "base"]);
let wt = tmp.path().join("wt");
p.materialize(&wt, "work/bl-x").unwrap();
fs::write(wt.join("conflict.txt"), "work\n").unwrap();
g(&wt, &["commit", "-qam", "work edit"]);
fs::write(root.join("conflict.txt"), "main\n").unwrap();
g(&root, &["commit", "-qam", "main edit"]);
assert!(p.deliver(&wt, "work/bl-x", "main", "r [bl-x]", "[bl-x]").is_err());
assert!(Project::run(&wt, &["merge", "main"]).is_err()); fs::write(wt.join("conflict.txt"), "merged\n").unwrap();
fs::write(wt.join("extra.txt"), "fixed in resolution\n").unwrap();
g(&wt, &["add", "-A"]);
g(&wt, &["commit", "-qm", "fold resolution"]);
p.deliver(&wt, "work/bl-x", "main", "r [bl-x]", "[bl-x]").unwrap();
assert_eq!(tip(&root), "r [bl-x]");
assert_eq!(g(&root, &["show", "main:conflict.txt"]), "merged\n");
assert_eq!(g(&root, &["show", "main:extra.txt"]), "fixed in resolution\n");
}
#[test]
fn a_rename_the_squash_diff_could_not_pair_is_still_authored_and_delivers() {
let (tmp, root, p) = project();
let lines = "line\n".repeat(60); fs::write(root.join("a.txt"), &lines).unwrap();
fs::write(root.join("c.txt"), "c\n").unwrap();
g(&root, &["add", "-A"]);
g(&root, &["commit", "-qm", "base"]);
g(&root, &["config", "diff.renameLimit", "1"]); let wt = tmp.path().join("wt");
p.materialize(&wt, "work/bl-x").unwrap();
g(&wt, &["mv", "a.txt", "b.txt"]);
fs::write(wt.join("b.txt"), format!("{lines}one more\n")).unwrap();
g(&wt, &["add", "-A"]);
g(&wt, &["commit", "-qm", "work renames a to b"]);
g(&wt, &["rm", "-q", "c.txt"]);
fs::write(wt.join("d.txt"), "d\n").unwrap();
g(&wt, &["add", "-A"]);
g(&wt, &["commit", "-qm", "work swaps c for d"]);
assert!(g(&root, &["diff", "--name-only", "main", "work/bl-x"]).contains("a.txt"));
assert!(!g(&root, &["log", "--format=", "--name-only", "--cc", "work/bl-x", "^main"]).contains("a.txt"));
p.deliver(&wt, "work/bl-x", "main", "renamed [bl-x]", "[bl-x]").unwrap();
assert_eq!(tip(&root), "renamed [bl-x]");
assert_eq!(g(&root, &["show", "main:b.txt"]), format!("{lines}one more\n"));
}