From 11d098477cfc76da0bc4385635e4c4b7124a9259 Mon Sep 17 00:00:00 2001 From: tv Date: Thu, 5 Nov 2026 03:40:09 +0100 Subject: announce only to those behind --- src/document/changes.rs | 43 +++++++++++++++++++++++++++++++++++++++++++ src/document/sync.rs | 5 +++++ 2 files changed, 48 insertions(+) (limited to 'src/document') diff --git a/src/document/changes.rs b/src/document/changes.rs index 19d4db6..1091261 100644 --- a/src/document/changes.rs +++ b/src/document/changes.rs @@ -8,6 +8,25 @@ use anyhow::{Context, Result, anyhow, bail}; use super::{Change, ChangeHash, Document, ReadDoc}; +/// Whether the history of `theirs` (of them, those `doc` has) holds all of `ours` +/// (heads of `doc`): a peer at `theirs` has every change we have. +pub(crate) fn covers(doc: &Document, theirs: &[ChangeHash], ours: &[ChangeHash]) -> bool { + let known: Vec = theirs + .iter() + .copied() + .filter(|head| doc.get_change_by_hash(head).is_some()) + .collect(); + if known.is_empty() { + return ours.is_empty(); + } + let beyond: std::collections::HashSet = doc + .get_changes_meta(&known) + .into_iter() + .map(|change| change.hash) + .collect(); + ours.iter().all(|head| !beyond.contains(head)) +} + /// Those of `heads` that `doc` doesn't have. pub(crate) fn missing(doc: &impl ReadDoc, heads: &[ChangeHash]) -> Vec { heads @@ -176,6 +195,30 @@ mod tests { }); } + /// Heads cover ours when their history holds all of ours: as far as ours, or + /// further; not when behind or beside; heads we don't know count for nothing. + #[test] + fn heads_that_cover_ours() { + let mut doc = Document::new(); + change(&mut doc, |doc| doc.put(ROOT, "a", 1_i64).unwrap()); + let early = doc.get_heads(); + change(&mut doc, |doc| doc.put(ROOT, "b", 2_i64).unwrap()); + let ours = doc.get_heads(); + let mut further = doc.fork(); + change(&mut further, |doc| doc.put(ROOT, "c", 3_i64).unwrap()); + doc.merge(&mut further.clone()).unwrap(); + let unknown = ChangeHash([7; 32]); + + assert!(covers(&doc, &ours, &ours)); + assert!(covers(&doc, &further.get_heads(), &ours), "further"); + assert!(!covers(&doc, &early, &ours), "behind"); + assert!(!covers(&doc, &[unknown], &ours), "unknown"); + assert!( + covers(&doc, &[unknown, ours[0]], &ours), + "the known one does" + ); + } + #[test] fn no_delta_without_its_heads() { let mut doc = Document::new(); diff --git a/src/document/sync.rs b/src/document/sync.rs index 8c04632..e3ff157 100644 --- a/src/document/sync.rs +++ b/src/document/sync.rs @@ -28,6 +28,11 @@ impl Received { !self.0.changes.is_empty() } + /// The sender's heads, as it says. + pub(crate) fn heads(&self) -> &[crate::document::ChangeHash] { + &self.0.heads + } + /// The bytes of the changes it carries, and of its bloom filters (`have`). pub(crate) fn sizes(&self) -> (usize, usize) { let changes = self.0.changes.iter().map(<[u8]>::len).sum(); -- cgit v1.3.1