Your browser sucks, JavaScript disabled! Gunnar's console needs JavaScript and WebAssembly. Git over SSH on port 2222 does not.

nordisk/nornir

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()));
}