From 855327d303566aac1d3e065d4ba7e16d9ccd18e6 Mon Sep 17 00:00:00 2001 From: cloudhead Date: Wed, 9 Oct 2024 16:59:53 +0200 Subject: [PATCH] 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. --- radicle-cli/src/commands/patch/edit.rs | 21 ++--- radicle-cob/src/backend/git/change.rs | 6 +- radicle-cob/src/change/store.rs | 18 ++++- .../src/object/collaboration/create.rs | 2 +- .../src/object/collaboration/update.rs | 2 +- radicle/src/cob/common.rs | 20 ----- radicle/src/cob/identity.rs | 44 +++++----- radicle/src/cob/issue.rs | 81 ++++++++++--------- radicle/src/cob/issue/cache.rs | 4 +- radicle/src/cob/patch.rs | 56 ++++++------- radicle/src/cob/store.rs | 36 +++++++-- radicle/src/storage/git.rs | 6 +- 12 files changed, 152 insertions(+), 144 deletions(-) diff --git a/radicle-cli/src/commands/patch/edit.rs b/radicle-cli/src/commands/patch/edit.rs index 3b49f79e..a3ac8631 100644 --- a/radicle-cli/src/commands/patch/edit.rs +++ b/radicle-cli/src/commands/patch/edit.rs @@ -1,6 +1,6 @@ use super::*; -use radicle::cob::{self, patch, resolve_embed}; +use radicle::cob::{self, patch}; use radicle::crypto; use radicle::prelude::*; use radicle::storage::git::Repository; @@ -19,12 +19,11 @@ pub fn run( let Ok(patch) = patches.get_mut(patch_id) else { anyhow::bail!("Patch `{patch_id}` not found"); }; - let (title, description) = term::patch::get_edit_message(message, &patch)?; match revision_id { - Some(id) => edit_revision(patch, id, title, description, repository, &signer), - None => edit_root(patch, title, description, repository, &signer), + Some(id) => edit_revision(patch, id, title, description, &signer), + None => edit_root(patch, title, description, &signer), } } @@ -32,7 +31,6 @@ fn edit_root( mut patch: patch::PatchMut<'_, '_, Repository, cob::cache::StoreWriter>, title: String, description: String, - repository: &Repository, signer: &G, ) -> anyhow::Result<()> where @@ -56,11 +54,7 @@ where let (root, _) = patch.root(); let target = patch.target(); - let embeds = patch - .embeds() - .iter() - .filter_map(|embed| resolve_embed(repository, embed.clone())) - .collect::>(); + let embeds = patch.embeds().to_owned(); patch.transaction("Edit root", signer, |tx| { if let Some(t) = title { @@ -80,17 +74,12 @@ fn edit_revision( revision: patch::RevisionId, mut title: String, description: String, - repository: &Repository, signer: &G, ) -> anyhow::Result<()> where G: crypto::Signer, { - let embeds = patch - .embeds() - .iter() - .filter_map(|embed| resolve_embed(repository, embed.clone())) - .collect::>(); + let embeds = patch.embeds().to_owned(); let description = if description.is_empty() { title } else { diff --git a/radicle-cob/src/backend/git/change.rs b/radicle-cob/src/backend/git/change.rs index 7efcaab2..9943c92d 100644 --- a/radicle-cob/src/backend/git/change.rs +++ b/radicle-cob/src/backend/git/change.rs @@ -341,7 +341,7 @@ fn write_commit( fn write_manifest( repo: &git2::Repository, manifest: &store::Manifest, - embeds: Vec, + embeds: Vec>, contents: &NonEmpty>, ) -> Result { let mut root = repo.treebuilder(None)?; @@ -372,10 +372,10 @@ fn write_manifest( let mut embeds_tree = repo.treebuilder(None)?; for embed in embeds { - let oid = repo.blob(&embed.content)?; + let oid = embed.content; 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()?; diff --git a/radicle-cob/src/change/store.rs b/radicle-cob/src/change/store.rs index a5f71ad5..3a5c24c6 100644 --- a/radicle-cob/src/change/store.rs +++ b/radicle-cob/src/change/store.rs @@ -45,7 +45,7 @@ pub struct Template { pub type_name: TypeName, pub tips: Vec, pub message: String, - pub embeds: Vec, + pub embeds: Vec>, pub contents: NonEmpty>, } @@ -191,6 +191,22 @@ pub struct Embed> { pub content: T, } +impl> Embed { + /// Create a new embed. + pub fn store( + name: impl ToString, + content: &[u8], + repo: &git2::Repository, + ) -> Result { + let oid = repo.blob(content)?; + + Ok(Self { + name: name.to_string(), + content: T::from(oid.into()), + }) + } +} + impl Embed> { /// Get the object id of the embedded content. pub fn oid(&self) -> Oid { diff --git a/radicle-cob/src/object/collaboration/create.rs b/radicle-cob/src/object/collaboration/create.rs index 43f50aae..68181754 100644 --- a/radicle-cob/src/object/collaboration/create.rs +++ b/radicle-cob/src/object/collaboration/create.rs @@ -18,7 +18,7 @@ pub struct Create { /// The message to add when creating this object. pub message: String, /// Embedded content. - pub embeds: Vec, + pub embeds: Vec>, /// COB version. pub version: Version, } diff --git a/radicle-cob/src/object/collaboration/update.rs b/radicle-cob/src/object/collaboration/update.rs index 7de291c4..8a632cc1 100644 --- a/radicle-cob/src/object/collaboration/update.rs +++ b/radicle-cob/src/object/collaboration/update.rs @@ -34,7 +34,7 @@ pub struct Update { /// The message to add when updating this object. pub message: String, /// Embedded files. - pub embeds: Vec, + pub embeds: Vec>, } /// Update an existing [`CollaborativeObject`]. diff --git a/radicle/src/cob/common.rs b/radicle/src/cob/common.rs index 0d69de15..9c7eec68 100644 --- a/radicle/src/cob/common.rs +++ b/radicle/src/cob/common.rs @@ -8,10 +8,8 @@ use base64::prelude::{Engine, BASE64_STANDARD}; use localtime::LocalTime; use serde::{Deserialize, Serialize}; -use crate::cob::Embed; use crate::git::Oid; use crate::prelude::{Did, PublicKey}; -use crate::storage::ReadRepository; /// Timestamp used for COB operations. #[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) -> Option>> { - 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. #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum Authorization { diff --git a/radicle/src/cob/identity.rs b/radicle/src/cob/identity.rs index bf21aad7..0266ab44 100644 --- a/radicle/src/cob/identity.rs +++ b/radicle/src/cob/identity.rs @@ -3,7 +3,7 @@ use std::{fmt, ops::Deref, str::FromStr}; use crypto::{PublicKey, Signature}; use once_cell::sync::Lazy; -use radicle_cob::{ObjectId, TypeName}; +use radicle_cob::{Embed, ObjectId, TypeName}; use radicle_crypto::{Signer, Verified}; use radicle_git_ext as git_ext; use radicle_git_ext::Oid; @@ -15,7 +15,7 @@ use crate::{ cob::{ op, store, store::{Cob, CobAction, Transaction}, - ActorId, Timestamp, + ActorId, Timestamp, Uri, }, identity::{ doc::{Doc, DocError, RepoId}, @@ -187,10 +187,12 @@ impl Identity { signer: &G, ) -> Result, cob::store::Error> { let mut store = cob::store::Store::open(store)?; - let (id, identity) = - Transaction::::initial("Initialize identity", &mut store, signer, |tx| { - tx.revision("Initial revision", "", doc, None, signer) - })?; + let (id, identity) = Transaction::::initial( + "Initialize identity", + &mut store, + signer, + |tx, repo| tx.revision("Initial revision", "", doc, None, repo, signer), + )?; Ok(IdentityMut { id, @@ -825,22 +827,26 @@ impl store::Transaction { pub fn redact(&mut self, revision: RevisionId) -> Result<(), store::Error> { self.push(Action::RevisionRedact { revision }) } +} +impl store::Transaction { pub fn revision( &mut self, title: impl ToString, description: impl ToString, doc: &Doc, parent: Option, + repo: &R, signer: &G, ) -> 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::::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. - self.embed([cob::Embed { - name: String::from("radicle.json"), - content, - }])?; + self.embed([embed])?; // Revision metadata. self.push(Action::Revision { @@ -891,10 +897,10 @@ where ) -> Result where G: Signer, - F: FnOnce(&mut Transaction) -> Result<(), store::Error>, + F: FnOnce(&mut Transaction, &R) -> Result<(), store::Error>, { 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)?; self.identity = doc; @@ -912,8 +918,8 @@ where signer: &G, ) -> Result { let parent = self.current; - let id = self.transaction("Propose revision", signer, |tx| { - tx.revision(title, description, doc, Some(parent), signer) + let id = self.transaction("Propose revision", signer, |tx, repo| { + tx.revision(title, description, doc, Some(parent), repo, signer) })?; Ok(id) @@ -929,7 +935,7 @@ where let revision = self.revision(revision).ok_or(Error::NotFound(id))?; 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. @@ -938,7 +944,7 @@ where revision: RevisionId, signer: &G, ) -> Result { - self.transaction("Reject revision", signer, |tx| tx.reject(revision)) + self.transaction("Reject revision", signer, |tx, _| tx.reject(revision)) } /// Redact a revision. @@ -947,7 +953,7 @@ where revision: RevisionId, signer: &G, ) -> Result { - 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. @@ -958,7 +964,7 @@ where description: String, signer: &G, ) -> Result { - self.transaction("Edit revision", signer, |tx| { + self.transaction("Edit revision", signer, |tx, _| { tx.edit(revision, title, description) }) } diff --git a/radicle/src/cob/issue.rs b/radicle/src/cob/issue.rs index dbdf453a..088cfd3a 100644 --- a/radicle/src/cob/issue.rs +++ b/radicle/src/cob/issue.rs @@ -472,15 +472,13 @@ impl store::Transaction { &mut self, id: CommentId, body: impl ToString, - embeds: Vec, + embeds: Vec>, ) -> Result<(), store::Error> { - let hashed = embeds.iter().map(|e| e.hashed()).collect(); - - self.embed(embeds)?; + self.embed(embeds.clone())?; self.push(Action::CommentEdit { id, body: body.to_string(), - embeds: hashed, + embeds, }) } @@ -506,15 +504,13 @@ impl store::Transaction { &mut self, body: S, reply_to: CommentId, - embeds: Vec, + embeds: Vec>, ) -> Result<(), store::Error> { - let hashed = embeds.iter().map(|e| e.hashed()).collect(); - - self.embed(embeds)?; + self.embed(embeds.clone())?; self.push(Action::Comment { body: body.to_string(), reply_to: Some(reply_to), - embeds: hashed, + embeds, }) } @@ -545,16 +541,15 @@ impl store::Transaction { fn thread( &mut self, body: S, - embeds: impl IntoIterator, + embeds: impl IntoIterator>, ) -> Result<(), store::Error> { let embeds = embeds.into_iter().collect::>(); - let hashed = embeds.iter().map(|e| e.hashed()).collect(); - self.embed(embeds)?; + self.embed(embeds.clone())?; self.push(Action::Comment { body: body.to_string(), reply_to: None, - embeds: hashed, + embeds, }) } } @@ -613,7 +608,7 @@ where pub fn edit_description( &mut self, description: impl ToString, - embeds: impl IntoIterator, + embeds: impl IntoIterator>, signer: &G, ) -> Result { let (id, _) = self.root(); @@ -633,7 +628,7 @@ where &mut self, body: S, reply_to: CommentId, - embeds: impl IntoIterator, + embeds: impl IntoIterator>, signer: &G, ) -> Result { self.transaction("Comment", signer, |tx| { @@ -646,7 +641,7 @@ where &mut self, id: CommentId, body: S, - embeds: impl IntoIterator, + embeds: impl IntoIterator>, signer: &G, ) -> Result { self.transaction("Edit comment", signer, |tx| { @@ -777,7 +772,7 @@ where description: impl ToString, labels: &[Label], assignees: &[Did], - embeds: impl IntoIterator, + embeds: impl IntoIterator>, cache: &'g mut C, signer: &G, ) -> Result, Error> @@ -785,7 +780,7 @@ where G: Signer, C: cob::cache::Update, { - 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.edit(title)?; @@ -1511,17 +1506,22 @@ mod test { fn test_embeds() { let test::setup::NodeWithRepo { node, repo, .. } = test::setup::NodeWithRepo::default(); let mut issues = Cache::no_cache(&*repo).unwrap(); + + let content1 = repo.backend.blob(b"Hello World!").unwrap(); + let content2 = repo.backend.blob(b"Hello Radicle!").unwrap(); + let content3 = repo.backend.blob(b"body { color: red }").unwrap(); + let embed1 = Embed { name: String::from("example.html"), - content: b"Hello World!".to_vec(), + content: Uri::from(Oid::from(content1)), }; let embed2 = Embed { name: String::from("style.css"), - content: b"body { color: red }".to_vec(), + content: Uri::from(Oid::from(content2)), }; let embed3 = Embed { 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 .create( @@ -1552,34 +1552,35 @@ mod test { let e2 = &c0.embeds()[1]; let e3 = &c1.embeds()[0]; - let b1 = repo.blob(Oid::try_from(&e1.content).unwrap()).unwrap(); - let b2 = repo.blob(Oid::try_from(&e2.content).unwrap()).unwrap(); - let b3 = repo.blob(Oid::try_from(&e3.content).unwrap()).unwrap(); + let b1 = Oid::try_from(&e1.content).unwrap(); + let b2 = Oid::try_from(&e2.content).unwrap(); + let b3 = Oid::try_from(&e3.content).unwrap(); - assert_eq!(b1.content(), &embed1.content); - assert_eq!(b2.content(), &embed2.content); - assert_eq!(b3.content(), &embed3.content); - - assert_eq!(b1.is_binary(), false); - assert_eq!(b2.is_binary(), false); - assert_eq!(b3.is_binary(), true); + assert_eq!(b1, Oid::try_from(&embed1.content).unwrap()); + assert_eq!(b2, Oid::try_from(&embed2.content).unwrap()); + assert_eq!(b3, Oid::try_from(&embed3.content).unwrap()); } #[test] fn test_embeds_edit() { let test::setup::NodeWithRepo { node, repo, .. } = test::setup::NodeWithRepo::default(); let mut issues = Cache::no_cache(&*repo).unwrap(); + + let content1 = repo.backend.blob(b"Hello World!").unwrap(); + let content1_edited = repo.backend.blob(b"Hello Radicle!").unwrap(); + let content2 = repo.backend.blob(b"body { color: red }").unwrap(); + let embed1 = Embed { name: String::from("example.html"), - content: b"Hello World!".to_vec(), + content: Uri::from(Oid::from(content1)), }; let embed1_edited = Embed { - name: String::from("example.html"), - content: b"Hello Radicle!".to_vec(), + name: String::from("style.css"), + content: Uri::from(Oid::from(content1_edited)), }; let embed2 = Embed { - name: String::from("style.css"), - content: b"body { color: red }".to_vec(), + name: String::from("bin"), + content: Uri::from(Oid::from(content2)), }; let mut issue = issues .create( @@ -1603,10 +1604,10 @@ mod test { assert_eq!(c0.embeds().len(), 1); 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!(b1.content(), &embed1_edited.content); + assert_eq!(e1.content, embed1_edited.content); + assert_eq!(b1, Oid::try_from(&embed1_edited.content).unwrap()); } #[test] diff --git a/radicle/src/cob/issue/cache.rs b/radicle/src/cob/issue/cache.rs index 80a3122b..30f3eea4 100644 --- a/radicle/src/cob/issue/cache.rs +++ b/radicle/src/cob/issue/cache.rs @@ -8,7 +8,7 @@ use crate::cob; use crate::cob::cache; use crate::cob::cache::{Remove, StoreReader, StoreWriter, Update}; 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::prelude::{Did, RepoId}; use crate::storage::{HasRepoId, ReadRepository, RepositoryError, SignRepository, WriteRepository}; @@ -79,7 +79,7 @@ impl<'a, R, C> Cache, C> { description: impl ToString, labels: &[Label], assignees: &[Did], - embeds: impl IntoIterator, + embeds: impl IntoIterator>, signer: &G, ) -> Result, super::Error> where diff --git a/radicle/src/cob/patch.rs b/radicle/src/cob/patch.rs index 4c22bf3c..4008ff73 100644 --- a/radicle/src/cob/patch.rs +++ b/radicle/src/cob/patch.rs @@ -475,7 +475,7 @@ impl Patch { } /// Patch embeds. - pub fn embeds(&self) -> &Vec> { + pub fn embeds(&self) -> &[Embed] { let (_, r) = self.root(); r.embeds() } @@ -1410,7 +1410,7 @@ impl Revision { self.description.iter() } - pub fn embeds(&self) -> &Vec> { + pub fn embeds(&self) -> &[Embed] { &self.description.last().embeds } @@ -1663,15 +1663,13 @@ impl store::Transaction { &mut self, revision: RevisionId, description: impl ToString, - embeds: Vec, + embeds: Vec>, ) -> Result<(), store::Error> { - let hashed = embeds.iter().map(|e| e.hashed()).collect(); - - self.embed(embeds)?; + self.embed(embeds.clone())?; self.push(Action::RevisionEdit { revision, description: description.to_string(), - embeds: hashed, + embeds, }) } @@ -1718,17 +1716,15 @@ impl store::Transaction { body: S, reply_to: Option, location: Option, - embeds: Vec, + embeds: Vec>, ) -> Result<(), store::Error> { - let hashed = embeds.iter().map(|e| e.hashed()).collect(); - - self.embed(embeds)?; + self.embed(embeds.clone())?; self.push(Action::RevisionComment { revision, body: body.to_string(), reply_to, location, - embeds: hashed, + embeds, }) } @@ -1738,16 +1734,14 @@ impl store::Transaction { revision: RevisionId, comment: CommentId, body: S, - embeds: Vec, + embeds: Vec>, ) -> Result<(), store::Error> { - let hashed = embeds.iter().map(|e| e.hashed()).collect(); - - self.embed(embeds)?; + self.embed(embeds.clone())?; self.push(Action::RevisionCommentEdit { revision, comment, body: body.to_string(), - embeds: hashed, + embeds, }) } @@ -1783,17 +1777,15 @@ impl store::Transaction { body: S, location: Option, reply_to: Option, - embeds: Vec, + embeds: Vec>, ) -> Result<(), store::Error> { - let hashed = embeds.iter().map(|e| e.hashed()).collect(); - self.embed(embeds)?; - + self.embed(embeds.clone())?; self.push(Action::ReviewComment { review, body: body.to_string(), location, reply_to, - embeds: hashed, + embeds, }) } @@ -1821,16 +1813,14 @@ impl store::Transaction { review: ReviewId, comment: EntryId, body: S, - embeds: Vec, + embeds: Vec>, ) -> Result<(), store::Error> { - let hashed = embeds.iter().map(|e| e.hashed()).collect(); - - self.embed(embeds)?; + self.embed(embeds.clone())?; self.push(Action::ReviewCommentEdit { review, comment, body: body.to_string(), - embeds: hashed, + embeds, }) } @@ -2011,7 +2001,7 @@ where &mut self, revision: RevisionId, description: String, - embeds: impl IntoIterator, + embeds: impl IntoIterator>, signer: &G, ) -> Result { self.transaction("Edit revision", signer, |tx| { @@ -2045,7 +2035,7 @@ where body: S, reply_to: Option, location: Option, - embeds: impl IntoIterator, + embeds: impl IntoIterator>, signer: &G, ) -> Result { self.transaction("Comment", signer, |tx| { @@ -2079,7 +2069,7 @@ where revision: RevisionId, comment: CommentId, body: S, - embeds: impl IntoIterator, + embeds: impl IntoIterator>, signer: &G, ) -> Result { self.transaction("Edit comment", signer, |tx| { @@ -2120,7 +2110,7 @@ where body: S, location: Option, reply_to: Option, - embeds: impl IntoIterator, + embeds: impl IntoIterator>, signer: &G, ) -> Result { self.transaction("Review comment", signer, |tx| { @@ -2140,7 +2130,7 @@ where review: ReviewId, comment: EntryId, body: S, - embeds: impl IntoIterator, + embeds: impl IntoIterator>, signer: &G, ) -> Result { self.transaction("Edit review comment", signer, |tx| { @@ -2567,7 +2557,7 @@ where where C: cob::cache::Update, { - 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.edit(title, target)?; diff --git a/radicle/src/cob/store.rs b/radicle/src/cob/store.rs index c34a7fb0..0984ee73 100644 --- a/radicle/src/cob/store.rs +++ b/radicle/src/cob/store.rs @@ -9,7 +9,7 @@ use radicle_cob::CollaborativeObject; use serde::{Deserialize, Serialize}; 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::prelude::*; use crate::storage::git as storage; @@ -92,6 +92,10 @@ pub enum Error { NotFound(TypeName, ObjectId), #[error("signed refs: {0}")] 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}")] RefLookup { name: git::RefString, @@ -148,12 +152,21 @@ where object_id: ObjectId, message: &str, actions: impl Into>, - embeds: Vec, + embeds: Vec>, signer: &G, ) -> Result, Error> { let actions = actions.into(); let related = actions.iter().flat_map(T::Action::parents).collect(); 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::>()?; let updated = cob::update( self.repo, signer, @@ -178,12 +191,21 @@ where &self, message: &str, actions: impl Into>, - embeds: Vec, + embeds: Vec>, signer: &G, ) -> Result<(ObjectId, T), Error> { let actions = actions.into(); let parents = actions.iter().flat_map(T::Action::parents).collect(); 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::>()?; let cob = cob::create::( self.repo, signer, @@ -263,7 +285,7 @@ where #[derive(Debug)] pub struct Transaction, R> { actions: Vec, - embeds: Vec, + embeds: Vec>, repo: PhantomData, } @@ -290,12 +312,12 @@ where ) -> Result<(ObjectId, T), Error> where G: Signer, - F: FnOnce(&mut Self) -> Result<(), Error>, + F: FnOnce(&mut Self, &R) -> Result<(), Error>, R: ReadRepository + SignRepository + cob::Store, T::Action: Serialize + Clone, { let mut tx = Transaction::default(); - operations(&mut tx)?; + operations(&mut tx, store.as_ref())?; let actions = NonEmpty::from_vec(tx.actions) .expect("Transaction::initial: transaction must contain at least one action"); @@ -311,7 +333,7 @@ where } /// Embed media into the transaction. - pub fn embed(&mut self, embeds: impl IntoIterator) -> Result<(), Error> { + pub fn embed(&mut self, embeds: impl IntoIterator>) -> Result<(), Error> { self.embeds.extend(embeds); Ok(()) diff --git a/radicle/src/storage/git.rs b/radicle/src/storage/git.rs index aa7abfa3..d65f5932 100644 --- a/radicle/src/storage/git.rs +++ b/radicle/src/storage/git.rs @@ -439,9 +439,13 @@ impl Repository { storage: &S, signer: &G, ) -> Result<(Self, git::Oid), RepositoryError> { - let (doc_oid, _) = doc.encode()?; + let (doc_oid, doc_bytes) = doc.encode()?; let id = RepoId::from(doc_oid); 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)?; Ok((repo, commit))