use super::*;
use super::super::model::Blocked;
use std::path::Path;
fn repository(dir: &Path, branch: &str) -> RepositoryFacts {
std::fs::create_dir_all(dir).expect("mkdir");
run(dir, &["init", "-q", "-b", branch]);
run(dir, &["config", "user.name", "Test"]);
run(dir, &["config", "user.email", "test@example.com"]);
std::fs::write(dir.join("file.txt"), format!("{}\n", dir.display())).expect("write");
run(dir, &["add", "."]);
run(dir, &["commit", "-q", "-m", "one"]);
facts(dir)
}
fn facts(dir: &Path) -> RepositoryFacts {
RepositoryFacts::read(RepositoryId::root(), dir, &dir.join(".git")).expect("facts")
}
fn run(dir: &Path, args: &[&str]) {
let output = crate::git_backend::git_command()
.args(args)
.current_dir(dir)
.output()
.unwrap_or_else(|e| panic!("git {args:?}: {e}"));
assert!(
output.status.success(),
"git {args:?} in {}: {}",
dir.display(),
String::from_utf8_lossy(&output.stderr)
);
}
fn pair() -> (tempfile::TempDir, RepositoryFacts, RepositoryFacts) {
let temp = tempfile::tempdir().expect("tempdir");
let origin = temp.path().join("origin");
repository(&origin, "feature/peer");
let desktop = temp.path().join("desktop");
let laptop = temp.path().join("laptop");
for clone in [&desktop, &laptop] {
let output = crate::git_backend::git_command()
.args(["clone", "-q", origin.to_str().expect("path"), clone.to_str().expect("path")])
.output()
.expect("clone");
assert!(output.status.success(), "{}", String::from_utf8_lossy(&output.stderr));
run(clone, &["config", "user.name", "Test"]);
run(clone, &["config", "user.email", "test@example.com"]);
}
let here = facts(&desktop);
let there = facts(&laptop);
(temp, here, there)
}
#[test]
fn test9944_matching_trees_are_eligible() {
let (_temp, here, there) = pair();
let epoch = eligibility(&here, &there).expect("matching clones are eligible");
assert_eq!(epoch.branch.as_deref(), Some("feature/peer"));
assert_eq!(epoch.head, here.head);
assert!(epoch.still_current(&here), "nothing moved, so the epoch stands");
assert_eq!(here.root_commit, there.root_commit);
assert!(!here.root_commit.is_empty());
}
#[test]
fn test9945_a_branch_mismatch_blocks() {
let (temp, here, _there) = pair();
let laptop = temp.path().join("laptop");
run(&laptop, &["switch", "-q", "-c", "main"]);
let there = facts(&laptop);
match eligibility(&here, &there) {
Err(Blocked::BranchMismatch { here, there }) => {
assert_eq!(here, "feature/peer");
assert_eq!(there, "main");
}
other => panic!("expected a branch mismatch, got {other:?}"),
}
}
#[test]
fn test9946_detached_heads_are_never_the_same_branch() {
let (temp, _here, _there) = pair();
let desktop = temp.path().join("desktop");
let laptop = temp.path().join("laptop");
let head = facts(&desktop).head.expect("a commit");
for clone in [&desktop, &laptop] {
run(clone, &["checkout", "-q", &head]);
}
let here = facts(&desktop);
let there = facts(&laptop);
assert_eq!(here.head, there.head, "the fixture must have identical commits");
assert!(here.branch.is_none() && there.branch.is_none());
match eligibility(&here, &there) {
Err(Blocked::DetachedHead { here, there }) => {
assert!(here && there, "both sides are detached");
}
other => panic!("expected a detached-head block, got {other:?}"),
}
}
#[test]
fn test9947_a_head_mismatch_blocks() {
let (temp, here, _there) = pair();
let laptop = temp.path().join("laptop");
std::fs::write(laptop.join("file.txt"), "two\n").expect("write");
run(&laptop, &["commit", "-qam", "two"]);
let there = facts(&laptop);
match eligibility(&here, &there) {
Err(Blocked::HeadMismatch { here: a, there: b }) => {
assert_eq!(Some(a), here.head);
assert_eq!(Some(b), there.head);
}
other => panic!("expected a head mismatch, got {other:?}"),
}
}
#[test]
fn test9948_unrelated_repositories_are_refused() {
let temp = tempfile::tempdir().expect("tempdir");
let one = temp.path().join("one");
let two = temp.path().join("two");
let here = repository(&one, "main");
let there = repository(&two, "main");
assert_ne!(
here.root_commit, there.root_commit,
"two histories must have two identities"
);
match eligibility(&here, &there) {
Err(Blocked::RepositoryMismatch { .. }) => {}
other => panic!("expected a repository mismatch, got {other:?}"),
}
}
#[test]
fn test9949_an_operation_in_progress_blocks() {
let (temp, here, there) = pair();
let laptop = temp.path().join("laptop");
std::fs::write(laptop.join(".git/MERGE_HEAD"), format!("{}\n", here.head.clone().unwrap()))
.expect("write");
let mid_merge = facts(&laptop);
assert_eq!(mid_merge.git_state, GitState::Merge);
match eligibility(&here, &mid_merge) {
Err(Blocked::GitOperation { here, there }) => {
assert!(!here && there, "only the peer is busy");
}
other => panic!("expected a git-operation block, got {other:?}"),
}
let _ = there;
}
#[test]
fn test9950_the_epoch_moves_with_the_repository() {
let (temp, here, _there) = pair();
let desktop = temp.path().join("desktop");
let epoch = here.epoch();
assert!(epoch.still_current(&facts(&desktop)));
std::fs::write(desktop.join("file.txt"), "changed\n").expect("write");
run(&desktop, &["commit", "-qam", "two"]);
assert!(
!epoch.still_current(&facts(&desktop)),
"a commit must invalidate a transfer prepared before it"
);
let after_commit = facts(&desktop).epoch();
run(&desktop, &["switch", "-q", "-c", "other"]);
assert!(
!after_commit.still_current(&facts(&desktop)),
"a branch change must invalidate a transfer prepared before it"
);
let after_switch = facts(&desktop).epoch();
std::fs::write(desktop.join("staged.txt"), "x\n").expect("write");
std::thread::sleep(std::time::Duration::from_millis(10));
run(&desktop, &["add", "staged.txt"]);
assert!(
after_switch.still_current(&facts(&desktop)),
"staging a file is not the repository moving under a transfer"
);
let before_rebase = facts(&desktop).epoch();
std::fs::create_dir_all(desktop.join(".git/rebase-merge")).expect("mkdir");
assert!(
!before_rebase.still_current(&facts(&desktop)),
"a git operation starting must invalidate a transfer prepared before it"
);
}
#[test]
fn test9951_a_missing_repository_blocks_and_says_which_side() {
let (_temp, here, _there) = pair();
let absent = RepositoryFacts::absent(RepositoryId::parse("cartridges").expect("id"));
match eligibility(&here, &absent) {
Err(Blocked::SubmoduleMissing { here, there }) => {
assert!(here && !there);
}
other => panic!("expected a missing-repository block, got {other:?}"),
}
match eligibility(&absent, &here) {
Err(Blocked::SubmoduleMissing { here, there }) => {
assert!(!here && there);
}
other => panic!("expected a missing-repository block, got {other:?}"),
}
}