cob: Use new `Immutable` lattice for merges

Instead of `Redactable`, which is not needed since we have an `LWWMap`,
we use a new `Immutable` semilattice.
This commit is contained in:
Alexis Sellier 2023-06-28 14:35:21 +02:00
parent 11b32725c8
commit 369fc7637c
No known key found for this signature in database
3 changed files with 72 additions and 10 deletions

View File

@ -0,0 +1,52 @@
use crate::Semilattice;
/// A [`Semilattice`] that panics when attempting to merge inequal elements.
/// Use this for types that will never merge.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Immutable<T>(pub T);
impl<T> Immutable<T> {
/// Create a new immutable object.
pub fn new(inner: T) -> Self {
Self(inner)
}
}
impl<T> std::ops::Deref for Immutable<T> {
type Target = T;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl<T: PartialEq> Semilattice for Immutable<T> {
fn merge(&mut self, other: Self) {
if self.0 != other.0 {
panic!("Immutable::merge: Cannot merge inequal objects");
}
}
}
#[cfg(test)]
mod test {
use super::*;
#[test]
#[should_panic]
fn test_merge_inequal() {
let mut a = Immutable::new(0);
let b = Immutable::new(1);
a.merge(b);
}
#[test]
fn test_merge_equal() {
let mut a = Immutable::new(1);
let b = Immutable::new(1);
a.merge(b);
assert_eq!(a, b);
}
}

View File

@ -5,6 +5,7 @@
pub mod clock;
pub mod gmap;
pub mod gset;
pub mod immutable;
pub mod lwwmap;
pub mod lwwreg;
pub mod lwwset;
@ -19,6 +20,7 @@ pub mod test;
pub use clock::Lamport;
pub use gmap::GMap;
pub use gset::GSet;
pub use immutable::Immutable;
pub use lwwmap::LWWMap;
pub use lwwreg::LWWReg;
pub use lwwset::LWWSet;

View File

@ -12,7 +12,9 @@ use serde::{Deserialize, Serialize};
use thiserror::Error;
use radicle_crdt::clock;
use radicle_crdt::{GMap, GSet, LWWMap, LWWReg, LWWSet, Lamport, Max, Redactable, Semilattice};
use radicle_crdt::{
GMap, GSet, Immutable, LWWMap, LWWReg, LWWSet, Lamport, Max, Redactable, Semilattice,
};
use crate::cob;
use crate::cob::common::{Author, Tag, Timestamp};
@ -181,6 +183,7 @@ impl MergeTarget {
}
}
/// Patch CRDT.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Patch {
/// Title of the patch.
@ -190,11 +193,20 @@ pub struct Patch {
/// Target this patch is meant to be merged in.
target: LWWReg<Max<MergeTarget>>,
/// Associated tags.
/// Tags can be added and removed at will.
tags: LWWSet<Tag>,
/// Patch merges.
merges: LWWMap<ActorId, Redactable<Merge>>,
///
/// Only one merge is allowed per user.
///
/// Merges can be removed and replaced, but not modified. Generally, once a revision is merged,
/// it stays that way. Being able to remove merges may be useful in case of force updates
/// on the target branch.
merges: LWWMap<ActorId, Immutable<Merge>>,
/// List of patch revisions. The initial changeset is part of the
/// first revision.
///
/// Revisions can be redacted, but are otherwise immutable.
revisions: GMap<RevisionId, Redactable<Revision>>,
/// Users assigned to review this patch.
reviewers: LWWSet<ActorId>,
@ -304,9 +316,7 @@ impl Patch {
/// Get the merges.
pub fn merges(&self) -> impl Iterator<Item = (&ActorId, &Merge)> {
self.merges
.iter()
.filter_map(|(a, m)| m.get().map(|m| (a, m)))
self.merges.iter().map(|(a, m)| (a, m.deref()))
}
/// Reference to the Git object containing the code on the latest revision.
@ -603,7 +613,7 @@ impl store::FromHistory for Patch {
}
self.merges.insert(
op.author,
Redactable::Present(Merge {
Immutable::new(Merge {
revision,
commit,
timestamp,
@ -614,9 +624,7 @@ impl store::FromHistory for Patch {
let mut merges = self.merges.iter().fold(
HashMap::<(RevisionId, git::Oid), usize>::new(),
|mut acc, (_, merge)| {
if let Some(merge) = merge.get() {
*acc.entry((merge.revision, merge.commit)).or_default() += 1;
}
*acc.entry((merge.revision, merge.commit)).or_default() += 1;
acc
},
);
@ -1799,7 +1807,7 @@ mod test {
let (merger, merge) = merges.first().unwrap();
assert_eq!(*merger, signer.public_key());
assert_eq!(merge.get().unwrap().commit, pr.base);
assert_eq!(merge.commit, pr.base);
}
#[test]