cob: Change APIs to take URIs for embeds
To facilitate edit actions, take URIs instead of the actual blobs. This means API callers don't have to load all the blobs just for them to be re-hashed when an edit action is submitted. There are some peculiarities when dealing with the `Identity` COB since the embed is the identity document itself. We handle that special case.
This commit is contained in:
parent
034eb41860
commit
855327d303
|
|
@ -1,6 +1,6 @@
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
use radicle::cob::{self, patch, resolve_embed};
|
use radicle::cob::{self, patch};
|
||||||
use radicle::crypto;
|
use radicle::crypto;
|
||||||
use radicle::prelude::*;
|
use radicle::prelude::*;
|
||||||
use radicle::storage::git::Repository;
|
use radicle::storage::git::Repository;
|
||||||
|
|
@ -19,12 +19,11 @@ pub fn run(
|
||||||
let Ok(patch) = patches.get_mut(patch_id) else {
|
let Ok(patch) = patches.get_mut(patch_id) else {
|
||||||
anyhow::bail!("Patch `{patch_id}` not found");
|
anyhow::bail!("Patch `{patch_id}` not found");
|
||||||
};
|
};
|
||||||
|
|
||||||
let (title, description) = term::patch::get_edit_message(message, &patch)?;
|
let (title, description) = term::patch::get_edit_message(message, &patch)?;
|
||||||
|
|
||||||
match revision_id {
|
match revision_id {
|
||||||
Some(id) => edit_revision(patch, id, title, description, repository, &signer),
|
Some(id) => edit_revision(patch, id, title, description, &signer),
|
||||||
None => edit_root(patch, title, description, repository, &signer),
|
None => edit_root(patch, title, description, &signer),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -32,7 +31,6 @@ fn edit_root<G>(
|
||||||
mut patch: patch::PatchMut<'_, '_, Repository, cob::cache::StoreWriter>,
|
mut patch: patch::PatchMut<'_, '_, Repository, cob::cache::StoreWriter>,
|
||||||
title: String,
|
title: String,
|
||||||
description: String,
|
description: String,
|
||||||
repository: &Repository,
|
|
||||||
signer: &G,
|
signer: &G,
|
||||||
) -> anyhow::Result<()>
|
) -> anyhow::Result<()>
|
||||||
where
|
where
|
||||||
|
|
@ -56,11 +54,7 @@ where
|
||||||
|
|
||||||
let (root, _) = patch.root();
|
let (root, _) = patch.root();
|
||||||
let target = patch.target();
|
let target = patch.target();
|
||||||
let embeds = patch
|
let embeds = patch.embeds().to_owned();
|
||||||
.embeds()
|
|
||||||
.iter()
|
|
||||||
.filter_map(|embed| resolve_embed(repository, embed.clone()))
|
|
||||||
.collect::<Vec<_>>();
|
|
||||||
|
|
||||||
patch.transaction("Edit root", signer, |tx| {
|
patch.transaction("Edit root", signer, |tx| {
|
||||||
if let Some(t) = title {
|
if let Some(t) = title {
|
||||||
|
|
@ -80,17 +74,12 @@ fn edit_revision<G>(
|
||||||
revision: patch::RevisionId,
|
revision: patch::RevisionId,
|
||||||
mut title: String,
|
mut title: String,
|
||||||
description: String,
|
description: String,
|
||||||
repository: &Repository,
|
|
||||||
signer: &G,
|
signer: &G,
|
||||||
) -> anyhow::Result<()>
|
) -> anyhow::Result<()>
|
||||||
where
|
where
|
||||||
G: crypto::Signer,
|
G: crypto::Signer,
|
||||||
{
|
{
|
||||||
let embeds = patch
|
let embeds = patch.embeds().to_owned();
|
||||||
.embeds()
|
|
||||||
.iter()
|
|
||||||
.filter_map(|embed| resolve_embed(repository, embed.clone()))
|
|
||||||
.collect::<Vec<_>>();
|
|
||||||
let description = if description.is_empty() {
|
let description = if description.is_empty() {
|
||||||
title
|
title
|
||||||
} else {
|
} else {
|
||||||
|
|
|
||||||
|
|
@ -341,7 +341,7 @@ fn write_commit(
|
||||||
fn write_manifest(
|
fn write_manifest(
|
||||||
repo: &git2::Repository,
|
repo: &git2::Repository,
|
||||||
manifest: &store::Manifest,
|
manifest: &store::Manifest,
|
||||||
embeds: Vec<Embed>,
|
embeds: Vec<Embed<Oid>>,
|
||||||
contents: &NonEmpty<Vec<u8>>,
|
contents: &NonEmpty<Vec<u8>>,
|
||||||
) -> Result<git2::Oid, git2::Error> {
|
) -> Result<git2::Oid, git2::Error> {
|
||||||
let mut root = repo.treebuilder(None)?;
|
let mut root = repo.treebuilder(None)?;
|
||||||
|
|
@ -372,10 +372,10 @@ fn write_manifest(
|
||||||
let mut embeds_tree = repo.treebuilder(None)?;
|
let mut embeds_tree = repo.treebuilder(None)?;
|
||||||
|
|
||||||
for embed in embeds {
|
for embed in embeds {
|
||||||
let oid = repo.blob(&embed.content)?;
|
let oid = embed.content;
|
||||||
let path = PathBuf::from(embed.name);
|
let path = PathBuf::from(embed.name);
|
||||||
|
|
||||||
embeds_tree.insert(path, oid, git2::FileMode::Blob.into())?;
|
embeds_tree.insert(path, *oid, git2::FileMode::Blob.into())?;
|
||||||
}
|
}
|
||||||
let oid = embeds_tree.write()?;
|
let oid = embeds_tree.write()?;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -45,7 +45,7 @@ pub struct Template<Id> {
|
||||||
pub type_name: TypeName,
|
pub type_name: TypeName,
|
||||||
pub tips: Vec<Id>,
|
pub tips: Vec<Id>,
|
||||||
pub message: String,
|
pub message: String,
|
||||||
pub embeds: Vec<Embed>,
|
pub embeds: Vec<Embed<Oid>>,
|
||||||
pub contents: NonEmpty<Vec<u8>>,
|
pub contents: NonEmpty<Vec<u8>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -191,6 +191,22 @@ pub struct Embed<T = Vec<u8>> {
|
||||||
pub content: T,
|
pub content: T,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl<T: From<Oid>> Embed<T> {
|
||||||
|
/// Create a new embed.
|
||||||
|
pub fn store(
|
||||||
|
name: impl ToString,
|
||||||
|
content: &[u8],
|
||||||
|
repo: &git2::Repository,
|
||||||
|
) -> Result<Self, git2::Error> {
|
||||||
|
let oid = repo.blob(content)?;
|
||||||
|
|
||||||
|
Ok(Self {
|
||||||
|
name: name.to_string(),
|
||||||
|
content: T::from(oid.into()),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl Embed<Vec<u8>> {
|
impl Embed<Vec<u8>> {
|
||||||
/// Get the object id of the embedded content.
|
/// Get the object id of the embedded content.
|
||||||
pub fn oid(&self) -> Oid {
|
pub fn oid(&self) -> Oid {
|
||||||
|
|
|
||||||
|
|
@ -18,7 +18,7 @@ pub struct Create {
|
||||||
/// The message to add when creating this object.
|
/// The message to add when creating this object.
|
||||||
pub message: String,
|
pub message: String,
|
||||||
/// Embedded content.
|
/// Embedded content.
|
||||||
pub embeds: Vec<Embed>,
|
pub embeds: Vec<Embed<Oid>>,
|
||||||
/// COB version.
|
/// COB version.
|
||||||
pub version: Version,
|
pub version: Version,
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -34,7 +34,7 @@ pub struct Update {
|
||||||
/// The message to add when updating this object.
|
/// The message to add when updating this object.
|
||||||
pub message: String,
|
pub message: String,
|
||||||
/// Embedded files.
|
/// Embedded files.
|
||||||
pub embeds: Vec<Embed>,
|
pub embeds: Vec<Embed<Oid>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Update an existing [`CollaborativeObject`].
|
/// Update an existing [`CollaborativeObject`].
|
||||||
|
|
|
||||||
|
|
@ -8,10 +8,8 @@ use base64::prelude::{Engine, BASE64_STANDARD};
|
||||||
use localtime::LocalTime;
|
use localtime::LocalTime;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
use crate::cob::Embed;
|
|
||||||
use crate::git::Oid;
|
use crate::git::Oid;
|
||||||
use crate::prelude::{Did, PublicKey};
|
use crate::prelude::{Did, PublicKey};
|
||||||
use crate::storage::ReadRepository;
|
|
||||||
|
|
||||||
/// Timestamp used for COB operations.
|
/// Timestamp used for COB operations.
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
|
||||||
|
|
@ -348,24 +346,6 @@ impl TryFrom<&Uri> for DataUri {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Resolve an embed with a URI to one with actual data.
|
|
||||||
pub fn resolve_embed(repo: &impl ReadRepository, embed: Embed<Uri>) -> Option<Embed<Vec<u8>>> {
|
|
||||||
DataUri::try_from(&embed.content)
|
|
||||||
.ok()
|
|
||||||
.map(|content| Embed {
|
|
||||||
name: embed.name.clone(),
|
|
||||||
content: content.into(),
|
|
||||||
})
|
|
||||||
.or_else(|| {
|
|
||||||
Oid::try_from(&embed.content).ok().and_then(|oid| {
|
|
||||||
repo.blob(oid).ok().map(|blob| Embed {
|
|
||||||
name: embed.name,
|
|
||||||
content: blob.content().to_vec(),
|
|
||||||
})
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
/// The result of an authorization check on an COB action.
|
/// The result of an authorization check on an COB action.
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
pub enum Authorization {
|
pub enum Authorization {
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@ use std::{fmt, ops::Deref, str::FromStr};
|
||||||
|
|
||||||
use crypto::{PublicKey, Signature};
|
use crypto::{PublicKey, Signature};
|
||||||
use once_cell::sync::Lazy;
|
use once_cell::sync::Lazy;
|
||||||
use radicle_cob::{ObjectId, TypeName};
|
use radicle_cob::{Embed, ObjectId, TypeName};
|
||||||
use radicle_crypto::{Signer, Verified};
|
use radicle_crypto::{Signer, Verified};
|
||||||
use radicle_git_ext as git_ext;
|
use radicle_git_ext as git_ext;
|
||||||
use radicle_git_ext::Oid;
|
use radicle_git_ext::Oid;
|
||||||
|
|
@ -15,7 +15,7 @@ use crate::{
|
||||||
cob::{
|
cob::{
|
||||||
op, store,
|
op, store,
|
||||||
store::{Cob, CobAction, Transaction},
|
store::{Cob, CobAction, Transaction},
|
||||||
ActorId, Timestamp,
|
ActorId, Timestamp, Uri,
|
||||||
},
|
},
|
||||||
identity::{
|
identity::{
|
||||||
doc::{Doc, DocError, RepoId},
|
doc::{Doc, DocError, RepoId},
|
||||||
|
|
@ -187,10 +187,12 @@ impl Identity {
|
||||||
signer: &G,
|
signer: &G,
|
||||||
) -> Result<IdentityMut<'a, R>, cob::store::Error> {
|
) -> Result<IdentityMut<'a, R>, cob::store::Error> {
|
||||||
let mut store = cob::store::Store::open(store)?;
|
let mut store = cob::store::Store::open(store)?;
|
||||||
let (id, identity) =
|
let (id, identity) = Transaction::<Identity, _>::initial(
|
||||||
Transaction::<Identity, _>::initial("Initialize identity", &mut store, signer, |tx| {
|
"Initialize identity",
|
||||||
tx.revision("Initial revision", "", doc, None, signer)
|
&mut store,
|
||||||
})?;
|
signer,
|
||||||
|
|tx, repo| tx.revision("Initial revision", "", doc, None, repo, signer),
|
||||||
|
)?;
|
||||||
|
|
||||||
Ok(IdentityMut {
|
Ok(IdentityMut {
|
||||||
id,
|
id,
|
||||||
|
|
@ -825,22 +827,26 @@ impl<R: ReadRepository> store::Transaction<Identity, R> {
|
||||||
pub fn redact(&mut self, revision: RevisionId) -> Result<(), store::Error> {
|
pub fn redact(&mut self, revision: RevisionId) -> Result<(), store::Error> {
|
||||||
self.push(Action::RevisionRedact { revision })
|
self.push(Action::RevisionRedact { revision })
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<R: WriteRepository> store::Transaction<Identity, R> {
|
||||||
pub fn revision<G: Signer>(
|
pub fn revision<G: Signer>(
|
||||||
&mut self,
|
&mut self,
|
||||||
title: impl ToString,
|
title: impl ToString,
|
||||||
description: impl ToString,
|
description: impl ToString,
|
||||||
doc: &Doc<Verified>,
|
doc: &Doc<Verified>,
|
||||||
parent: Option<RevisionId>,
|
parent: Option<RevisionId>,
|
||||||
|
repo: &R,
|
||||||
signer: &G,
|
signer: &G,
|
||||||
) -> Result<(), store::Error> {
|
) -> Result<(), store::Error> {
|
||||||
let (blob, content, signature) = doc.sign(signer).map_err(store::Error::Identity)?;
|
let (blob, bytes, signature) = doc.sign(signer).map_err(store::Error::Identity)?;
|
||||||
|
// Store document blob in repository.
|
||||||
|
let embed =
|
||||||
|
Embed::<Uri>::store("radicle.json", &bytes, repo.raw()).map_err(store::Error::Git)?;
|
||||||
|
debug_assert_eq!(embed.content, Uri::from(blob)); // Make sure we pre-computed the correct OID for the blob.
|
||||||
|
|
||||||
// Identity document.
|
// Identity document.
|
||||||
self.embed([cob::Embed {
|
self.embed([embed])?;
|
||||||
name: String::from("radicle.json"),
|
|
||||||
content,
|
|
||||||
}])?;
|
|
||||||
|
|
||||||
// Revision metadata.
|
// Revision metadata.
|
||||||
self.push(Action::Revision {
|
self.push(Action::Revision {
|
||||||
|
|
@ -891,10 +897,10 @@ where
|
||||||
) -> Result<EntryId, Error>
|
) -> Result<EntryId, Error>
|
||||||
where
|
where
|
||||||
G: Signer,
|
G: Signer,
|
||||||
F: FnOnce(&mut Transaction<Identity, R>) -> Result<(), store::Error>,
|
F: FnOnce(&mut Transaction<Identity, R>, &R) -> Result<(), store::Error>,
|
||||||
{
|
{
|
||||||
let mut tx = Transaction::default();
|
let mut tx = Transaction::default();
|
||||||
operations(&mut tx)?;
|
operations(&mut tx, self.store.as_ref())?;
|
||||||
|
|
||||||
let (doc, commit) = tx.commit(message, self.id, &mut self.store, signer)?;
|
let (doc, commit) = tx.commit(message, self.id, &mut self.store, signer)?;
|
||||||
self.identity = doc;
|
self.identity = doc;
|
||||||
|
|
@ -912,8 +918,8 @@ where
|
||||||
signer: &G,
|
signer: &G,
|
||||||
) -> Result<RevisionId, Error> {
|
) -> Result<RevisionId, Error> {
|
||||||
let parent = self.current;
|
let parent = self.current;
|
||||||
let id = self.transaction("Propose revision", signer, |tx| {
|
let id = self.transaction("Propose revision", signer, |tx, repo| {
|
||||||
tx.revision(title, description, doc, Some(parent), signer)
|
tx.revision(title, description, doc, Some(parent), repo, signer)
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
Ok(id)
|
Ok(id)
|
||||||
|
|
@ -929,7 +935,7 @@ where
|
||||||
let revision = self.revision(revision).ok_or(Error::NotFound(id))?;
|
let revision = self.revision(revision).ok_or(Error::NotFound(id))?;
|
||||||
let signature = revision.sign(signer)?;
|
let signature = revision.sign(signer)?;
|
||||||
|
|
||||||
self.transaction("Accept revision", signer, |tx| tx.accept(id, signature))
|
self.transaction("Accept revision", signer, |tx, _| tx.accept(id, signature))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Reject an active revision.
|
/// Reject an active revision.
|
||||||
|
|
@ -938,7 +944,7 @@ where
|
||||||
revision: RevisionId,
|
revision: RevisionId,
|
||||||
signer: &G,
|
signer: &G,
|
||||||
) -> Result<EntryId, Error> {
|
) -> Result<EntryId, Error> {
|
||||||
self.transaction("Reject revision", signer, |tx| tx.reject(revision))
|
self.transaction("Reject revision", signer, |tx, _| tx.reject(revision))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Redact a revision.
|
/// Redact a revision.
|
||||||
|
|
@ -947,7 +953,7 @@ where
|
||||||
revision: RevisionId,
|
revision: RevisionId,
|
||||||
signer: &G,
|
signer: &G,
|
||||||
) -> Result<EntryId, Error> {
|
) -> Result<EntryId, Error> {
|
||||||
self.transaction("Redact revision", signer, |tx| tx.redact(revision))
|
self.transaction("Redact revision", signer, |tx, _| tx.redact(revision))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Edit an active revision's title or description.
|
/// Edit an active revision's title or description.
|
||||||
|
|
@ -958,7 +964,7 @@ where
|
||||||
description: String,
|
description: String,
|
||||||
signer: &G,
|
signer: &G,
|
||||||
) -> Result<EntryId, Error> {
|
) -> Result<EntryId, Error> {
|
||||||
self.transaction("Edit revision", signer, |tx| {
|
self.transaction("Edit revision", signer, |tx, _| {
|
||||||
tx.edit(revision, title, description)
|
tx.edit(revision, title, description)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -472,15 +472,13 @@ impl<R: ReadRepository> store::Transaction<Issue, R> {
|
||||||
&mut self,
|
&mut self,
|
||||||
id: CommentId,
|
id: CommentId,
|
||||||
body: impl ToString,
|
body: impl ToString,
|
||||||
embeds: Vec<Embed>,
|
embeds: Vec<Embed<Uri>>,
|
||||||
) -> Result<(), store::Error> {
|
) -> Result<(), store::Error> {
|
||||||
let hashed = embeds.iter().map(|e| e.hashed()).collect();
|
self.embed(embeds.clone())?;
|
||||||
|
|
||||||
self.embed(embeds)?;
|
|
||||||
self.push(Action::CommentEdit {
|
self.push(Action::CommentEdit {
|
||||||
id,
|
id,
|
||||||
body: body.to_string(),
|
body: body.to_string(),
|
||||||
embeds: hashed,
|
embeds,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -506,15 +504,13 @@ impl<R: ReadRepository> store::Transaction<Issue, R> {
|
||||||
&mut self,
|
&mut self,
|
||||||
body: S,
|
body: S,
|
||||||
reply_to: CommentId,
|
reply_to: CommentId,
|
||||||
embeds: Vec<Embed>,
|
embeds: Vec<Embed<Uri>>,
|
||||||
) -> Result<(), store::Error> {
|
) -> Result<(), store::Error> {
|
||||||
let hashed = embeds.iter().map(|e| e.hashed()).collect();
|
self.embed(embeds.clone())?;
|
||||||
|
|
||||||
self.embed(embeds)?;
|
|
||||||
self.push(Action::Comment {
|
self.push(Action::Comment {
|
||||||
body: body.to_string(),
|
body: body.to_string(),
|
||||||
reply_to: Some(reply_to),
|
reply_to: Some(reply_to),
|
||||||
embeds: hashed,
|
embeds,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -545,16 +541,15 @@ impl<R: ReadRepository> store::Transaction<Issue, R> {
|
||||||
fn thread<S: ToString>(
|
fn thread<S: ToString>(
|
||||||
&mut self,
|
&mut self,
|
||||||
body: S,
|
body: S,
|
||||||
embeds: impl IntoIterator<Item = Embed>,
|
embeds: impl IntoIterator<Item = Embed<Uri>>,
|
||||||
) -> Result<(), store::Error> {
|
) -> Result<(), store::Error> {
|
||||||
let embeds = embeds.into_iter().collect::<Vec<_>>();
|
let embeds = embeds.into_iter().collect::<Vec<_>>();
|
||||||
let hashed = embeds.iter().map(|e| e.hashed()).collect();
|
|
||||||
|
|
||||||
self.embed(embeds)?;
|
self.embed(embeds.clone())?;
|
||||||
self.push(Action::Comment {
|
self.push(Action::Comment {
|
||||||
body: body.to_string(),
|
body: body.to_string(),
|
||||||
reply_to: None,
|
reply_to: None,
|
||||||
embeds: hashed,
|
embeds,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -613,7 +608,7 @@ where
|
||||||
pub fn edit_description<G: Signer>(
|
pub fn edit_description<G: Signer>(
|
||||||
&mut self,
|
&mut self,
|
||||||
description: impl ToString,
|
description: impl ToString,
|
||||||
embeds: impl IntoIterator<Item = Embed>,
|
embeds: impl IntoIterator<Item = Embed<Uri>>,
|
||||||
signer: &G,
|
signer: &G,
|
||||||
) -> Result<EntryId, Error> {
|
) -> Result<EntryId, Error> {
|
||||||
let (id, _) = self.root();
|
let (id, _) = self.root();
|
||||||
|
|
@ -633,7 +628,7 @@ where
|
||||||
&mut self,
|
&mut self,
|
||||||
body: S,
|
body: S,
|
||||||
reply_to: CommentId,
|
reply_to: CommentId,
|
||||||
embeds: impl IntoIterator<Item = Embed>,
|
embeds: impl IntoIterator<Item = Embed<Uri>>,
|
||||||
signer: &G,
|
signer: &G,
|
||||||
) -> Result<EntryId, Error> {
|
) -> Result<EntryId, Error> {
|
||||||
self.transaction("Comment", signer, |tx| {
|
self.transaction("Comment", signer, |tx| {
|
||||||
|
|
@ -646,7 +641,7 @@ where
|
||||||
&mut self,
|
&mut self,
|
||||||
id: CommentId,
|
id: CommentId,
|
||||||
body: S,
|
body: S,
|
||||||
embeds: impl IntoIterator<Item = Embed>,
|
embeds: impl IntoIterator<Item = Embed<Uri>>,
|
||||||
signer: &G,
|
signer: &G,
|
||||||
) -> Result<EntryId, Error> {
|
) -> Result<EntryId, Error> {
|
||||||
self.transaction("Edit comment", signer, |tx| {
|
self.transaction("Edit comment", signer, |tx| {
|
||||||
|
|
@ -777,7 +772,7 @@ where
|
||||||
description: impl ToString,
|
description: impl ToString,
|
||||||
labels: &[Label],
|
labels: &[Label],
|
||||||
assignees: &[Did],
|
assignees: &[Did],
|
||||||
embeds: impl IntoIterator<Item = Embed>,
|
embeds: impl IntoIterator<Item = Embed<Uri>>,
|
||||||
cache: &'g mut C,
|
cache: &'g mut C,
|
||||||
signer: &G,
|
signer: &G,
|
||||||
) -> Result<IssueMut<'a, 'g, R, C>, Error>
|
) -> Result<IssueMut<'a, 'g, R, C>, Error>
|
||||||
|
|
@ -785,7 +780,7 @@ where
|
||||||
G: Signer,
|
G: Signer,
|
||||||
C: cob::cache::Update<Issue>,
|
C: cob::cache::Update<Issue>,
|
||||||
{
|
{
|
||||||
let (id, issue) = Transaction::initial("Create issue", &mut self.raw, signer, |tx| {
|
let (id, issue) = Transaction::initial("Create issue", &mut self.raw, signer, |tx, _| {
|
||||||
tx.thread(description, embeds)?;
|
tx.thread(description, embeds)?;
|
||||||
tx.edit(title)?;
|
tx.edit(title)?;
|
||||||
|
|
||||||
|
|
@ -1511,17 +1506,22 @@ mod test {
|
||||||
fn test_embeds() {
|
fn test_embeds() {
|
||||||
let test::setup::NodeWithRepo { node, repo, .. } = test::setup::NodeWithRepo::default();
|
let test::setup::NodeWithRepo { node, repo, .. } = test::setup::NodeWithRepo::default();
|
||||||
let mut issues = Cache::no_cache(&*repo).unwrap();
|
let mut issues = Cache::no_cache(&*repo).unwrap();
|
||||||
|
|
||||||
|
let content1 = repo.backend.blob(b"<html>Hello World!</html>").unwrap();
|
||||||
|
let content2 = repo.backend.blob(b"<html>Hello Radicle!</html>").unwrap();
|
||||||
|
let content3 = repo.backend.blob(b"body { color: red }").unwrap();
|
||||||
|
|
||||||
let embed1 = Embed {
|
let embed1 = Embed {
|
||||||
name: String::from("example.html"),
|
name: String::from("example.html"),
|
||||||
content: b"<html>Hello World!</html>".to_vec(),
|
content: Uri::from(Oid::from(content1)),
|
||||||
};
|
};
|
||||||
let embed2 = Embed {
|
let embed2 = Embed {
|
||||||
name: String::from("style.css"),
|
name: String::from("style.css"),
|
||||||
content: b"body { color: red }".to_vec(),
|
content: Uri::from(Oid::from(content2)),
|
||||||
};
|
};
|
||||||
let embed3 = Embed {
|
let embed3 = Embed {
|
||||||
name: String::from("bin"),
|
name: String::from("bin"),
|
||||||
content: vec![0, 1, 2, 3, 4, 5, 6, 7, 8, 9],
|
content: Uri::from(Oid::from(content3)),
|
||||||
};
|
};
|
||||||
let mut issue = issues
|
let mut issue = issues
|
||||||
.create(
|
.create(
|
||||||
|
|
@ -1552,34 +1552,35 @@ mod test {
|
||||||
let e2 = &c0.embeds()[1];
|
let e2 = &c0.embeds()[1];
|
||||||
let e3 = &c1.embeds()[0];
|
let e3 = &c1.embeds()[0];
|
||||||
|
|
||||||
let b1 = repo.blob(Oid::try_from(&e1.content).unwrap()).unwrap();
|
let b1 = Oid::try_from(&e1.content).unwrap();
|
||||||
let b2 = repo.blob(Oid::try_from(&e2.content).unwrap()).unwrap();
|
let b2 = Oid::try_from(&e2.content).unwrap();
|
||||||
let b3 = repo.blob(Oid::try_from(&e3.content).unwrap()).unwrap();
|
let b3 = Oid::try_from(&e3.content).unwrap();
|
||||||
|
|
||||||
assert_eq!(b1.content(), &embed1.content);
|
assert_eq!(b1, Oid::try_from(&embed1.content).unwrap());
|
||||||
assert_eq!(b2.content(), &embed2.content);
|
assert_eq!(b2, Oid::try_from(&embed2.content).unwrap());
|
||||||
assert_eq!(b3.content(), &embed3.content);
|
assert_eq!(b3, Oid::try_from(&embed3.content).unwrap());
|
||||||
|
|
||||||
assert_eq!(b1.is_binary(), false);
|
|
||||||
assert_eq!(b2.is_binary(), false);
|
|
||||||
assert_eq!(b3.is_binary(), true);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_embeds_edit() {
|
fn test_embeds_edit() {
|
||||||
let test::setup::NodeWithRepo { node, repo, .. } = test::setup::NodeWithRepo::default();
|
let test::setup::NodeWithRepo { node, repo, .. } = test::setup::NodeWithRepo::default();
|
||||||
let mut issues = Cache::no_cache(&*repo).unwrap();
|
let mut issues = Cache::no_cache(&*repo).unwrap();
|
||||||
|
|
||||||
|
let content1 = repo.backend.blob(b"<html>Hello World!</html>").unwrap();
|
||||||
|
let content1_edited = repo.backend.blob(b"<html>Hello Radicle!</html>").unwrap();
|
||||||
|
let content2 = repo.backend.blob(b"body { color: red }").unwrap();
|
||||||
|
|
||||||
let embed1 = Embed {
|
let embed1 = Embed {
|
||||||
name: String::from("example.html"),
|
name: String::from("example.html"),
|
||||||
content: b"<html>Hello World!</html>".to_vec(),
|
content: Uri::from(Oid::from(content1)),
|
||||||
};
|
};
|
||||||
let embed1_edited = Embed {
|
let embed1_edited = Embed {
|
||||||
name: String::from("example.html"),
|
name: String::from("style.css"),
|
||||||
content: b"<html>Hello Radicle!</html>".to_vec(),
|
content: Uri::from(Oid::from(content1_edited)),
|
||||||
};
|
};
|
||||||
let embed2 = Embed {
|
let embed2 = Embed {
|
||||||
name: String::from("style.css"),
|
name: String::from("bin"),
|
||||||
content: b"body { color: red }".to_vec(),
|
content: Uri::from(Oid::from(content2)),
|
||||||
};
|
};
|
||||||
let mut issue = issues
|
let mut issue = issues
|
||||||
.create(
|
.create(
|
||||||
|
|
@ -1603,10 +1604,10 @@ mod test {
|
||||||
assert_eq!(c0.embeds().len(), 1);
|
assert_eq!(c0.embeds().len(), 1);
|
||||||
|
|
||||||
let e1 = &c0.embeds()[0];
|
let e1 = &c0.embeds()[0];
|
||||||
let b1 = repo.blob(Oid::try_from(&e1.content).unwrap()).unwrap();
|
let b1 = Oid::try_from(&e1.content).unwrap();
|
||||||
|
|
||||||
assert_eq!(e1.content, Uri::from(embed1_edited.oid()));
|
assert_eq!(e1.content, embed1_edited.content);
|
||||||
assert_eq!(b1.content(), &embed1_edited.content);
|
assert_eq!(b1, Oid::try_from(&embed1_edited.content).unwrap());
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,7 @@ use crate::cob;
|
||||||
use crate::cob::cache;
|
use crate::cob::cache;
|
||||||
use crate::cob::cache::{Remove, StoreReader, StoreWriter, Update};
|
use crate::cob::cache::{Remove, StoreReader, StoreWriter, Update};
|
||||||
use crate::cob::store;
|
use crate::cob::store;
|
||||||
use crate::cob::{Embed, Label, ObjectId, TypeName};
|
use crate::cob::{Embed, Label, ObjectId, TypeName, Uri};
|
||||||
use crate::crypto::Signer;
|
use crate::crypto::Signer;
|
||||||
use crate::prelude::{Did, RepoId};
|
use crate::prelude::{Did, RepoId};
|
||||||
use crate::storage::{HasRepoId, ReadRepository, RepositoryError, SignRepository, WriteRepository};
|
use crate::storage::{HasRepoId, ReadRepository, RepositoryError, SignRepository, WriteRepository};
|
||||||
|
|
@ -79,7 +79,7 @@ impl<'a, R, C> Cache<super::Issues<'a, R>, C> {
|
||||||
description: impl ToString,
|
description: impl ToString,
|
||||||
labels: &[Label],
|
labels: &[Label],
|
||||||
assignees: &[Did],
|
assignees: &[Did],
|
||||||
embeds: impl IntoIterator<Item = Embed>,
|
embeds: impl IntoIterator<Item = Embed<Uri>>,
|
||||||
signer: &G,
|
signer: &G,
|
||||||
) -> Result<IssueMut<'a, 'g, R, C>, super::Error>
|
) -> Result<IssueMut<'a, 'g, R, C>, super::Error>
|
||||||
where
|
where
|
||||||
|
|
|
||||||
|
|
@ -475,7 +475,7 @@ impl Patch {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Patch embeds.
|
/// Patch embeds.
|
||||||
pub fn embeds(&self) -> &Vec<Embed<Uri>> {
|
pub fn embeds(&self) -> &[Embed<Uri>] {
|
||||||
let (_, r) = self.root();
|
let (_, r) = self.root();
|
||||||
r.embeds()
|
r.embeds()
|
||||||
}
|
}
|
||||||
|
|
@ -1410,7 +1410,7 @@ impl Revision {
|
||||||
self.description.iter()
|
self.description.iter()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn embeds(&self) -> &Vec<Embed<Uri>> {
|
pub fn embeds(&self) -> &[Embed<Uri>] {
|
||||||
&self.description.last().embeds
|
&self.description.last().embeds
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1663,15 +1663,13 @@ impl<R: ReadRepository> store::Transaction<Patch, R> {
|
||||||
&mut self,
|
&mut self,
|
||||||
revision: RevisionId,
|
revision: RevisionId,
|
||||||
description: impl ToString,
|
description: impl ToString,
|
||||||
embeds: Vec<Embed>,
|
embeds: Vec<Embed<Uri>>,
|
||||||
) -> Result<(), store::Error> {
|
) -> Result<(), store::Error> {
|
||||||
let hashed = embeds.iter().map(|e| e.hashed()).collect();
|
self.embed(embeds.clone())?;
|
||||||
|
|
||||||
self.embed(embeds)?;
|
|
||||||
self.push(Action::RevisionEdit {
|
self.push(Action::RevisionEdit {
|
||||||
revision,
|
revision,
|
||||||
description: description.to_string(),
|
description: description.to_string(),
|
||||||
embeds: hashed,
|
embeds,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1718,17 +1716,15 @@ impl<R: ReadRepository> store::Transaction<Patch, R> {
|
||||||
body: S,
|
body: S,
|
||||||
reply_to: Option<CommentId>,
|
reply_to: Option<CommentId>,
|
||||||
location: Option<CodeLocation>,
|
location: Option<CodeLocation>,
|
||||||
embeds: Vec<Embed>,
|
embeds: Vec<Embed<Uri>>,
|
||||||
) -> Result<(), store::Error> {
|
) -> Result<(), store::Error> {
|
||||||
let hashed = embeds.iter().map(|e| e.hashed()).collect();
|
self.embed(embeds.clone())?;
|
||||||
|
|
||||||
self.embed(embeds)?;
|
|
||||||
self.push(Action::RevisionComment {
|
self.push(Action::RevisionComment {
|
||||||
revision,
|
revision,
|
||||||
body: body.to_string(),
|
body: body.to_string(),
|
||||||
reply_to,
|
reply_to,
|
||||||
location,
|
location,
|
||||||
embeds: hashed,
|
embeds,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1738,16 +1734,14 @@ impl<R: ReadRepository> store::Transaction<Patch, R> {
|
||||||
revision: RevisionId,
|
revision: RevisionId,
|
||||||
comment: CommentId,
|
comment: CommentId,
|
||||||
body: S,
|
body: S,
|
||||||
embeds: Vec<Embed>,
|
embeds: Vec<Embed<Uri>>,
|
||||||
) -> Result<(), store::Error> {
|
) -> Result<(), store::Error> {
|
||||||
let hashed = embeds.iter().map(|e| e.hashed()).collect();
|
self.embed(embeds.clone())?;
|
||||||
|
|
||||||
self.embed(embeds)?;
|
|
||||||
self.push(Action::RevisionCommentEdit {
|
self.push(Action::RevisionCommentEdit {
|
||||||
revision,
|
revision,
|
||||||
comment,
|
comment,
|
||||||
body: body.to_string(),
|
body: body.to_string(),
|
||||||
embeds: hashed,
|
embeds,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1783,17 +1777,15 @@ impl<R: ReadRepository> store::Transaction<Patch, R> {
|
||||||
body: S,
|
body: S,
|
||||||
location: Option<CodeLocation>,
|
location: Option<CodeLocation>,
|
||||||
reply_to: Option<CommentId>,
|
reply_to: Option<CommentId>,
|
||||||
embeds: Vec<Embed>,
|
embeds: Vec<Embed<Uri>>,
|
||||||
) -> Result<(), store::Error> {
|
) -> Result<(), store::Error> {
|
||||||
let hashed = embeds.iter().map(|e| e.hashed()).collect();
|
self.embed(embeds.clone())?;
|
||||||
self.embed(embeds)?;
|
|
||||||
|
|
||||||
self.push(Action::ReviewComment {
|
self.push(Action::ReviewComment {
|
||||||
review,
|
review,
|
||||||
body: body.to_string(),
|
body: body.to_string(),
|
||||||
location,
|
location,
|
||||||
reply_to,
|
reply_to,
|
||||||
embeds: hashed,
|
embeds,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1821,16 +1813,14 @@ impl<R: ReadRepository> store::Transaction<Patch, R> {
|
||||||
review: ReviewId,
|
review: ReviewId,
|
||||||
comment: EntryId,
|
comment: EntryId,
|
||||||
body: S,
|
body: S,
|
||||||
embeds: Vec<Embed>,
|
embeds: Vec<Embed<Uri>>,
|
||||||
) -> Result<(), store::Error> {
|
) -> Result<(), store::Error> {
|
||||||
let hashed = embeds.iter().map(|e| e.hashed()).collect();
|
self.embed(embeds.clone())?;
|
||||||
|
|
||||||
self.embed(embeds)?;
|
|
||||||
self.push(Action::ReviewCommentEdit {
|
self.push(Action::ReviewCommentEdit {
|
||||||
review,
|
review,
|
||||||
comment,
|
comment,
|
||||||
body: body.to_string(),
|
body: body.to_string(),
|
||||||
embeds: hashed,
|
embeds,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -2011,7 +2001,7 @@ where
|
||||||
&mut self,
|
&mut self,
|
||||||
revision: RevisionId,
|
revision: RevisionId,
|
||||||
description: String,
|
description: String,
|
||||||
embeds: impl IntoIterator<Item = Embed>,
|
embeds: impl IntoIterator<Item = Embed<Uri>>,
|
||||||
signer: &G,
|
signer: &G,
|
||||||
) -> Result<EntryId, Error> {
|
) -> Result<EntryId, Error> {
|
||||||
self.transaction("Edit revision", signer, |tx| {
|
self.transaction("Edit revision", signer, |tx| {
|
||||||
|
|
@ -2045,7 +2035,7 @@ where
|
||||||
body: S,
|
body: S,
|
||||||
reply_to: Option<CommentId>,
|
reply_to: Option<CommentId>,
|
||||||
location: Option<CodeLocation>,
|
location: Option<CodeLocation>,
|
||||||
embeds: impl IntoIterator<Item = Embed>,
|
embeds: impl IntoIterator<Item = Embed<Uri>>,
|
||||||
signer: &G,
|
signer: &G,
|
||||||
) -> Result<EntryId, Error> {
|
) -> Result<EntryId, Error> {
|
||||||
self.transaction("Comment", signer, |tx| {
|
self.transaction("Comment", signer, |tx| {
|
||||||
|
|
@ -2079,7 +2069,7 @@ where
|
||||||
revision: RevisionId,
|
revision: RevisionId,
|
||||||
comment: CommentId,
|
comment: CommentId,
|
||||||
body: S,
|
body: S,
|
||||||
embeds: impl IntoIterator<Item = Embed>,
|
embeds: impl IntoIterator<Item = Embed<Uri>>,
|
||||||
signer: &G,
|
signer: &G,
|
||||||
) -> Result<EntryId, Error> {
|
) -> Result<EntryId, Error> {
|
||||||
self.transaction("Edit comment", signer, |tx| {
|
self.transaction("Edit comment", signer, |tx| {
|
||||||
|
|
@ -2120,7 +2110,7 @@ where
|
||||||
body: S,
|
body: S,
|
||||||
location: Option<CodeLocation>,
|
location: Option<CodeLocation>,
|
||||||
reply_to: Option<CommentId>,
|
reply_to: Option<CommentId>,
|
||||||
embeds: impl IntoIterator<Item = Embed>,
|
embeds: impl IntoIterator<Item = Embed<Uri>>,
|
||||||
signer: &G,
|
signer: &G,
|
||||||
) -> Result<EntryId, Error> {
|
) -> Result<EntryId, Error> {
|
||||||
self.transaction("Review comment", signer, |tx| {
|
self.transaction("Review comment", signer, |tx| {
|
||||||
|
|
@ -2140,7 +2130,7 @@ where
|
||||||
review: ReviewId,
|
review: ReviewId,
|
||||||
comment: EntryId,
|
comment: EntryId,
|
||||||
body: S,
|
body: S,
|
||||||
embeds: impl IntoIterator<Item = Embed>,
|
embeds: impl IntoIterator<Item = Embed<Uri>>,
|
||||||
signer: &G,
|
signer: &G,
|
||||||
) -> Result<EntryId, Error> {
|
) -> Result<EntryId, Error> {
|
||||||
self.transaction("Edit review comment", signer, |tx| {
|
self.transaction("Edit review comment", signer, |tx| {
|
||||||
|
|
@ -2567,7 +2557,7 @@ where
|
||||||
where
|
where
|
||||||
C: cob::cache::Update<Patch>,
|
C: cob::cache::Update<Patch>,
|
||||||
{
|
{
|
||||||
let (id, patch) = Transaction::initial("Create patch", &mut self.raw, signer, |tx| {
|
let (id, patch) = Transaction::initial("Create patch", &mut self.raw, signer, |tx, _| {
|
||||||
tx.revision(description, base, oid)?;
|
tx.revision(description, base, oid)?;
|
||||||
tx.edit(title, target)?;
|
tx.edit(title, target)?;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,7 @@ use radicle_cob::CollaborativeObject;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
use crate::cob::op::Op;
|
use crate::cob::op::Op;
|
||||||
use crate::cob::{Create, Embed, EntryId, ObjectId, TypeName, Update, Updated, Version};
|
use crate::cob::{Create, Embed, EntryId, ObjectId, TypeName, Update, Updated, Uri, Version};
|
||||||
use crate::git;
|
use crate::git;
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
use crate::storage::git as storage;
|
use crate::storage::git as storage;
|
||||||
|
|
@ -92,6 +92,10 @@ pub enum Error {
|
||||||
NotFound(TypeName, ObjectId),
|
NotFound(TypeName, ObjectId),
|
||||||
#[error("signed refs: {0}")]
|
#[error("signed refs: {0}")]
|
||||||
SignRefs(#[from] storage::Error),
|
SignRefs(#[from] storage::Error),
|
||||||
|
#[error("invalid or unknown embed URI: {0}")]
|
||||||
|
EmbedUri(Uri),
|
||||||
|
#[error(transparent)]
|
||||||
|
Git(git::raw::Error),
|
||||||
#[error("failed to find reference '{name}': {err}")]
|
#[error("failed to find reference '{name}': {err}")]
|
||||||
RefLookup {
|
RefLookup {
|
||||||
name: git::RefString,
|
name: git::RefString,
|
||||||
|
|
@ -148,12 +152,21 @@ where
|
||||||
object_id: ObjectId,
|
object_id: ObjectId,
|
||||||
message: &str,
|
message: &str,
|
||||||
actions: impl Into<NonEmpty<T::Action>>,
|
actions: impl Into<NonEmpty<T::Action>>,
|
||||||
embeds: Vec<Embed>,
|
embeds: Vec<Embed<Uri>>,
|
||||||
signer: &G,
|
signer: &G,
|
||||||
) -> Result<Updated<T>, Error> {
|
) -> Result<Updated<T>, Error> {
|
||||||
let actions = actions.into();
|
let actions = actions.into();
|
||||||
let related = actions.iter().flat_map(T::Action::parents).collect();
|
let related = actions.iter().flat_map(T::Action::parents).collect();
|
||||||
let changes = actions.try_map(encoding::encode)?;
|
let changes = actions.try_map(encoding::encode)?;
|
||||||
|
let embeds = embeds
|
||||||
|
.into_iter()
|
||||||
|
.map(|e| {
|
||||||
|
Ok::<_, Error>(Embed {
|
||||||
|
content: git::Oid::try_from(&e.content).map_err(Error::EmbedUri)?,
|
||||||
|
name: e.name.clone(),
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.collect::<Result<_, _>>()?;
|
||||||
let updated = cob::update(
|
let updated = cob::update(
|
||||||
self.repo,
|
self.repo,
|
||||||
signer,
|
signer,
|
||||||
|
|
@ -178,12 +191,21 @@ where
|
||||||
&self,
|
&self,
|
||||||
message: &str,
|
message: &str,
|
||||||
actions: impl Into<NonEmpty<T::Action>>,
|
actions: impl Into<NonEmpty<T::Action>>,
|
||||||
embeds: Vec<Embed>,
|
embeds: Vec<Embed<Uri>>,
|
||||||
signer: &G,
|
signer: &G,
|
||||||
) -> Result<(ObjectId, T), Error> {
|
) -> Result<(ObjectId, T), Error> {
|
||||||
let actions = actions.into();
|
let actions = actions.into();
|
||||||
let parents = actions.iter().flat_map(T::Action::parents).collect();
|
let parents = actions.iter().flat_map(T::Action::parents).collect();
|
||||||
let contents = actions.try_map(encoding::encode)?;
|
let contents = actions.try_map(encoding::encode)?;
|
||||||
|
let embeds = embeds
|
||||||
|
.into_iter()
|
||||||
|
.map(|e| {
|
||||||
|
Ok::<_, Error>(Embed {
|
||||||
|
content: git::Oid::try_from(&e.content).map_err(Error::EmbedUri)?,
|
||||||
|
name: e.name.clone(),
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.collect::<Result<_, _>>()?;
|
||||||
let cob = cob::create::<T, _, G>(
|
let cob = cob::create::<T, _, G>(
|
||||||
self.repo,
|
self.repo,
|
||||||
signer,
|
signer,
|
||||||
|
|
@ -263,7 +285,7 @@ where
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct Transaction<T: Cob + cob::Evaluate<R>, R> {
|
pub struct Transaction<T: Cob + cob::Evaluate<R>, R> {
|
||||||
actions: Vec<T::Action>,
|
actions: Vec<T::Action>,
|
||||||
embeds: Vec<Embed>,
|
embeds: Vec<Embed<Uri>>,
|
||||||
repo: PhantomData<R>,
|
repo: PhantomData<R>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -290,12 +312,12 @@ where
|
||||||
) -> Result<(ObjectId, T), Error>
|
) -> Result<(ObjectId, T), Error>
|
||||||
where
|
where
|
||||||
G: Signer,
|
G: Signer,
|
||||||
F: FnOnce(&mut Self) -> Result<(), Error>,
|
F: FnOnce(&mut Self, &R) -> Result<(), Error>,
|
||||||
R: ReadRepository + SignRepository + cob::Store,
|
R: ReadRepository + SignRepository + cob::Store,
|
||||||
T::Action: Serialize + Clone,
|
T::Action: Serialize + Clone,
|
||||||
{
|
{
|
||||||
let mut tx = Transaction::default();
|
let mut tx = Transaction::default();
|
||||||
operations(&mut tx)?;
|
operations(&mut tx, store.as_ref())?;
|
||||||
|
|
||||||
let actions = NonEmpty::from_vec(tx.actions)
|
let actions = NonEmpty::from_vec(tx.actions)
|
||||||
.expect("Transaction::initial: transaction must contain at least one action");
|
.expect("Transaction::initial: transaction must contain at least one action");
|
||||||
|
|
@ -311,7 +333,7 @@ where
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Embed media into the transaction.
|
/// Embed media into the transaction.
|
||||||
pub fn embed(&mut self, embeds: impl IntoIterator<Item = Embed>) -> Result<(), Error> {
|
pub fn embed(&mut self, embeds: impl IntoIterator<Item = Embed<Uri>>) -> Result<(), Error> {
|
||||||
self.embeds.extend(embeds);
|
self.embeds.extend(embeds);
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|
|
||||||
|
|
@ -439,9 +439,13 @@ impl Repository {
|
||||||
storage: &S,
|
storage: &S,
|
||||||
signer: &G,
|
signer: &G,
|
||||||
) -> Result<(Self, git::Oid), RepositoryError> {
|
) -> Result<(Self, git::Oid), RepositoryError> {
|
||||||
let (doc_oid, _) = doc.encode()?;
|
let (doc_oid, doc_bytes) = doc.encode()?;
|
||||||
let id = RepoId::from(doc_oid);
|
let id = RepoId::from(doc_oid);
|
||||||
let repo = Self::create(paths::repository(storage, &id), id, storage.info())?;
|
let repo = Self::create(paths::repository(storage, &id), id, storage.info())?;
|
||||||
|
let oid = repo.backend.blob(&doc_bytes)?; // Store document blob in repository.
|
||||||
|
|
||||||
|
debug_assert_eq!(oid, *doc_oid);
|
||||||
|
|
||||||
let commit = doc.init(&repo, signer)?;
|
let commit = doc.init(&repo, signer)?;
|
||||||
|
|
||||||
Ok((repo, commit))
|
Ok((repo, commit))
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue