aboutsummaryrefslogtreecommitdiffstats
path: root/src/remake/delta.rs
diff options
context:
space:
mode:
authortv <tv@krebsco.de>2026-10-31 19:53:27 +0100
committertv <tv@krebsco.de>2026-10-31 19:53:27 +0100
commit6105507200231e67ba544c12aa10b988e1c23826 (patch)
tree880530b7401cb5d43f52e06a68b4d704326aa272 /src/remake/delta.rs
parent8d7adfbdb7798cca9d9783761f290e3bd85d9a3a (diff)
use Automerge as Document instead of AutoCommit
This allows a lot of operations to drop mut.
Diffstat (limited to 'src/remake/delta.rs')
-rw-r--r--src/remake/delta.rs72
1 files changed, 37 insertions, 35 deletions
diff --git a/src/remake/delta.rs b/src/remake/delta.rs
index 21c07b7..06f2913 100644
--- a/src/remake/delta.rs
+++ b/src/remake/delta.rs
@@ -23,7 +23,7 @@
use std::collections::{BTreeMap, HashSet};
-use automerge::{ActorId, AutoCommit, ChangeHash};
+use crate::document::{ActorId, ChangeHash, Document, Transactable};
use tracing::warn;
use super::kind::{Translation, Translator};
@@ -53,10 +53,10 @@ impl ReplayStats {
/// Replays into `dst` everything that changed in `src` between `before` and `after`,
/// as one commit. Both head sets must be in `src`.
pub(crate) fn replay(
- src: &mut AutoCommit,
+ src: &Document,
before: &[ChangeHash],
after: &[ChangeHash],
- dst: &mut AutoCommit,
+ dst: &mut Document,
translator: &Translator,
) -> ReplayStats {
if before == after {
@@ -64,14 +64,17 @@ pub(crate) fn replay(
}
let paths = changed_paths(src, before, after);
- replay_paths(src, dst, &paths, translator)
+ let mut transaction = dst.transaction();
+ let stats = replay_paths(src, &mut transaction, &paths, translator);
+ transaction.commit();
+ stats
}
-/// Replays the state of `paths` (from [`changed_paths`]) from `src` into `dst`, as one
-/// commit. Lets callers find the paths outside a lock and copy them inside it.
+/// Replays the state of `paths` (from [`changed_paths`]) from `src` into `dst` (the
+/// caller commits). Lets callers find the paths outside a lock and copy them inside it.
pub(crate) fn replay_paths(
- src: &AutoCommit,
- dst: &mut AutoCommit,
+ src: &Document,
+ dst: &mut impl Transactable,
paths: &[Vec<String>],
translator: &Translator,
) -> ReplayStats {
@@ -79,13 +82,12 @@ pub(crate) fn replay_paths(
for path in paths {
replay_path(src, dst, path, translator, &mut stats);
}
- let _ = dst.commit();
stats
}
fn replay_path(
- src: &AutoCommit,
- dst: &mut AutoCommit,
+ src: &Document,
+ dst: &mut impl Transactable,
path: &[String],
translator: &Translator,
stats: &mut ReplayStats,
@@ -164,7 +166,7 @@ pub(crate) struct Missing {
/// replayed by the nodes that switched, and replaying them again would overwrite newer
/// writes in the new epoch with stale ones.
pub(crate) fn missing_from_snapshot(
- doc: &mut AutoCommit,
+ doc: &Document,
clock: &BTreeMap<String, u64>,
own: &[ActorId],
) -> Missing {
@@ -225,7 +227,7 @@ pub(crate) fn missing_from_snapshot(
/// The minimal set of store paths covering every change between `before` and `after`:
/// sorted, and no path is a prefix of another.
pub(crate) fn changed_paths(
- doc: &mut AutoCommit,
+ doc: &Document,
before: &[ChangeHash],
after: &[ChangeHash],
) -> Vec<Vec<String>> {
@@ -255,14 +257,14 @@ mod tests {
use automerge::{ObjType, ROOT, ReadDoc, ScalarValue, transaction::Transactable};
use serde_json::json;
- fn view(doc: &AutoCommit) -> serde_json::Value {
+ fn view(doc: &Document) -> serde_json::Value {
store::read_path(doc, &[]).unwrap().unwrap()
}
/// A random store write as the API makes it (one change): a scalar or a small map
/// at a path up to three keys deep, or a delete. Writes the store refuses (e.g. into
/// a scalar) are rolled back.
- fn random_write(rng: &mut crate::testrand::Rng, doc: &mut AutoCommit) {
+ fn random_write(rng: &mut crate::testrand::Rng, doc: &mut Document) {
const KEYS: [&str; 3] = ["a", "b", "c"];
let depth = rng.between(1, 3);
let path: Vec<String> = (0..depth).map(|_| rng.pick(&KEYS).to_string()).collect();
@@ -289,17 +291,17 @@ mod tests {
fn replay_reproduces_the_document() {
crate::testrand::for_seeds(200, |rng| {
let actor = |i: u8| ActorId::from(&[i; 16][..]);
- let mut old = AutoCommit::new().with_actor(actor(0));
+ let mut old = Document::new().with_actor(actor(0));
for _ in 0..rng.between(0, 8) {
random_write(rng, &mut old);
}
let base = old.get_heads();
- let mut new = AutoCommit::new().with_actor(actor(100));
+ let mut new = Document::new().with_actor(actor(100));
change(&mut new, |new| {
store::materialize_into(&old, new, &|_| false).unwrap()
});
- let mut nodes: Vec<AutoCommit> = (1..=rng.between(1, 3))
+ let mut nodes: Vec<Document> = (1..=rng.between(1, 3))
.map(|i| old.fork().with_actor(actor(i as u8)))
.collect();
for _ in 0..rng.between(1, 20) {
@@ -311,7 +313,7 @@ mod tests {
}
let after = old.get_heads();
- let stats = replay(&mut old, &base, &after, &mut new, &Translator::default());
+ let stats = replay(&old, &base, &after, &mut new, &Translator::default());
assert_eq!(stats.failed, 0);
assert_eq!(view(&new), view(&old));
});
@@ -342,12 +344,12 @@ mod tests {
fn changed_paths_cover_every_change_minimally() {
crate::testrand::for_seeds(300, |rng| {
let actor = |i: u8| ActorId::from(&[i; 16][..]);
- let mut doc = AutoCommit::new().with_actor(actor(0));
+ let mut doc = Document::new().with_actor(actor(0));
for _ in 0..rng.between(0, 8) {
random_write(rng, &mut doc);
}
let before = doc.get_heads();
- let mut forks: Vec<AutoCommit> = (1..=rng.between(1, 3))
+ let mut forks: Vec<Document> = (1..=rng.between(1, 3))
.map(|i| doc.fork().with_actor(actor(i as u8)))
.collect();
for _ in 0..rng.between(0, 12) {
@@ -359,7 +361,7 @@ mod tests {
}
let after = doc.get_heads();
- let paths = changed_paths(&mut doc, &before, &after);
+ let paths = changed_paths(&doc, &before, &after);
for pair in paths.windows(2) {
assert!(pair[0] < pair[1], "not sorted: {paths:?}");
assert!(!pair[1].starts_with(&pair[0]), "not minimal: {paths:?}");
@@ -388,7 +390,7 @@ mod tests {
#[test]
fn minimal_paths() {
- let mut doc = AutoCommit::new();
+ let mut doc = Document::new();
change(&mut doc, |doc| doc.put(ROOT, "untouched", 1_i64).unwrap());
let before = doc.get_heads();
@@ -406,7 +408,7 @@ mod tests {
});
let after = doc.get_heads();
- let all = changed_paths(&mut doc, &before, &after);
+ let all = changed_paths(&doc, &before, &after);
assert_eq!(
all,
vec![
@@ -417,14 +419,14 @@ mod tests {
]
);
- let later = changed_paths(&mut doc, &middle, &after);
+ let later = changed_paths(&doc, &middle, &after);
assert_eq!(later, vec![vec!["c".to_owned()], vec!["l".to_owned()]]);
}
#[test]
fn replays_state_per_key() {
// Base shared by the old document and the snapshot.
- let mut old = AutoCommit::new();
+ let mut old = Document::new();
let m = change(&mut old, |old| {
let m = old.put_object(ROOT, "m", ObjType::Map).unwrap();
old.put(&m, "keep", "base").unwrap();
@@ -434,7 +436,7 @@ mod tests {
});
let base = old.get_heads();
- let mut new = AutoCommit::new();
+ let mut new = Document::new();
change(&mut new, |new| {
store::materialize_into(&old, new, &|_| false).unwrap();
// Something the new epoch already holds that the delta must not clobber.
@@ -451,7 +453,7 @@ mod tests {
});
let after = old.get_heads();
- let stats = replay(&mut old, &base, &after, &mut new, &Translator::default());
+ let stats = replay(&old, &base, &after, &mut new, &Translator::default());
assert_eq!(stats.failed, 0);
assert_eq!(
view(&new),
@@ -464,7 +466,7 @@ mod tests {
let own = ActorId::from(&b"own-actor"[..]);
let other = ActorId::from(&b"other-actor"[..]);
- let mut doc = AutoCommit::new().with_actor(own.clone());
+ let mut doc = Document::new().with_actor(own.clone());
change(&mut doc, |doc| doc.put(ROOT, "base", 1_i64).unwrap());
let base = doc.get_heads();
@@ -483,18 +485,18 @@ mod tests {
change(&mut doc, |doc| doc.put(ROOT, "mine", 1_i64).unwrap());
let after = doc.get_heads();
- let missing = missing_from_snapshot(&mut doc, &clock, std::slice::from_ref(&own));
+ let missing = missing_from_snapshot(&doc, &clock, std::slice::from_ref(&own));
assert_eq!(missing.own, 1);
assert_eq!(missing.before, with_foreign);
assert_ne!(missing.before, base);
assert_eq!(
- changed_paths(&mut doc, &missing.before, &after),
+ changed_paths(&doc, &missing.before, &after),
vec![vec!["mine".to_owned()]]
);
// Nothing of ours missing: nothing to replay.
let full: BTreeMap<String, u64> = [(own.to_string(), 2)].into_iter().collect();
- let none = missing_from_snapshot(&mut doc, &full, std::slice::from_ref(&own));
+ let none = missing_from_snapshot(&doc, &full, std::slice::from_ref(&own));
assert_eq!(none.own, 0);
let mut heads = after.clone();
heads.sort();
@@ -503,11 +505,11 @@ mod tests {
#[test]
fn protocol_metadata_is_never_replayed() {
- let mut old = AutoCommit::new();
+ let mut old = Document::new();
change(&mut old, |old| old.put(ROOT, "x", 1_i64).unwrap());
let base = old.get_heads();
- let mut new = AutoCommit::new();
+ let mut new = Document::new();
change(&mut new, |new| {
store::write_path(
new,
@@ -524,7 +526,7 @@ mod tests {
});
let after = old.get_heads();
- replay(&mut old, &base, &after, &mut new, &Translator::default());
+ replay(&old, &base, &after, &mut new, &Translator::default());
let meta = &view(&new)[meta::ROOT];
assert_eq!(meta[EPOCHS]["1"]["generation"], json!(1));
assert_eq!(meta["forgotten"]["pk"], json!(1));