use super::tests::{project, tip};
use super::*;
use crate::delivery::Repo;
use std::fs;
#[test]
fn deliver_captures_pending_work_and_squashes_it_onto_integration() {
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();
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:feature.txt"]).unwrap(), "shipped\n");
assert_eq!(Project::run(&root, &["rev-list", "--count", "main"]).unwrap().trim(), "2");
}
#[test]
fn deliver_with_no_pending_work_but_a_committed_branch_still_squashes() {
let (tmp, root, p) = project();
let wt = tmp.path().join("wt");
p.materialize(&wt, "work/bl-x").unwrap();
fs::write(wt.join("f.txt"), "x\n").unwrap();
Project::run(&wt, &["add", "-A"]).unwrap();
Project::run(&wt, &["commit", "-q", "-m", "wip"]).unwrap();
p.deliver(&wt, "work/bl-x", "main", "Land it [bl-x]", "[bl-x]").unwrap();
assert_eq!(tip(&root), "Land it [bl-x]");
}
#[test]
fn a_message_past_the_argv_limit_delivers_via_stdin_and_never_labels_the_capture() {
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 message = format!("Add feature [bl-x]\n\n{}", "context line\n".repeat(20_000));
p.deliver(&wt, "work/bl-x", "main", &message, "[bl-x]").unwrap();
let landed = Project::run(&root, &["log", "-1", "--format=%B", "main"]).unwrap();
assert_eq!(landed.trim_end(), message.trim_end());
assert_eq!(tip(&root), "Add feature [bl-x]");
let reflog = Project::run(&root, &["reflog", "show", "--format=%gs", "-1", "main"]).unwrap();
assert_eq!(reflog.trim(), "Add feature [bl-x]");
let capture = Project::run(&root, &["log", "--no-merges", "-1", "--format=%B", "work/bl-x"]).unwrap();
assert_eq!(capture.trim_end(), "Add feature [bl-x]");
}
#[test]
fn deliver_is_a_no_op_for_an_empty_deliverable() {
let (tmp, root, p) = project();
let wt = tmp.path().join("wt");
p.materialize(&wt, "work/bl-x").unwrap();
p.deliver(&wt, "work/bl-x", "main", "nothing [bl-x]", "[bl-x]").unwrap();
assert_eq!(tip(&root), "seed"); }
#[test]
fn deliver_is_a_no_op_when_the_branch_was_never_made() {
let (tmp, root, p) = project();
let wt = tmp.path().join("wt"); p.deliver(&wt, "work/bl-z", "main", "nothing [bl-z]", "[bl-z]").unwrap();
assert_eq!(tip(&root), "seed");
}
#[test]
fn a_clean_disjoint_target_advance_refuses_and_the_retry_lands_once_incorporated() {
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();
let pinned = Project::run(&root, &["rev-parse", "main"]).unwrap().trim().to_string();
let before = Project::run(&root, &["rev-parse", "work/bl-x"]).unwrap().trim().to_string();
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("stale source"), "{msg}");
assert!(msg.contains(&format!("main (pinned at {pinned}) is not yet in work/bl-x")), "{msg}");
assert!(msg.contains("Merge or rebase main into the work/bl-x worktree"), "{msg}");
assert_eq!(Project::run(&root, &["rev-parse", "work/bl-x"]).unwrap().trim(), before);
assert_eq!(tip(&root), "late main edit");
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:feature.txt"]).unwrap(), "shipped\n");
assert_eq!(Project::run(&root, &["show", "main:late.txt"]).unwrap(), "landed meanwhile\n");
}
#[test]
fn a_conflicting_target_advance_gives_the_same_refusal_and_never_attempts_a_merge() {
let (tmp, root, p) = project();
let wt = tmp.path().join("wt");
p.materialize(&wt, "work/bl-x").unwrap();
fs::write(wt.join("seed.txt"), "from work\n").unwrap();
Project::run(&wt, &["commit", "-qam", "work edit"]).unwrap();
fs::write(root.join("seed.txt"), "from main\n").unwrap();
Project::run(&root, &["commit", "-qam", "main edit"]).unwrap();
let pinned = Project::run(&root, &["rev-parse", "main"]).unwrap().trim().to_string();
let err = p.deliver(&wt, "work/bl-x", "main", "clash [bl-x]", "[bl-x]").unwrap_err();
let msg = err.to_string();
assert!(msg.contains(&format!("main (pinned at {pinned}) is not yet in work/bl-x")), "{msg}");
assert!(!msg.contains("delivery conflict"), "delivery attempted no merge: {msg}");
assert!(!Project::ok(&wt, &["rev-parse", "--verify", "--quiet", "MERGE_HEAD"]).unwrap());
assert!(Project::ok(&wt, &["diff", "--quiet", "HEAD"]).unwrap());
assert_eq!(tip(&root), "main edit");
}
#[test]
fn the_recursive_edge_refuses_a_stale_source_at_every_depth() {
let (tmp, root, p) = project();
let (aa, ab) = (tmp.path().join("aa"), tmp.path().join("ab"));
p.mint("work/bl-a", "main").unwrap(); for (wt, branch) in [(&aa, "work/bl-aa"), (&ab, "work/bl-ab")] {
p.mint(branch, "work/bl-a").unwrap(); p.materialize(wt, branch).unwrap();
}
fs::write(aa.join("aa.txt"), "first child\n").unwrap();
fs::write(ab.join("ab.txt"), "second child\n").unwrap();
p.deliver(&aa, "work/bl-aa", "work/bl-a", "AA [bl-aa]", "[bl-aa]").unwrap();
assert_eq!(Project::run(&root, &["log", "-1", "--format=%s", "work/bl-a"]).unwrap().trim(), "AA [bl-aa]");
assert_eq!(tip(&root), "seed");
let err = p.deliver(&ab, "work/bl-ab", "work/bl-a", "AB [bl-ab]", "[bl-ab]").unwrap_err();
assert!(err.to_string().contains("is not yet in work/bl-ab"), "{err}");
assert!(err.to_string().contains("Merge or rebase work/bl-a"), "{err}");
assert_eq!(Project::run(&root, &["log", "-1", "--format=%s", "work/bl-a"]).unwrap().trim(), "AA [bl-aa]");
Project::run(&ab, &["merge", "-q", "--no-edit", "work/bl-a"]).unwrap();
p.deliver(&ab, "work/bl-ab", "work/bl-a", "AB [bl-ab]", "[bl-ab]").unwrap();
assert_eq!(Project::run(&root, &["log", "-1", "--format=%s", "work/bl-a"]).unwrap().trim(), "AB [bl-ab]");
let a = tmp.path().join("a");
p.materialize(&a, "work/bl-a").unwrap();
p.deliver(&a, "work/bl-a", "main", "The epic [bl-a]", "[bl-a]").unwrap();
assert_eq!(tip(&root), "The epic [bl-a]");
assert_eq!(Project::run(&root, &["show", "main:aa.txt"]).unwrap(), "first child\n");
assert_eq!(Project::run(&root, &["show", "main:ab.txt"]).unwrap(), "second child\n");
assert_eq!(Project::run(&root, &["rev-list", "--count", "main"]).unwrap().trim(), "2");
}
#[test]
fn deliver_skips_when_this_incarnations_delivery_already_landed() {
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.deliver(&wt, "work/bl-x", "main", "Add feature [bl-x]", "[bl-x]").unwrap();
fs::write(root.join("other.txt"), "other\n").unwrap();
Project::run(&root, &["add", "-A"]).unwrap();
Project::run(&root, &["commit", "-qm", "concurrent work"]).unwrap();
p.deliver(&wt, "work/bl-x", "main", "Add feature [bl-x]", "[bl-x]").unwrap();
assert_eq!(p.marked("main", "[bl-x]").unwrap().len(), 1); assert_eq!(tip(&root), "concurrent work"); }
#[test]
fn deliver_skips_a_branch_already_fully_merged_into_integration() {
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", "Add feature [bl-x]"]).unwrap();
Project::run(&root, &["merge", "-q", "--ff-only", "work/bl-x"]).unwrap(); fs::write(root.join("other.txt"), "other\n").unwrap();
Project::run(&root, &["add", "-A"]).unwrap();
Project::run(&root, &["commit", "-qm", "concurrent work"]).unwrap();
p.deliver(&wt, "work/bl-x", "main", "Add feature [bl-x]", "[bl-x]").unwrap();
assert_eq!(p.marked("main", "[bl-x]").unwrap().len(), 1);
assert_eq!(tip(&root), "concurrent work");
}
#[test]
fn commit_swap_aborts_when_integration_moved_under_the_delivery() {
let (_tmp, root, _p) = project();
let stale = Project::run(&root, &["rev-parse", "main"]).unwrap().trim().to_string(); fs::write(root.join("other.txt"), "x\n").unwrap();
Project::run(&root, &["add", "-A"]).unwrap();
Project::run(&root, &["commit", "-qm", "concurrent"]).unwrap(); let tip_sha = Project::run(&root, &["rev-parse", "main"]).unwrap().trim().to_string();
let err = super::acts::commit_swap(&root, "main", "late [bl-x]", &tip_sha, &stale).unwrap_err();
assert!(err.to_string().contains("moved under the delivery"));
assert_eq!(tip(&root), "concurrent");
}
#[test]
fn marked_returns_the_marked_commits_newest_first() {
let (tmp, root, p) = project();
let wt = tmp.path().join("wt");
p.materialize(&wt, "work/bl-x").unwrap();
fs::write(wt.join("a.txt"), "1\n").unwrap();
p.deliver(&wt, "work/bl-x", "main", "first [bl-x]", "[bl-x]").unwrap();
p.discard(&wt, "work/bl-x").unwrap();
p.materialize(&wt, "work/bl-x").unwrap();
fs::write(wt.join("b.txt"), "2\n").unwrap();
p.deliver(&wt, "work/bl-x", "main", "second [bl-x]", "[bl-x]").unwrap();
let shas = p.marked("main", "[bl-x]").unwrap();
let subject = |sha: &str| Project::run(&root, &["log", "-1", "--format=%s", sha]).unwrap().trim().to_string();
assert_eq!(shas.iter().map(|s| subject(s)).collect::<Vec<_>>(), ["second [bl-x]", "first [bl-x]"]);
assert!(p.marked("main", "[bl-zzzz]").unwrap().is_empty());
}