Radish alpha
h
Radicle Heartwood Protocol & Stack
Radicle
Git (anonymous pull)
Log in to clone via SSH
cob: Add ability to add more parents to change
Alexis Sellier committed 3 years ago
commit 489370dcbd5aa7d8d8b60fe53bb6274f66222378
parent bda5d6f0803e1c78c78d84460ce899f28a7b2176
14 files changed +67 -30
modified radicle-cob/src/backend/git/change.rs
@@ -86,12 +86,13 @@ impl change::Storage for git2::Repository {
    type LoadError = error::Load;

    type ObjectId = Oid;
-
    type Resource = Oid;
+
    type Parent = Oid;
    type Signatures = ExtendedSignature;

    fn store<Signer>(
        &self,
-
        resource: Self::Resource,
+
        resource: Self::Parent,
+
        parents: Vec<Self::Parent>,
        signer: &Signer,
        spec: store::Template<Self::ObjectId>,
    ) -> Result<Change, Self::StoreError>
@@ -124,6 +125,7 @@ impl change::Storage for git2::Repository {
            revision: revision.into(),
            signature,
            resource,
+
            parents,
            manifest,
            contents,
            timestamp,
@@ -134,6 +136,11 @@ impl change::Storage for git2::Repository {
        let commit = Commit::read(self, id.into())?;
        let timestamp = git2::Time::from(commit.committer().time).seconds() as u64;
        let resource = parse_resource_trailer(commit.trailers())?;
+
        let parents = commit
+
            .parents()
+
            .map(Oid::from)
+
            .filter(|p| *p != resource)
+
            .collect();
        let mut signatures = Signatures::try_from(&commit)?
            .into_iter()
            .collect::<Vec<_>>();
@@ -153,6 +160,7 @@ impl change::Storage for git2::Repository {
            revision: tree.id().into(),
            signature: ExtendedSignature::new(key, sig),
            resource,
+
            parents,
            manifest,
            contents,
            timestamp,
modified radicle-cob/src/change/store.rs
@@ -19,17 +19,18 @@ pub trait Storage {
    type LoadError: Error + Send + Sync + 'static;

    type ObjectId;
-
    type Resource;
+
    type Parent;
    type Signatures;

    /// Store a new change.
    #[allow(clippy::type_complexity)]
    fn store<G>(
        &self,
-
        authority: Self::Resource,
+
        resource: Self::Parent,
+
        parents: Vec<Self::Parent>,
        signer: &G,
        template: Template<Self::ObjectId>,
-
    ) -> Result<Change<Self::Resource, Self::ObjectId, Self::Signatures>, Self::StoreError>
+
    ) -> Result<Change<Self::Parent, Self::ObjectId, Self::Signatures>, Self::StoreError>
    where
        G: crypto::Signer;

@@ -38,7 +39,7 @@ pub trait Storage {
    fn load(
        &self,
        id: Self::ObjectId,
-
    ) -> Result<Change<Self::Resource, Self::ObjectId, Self::Signatures>, Self::LoadError>;
+
    ) -> Result<Change<Self::Parent, Self::ObjectId, Self::Signatures>, Self::LoadError>;
}

/// Change template, used to create a new change.
@@ -62,6 +63,8 @@ pub struct Change<Resource, Id, Signature> {
    /// The parent resource that this change lives under. For example,
    /// this change could be for a patch of a project.
    pub resource: Resource,
+
    /// Other parents this change depends on.
+
    pub parents: Vec<Resource>,
    /// The manifest describing the type of object as well as the type
    /// of history for this `Change`.
    pub manifest: Manifest,
modified radicle-cob/src/change_graph.rs
@@ -31,7 +31,7 @@ impl ChangeGraph {
        oid: &ObjectId,
    ) -> Option<ChangeGraph>
    where
-
        S: change::Storage<ObjectId = Oid, Resource = Oid, Signatures = ExtendedSignature>,
+
        S: change::Storage<ObjectId = Oid, Parent = Oid, Signatures = ExtendedSignature>,
    {
        log::info!("loading object '{}' '{}'", typename, oid);
        let mut builder = GraphBuilder::default();
modified radicle-cob/src/lib.rs
@@ -128,7 +128,7 @@ where
            StoreError = git::change::error::Create,
            LoadError = git::change::error::Load,
            ObjectId = git_ext::Oid,
-
            Resource = git_ext::Oid,
+
            Parent = git_ext::Oid,
            Signatures = ExtendedSignature,
        >,
{
modified radicle-cob/src/object/collaboration/create.rs
@@ -64,7 +64,7 @@ where
{
    let Create { ref typename, .. } = &args;
    let init_change = storage
-
        .store(resource, signer, args.template())
+
        .store(resource, vec![], signer, args.template())
        .map_err(error::Create::from)?;
    let object_id = init_change.id().into();

modified radicle-cob/src/object/collaboration/update.rs
@@ -42,9 +42,10 @@ pub struct Update {
/// The `signer` is expected to be a cryptographic signing key. This
/// ensures that the objects origin is cryptographically verifiable.
///
-
/// The `resource` is the parent of this object, for example a
-
/// software project. Its content-address is stored in the
-
/// object's history.
+
/// The `resource` is the resource this change lives under, eg. a project.
+
///
+
/// The `parents` are other the parents of this object, for example a
+
/// code commit.
///
/// The `identifier` is a unqiue id that is passed through to the
/// [`crate::object::Storage`].
@@ -55,6 +56,7 @@ pub fn update<S, G>(
    storage: &S,
    signer: &G,
    resource: Oid,
+
    parents: Vec<Oid>,
    identifier: &S::Identifier,
    args: Update,
) -> Result<Updated, error::Update>
@@ -80,6 +82,7 @@ where

    let change = storage.store(
        resource,
+
        parents,
        signer,
        change::Template {
            tips: object.tips().iter().cloned().collect(),
modified radicle-cob/src/test/storage.rs
@@ -63,29 +63,30 @@ impl change::Storage for Storage {
    type LoadError = <git2::Repository as change::Storage>::LoadError;

    type ObjectId = <git2::Repository as change::Storage>::ObjectId;
-
    type Resource = <git2::Repository as change::Storage>::Resource;
+
    type Parent = <git2::Repository as change::Storage>::Parent;
    type Signatures = <git2::Repository as change::Storage>::Signatures;

    fn store<Signer>(
        &self,
-
        authority: Self::Resource,
+
        authority: Self::Parent,
+
        parents: Vec<Self::Parent>,
        signer: &Signer,
        spec: change::Template<Self::ObjectId>,
    ) -> Result<
-
        change::store::Change<Self::Resource, Self::ObjectId, Self::Signatures>,
+
        change::store::Change<Self::Parent, Self::ObjectId, Self::Signatures>,
        Self::StoreError,
    >
    where
        Signer: crypto::Signer,
    {
-
        self.as_raw().store(authority, signer, spec)
+
        self.as_raw().store(authority, parents, signer, spec)
    }

    fn load(
        &self,
        id: Self::ObjectId,
    ) -> Result<
-
        change::store::Change<Self::Resource, Self::ObjectId, Self::Signatures>,
+
        change::store::Change<Self::Parent, Self::ObjectId, Self::Signatures>,
        Self::LoadError,
    > {
        self.as_raw().load(id)
modified radicle-cob/src/tests.rs
@@ -126,6 +126,7 @@ fn update_cob() {
        &storage,
        &signer,
        proj.project.content_id,
+
        vec![],
        &proj.identifier(),
        Update {
            changes: nonempty!(b"issue 1".to_vec()),
@@ -188,6 +189,7 @@ fn traverse_cobs() {
        &storage,
        &neil_signer,
        neil_proj.project.content_id,
+
        vec![],
        &neil_proj.identifier(),
        Update {
            changes: nonempty!(b"issue 2".to_vec()),
modified radicle/src/cob/identity.rs
@@ -13,7 +13,7 @@ use crate::{
    cob::{
        self,
        common::Timestamp,
-
        store::{self, FromHistory as _, Transaction},
+
        store::{self, FromHistory as _, HistoryAction, Transaction},
    },
    identity::{doc::DocError, Did, Identity, IdentityError},
    prelude::{Doc, ReadRepository},
@@ -68,6 +68,8 @@ pub enum Action {
    },
}

+
impl HistoryAction for Action {}
+

/// Error applying an operation onto a state.
#[derive(Error, Debug)]
pub enum ApplyError {
modified radicle/src/cob/issue.rs
@@ -10,8 +10,8 @@ use radicle_crdt::{LWWReg, LWWSet, Max, Semilattice};

use crate::cob;
use crate::cob::common::{Author, Reaction, Tag, Timestamp};
-
use crate::cob::store::FromHistory as _;
use crate::cob::store::Transaction;
+
use crate::cob::store::{FromHistory as _, HistoryAction};
use crate::cob::thread;
use crate::cob::thread::{CommentId, Thread};
use crate::cob::{store, ActorId, EntryId, ObjectId, TypeName};
@@ -534,6 +534,8 @@ pub enum Action {
    },
}

+
impl HistoryAction for Action {}
+

impl From<thread::Action> for Action {
    fn from(action: thread::Action) -> Self {
        Self::Thread { action }
modified radicle/src/cob/patch.rs
@@ -15,8 +15,8 @@ use radicle_crdt::{GMap, GSet, LWWReg, LWWSet, Lamport, Max, Redactable, Semilat

use crate::cob;
use crate::cob::common::{Author, Tag, Timestamp};
-
use crate::cob::store::FromHistory as _;
use crate::cob::store::Transaction;
+
use crate::cob::store::{FromHistory as _, HistoryAction};
use crate::cob::thread;
use crate::cob::thread::CommentId;
use crate::cob::thread::Thread;
@@ -119,6 +119,8 @@ pub enum Action {
    },
}

+
impl HistoryAction for Action {}
+

/// Where a patch is intended to be merged.
#[derive(Default, Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
modified radicle/src/cob/store.rs
@@ -18,11 +18,19 @@ use crate::{cob, identity};
/// History type for standard radicle COBs.
pub const HISTORY_TYPE: &str = "radicle";

+
pub trait HistoryAction {
+
    /// Parent objects this action depends on. For example, patch revisions
+
    /// have the commit objects as their parent.
+
    fn parents(&self) -> Vec<git::Oid> {
+
        Vec::new()
+
    }
+
}
+

/// A type that can be materialized from an event history.
/// All collaborative objects implement this trait.
pub trait FromHistory: Sized + Default {
    /// The underlying action composing each operation.
-
    type Action: for<'de> Deserialize<'de> + Serialize;
+
    type Action: HistoryAction + for<'de> Deserialize<'de> + Serialize;
    /// Error returned by `apply` function.
    type Error: std::error::Error;

@@ -101,7 +109,7 @@ pub enum Error {

/// Storage for collaborative objects of a specific type `T` in a single repository.
pub struct Store<'a, T> {
-
    parent: git::Oid,
+
    identity: git::Oid,
    repo: &'a storage::Repository,
    witness: PhantomData<T>,
}
@@ -119,7 +127,7 @@ impl<'a, T> Store<'a, T> {

        Ok(Self {
            repo,
-
            parent: identity.head,
+
            identity: identity.head,
            witness: PhantomData,
        })
    }
@@ -137,11 +145,14 @@ where
        actions: impl Into<NonEmpty<T::Action>>,
        signer: &G,
    ) -> Result<Updated, Error> {
-
        let changes = actions.into().try_map(encoding::encode)?;
+
        let actions = actions.into();
+
        let parents = actions.iter().flat_map(T::Action::parents).collect();
+
        let changes = actions.try_map(encoding::encode)?;
        let updated = cob::update(
            self.repo,
            signer,
-
            self.parent,
+
            self.identity,
+
            parents,
            signer.public_key(),
            Update {
                object_id,
@@ -168,7 +179,7 @@ where
        let cob = cob::create(
            self.repo,
            signer,
-
            self.parent,
+
            self.identity,
            signer.public_key(),
            Create {
                history_type: HISTORY_TYPE.to_owned(),
@@ -304,7 +315,7 @@ impl<T: FromHistory> Transaction<T> {
        let author = self.actor;
        let timestamp = object.history().timestamp().into();
        let clock = self.clock.tick();
-
        let identity = store.parent;
+
        let identity = store.identity;

        // The history clock should be in sync with the tx clock.
        assert_eq!(object.history().clock(), self.clock.get());
modified radicle/src/cob/thread.rs
@@ -153,6 +153,8 @@ pub enum Action {
    },
}

+
impl cob::store::HistoryAction for Action {}
+

impl From<Action> for nonempty::NonEmpty<Action> {
    fn from(action: Action) -> Self {
        Self::new(action)
modified radicle/src/storage/git/cob.rs
@@ -41,19 +41,20 @@ impl change::Storage for Repository {
    type LoadError = <git2::Repository as change::Storage>::LoadError;

    type ObjectId = <git2::Repository as change::Storage>::ObjectId;
-
    type Resource = <git2::Repository as change::Storage>::Resource;
+
    type Parent = <git2::Repository as change::Storage>::Parent;
    type Signatures = <git2::Repository as change::Storage>::Signatures;

    fn store<Signer>(
        &self,
-
        authority: Self::Resource,
+
        authority: Self::Parent,
+
        parents: Vec<Self::Parent>,
        signer: &Signer,
        spec: change::Template<Self::ObjectId>,
    ) -> Result<cob::Change, Self::StoreError>
    where
        Signer: crypto::Signer,
    {
-
        self.backend.store(authority, signer, spec)
+
        self.backend.store(authority, parents, signer, spec)
    }

    fn load(&self, id: Self::ObjectId) -> Result<cob::Change, Self::LoadError> {