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:
parent
11b32725c8
commit
369fc7637c
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -5,6 +5,7 @@
|
||||||
pub mod clock;
|
pub mod clock;
|
||||||
pub mod gmap;
|
pub mod gmap;
|
||||||
pub mod gset;
|
pub mod gset;
|
||||||
|
pub mod immutable;
|
||||||
pub mod lwwmap;
|
pub mod lwwmap;
|
||||||
pub mod lwwreg;
|
pub mod lwwreg;
|
||||||
pub mod lwwset;
|
pub mod lwwset;
|
||||||
|
|
@ -19,6 +20,7 @@ pub mod test;
|
||||||
pub use clock::Lamport;
|
pub use clock::Lamport;
|
||||||
pub use gmap::GMap;
|
pub use gmap::GMap;
|
||||||
pub use gset::GSet;
|
pub use gset::GSet;
|
||||||
|
pub use immutable::Immutable;
|
||||||
pub use lwwmap::LWWMap;
|
pub use lwwmap::LWWMap;
|
||||||
pub use lwwreg::LWWReg;
|
pub use lwwreg::LWWReg;
|
||||||
pub use lwwset::LWWSet;
|
pub use lwwset::LWWSet;
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,9 @@ use serde::{Deserialize, Serialize};
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
|
|
||||||
use radicle_crdt::clock;
|
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;
|
||||||
use crate::cob::common::{Author, Tag, Timestamp};
|
use crate::cob::common::{Author, Tag, Timestamp};
|
||||||
|
|
@ -181,6 +183,7 @@ impl MergeTarget {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Patch CRDT.
|
||||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||||
pub struct Patch {
|
pub struct Patch {
|
||||||
/// Title of the patch.
|
/// Title of the patch.
|
||||||
|
|
@ -190,11 +193,20 @@ pub struct Patch {
|
||||||
/// Target this patch is meant to be merged in.
|
/// Target this patch is meant to be merged in.
|
||||||
target: LWWReg<Max<MergeTarget>>,
|
target: LWWReg<Max<MergeTarget>>,
|
||||||
/// Associated tags.
|
/// Associated tags.
|
||||||
|
/// Tags can be added and removed at will.
|
||||||
tags: LWWSet<Tag>,
|
tags: LWWSet<Tag>,
|
||||||
/// Patch merges.
|
/// 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
|
/// List of patch revisions. The initial changeset is part of the
|
||||||
/// first revision.
|
/// first revision.
|
||||||
|
///
|
||||||
|
/// Revisions can be redacted, but are otherwise immutable.
|
||||||
revisions: GMap<RevisionId, Redactable<Revision>>,
|
revisions: GMap<RevisionId, Redactable<Revision>>,
|
||||||
/// Users assigned to review this patch.
|
/// Users assigned to review this patch.
|
||||||
reviewers: LWWSet<ActorId>,
|
reviewers: LWWSet<ActorId>,
|
||||||
|
|
@ -304,9 +316,7 @@ impl Patch {
|
||||||
|
|
||||||
/// Get the merges.
|
/// Get the merges.
|
||||||
pub fn merges(&self) -> impl Iterator<Item = (&ActorId, &Merge)> {
|
pub fn merges(&self) -> impl Iterator<Item = (&ActorId, &Merge)> {
|
||||||
self.merges
|
self.merges.iter().map(|(a, m)| (a, m.deref()))
|
||||||
.iter()
|
|
||||||
.filter_map(|(a, m)| m.get().map(|m| (a, m)))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Reference to the Git object containing the code on the latest revision.
|
/// Reference to the Git object containing the code on the latest revision.
|
||||||
|
|
@ -603,7 +613,7 @@ impl store::FromHistory for Patch {
|
||||||
}
|
}
|
||||||
self.merges.insert(
|
self.merges.insert(
|
||||||
op.author,
|
op.author,
|
||||||
Redactable::Present(Merge {
|
Immutable::new(Merge {
|
||||||
revision,
|
revision,
|
||||||
commit,
|
commit,
|
||||||
timestamp,
|
timestamp,
|
||||||
|
|
@ -614,9 +624,7 @@ impl store::FromHistory for Patch {
|
||||||
let mut merges = self.merges.iter().fold(
|
let mut merges = self.merges.iter().fold(
|
||||||
HashMap::<(RevisionId, git::Oid), usize>::new(),
|
HashMap::<(RevisionId, git::Oid), usize>::new(),
|
||||||
|mut acc, (_, merge)| {
|
|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
|
acc
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
@ -1799,7 +1807,7 @@ mod test {
|
||||||
|
|
||||||
let (merger, merge) = merges.first().unwrap();
|
let (merger, merge) = merges.first().unwrap();
|
||||||
assert_eq!(*merger, signer.public_key());
|
assert_eq!(*merger, signer.public_key());
|
||||||
assert_eq!(merge.get().unwrap().commit, pr.base);
|
assert_eq!(merge.commit, pr.base);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue