cob: Add ability to add more parents to change
This allows change commits to have more than one parent that isn't part of the DAG. We can use this to create dependencies towards git objects such as commits, in the case of patch actions.
This commit is contained in:
parent
bda5d6f080
commit
489370dcbd
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
>,
|
||||
{
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
||||
|
|
|
|||
|
|
@ -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(),
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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()),
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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 }
|
||||
|
|
|
|||
|
|
@ -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")]
|
||||
|
|
|
|||
|
|
@ -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());
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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> {
|
||||
|
|
|
|||
Loading…
Reference in New Issue