crates/mimir-ingest/tests/ingest/dated.rs
//! The dated-sibling retry's git half: the committer time of a commit, the
//! last first-parent ancestor at or before a time, and the plan/restore pair
//! the driver uses to date every sibling clone back for one retry.
use std::path::{Path, PathBuf};
use mimir_ingest::checkout::checkout_changed;
use mimir_ingest::git::{commit_seconds, head, head_ancestor_before};
use mimir_ingest::{dated_sibling_plan, restore_siblings};
/// One commit of one file with a CHOSEN committer time (seconds, UTC).
/// `nornir_git::gitio::commit_all` stamps every fixture commit with one fixed
/// time, so distinct times must be written here, directly through gix.
fn commit_at(root: &Path, rel: &str, text: &str, msg: &str, seconds: i64) -> String {
let p = root.join(rel);
std::fs::create_dir_all(p.parent().unwrap()).unwrap();
std::fs::write(&p, text).unwrap();
let repo = gix::open(root).unwrap();
let parent = repo.head_id().ok().map(|id| id.detach());
let base = match parent {
Some(p) => repo.find_commit(p).unwrap().tree_id().unwrap().detach(),
None => gix::ObjectId::empty_tree(repo.object_hash()),
};
let mut editor = repo.edit_tree(base).unwrap();
let blob = repo.write_blob(text.as_bytes()).unwrap().detach();
editor.upsert(gix::bstr::BStr::new(rel), gix::object::tree::EntryKind::Blob, blob).unwrap();
let tree = editor.write().unwrap().detach();
let time = format!("{seconds} +0000");
let sig = gix::actor::SignatureRef { name: gix::bstr::BStr::new("Dated Fixture"), email: gix::bstr::BStr::new("dated@nornir.invalid"), time: &time };
repo.commit_as(sig, sig, "HEAD", msg, tree, parent.into_iter().collect::<Vec<_>>()).unwrap().to_string()
}
fn repo_at(root: PathBuf) -> PathBuf {
nornir_git::gitio::init(&root).unwrap();
root
}
#[test]
fn ancestor_at_or_before_picks_across_three_commits() {
let tmp = tempfile::tempdir().unwrap();
let repo = repo_at(tmp.path().join("r"));
let c1 = commit_at(&repo, "src/lib.rs", "// one", "one", 1_000);
let c2 = commit_at(&repo, "src/lib.rs", "// two", "two", 2_000);
let c3 = commit_at(&repo, "src/lib.rs", "// three", "three", 3_000);
assert_eq!(commit_seconds(&repo, &c2).unwrap(), 2_000);
assert_eq!(head_ancestor_before(&repo, 3_500).unwrap(), Some(c3));
assert_eq!(head_ancestor_before(&repo, 2_500).unwrap(), Some(c2.clone()));
// at-or-before: a commit AT the asked second is taken
assert_eq!(head_ancestor_before(&repo, 2_000).unwrap(), Some(c2));
assert_eq!(head_ancestor_before(&repo, 1_999).unwrap(), Some(c1));
}
#[test]
fn ancestor_is_none_when_every_commit_is_younger() {
let tmp = tempfile::tempdir().unwrap();
let repo = repo_at(tmp.path().join("r"));
commit_at(&repo, "src/lib.rs", "// one", "one", 1_000);
commit_at(&repo, "src/lib.rs", "// two", "two", 2_000);
assert_eq!(head_ancestor_before(&repo, 500).unwrap(), None);
}
#[test]
fn plan_dates_only_the_siblings_that_move_and_restore_puts_them_back() {
let tmp = tempfile::tempdir().unwrap();
let clones = tmp.path().join("clones");
let member = repo_at(clones.join("member"));
commit_at(&member, "src/lib.rs", "// member", "member", 2_000);
// a sibling whose tip is younger than the member's commit: dated back
let sib_new = repo_at(clones.join("sib_new"));
let old = commit_at(&sib_new, "src/lib.rs", "// v1", "v1", 1_000);
let tip = commit_at(&sib_new, "src/lib.rs", "// v2", "v2", 3_000);
// a sibling already old enough: its dated commit IS its head — skipped
let sib_old = repo_at(clones.join("sib_old"));
let only = commit_at(&sib_old, "src/lib.rs", "// v1", "v1", 1_000);
// a plain directory without .git: not a sibling
std::fs::create_dir_all(clones.join("notrepo")).unwrap();
let plan = dated_sibling_plan(&clones, &member, 2_000);
assert_eq!(plan, vec![(clones.join("sib_new"), tip.clone(), old.clone())]);
// the retry: every planned sibling checked out at its dated commit …
for (dir, _, dated) in &plan {
checkout_changed(dir, dated).unwrap();
}
assert_eq!(head(&sib_new).as_deref(), Some(old.as_str()));
assert_eq!(std::fs::read_to_string(sib_new.join("src/lib.rs")).unwrap(), "// v1");
// … and put back at the head it had, whatever the retry did
restore_siblings(&plan);
assert_eq!(head(&sib_new).as_deref(), Some(tip.as_str()));
assert_eq!(std::fs::read_to_string(sib_new.join("src/lib.rs")).unwrap(), "// v2");
assert_eq!(head(&sib_old).as_deref(), Some(only.as_str()));
}