use super::*;
use crate::delivery::Repo;
use crate::delivery_repo::tests::project;
use std::fs;
use std::path::{Path, PathBuf};
#[test]
fn deliver_leaves_the_owning_checkout_clean_not_phantom_staged() {
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!(Project::run(&root, &["status", "--porcelain"]).unwrap(), "");
assert_eq!(fs::read_to_string(root.join("feature.txt")).unwrap(), "shipped\n");
let reflog = Project::run(&root, &["reflog", "main"]).unwrap();
assert!(reflog.lines().next().unwrap().contains("Add feature [bl-x]"), "reflog: {reflog}");
}
#[test]
fn reconcile_is_idempotent_and_never_clobbers_a_real_edit() {
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();
p.reconcile("main").unwrap();
assert_eq!(Project::run(&root, &["status", "--porcelain"]).unwrap(), "");
fs::write(root.join("feature.txt"), "local edit\n").unwrap();
assert_eq!(Project::carry_forward(&root, "main").unwrap(), None);
assert_eq!(fs::read_to_string(root.join("feature.txt")).unwrap(), "local edit\n");
}
fn plumb_delivery(root: &Path) {
let g = |args: &[&str]| Project::run(root, args).unwrap();
let side = root.parent().unwrap().join("side");
g(&["worktree", "add", "-q", "--detach", side.to_str().unwrap(), "main"]);
fs::write(side.join("seed.txt"), "two\n").unwrap();
fs::write(side.join("c.txt"), "new\n").unwrap();
commit_and_move(root, &side);
}
fn commit_and_move(root: &Path, side: &Path) {
let g = |args: &[&str]| Project::run(root, args).unwrap();
Project::run(side, &["add", "-A"]).unwrap();
Project::run(side, &["commit", "-q", "-m", "delivery"]).unwrap();
let new = Project::run(side, &["rev-parse", "HEAD"]).unwrap();
g(&["update-ref", "-m", "delivery", "refs/heads/main", new.trim()]);
}
fn two_files() -> (tempfile::TempDir, PathBuf, Project) {
let (tmp, root, p) = project();
fs::write(root.join("b.txt"), "one\n").unwrap();
Project::run(&root, &["add", "b.txt"]).unwrap();
Project::run(&root, &["commit", "-q", "-m", "b"]).unwrap();
(tmp, root, p)
}
fn read(root: &Path, f: &str) -> String {
fs::read_to_string(root.join(f)).unwrap()
}
#[test]
fn a_fresh_close_is_carried_forward_including_an_added_file() {
let (_tmp, root, _p) = two_files();
plumb_delivery(&root);
assert_eq!(Project::carry_forward(&root, "main").unwrap(), None);
assert_eq!(read(&root, "seed.txt"), "two\n");
assert_eq!(read(&root, "c.txt"), "new\n");
assert_eq!(Project::run(&root, &["status", "--porcelain"]).unwrap(), "");
}
#[test]
fn an_unrelated_local_edit_is_carried_forward() {
let (_tmp, root, p) = two_files();
plumb_delivery(&root);
fs::write(root.join("b.txt"), "mine\n").unwrap();
p.reconcile("main").unwrap();
assert_eq!(read(&root, "seed.txt"), "two\n");
assert_eq!(read(&root, "b.txt"), "mine\n");
assert_eq!(Project::run(&root, &["status", "--porcelain"]).unwrap(), " M b.txt\n");
}
#[test]
fn two_unsynced_closes_are_carried_forward_together() {
let (_tmp, root, _p) = two_files();
plumb_delivery(&root);
let side = root.parent().unwrap().join("side");
fs::write(side.join("d.txt"), "later\n").unwrap();
commit_and_move(&root, &side);
assert_eq!(Project::carry_forward(&root, "main").unwrap(), None);
assert_eq!(read(&root, "c.txt"), "new\n");
assert_eq!(read(&root, "d.txt"), "later\n");
assert_eq!(Project::run(&root, &["status", "--porcelain"]).unwrap(), "");
}
#[test]
fn a_touched_file_edited_locally_is_left_alone_with_the_command() {
let (_tmp, root, p) = two_files();
plumb_delivery(&root);
fs::write(root.join("seed.txt"), "mine\n").unwrap();
p.reconcile("main").unwrap(); let warning = Project::carry_forward(&root, "main").unwrap().unwrap();
assert_eq!(read(&root, "seed.txt"), "mine\n");
assert!(!root.join("c.txt").exists());
assert!(warning.contains("NOT carried forward"), "{warning}");
assert!(warning.contains("read-tree -m -u"), "{warning}");
}
#[test]
fn a_staged_index_is_left_alone_and_says_why() {
let (_tmp, root, _p) = two_files();
plumb_delivery(&root);
fs::write(root.join("b.txt"), "staged\n").unwrap();
Project::run(&root, &["add", "b.txt"]).unwrap();
let warning = Project::carry_forward(&root, "main").unwrap().unwrap();
assert!(warning.contains("left alone"), "{warning}");
assert_eq!(Project::run(&root, &["show", ":b.txt"]).unwrap(), "staged\n");
assert!(!root.join("c.txt").exists());
}
#[test]
fn an_unmerged_index_is_skipped() {
let (_tmp, root, _p) = two_files();
let g = |args: &[&str]| Project::run(&root, args).unwrap();
g(&["checkout", "-q", "-b", "other"]);
fs::write(root.join("b.txt"), "theirs\n").unwrap();
g(&["commit", "-q", "-am", "theirs"]);
g(&["checkout", "-q", "main"]);
fs::write(root.join("b.txt"), "ours\n").unwrap();
g(&["commit", "-q", "-am", "ours"]);
assert!(!Project::ok(&root, &["merge", "other"]).unwrap());
plumb_delivery(&root);
assert_eq!(Project::carry_forward(&root, "main").unwrap(), None);
assert!(!root.join("c.txt").exists());
}
#[test]
fn a_checkout_on_another_branch_or_detached_is_untouched() {
for how in [&["checkout", "-q", "-b", "feature"][..], &["checkout", "-q", "--detach"][..]] {
let (_tmp, root, p) = two_files();
Project::run(&root, how).unwrap();
plumb_delivery(&root);
p.reconcile("main").unwrap();
assert_eq!(read(&root, "seed.txt"), "seed\n");
assert!(!root.join("c.txt").exists());
}
}
#[test]
fn checkouts_on_returns_only_the_branch_owners() {
let (tmp, root, p) = project();
let wt = tmp.path().join("wt");
p.materialize(&wt, "work/bl-x").unwrap();
let owners = p.checkouts_on("main").unwrap();
assert_eq!(owners.len(), 1);
assert_eq!(owners[0].canonicalize().unwrap(), root.canonicalize().unwrap());
let work = p.checkouts_on("work/bl-x").unwrap();
assert_eq!(work.len(), 1);
assert_eq!(work[0].canonicalize().unwrap(), wt.canonicalize().unwrap());
}