diff options
| -rw-r--r-- | src/access/screen.rs | 115 |
1 files changed, 90 insertions, 25 deletions
diff --git a/src/access/screen.rs b/src/access/screen.rs index ae61416..e15b407 100644 --- a/src/access/screen.rs +++ b/src/access/screen.rs @@ -21,6 +21,7 @@ //! //! A change whose dependencies are neither in the document nor among those received //! waits (`Waiting`) until they come, a bounded number; Automerge never sees it before. +//! One that builds on a change refused meanwhile is refused then, not left waiting. //! With signatures required (`access::policy`), so does one no signature by its //! committer vouches for yet, and whatever builds on it, until one comes, but an epoch's //! root, which no node signs: a signature vouches for its change and the changes of its @@ -248,31 +249,7 @@ pub(crate) fn apply( .collect(); // Building on one refused before: refused too, without waiting for it (it never // comes) or judging it. - let (building, candidates): (Vec<Change>, Vec<Change>) = candidates - .into_iter() - .partition(|change| change.deps().iter().any(|dep| refusals.contains(dep))); - for change in building { - let dep = change - .deps() - .iter() - .find(|dep| refusals.contains(dep)) - .copied() - .expect("partitioned so"); - let root = refusals.root(&dep).unwrap_or(dep); - refusals.insert(change.hash(), root); - screened.roots.insert(root); - screened.refused.push(Refused { - committer: change.author().and_then(|author| { - std::str::from_utf8(author.as_bytes()) - .ok() - .map(str::to_owned) - }), - user: change.message().and_then(user_of).map(str::to_owned), - paths: BTreeSet::new(), - why: format!("it builds on refused change {dep}"), - change, - }); - } + let candidates = refuse_building(candidates, refusals, &mut screened); let (ready, unready) = in_order(doc, candidates); waiting.0 = unready; if ready.is_empty() { @@ -469,10 +446,55 @@ pub(crate) fn apply( screened.roots.insert(root); } } + // Waiting for one refused since (or building on such): refused now, not left to + // age out. + waiting.0 = refuse_building(std::mem::take(&mut waiting.0), refusals, &mut screened); + screened.roots.retain(|root| !forged.contains(root)); waiting.keep_newest(); Ok(screened) } +/// Of `changes`, those building on a change `refusals` holds, or on one of these that +/// does, and so on: refused, into `refusals` and `screened`, without being judged. The +/// rest, as they were. +fn refuse_building( + mut changes: Vec<Change>, + refusals: &mut Refusals, + screened: &mut Screened, +) -> Vec<Change> { + loop { + let (building, rest): (Vec<Change>, Vec<Change>) = changes + .into_iter() + .partition(|change| change.deps().iter().any(|dep| refusals.contains(dep))); + changes = rest; + if building.is_empty() { + return changes; + } + for change in building { + let dep = change + .deps() + .iter() + .find(|dep| refusals.contains(dep)) + .copied() + .expect("partitioned so"); + let root = refusals.root(&dep).unwrap_or(dep); + refusals.insert(change.hash(), root); + screened.roots.insert(root); + screened.refused.push(Refused { + committer: change.author().and_then(|author| { + std::str::from_utf8(author.as_bytes()) + .ok() + .map(str::to_owned) + }), + user: change.message().and_then(user_of).map(str::to_owned), + paths: BTreeSet::new(), + why: format!("it builds on refused change {dep}"), + change, + }); + } + } +} + /// `accepted` applied to `doc`. Automerge takes them all or none: one it won't take (a /// fork of an actor's changes, under a policy without signatures) fails the lot, so the /// error names them (the first few), with the node each says made it, for the log of @@ -919,6 +941,49 @@ mod tests { ); } + /// A change waiting for a dependency that hasn't come, building on another refused + /// meanwhile: refused at once, not left to wait. + #[test] + fn waiting_on_a_refused_change_refused_at_once() { + let mut theirs = genesis(); + let mut other = theirs.fork(); + write(&mut theirs, "a", Some("mallory"), "notes/x", json!(1)); + write(&mut other, "a", Some("alice"), "notes/w", json!(2)); + let missing = other.get_heads()[0]; + theirs.merge(&mut other).unwrap(); + write(&mut theirs, "a", Some("alice"), "notes/z", json!(3)); + let last = theirs.get_heads()[0]; + let mut ours = Document::new(); + let mut waiting = Waiting::default(); + let mut refusals = Refusals::default(); + let mut screen_with = |ours: &mut Document, changes, waiting: &mut Waiting| { + apply( + ours, + Epoch::GENESIS, + changes, + waiting, + &mut refusals, + None, + &mut crate::document::PatchLog::inactive(), + ) + .unwrap() + }; + let all = received(&theirs, &[]); + let first: Vec<Change> = all.iter().filter(|c| c.hash() == last).cloned().collect(); + let screened = screen_with(&mut ours, first, &mut waiting); + assert_eq!((screened.applied, waiting.len()), (0, 1)); + // All but one it builds on: mallory's refused, and the waiting one with it. + let rest: Vec<Change> = all + .into_iter() + .filter(|c| c.hash() != missing && c.hash() != last) + .collect(); + let screened = screen_with(&mut ours, rest, &mut waiting); + let refused: Vec<ChangeHash> = screened.refused.iter().map(|r| r.change.hash()).collect(); + assert_eq!(refused.len(), 2); + assert!(refused.contains(&last)); + assert_eq!(waiting.len(), 0); + } + /// With signatures required, changes no signature vouches for wait, with what /// builds on them, until one comes; one signature vouches for its actor's earlier /// changes too. |
