cob: Rename `Change` to `Op`

Helps with the confusion with `radicle_cob::Change` and matches the
name for this object in common literature.

Signed-off-by: Alexis Sellier <alexis@radicle.xyz>
This commit is contained in:
Alexis Sellier 2022-12-01 20:29:02 +01:00
parent fa3c155865
commit cc24699232
No known key found for this signature in database
5 changed files with 132 additions and 137 deletions

View File

@ -1,16 +1,16 @@
pub mod change;
pub mod common; pub mod common;
pub mod issue; pub mod issue;
pub mod op;
pub mod patch; pub mod patch;
pub mod store; pub mod store;
pub mod thread; pub mod thread;
pub use change::{Actor, ActorId, Change, ChangeId};
pub use cob::{create, get, list, remove, update}; pub use cob::{create, get, list, remove, update};
pub use cob::{ pub use cob::{
identity, object::collaboration::error, CollaborativeObject, Contents, Create, Entry, History, identity, object::collaboration::error, CollaborativeObject, Contents, Create, Entry, History,
ObjectId, TypeName, Update, ObjectId, TypeName, Update,
}; };
pub use common::*; pub use common::*;
pub use op::{Actor, ActorId, Op, OpId};
use radicle_cob as cob; use radicle_cob as cob;

View File

