use std::sync::Arc;
use ipld_core::ipld::Ipld;
use mnem_core::error::{Error, RepoError};
use mnem_core::id::{CODEC_RAW, Cid, Multihash, NodeId};
use mnem_core::objects::{Node, RefTarget};
use mnem_core::repo::{CommitOptions, ReadonlyRepo};
use mnem_core::store::{Blockstore, MemoryBlockstore, MemoryOpHeadsStore, OpHeadsStore};
fn cid(seed: u32) -> Cid {
Cid::new(CODEC_RAW, Multihash::sha2_256(&seed.to_be_bytes()))
}
fn main() -> Result<(), Box<dyn std::error::Error>> {
let bs: Arc<dyn Blockstore> = Arc::new(MemoryBlockstore::new());
let ohs: Arc<dyn OpHeadsStore> = Arc::new(MemoryOpHeadsStore::new());
let repo = ReadonlyRepo::init(bs.clone(), ohs.clone())?;
println!("# mnem M11: CAS + linearize smoke test");
println!("# mnem-core: {}", mnem_core::VERSION);
println!();
let v1 = RefTarget::normal(cid(1));
let r1 = repo.update_ref(
"refs/heads/main",
None,
Some(v1.clone()),
"alice@example.org",
)?;
println!("CAS create: refs/heads/main -> {v1:?}");
let wrong = RefTarget::normal(cid(99));
let stale = r1.update_ref(
"refs/heads/main",
Some(&wrong),
Some(RefTarget::normal(cid(2))),
"alice@example.org",
);
match stale {
Err(Error::Repo(RepoError::Stale)) => {
println!("CAS stale: ok (wrong expected_prev rejected)");
}
other => panic!("expected Stale, got {other:?}"),
}
let v2 = RefTarget::normal(cid(2));
let r2 = r1.update_ref(
"refs/heads/main",
Some(&v1),
Some(v2.clone()),
"alice@example.org",
)?;
println!("CAS update: refs/heads/main -> {v2:?}");
let r3 = r2.update_ref("refs/heads/main", Some(&v2), None, "alice@example.org")?;
assert!(!r3.view().refs.contains_key("refs/heads/main"));
println!("CAS delete: refs/heads/main removed");
println!();
let mut tx = r3.start_transaction();
let alice =
Node::new(NodeId::new_v7(), "Person").with_prop("name", Ipld::String("Alice".into()));
tx.add_node(&alice)?;
let r4 = tx.commit_opts(CommitOptions {
author: "alice@example.org",
message: "add Alice (linearized)",
linearize: true,
time_micros: None,
change_id: None,
})?;
println!("linearize happy path: commit succeeded at {}", r4.op_id());
let stale_tx_base = r4.clone();
let mut stale_tx = stale_tx_base.start_transaction();
stale_tx.add_node(&Node::new(NodeId::new_v7(), "Ghost"))?;
let mut winner_tx = r4.start_transaction();
winner_tx.add_node(&Node::new(NodeId::new_v7(), "Bob"))?;
let r5 = winner_tx.commit("alice@example.org", "concurrent: add Bob")?;
println!("concurrent writer advanced op-head to {}", r5.op_id());
let outcome = stale_tx.commit_opts(CommitOptions {
author: "alice@example.org",
message: "from stale base",
linearize: true,
time_micros: None,
change_id: None,
});
match outcome {
Err(Error::Repo(RepoError::Stale)) => {
println!("linearize stale: ok (stale base rejected)");
}
Ok(_) => panic!("linearize commit against stale base should have failed"),
Err(e) => panic!("unexpected error: {e:?}"),
}
let mut late_tx = r4.start_transaction();
late_tx.add_node(&Node::new(NodeId::new_v7(), "Carol"))?;
let late = late_tx.commit("alice@example.org", "late but not linearized")?;
let heads_now = ohs.current()?;
println!(
"non-linearize stale: ok (new head appended; total heads now {})",
heads_now.len()
);
println!(" - the next reader will 3-way-merge these heads (M8.5 feature)");
drop(late);
println!();
println!("# M11 CAS + linearize smoke test: ok");
Ok(())
}