aboutsummaryrefslogtreecommitdiffstats
path: root/src/document
diff options
context:
space:
mode:
authortv <tv@krebsco.de>2026-11-05 03:40:09 +0100
committertv <tv@krebsco.de>2026-11-05 03:40:09 +0100
commit11d098477cfc76da0bc4385635e4c4b7124a9259 (patch)
tree941640ead4d8e80b291780f708a56f8be53a0fe1 /src/document
parent9f9015a35b591043ebaf6e306f967ffc62cd07f5 (diff)
announce only to those behind
Diffstat (limited to 'src/document')
-rw-r--r--src/document/changes.rs43
-rw-r--r--src/document/sync.rs5
2 files changed, 48 insertions, 0 deletions
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<ChangeHash> = 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<ChangeHash> = 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<ChangeHash> {
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();