@ -11,12 +11,12 @@ use crate::cob;
use crate::cob::common::{Author, Reaction, Tag}; use crate::cob::common::{Author, Reaction, Tag};
use crate::cob::thread; use crate::cob::thread;
use crate::cob::thread::{CommentId, Thread}; use crate::cob::thread::{CommentId, Thread};
use crate::cob::{store, ChangeId, ObjectId, TypeName}; use crate::cob::{store, ObjectId, OpId, TypeName};
use crate::crypto::{PublicKey, Signer}; use crate::crypto::{PublicKey, Signer};
use crate::storage::git as storage; use crate::storage::git as storage;
/// Issue change. /// Issue operation.
pub type Change = crate::cob::Change<Action>; pub type Op = crate::cob::Op<Action>;
/// Type name of an issue. /// Type name of an issue.
pub static TYPENAME: Lazy<TypeName> = pub static TYPENAME: Lazy<TypeName> =
@ -111,9 +111,9 @@ impl store::FromHistory for Issue {
history: &radicle_cob::History, history: &radicle_cob::History,
) -> Result<(Self, clock::Lamport), store::Error> { ) -> Result<(Self, clock::Lamport), store::Error> {
let obj = history.traverse(Self::default(), |mut acc, entry| { let obj = history.traverse(Self::default(), |mut acc, entry| {
if let Ok(change) = Change::try_from(entry) { if let Ok(op) = Op::try_from(entry) {
if let Err(err) = acc.apply(change) { if let Err(err) = acc.apply(op) {
log::warn!("Error applying change to issue state: {err}"); log::warn!("Error applying op to issue state: {err}");
return ControlFlow::Break(acc); return ControlFlow::Break(acc);
} }
} else { } else {
@ -154,28 +154,28 @@ impl Issue {
self.thread.comments().map(|(id, comment)| (id, comment)) self.thread.comments().map(|(id, comment)| (id, comment))
} }
pub fn apply(&mut self, change: Change) -> Result<(), Error> { pub fn apply(&mut self, op: Op) -> Result<(), Error> {
match change.action { match op.action {
Action::Title { title } => { Action::Title { title } => {
self.title.set(title, change.clock); self.title.set(title, op.clock);
} }
Action::Lifecycle { status } => { Action::Lifecycle { status } => {
self.status.set(status, change.clock); self.status.set(status, op.clock);
} }
Action::Tag { add, remove } => { Action::Tag { add, remove } => {
for tag in add { for tag in add {
self.tags.insert(tag, change.clock); self.tags.insert(tag, op.clock);
} }
for tag in remove { for tag in remove {
self.tags.remove(tag, change.clock); self.tags.remove(tag, op.clock);
} }
} }
Action::Thread { action } => { Action::Thread { action } => {
self.thread.apply([cob::Change { self.thread.apply([cob::Op {
action, action,
author: change.author, author: op.author,
clock: change.clock, clock: op.clock,
timestamp: change.timestamp, timestamp: op.timestamp,
}]); }]);
} }
} }
@ -205,7 +205,7 @@ impl<'a, 'g> IssueMut<'a, 'g> {
} }
/// Lifecycle an issue. /// Lifecycle an issue.
pub fn lifecycle<G: Signer>(&mut self, status: Status, signer: &G) -> Result<ChangeId, Error> { pub fn lifecycle<G: Signer>(&mut self, status: Status, signer: &G) -> Result<OpId, Error> {
let action = Action::Lifecycle { status }; let action = Action::Lifecycle { status };
self.apply("Lifecycle", action, signer) self.apply("Lifecycle", action, signer)
} }
@ -230,7 +230,7 @@ impl<'a, 'g> IssueMut<'a, 'g> {
add: impl IntoIterator<Item = Tag>, add: impl IntoIterator<Item = Tag>,
remove: impl IntoIterator<Item = Tag>, remove: impl IntoIterator<Item = Tag>,
signer: &G, signer: &G,
) -> Result<ChangeId, Error> { ) -> Result<OpId, Error> {
let add = add.into_iter().collect::<Vec<_>>(); let add = add.into_iter().collect::<Vec<_>>();
let remove = remove.into_iter().collect::<Vec<_>>(); let remove = remove.into_iter().collect::<Vec<_>>();
let action = Action::Tag { add, remove }; let action = Action::Tag { add, remove };
@ -244,7 +244,7 @@ impl<'a, 'g> IssueMut<'a, 'g> {
parent: CommentId, parent: CommentId,
body: S, body: S,
signer: &G, signer: &G,
) -> Result<ChangeId, Error> { ) -> Result<OpId, Error> {
let body = body.into(); let body = body.into();
assert!(self.thread.comment(&parent).is_some()); assert!(self.thread.comment(&parent).is_some());
@ -262,7 +262,7 @@ impl<'a, 'g> IssueMut<'a, 'g> {
to: CommentId, to: CommentId,
reaction: Reaction, reaction: Reaction,
signer: &G, signer: &G,
) -> Result<ChangeId, Error> { ) -> Result<OpId, Error> {
let action = Action::Thread { let action = Action::Thread {
action: thread::Action::React { action: thread::Action::React {
to, to,
@ -273,26 +273,26 @@ impl<'a, 'g> IssueMut<'a, 'g> {
self.apply("React", action, signer) self.apply("React", action, signer)
} }
/// Apply a change to the issue. /// Apply an op to the issue.
pub fn apply<G: Signer>( pub fn apply<G: Signer>(
&mut self, &mut self,
msg: &'static str, msg: &'static str,
action: Action, action: Action,
signer: &G, signer: &G,
) -> Result<ChangeId, Error> { ) -> Result<OpId, Error> {
let cob = self let cob = self
.store .store
.update(self.id, msg, action.clone(), signer) .update(self.id, msg, action.clone(), signer)
.map_err(Error::Store)?; .map_err(Error::Store)?;
let clock = cob.history().clock().into(); let clock = cob.history().clock().into();
let timestamp = cob.history().timestamp().into(); let timestamp = cob.history().timestamp().into();
let change = Change { let op = Op {
action, action,
author: *signer.public_key(), author: *signer.public_key(),
clock, clock,
timestamp, timestamp,
}; };
self.issue.apply(change)?; self.issue.apply(op)?;
Ok((clock, *signer.public_key())) Ok((clock, *signer.public_key()))
} }

View File

@ -8,40 +8,41 @@ use radicle_crdt::clock;
use radicle_crdt::clock::Lamport; use radicle_crdt::clock::Lamport;
use radicle_crypto::{PublicKey, Signer}; use radicle_crypto::{PublicKey, Signer};
/// Identifies a change. /// Identifies an [`Op`].
pub type ChangeId = (Lamport, ActorId); pub type OpId = (Lamport, ActorId);
/// The author of a change. /// The author of an [`Op`].
pub type ActorId = PublicKey; pub type ActorId = PublicKey;
/// Error decoding a change from an entry. /// Error decoding an operation from an entry.
#[derive(Error, Debug)] #[derive(Error, Debug)]
pub enum ChangeDecodeError { pub enum OpDecodeError {
#[error("deserialization from json failed: {0}")] #[error("deserialization from json failed: {0}")]
Deserialize(#[from] serde_json::Error), Deserialize(#[from] serde_json::Error),
} }
/// The `Change` is the unit of replication. /// The `Op` is the operation that is applied onto a state to form a CRDT.
/// Everything that can be done in the system is represented by a `Change` object. ///
/// Changes are applied to an accumulator to yield a final state. /// Everything that can be done in the system is represented by an `Op`.
/// Operations are applied to an accumulator to yield a final state.
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct Change<A> { pub struct Op<A> {
/// The action carried out by this change. /// The action carried out by this operation.
pub action: A, pub action: A,
/// The author of the change. /// The author of the operation.
pub author: ActorId, pub author: ActorId,
/// Lamport clock. /// Lamport clock.
pub clock: Lamport, pub clock: Lamport,
/// Timestamp of this change. /// Timestamp of this operation.
pub timestamp: clock::Physical, pub timestamp: clock::Physical,
} }
impl<'a: 'de, 'de, A: serde::Deserialize<'de>> TryFrom<&'a EntryWithClock> for Change<A> { impl<'a: 'de, 'de, A: serde::Deserialize<'de>> TryFrom<&'a EntryWithClock> for Op<A> {
type Error = ChangeDecodeError; type Error = OpDecodeError;
fn try_from(entry: &'a EntryWithClock) -> Result<Self, Self::Error> { fn try_from(entry: &'a EntryWithClock) -> Result<Self, Self::Error> {
let action = serde_json::from_slice(entry.contents())?; let action = serde_json::from_slice(entry.contents())?;
Ok(Change { Ok(Op {
action, action,
author: *entry.actor(), author: *entry.actor(),
clock: entry.clock().into(), clock: entry.clock().into(),
@ -50,26 +51,20 @@ impl<'a: 'de, 'de, A: serde::Deserialize<'de>> TryFrom<&'a EntryWithClock> for C
} }
} }
impl<A> Change<A> { impl<A> Op<A> {
/// Get the change id. /// Get the op id.
pub fn id(&self) -> ChangeId { /// This uniquely identifies each operation in the CRDT.
pub fn id(&self) -> OpId {
(self.clock, self.author) (self.clock, self.author)
} }
} }
impl<'de, A: Deserialize<'de>> Change<A> { /// An object that can be used to create and sign operations.
/// Deserialize a change from a byte string.
pub fn decode(bytes: &'de [u8]) -> Result<Self, serde_json::Error> {
serde_json::from_slice(bytes)
}
}
/// An object that can be used to create and sign changes.
#[derive(Default)] #[derive(Default)]
pub struct Actor<G, A> { pub struct Actor<G, A> {
pub signer: G, pub signer: G,
pub clock: Lamport, pub clock: Lamport,
pub changes: BTreeMap<(Lamport, PublicKey), Change<A>>, pub ops: BTreeMap<(Lamport, PublicKey), Op<A>>,
} }
impl<G: Signer, A: Clone + Serialize> Actor<G, A> { impl<G: Signer, A: Clone + Serialize> Actor<G, A> {
@ -77,15 +72,15 @@ impl<G: Signer, A: Clone + Serialize> Actor<G, A> {
Self { Self {
signer, signer,
clock: Lamport::default(), clock: Lamport::default(),
changes: BTreeMap::default(), ops: BTreeMap::default(),
} }
} }
pub fn receive(&mut self, changes: impl IntoIterator<Item = Change<A>>) -> Lamport { pub fn receive(&mut self, ops: impl IntoIterator<Item = Op<A>>) -> Lamport {
for change in changes { for op in ops {
let clock = change.clock; let clock = op.clock;
self.changes.insert((clock, change.author), change); self.ops.insert((clock, op.author), op);
self.clock.merge(clock); self.clock.merge(clock);
} }
self.clock self.clock
@ -93,29 +88,29 @@ impl<G: Signer, A: Clone + Serialize> Actor<G, A> {
/// Reset actor state to initial state. /// Reset actor state to initial state.
pub fn reset(&mut self) { pub fn reset(&mut self) {
self.changes.clear(); self.ops.clear();
self.clock = Lamport::default(); self.clock = Lamport::default();
} }
/// Returned an ordered list of events. /// Returned an ordered list of events.
pub fn timeline(&self) -> impl Iterator<Item = &Change<A>> { pub fn timeline(&self) -> impl Iterator<Item = &Op<A>> {
self.changes.values() self.ops.values()
} }
/// Create a new change. /// Create a new operation.
pub fn change(&mut self, action: A) -> Change<A> { pub fn op(&mut self, action: A) -> Op<A> {
let author = *self.signer.public_key(); let author = *self.signer.public_key();
let clock = self.clock; let clock = self.clock;
let timestamp = clock::Physical::now(); let timestamp = clock::Physical::now();
let change = Change { let op = Op {
action, action,
author, author,
clock, clock,
timestamp, timestamp,
}; };
self.changes.insert((self.clock, author), change.clone()); self.ops.insert((self.clock, author), op.clone());
self.clock.tick(); self.clock.tick();
change op
} }
} }

View File

@ -16,27 +16,27 @@ use crate::cob::common::{Author, Tag, Timestamp};
use crate::cob::thread; use crate::cob::thread;
use crate::cob::thread::CommentId; use crate::cob::thread::CommentId;
use crate::cob::thread::Thread; use crate::cob::thread::Thread;
use crate::cob::{store, ActorId, ChangeId, ObjectId, TypeName}; use crate::cob::{store, ActorId, ObjectId, OpId, TypeName};
use crate::crypto::{PublicKey, Signer}; use crate::crypto::{PublicKey, Signer};
use crate::git; use crate::git;
use crate::prelude::*; use crate::prelude::*;
use crate::storage::git as storage; use crate::storage::git as storage;
/// The logical clock we use to order changes to patches. /// The logical clock we use to order operations to patches.
pub use clock::Lamport as Clock; pub use clock::Lamport as Clock;
/// Type name of a patch. /// Type name of a patch.
pub static TYPENAME: Lazy<TypeName> = pub static TYPENAME: Lazy<TypeName> =
Lazy::new(|| FromStr::from_str("xyz.radicle.patch").expect("type name is valid")); Lazy::new(|| FromStr::from_str("xyz.radicle.patch").expect("type name is valid"));
/// Patch change. /// Patch operation.
pub type Change = crate::cob::Change<Action>; pub type Op = crate::cob::Op<Action>;
/// Identifier for a patch. /// Identifier for a patch.
pub type PatchId = ObjectId; pub type PatchId = ObjectId;
/// Unique identifier for a patch revision. /// Unique identifier for a patch revision.
pub type RevisionId = ChangeId; pub type RevisionId = OpId;
/// Index of a revision in the revisions list. /// Index of a revision in the revisions list.
pub type RevisionIx = usize; pub type RevisionIx = usize;
@ -49,10 +49,10 @@ pub enum ApplyError {
/// This error indicates that the operations are not being applied /// This error indicates that the operations are not being applied
/// in causal order, which is a requirement for this CRDT. /// in causal order, which is a requirement for this CRDT.
/// ///
/// For example, this can occur if a change references another change /// For example, this can occur if an operation references anothern operation
/// that hasn't happened yet. /// that hasn't happened yet.
#[error("causal dependency {0:?} missing")] #[error("causal dependency {0:?} missing")]
Missing(ChangeId), Missing(OpId),
} }
/// Error updating or creating patches. /// Error updating or creating patches.
@ -221,36 +221,36 @@ impl Patch {
matches!(self.status.get().get(), &Status::Archived) matches!(self.status.get().get(), &Status::Archived)
} }
/// Apply a list of changes to the state. /// Apply a list of operations to the state.
pub fn apply(&mut self, changes: impl IntoIterator<Item = Change>) -> Result<(), ApplyError> { pub fn apply(&mut self, ops: impl IntoIterator<Item = Op>) -> Result<(), ApplyError> {
for change in changes { for op in ops {
self.apply_one(change)?; self.apply_one(op)?;
} }
Ok(()) Ok(())
} }
/// Apply a single change to the state. /// Apply a single op to the state.
pub fn apply_one(&mut self, change: Change) -> Result<(), ApplyError> { pub fn apply_one(&mut self, op: Op) -> Result<(), ApplyError> {
let id = change.id(); let id = op.id();
let author = Author::new(change.author); let author = Author::new(op.author);
let timestamp = change.timestamp; let timestamp = op.timestamp;
match change.action { match op.action {
Action::Edit { Action::Edit {
title, title,
description, description,
target, target,
} => { } => {
self.title.set(title, change.clock); self.title.set(title, op.clock);
self.description.set(description, change.clock); self.description.set(description, op.clock);
self.target.set(target, change.clock); self.target.set(target, op.clock);
} }
Action::Tag { add, remove } => { Action::Tag { add, remove } => {
for tag in add { for tag in add {
self.tags.insert(tag, change.clock); self.tags.insert(tag, op.clock);
} }
for tag in remove { for tag in remove {
self.tags.remove(tag, change.clock); self.tags.remove(tag, op.clock);
} }
} }
Action::Revision { base, oid } => { Action::Revision { base, oid } => {
@ -274,7 +274,7 @@ impl Patch {
} => { } => {
if let Some(Redactable::Present(revision)) = self.revisions.get_mut(&revision) { if let Some(Redactable::Present(revision)) = self.revisions.get_mut(&revision) {
revision.reviews.insert( revision.reviews.insert(
change.author, op.author,
Review::new(verdict, comment.to_owned(), inline.to_owned(), timestamp), Review::new(verdict, comment.to_owned(), inline.to_owned(), timestamp),
); );
} else { } else {
@ -285,12 +285,12 @@ impl Patch {
if let Some(Redactable::Present(revision)) = self.revisions.get_mut(&revision) { if let Some(Redactable::Present(revision)) = self.revisions.get_mut(&revision) {
revision.merges.insert( revision.merges.insert(
Merge { Merge {
node: change.author, node: op.author,
commit, commit,
timestamp, timestamp,
} }
.into(), .into(),
change.clock, op.clock,
); );
} else { } else {
return Err(ApplyError::Missing(revision)); return Err(ApplyError::Missing(revision));
@ -300,10 +300,10 @@ impl Patch {
// TODO(cloudhead): Make sure we can deal with redacted revisions which are added // TODO(cloudhead): Make sure we can deal with redacted revisions which are added
// to out of order, like in the `Merge` case. // to out of order, like in the `Merge` case.
if let Some(Redactable::Present(revision)) = self.revisions.get_mut(&revision) { if let Some(Redactable::Present(revision)) = self.revisions.get_mut(&revision) {
revision.discussion.apply([cob::Change { revision.discussion.apply([cob::Op {
action, action,
author: change.author, author: op.author,
clock: change.clock, clock: op.clock,
timestamp, timestamp,
}]); }]);
} else { } else {
@ -326,9 +326,9 @@ impl store::FromHistory for Patch {
history: &radicle_cob::History, history: &radicle_cob::History,
) -> Result<(Self, clock::Lamport), store::Error> { ) -> Result<(Self, clock::Lamport), store::Error> {
let obj = history.traverse(Self::default(), |mut acc, entry| { let obj = history.traverse(Self::default(), |mut acc, entry| {
if let Ok(change) = Change::try_from(entry) { if let Ok(op) = Op::try_from(entry) {
if let Err(err) = acc.apply([change]) { if let Err(err) = acc.apply([op]) {
log::warn!("Error applying change to patch state: {err}"); log::warn!("Error applying op to patch state: {err}");
return ControlFlow::Break(acc); return ControlFlow::Break(acc);
} }
} else { } else {
@ -554,7 +554,7 @@ impl<'a, 'g> PatchMut<'a, 'g> {
description: String, description: String,
target: MergeTarget, target: MergeTarget,
signer: &G, signer: &G,
) -> Result<ChangeId, Error> { ) -> Result<OpId, Error> {
let action = Action::Edit { let action = Action::Edit {
title, title,
description, description,
@ -589,7 +589,7 @@ impl<'a, 'g> PatchMut<'a, 'g> {
comment: Option<String>, comment: Option<String>,
inline: Vec<CodeComment>, inline: Vec<CodeComment>,
signer: &G, signer: &G,
) -> Result<ChangeId, Error> { ) -> Result<OpId, Error> {
let action = Action::Review { let action = Action::Review {
revision, revision,
comment, comment,
@ -605,7 +605,7 @@ impl<'a, 'g> PatchMut<'a, 'g> {
revision: RevisionId, revision: RevisionId,
commit: git::Oid, commit: git::Oid,
signer: &G, signer: &G,
) -> Result<ChangeId, Error> { ) -> Result<OpId, Error> {
let action = Action::Merge { revision, commit }; let action = Action::Merge { revision, commit };
self.apply("Merge revision", action, signer) self.apply("Merge revision", action, signer)
} }
@ -617,7 +617,7 @@ impl<'a, 'g> PatchMut<'a, 'g> {
base: impl Into<git::Oid>, base: impl Into<git::Oid>,
oid: impl Into<git::Oid>, oid: impl Into<git::Oid>,
signer: &G, signer: &G,
) -> Result<ChangeId, Error> { ) -> Result<OpId, Error> {
let description = description.into(); let description = description.into();
let base = base.into(); let base = base.into();
let oid = oid.into(); let oid = oid.into();
@ -637,7 +637,7 @@ impl<'a, 'g> PatchMut<'a, 'g> {
add: impl IntoIterator<Item = Tag>, add: impl IntoIterator<Item = Tag>,
remove: impl IntoIterator<Item = Tag>, remove: impl IntoIterator<Item = Tag>,
signer: &G, signer: &G,
) -> Result<ChangeId, Error> { ) -> Result<OpId, Error> {
let add = add.into_iter().collect::<Vec<_>>(); let add = add.into_iter().collect::<Vec<_>>();
let remove = remove.into_iter().collect::<Vec<_>>(); let remove = remove.into_iter().collect::<Vec<_>>();
let action = Action::Tag { add, remove }; let action = Action::Tag { add, remove };
@ -645,26 +645,26 @@ impl<'a, 'g> PatchMut<'a, 'g> {
self.apply("Tag", action, signer) self.apply("Tag", action, signer)
} }
/// Apply a change to the patch. /// Apply an operation to the patch.
pub fn apply<G: Signer>( pub fn apply<G: Signer>(
&mut self, &mut self,
msg: &'static str, msg: &'static str,
action: Action, action: Action,
signer: &G, signer: &G,
) -> Result<ChangeId, Error> { ) -> Result<OpId, Error> {
let cob = self let cob = self
.store .store
.update(self.id, msg, action.clone(), signer) .update(self.id, msg, action.clone(), signer)
.map_err(Error::Store)?; .map_err(Error::Store)?;
let clock = cob.history().clock().into(); let clock = cob.history().clock().into();
let timestamp = cob.history().timestamp().into(); let timestamp = cob.history().timestamp().into();
let change = Change { let op = Op {
action, action,
author: *signer.public_key(), author: *signer.public_key(),
clock, clock,
timestamp, timestamp,
}; };
self.patch.apply_one(change)?; self.patch.apply_one(op)?;
Ok((clock, *signer.public_key())) Ok((clock, *signer.public_key()))
} }
@ -781,13 +781,13 @@ mod test {
use quickcheck::{Arbitrary, TestResult}; use quickcheck::{Arbitrary, TestResult};
use super::*; use super::*;
use crate::cob::change::{Actor, ActorId}; use crate::cob::op::{Actor, ActorId};
use crate::crypto::test::signer::MockSigner; use crate::crypto::test::signer::MockSigner;
use crate::test; use crate::test;
#[derive(Clone)] #[derive(Clone)]
struct Changes<const N: usize> { struct Changes<const N: usize> {
permutations: [Vec<Change>; N], permutations: [Vec<Op>; N],
} }
impl<const N: usize> std::fmt::Debug for Changes<N> { impl<const N: usize> std::fmt::Debug for Changes<N> {
@ -805,7 +805,7 @@ mod test {
impl<const N: usize> Arbitrary for Changes<N> { impl<const N: usize> Arbitrary for Changes<N> {
fn arbitrary(g: &mut quickcheck::Gen) -> Self { fn arbitrary(g: &mut quickcheck::Gen) -> Self {
type State = (clock::Lamport, Vec<ChangeId>, Vec<Tag>); type State = (clock::Lamport, Vec<OpId>, Vec<Tag>);
let author = ActorId::from([0; 32]); let author = ActorId::from([0; 32]);
let rng = fastrand::Rng::with_seed(u64::arbitrary(g)); let rng = fastrand::Rng::with_seed(u64::arbitrary(g));
@ -875,11 +875,11 @@ mod test {
}); });
let mut changes = Vec::new(); let mut changes = Vec::new();
let mut permutations: [Vec<Change>; N] = array::from_fn(|_| Vec::new()); let mut permutations: [Vec<Op>; N] = array::from_fn(|_| Vec::new());
let timestamp = Timestamp::now() + rng.u64(..60); let timestamp = Timestamp::now() + rng.u64(..60);
for (clock, action) in gen.take(g.size()) { for (clock, action) in gen.take(g.size()) {
changes.push(Change { changes.push(Op {
action, action,
author, author,
clock, clock,
@ -1054,15 +1054,15 @@ mod test {
let mut alice = Actor::<_, Action>::new(MockSigner::default()); let mut alice = Actor::<_, Action>::new(MockSigner::default());
let mut patch = Patch::default(); let mut patch = Patch::default();
let a1 = alice.change(Action::Revision { base, oid }); let a1 = alice.op(Action::Revision { base, oid });
let a2 = alice.change(Action::Redact { revision: a1.id() }); let a2 = alice.op(Action::Redact { revision: a1.id() });
let a3 = alice.change(Action::Review { let a3 = alice.op(Action::Review {
revision: a1.id(), revision: a1.id(),
comment: None, comment: None,
verdict: Some(Verdict::Accept), verdict: Some(Verdict::Accept),
inline: vec![], inline: vec![],
}); });
let a4 = alice.change(Action::Merge { let a4 = alice.op(Action::Merge {
revision: a1.id(), revision: a1.id(),
commit: oid, commit: oid,
}); });
@ -1085,8 +1085,8 @@ mod test {
let mut p1 = Patch::default(); let mut p1 = Patch::default();
let mut p2 = Patch::default(); let mut p2 = Patch::default();
let a1 = alice.change(Action::Revision { base, oid }); let a1 = alice.op(Action::Revision { base, oid });
let a2 = alice.change(Action::Redact { revision: a1.id() }); let a2 = alice.op(Action::Redact { revision: a1.id() });
p1.apply([a1.clone(), a2.clone(), a1.clone()]).unwrap(); p1.apply([a1.clone(), a2.clone(), a1.clone()]).unwrap();
p2.apply([a1.clone(), a1, a2]).unwrap(); p2.apply([a1.clone(), a1, a2]).unwrap();

View File

@ -11,7 +11,7 @@ use serde::{Deserialize, Serialize};
use crate::cob; use crate::cob;
use crate::cob::common::{Reaction, Timestamp}; use crate::cob::common::{Reaction, Timestamp};
use crate::cob::store; use crate::cob::store;
use crate::cob::{ActorId, Change, ChangeId, History, TypeName}; use crate::cob::{ActorId, History, Op, OpId, TypeName};
use crate::crypto::Signer; use crate::crypto::Signer;
use crdt::clock::Lamport; use crdt::clock::Lamport;
@ -24,7 +24,7 @@ pub static TYPENAME: Lazy<TypeName> =
Lazy::new(|| FromStr::from_str("xyz.radicle.thread").expect("type name is valid")); Lazy::new(|| FromStr::from_str("xyz.radicle.thread").expect("type name is valid"));
/// Identifies a comment. /// Identifies a comment.
pub type CommentId = ChangeId; pub type CommentId = OpId;
/// A comment on a discussion thread. /// A comment on a discussion thread.
#[derive(Default, Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] #[derive(Default, Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
@ -32,14 +32,14 @@ pub struct Comment {
/// The comment body. /// The comment body.
pub body: String, pub body: String,
/// Thread or comment this is a reply to. /// Thread or comment this is a reply to.
pub reply_to: Option<ChangeId>, pub reply_to: Option<OpId>,
/// When the comment was authored. /// When the comment was authored.
pub timestamp: Timestamp, pub timestamp: Timestamp,
} }
impl Comment { impl Comment {
/// Create a new comment. /// Create a new comment.
pub fn new(body: String, reply_to: Option<ChangeId>, timestamp: Timestamp) -> Self { pub fn new(body: String, reply_to: Option<OpId>, timestamp: Timestamp) -> Self {
Self { Self {
body, body,
reply_to, reply_to,
@ -66,13 +66,13 @@ pub enum Action {
/// Comment body. /// Comment body.
body: String, body: String,
/// Another comment this is a reply to. /// Another comment this is a reply to.
reply_to: Option<ChangeId>, reply_to: Option<OpId>,
}, },
/// Redact a change. Not all changes can be redacted. /// Redact a change. Not all changes can be redacted.
Redact { id: ChangeId }, Redact { id: OpId },
/// React to a change. /// React to a change.
React { React {
to: ChangeId, to: OpId,
reaction: Reaction, reaction: Reaction,
active: bool, active: bool,
}, },
@ -96,7 +96,7 @@ impl store::FromHistory for Thread {
fn from_history(history: &History) -> Result<(Self, Lamport), store::Error> { fn from_history(history: &History) -> Result<(Self, Lamport), store::Error> {
let obj = history.traverse(Thread::default(), |mut acc, entry| { let obj = history.traverse(Thread::default(), |mut acc, entry| {
if let Ok(change) = Change::try_from(entry) { if let Ok(change) = Op::try_from(entry) {
acc.apply([change]); acc.apply([change]);
ControlFlow::Continue(acc) ControlFlow::Continue(acc)
} else { } else {
@ -168,7 +168,7 @@ impl Thread {
.map(|(a, r)| (a, r)) .map(|(a, r)| (a, r))
} }
pub fn apply(&mut self, changes: impl IntoIterator<Item = Change<Action>>) { pub fn apply(&mut self, changes: impl IntoIterator<Item = Op<Action>>) {
for change in changes.into_iter() { for change in changes.into_iter() {
let id = change.id(); let id = change.id();
@ -257,16 +257,16 @@ impl<G: Signer> Actor<G> {
} }
/// Create a new comment. /// Create a new comment.
pub fn comment(&mut self, body: &str, reply_to: Option<ChangeId>) -> Change<Action> { pub fn comment(&mut self, body: &str, reply_to: Option<OpId>) -> Op<Action> {
self.change(Action::Comment { self.op(Action::Comment {
body: String::from(body), body: String::from(body),
reply_to, reply_to,
}) })
} }
/// Create a new redaction. /// Create a new redaction.
pub fn redact(&mut self, id: ChangeId) -> Change<Action> { pub fn redact(&mut self, id: OpId) -> Op<Action> {
self.change(Action::Redact { id }) self.op(Action::Redact { id })
} }
} }
@ -299,7 +299,7 @@ mod tests {
#[derive(Clone)] #[derive(Clone)]
struct Changes<const N: usize> { struct Changes<const N: usize> {
permutations: [Vec<Change<Action>>; N], permutations: [Vec<Op<Action>>; N],
} }
impl<const N: usize> std::fmt::Debug for Changes<N> { impl<const N: usize> std::fmt::Debug for Changes<N> {
@ -320,7 +320,7 @@ mod tests {
let author = ActorId::from([0; 32]); let author = ActorId::from([0; 32]);
let rng = fastrand::Rng::with_seed(u64::arbitrary(g)); let rng = fastrand::Rng::with_seed(u64::arbitrary(g));
let gen = let gen =
WeightedGenerator::<(Lamport, Action), (Lamport, Vec<ChangeId>)>::new(rng.clone()) WeightedGenerator::<(Lamport, Action), (Lamport, Vec<OpId>)>::new(rng.clone())
.variant(3, |(clock, changes), rng| { .variant(3, |(clock, changes), rng| {
changes.push((clock.tick(), author)); changes.push((clock.tick(), author));
@ -357,11 +357,11 @@ mod tests {
}); });
let mut changes = Vec::new(); let mut changes = Vec::new();
let mut permutations: [Vec<Change<Action>>; N] = array::from_fn(|_| Vec::new()); let mut permutations: [Vec<Op<Action>>; N] = array::from_fn(|_| Vec::new());
let timestamp = Timestamp::now() + rng.u64(..60); let timestamp = Timestamp::now() + rng.u64(..60);
for (clock, action) in gen.take(g.size().min(8)) { for (clock, action) in gen.take(g.size().min(8)) {
changes.push(Change { changes.push(Op {
action, action,
author, author,
clock, clock,
@ -493,9 +493,9 @@ mod tests {
eve.receive([a2.clone()]); eve.receive([a2.clone()]);
bob.receive([a2.clone()]); bob.receive([a2.clone()]);
assert_eq!(alice.changes.len(), 7); assert_eq!(alice.ops.len(), 7);
assert_eq!(bob.changes.len(), 7); assert_eq!(bob.ops.len(), 7);
assert_eq!(eve.changes.len(), 7); assert_eq!(eve.ops.len(), 7);
assert_eq!( assert_eq!(
bob.timeline().collect::<Vec<_>>(), bob.timeline().collect::<Vec<_>>(),