use super::*;
use crate::tracker::fixtures::{
binding, checkout, commit, empty_remote, env_held, env_top, legacy_remote, local_unpushed,
remote_with_branch, revoked_remote, store_clone, tip, BRANCH,
};
use tempfile::TempDir;
#[test]
fn push_keeps_work_local_when_the_remote_tip_is_not_a_store() {
let tmp = TempDir::new().unwrap();
let remote = legacy_remote(tmp.path());
let store = local_unpushed(tmp.path());
let before = tip(&remote, BRANCH);
push(&binding(Some(&remote), &store), &env_top()).unwrap();
assert_eq!(tip(&remote, BRANCH), before); }
#[test]
fn push_to_an_unreachable_remote_fails_open_and_keeps_the_store_ahead() {
let tmp = TempDir::new().unwrap();
let store = local_unpushed(tmp.path());
let sealed = tip(&store, "HEAD");
let gone = tmp.path().join("no-such-remote.git");
push(&binding(Some(&gone), &store), &env_top()).unwrap();
assert_eq!(tip(&store, "HEAD"), sealed, "the seal stays, unpublished");
}
#[test]
fn push_publishes_the_local_balls_branch_to_the_remote() {
let tmp = TempDir::new().unwrap();
let remote = remote_with_branch(tmp.path());
let store = store_clone(tmp.path(), &remote);
let landed = commit(&store, "landed.txt", "landed");
push(&binding(Some(&remote), &store), &env_top()).unwrap();
assert_eq!(tip(&remote, BRANCH), landed);
}
#[test]
fn push_from_a_nested_op_on_a_held_store_publishes_nothing() {
let tmp = TempDir::new().unwrap();
let remote = remote_with_branch(tmp.path());
let store = store_clone(tmp.path(), &remote);
let before = tip(&remote, BRANCH);
let landed = commit(&store, "landed.txt", "landed");
assert_ne!(landed, before, "the fixture must leave something worth publishing");
push(&binding(Some(&remote), &store), &env_held(&[&store, &store])).unwrap();
assert_eq!(tip(&remote, BRANCH), before, "a nested op must not publish a held anvil");
}
#[test]
fn push_from_a_nested_op_on_an_unheld_store_publishes() {
let tmp = TempDir::new().unwrap();
let remote = remote_with_branch(tmp.path());
let store = store_clone(tmp.path(), &remote);
let landed = commit(&store, "landed.txt", "landed");
let other = tmp.path().join("elsewhere/tasks");
push(&binding(Some(&remote), &store), &env_held(&[&other, &store])).unwrap();
assert_eq!(tip(&remote, BRANCH), landed, "an unheld anvil is this op's to publish");
}
#[test]
fn push_in_stealth_is_a_no_op() {
let tmp = TempDir::new().unwrap();
let remote = empty_remote(tmp.path());
let store = local_unpushed(tmp.path());
push(&binding(None, &store), &env_top()).unwrap();
assert!(git(&remote, &["rev-parse", BRANCH]).is_err());
}
#[test]
fn a_rejected_push_reconciles_a_different_ball_race_and_lands() {
let tmp = TempDir::new().unwrap();
let remote = remote_with_branch(tmp.path());
let store = store_clone(tmp.path(), &remote);
let other = checkout(tmp.path(), &remote, "other");
commit(&other, "tasks/bl-aaaa.md", "theirs");
git(&other, &["push", "-q", "origin", BRANCH]).unwrap();
let sealed = commit(&store, "tasks/bl-bbbb.md", "ours");
push(&binding(Some(&remote), &store), &env_top()).unwrap();
let landed = tip(&store, "HEAD");
assert_ne!(landed, sealed, "the seal was rebased — its sha is scratch (bl-3616 §6.1)");
assert_eq!(tip(&remote, BRANCH), landed, "the rebased seal published");
assert!(git(&store, &["cat-file", "-e", "HEAD:tasks/bl-aaaa.md"]).is_ok(), "theirs arrived");
assert!(git(&store, &["cat-file", "-e", "HEAD:tasks/bl-bbbb.md"]).is_ok(), "ours survived");
assert!(git(&store, &["rev-parse", "--verify", "-q", "REBASE_HEAD"]).is_err(), "no rebase in progress");
}
#[test]
fn a_rejected_push_refuses_a_same_ball_race_naming_the_ball() {
let tmp = TempDir::new().unwrap();
let remote = remote_with_branch(tmp.path());
let store = store_clone(tmp.path(), &remote);
let other = checkout(tmp.path(), &remote, "other");
commit(&other, "tasks/bl-c0de.md", "claimant = other");
let published = git(&other, &["push", "-q", "origin", BRANCH]).map(|_| tip(&remote, BRANCH)).unwrap();
let sealed = commit(&store, "tasks/bl-c0de.md", "claimant = me");
let err = push(&binding(Some(&remote), &store), &env_top()).unwrap_err().to_string();
assert!(err.contains("push rejected"), "{err}");
assert!(err.contains("bl-c0de changed on both sides"), "{err}");
assert!(err.contains("nothing was published and nothing local was changed"), "{err}");
assert!(err.contains("run `bl sync`, then re-run it"), "{err}");
assert!(err.contains(&format!("the store checkout, {}:", store.display())), "{err}");
assert!(err.contains("`git rebase FETCH_HEAD`"), "{err}");
for raw in ["CONFLICT", "hint:", "could not apply", "Could not apply"] {
assert!(!err.contains(raw), "raw git leaked ({raw}): {err}");
}
assert_eq!(tip(&store, "HEAD"), sealed, "the seal stays for core to un-seal");
assert_eq!(tip(&remote, BRANCH), published, "the remote is untouched");
assert!(git(&store, &["rev-parse", "--verify", "-q", "REBASE_HEAD"]).is_err(), "the rebase was aborted");
assert!(git(&store, &["ls-files", "-u"]).unwrap().is_empty(), "no unmerged paths left behind");
}
#[test]
fn a_close_over_a_remote_update_is_the_same_named_refusal() {
let tmp = TempDir::new().unwrap();
let remote = remote_with_branch(tmp.path());
commit(&checkout(tmp.path(), &remote, "seed2"), "tasks/bl-dead.md", "born");
git(&tmp.path().join("seed2"), &["push", "-q", "origin", BRANCH]).unwrap();
let store = store_clone(tmp.path(), &remote);
let other = checkout(tmp.path(), &remote, "other");
commit(&other, "tasks/bl-dead.md", "updated remotely");
git(&other, &["push", "-q", "origin", BRANCH]).unwrap();
git(&store, &["rm", "-q", "tasks/bl-dead.md"]).unwrap();
git(&store, &["commit", "-q", "-m", "close bl-dead"]).unwrap();
let err = push(&binding(Some(&remote), &store), &env_top()).unwrap_err().to_string();
assert!(err.contains("bl-dead changed on both sides"), "{err}");
}
#[cfg(unix)]
#[test]
fn a_push_denied_after_a_clean_reconcile_stays_fail_closed() {
let tmp = TempDir::new().unwrap();
let remote = revoked_remote(tmp.path());
let store = store_clone(tmp.path(), &remote);
commit(&store, "tasks/bl-0001.md", "sealed");
let err = push(&binding(Some(&remote), &store), &env_top()).unwrap_err().to_string();
assert!(err.contains("even after reconciling"), "{err}");
assert!(err.contains("permissions, a server hook"), "{err}");
}