radicle: Refactor COB Storage Access
Instead of passing the signer as an argument to many methods on `Store`,
scope the `Store` itself to a signer.
This further allows to differentiate two different access modes on the
store in `radicle::cob::store::access`: `WriteAs` (which requires signer)
and `ReadOnly` (which does not require a signer).
The caches for issues and patches in `radicle::cob::{issue,patch}::Cache`
are concretised by removing the first type parameter, since it was
specific to issues and patches anyway. This was done in this commit as
it touches very similar usage sites.
Make `Device` less prominent, and instead lean more heavily towards
traits from the `signature` crate, such as `Keypair` and `Verifier`
in addition to `Signer`. Trait bounds regarding `Signer` could be
simplified, but this is left for the future.
In `radicle-cli`, the function `term::cob::patches_mut`, which generates
errors with a hint is used instead of the lower-level `Profile::patches_mut`.
Commands `rad issue cache` and `rad patch cache` now construct a
writeable cache on top of a read-only store.
Many knock-on changes are handled as well, to arrive at a clean state.
This commit is contained in:
parent
10a82958ca
commit
f223afd9d7
|
|
@ -11,6 +11,8 @@ use nonempty::NonEmpty;
|
|||
|
||||
use radicle::cob;
|
||||
use radicle::cob::store::CobAction;
|
||||
use radicle::cob::store::access::ReadOnly;
|
||||
use radicle::cob::store::access::WriteAs;
|
||||
use radicle::cob::stream::CobStream as _;
|
||||
use radicle::git;
|
||||
use radicle::prelude::*;
|
||||
|
|
@ -48,21 +50,22 @@ pub fn run(args: Args, ctx: impl term::Context) -> anyhow::Result<()> {
|
|||
type_name,
|
||||
operation,
|
||||
}) => {
|
||||
let signer = &profile.signer()?;
|
||||
let signer = profile.signer()?;
|
||||
let access = WriteAs::new(&signer);
|
||||
let repo = storage.repository_mut(repo)?;
|
||||
let embeds = embeds(&repo, operation.embed_files, operation.embed_hashes)?;
|
||||
|
||||
let oid = match type_name {
|
||||
Patch => {
|
||||
let store: Store<cob::patch::Patch, _> = Store::open(&repo)?;
|
||||
let mut store: Store<cob::patch::Patch, _, _> = Store::open(&repo, access)?;
|
||||
let actions = read_jsonl_actions(&operation.actions)?;
|
||||
let (oid, _) = store.create(&operation.message, actions, embeds, signer)?;
|
||||
let (oid, _) = store.create(&operation.message, actions, embeds)?;
|
||||
oid
|
||||
}
|
||||
Issue => {
|
||||
let store: Store<cob::issue::Issue, _> = Store::open(&repo)?;
|
||||
let mut store: Store<cob::issue::Issue, _, _> = Store::open(&repo, access)?;
|
||||
let actions = read_jsonl_actions(&operation.actions)?;
|
||||
let (oid, _) = store.create(&operation.message, actions, embeds, signer)?;
|
||||
let (oid, _) = store.create(&operation.message, actions, embeds)?;
|
||||
oid
|
||||
}
|
||||
Identity => anyhow::bail!(
|
||||
|
|
@ -70,10 +73,10 @@ pub fn run(args: Args, ctx: impl term::Context) -> anyhow::Result<()> {
|
|||
&type_name
|
||||
),
|
||||
Other(type_name) => {
|
||||
let store: Store<cob::external::External, _> =
|
||||
Store::open_for(&type_name, &repo)?;
|
||||
let mut store: Store<cob::external::External, _, _> =
|
||||
Store::open_for(&type_name, &repo, access)?;
|
||||
let actions = read_jsonl_actions(&operation.actions)?;
|
||||
let (oid, _) = store.create(&operation.message, actions, embeds, signer)?;
|
||||
let (oid, _) = store.create(&operation.message, actions, embeds)?;
|
||||
oid
|
||||
}
|
||||
};
|
||||
|
|
@ -178,9 +181,9 @@ pub fn run(args: Args, ctx: impl term::Context) -> anyhow::Result<()> {
|
|||
Patch => {
|
||||
let actions: Vec<cob::patch::Action> =
|
||||
read_jsonl_actions(&operation.actions)?.into();
|
||||
let mut patches = profile.patches_mut(&repo)?;
|
||||
let mut patches = crate::terminal::cob::patches_mut(&profile, &repo, signer)?;
|
||||
let mut patch = patches.get_mut(&oid)?;
|
||||
patch.transaction(&operation.message, &*profile.signer()?, |tx| {
|
||||
patch.transaction(&operation.message, |tx| {
|
||||
tx.extend(actions)?;
|
||||
tx.embed(embeds)?;
|
||||
Ok(())
|
||||
|
|
@ -189,9 +192,9 @@ pub fn run(args: Args, ctx: impl term::Context) -> anyhow::Result<()> {
|
|||
Issue => {
|
||||
let actions: Vec<cob::issue::Action> =
|
||||
read_jsonl_actions(&operation.actions)?.into();
|
||||
let mut issues = profile.issues_mut(&repo)?;
|
||||
let mut issues = crate::terminal::cob::issues_mut(&profile, &repo, signer)?;
|
||||
let mut issue = issues.get_mut(&oid)?;
|
||||
issue.transaction(&operation.message, &*profile.signer()?, |tx| {
|
||||
issue.transaction(&operation.message, |tx| {
|
||||
tx.extend(actions)?;
|
||||
tx.embed(embeds)?;
|
||||
Ok(())
|
||||
|
|
@ -204,9 +207,10 @@ pub fn run(args: Args, ctx: impl term::Context) -> anyhow::Result<()> {
|
|||
Other(type_name) => {
|
||||
use cob::external::{Action, External};
|
||||
let actions: Vec<Action> = read_jsonl_actions(&operation.actions)?.into();
|
||||
let mut store: Store<External, _> = Store::open_for(&type_name, &repo)?;
|
||||
let mut store: Store<External, _, _> =
|
||||
Store::open_for(&type_name, &repo, WriteAs::new(signer))?;
|
||||
let tx = cob::store::Transaction::new(type_name.clone(), actions, embeds);
|
||||
let (_, oid) = tx.commit(&operation.message, oid, &mut store, signer)?;
|
||||
let (_, oid) = tx.commit(&operation.message, oid, &mut store)?;
|
||||
oid
|
||||
}
|
||||
};
|
||||
|
|
@ -244,6 +248,7 @@ fn show(
|
|||
}
|
||||
FilteredTypeName::Issue => {
|
||||
use radicle::issue::cache::Issues as _;
|
||||
|
||||
let issues = term::cob::issues(profile, repo)?;
|
||||
for oid in oids {
|
||||
let oid = &oid.resolve(&repo.backend)?;
|
||||
|
|
@ -259,6 +264,7 @@ fn show(
|
|||
}
|
||||
FilteredTypeName::Patch => {
|
||||
use radicle::patch::cache::Patches as _;
|
||||
|
||||
let patches = term::cob::patches(profile, repo)?;
|
||||
for oid in oids {
|
||||
let oid = &oid.resolve(&repo.backend)?;
|
||||
|
|
@ -273,8 +279,9 @@ fn show(
|
|||
}
|
||||
}
|
||||
FilteredTypeName::Other(type_name) => {
|
||||
let store =
|
||||
cob::store::Store::<cob::external::External, _>::open_for(&type_name, repo)?;
|
||||
let store = cob::store::Store::<cob::external::External, _, _>::open_for(
|
||||
&type_name, repo, ReadOnly,
|
||||
)?;
|
||||
for oid in oids {
|
||||
let oid = &oid.resolve(&repo.backend)?;
|
||||
let cob = store
|
||||
|
|
|
|||
|
|
@ -9,7 +9,6 @@ use radicle::cob::identity::{self, IdentityMut, Revision, RevisionId};
|
|||
use radicle::identity::doc::update;
|
||||
use radicle::identity::{Doc, Identity, RawDoc, doc};
|
||||
use radicle::node::NodeId;
|
||||
use radicle::node::device::Device;
|
||||
use radicle::storage::{ReadStorage as _, WriteRepository};
|
||||
use radicle::{Profile, cob, crypto};
|
||||
use radicle_surf::diff::Diff;
|
||||
|
|
@ -37,7 +36,9 @@ pub fn run(args: Args, ctx: impl term::Context) -> anyhow::Result<()> {
|
|||
let repo = storage
|
||||
.repository(rid)
|
||||
.context(anyhow!("repository `{rid}` not found in local storage"))?;
|
||||
let mut identity = Identity::load_mut(&repo)?;
|
||||
|
||||
let device = profile.signer()?;
|
||||
let mut identity = Identity::load_mut(&repo, &device)?;
|
||||
let current = identity.current().clone();
|
||||
|
||||
let interactive = args.interactive();
|
||||
|
|
@ -47,14 +48,13 @@ pub fn run(args: Args, ctx: impl term::Context) -> anyhow::Result<()> {
|
|||
Command::Accept { revision } => {
|
||||
let revision = get(revision, &identity, &repo)?.clone();
|
||||
let id = revision.id;
|
||||
let signer = term::signer(&profile)?;
|
||||
|
||||
if !revision.is_active() {
|
||||
anyhow::bail!("cannot vote on revision that is {}", revision.state);
|
||||
}
|
||||
|
||||
if interactive.confirm(format!("Accept revision {}?", term::format::tertiary(id))) {
|
||||
identity.accept(&revision.id, &signer)?;
|
||||
identity.accept(&revision.id)?;
|
||||
|
||||
if let Some(revision) = identity.revision(&id) {
|
||||
// Update the canonical head to point to the latest accepted revision.
|
||||
|
|
@ -72,7 +72,6 @@ pub fn run(args: Args, ctx: impl term::Context) -> anyhow::Result<()> {
|
|||
}
|
||||
Command::Reject { revision } => {
|
||||
let revision = get(revision, &identity, &repo)?.clone();
|
||||
let signer = term::signer(&profile)?;
|
||||
|
||||
if !revision.is_active() {
|
||||
anyhow::bail!("cannot vote on revision that is {}", revision.state);
|
||||
|
|
@ -82,7 +81,7 @@ pub fn run(args: Args, ctx: impl term::Context) -> anyhow::Result<()> {
|
|||
"Reject revision {}?",
|
||||
term::format::tertiary(revision.id)
|
||||
)) {
|
||||
identity.reject(revision.id, &signer)?;
|
||||
identity.reject(revision.id)?;
|
||||
|
||||
if !args.quiet {
|
||||
term::success!("Revision {} rejected", revision.id);
|
||||
|
|
@ -96,7 +95,6 @@ pub fn run(args: Args, ctx: impl term::Context) -> anyhow::Result<()> {
|
|||
description,
|
||||
} => {
|
||||
let revision = get(revision, &identity, &repo)?.clone();
|
||||
let signer = term::signer(&profile)?;
|
||||
|
||||
if !revision.is_active() {
|
||||
anyhow::bail!("revision can no longer be edited");
|
||||
|
|
@ -104,7 +102,7 @@ pub fn run(args: Args, ctx: impl term::Context) -> anyhow::Result<()> {
|
|||
let Some((title, description)) = edit_title_description(title, description)? else {
|
||||
anyhow::bail!("revision title or description missing");
|
||||
};
|
||||
identity.edit(revision.id, title, description, &signer)?;
|
||||
identity.edit(revision.id, title, description)?;
|
||||
|
||||
if !args.quiet {
|
||||
term::success!("Revision {} edited", revision.id);
|
||||
|
|
@ -193,15 +191,7 @@ pub fn run(args: Args, ctx: impl term::Context) -> anyhow::Result<()> {
|
|||
}
|
||||
return Ok(());
|
||||
}
|
||||
let signer = term::signer(&profile)?;
|
||||
let revision = update(
|
||||
title,
|
||||
description,
|
||||
proposal,
|
||||
&mut identity,
|
||||
&signer,
|
||||
&profile,
|
||||
)?;
|
||||
let revision = update(title, description, proposal, &mut identity, &profile)?;
|
||||
|
||||
if revision.is_accepted() && revision.parent == Some(current.id) {
|
||||
// Update the canonical head to point to the latest accepted revision.
|
||||
|
|
@ -253,7 +243,6 @@ pub fn run(args: Args, ctx: impl term::Context) -> anyhow::Result<()> {
|
|||
}
|
||||
Command::Redact { revision } => {
|
||||
let revision = get(revision, &identity, &repo)?.clone();
|
||||
let signer = term::signer(&profile)?;
|
||||
|
||||
if revision.is_accepted() {
|
||||
anyhow::bail!("cannot redact accepted revision");
|
||||
|
|
@ -262,7 +251,7 @@ pub fn run(args: Args, ctx: impl term::Context) -> anyhow::Result<()> {
|
|||
"Redact revision {}?",
|
||||
term::format::tertiary(revision.id)
|
||||
)) {
|
||||
identity.redact(revision.id, &signer)?;
|
||||
identity.redact(revision.id)?;
|
||||
|
||||
if !args.quiet {
|
||||
term::success!("Revision {} redacted", revision.id);
|
||||
|
|
@ -422,21 +411,23 @@ and description.
|
|||
Ok(result)
|
||||
}
|
||||
|
||||
fn update<R, G>(
|
||||
fn update<Repo, Signer>(
|
||||
title: Option<Title>,
|
||||
description: Option<String>,
|
||||
doc: Doc,
|
||||
current: &mut IdentityMut<R>,
|
||||
signer: &Device<G>,
|
||||
current: &mut IdentityMut<Repo, Signer>,
|
||||
profile: &Profile,
|
||||
) -> anyhow::Result<Revision>
|
||||
where
|
||||
R: WriteRepository + cob::Store<Namespace = NodeId>,
|
||||
G: crypto::signature::Signer<crypto::Signature>,
|
||||
Repo: WriteRepository + cob::Store<Namespace = NodeId>,
|
||||
Signer: crypto::signature::Keypair<VerifyingKey = crypto::PublicKey>,
|
||||
Signer: crypto::signature::Signer<crypto::Signature>,
|
||||
Signer: crypto::signature::Signer<crypto::ssh::ExtendedSignature>,
|
||||
Signer: crypto::signature::Verifier<crypto::Signature>,
|
||||
{
|
||||
if let Some((title, description)) = edit_title_description(title, description)? {
|
||||
let id = current
|
||||
.update(title, description, &doc, signer)
|
||||
.update(title, description, &doc)
|
||||
.map_err(|e| on_identity_err(e, profile))?;
|
||||
let revision = current
|
||||
.revision(&id)
|
||||
|
|
|
|||
|
|
@ -6,13 +6,13 @@ use anyhow::Context as _;
|
|||
|
||||
use radicle::cob::common::Label;
|
||||
use radicle::cob::issue::{CloseReason, State};
|
||||
use radicle::cob::store::access::WriteAs;
|
||||
use radicle::cob::{Title, issue};
|
||||
|
||||
use radicle::Profile;
|
||||
use radicle::crypto;
|
||||
use radicle::issue::cache::Issues as _;
|
||||
use radicle::node::NodeId;
|
||||
use radicle::node::device::Device;
|
||||
use radicle::prelude::Did;
|
||||
use radicle::profile;
|
||||
use radicle::storage;
|
||||
|
|
@ -48,7 +48,8 @@ pub fn run(args: Args, ctx: impl term::Context) -> anyhow::Result<()> {
|
|||
.unwrap_or_else(|| Command::List(args.empty.into()));
|
||||
|
||||
let announce = !args.no_announce && command.should_announce_for();
|
||||
let mut issues = term::cob::issues_mut(&profile, &repo)?;
|
||||
let signer = profile.signer()?;
|
||||
let mut issues = term::cob::issues_mut(&profile, &repo, &signer)?;
|
||||
|
||||
match command {
|
||||
Command::Edit {
|
||||
|
|
@ -56,8 +57,7 @@ pub fn run(args: Args, ctx: impl term::Context) -> anyhow::Result<()> {
|
|||
title,
|
||||
description,
|
||||
} => {
|
||||
let signer = term::signer(&profile)?;
|
||||
let issue = edit(&mut issues, &repo, id, title, description, &signer)?;
|
||||
let issue = edit(&mut issues, &repo, id, title, description)?;
|
||||
if !args.quiet {
|
||||
term::issue::show(&issue, issue.id(), Format::Header, args.verbose, &profile)?;
|
||||
}
|
||||
|
|
@ -68,7 +68,6 @@ pub fn run(args: Args, ctx: impl term::Context) -> anyhow::Result<()> {
|
|||
labels,
|
||||
assignees,
|
||||
} => {
|
||||
let signer = term::signer(&profile)?;
|
||||
open(
|
||||
title,
|
||||
description,
|
||||
|
|
@ -77,7 +76,6 @@ pub fn run(args: Args, ctx: impl term::Context) -> anyhow::Result<()> {
|
|||
args.verbose,
|
||||
args.quiet,
|
||||
&mut issues,
|
||||
&signer,
|
||||
&profile,
|
||||
)?;
|
||||
}
|
||||
|
|
@ -131,10 +129,9 @@ pub fn run(args: Args, ctx: impl term::Context) -> anyhow::Result<()> {
|
|||
Command::State { id, target_state } => {
|
||||
let to: StateArg = target_state.into();
|
||||
let id = id.resolve(&repo.backend)?;
|
||||
let signer = term::signer(&profile)?;
|
||||
let mut issue = issues.get_mut(&id)?;
|
||||
let state = to.into();
|
||||
issue.lifecycle(state, &signer)?;
|
||||
issue.lifecycle(state)?;
|
||||
|
||||
if !args.quiet {
|
||||
let success =
|
||||
|
|
@ -155,7 +152,6 @@ pub fn run(args: Args, ctx: impl term::Context) -> anyhow::Result<()> {
|
|||
} => {
|
||||
let id = id.resolve(&repo.backend)?;
|
||||
if let Ok(mut issue) = issues.get_mut(&id) {
|
||||
let signer = term::signer(&profile)?;
|
||||
let comment_id = match comment_id {
|
||||
Some(cid) => cid.resolve(&repo.backend)?,
|
||||
None => *term::io::comment_select(&issue).map(|(cid, _)| cid)?,
|
||||
|
|
@ -164,11 +160,10 @@ pub fn run(args: Args, ctx: impl term::Context) -> anyhow::Result<()> {
|
|||
Some(reaction) => reaction,
|
||||
None => term::io::reaction_select()?,
|
||||
};
|
||||
issue.react(comment_id, reaction, true, &signer)?;
|
||||
issue.react(comment_id, reaction, true)?;
|
||||
}
|
||||
}
|
||||
Command::Assign { id, add, delete } => {
|
||||
let signer = term::signer(&profile)?;
|
||||
let id = id.resolve(&repo.backend)?;
|
||||
let Ok(mut issue) = issues.get_mut(&id) else {
|
||||
anyhow::bail!("Issue `{id}` not found");
|
||||
|
|
@ -179,7 +174,7 @@ pub fn run(args: Args, ctx: impl term::Context) -> anyhow::Result<()> {
|
|||
.chain(add.iter())
|
||||
.cloned()
|
||||
.collect::<Vec<_>>();
|
||||
issue.assign(assignees, &signer)?;
|
||||
issue.assign(assignees)?;
|
||||
}
|
||||
Command::Label { id, add, delete } => {
|
||||
let id = id.resolve(&repo.backend)?;
|
||||
|
|
@ -192,8 +187,7 @@ pub fn run(args: Args, ctx: impl term::Context) -> anyhow::Result<()> {
|
|||
.chain(add.iter())
|
||||
.cloned()
|
||||
.collect::<Vec<_>>();
|
||||
let signer = term::signer(&profile)?;
|
||||
issue.label(labels, &signer)?;
|
||||
issue.label(labels)?;
|
||||
}
|
||||
Command::List(list_args) => {
|
||||
list(
|
||||
|
|
@ -206,8 +200,7 @@ pub fn run(args: Args, ctx: impl term::Context) -> anyhow::Result<()> {
|
|||
}
|
||||
Command::Delete { id } => {
|
||||
let id = id.resolve(&repo.backend)?;
|
||||
let signer = term::signer(&profile)?;
|
||||
issues.remove(&id, &signer)?;
|
||||
issues.remove(&id)?;
|
||||
}
|
||||
Command::Cache { id, storage } => {
|
||||
let mode = if storage {
|
||||
|
|
@ -365,20 +358,22 @@ fn mk_issue_row(
|
|||
]
|
||||
}
|
||||
|
||||
fn open<R, G>(
|
||||
fn open<Repo, Signer>(
|
||||
title: Option<Title>,
|
||||
description: Option<String>,
|
||||
labels: Vec<Label>,
|
||||
assignees: Vec<Did>,
|
||||
verbose: bool,
|
||||
quiet: bool,
|
||||
cache: &mut issue::Cache<issue::Issues<'_, R>, cob::cache::StoreWriter>,
|
||||
signer: &Device<G>,
|
||||
cache: &mut issue::Cache<'_, Repo, WriteAs<'_, Signer>, cob::cache::StoreWriter>,
|
||||
profile: &Profile,
|
||||
) -> anyhow::Result<()>
|
||||
where
|
||||
R: WriteRepository + cob::Store<Namespace = NodeId>,
|
||||
G: crypto::signature::Signer<crypto::Signature>,
|
||||
Repo: WriteRepository + cob::Store<Namespace = NodeId>,
|
||||
Signer: crypto::signature::Keypair<VerifyingKey = crypto::PublicKey>,
|
||||
Signer: crypto::signature::Signer<crypto::Signature>,
|
||||
Signer: crypto::signature::Signer<crypto::ssh::ExtendedSignature>,
|
||||
Signer: crypto::signature::Verifier<crypto::Signature>,
|
||||
{
|
||||
let (title, description) = if let (Some(t), Some(d)) = (title.as_ref(), description.as_ref()) {
|
||||
(t.to_owned(), d.to_owned())
|
||||
|
|
@ -393,7 +388,6 @@ where
|
|||
labels.as_slice(),
|
||||
assignees.as_slice(),
|
||||
[],
|
||||
signer,
|
||||
)?;
|
||||
|
||||
if !quiet {
|
||||
|
|
@ -402,17 +396,19 @@ where
|
|||
Ok(())
|
||||
}
|
||||
|
||||
fn edit<'a, 'g, R, G>(
|
||||
issues: &'g mut issue::Cache<issue::Issues<'a, R>, cob::cache::StoreWriter>,
|
||||
fn edit<'a, 'b, 'g, Repo, Signer>(
|
||||
issues: &'g mut issue::Cache<'a, Repo, WriteAs<'b, Signer>, cob::cache::StoreWriter>,
|
||||
repo: &storage::git::Repository,
|
||||
id: Rev,
|
||||
title: Option<Title>,
|
||||
description: Option<String>,
|
||||
signer: &Device<G>,
|
||||
) -> anyhow::Result<issue::IssueMut<'a, 'g, R, cob::cache::StoreWriter>>
|
||||
) -> anyhow::Result<issue::IssueMut<'a, 'b, 'g, Repo, Signer, cob::cache::StoreWriter>>
|
||||
where
|
||||
R: WriteRepository + cob::Store<Namespace = NodeId>,
|
||||
G: crypto::signature::Signer<crypto::Signature>,
|
||||
Repo: WriteRepository + cob::Store<Namespace = NodeId>,
|
||||
Signer: crypto::signature::Keypair<VerifyingKey = crypto::PublicKey>,
|
||||
Signer: crypto::signature::Signer<crypto::Signature>,
|
||||
Signer: radicle::crypto::signature::Signer<radicle::crypto::ssh::ExtendedSignature>,
|
||||
Signer: crypto::signature::Verifier<crypto::Signature>,
|
||||
{
|
||||
let id = id.resolve(&repo.backend)?;
|
||||
let mut issue = issues.get_mut(&id)?;
|
||||
|
|
@ -421,7 +417,7 @@ where
|
|||
|
||||
if title.is_some() || description.is_some() {
|
||||
// Editing by command line arguments.
|
||||
issue.transaction("Edit", signer, |tx| {
|
||||
issue.transaction("Edit", |tx| {
|
||||
if let Some(t) = title {
|
||||
tx.edit(t)?;
|
||||
}
|
||||
|
|
@ -442,7 +438,7 @@ where
|
|||
return Ok(issue);
|
||||
};
|
||||
|
||||
issue.transaction("Edit", signer, |tx| {
|
||||
issue.transaction("Edit", |tx| {
|
||||
tx.edit(title)?;
|
||||
tx.edit_comment(comment_id, description, vec![])?;
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
use std::ops::ControlFlow;
|
||||
|
||||
use radicle::Profile;
|
||||
use radicle::cob::store::access::ReadOnly;
|
||||
use radicle::issue::IssueId;
|
||||
use radicle::storage::ReadStorage as _;
|
||||
use radicle::storage::git::Repository;
|
||||
|
|
@ -37,7 +38,18 @@ pub fn run(mode: CacheMode, profile: &Profile) -> anyhow::Result<()> {
|
|||
}
|
||||
|
||||
fn cache(id: Option<IssueId>, repository: &Repository, profile: &Profile) -> anyhow::Result<()> {
|
||||
let mut issues = term::cob::issues_mut(profile, repository)?;
|
||||
let mut issues = {
|
||||
// NOTE: Since we require a cache that is writeable, on top of a store that
|
||||
// is read-only, we can neither use [`term::cob::issues_mut`] nor [`term::cob::issues`]
|
||||
// since these convenience functions pair a writeable cache with a writeable
|
||||
// store, and respectively a read-only cache with a read-only store.
|
||||
|
||||
let db = profile.cobs_db_mut()?;
|
||||
db.check_version()?;
|
||||
let store = radicle::cob::issue::Issues::open(repository, ReadOnly)?;
|
||||
|
||||
radicle::cob::issue::Cache::open(store, db)
|
||||
};
|
||||
|
||||
match id {
|
||||
Some(id) => {
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
use radicle::Profile;
|
||||
use radicle::cob::store::access::WriteAs;
|
||||
use radicle::cob::thread;
|
||||
use radicle::storage::WriteRepository;
|
||||
use radicle::{cob, git, issue, storage};
|
||||
|
|
@ -8,27 +9,35 @@ use crate::terminal as term;
|
|||
use crate::terminal::Element as _;
|
||||
use crate::terminal::patch::Message;
|
||||
|
||||
pub(super) fn comment(
|
||||
pub(super) fn comment<Signer>(
|
||||
profile: &Profile,
|
||||
repo: &storage::git::Repository,
|
||||
issues: &mut issue::Cache<
|
||||
issue::Issues<'_, storage::git::Repository>,
|
||||
'_,
|
||||
storage::git::Repository,
|
||||
WriteAs<'_, Signer>,
|
||||
cob::cache::Store<cob::cache::Write>,
|
||||
>,
|
||||
id: Rev,
|
||||
message: Message,
|
||||
reply_to: Option<Rev>,
|
||||
quiet: bool,
|
||||
) -> Result<(), anyhow::Error> {
|
||||
) -> Result<(), anyhow::Error>
|
||||
where
|
||||
Signer: radicle::crypto::signature::Keypair<VerifyingKey = radicle::crypto::PublicKey>,
|
||||
Signer: radicle::crypto::signature::Signer<radicle::crypto::Signature>,
|
||||
Signer: radicle::crypto::signature::Signer<radicle::crypto::ssh::ExtendedSignature>,
|
||||
Signer: radicle::crypto::signature::Verifier<radicle::crypto::Signature>,
|
||||
{
|
||||
let reply_to = reply_to
|
||||
.map(|rev| rev.resolve::<git::Oid>(repo.raw()))
|
||||
.transpose()?;
|
||||
let signer = term::signer(profile)?;
|
||||
let issue_id = id.resolve::<cob::ObjectId>(&repo.backend)?;
|
||||
let mut issue = issues.get_mut(&issue_id)?;
|
||||
let (root_comment_id, _) = issue.root();
|
||||
let body = prompt_comment(message, issue.thread(), reply_to, None)?;
|
||||
let comment_id = issue.comment(body, reply_to.unwrap_or(*root_comment_id), vec![], &signer)?;
|
||||
|
||||
let comment_id = issue.comment(body, reply_to.unwrap_or(*root_comment_id), vec![])?;
|
||||
if quiet {
|
||||
term::print(comment_id);
|
||||
} else {
|
||||
|
|
@ -38,19 +47,26 @@ pub(super) fn comment(
|
|||
Ok(())
|
||||
}
|
||||
|
||||
pub(super) fn edit(
|
||||
pub(super) fn edit<Signer>(
|
||||
profile: &Profile,
|
||||
repo: &storage::git::Repository,
|
||||
issues: &mut issue::Cache<
|
||||
issue::Issues<'_, storage::git::Repository>,
|
||||
'_,
|
||||
storage::git::Repository,
|
||||
WriteAs<'_, Signer>,
|
||||
cob::cache::Store<cob::cache::Write>,
|
||||
>,
|
||||
id: Rev,
|
||||
message: Message,
|
||||
comment_id: Rev,
|
||||
quiet: bool,
|
||||
) -> Result<(), anyhow::Error> {
|
||||
let signer = term::signer(profile)?;
|
||||
) -> Result<(), anyhow::Error>
|
||||
where
|
||||
Signer: radicle::crypto::signature::Keypair<VerifyingKey = radicle::crypto::PublicKey>,
|
||||
Signer: radicle::crypto::signature::Signer<radicle::crypto::Signature>,
|
||||
Signer: radicle::crypto::signature::Signer<radicle::crypto::ssh::ExtendedSignature>,
|
||||
Signer: radicle::crypto::signature::Verifier<radicle::crypto::Signature>,
|
||||
{
|
||||
let issue_id = id.resolve::<cob::ObjectId>(&repo.backend)?;
|
||||
let comment_id = comment_id.resolve(&repo.backend)?;
|
||||
let mut issue = issues.get_mut(&issue_id)?;
|
||||
|
|
@ -64,7 +80,7 @@ pub(super) fn edit(
|
|||
comment.reply_to(),
|
||||
Some(comment.body()),
|
||||
)?;
|
||||
issue.edit_comment(comment_id, body, vec![], &signer)?;
|
||||
issue.edit_comment(comment_id, body, vec![])?;
|
||||
if quiet {
|
||||
term::print(comment_id);
|
||||
} else {
|
||||
|
|
|
|||
|
|
@ -10,15 +10,15 @@ pub fn run(
|
|||
repository: &Repository,
|
||||
) -> anyhow::Result<()> {
|
||||
let signer = term::signer(profile)?;
|
||||
let mut patches = term::cob::patches_mut(profile, repository)?;
|
||||
let mut patches = term::cob::patches_mut(profile, repository, &signer)?;
|
||||
let Ok(mut patch) = patches.get_mut(patch_id) else {
|
||||
anyhow::bail!("Patch `{patch_id}` not found");
|
||||
};
|
||||
|
||||
if undo {
|
||||
patch.unarchive(&signer)?;
|
||||
patch.unarchive()?;
|
||||
} else {
|
||||
patch.archive(&signer)?;
|
||||
patch.archive()?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ pub fn run(
|
|||
repository: &Repository,
|
||||
) -> anyhow::Result<()> {
|
||||
let signer = term::signer(profile)?;
|
||||
let mut patches = term::cob::patches_mut(profile, repository)?;
|
||||
let mut patches = term::cob::patches_mut(profile, repository, &signer)?;
|
||||
let Ok(mut patch) = patches.get_mut(patch_id) else {
|
||||
anyhow::bail!("Patch `{patch_id}` not found");
|
||||
};
|
||||
|
|
@ -24,6 +24,6 @@ pub fn run(
|
|||
.filter(|did| !delete.contains(did))
|
||||
.chain(add)
|
||||
.collect::<BTreeSet<_>>();
|
||||
patch.assign(assignees, &signer)?;
|
||||
patch.assign(assignees)?;
|
||||
Ok(())
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
use std::ops::ControlFlow;
|
||||
|
||||
use radicle::Profile;
|
||||
use radicle::cob::store::access::ReadOnly;
|
||||
use radicle::patch::PatchId;
|
||||
use radicle::storage::ReadStorage as _;
|
||||
use radicle::storage::git::Repository;
|
||||
|
|
@ -37,7 +38,18 @@ pub fn run(mode: CacheMode, profile: &Profile) -> anyhow::Result<()> {
|
|||
}
|
||||
|
||||
fn cache(id: Option<PatchId>, repository: &Repository, profile: &Profile) -> anyhow::Result<()> {
|
||||
let mut patches = term::cob::patches_mut(profile, repository)?;
|
||||
let mut patches = {
|
||||
// NOTE: Since we require a cache that is writeable, on top of a store that
|
||||
// is read-only, we can neither use [`term::cob::patches_mut`] nor [`term::cob::patches`]
|
||||
// since these convenience functions pair a writeable cache with a writeable
|
||||
// store, and respectively a read-only cache with a read-only store.
|
||||
|
||||
let db = profile.cobs_db_mut()?;
|
||||
db.check_version()?;
|
||||
let store = radicle::cob::patch::Patches::open(repository, ReadOnly)?;
|
||||
|
||||
radicle::cob::patch::Cache::open(store, db)
|
||||
};
|
||||
|
||||
match id {
|
||||
Some(id) => {
|
||||
|
|
|
|||
|
|
@ -24,7 +24,7 @@ pub fn run(
|
|||
profile: &Profile,
|
||||
) -> anyhow::Result<()> {
|
||||
let signer = term::signer(profile)?;
|
||||
let mut patches = term::cob::patches_mut(profile, repo)?;
|
||||
let mut patches = term::cob::patches_mut(profile, repo, &signer)?;
|
||||
|
||||
let revision_id = revision_id.resolve::<cob::EntryId>(&repo.backend)?;
|
||||
let ByRevision {
|
||||
|
|
@ -37,7 +37,7 @@ pub fn run(
|
|||
.ok_or_else(|| anyhow!("Patch revision `{revision_id}` not found"))?;
|
||||
let mut patch = patch::PatchMut::new(patch_id, patch, &mut patches);
|
||||
let (body, reply_to) = prompt(message, reply_to, &revision, repo)?;
|
||||
let comment_id = patch.comment(revision_id, body, reply_to, None, vec![], &signer)?;
|
||||
let comment_id = patch.comment(revision_id, body, reply_to, None, vec![])?;
|
||||
let comment = patch
|
||||
.revision(&revision_id)
|
||||
.ok_or(anyhow!("error retrieving revision `{revision_id}`"))?
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@ pub fn run(
|
|||
profile: &Profile,
|
||||
) -> anyhow::Result<()> {
|
||||
let signer = term::signer(profile)?;
|
||||
let mut patches = profile.patches_mut(repo)?;
|
||||
let mut patches = term::cob::patches_mut(profile, repo, &signer)?;
|
||||
let revision_id = revision_id.resolve::<cob::EntryId>(&repo.backend)?;
|
||||
let ByRevision {
|
||||
id: patch_id,
|
||||
|
|
@ -33,7 +33,7 @@ pub fn run(
|
|||
.ok_or_else(|| anyhow!("Patch revision `{revision_id}` not found"))?;
|
||||
let (body, _) = super::prompt(message, None, &revision, repo)?;
|
||||
let mut patch = patch::PatchMut::new(patch_id, patch, &mut patches);
|
||||
patch.comment_edit(revision_id, comment_id, body, vec![], &signer)?;
|
||||
patch.comment_edit(revision_id, comment_id, body, vec![])?;
|
||||
|
||||
if !quiet {
|
||||
let comment = patch
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@ pub fn run(
|
|||
profile: &Profile,
|
||||
) -> anyhow::Result<()> {
|
||||
let signer = term::signer(profile)?;
|
||||
let mut patches = profile.patches_mut(repo)?;
|
||||
let mut patches = term::cob::patches_mut(profile, repo, &signer)?;
|
||||
let revision_id = revision_id.resolve::<cob::EntryId>(&repo.backend)?;
|
||||
let ByRevision {
|
||||
id: patch_id,
|
||||
|
|
@ -32,7 +32,7 @@ pub fn run(
|
|||
.find_by_revision(&patch::RevisionId::from(revision_id))?
|
||||
.ok_or_else(|| anyhow!("Patch revision `{revision_id}` not found"))?;
|
||||
let mut patch = patch::PatchMut::new(patch_id, patch, &mut patches);
|
||||
patch.comment_react(revision_id, comment, reaction, active, &signer)?;
|
||||
patch.comment_react(revision_id, comment, reaction, active)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@ pub fn run(
|
|||
profile: &Profile,
|
||||
) -> anyhow::Result<()> {
|
||||
let signer = term::signer(profile)?;
|
||||
let mut patches = profile.patches_mut(repo)?;
|
||||
let mut patches = term::cob::patches_mut(profile, repo, &signer)?;
|
||||
let revision_id = revision_id.resolve::<cob::EntryId>(&repo.backend)?;
|
||||
let ByRevision {
|
||||
id: patch_id,
|
||||
|
|
@ -29,7 +29,7 @@ pub fn run(
|
|||
.find_by_revision(&patch::RevisionId::from(revision_id))?
|
||||
.ok_or_else(|| anyhow!("Patch revision `{revision_id}` not found"))?;
|
||||
let mut patch = patch::PatchMut::new(patch_id, patch, &mut patches);
|
||||
patch.comment_redact(revision_id, comment, &signer)?;
|
||||
patch.comment_redact(revision_id, comment)?;
|
||||
term::success!("Redacted comment {comment}");
|
||||
|
||||
Ok(())
|
||||
|
|
|
|||
|
|
@ -4,9 +4,9 @@ use radicle::storage::git::Repository;
|
|||
use super::*;
|
||||
|
||||
pub fn run(patch_id: &PatchId, profile: &Profile, repository: &Repository) -> anyhow::Result<()> {
|
||||
let signer = &term::signer(profile)?;
|
||||
let mut patches = term::cob::patches_mut(profile, repository)?;
|
||||
patches.remove(patch_id, signer)?;
|
||||
let signer = term::signer(profile)?;
|
||||
let mut patches = term::cob::patches_mut(profile, repository, &signer)?;
|
||||
patches.remove(patch_id)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,7 +4,6 @@ use radicle::cob;
|
|||
use radicle::cob::Title;
|
||||
use radicle::cob::patch;
|
||||
use radicle::crypto;
|
||||
use radicle::node::device::Device;
|
||||
use radicle::prelude::*;
|
||||
use radicle::storage::git::Repository;
|
||||
|
||||
|
|
@ -18,26 +17,28 @@ pub fn run(
|
|||
repository: &Repository,
|
||||
) -> anyhow::Result<()> {
|
||||
let signer = term::signer(profile)?;
|
||||
let mut patches = term::cob::patches_mut(profile, repository)?;
|
||||
let mut patches = term::cob::patches_mut(profile, repository, &signer)?;
|
||||
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, &signer),
|
||||
None => edit_root(patch, title, description, &signer),
|
||||
Some(id) => edit_revision(patch, id, title, description),
|
||||
None => edit_root(patch, title, description),
|
||||
}
|
||||
}
|
||||
|
||||
fn edit_root<G>(
|
||||
mut patch: patch::PatchMut<'_, '_, Repository, cob::cache::StoreWriter>,
|
||||
fn edit_root<Signer>(
|
||||
mut patch: patch::PatchMut<'_, '_, '_, Repository, Signer, cob::cache::StoreWriter>,
|
||||
title: Title,
|
||||
description: String,
|
||||
signer: &Device<G>,
|
||||
) -> anyhow::Result<()>
|
||||
where
|
||||
G: crypto::signature::Signer<crypto::Signature>,
|
||||
Signer: crypto::signature::Signer<crypto::Signature>,
|
||||
Signer: crypto::signature::Keypair<VerifyingKey = crypto::PublicKey>,
|
||||
Signer: crypto::signature::Signer<crypto::ssh::ExtendedSignature>,
|
||||
Signer: crypto::signature::Verifier<crypto::Signature>,
|
||||
{
|
||||
let title = if title.as_ref() != patch.title() {
|
||||
Some(title)
|
||||
|
|
@ -59,7 +60,7 @@ where
|
|||
let target = patch.target();
|
||||
let embeds = patch.embeds().to_owned();
|
||||
|
||||
patch.transaction("Edit root", signer, |tx| {
|
||||
patch.transaction("Edit root", |tx| {
|
||||
if let Some(t) = title {
|
||||
tx.edit(t, target)?;
|
||||
}
|
||||
|
|
@ -72,15 +73,17 @@ where
|
|||
Ok(())
|
||||
}
|
||||
|
||||
fn edit_revision<G>(
|
||||
mut patch: patch::PatchMut<'_, '_, Repository, cob::cache::StoreWriter>,
|
||||
fn edit_revision<Signer>(
|
||||
mut patch: patch::PatchMut<'_, '_, '_, Repository, Signer, cob::cache::StoreWriter>,
|
||||
revision: patch::RevisionId,
|
||||
title: Title,
|
||||
description: String,
|
||||
signer: &Device<G>,
|
||||
) -> anyhow::Result<()>
|
||||
where
|
||||
G: crypto::signature::Signer<crypto::Signature>,
|
||||
Signer: crypto::signature::Keypair<VerifyingKey = crypto::PublicKey>,
|
||||
Signer: crypto::signature::Signer<crypto::Signature>,
|
||||
Signer: crypto::signature::Signer<radicle::crypto::ssh::ExtendedSignature>,
|
||||
Signer: crypto::signature::Verifier<crypto::Signature>,
|
||||
{
|
||||
let embeds = patch.embeds().to_owned();
|
||||
let mut message = title.to_string();
|
||||
|
|
@ -91,7 +94,7 @@ where
|
|||
message.push_str(&description);
|
||||
message
|
||||
};
|
||||
patch.transaction("Edit revision", signer, |tx| {
|
||||
patch.transaction("Edit revision", |tx| {
|
||||
tx.edit_revision(revision, message, embeds)?;
|
||||
Ok(())
|
||||
})?;
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ pub fn run(
|
|||
repository: &Repository,
|
||||
) -> anyhow::Result<()> {
|
||||
let signer = term::signer(profile)?;
|
||||
let mut patches = term::cob::patches_mut(profile, repository)?;
|
||||
let mut patches = term::cob::patches_mut(profile, repository, &signer)?;
|
||||
let Ok(mut patch) = patches.get_mut(patch_id) else {
|
||||
anyhow::bail!("Patch `{patch_id}` not found");
|
||||
};
|
||||
|
|
@ -22,6 +22,6 @@ pub fn run(
|
|||
.chain(add.iter())
|
||||
.cloned()
|
||||
.collect::<Vec<_>>();
|
||||
patch.label(labels, &signer)?;
|
||||
patch.label(labels)?;
|
||||
Ok(())
|
||||
}
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@ pub fn run(
|
|||
profile: &Profile,
|
||||
) -> anyhow::Result<()> {
|
||||
let signer = term::signer(profile)?;
|
||||
let mut patches = profile.patches_mut(repo)?;
|
||||
let mut patches = crate::terminal::cob::patches_mut(profile, repo, &signer)?;
|
||||
let revision_id = revision_id.resolve::<cob::EntryId>(&repo.backend)?;
|
||||
let ByRevision {
|
||||
id: patch_id,
|
||||
|
|
@ -29,7 +29,7 @@ pub fn run(
|
|||
.find_by_revision(&patch::RevisionId::from(revision_id))?
|
||||
.ok_or_else(|| anyhow!("Patch revision `{revision_id}` not found"))?;
|
||||
let mut patch = patch::PatchMut::new(patch_id, patch, &mut patches);
|
||||
patch.react(revision_id, reaction, None, active, &signer)?;
|
||||
patch.react(revision_id, reaction, None, active)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,15 +10,10 @@ pub fn run(
|
|||
repository: &Repository,
|
||||
) -> anyhow::Result<bool> {
|
||||
let signer = term::signer(profile)?;
|
||||
let mut patches = term::cob::patches_mut(profile, repository)?;
|
||||
let mut patches = term::cob::patches_mut(profile, repository, &signer)?;
|
||||
let Ok(mut patch) = patches.get_mut(patch_id) else {
|
||||
anyhow::bail!("Patch `{patch_id}` not found");
|
||||
};
|
||||
|
||||
if undo {
|
||||
patch.unready(&signer)
|
||||
} else {
|
||||
patch.ready(&signer)
|
||||
}
|
||||
.map_err(anyhow::Error::from)
|
||||
if undo { patch.unready() } else { patch.ready() }.map_err(anyhow::Error::from)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ pub fn run(
|
|||
repository: &Repository,
|
||||
) -> anyhow::Result<()> {
|
||||
let signer = &term::signer(profile)?;
|
||||
let mut patches = term::cob::patches_mut(profile, repository)?;
|
||||
let mut patches = term::cob::patches_mut(profile, repository, signer)?;
|
||||
|
||||
let revision_id = revision_id.resolve::<Oid>(&repository.backend)?;
|
||||
let patch::ByRevision {
|
||||
|
|
@ -27,7 +27,7 @@ pub fn run(
|
|||
anyhow::bail!("Patch `{patch_id}` not found");
|
||||
};
|
||||
|
||||
patch.redact(revision_id, signer)?;
|
||||
patch.redact(revision_id)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
|
|
|||
|
|
@ -15,12 +15,12 @@ pub fn resolve(
|
|||
profile: &Profile,
|
||||
) -> anyhow::Result<()> {
|
||||
let signer = term::signer(profile)?;
|
||||
let mut patches = term::cob::patches_mut(profile, repo)?;
|
||||
let mut patches = term::cob::patches_mut(profile, repo, &signer)?;
|
||||
let patch = patches
|
||||
.get(&patch_id)?
|
||||
.ok_or_else(|| anyhow!("Patch `{patch_id}` not found"))?;
|
||||
let mut patch = patch::PatchMut::new(patch_id, patch, &mut patches);
|
||||
patch.resolve_review_comment(review, comment, &signer)?;
|
||||
patch.resolve_review_comment(review, comment)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
|
@ -32,11 +32,11 @@ pub fn unresolve(
|
|||
profile: &Profile,
|
||||
) -> anyhow::Result<()> {
|
||||
let signer = term::signer(profile)?;
|
||||
let mut patches = term::cob::patches_mut(profile, repo)?;
|
||||
let mut patches = term::cob::patches_mut(profile, repo, &signer)?;
|
||||
let patch = patches
|
||||
.get(&patch_id)?
|
||||
.ok_or_else(|| anyhow!("Patch `{patch_id}` not found"))?;
|
||||
let mut patch = patch::PatchMut::new(patch_id, patch, &mut patches);
|
||||
patch.unresolve_review_comment(review, comment, &signer)?;
|
||||
patch.unresolve_review_comment(review, comment)?;
|
||||
Ok(())
|
||||
}
|
||||
|
|
|
|||
|
|
@ -69,7 +69,7 @@ pub fn run(
|
|||
"couldn't load repository {} from local state",
|
||||
repository.id
|
||||
))?;
|
||||
let mut patches = term::cob::patches_mut(profile, repository)?;
|
||||
let mut patches = term::cob::patches_mut(profile, repository, &signer)?;
|
||||
let mut patch = patches
|
||||
.get_mut(&patch_id)
|
||||
.context(format!("couldn't find patch {patch_id} locally"))?;
|
||||
|
|
@ -111,7 +111,7 @@ pub fn run(
|
|||
} else {
|
||||
Some(message)
|
||||
};
|
||||
patch.review(revision_id, verdict, message, vec![], &signer)?;
|
||||
patch.review(revision_id, verdict, message, vec![])?;
|
||||
|
||||
match verdict {
|
||||
Some(Verdict::Accept) => {
|
||||
|
|
|
|||
|
|
@ -648,7 +648,7 @@ impl<'a> ReviewBuilder<'a> {
|
|||
};
|
||||
let diff = self.diff(&brain.accepted, &tree, repo, opts)?;
|
||||
let drafts = DraftStore::new(self.repo, *signer.public_key());
|
||||
let mut patches = cob::patch::Cache::no_cache(&drafts)?;
|
||||
let mut patches = cob::patch::Cache::no_cache(&drafts, signer)?;
|
||||
let mut patch = patches.get_mut(&patch_id)?;
|
||||
let mut queue = ReviewQueue::from(diff);
|
||||
|
||||
|
|
@ -668,7 +668,6 @@ impl<'a> ReviewBuilder<'a> {
|
|||
Some(Verdict::Reject),
|
||||
None,
|
||||
vec![],
|
||||
signer,
|
||||
)?
|
||||
};
|
||||
|
||||
|
|
@ -719,7 +718,7 @@ impl<'a> ReviewBuilder<'a> {
|
|||
let builder = CommentBuilder::new(revision.head(), path.to_path_buf());
|
||||
let comments = builder.edit(hunk)?;
|
||||
|
||||
patch.transaction("Review comments", signer, |tx| {
|
||||
patch.transaction("Review comments", |tx| {
|
||||
for comment in comments {
|
||||
tx.review_comment(
|
||||
review,
|
||||
|
|
|
|||
|
|
@ -20,7 +20,8 @@ pub fn run(
|
|||
let head_branch = try_branch(workdir.head()?)?;
|
||||
|
||||
let (_, target_oid) = get_merge_target(repository, &head_branch)?;
|
||||
let mut patches = term::cob::patches_mut(profile, repository)?;
|
||||
let signer = term::signer(profile)?;
|
||||
let mut patches = term::cob::patches_mut(profile, repository, &signer)?;
|
||||
let Ok(mut patch) = patches.get_mut(&patch_id) else {
|
||||
anyhow::bail!("Patch `{patch_id}` not found");
|
||||
};
|
||||
|
|
@ -45,8 +46,7 @@ pub fn run(
|
|||
|
||||
let (_, revision) = patch.latest();
|
||||
let message = term::patch::get_update_message(message, workdir, revision, &head_oid.into())?;
|
||||
let signer = term::signer(profile)?;
|
||||
let revision = patch.update(message, base_oid, head_oid, &signer)?;
|
||||
let revision = patch.update(message, base_oid, head_oid)?;
|
||||
|
||||
term::print(revision);
|
||||
|
||||
|
|
|
|||
|
|
@ -21,7 +21,8 @@ pub fn run(args: Args, ctx: impl term::Context) -> anyhow::Result<()> {
|
|||
};
|
||||
|
||||
let repo = profile.storage.repository_mut(rid)?;
|
||||
let mut identity = Identity::load_mut(&repo)?;
|
||||
let signer = profile.signer()?;
|
||||
let mut identity = Identity::load_mut(&repo, &signer)?;
|
||||
let doc = identity.doc();
|
||||
|
||||
if doc.is_public() {
|
||||
|
|
@ -51,12 +52,7 @@ pub fn run(args: Args, ctx: impl term::Context) -> anyhow::Result<()> {
|
|||
// SAFETY: the `Title` here is guaranteed to be nonempty and does not
|
||||
// contain `\n` or `\r`.
|
||||
#[allow(clippy::unwrap_used)]
|
||||
identity.update(
|
||||
cob::Title::new("Publish repository").unwrap(),
|
||||
"",
|
||||
&doc,
|
||||
&signer,
|
||||
)?;
|
||||
identity.update(cob::Title::new("Publish repository").unwrap(), "", &doc)?;
|
||||
repo.sign_refs(&signer)?;
|
||||
repo.set_identity_head()?;
|
||||
let validations = repo.validate()?;
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ use radicle::{
|
|||
cob::{
|
||||
self,
|
||||
cache::{MigrateCallback, MigrateProgress},
|
||||
store::access::{ReadOnly, WriteAs},
|
||||
},
|
||||
prelude::NodeId,
|
||||
profile,
|
||||
|
|
@ -58,47 +59,53 @@ pub mod migrate {
|
|||
}
|
||||
|
||||
/// Return a read-only handle for the patches cache.
|
||||
pub fn patches<'a, R>(
|
||||
pub fn patches<'a, Repo>(
|
||||
profile: &Profile,
|
||||
repository: &'a R,
|
||||
) -> Result<cob::patch::Cache<cob::patch::Patches<'a, R>, cob::cache::StoreReader>, anyhow::Error>
|
||||
repository: &'a Repo,
|
||||
) -> Result<cob::patch::Cache<'a, Repo, ReadOnly, cob::cache::StoreReader>, anyhow::Error>
|
||||
where
|
||||
R: ReadRepository + cob::Store<Namespace = NodeId>,
|
||||
Repo: ReadRepository + cob::Store<Namespace = NodeId>,
|
||||
{
|
||||
profile.patches(repository).map_err(with_hint)
|
||||
}
|
||||
|
||||
/// Return a read-write handle for the patches cache.
|
||||
pub fn patches_mut<'a, R>(
|
||||
/// Prefer this over [`radicle::profile::Home::patches_mut`],
|
||||
/// to obtain an error hint in case migrations must be run.
|
||||
pub fn patches_mut<'a, 'b, Repo, Signer>(
|
||||
profile: &Profile,
|
||||
repository: &'a R,
|
||||
) -> Result<cob::patch::Cache<cob::patch::Patches<'a, R>, cob::cache::StoreWriter>, anyhow::Error>
|
||||
repository: &'a Repo,
|
||||
signer: &'b Signer,
|
||||
) -> Result<cob::patch::Cache<'a, Repo, WriteAs<'b, Signer>, cob::cache::StoreWriter>, anyhow::Error>
|
||||
where
|
||||
R: ReadRepository + cob::Store<Namespace = NodeId>,
|
||||
Repo: ReadRepository + cob::Store<Namespace = NodeId>,
|
||||
{
|
||||
profile.patches_mut(repository).map_err(with_hint)
|
||||
profile.patches_mut(repository, signer).map_err(with_hint)
|
||||
}
|
||||
|
||||
/// Return a read-only handle for the issues cache.
|
||||
pub fn issues<'a, R>(
|
||||
pub fn issues<'a, Repo>(
|
||||
profile: &Profile,
|
||||
repository: &'a R,
|
||||
) -> Result<cob::issue::Cache<cob::issue::Issues<'a, R>, cob::cache::StoreReader>, anyhow::Error>
|
||||
repository: &'a Repo,
|
||||
) -> Result<cob::issue::Cache<'a, Repo, ReadOnly, cob::cache::StoreReader>, anyhow::Error>
|
||||
where
|
||||
R: ReadRepository + cob::Store<Namespace = NodeId>,
|
||||
Repo: ReadRepository + cob::Store<Namespace = NodeId>,
|
||||
{
|
||||
profile.issues(repository).map_err(with_hint)
|
||||
}
|
||||
|
||||
/// Return a read-write handle for the issues cache.
|
||||
pub fn issues_mut<'a, R>(
|
||||
/// Prefer this over [`radicle::profile::Home::issues_mut`],
|
||||
/// to obtain an error hint in case migrations must be run.
|
||||
pub fn issues_mut<'a, 'b, Repo, Signer>(
|
||||
profile: &Profile,
|
||||
repository: &'a R,
|
||||
) -> Result<cob::issue::Cache<cob::issue::Issues<'a, R>, cob::cache::StoreWriter>, anyhow::Error>
|
||||
repository: &'a Repo,
|
||||
signer: &'b Signer,
|
||||
) -> Result<cob::issue::Cache<'a, Repo, WriteAs<'b, Signer>, cob::cache::StoreWriter>, anyhow::Error>
|
||||
where
|
||||
R: ReadRepository + cob::Store<Namespace = NodeId>,
|
||||
Repo: ReadRepository + cob::Store<Namespace = NodeId>,
|
||||
{
|
||||
profile.issues_mut(repository).map_err(with_hint)
|
||||
profile.issues_mut(repository, signer).map_err(with_hint)
|
||||
}
|
||||
|
||||
/// Adds a hint to the COB out-of-date database error.
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ use std::path::Path;
|
|||
|
||||
use crate::util::environment::Environment;
|
||||
use crate::{program_reports_version, test};
|
||||
use radicle::cob::store::access::{ReadOnly, WriteAs};
|
||||
use radicle::node::Handle;
|
||||
use radicle::test::fixtures;
|
||||
|
||||
|
|
@ -135,7 +136,8 @@ fn test_cob_replication() {
|
|||
.unwrap();
|
||||
|
||||
let bob_repo = radicle::storage::ReadStorage::repository(&bob.storage, rid).unwrap();
|
||||
let mut bob_issues = radicle::cob::issue::Issues::open(&bob_repo).unwrap();
|
||||
let mut bob_issues =
|
||||
radicle::cob::issue::Issues::open(&bob_repo, WriteAs::new(&bob.signer)).unwrap();
|
||||
let mut bob_cache = radicle::cob::cache::InMemory::default();
|
||||
let issue = bob_issues
|
||||
.create(
|
||||
|
|
@ -145,7 +147,6 @@ fn test_cob_replication() {
|
|||
&[],
|
||||
[],
|
||||
&mut bob_cache,
|
||||
&bob.signer,
|
||||
)
|
||||
.unwrap();
|
||||
log::debug!(target: "test", "Issue {} created", issue.id());
|
||||
|
|
@ -163,7 +164,7 @@ fn test_cob_replication() {
|
|||
.unwrap();
|
||||
|
||||
let alice_repo = radicle::storage::ReadStorage::repository(&alice.storage, rid).unwrap();
|
||||
let alice_issues = radicle::cob::issue::Issues::open(&alice_repo).unwrap();
|
||||
let alice_issues = radicle::cob::issue::Issues::open(&alice_repo, ReadOnly).unwrap();
|
||||
let alice_issue = alice_issues.get(issue.id()).unwrap().unwrap();
|
||||
|
||||
assert_eq!(alice_issue.title(), "Something's fishy");
|
||||
|
|
@ -192,7 +193,8 @@ fn test_cob_deletion() {
|
|||
bob.routes_to(&[(rid, alice.id)]);
|
||||
|
||||
let alice_repo = radicle::storage::ReadStorage::repository(&alice.storage, rid).unwrap();
|
||||
let mut alice_issues = radicle::cob::issue::Cache::no_cache(&alice_repo).unwrap();
|
||||
let mut alice_issues =
|
||||
radicle::cob::issue::Cache::no_cache(&alice_repo, &alice.signer).unwrap();
|
||||
let issue = alice_issues
|
||||
.create(
|
||||
radicle::cob::Title::new("Something's fishy").unwrap(),
|
||||
|
|
@ -200,7 +202,6 @@ fn test_cob_deletion() {
|
|||
&[],
|
||||
&[],
|
||||
[],
|
||||
&alice.signer,
|
||||
)
|
||||
.unwrap();
|
||||
let issue_id = issue.id();
|
||||
|
|
@ -210,11 +211,12 @@ fn test_cob_deletion() {
|
|||
.unwrap();
|
||||
|
||||
let bob_repo = radicle::storage::ReadStorage::repository(&bob.storage, rid).unwrap();
|
||||
let bob_issues = radicle::cob::issue::Issues::open(&bob_repo).unwrap();
|
||||
let bob_issues = radicle::cob::issue::Issues::open(&bob_repo, ReadOnly).unwrap();
|
||||
assert!(bob_issues.get(issue_id).unwrap().is_some());
|
||||
|
||||
let mut alice_issues = radicle::cob::issue::Cache::no_cache(&alice_repo).unwrap();
|
||||
alice_issues.remove(issue_id, &alice.signer).unwrap();
|
||||
let mut alice_issues =
|
||||
radicle::cob::issue::Cache::no_cache(&alice_repo, &alice.signer).unwrap();
|
||||
alice_issues.remove(issue_id).unwrap();
|
||||
|
||||
log::debug!(target: "test", "Removing issue..");
|
||||
|
||||
|
|
@ -225,6 +227,6 @@ fn test_cob_deletion() {
|
|||
radicle::node::FetchResult::Success { .. }
|
||||
);
|
||||
let bob_repo = radicle::storage::ReadStorage::repository(&bob.storage, rid).unwrap();
|
||||
let bob_issues = radicle::cob::issue::Issues::open(&bob_repo).unwrap();
|
||||
let bob_issues = radicle::cob::issue::Issues::open(&bob_repo, ReadOnly).unwrap();
|
||||
assert!(bob_issues.get(issue_id).unwrap().is_none());
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,6 +11,10 @@ use std::{
|
|||
|
||||
use crossbeam_channel as chan;
|
||||
|
||||
use crate::node::NodeId;
|
||||
use crate::node::device::Device;
|
||||
use crate::storage::git::transport;
|
||||
use crate::{Runtime, runtime, runtime::Handle, service};
|
||||
use radicle::Storage;
|
||||
use radicle::cob;
|
||||
use radicle::cob::issue;
|
||||
|
|
@ -34,11 +38,6 @@ use radicle::rad;
|
|||
use radicle::storage::{ReadStorage as _, RemoteRepository as _, SignRepository as _};
|
||||
use radicle::test::fixtures;
|
||||
|
||||
use crate::node::NodeId;
|
||||
use crate::node::device::Device;
|
||||
use crate::storage::git::transport;
|
||||
use crate::{Runtime, runtime, runtime::Handle, service};
|
||||
|
||||
/// A node that can be run.
|
||||
pub struct Node<G> {
|
||||
pub id: NodeId,
|
||||
|
|
@ -360,13 +359,10 @@ impl<G: Signer<Signature> + cyphernet::Ecdh> NodeHandle<G> {
|
|||
}
|
||||
|
||||
/// Create an [`issue::Issue`] in the `NodeHandle`'s storage.
|
||||
pub fn issue(&self, rid: RepoId, title: cob::Title, desc: &str) -> cob::ObjectId {
|
||||
pub fn issue(&mut self, rid: RepoId, title: cob::Title, desc: &str) -> cob::ObjectId {
|
||||
let repo = self.storage.repository(rid).unwrap();
|
||||
let mut issues = issue::Cache::no_cache(&repo).unwrap();
|
||||
*issues
|
||||
.create(title, desc, &[], &[], [], &self.signer)
|
||||
.unwrap()
|
||||
.id()
|
||||
let mut issues = issue::Cache::no_cache(&repo, &self.signer).unwrap();
|
||||
*issues.create(title, desc, &[], &[], []).unwrap().id()
|
||||
}
|
||||
|
||||
/// Perform a commit to `refname` by generating a blob of random data to a
|
||||
|
|
|
|||
|
|
@ -134,7 +134,6 @@ impl Default for Config<MockSigner> {
|
|||
impl<G: crypto::signature::Signer<crypto::Signature>> Peer<Storage, G> {
|
||||
pub fn project(&mut self, name: &str, description: &str) -> RepoId {
|
||||
radicle::storage::git::transport::local::register(self.storage().clone());
|
||||
|
||||
let (repo, _) = fixtures::repository(self.tempdir.path().join(name));
|
||||
let (rid, _, _) = rad::init(
|
||||
&repo,
|
||||
|
|
|
|||
|
|
@ -1021,7 +1021,7 @@ fn test_refs_announcement_offline() {
|
|||
// Create an issue without telling the node.
|
||||
let repo = alice.storage().repository(rid).unwrap();
|
||||
let old_refs = RefsAt::new(&repo, alice.id).unwrap();
|
||||
let mut issues = radicle::issue::Cache::no_cache(&repo).unwrap();
|
||||
let mut issues = radicle::issue::Cache::no_cache(&repo, alice.signer()).unwrap();
|
||||
issues
|
||||
.create(
|
||||
cob::Title::new("Issue while offline!").unwrap(),
|
||||
|
|
@ -1029,7 +1029,6 @@ fn test_refs_announcement_offline() {
|
|||
&[],
|
||||
&[],
|
||||
[],
|
||||
alice.signer(),
|
||||
)
|
||||
.unwrap();
|
||||
let new_refs = RefsAt::new(&repo, alice.id).unwrap();
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ use std::{collections::HashSet, thread, time};
|
|||
|
||||
use radicle::cob;
|
||||
use radicle::cob::Title;
|
||||
use radicle::cob::store::access::{ReadOnly, WriteAs};
|
||||
use radicle_crypto::test::signer::MockSigner;
|
||||
use test_log::test;
|
||||
|
||||
|
|
@ -514,6 +515,7 @@ fn test_missing_remote() {
|
|||
.unwrap();
|
||||
assert!(result.is_success());
|
||||
log::debug!(target: "test", "Fetch complete with {}", bob.id);
|
||||
|
||||
rad::fork_remote(acme, &alice.id, &carol, &bob.storage).unwrap();
|
||||
|
||||
alice.issue(
|
||||
|
|
@ -935,7 +937,7 @@ fn test_non_fastforward_sigrefs() {
|
|||
let rid = bob.project("acme", "");
|
||||
|
||||
let mut alice = alice.spawn();
|
||||
let bob = bob.spawn();
|
||||
let mut bob = bob.spawn();
|
||||
let mut eve = eve.spawn();
|
||||
|
||||
alice.handle.seed(rid, Scope::All).unwrap();
|
||||
|
|
@ -1114,7 +1116,7 @@ fn test_outdated_sigrefs() {
|
|||
FetchResult::Success { .. }
|
||||
);
|
||||
let repo = alice.storage.repository(rid).unwrap();
|
||||
let issues = issue::Issues::open(&repo).unwrap();
|
||||
let issues = issue::Issues::open(&repo, WriteAs::new(&alice.signer)).unwrap();
|
||||
assert!(
|
||||
issues.get(&issue_id).unwrap().is_some(),
|
||||
"Alice did not fetch issue {issue_id}"
|
||||
|
|
@ -1348,12 +1350,14 @@ fn missing_delegate_default_branch() {
|
|||
.unwrap();
|
||||
assert!(bob.storage.contains(&rid).unwrap());
|
||||
|
||||
let bob_key = *bob.signer.public_key();
|
||||
|
||||
// Helper to assert that Bob's default branch is not in storage
|
||||
let assert_bobs_default_is_missing = |repo: &Repository| {
|
||||
let doc = repo.identity_doc().unwrap();
|
||||
let project = doc.project().unwrap();
|
||||
let default_branch = repo.reference(
|
||||
bob.signer.public_key(),
|
||||
&bob_key,
|
||||
&radicle::git::refs::branch(project.default_branch()),
|
||||
);
|
||||
assert!(matches!(
|
||||
|
|
@ -1365,7 +1369,7 @@ fn missing_delegate_default_branch() {
|
|||
// Add Bob as a delegate to the identity document
|
||||
{
|
||||
let repo = alice.storage.repository(rid).unwrap();
|
||||
let mut identity = Identity::load_mut(&repo).unwrap();
|
||||
let mut identity = Identity::load_mut(&repo, &alice.signer).unwrap();
|
||||
let doc = repo
|
||||
.identity_doc()
|
||||
.unwrap()
|
||||
|
|
@ -1375,7 +1379,7 @@ fn missing_delegate_default_branch() {
|
|||
})
|
||||
.unwrap();
|
||||
let rev = identity
|
||||
.update(Title::new("Add Bob").unwrap(), "", &doc, &alice.signer)
|
||||
.update(Title::new("Add Bob").unwrap(), "", &doc)
|
||||
.unwrap();
|
||||
repo.set_identity_head_to(rev).unwrap();
|
||||
|
||||
|
|
@ -1735,6 +1739,7 @@ fn test_non_fastforward_identity_doc() {
|
|||
.handle
|
||||
.fetch(rid, alice.id, DEFAULT_TIMEOUT, None)
|
||||
.unwrap();
|
||||
|
||||
// Alice pushes new references to her laptop
|
||||
let issue = alice_laptop.issue(
|
||||
rid,
|
||||
|
|
@ -1752,7 +1757,7 @@ fn test_non_fastforward_identity_doc() {
|
|||
// Alice updates the identity of the document to include her laptop
|
||||
let (prev, next) = {
|
||||
let repo = alice.storage.repository(rid).unwrap();
|
||||
let mut identity = Identity::load_mut(&repo).unwrap();
|
||||
let mut identity = Identity::load_mut(&repo, &alice.signer).unwrap();
|
||||
let prev = identity.current;
|
||||
let doc = repo
|
||||
.identity_doc()
|
||||
|
|
@ -1761,7 +1766,7 @@ fn test_non_fastforward_identity_doc() {
|
|||
.with_edits(|raw| raw.delegate(alice_laptop.id.into()))
|
||||
.unwrap();
|
||||
let rev = identity
|
||||
.update(Title::new("Add Laptop").unwrap(), "", &doc, &alice.signer)
|
||||
.update(Title::new("Add Laptop").unwrap(), "", &doc)
|
||||
.unwrap();
|
||||
repo.set_identity_head_to(rev).unwrap();
|
||||
(prev, rev)
|
||||
|
|
@ -1780,7 +1785,7 @@ fn test_non_fastforward_identity_doc() {
|
|||
assert!(matches!(result, FetchResult::Success { .. }));
|
||||
assert!(!has_issue(&bob, &issue));
|
||||
let repo = bob.storage.repository(rid).unwrap();
|
||||
let identity = Identity::load_mut(&repo).unwrap();
|
||||
let identity = Identity::load_mut(&repo, &bob.signer).unwrap();
|
||||
assert_eq!(identity.current, next);
|
||||
assert_eq!(identity.parent, Some(prev));
|
||||
|
||||
|
|
@ -1793,7 +1798,7 @@ fn test_non_fastforward_identity_doc() {
|
|||
assert!(matches!(result, FetchResult::Success { .. }));
|
||||
assert!(has_issue(&bob, &issue));
|
||||
let repo = bob.storage.repository(rid).unwrap();
|
||||
let identity = Identity::load_mut(&repo).unwrap();
|
||||
let identity = Identity::load_mut(&repo, &bob.signer).unwrap();
|
||||
assert_eq!(identity.current, next);
|
||||
assert_eq!(identity.parent, Some(prev));
|
||||
}
|
||||
|
|
@ -1917,7 +1922,7 @@ fn fetch_does_not_contain_rad_sigrefs_parent() {
|
|||
FetchResult::Success { .. }
|
||||
);
|
||||
let repo = bob.storage.repository(rid).unwrap();
|
||||
let issues = issue::Issues::open(&repo).unwrap();
|
||||
let issues = issue::Issues::open(&repo, ReadOnly).unwrap();
|
||||
assert!(
|
||||
issues.get(&issue_id).unwrap().is_some(),
|
||||
"Bob did not fetch issue {issue_id}"
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
use radicle::cob::store::access::ReadOnly;
|
||||
use radicle::identity::CanonicalRefs;
|
||||
use radicle::identity::doc::CanonicalRefsError;
|
||||
use radicle::storage::git::TempRepository;
|
||||
|
|
@ -272,8 +273,8 @@ where
|
|||
C: cob::cache::Update<cob::issue::Issue> + cob::cache::Update<cob::patch::Patch>,
|
||||
C: cob::cache::Remove<cob::issue::Issue> + cob::cache::Remove<cob::patch::Patch>,
|
||||
{
|
||||
let mut issues = cob::store::Store::<cob::issue::Issue, _>::open(storage)?;
|
||||
let mut patches = cob::store::Store::<cob::patch::Patch, _>::open(storage)?;
|
||||
let mut issues = cob::store::Store::<cob::issue::Issue, _, _>::open(storage, ReadOnly)?;
|
||||
let mut patches = cob::store::Store::<cob::patch::Patch, _, _>::open(storage, ReadOnly)?;
|
||||
|
||||
for update in refs {
|
||||
match update {
|
||||
|
|
@ -303,15 +304,15 @@ where
|
|||
}
|
||||
|
||||
/// Update or remove a cache entry.
|
||||
fn update_or_remove<R, C, T>(
|
||||
store: &mut cob::store::Store<T, R>,
|
||||
fn update_or_remove<T, Repo, C>(
|
||||
store: &mut cob::store::Store<T, Repo, ReadOnly>,
|
||||
cache: &mut C,
|
||||
rid: &RepoId,
|
||||
tid: TypedId,
|
||||
) -> Result<(), error::Cache>
|
||||
where
|
||||
R: cob::Store + ReadRepository,
|
||||
T: cob::Evaluate<R> + cob::store::Cob + cob::store::CobWithType,
|
||||
T: cob::Evaluate<Repo> + cob::store::Cob + cob::store::CobWithType,
|
||||
Repo: cob::Store<Namespace = NodeId> + ReadRepository,
|
||||
C: cob::cache::Update<T> + cob::cache::Remove<T>,
|
||||
{
|
||||
match store.get(&tid.id) {
|
||||
|
|
|
|||
|
|
@ -28,6 +28,7 @@ use std::process;
|
|||
use std::str::FromStr;
|
||||
use std::{env, fmt};
|
||||
|
||||
use radicle::cob::store::access::{ReadOnly, WriteAs};
|
||||
use thiserror::Error;
|
||||
|
||||
use radicle::prelude::NodeId;
|
||||
|
|
@ -458,10 +459,10 @@ pub(crate) fn warn(s: impl fmt::Display) {
|
|||
}
|
||||
|
||||
/// Get the patch store.
|
||||
pub(crate) fn patches<'a, R: ReadRepository + cob::Store<Namespace = NodeId>>(
|
||||
pub(crate) fn patches<'a, Repo: ReadRepository + cob::Store<Namespace = NodeId>>(
|
||||
profile: &Profile,
|
||||
repo: &'a R,
|
||||
) -> Result<cob::patch::Cache<cob::patch::Patches<'a, R>, cob::cache::StoreReader>, list::Error> {
|
||||
repo: &'a Repo,
|
||||
) -> Result<cob::patch::Cache<'a, Repo, ReadOnly, cob::cache::StoreReader>, list::Error> {
|
||||
match profile.patches(repo) {
|
||||
Ok(patches) => Ok(patches),
|
||||
Err(err @ profile::Error::CobsCache(cob::cache::Error::OutOfDate)) => {
|
||||
|
|
@ -473,14 +474,17 @@ pub(crate) fn patches<'a, R: ReadRepository + cob::Store<Namespace = NodeId>>(
|
|||
}
|
||||
|
||||
/// Get the mutable patch store.
|
||||
pub(crate) fn patches_mut<'a>(
|
||||
pub(crate) fn patches_mut<'a, 'b, Signer>(
|
||||
profile: &Profile,
|
||||
repo: &'a storage::git::Repository,
|
||||
signer: &'b Signer,
|
||||
) -> Result<
|
||||
cob::patch::Cache<cob::patch::Patches<'a, storage::git::Repository>, cob::cache::StoreWriter>,
|
||||
cob::patch::Cache<'a, storage::git::Repository, WriteAs<'b, Signer>, cob::cache::StoreWriter>,
|
||||
push::Error,
|
||||
> {
|
||||
match profile.patches_mut(repo) {
|
||||
>
|
||||
where
|
||||
{
|
||||
match profile.patches_mut(repo, signer) {
|
||||
Ok(patches) => Ok(patches),
|
||||
Err(err @ profile::Error::CobsCache(cob::cache::Error::OutOfDate)) => {
|
||||
hint(cli::cob::MIGRATION_HINT);
|
||||
|
|
|
|||
|
|
@ -8,9 +8,9 @@ use std::process::ExitStatus;
|
|||
use std::str::FromStr;
|
||||
use std::{assert_eq, io};
|
||||
|
||||
use radicle::cob::store::access::WriteAs;
|
||||
use radicle::identity::crefs::GetCanonicalRefs as _;
|
||||
use radicle::identity::doc::CanonicalRefsError;
|
||||
use radicle::node::device::Device;
|
||||
use thiserror::Error;
|
||||
|
||||
use radicle::Profile;
|
||||
|
|
@ -321,7 +321,8 @@ pub(super) fn run(
|
|||
.map_err(Error::from)
|
||||
}
|
||||
Command::Push(git::fmt::refspec::Refspec { src, dst, force }) => {
|
||||
let patches = crate::patches_mut(profile, stored)?;
|
||||
let signer = profile.signer()?;
|
||||
let patches = crate::patches_mut(profile, stored, &signer)?;
|
||||
let action = PushAction::new(dst)?;
|
||||
|
||||
match action {
|
||||
|
|
@ -332,7 +333,6 @@ pub(super) fn run(
|
|||
&working,
|
||||
stored,
|
||||
patches,
|
||||
&signer,
|
||||
profile,
|
||||
opts.clone(),
|
||||
git,
|
||||
|
|
@ -560,24 +560,28 @@ impl<'a, G> Drop for TempPatchRef<'a, G> {
|
|||
}
|
||||
|
||||
/// Open a new patch.
|
||||
fn patch_open<G, S>(
|
||||
fn patch_open<S, Signer>(
|
||||
head: &git::Oid,
|
||||
upstream: &Option<git::fmt::RefString>,
|
||||
nid: &NodeId,
|
||||
working: &git::raw::Repository,
|
||||
stored: &storage::git::Repository,
|
||||
mut patches: patch::Cache<
|
||||
patch::Patches<'_, storage::git::Repository>,
|
||||
'_,
|
||||
storage::git::Repository,
|
||||
WriteAs<'_, Signer>,
|
||||
cob::cache::StoreWriter,
|
||||
>,
|
||||
signer: &Device<G>,
|
||||
profile: &Profile,
|
||||
opts: Options,
|
||||
git: &S,
|
||||
) -> Result<Option<ExplorerResource>, Error>
|
||||
where
|
||||
G: crypto::signature::Signer<crypto::Signature>,
|
||||
S: GitService,
|
||||
Signer: crypto::signature::Keypair<VerifyingKey = crypto::PublicKey>,
|
||||
Signer: crypto::signature::Signer<crypto::Signature>,
|
||||
Signer: crypto::signature::Signer<crypto::ssh::ExtendedSignature>,
|
||||
Signer: crypto::signature::Verifier<crypto::Signature>,
|
||||
{
|
||||
let temp = TempPatchRef::new(stored, head, nid, git);
|
||||
temp.push(head, opts.verbosity)?;
|
||||
|
|
@ -601,7 +605,6 @@ where
|
|||
base,
|
||||
*head,
|
||||
&[],
|
||||
signer,
|
||||
)
|
||||
} else {
|
||||
patches.create(
|
||||
|
|
@ -611,7 +614,6 @@ where
|
|||
base,
|
||||
*head,
|
||||
&[],
|
||||
signer,
|
||||
)
|
||||
}?;
|
||||
|
||||
|
|
@ -698,7 +700,7 @@ where
|
|||
|
||||
/// Update an existing patch.
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
fn patch_update<G, S>(
|
||||
fn patch_update<S, Signer>(
|
||||
head: &git::Oid,
|
||||
dst: &git::fmt::Qualified,
|
||||
force: bool,
|
||||
|
|
@ -707,17 +709,22 @@ fn patch_update<G, S>(
|
|||
working: &git::raw::Repository,
|
||||
stored: &storage::git::Repository,
|
||||
mut patches: patch::Cache<
|
||||
patch::Patches<'_, storage::git::Repository>,
|
||||
'_,
|
||||
storage::git::Repository,
|
||||
WriteAs<'_, Signer>,
|
||||
cob::cache::StoreWriter,
|
||||
>,
|
||||
signer: &Device<G>,
|
||||
signer: &Signer,
|
||||
opts: Options,
|
||||
expected_refs: &[String],
|
||||
git: &S,
|
||||
) -> Result<Option<ExplorerResource>, Error>
|
||||
where
|
||||
G: crypto::signature::Signer<crypto::Signature>,
|
||||
S: GitService,
|
||||
Signer: crypto::signature::Keypair<VerifyingKey = crypto::PublicKey>,
|
||||
Signer: crypto::signature::Signer<crypto::Signature>,
|
||||
Signer: crypto::signature::Signer<crypto::ssh::ExtendedSignature>,
|
||||
Signer: crypto::signature::Verifier<crypto::Signature>,
|
||||
{
|
||||
let Ok(Some(patch)) = patches.get(&patch_id) else {
|
||||
return Err(Error::NotFound(patch_id));
|
||||
|
|
@ -754,7 +761,7 @@ where
|
|||
)?;
|
||||
|
||||
let mut patch_mut = patch::PatchMut::new(patch_id, patch, &mut patches);
|
||||
let revision = patch_mut.update(message, base, *head, signer)?;
|
||||
let revision = patch_mut.update(message, base, *head)?;
|
||||
let Some(revision) = patch_mut.revision(&revision).cloned() else {
|
||||
return Err(Error::RevisionNotFound(revision));
|
||||
};
|
||||
|
|
@ -787,7 +794,7 @@ where
|
|||
Ok(Some(ExplorerResource::Patch { id: patch_id }))
|
||||
}
|
||||
|
||||
fn push<G, S>(
|
||||
fn push<S, Signer>(
|
||||
src: &git::Oid,
|
||||
dst: &git::fmt::Qualified,
|
||||
force: bool,
|
||||
|
|
@ -795,17 +802,22 @@ fn push<G, S>(
|
|||
working: &git::raw::Repository,
|
||||
stored: &storage::git::Repository,
|
||||
mut patches: patch::Cache<
|
||||
patch::Patches<'_, storage::git::Repository>,
|
||||
'_,
|
||||
storage::git::Repository,
|
||||
WriteAs<'_, Signer>,
|
||||
cob::cache::StoreWriter,
|
||||
>,
|
||||
signer: &Device<G>,
|
||||
signer: &Signer,
|
||||
verbosity: Verbosity,
|
||||
expected_refs: &[String],
|
||||
git: &S,
|
||||
) -> Result<Option<ExplorerResource>, Error>
|
||||
where
|
||||
G: crypto::signature::Signer<crypto::Signature>,
|
||||
S: GitService,
|
||||
Signer: crypto::signature::Keypair<VerifyingKey = crypto::PublicKey>,
|
||||
Signer: crypto::signature::Signer<crypto::Signature>,
|
||||
Signer: crypto::signature::Signer<crypto::ssh::ExtendedSignature>,
|
||||
Signer: crypto::signature::Verifier<crypto::Signature>,
|
||||
{
|
||||
let head = *src;
|
||||
let dst = dst.with_namespace(nid.into());
|
||||
|
|
@ -832,7 +844,7 @@ where
|
|||
let old = old.peel_to_commit()?.id();
|
||||
// Only delegates affect the merge state of the COB.
|
||||
if stored.delegates()?.contains(&nid.into()) {
|
||||
patch_revert_all(old.into(), head, &stored.backend, &mut patches, signer)?;
|
||||
patch_revert_all(old.into(), head, &stored.backend, &mut patches)?;
|
||||
patch_merge_all(old.into(), head, working, &mut patches, signer)?;
|
||||
}
|
||||
}
|
||||
|
|
@ -841,18 +853,20 @@ where
|
|||
}
|
||||
|
||||
/// Revert all patches that are no longer included in the base branch.
|
||||
fn patch_revert_all<G>(
|
||||
fn patch_revert_all<Signer>(
|
||||
old: git::Oid,
|
||||
new: git::Oid,
|
||||
stored: &git::raw::Repository,
|
||||
patches: &mut patch::Cache<
|
||||
patch::Patches<'_, storage::git::Repository>,
|
||||
'_,
|
||||
storage::git::Repository,
|
||||
WriteAs<'_, Signer>,
|
||||
cob::cache::StoreWriter,
|
||||
>,
|
||||
_signer: &Device<G>,
|
||||
) -> Result<(), Error>
|
||||
where
|
||||
G: crypto::signature::Signer<crypto::Signature>,
|
||||
Signer: crypto::signature::Keypair<VerifyingKey = crypto::PublicKey>,
|
||||
Signer: crypto::signature::Signer<crypto::Signature>,
|
||||
{
|
||||
// Find all commits reachable from the old OID but not from the new OID.
|
||||
let mut revwalk = stored.revwalk()?;
|
||||
|
|
@ -906,18 +920,23 @@ where
|
|||
}
|
||||
|
||||
/// Merge all patches that have been included in the base branch.
|
||||
fn patch_merge_all<G>(
|
||||
fn patch_merge_all<Signer>(
|
||||
old: git::Oid,
|
||||
new: git::Oid,
|
||||
working: &git::raw::Repository,
|
||||
patches: &mut patch::Cache<
|
||||
patch::Patches<'_, storage::git::Repository>,
|
||||
'_,
|
||||
storage::git::Repository,
|
||||
WriteAs<'_, Signer>,
|
||||
cob::cache::StoreWriter,
|
||||
>,
|
||||
signer: &Device<G>,
|
||||
signer: &Signer,
|
||||
) -> Result<(), Error>
|
||||
where
|
||||
G: crypto::signature::Signer<crypto::Signature>,
|
||||
Signer: crypto::signature::Keypair<VerifyingKey = crypto::PublicKey>,
|
||||
Signer: crypto::signature::Signer<crypto::Signature>,
|
||||
Signer: crypto::signature::Signer<crypto::ssh::ExtendedSignature>,
|
||||
Signer: crypto::signature::Verifier<crypto::Signature>,
|
||||
{
|
||||
let mut revwalk = working.revwalk()?;
|
||||
revwalk.push_range(&format!("{old}..{new}"))?;
|
||||
|
|
@ -959,19 +978,22 @@ where
|
|||
Ok(())
|
||||
}
|
||||
|
||||
fn patch_merge<C, G>(
|
||||
mut patch: patch::PatchMut<storage::git::Repository, C>,
|
||||
fn patch_merge<Signer, C>(
|
||||
mut patch: patch::PatchMut<'_, '_, '_, storage::git::Repository, Signer, C>,
|
||||
revision: patch::RevisionId,
|
||||
commit: git::Oid,
|
||||
working: &git::raw::Repository,
|
||||
signer: &Device<G>,
|
||||
signer: &Signer,
|
||||
) -> Result<(), Error>
|
||||
where
|
||||
Signer: crypto::signature::Keypair<VerifyingKey = crypto::PublicKey>,
|
||||
Signer: crypto::signature::Signer<crypto::Signature>,
|
||||
Signer: crypto::signature::Signer<crypto::ssh::ExtendedSignature>,
|
||||
Signer: crypto::signature::Verifier<crypto::Signature>,
|
||||
C: cob::cache::Update<patch::Patch>,
|
||||
G: crypto::signature::Signer<crypto::Signature>,
|
||||
{
|
||||
let (latest, _) = patch.latest();
|
||||
let merged = patch.merge(revision, commit, signer)?;
|
||||
let merged = patch.merge(revision, commit)?;
|
||||
|
||||
if revision == latest {
|
||||
eprintln!(
|
||||
|
|
|
|||
|
|
@ -112,9 +112,10 @@ pub enum Error {
|
|||
OutOfDate,
|
||||
}
|
||||
|
||||
/// Read and write to the store.
|
||||
/// Read from and write to the store.
|
||||
pub type StoreWriter = Store<Write>;
|
||||
/// Write to the store.
|
||||
|
||||
/// Read from the store.
|
||||
pub type StoreReader = Store<Read>;
|
||||
|
||||
/// Read-only type witness.
|
||||
|
|
|
|||
|
|
@ -3,15 +3,16 @@ use std::sync::LazyLock;
|
|||
use std::{fmt, ops::Deref, str::FromStr};
|
||||
|
||||
use crypto::{PublicKey, Signature};
|
||||
use nonempty::NonEmpty;
|
||||
use radicle_cob::{Embed, ObjectId, TypeName};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use thiserror::Error;
|
||||
|
||||
use crate::cob::store::access::WriteAs;
|
||||
use crate::git;
|
||||
use crate::git::Oid;
|
||||
use crate::identity::doc::Doc;
|
||||
use crate::node::NodeId;
|
||||
use crate::node::device::Device;
|
||||
use crate::storage;
|
||||
use crate::{
|
||||
cob,
|
||||
|
|
@ -219,33 +220,34 @@ impl Identity {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn initialize<'a, R, G>(
|
||||
pub fn initialize<'a, 'b, Repo, Signer>(
|
||||
doc: &Doc,
|
||||
store: &'a R,
|
||||
signer: &Device<G>,
|
||||
) -> Result<IdentityMut<'a, R>, cob::store::Error>
|
||||
store: &'a Repo,
|
||||
signer: &'b Signer,
|
||||
) -> Result<IdentityMut<'a, 'b, Repo, Signer>, cob::store::Error>
|
||||
where
|
||||
G: crypto::signature::Signer<crypto::Signature>,
|
||||
R: WriteRepository + cob::Store<Namespace = NodeId>,
|
||||
Repo: WriteRepository + cob::Store<Namespace = NodeId>,
|
||||
Signer: crypto::signature::Keypair<VerifyingKey = crypto::PublicKey>,
|
||||
Signer: crypto::signature::Signer<crypto::Signature>,
|
||||
Signer: crypto::signature::Signer<crypto::ssh::ExtendedSignature>,
|
||||
Signer: crypto::signature::Verifier<crypto::Signature>,
|
||||
{
|
||||
let mut store = cob::store::Store::open(store)?;
|
||||
let (id, identity) = Transaction::<Identity, _>::initial(
|
||||
"Initialize identity",
|
||||
&mut store,
|
||||
signer,
|
||||
|tx, repo| {
|
||||
tx.revision(
|
||||
// SAFETY: "Initial revision" is a valid title
|
||||
let mut store = cob::store::Store::open(store, WriteAs::new(signer))?;
|
||||
|
||||
#[allow(clippy::unwrap_used)]
|
||||
cob::Title::new("Initial revision").unwrap(),
|
||||
"",
|
||||
doc,
|
||||
None,
|
||||
repo,
|
||||
signer,
|
||||
)
|
||||
},
|
||||
)?;
|
||||
let title = cob::Title::new("Initial revision").unwrap();
|
||||
|
||||
#[allow(deprecated)]
|
||||
let (actions, embeds) = {
|
||||
let repo = store.repo();
|
||||
let signer = store.signer();
|
||||
Transaction::new_revision(title, "", doc, None, repo, signer)?.into_inner()
|
||||
};
|
||||
|
||||
let actions = NonEmpty::from_vec(actions)
|
||||
.expect("Transaction::initial: transaction must contain at least one action");
|
||||
|
||||
let (id, identity) = store.create("Initialize identity", actions, embeds)?;
|
||||
|
||||
Ok(IdentityMut {
|
||||
id,
|
||||
|
|
@ -254,10 +256,10 @@ impl Identity {
|
|||
})
|
||||
}
|
||||
|
||||
pub fn get<R: ReadRepository + cob::Store>(
|
||||
object: &ObjectId,
|
||||
repo: &R,
|
||||
) -> Result<Identity, store::Error> {
|
||||
pub fn get<Repo>(object: &ObjectId, repo: &Repo) -> Result<Identity, store::Error>
|
||||
where
|
||||
Repo: ReadRepository + cob::Store,
|
||||
{
|
||||
use cob::store::CobWithType;
|
||||
|
||||
cob::get::<Self, _>(repo, Self::type_name(), object)
|
||||
|
|
@ -266,12 +268,17 @@ impl Identity {
|
|||
}
|
||||
|
||||
/// Get a proposal mutably.
|
||||
pub fn get_mut<'a, R: WriteRepository + cob::Store<Namespace = NodeId>>(
|
||||
pub fn get_mut<'a, 'b, Repo, Signer>(
|
||||
id: &ObjectId,
|
||||
repo: &'a R,
|
||||
) -> Result<IdentityMut<'a, R>, store::Error> {
|
||||
repo: &'a Repo,
|
||||
signer: &'b Signer,
|
||||
) -> Result<IdentityMut<'a, 'b, Repo, Signer>, store::Error>
|
||||
where
|
||||
Repo: WriteRepository + cob::Store<Namespace = NodeId>,
|
||||
Signer: crypto::signature::Signer<crypto::Signature>,
|
||||
{
|
||||
let obj = Self::get(id, repo)?;
|
||||
let store = cob::store::Store::open(repo)?;
|
||||
let store = cob::store::Store::open(repo, WriteAs::new(signer))?;
|
||||
|
||||
Ok(IdentityMut {
|
||||
id: *id,
|
||||
|
|
@ -287,13 +294,18 @@ impl Identity {
|
|||
Self::get(&oid, repo).map_err(RepositoryError::from)
|
||||
}
|
||||
|
||||
pub fn load_mut<R: WriteRepository + cob::Store<Namespace = NodeId>>(
|
||||
repo: &R,
|
||||
) -> Result<IdentityMut<'_, R>, RepositoryError> {
|
||||
pub fn load_mut<'a, 'b, Repo, Signer>(
|
||||
repo: &'a Repo,
|
||||
signer: &'b Signer,
|
||||
) -> Result<IdentityMut<'a, 'b, Repo, Signer>, RepositoryError>
|
||||
where
|
||||
Repo: WriteRepository + cob::Store<Namespace = NodeId>,
|
||||
Signer: crypto::signature::Signer<crypto::Signature>,
|
||||
{
|
||||
let oid = repo.identity_root()?;
|
||||
let oid = ObjectId::from(oid);
|
||||
|
||||
Self::get_mut(&oid, repo).map_err(RepositoryError::from)
|
||||
Self::get_mut(&oid, repo, signer).map_err(RepositoryError::from)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -885,43 +897,47 @@ impl<R: ReadRepository> store::Transaction<Identity, R> {
|
|||
}
|
||||
|
||||
impl<R: WriteRepository> store::Transaction<Identity, R> {
|
||||
pub fn revision<G: crypto::signature::Signer<crypto::Signature>>(
|
||||
&mut self,
|
||||
pub fn new_revision<G: crypto::signature::Signer<crypto::Signature>>(
|
||||
title: cob::Title,
|
||||
description: impl ToString,
|
||||
doc: &Doc,
|
||||
parent: Option<RevisionId>,
|
||||
repo: &R,
|
||||
signer: &Device<G>,
|
||||
) -> Result<(), store::Error> {
|
||||
signer: &G,
|
||||
) -> Result<Self, store::Error> {
|
||||
let mut tx = Transaction::default();
|
||||
|
||||
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.
|
||||
self.embed([embed])?;
|
||||
tx.embed([embed])?;
|
||||
|
||||
// Revision metadata.
|
||||
self.push(Action::Revision {
|
||||
tx.push(Action::Revision {
|
||||
title,
|
||||
description: description.to_string(),
|
||||
blob,
|
||||
parent,
|
||||
signature,
|
||||
})
|
||||
})?;
|
||||
|
||||
Ok(tx)
|
||||
}
|
||||
}
|
||||
|
||||
pub struct IdentityMut<'a, R> {
|
||||
pub struct IdentityMut<'a, 'b, Repo, Signer> {
|
||||
pub id: ObjectId,
|
||||
|
||||
identity: Identity,
|
||||
store: store::Store<'a, Identity, R>,
|
||||
store: store::Store<'a, Identity, Repo, WriteAs<'b, Signer>>,
|
||||
}
|
||||
|
||||
impl<R> fmt::Debug for IdentityMut<'_, R> {
|
||||
impl<Repo, Signer> fmt::Debug for IdentityMut<'_, '_, Repo, Signer> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
f.debug_struct("IdentityMut")
|
||||
.field("id", &self.id)
|
||||
|
|
@ -930,11 +946,16 @@ impl<R> fmt::Debug for IdentityMut<'_, R> {
|
|||
}
|
||||
}
|
||||
|
||||
impl<R> IdentityMut<'_, R>
|
||||
impl<Repo, Signer> IdentityMut<'_, '_, Repo, Signer>
|
||||
where
|
||||
R: WriteRepository + cob::Store<Namespace = NodeId>,
|
||||
Repo: WriteRepository + cob::Store<Namespace = NodeId>,
|
||||
Signer: crypto::signature::Keypair<VerifyingKey = crypto::PublicKey>,
|
||||
Signer: crypto::signature::Signer<crypto::Signature>,
|
||||
Signer: crypto::signature::Signer<crypto::ssh::ExtendedSignature>,
|
||||
Signer: crypto::signature::Verifier<crypto::Signature>,
|
||||
{
|
||||
/// Reload the identity data from storage.
|
||||
#[cfg(test)]
|
||||
pub fn reload(&mut self) -> Result<(), store::Error> {
|
||||
self.identity = self
|
||||
.store
|
||||
|
|
@ -944,20 +965,14 @@ where
|
|||
Ok(())
|
||||
}
|
||||
|
||||
pub fn transaction<G, F>(
|
||||
&mut self,
|
||||
message: &str,
|
||||
signer: &Device<G>,
|
||||
operations: F,
|
||||
) -> Result<EntryId, Error>
|
||||
pub fn transaction<F>(&mut self, message: &str, operations: F) -> Result<EntryId, Error>
|
||||
where
|
||||
G: crypto::signature::Signer<crypto::Signature>,
|
||||
F: FnOnce(&mut Transaction<Identity, R>, &R) -> Result<(), store::Error>,
|
||||
F: FnOnce(&mut Transaction<Identity, Repo>, &Repo) -> Result<(), store::Error>,
|
||||
{
|
||||
let mut tx = Transaction::default();
|
||||
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)?;
|
||||
self.identity = doc;
|
||||
|
||||
Ok(commit)
|
||||
|
|
@ -965,70 +980,61 @@ where
|
|||
|
||||
/// Update the identity by proposing a new revision.
|
||||
/// If the signer is the only delegate, the revision is accepted automatically.
|
||||
pub fn update<G>(
|
||||
pub fn update(
|
||||
&mut self,
|
||||
title: cob::Title,
|
||||
description: impl ToString,
|
||||
doc: &Doc,
|
||||
signer: &Device<G>,
|
||||
) -> Result<RevisionId, Error>
|
||||
where
|
||||
G: crypto::signature::Signer<crypto::Signature>,
|
||||
{
|
||||
let parent = self.current;
|
||||
let id = self.transaction("Propose revision", signer, |tx, repo| {
|
||||
tx.revision(title, description, doc, Some(parent), repo, signer)
|
||||
})?;
|
||||
) -> Result<RevisionId, Error> {
|
||||
let parent = Some(self.current);
|
||||
|
||||
Ok(id)
|
||||
#[allow(deprecated)]
|
||||
let tx = {
|
||||
let signer = self.store.signer();
|
||||
let repo = self.store.repo();
|
||||
Transaction::new_revision(title, description, doc, parent, repo, signer)?
|
||||
};
|
||||
let (doc, commit) = tx.commit("Propose revision", self.id, &mut self.store)?;
|
||||
self.identity = doc;
|
||||
|
||||
Ok(commit)
|
||||
}
|
||||
|
||||
/// Accept an active revision.
|
||||
pub fn accept<G>(&mut self, revision: &RevisionId, signer: &Device<G>) -> Result<EntryId, Error>
|
||||
where
|
||||
G: crypto::signature::Signer<crypto::Signature>,
|
||||
{
|
||||
pub fn accept(&mut self, revision: &RevisionId) -> Result<EntryId, Error> {
|
||||
let id = *revision;
|
||||
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))
|
||||
#[allow(deprecated)]
|
||||
let signature = revision.sign(self.store.signer())?;
|
||||
|
||||
self.transaction("Accept revision", |tx, _| tx.accept(id, signature))
|
||||
}
|
||||
|
||||
/// Reject an active revision.
|
||||
pub fn reject<G>(&mut self, revision: RevisionId, signer: &Device<G>) -> Result<EntryId, Error>
|
||||
where
|
||||
G: crypto::signature::Signer<crypto::Signature>,
|
||||
{
|
||||
self.transaction("Reject revision", signer, |tx, _| tx.reject(revision))
|
||||
pub fn reject(&mut self, revision: RevisionId) -> Result<EntryId, Error> {
|
||||
self.transaction("Reject revision", |tx, _| tx.reject(revision))
|
||||
}
|
||||
|
||||
/// Redact a revision.
|
||||
pub fn redact<G>(&mut self, revision: RevisionId, signer: &Device<G>) -> Result<EntryId, Error>
|
||||
where
|
||||
G: crypto::signature::Signer<crypto::Signature>,
|
||||
{
|
||||
self.transaction("Redact revision", signer, |tx, _| tx.redact(revision))
|
||||
pub fn redact(&mut self, revision: RevisionId) -> Result<EntryId, Error> {
|
||||
self.transaction("Redact revision", |tx, _| tx.redact(revision))
|
||||
}
|
||||
|
||||
/// Edit an active revision's title or description.
|
||||
pub fn edit<G>(
|
||||
pub fn edit(
|
||||
&mut self,
|
||||
revision: RevisionId,
|
||||
title: cob::Title,
|
||||
description: String,
|
||||
signer: &Device<G>,
|
||||
) -> Result<EntryId, Error>
|
||||
where
|
||||
G: crypto::signature::Signer<crypto::Signature>,
|
||||
{
|
||||
self.transaction("Edit revision", signer, |tx, _| {
|
||||
) -> Result<EntryId, Error> {
|
||||
self.transaction("Edit revision", |tx, _| {
|
||||
tx.edit(revision, title, description)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl<R> Deref for IdentityMut<'_, R> {
|
||||
impl<Repo, Signer> Deref for IdentityMut<'_, '_, Repo, Signer> {
|
||||
type Target = Identity;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
|
|
@ -1076,6 +1082,7 @@ mod test {
|
|||
use crate::identity::Visibility;
|
||||
use crate::identity::did::Did;
|
||||
use crate::identity::doc::PayloadId;
|
||||
use crate::node::device::Device;
|
||||
use crate::rad;
|
||||
use crate::storage::ReadStorage;
|
||||
use crate::storage::git::Storage;
|
||||
|
|
@ -1101,7 +1108,7 @@ mod test {
|
|||
let NodeWithRepo { node, repo } = NodeWithRepo::default();
|
||||
let bob = Device::mock();
|
||||
let signer = &node.signer;
|
||||
let mut identity = Identity::load_mut(&*repo).unwrap();
|
||||
let mut identity = Identity::load_mut(&*repo, signer).unwrap();
|
||||
let mut doc = identity.doc().clone().edit();
|
||||
let title = Title::new("Identity update").unwrap();
|
||||
let description = "";
|
||||
|
|
@ -1111,12 +1118,7 @@ mod test {
|
|||
assert!(identity.current().is_accepted());
|
||||
// Using an identical document to the current one fails.
|
||||
identity
|
||||
.update(
|
||||
title.clone(),
|
||||
description,
|
||||
&doc.clone().verified().unwrap(),
|
||||
signer,
|
||||
)
|
||||
.update(title.clone(), description, &doc.clone().verified().unwrap())
|
||||
.unwrap_err();
|
||||
assert_eq!(identity.current, r0);
|
||||
|
||||
|
|
@ -1129,12 +1131,7 @@ mod test {
|
|||
doc.delegate(bob.public_key().into());
|
||||
// The update should go through now.
|
||||
let r1 = identity
|
||||
.update(
|
||||
title.clone(),
|
||||
description,
|
||||
&doc.clone().verified().unwrap(),
|
||||
signer,
|
||||
)
|
||||
.update(title.clone(), description, &doc.clone().verified().unwrap())
|
||||
.unwrap();
|
||||
assert!(identity.revision(&r1).unwrap().is_accepted());
|
||||
assert_eq!(identity.current, r1);
|
||||
|
|
@ -1143,12 +1140,7 @@ mod test {
|
|||
// signs it.
|
||||
doc.visibility = Visibility::private([]);
|
||||
let r2 = identity
|
||||
.update(
|
||||
title.clone(),
|
||||
description,
|
||||
&doc.clone().verified().unwrap(),
|
||||
signer,
|
||||
)
|
||||
.update(title.clone(), description, &doc.clone().verified().unwrap())
|
||||
.unwrap();
|
||||
// R1 is still the head.
|
||||
assert_eq!(identity.current, r1);
|
||||
|
|
@ -1159,7 +1151,10 @@ mod test {
|
|||
&Visibility::Public
|
||||
);
|
||||
// Now let's add a signature on R2 from Bob.
|
||||
identity.accept(&r2, &bob).unwrap();
|
||||
let mut bob_identity = Identity::load_mut(&*repo, &bob).unwrap();
|
||||
bob_identity.accept(&r2).unwrap();
|
||||
|
||||
identity.reload().unwrap();
|
||||
|
||||
// R2 is now the head.
|
||||
assert_eq!(identity.current, r2);
|
||||
|
|
@ -1177,7 +1172,8 @@ mod test {
|
|||
let bob = Device::mock();
|
||||
let eve = Device::mock();
|
||||
let signer = &node.signer;
|
||||
let mut identity = Identity::load_mut(&*repo).unwrap();
|
||||
|
||||
let mut identity = Identity::load_mut(&*repo, signer).unwrap();
|
||||
let mut doc = identity.doc().clone().edit();
|
||||
let description = "";
|
||||
|
||||
|
|
@ -1188,7 +1184,6 @@ mod test {
|
|||
cob::Title::new("Identity update").unwrap(),
|
||||
description,
|
||||
&doc.clone().verified().unwrap(),
|
||||
signer,
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(identity.current, r1);
|
||||
|
|
@ -1199,13 +1194,14 @@ mod test {
|
|||
cob::Title::new("Make private").unwrap(),
|
||||
description,
|
||||
&doc.clone().verified().unwrap(),
|
||||
&node.signer,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let mut bob_identity = Identity::load_mut(&*repo, &bob).unwrap();
|
||||
|
||||
// 1/2 rejected means that we can never reach the required 2/2 votes.
|
||||
identity.reject(r2, &bob).unwrap();
|
||||
let r2 = identity.revision(&r2).unwrap();
|
||||
bob_identity.reject(r2).unwrap();
|
||||
let r2 = bob_identity.revision(&r2).unwrap();
|
||||
assert_eq!(r2.state, State::Rejected);
|
||||
|
||||
// Now let's add another delegate.
|
||||
|
|
@ -1215,10 +1211,13 @@ mod test {
|
|||
cob::Title::new("Add Eve").unwrap(),
|
||||
description,
|
||||
&doc.clone().verified().unwrap(),
|
||||
&node.signer,
|
||||
)
|
||||
.unwrap();
|
||||
let _ = identity.accept(&r3, &bob).unwrap();
|
||||
|
||||
bob_identity.reload().unwrap();
|
||||
let _ = bob_identity.accept(&r3).unwrap();
|
||||
|
||||
identity.reload().unwrap();
|
||||
assert_eq!(identity.current, r3);
|
||||
|
||||
doc.visibility = Visibility::Public;
|
||||
|
|
@ -1227,18 +1226,19 @@ mod test {
|
|||
cob::Title::new("Make public").unwrap(),
|
||||
description,
|
||||
&doc.verified().unwrap(),
|
||||
&node.signer,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
// 1/3 rejected means that we can still reach the 2/3 required votes.
|
||||
identity.reject(r3, &bob).unwrap();
|
||||
bob_identity.reject(r3).unwrap();
|
||||
let r3 = identity.revision(&r3).unwrap().clone();
|
||||
assert_eq!(r3.state, State::Active); // Still active.
|
||||
|
||||
let mut eve_identity = Identity::load_mut(&*repo, &eve).unwrap();
|
||||
|
||||
// 2/3 rejected means that we can no longer reach the 2/3 required votes.
|
||||
identity.reject(r3.id, &eve).unwrap();
|
||||
let r3 = identity.revision(&r3.id).unwrap();
|
||||
eve_identity.reject(r3.id).unwrap();
|
||||
let r3 = eve_identity.revision(&r3.id).unwrap();
|
||||
assert_eq!(r3.state, State::Rejected);
|
||||
}
|
||||
|
||||
|
|
@ -1248,7 +1248,7 @@ mod test {
|
|||
let alice = &network.alice;
|
||||
let bob = &network.bob;
|
||||
|
||||
let mut alice_identity = Identity::load_mut(&*alice.repo).unwrap();
|
||||
let mut alice_identity = Identity::load_mut(&*alice.repo, &alice.signer).unwrap();
|
||||
let mut alice_doc = alice_identity.doc().clone().edit();
|
||||
|
||||
alice_doc.delegate(bob.signer.public_key().into());
|
||||
|
|
@ -1257,13 +1257,12 @@ mod test {
|
|||
cob::Title::new("Add Bob").unwrap(),
|
||||
"",
|
||||
&alice_doc.clone().verified().unwrap(),
|
||||
&alice.signer,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
bob.repo.fetch(alice);
|
||||
|
||||
let mut bob_identity = Identity::load_mut(&*bob.repo).unwrap();
|
||||
let bob_identity = Identity::load(&*bob.repo).unwrap();
|
||||
let bob_doc = bob_identity.doc().clone();
|
||||
assert!(bob_doc.is_delegate(&bob.signer.public_key().into()));
|
||||
|
||||
|
|
@ -1274,16 +1273,19 @@ mod test {
|
|||
cob::Title::new("Change visibility").unwrap(),
|
||||
"",
|
||||
&alice_doc.clone().clone().verified().unwrap(),
|
||||
&alice.signer,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let bob_identity_mut = Identity::load_mut(&*bob.repo, &bob.signer).unwrap();
|
||||
assert_eq!(*bob_identity_mut, bob_identity);
|
||||
let mut bob_identity = bob_identity_mut;
|
||||
|
||||
// Bob makes the same change without knowing Alice already did.
|
||||
let b1 = bob_identity
|
||||
.update(
|
||||
cob::Title::new("Make private").unwrap(),
|
||||
"",
|
||||
&alice_doc.verified().unwrap(),
|
||||
&bob.signer,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
|
|
@ -1301,7 +1303,7 @@ mod test {
|
|||
assert_eq!(bob_identity.revision(&b1).unwrap().state, State::Active);
|
||||
|
||||
// Now Bob accepts Alice's proposal. This voids his own.
|
||||
bob_identity.accept(&a2, &bob.signer).unwrap();
|
||||
bob_identity.accept(&a2).unwrap();
|
||||
assert_eq!(bob_identity.current, a2);
|
||||
assert_eq!(bob_identity.revision(&a1).unwrap().state, State::Accepted);
|
||||
assert_eq!(bob_identity.revision(&a2).unwrap().state, State::Accepted);
|
||||
|
|
@ -1315,7 +1317,7 @@ mod test {
|
|||
let bob = &network.bob;
|
||||
let eve = &network.eve;
|
||||
|
||||
let mut alice_identity = Identity::load_mut(&*alice.repo).unwrap();
|
||||
let mut alice_identity = Identity::load_mut(&*alice.repo, &alice.signer).unwrap();
|
||||
let mut alice_doc = alice_identity.doc().clone().edit();
|
||||
|
||||
alice_doc.delegate(bob.signer.public_key().into());
|
||||
|
|
@ -1325,7 +1327,6 @@ mod test {
|
|||
cob::Title::new("Add Bob").unwrap(),
|
||||
"Eh.",
|
||||
&alice_doc.clone().clone().verified().unwrap(),
|
||||
&alice.signer,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
|
|
@ -1335,20 +1336,16 @@ mod test {
|
|||
cob::Title::new("Change visibility").unwrap(),
|
||||
"Eh.",
|
||||
&alice_doc.verified().unwrap(),
|
||||
&alice.signer,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
bob.repo.fetch(alice);
|
||||
let a3 = cob::stable::with_advanced_timestamp(|| {
|
||||
alice_identity.redact(a2, &alice.signer).unwrap()
|
||||
});
|
||||
let a3 = cob::stable::with_advanced_timestamp(|| alice_identity.redact(a2).unwrap());
|
||||
assert!(alice_identity.revision(&a1).is_some());
|
||||
assert_eq!(alice_identity.timeline, vec![a0, a1, a2, a3]);
|
||||
|
||||
let mut bob_identity = Identity::load_mut(&*bob.repo).unwrap();
|
||||
let b1 =
|
||||
cob::stable::with_advanced_timestamp(|| bob_identity.accept(&a2, &bob.signer).unwrap());
|
||||
let mut bob_identity = Identity::load_mut(&*bob.repo, &bob.signer).unwrap();
|
||||
let b1 = cob::stable::with_advanced_timestamp(|| bob_identity.accept(&a2).unwrap());
|
||||
|
||||
assert_eq!(bob_identity.timeline, vec![a0, a1, a2, b1]);
|
||||
assert_eq!(bob_identity.revision(&a2).unwrap().state, State::Accepted);
|
||||
|
|
@ -1367,7 +1364,7 @@ mod test {
|
|||
let bob = &network.bob;
|
||||
let eve = &network.eve;
|
||||
|
||||
let mut alice_identity = Identity::load_mut(&*alice.repo).unwrap();
|
||||
let mut alice_identity = Identity::load_mut(&*alice.repo, &alice.signer).unwrap();
|
||||
let mut alice_doc = alice_identity.doc().clone().edit();
|
||||
|
||||
alice_doc.delegate(bob.signer.public_key().into());
|
||||
|
|
@ -1378,7 +1375,6 @@ mod test {
|
|||
cob::Title::new("Add Bob and Eve").unwrap(),
|
||||
"Eh#!",
|
||||
&alice_doc.clone().verified().unwrap(),
|
||||
&alice.signer,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
|
|
@ -1388,7 +1384,6 @@ mod test {
|
|||
cob::Title::new("Remove Eve").unwrap(),
|
||||
"",
|
||||
&alice_doc.verified().unwrap(),
|
||||
&alice.signer,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
|
|
@ -1396,12 +1391,11 @@ mod test {
|
|||
bob.repo.fetch(alice);
|
||||
eve.repo.fetch(bob);
|
||||
|
||||
let mut bob_identity = Identity::load_mut(&*bob.repo).unwrap();
|
||||
let b1 =
|
||||
cob::stable::with_advanced_timestamp(|| bob_identity.accept(&a2, &bob.signer).unwrap());
|
||||
let mut bob_identity = Identity::load_mut(&*bob.repo, &bob.signer).unwrap();
|
||||
let b1 = cob::stable::with_advanced_timestamp(|| bob_identity.accept(&a2).unwrap());
|
||||
assert_eq!(bob_identity.current, a2);
|
||||
|
||||
let mut eve_identity = Identity::load_mut(&*eve.repo).unwrap();
|
||||
let mut eve_identity = Identity::load_mut(&*eve.repo, &eve.signer).unwrap();
|
||||
let mut eve_doc = eve_identity.doc().clone().edit();
|
||||
eve_doc.visibility = Visibility::private([eve.signer.public_key().into()]);
|
||||
let e1 = cob::stable::with_advanced_timestamp(|| {
|
||||
|
|
@ -1410,7 +1404,6 @@ mod test {
|
|||
cob::Title::new("Change visibility").unwrap(),
|
||||
"",
|
||||
&eve_doc.verified().unwrap(),
|
||||
&eve.signer,
|
||||
)
|
||||
.unwrap()
|
||||
});
|
||||
|
|
@ -1443,7 +1436,7 @@ mod test {
|
|||
let bob = &network.bob;
|
||||
let eve = &network.eve;
|
||||
|
||||
let mut alice_identity = Identity::load_mut(&*alice.repo).unwrap();
|
||||
let mut alice_identity = Identity::load_mut(&*alice.repo, &alice.signer).unwrap();
|
||||
let mut alice_doc = alice_identity.doc().clone().edit();
|
||||
|
||||
alice_doc.delegate(bob.signer.public_key().into());
|
||||
|
|
@ -1454,7 +1447,6 @@ mod test {
|
|||
cob::Title::new("Add Bob and Eve").unwrap(),
|
||||
"Eh!#",
|
||||
&alice_doc.clone().verified().unwrap(),
|
||||
&alice.signer,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
|
|
@ -1464,7 +1456,6 @@ mod test {
|
|||
cob::Title::new("Change visibility").unwrap(),
|
||||
"",
|
||||
&alice_doc.verified().unwrap(),
|
||||
&alice.signer,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
|
|
@ -1473,14 +1464,12 @@ mod test {
|
|||
eve.repo.fetch(bob);
|
||||
|
||||
// Bob accepts alice's revision.
|
||||
let mut bob_identity = Identity::load_mut(&*bob.repo).unwrap();
|
||||
let b1 =
|
||||
cob::stable::with_advanced_timestamp(|| bob_identity.accept(&a2, &bob.signer).unwrap());
|
||||
let mut bob_identity = Identity::load_mut(&*bob.repo, &bob.signer).unwrap();
|
||||
let b1 = cob::stable::with_advanced_timestamp(|| bob_identity.accept(&a2).unwrap());
|
||||
|
||||
// Eve rejects the revision, not knowing.
|
||||
let mut eve_identity = Identity::load_mut(&*eve.repo).unwrap();
|
||||
let e1 =
|
||||
cob::stable::with_advanced_timestamp(|| eve_identity.reject(a2, &eve.signer).unwrap());
|
||||
let mut eve_identity = Identity::load_mut(&*eve.repo, &eve.signer).unwrap();
|
||||
let e1 = cob::stable::with_advanced_timestamp(|| eve_identity.reject(a2).unwrap());
|
||||
assert!(eve_identity.revision(&a2).unwrap().is_active());
|
||||
|
||||
// Then she submits a new revision.
|
||||
|
|
@ -1491,7 +1480,6 @@ mod test {
|
|||
cob::Title::new("Change visibility").unwrap(),
|
||||
"",
|
||||
&eve_doc.verified().unwrap(),
|
||||
&eve.signer,
|
||||
)
|
||||
.unwrap();
|
||||
assert!(eve_identity.revision(&e2).unwrap().is_active());
|
||||
|
|
@ -1529,7 +1517,7 @@ mod test {
|
|||
let bob = &network.bob;
|
||||
let eve = &network.eve;
|
||||
|
||||
let mut alice_identity = Identity::load_mut(&*alice.repo).unwrap();
|
||||
let mut alice_identity = Identity::load_mut(&*alice.repo, &alice.signer).unwrap();
|
||||
let mut alice_doc = alice_identity.doc().clone().edit();
|
||||
|
||||
alice.repo.fetch(bob);
|
||||
|
|
@ -1542,14 +1530,13 @@ mod test {
|
|||
cob::Title::new("Add Bob and Eve").unwrap(),
|
||||
"",
|
||||
&alice_doc.verified().unwrap(),
|
||||
&alice.signer,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
bob.repo.fetch(alice);
|
||||
eve.repo.fetch(alice);
|
||||
|
||||
let mut bob_identity = Identity::load_mut(&*bob.repo).unwrap();
|
||||
let mut bob_identity = Identity::load_mut(&*bob.repo, &bob.signer).unwrap();
|
||||
let mut bob_doc = bob_identity.doc().clone().edit();
|
||||
assert!(bob_doc.is_delegate(&bob.signer.public_key().into()));
|
||||
|
||||
|
|
@ -1568,14 +1555,14 @@ mod test {
|
|||
cob::Title::new("Change visibility #1").unwrap(),
|
||||
"",
|
||||
&bob_doc.verified().unwrap(),
|
||||
&bob.signer,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
alice.repo.fetch(bob);
|
||||
eve.repo.fetch(bob);
|
||||
|
||||
// In the meantime, Eve does the same thing on her side.
|
||||
let mut eve_identity = Identity::load_mut(&*eve.repo).unwrap();
|
||||
let mut eve_identity = Identity::load_mut(&*eve.repo, &eve.signer).unwrap();
|
||||
let mut eve_doc = eve_identity.doc().clone().edit();
|
||||
eve_doc.visibility = Visibility::private([]);
|
||||
let e1 = eve_identity
|
||||
|
|
@ -1583,18 +1570,16 @@ mod test {
|
|||
cob::Title::new("Change visibility #2").unwrap(),
|
||||
"Woops",
|
||||
&eve_doc.verified().unwrap(),
|
||||
&eve.signer,
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(eve_identity.revisions().count(), 4);
|
||||
assert_eq!(eve_identity.revision(&e1).unwrap().state, State::Active);
|
||||
|
||||
alice_identity.reload().unwrap();
|
||||
let a2 = cob::stable::with_advanced_timestamp(|| {
|
||||
alice_identity.accept(&b1, &alice.signer).unwrap()
|
||||
});
|
||||
let a2 = cob::stable::with_advanced_timestamp(|| alice_identity.accept(&b1).unwrap());
|
||||
|
||||
eve.repo.fetch(alice);
|
||||
|
||||
eve_identity.reload().unwrap();
|
||||
|
||||
assert_eq!(eve_identity.timeline, vec![a0, a1, b1, e1, a2]);
|
||||
|
|
@ -1619,7 +1604,7 @@ mod test {
|
|||
rad::fork_remote(id, alice.public_key(), &eve, &storage).unwrap();
|
||||
|
||||
let repo = storage.repository(id).unwrap();
|
||||
let mut identity = Identity::load_mut(&repo).unwrap();
|
||||
let mut identity = Identity::load_mut(&repo, &alice).unwrap();
|
||||
let doc = identity.doc().clone();
|
||||
let prj = doc.project().unwrap();
|
||||
let mut doc = doc.edit();
|
||||
|
|
@ -1633,7 +1618,6 @@ mod test {
|
|||
cob::Title::new("Update description").unwrap(),
|
||||
"",
|
||||
&doc.clone().verified().unwrap(),
|
||||
&alice,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
|
|
@ -1645,7 +1629,6 @@ mod test {
|
|||
cob::Title::new("Add bob").unwrap(),
|
||||
"",
|
||||
&doc.clone().verified().unwrap(),
|
||||
&alice,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
|
|
@ -1658,25 +1641,26 @@ mod test {
|
|||
cob::Title::new("Add eve").unwrap(),
|
||||
"",
|
||||
&doc.clone().verified().unwrap(),
|
||||
&alice,
|
||||
)
|
||||
.unwrap();
|
||||
identity.accept(&revision, &bob).unwrap();
|
||||
|
||||
let mut bob_identity = Identity::load_mut(&repo, &bob).unwrap();
|
||||
bob_identity.accept(&revision).unwrap();
|
||||
|
||||
// Update description again with signatures by Eve and Bob.
|
||||
let desc = prj.description().to_owned() + "?";
|
||||
let prj = prj.update(None, desc, None).unwrap();
|
||||
doc.payload.insert(PayloadId::project(), prj.into());
|
||||
|
||||
let revision = identity
|
||||
let revision = bob_identity
|
||||
.update(
|
||||
cob::Title::new("Update description again").unwrap(),
|
||||
"Bob's repository",
|
||||
&doc.verified().unwrap(),
|
||||
&bob,
|
||||
)
|
||||
.unwrap();
|
||||
identity.accept(&revision, &eve).unwrap();
|
||||
|
||||
let mut eve_identity = Identity::load_mut(&repo, &eve).unwrap();
|
||||
eve_identity.accept(&revision).unwrap();
|
||||
|
||||
let identity: Identity = Identity::load(&repo).unwrap();
|
||||
let root = repo.identity_root().unwrap();
|
||||
|
|
|
|||
|
|
@ -11,13 +11,13 @@ use thiserror::Error;
|
|||
use crate::cob;
|
||||
use crate::cob::common::{Author, Authorization, Label, Reaction, Timestamp, Uri};
|
||||
use crate::cob::store::Transaction;
|
||||
use crate::cob::store::access::WriteAs;
|
||||
use crate::cob::store::{Cob, CobAction};
|
||||
use crate::cob::thread::{Comment, CommentId, Thread};
|
||||
use crate::cob::{ActorId, Embed, EntryId, ObjectId, TypeName, op, store};
|
||||
use crate::cob::{TitleError, thread};
|
||||
use crate::identity::doc::DocError;
|
||||
use crate::node::NodeId;
|
||||
use crate::node::device::Device;
|
||||
use crate::prelude::{Did, Doc, ReadRepository, RepoId};
|
||||
use crate::storage;
|
||||
use crate::storage::{HasRepoId, RepositoryError, WriteRepository};
|
||||
|
|
@ -472,8 +472,10 @@ impl Issue {
|
|||
}
|
||||
}
|
||||
|
||||
impl<'a, 'g, R, C> From<IssueMut<'a, 'g, R, C>> for (IssueId, Issue) {
|
||||
fn from(value: IssueMut<'a, 'g, R, C>) -> Self {
|
||||
impl<'a, 'b, 'g, Repo, Signer, Cache> From<IssueMut<'a, 'b, 'g, Repo, Signer, Cache>>
|
||||
for (IssueId, Issue)
|
||||
{
|
||||
fn from(value: IssueMut<'a, 'b, 'g, Repo, Signer, Cache>) -> Self {
|
||||
(value.id, value.issue)
|
||||
}
|
||||
}
|
||||
|
|
@ -579,14 +581,14 @@ impl<R: ReadRepository> store::Transaction<Issue, R> {
|
|||
}
|
||||
}
|
||||
|
||||
pub struct IssueMut<'a, 'g, R, C> {
|
||||
pub struct IssueMut<'a, 'b, 'g, Repo, Signer, Cache> {
|
||||
id: ObjectId,
|
||||
issue: Issue,
|
||||
store: &'g mut Issues<'a, R>,
|
||||
cache: &'g mut C,
|
||||
store: &'g mut Issues<'a, Repo, WriteAs<'b, Signer>>,
|
||||
cache: &'g mut Cache,
|
||||
}
|
||||
|
||||
impl<R, C> std::fmt::Debug for IssueMut<'_, '_, R, C> {
|
||||
impl<Repo, Signer, Cache> std::fmt::Debug for IssueMut<'_, '_, '_, Repo, Signer, Cache> {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||
f.debug_struct("IssueMut")
|
||||
.field("id", &self.id)
|
||||
|
|
@ -595,10 +597,14 @@ impl<R, C> std::fmt::Debug for IssueMut<'_, '_, R, C> {
|
|||
}
|
||||
}
|
||||
|
||||
impl<R, C> IssueMut<'_, '_, R, C>
|
||||
impl<Repo, Signer, Cache> IssueMut<'_, '_, '_, Repo, Signer, Cache>
|
||||
where
|
||||
R: WriteRepository + cob::Store<Namespace = NodeId>,
|
||||
C: cob::cache::Update<Issue>,
|
||||
Repo: WriteRepository + cob::Store<Namespace = NodeId>,
|
||||
Signer: crypto::signature::Keypair<VerifyingKey = crypto::PublicKey>,
|
||||
Signer: crypto::signature::Signer<crypto::Signature>,
|
||||
Signer: crypto::signature::Signer<crypto::ssh::ExtendedSignature>,
|
||||
Signer: crypto::signature::Verifier<crypto::Signature>,
|
||||
Cache: cob::cache::Update<Issue>,
|
||||
{
|
||||
/// Reload the issue data from storage.
|
||||
pub fn reload(&mut self) -> Result<(), store::Error> {
|
||||
|
|
@ -606,7 +612,6 @@ where
|
|||
.store
|
||||
.get(&self.id)?
|
||||
.ok_or_else(|| store::Error::NotFound(TYPENAME.clone(), self.id))?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
|
@ -616,132 +621,89 @@ where
|
|||
}
|
||||
|
||||
/// Assign one or more actors to an issue.
|
||||
pub fn assign<G>(
|
||||
&mut self,
|
||||
assignees: impl IntoIterator<Item = Did>,
|
||||
signer: &Device<G>,
|
||||
) -> Result<EntryId, Error>
|
||||
where
|
||||
G: crypto::signature::Signer<crypto::Signature>,
|
||||
{
|
||||
self.transaction("Assign", signer, |tx| tx.assign(assignees))
|
||||
pub fn assign(&mut self, assignees: impl IntoIterator<Item = Did>) -> Result<EntryId, Error> {
|
||||
self.transaction("Assign", |tx| tx.assign(assignees))
|
||||
}
|
||||
|
||||
/// Set the issue title.
|
||||
pub fn edit<G>(&mut self, title: cob::Title, signer: &Device<G>) -> Result<EntryId, Error>
|
||||
where
|
||||
G: crypto::signature::Signer<crypto::Signature>,
|
||||
{
|
||||
self.transaction("Edit", signer, |tx| tx.edit(title))
|
||||
pub fn edit(&mut self, title: cob::Title) -> Result<EntryId, Error> {
|
||||
self.transaction("Edit", |tx| tx.edit(title))
|
||||
}
|
||||
|
||||
/// Set the issue description.
|
||||
pub fn edit_description<G>(
|
||||
pub fn edit_description(
|
||||
&mut self,
|
||||
description: impl ToString,
|
||||
embeds: impl IntoIterator<Item = Embed<Uri>>,
|
||||
signer: &Device<G>,
|
||||
) -> Result<EntryId, Error>
|
||||
where
|
||||
G: crypto::signature::Signer<crypto::Signature>,
|
||||
{
|
||||
let (id, _) = self.root();
|
||||
) -> Result<EntryId, Error> {
|
||||
let (id, _) = self.issue.root();
|
||||
let id = *id;
|
||||
self.transaction("Edit description", signer, |tx| {
|
||||
self.transaction("Edit description", |tx| {
|
||||
tx.edit_comment(id, description, embeds.into_iter().collect())
|
||||
})
|
||||
}
|
||||
|
||||
/// Lifecycle an issue.
|
||||
pub fn lifecycle<G>(&mut self, state: State, signer: &Device<G>) -> Result<EntryId, Error>
|
||||
where
|
||||
G: crypto::signature::Signer<crypto::Signature>,
|
||||
{
|
||||
self.transaction("Lifecycle", signer, |tx| tx.lifecycle(state))
|
||||
pub fn lifecycle(&mut self, state: State) -> Result<EntryId, Error> {
|
||||
self.transaction("Lifecycle", |tx| tx.lifecycle(state))
|
||||
}
|
||||
|
||||
/// Comment on an issue.
|
||||
pub fn comment<G, S>(
|
||||
pub fn comment(
|
||||
&mut self,
|
||||
body: S,
|
||||
body: impl ToString,
|
||||
reply_to: CommentId,
|
||||
embeds: impl IntoIterator<Item = Embed<Uri>>,
|
||||
signer: &Device<G>,
|
||||
) -> Result<EntryId, Error>
|
||||
where
|
||||
G: crypto::signature::Signer<crypto::Signature>,
|
||||
S: ToString,
|
||||
{
|
||||
self.transaction("Comment", signer, |tx| {
|
||||
) -> Result<EntryId, Error> {
|
||||
self.transaction("Comment", |tx| {
|
||||
tx.comment(body, reply_to, embeds.into_iter().collect())
|
||||
})
|
||||
}
|
||||
|
||||
/// Edit a comment.
|
||||
pub fn edit_comment<G, S>(
|
||||
pub fn edit_comment(
|
||||
&mut self,
|
||||
id: CommentId,
|
||||
body: S,
|
||||
body: impl ToString,
|
||||
embeds: impl IntoIterator<Item = Embed<Uri>>,
|
||||
signer: &Device<G>,
|
||||
) -> Result<EntryId, Error>
|
||||
where
|
||||
G: crypto::signature::Signer<crypto::Signature>,
|
||||
S: ToString,
|
||||
{
|
||||
self.transaction("Edit comment", signer, |tx| {
|
||||
) -> Result<EntryId, Error> {
|
||||
self.transaction("Edit comment", |tx| {
|
||||
tx.edit_comment(id, body, embeds.into_iter().collect())
|
||||
})
|
||||
}
|
||||
|
||||
/// Redact a comment.
|
||||
pub fn redact_comment<G>(&mut self, id: CommentId, signer: &Device<G>) -> Result<EntryId, Error>
|
||||
where
|
||||
G: crypto::signature::Signer<crypto::Signature>,
|
||||
{
|
||||
self.transaction("Redact comment", signer, |tx| tx.redact_comment(id))
|
||||
pub fn redact_comment(&mut self, id: CommentId) -> Result<EntryId, Error> {
|
||||
self.transaction("Redact comment", |tx| tx.redact_comment(id))
|
||||
}
|
||||
|
||||
/// Label an issue.
|
||||
pub fn label<G>(
|
||||
&mut self,
|
||||
labels: impl IntoIterator<Item = Label>,
|
||||
signer: &Device<G>,
|
||||
) -> Result<EntryId, Error>
|
||||
where
|
||||
G: crypto::signature::Signer<crypto::Signature>,
|
||||
{
|
||||
self.transaction("Label", signer, |tx| tx.label(labels))
|
||||
pub fn label(&mut self, labels: impl IntoIterator<Item = Label>) -> Result<EntryId, Error> {
|
||||
self.transaction("Label", |tx| tx.label(labels))
|
||||
}
|
||||
|
||||
/// React to an issue comment.
|
||||
pub fn react<G>(
|
||||
pub fn react(
|
||||
&mut self,
|
||||
to: CommentId,
|
||||
reaction: Reaction,
|
||||
active: bool,
|
||||
signer: &Device<G>,
|
||||
) -> Result<EntryId, Error>
|
||||
where
|
||||
G: crypto::signature::Signer<crypto::Signature>,
|
||||
{
|
||||
self.transaction("React", signer, |tx| tx.react(to, reaction, active))
|
||||
) -> Result<EntryId, Error> {
|
||||
self.transaction("React", |tx| tx.react(to, reaction, active))
|
||||
}
|
||||
|
||||
pub fn transaction<G, F>(
|
||||
&mut self,
|
||||
message: &str,
|
||||
signer: &Device<G>,
|
||||
operations: F,
|
||||
) -> Result<EntryId, Error>
|
||||
pub fn transaction<F>(&mut self, message: &str, operations: F) -> Result<EntryId, Error>
|
||||
where
|
||||
G: crypto::signature::Signer<crypto::Signature>,
|
||||
F: FnOnce(&mut Transaction<Issue, R>) -> Result<(), store::Error>,
|
||||
Signer: crypto::signature::Keypair<VerifyingKey = crypto::PublicKey>,
|
||||
Signer: crypto::signature::Signer<crypto::Signature>,
|
||||
Signer: crypto::signature::Signer<crypto::ssh::ExtendedSignature>,
|
||||
Signer: crypto::signature::Verifier<crypto::Signature>,
|
||||
F: FnOnce(&mut Transaction<Issue, Repo>) -> Result<(), store::Error>,
|
||||
{
|
||||
let mut tx = Transaction::default();
|
||||
operations(&mut tx)?;
|
||||
|
||||
let (issue, commit) = tx.commit(message, self.id, &mut self.store.raw, signer)?;
|
||||
let (issue, commit) = tx.commit(message, self.id, &mut self.store.raw)?;
|
||||
self.cache
|
||||
.update(&self.store.as_ref().id(), &self.id, &issue)
|
||||
.map_err(|e| Error::CacheUpdate {
|
||||
|
|
@ -754,7 +716,7 @@ where
|
|||
}
|
||||
}
|
||||
|
||||
impl<R, C> Deref for IssueMut<'_, '_, R, C> {
|
||||
impl<Repo, Signer, Cache> Deref for IssueMut<'_, '_, '_, Repo, Signer, Cache> {
|
||||
type Target = Issue;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
|
|
@ -762,24 +724,24 @@ impl<R, C> Deref for IssueMut<'_, '_, R, C> {
|
|||
}
|
||||
}
|
||||
|
||||
pub struct Issues<'a, R> {
|
||||
raw: store::Store<'a, Issue, R>,
|
||||
pub struct Issues<'a, Repo, Access> {
|
||||
raw: store::Store<'a, Issue, Repo, Access>,
|
||||
}
|
||||
|
||||
impl<'a, R> Deref for Issues<'a, R> {
|
||||
type Target = store::Store<'a, Issue, R>;
|
||||
impl<'a, Repo, Access> Deref for Issues<'a, Repo, Access> {
|
||||
type Target = store::Store<'a, Issue, Repo, Access>;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.raw
|
||||
}
|
||||
}
|
||||
|
||||
impl<R> HasRepoId for Issues<'_, R>
|
||||
impl<Repo, Access> HasRepoId for Issues<'_, Repo, Access>
|
||||
where
|
||||
R: ReadRepository,
|
||||
Repo: HasRepoId,
|
||||
{
|
||||
fn rid(&self) -> RepoId {
|
||||
self.raw.as_ref().id()
|
||||
self.raw.rid()
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -798,39 +760,61 @@ impl IssueCounts {
|
|||
}
|
||||
}
|
||||
|
||||
impl<'a, R> Issues<'a, R>
|
||||
impl<'a, Repo, Access> Issues<'a, Repo, Access>
|
||||
where
|
||||
R: ReadRepository + cob::Store<Namespace = NodeId>,
|
||||
Repo: ReadRepository + cob::Store<Namespace = NodeId>,
|
||||
Access: store::access::Access,
|
||||
{
|
||||
/// Open an issues store.
|
||||
pub fn open(repository: &'a R) -> Result<Self, RepositoryError> {
|
||||
pub fn open(repository: &'a Repo, access: Access) -> Result<Self, RepositoryError> {
|
||||
let identity = repository.identity_head()?;
|
||||
let raw = store::Store::open(repository)?.identity(identity);
|
||||
let raw = store::Store::open(repository, access)?.identity(identity);
|
||||
|
||||
Ok(Self { raw })
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, R> Issues<'a, R>
|
||||
impl<'a, 'b, Repo, Signer> Issues<'a, Repo, WriteAs<'b, Signer>>
|
||||
where
|
||||
R: WriteRepository + cob::Store<Namespace = NodeId>,
|
||||
Repo: WriteRepository + cob::Store<Namespace = NodeId>,
|
||||
Signer: crypto::signature::Keypair<VerifyingKey = crypto::PublicKey>,
|
||||
Signer: crypto::signature::Signer<crypto::Signature>,
|
||||
Signer: crypto::signature::Signer<crypto::ssh::ExtendedSignature>,
|
||||
Signer: crypto::signature::Verifier<crypto::Signature>,
|
||||
{
|
||||
/// Get an issue mutably.
|
||||
pub fn get_mut<'g, Cache>(
|
||||
&'g mut self,
|
||||
id: &ObjectId,
|
||||
cache: &'g mut Cache,
|
||||
) -> Result<IssueMut<'a, 'b, 'g, Repo, Signer, Cache>, Error> {
|
||||
let issue = self
|
||||
.raw
|
||||
.get(id)?
|
||||
.ok_or_else(move || store::Error::NotFound(TYPENAME.clone(), *id))?;
|
||||
|
||||
Ok(IssueMut {
|
||||
id: *id,
|
||||
issue,
|
||||
store: self,
|
||||
cache,
|
||||
})
|
||||
}
|
||||
|
||||
/// Create a new issue.
|
||||
pub fn create<'g, G, C>(
|
||||
pub fn create<'g, Cache>(
|
||||
&'g mut self,
|
||||
title: cob::Title,
|
||||
description: impl ToString,
|
||||
labels: &[Label],
|
||||
assignees: &[Did],
|
||||
embeds: impl IntoIterator<Item = Embed<Uri>>,
|
||||
cache: &'g mut C,
|
||||
signer: &Device<G>,
|
||||
) -> Result<IssueMut<'a, 'g, R, C>, Error>
|
||||
cache: &'g mut Cache,
|
||||
) -> Result<IssueMut<'a, 'b, 'g, Repo, Signer, Cache>, Error>
|
||||
where
|
||||
G: crypto::signature::Signer<crypto::Signature>,
|
||||
C: cob::cache::Update<Issue>,
|
||||
Cache: 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, |tx, _| {
|
||||
tx.thread(description, embeds)?;
|
||||
tx.edit(title)?;
|
||||
|
||||
|
|
@ -855,43 +839,24 @@ where
|
|||
}
|
||||
|
||||
/// Remove an issue.
|
||||
pub fn remove<C, G>(&self, id: &ObjectId, signer: &Device<G>) -> Result<(), store::Error>
|
||||
pub fn remove<C, G>(&mut self, id: &ObjectId) -> Result<(), store::Error>
|
||||
where
|
||||
C: cob::cache::Remove<Issue>,
|
||||
G: crypto::signature::Signer<crypto::Signature>,
|
||||
{
|
||||
self.raw.remove(id, signer)
|
||||
self.raw.remove(id)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, R> Issues<'a, R>
|
||||
impl<'a, Repo, Access> Issues<'a, Repo, Access>
|
||||
where
|
||||
R: ReadRepository + cob::Store,
|
||||
Repo: ReadRepository + cob::Store<Namespace = NodeId>,
|
||||
Access: store::access::Access,
|
||||
{
|
||||
/// Get an issue.
|
||||
pub fn get(&self, id: &ObjectId) -> Result<Option<Issue>, store::Error> {
|
||||
self.raw.get(id)
|
||||
}
|
||||
|
||||
/// Get an issue mutably.
|
||||
pub fn get_mut<'g, C>(
|
||||
&'g mut self,
|
||||
id: &ObjectId,
|
||||
cache: &'g mut C,
|
||||
) -> Result<IssueMut<'a, 'g, R, C>, store::Error> {
|
||||
let issue = self
|
||||
.raw
|
||||
.get(id)?
|
||||
.ok_or_else(move || store::Error::NotFound(TYPENAME.clone(), *id))?;
|
||||
|
||||
Ok(IssueMut {
|
||||
id: *id,
|
||||
issue,
|
||||
store: self,
|
||||
cache,
|
||||
})
|
||||
}
|
||||
|
||||
/// Issues count by state.
|
||||
pub fn counts(&self) -> Result<IssueCounts, Error> {
|
||||
let all = self.all()?;
|
||||
|
|
@ -984,23 +949,24 @@ mod test {
|
|||
use crate::cob::{ActorId, Reaction, store::CobWithType};
|
||||
use crate::git::Oid;
|
||||
use crate::issue::cache::Issues as _;
|
||||
use crate::node::device::Device;
|
||||
use crate::test::arbitrary;
|
||||
use crate::{assert_matches, test};
|
||||
|
||||
#[test]
|
||||
fn test_concurrency() {
|
||||
let t = test::setup::Network::default();
|
||||
let mut issues_alice = Cache::no_cache(&*t.alice.repo).unwrap();
|
||||
let mut bob_issues = Cache::no_cache(&*t.bob.repo).unwrap();
|
||||
let mut eve_issues = Cache::no_cache(&*t.eve.repo).unwrap();
|
||||
let mut issue_alice = issues_alice
|
||||
|
||||
let mut alice_issues = Cache::no_cache(&*t.alice.repo, &t.alice.signer).unwrap();
|
||||
let mut bob_issues = Cache::no_cache(&*t.bob.repo, &t.bob.signer).unwrap();
|
||||
let mut eve_issues = Cache::no_cache(&*t.eve.repo, &t.eve.signer).unwrap();
|
||||
let mut issue_alice = alice_issues
|
||||
.create(
|
||||
cob::Title::new("Alice Issue").unwrap(),
|
||||
"Alice's comment",
|
||||
&[],
|
||||
&[],
|
||||
[],
|
||||
&t.alice.signer,
|
||||
)
|
||||
.unwrap();
|
||||
let id = *issue_alice.id();
|
||||
|
|
@ -1011,12 +977,8 @@ mod test {
|
|||
let mut issue_eve = eve_issues.get_mut(&id).unwrap();
|
||||
let mut issue_bob = bob_issues.get_mut(&id).unwrap();
|
||||
|
||||
issue_bob
|
||||
.comment("Bob's reply", *id, vec![], &t.bob.signer)
|
||||
.unwrap();
|
||||
issue_alice
|
||||
.comment("Alice's reply", *id, vec![], &t.alice.signer)
|
||||
.unwrap();
|
||||
issue_bob.comment("Bob's reply", *id, vec![]).unwrap();
|
||||
issue_alice.comment("Alice's reply", *id, vec![]).unwrap();
|
||||
|
||||
assert_eq!(issue_bob.comments().count(), 2);
|
||||
assert_eq!(issue_alice.comments().count(), 2);
|
||||
|
|
@ -1042,9 +1004,7 @@ mod test {
|
|||
|
||||
t.eve.repo.fetch(&t.alice);
|
||||
|
||||
let eve_reply = issue_eve
|
||||
.comment("Eve's reply", *id, vec![], &t.eve.signer)
|
||||
.unwrap();
|
||||
let eve_reply = issue_eve.comment("Eve's reply", *id, vec![]).unwrap();
|
||||
|
||||
t.bob.repo.fetch(&t.eve);
|
||||
t.alice.repo.fetch(&t.eve);
|
||||
|
|
@ -1078,7 +1038,7 @@ mod test {
|
|||
#[test]
|
||||
fn test_issue_create_and_assign() {
|
||||
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, &node.signer).unwrap();
|
||||
|
||||
let assignee = Did::from(arbitrary::r#gen::<ActorId>(1));
|
||||
let assignee_two = Did::from(arbitrary::r#gen::<ActorId>(1));
|
||||
|
|
@ -1089,7 +1049,6 @@ mod test {
|
|||
&[],
|
||||
&[assignee],
|
||||
[],
|
||||
&node.signer,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
|
|
@ -1101,9 +1060,7 @@ mod test {
|
|||
assert!(assignees.contains(&assignee));
|
||||
|
||||
let mut issue = issues.get_mut(&id).unwrap();
|
||||
issue
|
||||
.assign([assignee, assignee_two], &node.signer)
|
||||
.unwrap();
|
||||
issue.assign([assignee, assignee_two]).unwrap();
|
||||
|
||||
let id = issue.id;
|
||||
let issue = issues.get(&id).unwrap().unwrap();
|
||||
|
|
@ -1117,7 +1074,7 @@ mod test {
|
|||
#[test]
|
||||
fn test_issue_create_and_reassign() {
|
||||
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, &node.signer).unwrap();
|
||||
|
||||
let assignee = Did::from(arbitrary::r#gen::<ActorId>(1));
|
||||
let assignee_two = Did::from(arbitrary::r#gen::<ActorId>(1));
|
||||
|
|
@ -1128,12 +1085,11 @@ mod test {
|
|||
&[],
|
||||
&[assignee, assignee_two],
|
||||
[],
|
||||
&node.signer,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
issue.assign([assignee_two], &node.signer).unwrap();
|
||||
issue.assign([assignee_two], &node.signer).unwrap();
|
||||
issue.assign([assignee_two]).unwrap();
|
||||
issue.assign([assignee_two]).unwrap();
|
||||
issue.reload().unwrap();
|
||||
|
||||
let assignees: Vec<_> = issue.assignees().cloned().collect::<Vec<_>>();
|
||||
|
|
@ -1141,7 +1097,7 @@ mod test {
|
|||
assert_eq!(1, assignees.len());
|
||||
assert!(assignees.contains(&assignee_two));
|
||||
|
||||
issue.assign([], &node.signer).unwrap();
|
||||
issue.assign([]).unwrap();
|
||||
issue.reload().unwrap();
|
||||
|
||||
assert_eq!(0, issue.assignees().count());
|
||||
|
|
@ -1150,7 +1106,7 @@ mod test {
|
|||
#[test]
|
||||
fn test_issue_create_and_get() {
|
||||
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, &node.signer).unwrap();
|
||||
let created = issues
|
||||
.create(
|
||||
cob::Title::new("My first issue").unwrap(),
|
||||
|
|
@ -1158,7 +1114,6 @@ mod test {
|
|||
&[],
|
||||
&[],
|
||||
[],
|
||||
&node.signer,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
|
|
@ -1176,7 +1131,7 @@ mod test {
|
|||
#[test]
|
||||
fn test_issue_create_and_change_state() {
|
||||
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, &node.signer).unwrap();
|
||||
let mut issue = issues
|
||||
.create(
|
||||
cob::Title::new("My first issue").unwrap(),
|
||||
|
|
@ -1184,17 +1139,13 @@ mod test {
|
|||
&[],
|
||||
&[],
|
||||
[],
|
||||
&node.signer,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
issue
|
||||
.lifecycle(
|
||||
State::Closed {
|
||||
.lifecycle(State::Closed {
|
||||
reason: CloseReason::Other,
|
||||
},
|
||||
&node.signer,
|
||||
)
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
let id = issue.id;
|
||||
|
|
@ -1207,7 +1158,7 @@ mod test {
|
|||
}
|
||||
);
|
||||
|
||||
issue.lifecycle(State::Open, &node.signer).unwrap();
|
||||
issue.lifecycle(State::Open).unwrap();
|
||||
let issue = issues.get(&id).unwrap().unwrap();
|
||||
|
||||
assert_eq!(*issue.state(), State::Open);
|
||||
|
|
@ -1216,7 +1167,7 @@ mod test {
|
|||
#[test]
|
||||
fn test_issue_create_and_unassign() {
|
||||
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, &node.signer).unwrap();
|
||||
|
||||
let assignee = Did::from(arbitrary::r#gen::<ActorId>(1));
|
||||
let assignee_two = Did::from(arbitrary::r#gen::<ActorId>(1));
|
||||
|
|
@ -1227,12 +1178,11 @@ mod test {
|
|||
&[],
|
||||
&[assignee, assignee_two],
|
||||
[],
|
||||
&node.signer,
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(2, issue.assignees().count());
|
||||
|
||||
issue.assign([assignee_two], &node.signer).unwrap();
|
||||
issue.assign([assignee_two]).unwrap();
|
||||
issue.reload().unwrap();
|
||||
|
||||
let assignees: Vec<_> = issue.assignees().cloned().collect::<Vec<_>>();
|
||||
|
|
@ -1244,7 +1194,7 @@ mod test {
|
|||
#[test]
|
||||
fn test_issue_edit() {
|
||||
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, &node.signer).unwrap();
|
||||
|
||||
let mut issue = issues
|
||||
.create(
|
||||
|
|
@ -1253,13 +1203,10 @@ mod test {
|
|||
&[],
|
||||
&[],
|
||||
[],
|
||||
&node.signer,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
issue
|
||||
.edit(cob::Title::new("Sorry typo").unwrap(), &node.signer)
|
||||
.unwrap();
|
||||
issue.edit(cob::Title::new("Sorry typo").unwrap()).unwrap();
|
||||
|
||||
let id = issue.id;
|
||||
let issue = issues.get(&id).unwrap().unwrap();
|
||||
|
|
@ -1271,7 +1218,7 @@ mod test {
|
|||
#[test]
|
||||
fn test_issue_edit_description() {
|
||||
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, &node.signer).unwrap();
|
||||
let mut issue = issues
|
||||
.create(
|
||||
cob::Title::new("My first issue").unwrap(),
|
||||
|
|
@ -1279,12 +1226,11 @@ mod test {
|
|||
&[],
|
||||
&[],
|
||||
[],
|
||||
&node.signer,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
issue
|
||||
.edit_description("Bob Loblaw law blog", vec![], &node.signer)
|
||||
.edit_description("Bob Loblaw law blog", vec![])
|
||||
.unwrap();
|
||||
|
||||
let id = issue.id;
|
||||
|
|
@ -1297,7 +1243,7 @@ mod test {
|
|||
#[test]
|
||||
fn test_issue_react() {
|
||||
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, &node.signer).unwrap();
|
||||
let mut issue = issues
|
||||
.create(
|
||||
cob::Title::new("My first issue").unwrap(),
|
||||
|
|
@ -1305,14 +1251,13 @@ mod test {
|
|||
&[],
|
||||
&[],
|
||||
[],
|
||||
&node.signer,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let (comment, _) = issue.root();
|
||||
let comment = *comment;
|
||||
let reaction = Reaction::new('🥳').unwrap();
|
||||
issue.react(comment, reaction, true, &node.signer).unwrap();
|
||||
issue.react(comment, reaction, true).unwrap();
|
||||
|
||||
let id = issue.id;
|
||||
let issue = issues.get(&id).unwrap().unwrap();
|
||||
|
|
@ -1327,7 +1272,7 @@ mod test {
|
|||
#[test]
|
||||
fn test_issue_reply() {
|
||||
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, &node.signer).unwrap();
|
||||
let mut issue = issues
|
||||
.create(
|
||||
cob::Title::new("My first issue").unwrap(),
|
||||
|
|
@ -1335,18 +1280,13 @@ mod test {
|
|||
&[],
|
||||
&[],
|
||||
[],
|
||||
&node.signer,
|
||||
)
|
||||
.unwrap();
|
||||
let (root, _) = issue.root();
|
||||
let root = *root;
|
||||
|
||||
let c1 = issue
|
||||
.comment("Hi hi hi.", root, vec![], &node.signer)
|
||||
.unwrap();
|
||||
let c2 = issue
|
||||
.comment("Ha ha ha.", root, vec![], &node.signer)
|
||||
.unwrap();
|
||||
let c1 = issue.comment("Hi hi hi.", root, vec![]).unwrap();
|
||||
let c2 = issue.comment("Ha ha ha.", root, vec![]).unwrap();
|
||||
|
||||
let id = issue.id;
|
||||
let mut issue = issues.get_mut(&id).unwrap();
|
||||
|
|
@ -1356,14 +1296,10 @@ mod test {
|
|||
assert_eq!(reply1.body(), "Hi hi hi.");
|
||||
assert_eq!(reply2.body(), "Ha ha ha.");
|
||||
|
||||
issue.comment("Re: Hi.", c1, vec![], &node.signer).unwrap();
|
||||
issue.comment("Re: Ha.", c2, vec![], &node.signer).unwrap();
|
||||
issue
|
||||
.comment("Re: Ha. Ha.", c2, vec![], &node.signer)
|
||||
.unwrap();
|
||||
issue
|
||||
.comment("Re: Ha. Ha. Ha.", c2, vec![], &node.signer)
|
||||
.unwrap();
|
||||
issue.comment("Re: Hi.", c1, vec![]).unwrap();
|
||||
issue.comment("Re: Ha.", c2, vec![]).unwrap();
|
||||
issue.comment("Re: Ha. Ha.", c2, vec![]).unwrap();
|
||||
issue.comment("Re: Ha. Ha. Ha.", c2, vec![]).unwrap();
|
||||
|
||||
let issue = issues.get(&id).unwrap().unwrap();
|
||||
|
||||
|
|
@ -1382,7 +1318,7 @@ mod test {
|
|||
#[test]
|
||||
fn test_issue_label() {
|
||||
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, &node.signer).unwrap();
|
||||
let bug_label = Label::new("bug").unwrap();
|
||||
let ux_label = Label::new("ux").unwrap();
|
||||
let wontfix_label = Label::new("wontfix").unwrap();
|
||||
|
|
@ -1393,18 +1329,12 @@ mod test {
|
|||
std::slice::from_ref(&ux_label),
|
||||
&[],
|
||||
[],
|
||||
&node.signer,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
issue.label([ux_label.clone(), bug_label.clone()]).unwrap();
|
||||
issue
|
||||
.label([ux_label.clone(), bug_label.clone()], &node.signer)
|
||||
.unwrap();
|
||||
issue
|
||||
.label(
|
||||
[ux_label.clone(), bug_label.clone(), wontfix_label.clone()],
|
||||
&node.signer,
|
||||
)
|
||||
.label([ux_label.clone(), bug_label.clone(), wontfix_label.clone()])
|
||||
.unwrap();
|
||||
|
||||
let id = issue.id;
|
||||
|
|
@ -1420,7 +1350,7 @@ mod test {
|
|||
fn test_issue_comment() {
|
||||
let test::setup::NodeWithRepo { node, repo, .. } = test::setup::NodeWithRepo::default();
|
||||
let author = *node.signer.public_key();
|
||||
let mut issues = Cache::no_cache(&*repo).unwrap();
|
||||
let mut issues = Cache::no_cache(&*repo, &node.signer).unwrap();
|
||||
let mut issue = issues
|
||||
.create(
|
||||
cob::Title::new("My first issue").unwrap(),
|
||||
|
|
@ -1428,7 +1358,6 @@ mod test {
|
|||
&[],
|
||||
&[],
|
||||
[],
|
||||
&node.signer,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
|
|
@ -1436,12 +1365,8 @@ mod test {
|
|||
let (c0, _) = issue.root();
|
||||
let c0 = *c0;
|
||||
|
||||
issue
|
||||
.comment("Ho ho ho.", c0, vec![], &node.signer)
|
||||
.unwrap();
|
||||
issue
|
||||
.comment("Ha ha ha.", c0, vec![], &node.signer)
|
||||
.unwrap();
|
||||
issue.comment("Ho ho ho.", c0, vec![]).unwrap();
|
||||
issue.comment("Ha ha ha.", c0, vec![]).unwrap();
|
||||
|
||||
let id = issue.id;
|
||||
let issue = issues.get(&id).unwrap().unwrap();
|
||||
|
|
@ -1460,7 +1385,7 @@ mod test {
|
|||
#[test]
|
||||
fn test_issue_comment_redact() {
|
||||
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, &node.signer).unwrap();
|
||||
let mut issue = issues
|
||||
.create(
|
||||
cob::Title::new("My first issue").unwrap(),
|
||||
|
|
@ -1468,7 +1393,6 @@ mod test {
|
|||
&[],
|
||||
&[],
|
||||
[],
|
||||
&node.signer,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
|
|
@ -1476,17 +1400,15 @@ mod test {
|
|||
let (c0, _) = issue.root();
|
||||
let c0 = *c0;
|
||||
|
||||
let comment = issue
|
||||
.comment("Ho ho ho.", c0, vec![], &node.signer)
|
||||
.unwrap();
|
||||
let comment = issue.comment("Ho ho ho.", c0, vec![]).unwrap();
|
||||
issue.reload().unwrap();
|
||||
assert_eq!(issue.comments().count(), 2);
|
||||
|
||||
issue.redact_comment(comment, &node.signer).unwrap();
|
||||
issue.redact_comment(comment).unwrap();
|
||||
assert_eq!(issue.comments().count(), 1);
|
||||
|
||||
// Can't redact root comment.
|
||||
issue.redact_comment(*issue.id, &node.signer).unwrap_err();
|
||||
issue.redact_comment(*issue.id).unwrap_err();
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
|
@ -1508,36 +1430,15 @@ mod test {
|
|||
#[test]
|
||||
fn test_issue_all() {
|
||||
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, &node.signer).unwrap();
|
||||
issues
|
||||
.create(
|
||||
cob::Title::new("First").unwrap(),
|
||||
"Blah",
|
||||
&[],
|
||||
&[],
|
||||
[],
|
||||
&node.signer,
|
||||
)
|
||||
.create(cob::Title::new("First").unwrap(), "Blah", &[], &[], [])
|
||||
.unwrap();
|
||||
issues
|
||||
.create(
|
||||
cob::Title::new("Second").unwrap(),
|
||||
"Blah",
|
||||
&[],
|
||||
&[],
|
||||
[],
|
||||
&node.signer,
|
||||
)
|
||||
.create(cob::Title::new("Second").unwrap(), "Blah", &[], &[], [])
|
||||
.unwrap();
|
||||
issues
|
||||
.create(
|
||||
cob::Title::new("Third").unwrap(),
|
||||
"Blah",
|
||||
&[],
|
||||
&[],
|
||||
[],
|
||||
&node.signer,
|
||||
)
|
||||
.create(cob::Title::new("Third").unwrap(), "Blah", &[], &[], [])
|
||||
.unwrap();
|
||||
|
||||
let issues = issues
|
||||
|
|
@ -1557,7 +1458,7 @@ mod test {
|
|||
#[test]
|
||||
fn test_issue_multilines() {
|
||||
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, &node.signer).unwrap();
|
||||
let created = issues
|
||||
.create(
|
||||
cob::Title::new("My first issue").unwrap(),
|
||||
|
|
@ -1565,7 +1466,6 @@ mod test {
|
|||
&[],
|
||||
&[],
|
||||
[],
|
||||
&node.signer,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
|
|
@ -1583,7 +1483,7 @@ mod test {
|
|||
#[test]
|
||||
fn test_embeds() {
|
||||
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, &node.signer).unwrap();
|
||||
|
||||
let content1 = repo.backend.blob(b"<html>Hello World!</html>").unwrap();
|
||||
let content2 = repo.backend.blob(b"<html>Hello Radicle!</html>").unwrap();
|
||||
|
|
@ -1608,17 +1508,11 @@ mod test {
|
|||
&[],
|
||||
&[],
|
||||
[embed1.clone(), embed2.clone()],
|
||||
&node.signer,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
issue
|
||||
.comment(
|
||||
"Here's a binary file",
|
||||
*issue.id,
|
||||
[embed3.clone()],
|
||||
&node.signer,
|
||||
)
|
||||
.comment("Here's a binary file", *issue.id, [embed3.clone()])
|
||||
.unwrap();
|
||||
|
||||
issue.reload().unwrap();
|
||||
|
|
@ -1642,7 +1536,7 @@ mod test {
|
|||
#[test]
|
||||
fn test_embeds_edit() {
|
||||
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, &node.signer).unwrap();
|
||||
|
||||
let content1 = repo.backend.blob(b"<html>Hello World!</html>").unwrap();
|
||||
let content1_edited = repo.backend.blob(b"<html>Hello Radicle!</html>").unwrap();
|
||||
|
|
@ -1667,13 +1561,12 @@ mod test {
|
|||
&[],
|
||||
&[],
|
||||
[embed1, embed2],
|
||||
&node.signer,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
issue.reload().unwrap();
|
||||
issue
|
||||
.edit_description("My first issue", [embed1_edited.clone()], &node.signer)
|
||||
.edit_description("My first issue", [embed1_edited.clone()])
|
||||
.unwrap();
|
||||
issue.reload().unwrap();
|
||||
|
||||
|
|
@ -1691,7 +1584,7 @@ mod test {
|
|||
#[test]
|
||||
fn test_invalid_actions() {
|
||||
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, &node.signer).unwrap();
|
||||
let mut issue = issues
|
||||
.create(
|
||||
cob::Title::new("My first issue").unwrap(),
|
||||
|
|
@ -1699,14 +1592,11 @@ mod test {
|
|||
&[],
|
||||
&[],
|
||||
[],
|
||||
&node.signer,
|
||||
)
|
||||
.unwrap();
|
||||
let missing = arbitrary::oid();
|
||||
|
||||
issue
|
||||
.comment("Invalid", missing, [], &node.signer)
|
||||
.unwrap_err();
|
||||
issue.comment("Invalid", missing, []).unwrap_err();
|
||||
assert_eq!(issue.comments().count(), 1);
|
||||
issue.reload().unwrap();
|
||||
assert_eq!(issue.comments().count(), 1);
|
||||
|
|
@ -1725,7 +1615,7 @@ mod test {
|
|||
#[test]
|
||||
fn test_invalid_tx() {
|
||||
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, &node.signer).unwrap();
|
||||
let mut issue = issues
|
||||
.create(
|
||||
cob::Title::new("My first issue").unwrap(),
|
||||
|
|
@ -1733,7 +1623,6 @@ mod test {
|
|||
&[],
|
||||
&[],
|
||||
[],
|
||||
&node.signer,
|
||||
)
|
||||
.unwrap();
|
||||
let missing = arbitrary::oid();
|
||||
|
|
@ -1742,7 +1631,7 @@ mod test {
|
|||
// Even creating it via a transaction will trigger an error.
|
||||
let mut tx = Transaction::<Issue, _>::default();
|
||||
tx.comment("Invalid comment", missing, vec![]).unwrap();
|
||||
tx.commit("Add comment", issue.id, &mut issue.store.raw, &node.signer)
|
||||
tx.commit("Add comment", issue.id, &mut issue.store.raw)
|
||||
.unwrap_err();
|
||||
|
||||
issue.reload().unwrap();
|
||||
|
|
@ -1752,7 +1641,7 @@ mod test {
|
|||
#[test]
|
||||
fn test_invalid_tx_reference() {
|
||||
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, &node.signer).unwrap();
|
||||
let issue = issues
|
||||
.create(
|
||||
cob::Title::new("My first issue").unwrap(),
|
||||
|
|
@ -1760,7 +1649,6 @@ mod test {
|
|||
&[],
|
||||
&[],
|
||||
[],
|
||||
&node.signer,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
|
|
@ -1783,7 +1671,7 @@ mod test {
|
|||
let identity = repo.identity().unwrap().head();
|
||||
let missing = arbitrary::oid();
|
||||
let type_name = Issue::type_name().clone();
|
||||
let mut issues = Cache::no_cache(&*repo).unwrap();
|
||||
let mut issues = Cache::no_cache(&*repo, &node.signer).unwrap();
|
||||
let mut issue = issues
|
||||
.create(
|
||||
cob::Title::new("My first issue").unwrap(),
|
||||
|
|
@ -1791,7 +1679,6 @@ mod test {
|
|||
&[],
|
||||
&[],
|
||||
[],
|
||||
&node.signer,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
|
|
@ -1848,9 +1735,7 @@ mod test {
|
|||
|
||||
// Additionally, when adding a *valid* comment, it does not build upon the bad operation.
|
||||
issue.reload().unwrap();
|
||||
issue
|
||||
.comment("Valid comment", *issue.id, vec![], &node.signer)
|
||||
.unwrap();
|
||||
issue.comment("Valid comment", *issue.id, vec![]).unwrap();
|
||||
issue.reload().unwrap();
|
||||
assert_eq!(issue.comments().count(), 2);
|
||||
assert_eq!(issue.thread.timeline().count(), 2);
|
||||
|
|
@ -1864,10 +1749,13 @@ mod test {
|
|||
assert_eq!(cob.history.len(), 3);
|
||||
assert_eq!(cob.object.len(), 3);
|
||||
|
||||
// If Eve now writes a valid comment via the `Issue` type, it will overwrite her invalid
|
||||
let mut eve_issues = Cache::no_cache(&*repo, &eve).unwrap();
|
||||
let mut eve_issue = eve_issues.get_mut(issue.id()).unwrap();
|
||||
|
||||
// If Eve now writes a valid comment via the `IssueMut` type, it will overwrite her invalid
|
||||
// one, since it won't be loaded as a tip.
|
||||
issue
|
||||
.comment("Eve's comment", *issue.id, vec![], &eve)
|
||||
eve_issue
|
||||
.comment("Eve's comment", *issue.id, vec![])
|
||||
.unwrap();
|
||||
|
||||
let cob = cob::get::<NonEmpty<cob::Entry>, _>(&*repo, &type_name, issue.id())
|
||||
|
|
@ -1876,7 +1764,7 @@ mod test {
|
|||
|
||||
// There are three nodes still, but they are all valid comments.
|
||||
// The invalid comment of Eve was replaced with a valid one.
|
||||
assert_eq!(issue.comments().count(), 3);
|
||||
assert_eq!(eve_issue.comments().count(), 3);
|
||||
assert_eq!(cob.history.len(), 3);
|
||||
assert_eq!(cob.object.len(), 3);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,9 +8,9 @@ use crate::cob;
|
|||
use crate::cob::cache;
|
||||
use crate::cob::cache::{Remove, StoreReader, StoreWriter, Update};
|
||||
use crate::cob::store;
|
||||
use crate::cob::store::access::{ReadOnly, WriteAs};
|
||||
use crate::cob::{Embed, Label, ObjectId, TypeName, Uri};
|
||||
use crate::node::NodeId;
|
||||
use crate::node::device::Device;
|
||||
use crate::prelude::{Did, RepoId};
|
||||
use crate::storage::{HasRepoId, ReadRepository, RepositoryError, SignRepository, WriteRepository};
|
||||
|
||||
|
|
@ -77,39 +77,45 @@ impl<T> IssuesMut for T where T: Issues + Update<Issue> + Remove<Issue> {}
|
|||
/// The `store` is used for the main storage when performing a
|
||||
/// write-through. It is also used for identifying which `RepoId` is
|
||||
/// being used for the `cache`.
|
||||
pub struct Cache<R, C> {
|
||||
store: R,
|
||||
pub struct Cache<'a, Repo, Access, C> {
|
||||
store: super::Issues<'a, Repo, Access>,
|
||||
cache: C,
|
||||
}
|
||||
|
||||
impl<R, C> Cache<R, C> {
|
||||
pub fn new(store: R, cache: C) -> Self {
|
||||
impl<'a, Repo, Access, C> Cache<'a, Repo, Access, C> {
|
||||
pub fn new(store: super::Issues<'a, Repo, Access>, cache: C) -> Self {
|
||||
Self { store, cache }
|
||||
}
|
||||
}
|
||||
|
||||
pub fn rid(&self) -> RepoId
|
||||
where
|
||||
R: HasRepoId,
|
||||
{
|
||||
impl<'a, Repo, Access, C> HasRepoId for Cache<'a, Repo, Access, C>
|
||||
where
|
||||
Repo: HasRepoId,
|
||||
{
|
||||
fn rid(&self) -> RepoId {
|
||||
self.store.rid()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, R, C> Cache<super::Issues<'a, R>, C> {
|
||||
impl<'a, 'b, Repo, Signer, C> Cache<'a, Repo, WriteAs<'b, Signer>, C>
|
||||
where
|
||||
Signer: crypto::signature::Keypair<VerifyingKey = crypto::PublicKey>,
|
||||
Signer: crypto::signature::Signer<crypto::Signature>,
|
||||
Signer: crypto::signature::Signer<crypto::ssh::ExtendedSignature>,
|
||||
Signer: crypto::signature::Verifier<crypto::Signature>,
|
||||
{
|
||||
/// Create a new [`Issue`] using the [`super::Issues`] as the
|
||||
/// main storage, and writing the update to the `cache`.
|
||||
pub fn create<'g, G>(
|
||||
pub fn create<'g>(
|
||||
&'g mut self,
|
||||
title: cob::Title,
|
||||
description: impl ToString,
|
||||
labels: &[Label],
|
||||
assignees: &[Did],
|
||||
embeds: impl IntoIterator<Item = Embed<Uri>>,
|
||||
signer: &Device<G>,
|
||||
) -> Result<IssueMut<'a, 'g, R, C>, super::Error>
|
||||
) -> Result<IssueMut<'a, 'b, 'g, Repo, Signer, C>, super::Error>
|
||||
where
|
||||
R: ReadRepository + WriteRepository + cob::Store<Namespace = NodeId>,
|
||||
G: crypto::signature::Signer<crypto::Signature>,
|
||||
Repo: ReadRepository + WriteRepository + cob::Store<Namespace = NodeId>,
|
||||
C: Update<Issue>,
|
||||
{
|
||||
self.store.create(
|
||||
|
|
@ -119,19 +125,17 @@ impl<'a, R, C> Cache<super::Issues<'a, R>, C> {
|
|||
assignees,
|
||||
embeds,
|
||||
&mut self.cache,
|
||||
signer,
|
||||
)
|
||||
}
|
||||
|
||||
/// Remove the given `id` from the [`super::Issues`] storage, and
|
||||
/// removing the entry from the `cache`.
|
||||
pub fn remove<G>(&mut self, id: &IssueId, signer: &Device<G>) -> Result<(), super::Error>
|
||||
pub fn remove(&mut self, id: &IssueId) -> Result<(), super::Error>
|
||||
where
|
||||
G: crypto::signature::Signer<crypto::Signature>,
|
||||
R: ReadRepository + SignRepository + cob::Store<Namespace = NodeId>,
|
||||
Repo: ReadRepository + SignRepository + cob::Store<Namespace = NodeId>,
|
||||
C: Remove<Issue>,
|
||||
{
|
||||
self.store.remove(id, signer)?;
|
||||
self.store.raw.remove(id)?;
|
||||
self.cache
|
||||
.remove(id)
|
||||
.map_err(|e| super::Error::CacheRemove {
|
||||
|
|
@ -140,12 +144,17 @@ impl<'a, R, C> Cache<super::Issues<'a, R>, C> {
|
|||
})?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, Repo, Access, C> Cache<'a, Repo, Access, C>
|
||||
where
|
||||
Access: cob::store::access::Access,
|
||||
{
|
||||
/// Read the given `id` from the [`super::Issues`] store and
|
||||
/// writing it to the `cache`.
|
||||
pub fn write(&mut self, id: &IssueId) -> Result<(), super::Error>
|
||||
where
|
||||
R: ReadRepository + cob::Store,
|
||||
Repo: ReadRepository + cob::Store<Namespace = NodeId>,
|
||||
C: Update<Issue>,
|
||||
{
|
||||
let issue = self
|
||||
|
|
@ -171,7 +180,7 @@ impl<'a, R, C> Cache<super::Issues<'a, R>, C> {
|
|||
on_issue: impl Fn(&Result<(IssueId, Issue), store::Error>, &cache::Progress) -> ControlFlow<()>,
|
||||
) -> Result<(), super::Error>
|
||||
where
|
||||
R: ReadRepository + cob::Store,
|
||||
Repo: ReadRepository + cob::Store<Namespace = NodeId>,
|
||||
C: Update<Issue> + Remove<Issue>,
|
||||
{
|
||||
// Start by clearing the cache. This will get rid of issues that are cached but
|
||||
|
|
@ -198,14 +207,15 @@ impl<'a, R, C> Cache<super::Issues<'a, R>, C> {
|
|||
}
|
||||
}
|
||||
|
||||
impl<'a, R> Cache<super::Issues<'a, R>, cache::NoCache>
|
||||
impl<'a, 'b, Repo, Signer> Cache<'a, Repo, WriteAs<'b, Signer>, cache::NoCache>
|
||||
where
|
||||
R: ReadRepository + cob::Store<Namespace = NodeId>,
|
||||
Repo: WriteRepository + cob::Store<Namespace = NodeId>,
|
||||
Signer: crypto::signature::Signer<crypto::Signature>,
|
||||
{
|
||||
/// Get a `Cache` that does no write-through modifications and
|
||||
/// uses the [`super::Issues`] store for all reads and writes.
|
||||
pub fn no_cache(repository: &'a R) -> Result<Self, RepositoryError> {
|
||||
let store = super::Issues::open(repository)?;
|
||||
pub fn no_cache(repository: &'a Repo, signer: &'b Signer) -> Result<Self, RepositoryError> {
|
||||
let store = super::Issues::open(repository, WriteAs::new(signer))?;
|
||||
Ok(Self {
|
||||
store,
|
||||
cache: cache::NoCache,
|
||||
|
|
@ -216,7 +226,7 @@ where
|
|||
pub fn get_mut<'g>(
|
||||
&'g mut self,
|
||||
id: &ObjectId,
|
||||
) -> Result<IssueMut<'a, 'g, R, cache::NoCache>, super::Error> {
|
||||
) -> Result<IssueMut<'a, 'b, 'g, Repo, Signer, cache::NoCache>, super::Error> {
|
||||
let issue = self
|
||||
.store
|
||||
.get(id)?
|
||||
|
|
@ -231,28 +241,28 @@ where
|
|||
}
|
||||
}
|
||||
|
||||
impl<R> Cache<R, StoreReader> {
|
||||
pub fn reader(store: R, cache: StoreReader) -> Self {
|
||||
impl<'a, Repo> Cache<'a, Repo, ReadOnly, StoreReader> {
|
||||
pub fn reader(store: super::Issues<'a, Repo, ReadOnly>, cache: StoreReader) -> Self {
|
||||
Self { store, cache }
|
||||
}
|
||||
}
|
||||
|
||||
impl<R> Cache<R, StoreWriter> {
|
||||
pub fn open(store: R, cache: StoreWriter) -> Self {
|
||||
impl<'a, Repo, Access> Cache<'a, Repo, Access, StoreWriter> {
|
||||
pub fn open(store: super::Issues<'a, Repo, Access>, cache: StoreWriter) -> Self {
|
||||
Self { store, cache }
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, R> Cache<super::Issues<'a, R>, StoreWriter>
|
||||
impl<'a, 'b, Repo, Signer> Cache<'a, Repo, WriteAs<'b, Signer>, StoreWriter>
|
||||
where
|
||||
R: ReadRepository + cob::Store,
|
||||
Repo: ReadRepository + cob::Store<Namespace = NodeId>,
|
||||
{
|
||||
/// Get the [`IssueMut`], identified by `id`, using the
|
||||
/// `StoreWriter` for retrieving the `Issue`.
|
||||
pub fn get_mut<'g>(
|
||||
&'g mut self,
|
||||
id: &ObjectId,
|
||||
) -> Result<IssueMut<'a, 'g, R, StoreWriter>, Error> {
|
||||
) -> Result<IssueMut<'a, 'b, 'g, Repo, Signer, StoreWriter>, Error> {
|
||||
let issue = Issues::get(self, id)?
|
||||
.ok_or_else(move || Error::NotFound(super::TYPENAME.clone(), *id))?;
|
||||
|
||||
|
|
@ -265,7 +275,7 @@ where
|
|||
}
|
||||
}
|
||||
|
||||
impl<R, C> cache::Update<Issue> for Cache<R, C>
|
||||
impl<'a, Repo, Access, C> cache::Update<Issue> for Cache<'a, Repo, Access, C>
|
||||
where
|
||||
C: cache::Update<Issue>,
|
||||
{
|
||||
|
|
@ -282,7 +292,7 @@ where
|
|||
}
|
||||
}
|
||||
|
||||
impl<R, C> cache::Remove<Issue> for Cache<R, C>
|
||||
impl<'a, Repo, Access, C> cache::Remove<Issue> for Cache<'a, Repo, Access, C>
|
||||
where
|
||||
C: cache::Remove<Issue>,
|
||||
{
|
||||
|
|
@ -377,9 +387,10 @@ impl Iterator for NoCacheIter<'_> {
|
|||
}
|
||||
}
|
||||
|
||||
impl<R> Issues for Cache<super::Issues<'_, R>, cache::NoCache>
|
||||
impl<'a, Repo, Access> Issues for Cache<'a, Repo, Access, cache::NoCache>
|
||||
where
|
||||
R: ReadRepository + cob::Store,
|
||||
Repo: ReadRepository + cob::Store<Namespace = NodeId>,
|
||||
Access: store::access::Access,
|
||||
{
|
||||
type Error = super::Error;
|
||||
type Iter<'b>
|
||||
|
|
@ -459,9 +470,9 @@ impl Iterator for IssuesIter<'_> {
|
|||
}
|
||||
}
|
||||
|
||||
impl<R> Issues for Cache<R, StoreWriter>
|
||||
impl<'a, Repo, Access> Issues for Cache<'a, Repo, Access, StoreWriter>
|
||||
where
|
||||
R: HasRepoId,
|
||||
Repo: HasRepoId,
|
||||
{
|
||||
type Error = Error;
|
||||
type Iter<'b>
|
||||
|
|
@ -486,9 +497,9 @@ where
|
|||
}
|
||||
}
|
||||
|
||||
impl<R> Issues for Cache<R, StoreReader>
|
||||
impl<'a, Repo, Access> Issues for Cache<'a, Repo, Access, StoreReader>
|
||||
where
|
||||
R: HasRepoId,
|
||||
Repo: HasRepoId,
|
||||
{
|
||||
type Error = Error;
|
||||
type Iter<'b>
|
||||
|
|
@ -618,14 +629,17 @@ mod tests {
|
|||
|
||||
use crate::cob::cache::{Store, Update, Write};
|
||||
use crate::cob::migrate;
|
||||
use crate::cob::store::access::ReadOnly;
|
||||
use crate::cob::thread::Thread;
|
||||
use crate::issue::{CloseReason, Issue, IssueCounts, IssueId, State};
|
||||
use crate::storage::HasRepoId as _;
|
||||
use crate::test::arbitrary;
|
||||
use crate::test::storage::MockRepository;
|
||||
|
||||
use super::{Cache, Issues};
|
||||
|
||||
fn memory(store: MockRepository) -> Cache<MockRepository, Store<Write>> {
|
||||
fn memory<'a>(store: &'a MockRepository) -> Cache<'a, MockRepository, ReadOnly, Store<Write>> {
|
||||
let store = super::super::Issues::open(store, ReadOnly).unwrap();
|
||||
let cache = Store::<Write>::memory()
|
||||
.unwrap()
|
||||
.with_migrations(migrate::ignore)
|
||||
|
|
@ -636,7 +650,7 @@ mod tests {
|
|||
#[test]
|
||||
fn test_is_empty() {
|
||||
let repo = arbitrary::r#gen::<MockRepository>(1);
|
||||
let mut cache = memory(repo);
|
||||
let mut cache = memory(&repo);
|
||||
assert!(cache.is_empty().unwrap());
|
||||
|
||||
let issue = Issue::new(Thread::default());
|
||||
|
|
@ -658,7 +672,7 @@ mod tests {
|
|||
#[test]
|
||||
fn test_counts() {
|
||||
let repo = arbitrary::r#gen::<MockRepository>(1);
|
||||
let mut cache = memory(repo);
|
||||
let mut cache = memory(&repo);
|
||||
let n_open = arbitrary::r#gen::<u8>(0);
|
||||
let n_closed = arbitrary::r#gen::<u8>(1);
|
||||
let open_ids = (0..n_open)
|
||||
|
|
@ -699,7 +713,7 @@ mod tests {
|
|||
#[test]
|
||||
fn test_get() {
|
||||
let repo = arbitrary::r#gen::<MockRepository>(1);
|
||||
let mut cache = memory(repo);
|
||||
let mut cache = memory(&repo);
|
||||
let ids = (0..arbitrary::r#gen::<u8>(1))
|
||||
.map(|_| IssueId::from(arbitrary::oid()))
|
||||
.collect::<BTreeSet<IssueId>>();
|
||||
|
|
@ -734,7 +748,7 @@ mod tests {
|
|||
#[test]
|
||||
fn test_list() {
|
||||
let repo = arbitrary::r#gen::<MockRepository>(1);
|
||||
let mut cache = memory(repo);
|
||||
let mut cache = memory(&repo);
|
||||
let ids = (0..arbitrary::r#gen::<u8>(1))
|
||||
.map(|_| IssueId::from(arbitrary::oid()))
|
||||
.collect::<BTreeSet<IssueId>>();
|
||||
|
|
@ -764,7 +778,7 @@ mod tests {
|
|||
#[test]
|
||||
fn test_list_by_status() {
|
||||
let repo = arbitrary::r#gen::<MockRepository>(1);
|
||||
let mut cache = memory(repo);
|
||||
let mut cache = memory(&repo);
|
||||
let ids = (0..arbitrary::r#gen::<u8>(1))
|
||||
.map(|_| IssueId::from(arbitrary::oid()))
|
||||
.collect::<BTreeSet<IssueId>>();
|
||||
|
|
@ -794,7 +808,7 @@ mod tests {
|
|||
#[test]
|
||||
fn test_remove() {
|
||||
let repo = arbitrary::r#gen::<MockRepository>(1);
|
||||
let mut cache = memory(repo);
|
||||
let mut cache = memory(&repo);
|
||||
let ids = (0..arbitrary::r#gen::<u8>(1))
|
||||
.map(|_| IssueId::from(arbitrary::oid()))
|
||||
.collect::<BTreeSet<IssueId>>();
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load Diff
|
|
@ -8,9 +8,9 @@ use crate::cob;
|
|||
use crate::cob::cache::{self, StoreReader};
|
||||
use crate::cob::cache::{Remove, StoreWriter, Update};
|
||||
use crate::cob::store;
|
||||
use crate::cob::store::access::{ReadOnly, WriteAs};
|
||||
use crate::cob::{Label, ObjectId, TypeName};
|
||||
use crate::git;
|
||||
use crate::node::device::Device;
|
||||
use crate::prelude::RepoId;
|
||||
use crate::storage::{HasRepoId, ReadRepository, RepositoryError, SignRepository, WriteRepository};
|
||||
|
||||
|
|
@ -87,28 +87,36 @@ impl<T> PatchesMut for T where T: Patches + Update<Patch> + Remove<Patch> {}
|
|||
/// The `store` is used for the main storage when performing a
|
||||
/// write-through. It is also used for identifying which `RepoId` is
|
||||
/// being used for the `cache`.
|
||||
pub struct Cache<R, C> {
|
||||
pub(super) store: R,
|
||||
pub struct Cache<'a, Repo, Access, C> {
|
||||
pub(super) store: super::Patches<'a, Repo, Access>,
|
||||
pub(super) cache: C,
|
||||
}
|
||||
|
||||
impl<R, C> Cache<R, C> {
|
||||
pub fn new(store: R, cache: C) -> Self {
|
||||
impl<'a, Repo, Access, C> Cache<'a, Repo, Access, C> {
|
||||
pub fn new(store: super::Patches<'a, Repo, Access>, cache: C) -> Self {
|
||||
Self { store, cache }
|
||||
}
|
||||
}
|
||||
|
||||
pub fn rid(&self) -> RepoId
|
||||
where
|
||||
R: HasRepoId,
|
||||
{
|
||||
impl<'a, Repo, Access, C> HasRepoId for Cache<'a, Repo, Access, C>
|
||||
where
|
||||
Repo: HasRepoId,
|
||||
{
|
||||
fn rid(&self) -> RepoId {
|
||||
self.store.rid()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, R, C> Cache<super::Patches<'a, R>, C> {
|
||||
impl<'a, 'b, Repo, Signer, C> Cache<'a, Repo, WriteAs<'b, Signer>, C>
|
||||
where
|
||||
Signer: crypto::signature::Keypair<VerifyingKey = crypto::PublicKey>,
|
||||
Signer: crypto::signature::Signer<crypto::Signature>,
|
||||
Signer: crypto::signature::Signer<crypto::ssh::ExtendedSignature>,
|
||||
Signer: crypto::signature::Verifier<crypto::Signature>,
|
||||
{
|
||||
/// Create a new [`Patch`] using the [`super::Patches`] as the
|
||||
/// main storage, and writing the update to the `cache`.
|
||||
pub fn create<'g, G>(
|
||||
pub fn create<'g>(
|
||||
&'g mut self,
|
||||
title: cob::Title,
|
||||
description: impl ToString,
|
||||
|
|
@ -116,11 +124,9 @@ impl<'a, R, C> Cache<super::Patches<'a, R>, C> {
|
|||
base: impl Into<git::Oid>,
|
||||
oid: impl Into<git::Oid>,
|
||||
labels: &[Label],
|
||||
signer: &Device<G>,
|
||||
) -> Result<PatchMut<'a, 'g, R, C>, super::Error>
|
||||
) -> Result<PatchMut<'a, 'b, 'g, Repo, Signer, C>, super::Error>
|
||||
where
|
||||
R: WriteRepository + cob::Store<Namespace = NodeId>,
|
||||
G: crypto::signature::Signer<crypto::Signature>,
|
||||
Repo: WriteRepository + cob::Store<Namespace = NodeId>,
|
||||
C: Update<Patch>,
|
||||
{
|
||||
self.store.create(
|
||||
|
|
@ -131,14 +137,13 @@ impl<'a, R, C> Cache<super::Patches<'a, R>, C> {
|
|||
oid,
|
||||
labels,
|
||||
&mut self.cache,
|
||||
signer,
|
||||
)
|
||||
}
|
||||
|
||||
/// Create a new [`Patch`], in a draft state, using the
|
||||
/// [`super::Patches`] as the main storage, and writing the update
|
||||
/// to the `cache`.
|
||||
pub fn draft<'g, G>(
|
||||
pub fn draft<'g>(
|
||||
&'g mut self,
|
||||
title: cob::Title,
|
||||
description: impl ToString,
|
||||
|
|
@ -146,11 +151,9 @@ impl<'a, R, C> Cache<super::Patches<'a, R>, C> {
|
|||
base: impl Into<git::Oid>,
|
||||
oid: impl Into<git::Oid>,
|
||||
labels: &[Label],
|
||||
signer: &Device<G>,
|
||||
) -> Result<PatchMut<'a, 'g, R, C>, super::Error>
|
||||
) -> Result<PatchMut<'a, 'b, 'g, Repo, Signer, C>, super::Error>
|
||||
where
|
||||
R: WriteRepository + cob::Store<Namespace = NodeId>,
|
||||
G: crypto::signature::Signer<crypto::Signature>,
|
||||
Repo: WriteRepository + cob::Store<Namespace = NodeId>,
|
||||
C: Update<Patch>,
|
||||
{
|
||||
self.store.draft(
|
||||
|
|
@ -161,19 +164,17 @@ impl<'a, R, C> Cache<super::Patches<'a, R>, C> {
|
|||
oid,
|
||||
labels,
|
||||
&mut self.cache,
|
||||
signer,
|
||||
)
|
||||
}
|
||||
|
||||
/// Remove the given `id` from the [`super::Patches`] storage, and
|
||||
/// removing the entry from the `cache`.
|
||||
pub fn remove<G>(&mut self, id: &PatchId, signer: &Device<G>) -> Result<(), super::Error>
|
||||
pub fn remove(&mut self, id: &PatchId) -> Result<(), super::Error>
|
||||
where
|
||||
G: crypto::signature::Signer<crypto::Signature>,
|
||||
R: ReadRepository + SignRepository + cob::Store<Namespace = NodeId>,
|
||||
Repo: ReadRepository + SignRepository + cob::Store<Namespace = NodeId>,
|
||||
C: Remove<Patch>,
|
||||
{
|
||||
self.store.remove(id, signer)?;
|
||||
self.store.raw.remove(id)?;
|
||||
self.cache
|
||||
.remove(id)
|
||||
.map_err(|e| super::Error::CacheRemove {
|
||||
|
|
@ -182,12 +183,17 @@ impl<'a, R, C> Cache<super::Patches<'a, R>, C> {
|
|||
})?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, Repo, Access, C> Cache<'a, Repo, Access, C>
|
||||
where
|
||||
Access: cob::store::access::Access,
|
||||
{
|
||||
/// Read the given `id` from the [`super::Patches`] store and
|
||||
/// writing it to the `cache`.
|
||||
pub fn write(&mut self, id: &PatchId) -> Result<(), super::Error>
|
||||
where
|
||||
R: ReadRepository + cob::Store,
|
||||
Repo: ReadRepository + cob::Store<Namespace = NodeId>,
|
||||
C: Update<Patch>,
|
||||
{
|
||||
let issue = self
|
||||
|
|
@ -213,7 +219,7 @@ impl<'a, R, C> Cache<super::Patches<'a, R>, C> {
|
|||
callback: impl Fn(&Result<(PatchId, Patch), store::Error>, &cache::Progress) -> ControlFlow<()>,
|
||||
) -> Result<(), super::Error>
|
||||
where
|
||||
R: ReadRepository + cob::Store,
|
||||
Repo: ReadRepository + cob::Store<Namespace = NodeId>,
|
||||
C: Update<Patch> + Remove<Patch>,
|
||||
{
|
||||
// Start by clearing the cache. This will get rid of patches that are cached but
|
||||
|
|
@ -240,28 +246,28 @@ impl<'a, R, C> Cache<super::Patches<'a, R>, C> {
|
|||
}
|
||||
}
|
||||
|
||||
impl<R> Cache<R, StoreReader> {
|
||||
pub fn reader(store: R, cache: StoreReader) -> Self {
|
||||
impl<'a, Repo> Cache<'a, Repo, ReadOnly, StoreReader> {
|
||||
pub fn reader(store: super::Patches<'a, Repo, ReadOnly>, cache: StoreReader) -> Self {
|
||||
Self { store, cache }
|
||||
}
|
||||
}
|
||||
|
||||
impl<R> Cache<R, StoreWriter> {
|
||||
pub fn open(store: R, cache: StoreWriter) -> Self {
|
||||
impl<'a, Repo, Access> Cache<'a, Repo, Access, StoreWriter> {
|
||||
pub fn open(store: super::Patches<'a, Repo, Access>, cache: StoreWriter) -> Self {
|
||||
Self { store, cache }
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, R> Cache<super::Patches<'a, R>, StoreWriter>
|
||||
impl<'a, 'b, Repo, Signer> Cache<'a, Repo, WriteAs<'b, Signer>, StoreWriter>
|
||||
where
|
||||
R: ReadRepository + cob::Store,
|
||||
Repo: ReadRepository + cob::Store<Namespace = NodeId>,
|
||||
{
|
||||
/// Get the [`PatchMut`], identified by `id`, using the
|
||||
/// `StoreWriter` for retrieving the `Patch`.
|
||||
pub fn get_mut<'g>(
|
||||
&'g mut self,
|
||||
id: &ObjectId,
|
||||
) -> Result<PatchMut<'a, 'g, R, StoreWriter>, Error> {
|
||||
) -> Result<PatchMut<'a, 'b, 'g, Repo, Signer, StoreWriter>, Error> {
|
||||
let patch = Patches::get(self, id)?
|
||||
.ok_or_else(move || Error::NotFound(super::TYPENAME.clone(), *id))?;
|
||||
|
||||
|
|
@ -274,14 +280,14 @@ where
|
|||
}
|
||||
}
|
||||
|
||||
impl<'a, R> Cache<super::Patches<'a, R>, cache::NoCache>
|
||||
impl<'a, 'b, Repo, Signer> Cache<'a, Repo, WriteAs<'b, Signer>, cache::NoCache>
|
||||
where
|
||||
R: ReadRepository + cob::Store<Namespace = NodeId>,
|
||||
Repo: ReadRepository + cob::Store<Namespace = NodeId>,
|
||||
{
|
||||
/// Get a `Cache` that does no write-through modifications and
|
||||
/// uses the [`super::Patches`] store for all reads and writes.
|
||||
pub fn no_cache(repository: &'a R) -> Result<Self, RepositoryError> {
|
||||
let store = super::Patches::open(repository)?;
|
||||
pub fn no_cache(repository: &'a Repo, signer: &'b Signer) -> Result<Self, RepositoryError> {
|
||||
let store = super::Patches::open(repository, WriteAs::new(signer))?;
|
||||
Ok(Self {
|
||||
store,
|
||||
cache: cache::NoCache,
|
||||
|
|
@ -292,7 +298,7 @@ where
|
|||
pub fn get_mut<'g>(
|
||||
&'g mut self,
|
||||
id: &ObjectId,
|
||||
) -> Result<PatchMut<'a, 'g, R, cache::NoCache>, super::Error> {
|
||||
) -> Result<PatchMut<'a, 'b, 'g, Repo, Signer, cache::NoCache>, super::Error> {
|
||||
let patch = self
|
||||
.store
|
||||
.get(id)?
|
||||
|
|
@ -307,7 +313,7 @@ where
|
|||
}
|
||||
}
|
||||
|
||||
impl<R, C> cache::Update<Patch> for Cache<R, C>
|
||||
impl<'a, Repo, Access, C> cache::Update<Patch> for Cache<'a, Repo, Access, C>
|
||||
where
|
||||
C: cache::Update<Patch>,
|
||||
{
|
||||
|
|
@ -324,7 +330,7 @@ where
|
|||
}
|
||||
}
|
||||
|
||||
impl<R, C> cache::Remove<Patch> for Cache<R, C>
|
||||
impl<'a, Repo, Access, C> cache::Remove<Patch> for Cache<'a, Repo, Access, C>
|
||||
where
|
||||
C: cache::Remove<Patch>,
|
||||
{
|
||||
|
|
@ -443,9 +449,9 @@ impl Iterator for PatchesIter<'_> {
|
|||
}
|
||||
}
|
||||
|
||||
impl<R> Patches for Cache<R, StoreReader>
|
||||
impl<'a, Repo, Access> Patches for Cache<'a, Repo, Access, StoreReader>
|
||||
where
|
||||
R: HasRepoId,
|
||||
Repo: HasRepoId,
|
||||
{
|
||||
type Error = Error;
|
||||
type Iter<'b>
|
||||
|
|
@ -486,9 +492,10 @@ impl Iterator for NoCacheIter<'_> {
|
|||
}
|
||||
}
|
||||
|
||||
impl<R> Patches for Cache<super::Patches<'_, R>, cache::NoCache>
|
||||
impl<'a, Repo, Access> Patches for Cache<'a, Repo, Access, cache::NoCache>
|
||||
where
|
||||
R: ReadRepository + cob::Store<Namespace = NodeId>,
|
||||
Repo: ReadRepository + cob::Store<Namespace = NodeId>,
|
||||
Access: store::access::Access,
|
||||
{
|
||||
type Error = super::Error;
|
||||
type Iter<'b>
|
||||
|
|
@ -535,9 +542,10 @@ where
|
|||
}
|
||||
}
|
||||
|
||||
impl<R> Patches for Cache<R, StoreWriter>
|
||||
impl<'a, Repo, Access> Patches for Cache<'a, Repo, Access, StoreWriter>
|
||||
where
|
||||
R: HasRepoId,
|
||||
Repo: HasRepoId + cob::Store<Namespace = NodeId>,
|
||||
Access: store::access::Access,
|
||||
{
|
||||
type Error = Error;
|
||||
type Iter<'b>
|
||||
|
|
@ -711,6 +719,7 @@ mod tests {
|
|||
use radicle_cob::ObjectId;
|
||||
|
||||
use crate::cob::cache::{Store, Update, Write};
|
||||
use crate::cob::store::access::ReadOnly;
|
||||
use crate::cob::thread::{Comment, Thread};
|
||||
use crate::cob::{Author, Title, migrate};
|
||||
use crate::patch::{
|
||||
|
|
@ -718,12 +727,14 @@ mod tests {
|
|||
};
|
||||
use crate::prelude::Did;
|
||||
use crate::profile::env;
|
||||
use crate::storage::HasRepoId as _;
|
||||
use crate::test::arbitrary;
|
||||
use crate::test::storage::MockRepository;
|
||||
|
||||
use super::{Cache, Patches};
|
||||
|
||||
fn memory(store: MockRepository) -> Cache<MockRepository, Store<Write>> {
|
||||
fn memory<'a>(store: &'a MockRepository) -> Cache<'a, MockRepository, ReadOnly, Store<Write>> {
|
||||
let store = super::super::Patches::open(store, ReadOnly).unwrap();
|
||||
let cache = Store::<Write>::memory()
|
||||
.unwrap()
|
||||
.with_migrations(migrate::ignore)
|
||||
|
|
@ -764,7 +775,7 @@ mod tests {
|
|||
#[test]
|
||||
fn test_is_empty() {
|
||||
let repo = arbitrary::r#gen::<MockRepository>(1);
|
||||
let mut cache = memory(repo);
|
||||
let mut cache = memory(&repo);
|
||||
assert!(cache.is_empty().unwrap());
|
||||
|
||||
let patch = Patch::new(
|
||||
|
|
@ -792,7 +803,7 @@ mod tests {
|
|||
#[test]
|
||||
fn test_counts() {
|
||||
let repo = arbitrary::r#gen::<MockRepository>(1);
|
||||
let mut cache = memory(repo);
|
||||
let mut cache = memory(&repo);
|
||||
let n_open = arbitrary::r#gen::<u8>(0);
|
||||
let n_draft = arbitrary::r#gen::<u8>(1);
|
||||
let n_archived = arbitrary::r#gen::<u8>(1);
|
||||
|
|
@ -880,7 +891,7 @@ mod tests {
|
|||
#[test]
|
||||
fn test_get() {
|
||||
let repo = arbitrary::r#gen::<MockRepository>(1);
|
||||
let mut cache = memory(repo);
|
||||
let mut cache = memory(&repo);
|
||||
let ids = (0..arbitrary::r#gen::<u8>(1))
|
||||
.map(|_| PatchId::from(arbitrary::oid()))
|
||||
.collect::<BTreeSet<PatchId>>();
|
||||
|
|
@ -916,7 +927,7 @@ mod tests {
|
|||
#[test]
|
||||
fn test_find_by_revision() {
|
||||
let repo = arbitrary::r#gen::<MockRepository>(1);
|
||||
let mut cache = memory(repo);
|
||||
let mut cache = memory(&repo);
|
||||
let patch_id = PatchId::from(arbitrary::oid());
|
||||
let revisions = (0..arbitrary::r#gen::<NonZeroU8>(1).into())
|
||||
.map(|_| revision())
|
||||
|
|
@ -958,7 +969,7 @@ mod tests {
|
|||
#[test]
|
||||
fn test_list() {
|
||||
let repo = arbitrary::r#gen::<MockRepository>(1);
|
||||
let mut cache = memory(repo);
|
||||
let mut cache = memory(&repo);
|
||||
let ids = (0..arbitrary::r#gen::<u8>(1))
|
||||
.map(|_| PatchId::from(arbitrary::oid()))
|
||||
.collect::<BTreeSet<PatchId>>();
|
||||
|
|
@ -989,7 +1000,7 @@ mod tests {
|
|||
#[test]
|
||||
fn test_list_by_status() {
|
||||
let repo = arbitrary::r#gen::<MockRepository>(1);
|
||||
let mut cache = memory(repo);
|
||||
let mut cache = memory(&repo);
|
||||
let ids = (0..arbitrary::r#gen::<u8>(1))
|
||||
.map(|_| PatchId::from(arbitrary::oid()))
|
||||
.collect::<BTreeSet<PatchId>>();
|
||||
|
|
@ -1020,7 +1031,7 @@ mod tests {
|
|||
#[test]
|
||||
fn test_remove() {
|
||||
let repo = arbitrary::r#gen::<MockRepository>(1);
|
||||
let mut cache = memory(repo);
|
||||
let mut cache = memory(&repo);
|
||||
let ids = (0..arbitrary::r#gen::<u8>(1))
|
||||
.map(|_| PatchId::from(arbitrary::oid()))
|
||||
.collect::<BTreeSet<PatchId>>();
|
||||
|
|
|
|||
|
|
@ -11,12 +11,14 @@ use serde::{Deserialize, Serialize};
|
|||
use crate::cob::op::Op;
|
||||
use crate::cob::{Create, Embed, EntryId, ObjectId, TypeName, Update, Updated, Uri, Version};
|
||||
use crate::git;
|
||||
use crate::node::device::Device;
|
||||
use crate::prelude::*;
|
||||
use crate::storage::SignRepository;
|
||||
use crate::storage::git as storage;
|
||||
use crate::storage::{HasRepoId, SignRepository};
|
||||
use crate::{cob, identity};
|
||||
|
||||
pub mod access;
|
||||
use access::WriteAs;
|
||||
|
||||
pub trait CobAction {
|
||||
/// Parent objects this action depends on. For example, patch revisions
|
||||
/// have the commit objects as their parent.
|
||||
|
|
@ -135,94 +137,147 @@ pub enum Error {
|
|||
ClashingIdentifiers(String, String),
|
||||
}
|
||||
|
||||
/// Storage for collaborative objects of a specific type `T` in a single repository.
|
||||
pub struct Store<'a, T, R> {
|
||||
identity: Option<git::Oid>,
|
||||
repo: &'a R,
|
||||
/// The [`Scope`] of a [`Store`] keeps track of what collaborative object is
|
||||
/// being accessed.
|
||||
///
|
||||
/// For example, a `Scope<Patch>` keeps track of `Patch` access.
|
||||
///
|
||||
/// The type parameter `T` is the collaborative object type.
|
||||
pub struct Scope<'a, T> {
|
||||
witness: PhantomData<T>,
|
||||
type_name: &'a TypeName,
|
||||
}
|
||||
|
||||
impl<T, R> AsRef<R> for Store<'_, T, R> {
|
||||
fn as_ref(&self) -> &R {
|
||||
impl<'a, T: CobWithType> Scope<'a, T> {
|
||||
fn new() -> Self {
|
||||
Self {
|
||||
witness: PhantomData,
|
||||
type_name: T::type_name(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Storage for collaborative objects of a specific type `T` in a single repository.
|
||||
pub struct Store<'a, T, Repo, Access> {
|
||||
identity: Option<git::Oid>,
|
||||
repo: &'a Repo,
|
||||
scope: Scope<'a, T>,
|
||||
access: Access,
|
||||
}
|
||||
|
||||
impl<T, Repo, Access> HasRepoId for Store<'_, T, Repo, Access>
|
||||
where
|
||||
Repo: HasRepoId,
|
||||
{
|
||||
fn rid(&self) -> RepoId {
|
||||
self.repo.rid()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, Repo, Access> AsRef<Repo> for Store<'_, T, Repo, Access> {
|
||||
fn as_ref(&self) -> &Repo {
|
||||
self.repo
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, T, R> Store<'a, T, R>
|
||||
impl<'a, T, Repo, Access> Store<'a, T, Repo, Access>
|
||||
where
|
||||
R: ReadRepository + cob::Store,
|
||||
Repo: ReadRepository + cob::Store<Namespace = NodeId>,
|
||||
Access: access::Access,
|
||||
{
|
||||
/// Open a new generic store.
|
||||
pub fn open_for(type_name: &'a TypeName, repo: &'a R) -> Result<Self, Error> {
|
||||
pub fn open_for(
|
||||
type_name: &'a TypeName,
|
||||
repo: &'a Repo,
|
||||
access: Access,
|
||||
) -> Result<Self, Error> {
|
||||
Ok(Self {
|
||||
repo,
|
||||
identity: None,
|
||||
scope: Scope {
|
||||
witness: PhantomData,
|
||||
type_name,
|
||||
},
|
||||
access,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, T, Repo, Access> Store<'a, T, Repo, Access>
|
||||
where
|
||||
Repo: ReadRepository + cob::Store<Namespace = NodeId>,
|
||||
Access: access::Access,
|
||||
{
|
||||
/// Return a new store with the attached identity.
|
||||
pub fn identity(self, identity: git::Oid) -> Self {
|
||||
Self {
|
||||
repo: self.repo,
|
||||
witness: self.witness,
|
||||
identity: Some(identity),
|
||||
type_name: self.type_name,
|
||||
scope: self.scope,
|
||||
access: self.access,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, T, R> Store<'a, T, R>
|
||||
impl<'a, T, Repo, Access> Store<'a, T, Repo, Access>
|
||||
where
|
||||
R: ReadRepository + cob::Store<Namespace = NodeId>,
|
||||
T: CobWithType,
|
||||
Repo: ReadRepository + cob::Store<Namespace = NodeId>,
|
||||
Access: access::Access,
|
||||
{
|
||||
/// Open a new generic store.
|
||||
pub fn open(repo: &'a R) -> Result<Self, Error> {
|
||||
pub fn open(repo: &'a Repo, access: Access) -> Result<Self, Error> {
|
||||
Ok(Self {
|
||||
repo,
|
||||
identity: None,
|
||||
witness: PhantomData,
|
||||
type_name: T::type_name(),
|
||||
scope: Scope::new(),
|
||||
access,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, R> Store<'_, T, R>
|
||||
impl<'a, 'b, T, Repo, Signer> Store<'a, T, Repo, WriteAs<'b, Signer>>
|
||||
where
|
||||
R: ReadRepository + cob::Store<Namespace = NodeId>,
|
||||
T: Cob + cob::Evaluate<R>,
|
||||
T: Cob + cob::Evaluate<Repo>,
|
||||
Repo: ReadRepository + cob::Store<Namespace = NodeId>,
|
||||
{
|
||||
#[deprecated(note = "only exists to accommodate signatures in crate::cob::identity")]
|
||||
pub(super) fn signer(&self) -> &Signer {
|
||||
self.access.signer
|
||||
}
|
||||
|
||||
#[deprecated(note = "only exists to accommodate signatures in crate::cob::identity")]
|
||||
pub(super) fn repo(&self) -> &Repo {
|
||||
self.repo
|
||||
}
|
||||
|
||||
pub fn transaction(
|
||||
&self,
|
||||
actions: Vec<T::Action>,
|
||||
embeds: Vec<Embed<Uri>>,
|
||||
) -> Transaction<T, R> {
|
||||
Transaction::new(self.type_name.clone(), actions, embeds)
|
||||
) -> Transaction<T, Repo> {
|
||||
Transaction::new(self.scope.type_name.clone(), actions, embeds)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, R> Store<'_, T, R>
|
||||
impl<'a, 'b, T, Repo, Signer> Store<'a, T, Repo, WriteAs<'b, Signer>>
|
||||
where
|
||||
R: ReadRepository + SignRepository + cob::Store<Namespace = NodeId>,
|
||||
T: Cob + cob::Evaluate<R>,
|
||||
T: Cob + cob::Evaluate<Repo>,
|
||||
T::Action: Serialize,
|
||||
Repo: ReadRepository + SignRepository + cob::Store<Namespace = NodeId>,
|
||||
Signer: crypto::signature::Keypair<VerifyingKey = crypto::PublicKey>,
|
||||
Signer: crypto::signature::Signer<crypto::Signature>,
|
||||
Signer: crypto::signature::Signer<crypto::ssh::ExtendedSignature>,
|
||||
Signer: crypto::signature::Verifier<crypto::Signature>,
|
||||
{
|
||||
/// Update an object.
|
||||
pub fn update<G>(
|
||||
&self,
|
||||
pub fn update(
|
||||
&mut self,
|
||||
type_name: &TypeName,
|
||||
object_id: ObjectId,
|
||||
message: &str,
|
||||
actions: impl Into<NonEmpty<T::Action>>,
|
||||
embeds: Vec<Embed<Uri>>,
|
||||
signer: &Device<G>,
|
||||
) -> Result<Updated<T>, Error>
|
||||
where
|
||||
G: crypto::signature::Signer<crypto::Signature>,
|
||||
{
|
||||
) -> Result<Updated<T>, Error> {
|
||||
let actions = actions.into();
|
||||
let related = actions.iter().flat_map(T::Action::parents).collect();
|
||||
let changes = actions.try_map(encoding::encode)?;
|
||||
|
|
@ -235,12 +290,13 @@ where
|
|||
})
|
||||
})
|
||||
.collect::<Result<_, _>>()?;
|
||||
let namespace = self.access.signer.verifying_key();
|
||||
let updated = cob::update(
|
||||
self.repo,
|
||||
signer,
|
||||
self.access.signer,
|
||||
self.identity,
|
||||
related,
|
||||
signer.public_key(),
|
||||
&namespace,
|
||||
Update {
|
||||
object_id,
|
||||
type_name: type_name.clone(),
|
||||
|
|
@ -250,23 +306,19 @@ where
|
|||
},
|
||||
)?;
|
||||
self.repo
|
||||
.sign_refs(signer)
|
||||
.sign_refs(self.access.signer)
|
||||
.map_err(|e| Error::SignRefs(Box::new(e)))?;
|
||||
|
||||
Ok(updated)
|
||||
}
|
||||
|
||||
/// Create an object.
|
||||
pub fn create<G>(
|
||||
&self,
|
||||
pub fn create(
|
||||
&mut self,
|
||||
message: &str,
|
||||
actions: impl Into<NonEmpty<T::Action>>,
|
||||
embeds: Vec<Embed<Uri>>,
|
||||
signer: &Device<G>,
|
||||
) -> Result<(ObjectId, T), Error>
|
||||
where
|
||||
G: crypto::signature::Signer<crypto::Signature>,
|
||||
{
|
||||
) -> 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)?;
|
||||
|
|
@ -279,14 +331,15 @@ where
|
|||
})
|
||||
})
|
||||
.collect::<Result<_, _>>()?;
|
||||
let namespace = self.access.signer.verifying_key();
|
||||
let cob = cob::create::<T, _, _>(
|
||||
self.repo,
|
||||
signer,
|
||||
self.access.signer,
|
||||
self.identity,
|
||||
parents,
|
||||
signer.public_key(),
|
||||
&namespace,
|
||||
Create {
|
||||
type_name: self.type_name.clone(),
|
||||
type_name: self.scope.type_name.clone(),
|
||||
version: Version::default(),
|
||||
message: message.to_owned(),
|
||||
embeds,
|
||||
|
|
@ -296,28 +349,23 @@ where
|
|||
// Nb. We can't sign our refs before the identity refs exist, which are created after
|
||||
// the identity COB is created. Therefore we manually sign refs when creating identity
|
||||
// COBs.
|
||||
if self.type_name != &*crate::cob::identity::TYPENAME {
|
||||
if self.scope.type_name != &*crate::cob::identity::TYPENAME {
|
||||
self.repo
|
||||
.sign_refs(signer)
|
||||
.sign_refs(self.access.signer)
|
||||
.map_err(|e| Error::SignRefs(Box::new(e)))?;
|
||||
}
|
||||
Ok((*cob.id(), cob.object))
|
||||
}
|
||||
|
||||
/// Remove an object.
|
||||
pub fn remove<G>(&self, id: &ObjectId, signer: &Device<G>) -> Result<(), Error>
|
||||
where
|
||||
G: crypto::signature::Signer<crypto::Signature>,
|
||||
{
|
||||
let name = git::refs::storage::cob(signer.public_key(), self.type_name, id);
|
||||
match self
|
||||
.repo
|
||||
.reference_oid(signer.public_key(), &name.strip_namespace())
|
||||
{
|
||||
pub fn remove(&mut self, id: &ObjectId) -> Result<(), Error> {
|
||||
let namespace = self.access.signer.verifying_key();
|
||||
let name = git::refs::storage::cob(&namespace, self.scope.type_name, id);
|
||||
match self.repo.reference_oid(&namespace, &name.strip_namespace()) {
|
||||
Ok(_) => {
|
||||
cob::remove(self.repo, signer.public_key(), self.type_name, id)?;
|
||||
cob::remove(self.repo, &namespace, self.scope.type_name, id)?;
|
||||
self.repo
|
||||
.sign_refs(signer)
|
||||
.sign_refs(self.access.signer)
|
||||
.map_err(|e| Error::SignRefs(Box::new(e)))?;
|
||||
Ok(())
|
||||
}
|
||||
|
|
@ -330,15 +378,16 @@ where
|
|||
}
|
||||
}
|
||||
|
||||
impl<'a, T, R> Store<'a, T, R>
|
||||
impl<'a, T, Repo, Access> Store<'a, T, Repo, Access>
|
||||
where
|
||||
R: ReadRepository + cob::Store,
|
||||
T: Cob + cob::Evaluate<R> + 'a,
|
||||
T: Cob + cob::Evaluate<Repo> + 'a,
|
||||
T::Action: Serialize,
|
||||
Repo: ReadRepository + cob::Store<Namespace = NodeId>,
|
||||
Access: access::Access,
|
||||
{
|
||||
/// Get an object.
|
||||
pub fn get(&self, id: &ObjectId) -> Result<Option<T>, Error> {
|
||||
cob::get::<T, _>(self.repo, self.type_name, id)
|
||||
cob::get::<T, _>(self.repo, self.scope.type_name, id)
|
||||
.map(|r| r.map(|cob| cob.object))
|
||||
.map_err(Error::from)
|
||||
}
|
||||
|
|
@ -346,9 +395,11 @@ where
|
|||
/// Return all objects.
|
||||
pub fn all(
|
||||
&self,
|
||||
) -> Result<impl ExactSizeIterator<Item = Result<(ObjectId, T), Error>> + use<'a, T, R>, Error>
|
||||
{
|
||||
let raw = cob::list::<T, _>(self.repo, self.type_name)?;
|
||||
) -> Result<
|
||||
impl ExactSizeIterator<Item = Result<(ObjectId, T), Error>> + use<'a, T, Repo, Access>,
|
||||
Error,
|
||||
> {
|
||||
let raw = cob::list::<T, _>(self.repo, self.scope.type_name)?;
|
||||
|
||||
Ok(raw.into_iter().map(|o| Ok((*o.id(), o.object))))
|
||||
}
|
||||
|
|
@ -360,7 +411,7 @@ where
|
|||
|
||||
/// Return objects count.
|
||||
pub fn count(&self) -> Result<usize, Error> {
|
||||
let raw = cob::list::<T, _>(self.repo, self.type_name)?;
|
||||
let raw = cob::list::<T, _>(self.repo, self.scope.type_name)?;
|
||||
|
||||
Ok(raw.len())
|
||||
}
|
||||
|
|
@ -408,26 +459,33 @@ where
|
|||
type_name,
|
||||
}
|
||||
}
|
||||
|
||||
#[deprecated(note = "only exists to accommodate signatures in crate::cob::identity")]
|
||||
pub(super) fn into_inner(self) -> (Vec<T::Action>, Vec<Embed<Uri>>) {
|
||||
(self.actions, self.embeds)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, R> Transaction<T, R>
|
||||
impl<T, Repo> Transaction<T, Repo>
|
||||
where
|
||||
T: Cob + CobWithType + cob::Evaluate<R>,
|
||||
T: Cob + CobWithType + cob::Evaluate<Repo>,
|
||||
{
|
||||
/// Create a new transaction to be used as the initial set of operations for a COB.
|
||||
pub fn initial<G, F, Tx>(
|
||||
pub fn initial<'a, 'b, Signer, Tx, F>(
|
||||
message: &str,
|
||||
store: &mut Store<T, R>,
|
||||
signer: &Device<G>,
|
||||
store: &mut Store<'a, T, Repo, WriteAs<'b, Signer>>,
|
||||
operations: F,
|
||||
) -> Result<(ObjectId, T), Error>
|
||||
where
|
||||
T::Action: Serialize + Clone,
|
||||
Repo: ReadRepository + SignRepository + cob::Store<Namespace = NodeId>,
|
||||
Signer: crypto::signature::Keypair<VerifyingKey = crypto::PublicKey>,
|
||||
Signer: crypto::signature::Signer<crypto::Signature>,
|
||||
Signer: crypto::signature::Signer<crypto::ssh::ExtendedSignature>,
|
||||
Signer: crypto::signature::Verifier<crypto::Signature>,
|
||||
Tx: From<Self>,
|
||||
Self: From<Tx>,
|
||||
G: crypto::signature::Signer<crypto::Signature>,
|
||||
F: FnOnce(&mut Tx, &R) -> Result<(), Error>,
|
||||
R: ReadRepository + SignRepository + cob::Store<Namespace = NodeId>,
|
||||
T::Action: Serialize + Clone,
|
||||
F: FnOnce(&mut Tx, &Repo) -> Result<(), Error>,
|
||||
{
|
||||
let mut tx = Tx::from(Transaction::default());
|
||||
operations(&mut tx, store.as_ref())?;
|
||||
|
|
@ -436,13 +494,13 @@ where
|
|||
let actions = NonEmpty::from_vec(tx.actions)
|
||||
.expect("Transaction::initial: transaction must contain at least one action");
|
||||
|
||||
store.create(message, actions, tx.embeds, signer)
|
||||
store.create(message, actions, tx.embeds)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, R> Transaction<T, R>
|
||||
impl<T, Repo> Transaction<T, Repo>
|
||||
where
|
||||
T: Cob + cob::Evaluate<R>,
|
||||
T: Cob + cob::Evaluate<Repo>,
|
||||
{
|
||||
/// Add an action to this transaction.
|
||||
pub fn push(&mut self, action: T::Action) -> Result<(), Error> {
|
||||
|
|
@ -482,25 +540,28 @@ where
|
|||
/// Commit transaction.
|
||||
///
|
||||
/// Returns an operation that can be applied onto an in-memory state.
|
||||
pub fn commit<G>(
|
||||
pub fn commit<'a, Signer>(
|
||||
self,
|
||||
msg: &str,
|
||||
id: ObjectId,
|
||||
store: &mut Store<T, R>,
|
||||
signer: &Device<G>,
|
||||
store: &mut Store<T, Repo, WriteAs<'a, Signer>>,
|
||||
) -> Result<(T, EntryId), Error>
|
||||
where
|
||||
R: ReadRepository + SignRepository + cob::Store<Namespace = NodeId>,
|
||||
T::Action: Serialize + Clone,
|
||||
G: crypto::signature::Signer<crypto::Signature>,
|
||||
Repo: ReadRepository + SignRepository + cob::Store<Namespace = NodeId>,
|
||||
Signer: crypto::signature::Keypair<VerifyingKey = crypto::PublicKey>,
|
||||
Signer: crypto::signature::Signer<crypto::Signature>,
|
||||
Signer: crypto::signature::Signer<crypto::ssh::ExtendedSignature>,
|
||||
Signer: crypto::signature::Verifier<crypto::Signature>,
|
||||
{
|
||||
let actions = NonEmpty::from_vec(self.actions)
|
||||
.expect("Transaction::commit: transaction must not be empty");
|
||||
|
||||
let Updated {
|
||||
head,
|
||||
object: CollaborativeObject { object, .. },
|
||||
..
|
||||
} = store.update(&self.type_name, id, msg, actions, self.embeds, signer)?;
|
||||
} = store.update(&self.type_name, id, msg, actions, self.embeds)?;
|
||||
|
||||
Ok((object, head))
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,37 @@
|
|||
//! Stores carry [`Access`] to indicate allowed access modes. In particular
|
||||
//! whether writes to the store are allowed.
|
||||
|
||||
pub use seal::Access;
|
||||
|
||||
/// [`ReadOnly`] is used for read-only [`Access`].
|
||||
pub struct ReadOnly;
|
||||
|
||||
/// [`WriteAs`] is used for write [`Access`].
|
||||
pub struct WriteAs<'a, Signer> {
|
||||
pub(super) signer: &'a Signer,
|
||||
}
|
||||
|
||||
impl<'a, Signer> WriteAs<'a, Signer> {
|
||||
pub fn new(signer: &'a Signer) -> Self {
|
||||
Self { signer }
|
||||
}
|
||||
}
|
||||
|
||||
// See <https://predr.ag/blog/definitive-guide-to-sealed-traits-in-rust/#sealing-traits-via-method-signatures>.
|
||||
#[allow(private_interfaces)]
|
||||
mod seal {
|
||||
enum Seal {}
|
||||
|
||||
/// Marker trait for COB store access modes.
|
||||
pub trait Access {
|
||||
fn seal(&self, _: Seal);
|
||||
}
|
||||
|
||||
impl Access for super::ReadOnly {
|
||||
fn seal(&self, _: Seal) {}
|
||||
}
|
||||
|
||||
impl<Signer> Access for super::WriteAs<'_, Signer> {
|
||||
fn seal(&self, _: Seal) {}
|
||||
}
|
||||
}
|
||||
|
|
@ -204,3 +204,12 @@ impl AsRef<crypto::PublicKey> for BoxedDevice {
|
|||
impl KeypairRef for BoxedDevice {
|
||||
type VerifyingKey = crypto::PublicKey;
|
||||
}
|
||||
|
||||
impl Verifier<Signature> for BoxedDevice {
|
||||
fn verify(&self, msg: &[u8], signature: &Signature) -> Result<(), crypto::signature::Error> {
|
||||
self.0
|
||||
.node
|
||||
.verify(msg, signature)
|
||||
.map_err(crypto::signature::Error::from_source)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -22,6 +22,7 @@ use localtime::LocalTime;
|
|||
use thiserror::Error;
|
||||
|
||||
use crate::cob::migrate;
|
||||
use crate::cob::store::access::{ReadOnly, WriteAs};
|
||||
use crate::crypto::PublicKey;
|
||||
use crate::crypto::ssh::agent::Agent;
|
||||
use crate::crypto::ssh::{Keystore, Passphrase, keystore};
|
||||
|
|
@ -757,15 +758,16 @@ impl Home {
|
|||
}
|
||||
|
||||
/// Return a read-only handle for the issues cache.
|
||||
pub fn issues<'a, R>(
|
||||
pub fn issues<'a, Repo>(
|
||||
&self,
|
||||
repository: &'a R,
|
||||
) -> Result<cob::issue::Cache<cob::issue::Issues<'a, R>, cob::cache::StoreReader>, Error>
|
||||
repository: &'a Repo,
|
||||
) -> Result<cob::issue::Cache<'a, Repo, ReadOnly, cob::cache::StoreReader>, Error>
|
||||
where
|
||||
R: ReadRepository + cob::Store<Namespace = NodeId>,
|
||||
Repo: ReadRepository + cob::Store<Namespace = NodeId>,
|
||||
{
|
||||
let db = self.cobs_db()?;
|
||||
let store = cob::issue::Issues::open(repository)?;
|
||||
|
||||
let store = cob::issue::Issues::open(repository, ReadOnly)?;
|
||||
|
||||
db.check_version()?;
|
||||
|
||||
|
|
@ -773,15 +775,16 @@ impl Home {
|
|||
}
|
||||
|
||||
/// Return a read-write handle for the issues cache.
|
||||
pub fn issues_mut<'a, R>(
|
||||
pub fn issues_mut<'a, 'b, Repo, Signer>(
|
||||
&self,
|
||||
repository: &'a R,
|
||||
) -> Result<cob::issue::Cache<cob::issue::Issues<'a, R>, cob::cache::StoreWriter>, Error>
|
||||
repository: &'a Repo,
|
||||
signer: &'b Signer,
|
||||
) -> Result<cob::issue::Cache<'a, Repo, WriteAs<'b, Signer>, cob::cache::StoreWriter>, Error>
|
||||
where
|
||||
R: ReadRepository + cob::Store<Namespace = NodeId>,
|
||||
Repo: ReadRepository + cob::Store<Namespace = NodeId>,
|
||||
{
|
||||
let db = self.cobs_db_mut()?;
|
||||
let store = cob::issue::Issues::open(repository)?;
|
||||
let store = cob::issue::Issues::open(repository, WriteAs::new(signer))?;
|
||||
|
||||
db.check_version()?;
|
||||
|
||||
|
|
@ -789,15 +792,15 @@ impl Home {
|
|||
}
|
||||
|
||||
/// Return a read-only handle for the patches cache.
|
||||
pub fn patches<'a, R>(
|
||||
pub fn patches<'a, Repo>(
|
||||
&self,
|
||||
repository: &'a R,
|
||||
) -> Result<cob::patch::Cache<cob::patch::Patches<'a, R>, cob::cache::StoreReader>, Error>
|
||||
repository: &'a Repo,
|
||||
) -> Result<cob::patch::Cache<'a, Repo, ReadOnly, cob::cache::StoreReader>, Error>
|
||||
where
|
||||
R: ReadRepository + cob::Store<Namespace = NodeId>,
|
||||
Repo: ReadRepository + cob::Store<Namespace = NodeId>,
|
||||
{
|
||||
let db = self.cobs_db()?;
|
||||
let store = cob::patch::Patches::open(repository)?;
|
||||
let store = cob::patch::Patches::open(repository, ReadOnly)?;
|
||||
|
||||
db.check_version()?;
|
||||
|
||||
|
|
@ -805,15 +808,16 @@ impl Home {
|
|||
}
|
||||
|
||||
/// Return a read-write handle for the patches cache.
|
||||
pub fn patches_mut<'a, R>(
|
||||
pub fn patches_mut<'a, 'b, Repo, Signer>(
|
||||
&self,
|
||||
repository: &'a R,
|
||||
) -> Result<cob::patch::Cache<cob::patch::Patches<'a, R>, cob::cache::StoreWriter>, Error>
|
||||
repository: &'a Repo,
|
||||
signer: &'b Signer,
|
||||
) -> Result<cob::patch::Cache<'a, Repo, WriteAs<'b, Signer>, cob::cache::StoreWriter>, Error>
|
||||
where
|
||||
R: ReadRepository + cob::Store<Namespace = NodeId>,
|
||||
Repo: ReadRepository + cob::Store<Namespace = NodeId>,
|
||||
{
|
||||
let db = self.cobs_db_mut()?;
|
||||
let store = cob::patch::Patches::open(repository)?;
|
||||
let store = cob::patch::Patches::open(repository, WriteAs::new(signer))?;
|
||||
|
||||
db.check_version()?;
|
||||
|
||||
|
|
|
|||
|
|
@ -76,6 +76,7 @@ where
|
|||
)
|
||||
})?;
|
||||
let doc = identity::Doc::initial(proj, delegate, visibility);
|
||||
|
||||
let (project, identity) = Repository::init(&doc, &storage, signer)?;
|
||||
let url = git::Url::from(project.id);
|
||||
|
||||
|
|
|
|||
|
|
@ -24,7 +24,6 @@ use crate::identity::{Did, PayloadError, doc};
|
|||
use crate::identity::{Doc, DocAt, DocError};
|
||||
use crate::identity::{Identity, RepoId};
|
||||
use crate::node::SyncedAt;
|
||||
use crate::node::device::Device;
|
||||
use crate::storage::git::NAMESPACES_GLOB;
|
||||
use crate::storage::refs::{FeatureLevel, Refs, SignedRefs};
|
||||
|
||||
|
|
@ -697,16 +696,20 @@ pub trait WriteRepository: ReadRepository + SignRepository {
|
|||
/// Allows signing refs.
|
||||
pub trait SignRepository {
|
||||
/// Sign the repository's refs under the `refs/rad/sigrefs` branch.
|
||||
fn sign_refs<G>(&self, signer: &Device<G>) -> Result<SignedRefs, RepositoryError>
|
||||
fn sign_refs<Signer>(&self, signer: &Signer) -> Result<SignedRefs, RepositoryError>
|
||||
where
|
||||
G: crypto::signature::Signer<crypto::Signature>;
|
||||
Signer: crypto::signature::Keypair<VerifyingKey = crypto::PublicKey>,
|
||||
Signer: crypto::signature::Signer<crypto::Signature>,
|
||||
Signer: crypto::signature::Verifier<crypto::Signature>;
|
||||
|
||||
/// Sign the repository's refs under the `refs/rad/sigrefs` branch, even if unchanged.
|
||||
///
|
||||
/// Most users will prefer [`Self::sign_refs`].
|
||||
fn force_sign_refs<G>(&self, signer: &Device<G>) -> Result<SignedRefs, RepositoryError>
|
||||
fn force_sign_refs<Signer>(&self, signer: &Signer) -> Result<SignedRefs, RepositoryError>
|
||||
where
|
||||
G: crypto::signature::Signer<crypto::Signature>;
|
||||
Signer: crypto::signature::Keypair<VerifyingKey = crypto::PublicKey>,
|
||||
Signer: crypto::signature::Signer<crypto::Signature>,
|
||||
Signer: crypto::signature::Verifier<crypto::Signature>;
|
||||
}
|
||||
|
||||
impl<T, S> ReadStorage for T
|
||||
|
|
|
|||
|
|
@ -1004,43 +1004,52 @@ impl WriteRepository for Repository {
|
|||
}
|
||||
|
||||
impl SignRepository for Repository {
|
||||
fn sign_refs<G: crypto::signature::Signer<crypto::Signature>>(
|
||||
&self,
|
||||
signer: &Device<G>,
|
||||
) -> Result<SignedRefs, RepositoryError> {
|
||||
fn sign_refs<Signer>(&self, signer: &Signer) -> Result<SignedRefs, RepositoryError>
|
||||
where
|
||||
Signer: crypto::signature::Keypair<VerifyingKey = crypto::PublicKey>,
|
||||
Signer: crypto::signature::Signer<crypto::Signature>,
|
||||
Signer: crypto::signature::Verifier<crypto::Signature>,
|
||||
{
|
||||
self.sign_refs_with(signer, false)
|
||||
}
|
||||
|
||||
fn force_sign_refs<G: crypto::signature::Signer<crypto::Signature>>(
|
||||
&self,
|
||||
signer: &Device<G>,
|
||||
) -> Result<SignedRefs, RepositoryError> {
|
||||
fn force_sign_refs<Signer>(&self, signer: &Signer) -> Result<SignedRefs, RepositoryError>
|
||||
where
|
||||
Signer: crypto::signature::Keypair<VerifyingKey = crypto::PublicKey>,
|
||||
Signer: crypto::signature::Signer<crypto::Signature>,
|
||||
Signer: crypto::signature::Verifier<crypto::Signature>,
|
||||
{
|
||||
self.sign_refs_with(signer, true)
|
||||
}
|
||||
}
|
||||
|
||||
impl Repository {
|
||||
fn sign_refs_with<G: crypto::signature::Signer<crypto::Signature>>(
|
||||
fn sign_refs_with<Signer>(
|
||||
&self,
|
||||
signer: &Device<G>,
|
||||
signer: &Signer,
|
||||
force: bool,
|
||||
) -> Result<SignedRefs, RepositoryError> {
|
||||
let remote = signer.public_key();
|
||||
) -> Result<SignedRefs, RepositoryError>
|
||||
where
|
||||
Signer: crypto::signature::Keypair<VerifyingKey = crypto::PublicKey>,
|
||||
Signer: crypto::signature::Signer<crypto::Signature>,
|
||||
Signer: crypto::signature::Verifier<crypto::Signature>,
|
||||
{
|
||||
let remote = signer.verifying_key();
|
||||
// Ensure the root reference is set, which is checked during sigref verification.
|
||||
if self
|
||||
.reference_oid(remote, &git::refs::storage::IDENTITY_ROOT)
|
||||
.reference_oid(&remote, &git::refs::storage::IDENTITY_ROOT)
|
||||
.is_err()
|
||||
{
|
||||
self.set_remote_identity_root(remote)?;
|
||||
self.set_remote_identity_root(&remote)?;
|
||||
}
|
||||
|
||||
let committer = refs::sigrefs::git::Committer::from_env_or_now(remote);
|
||||
let committer = refs::sigrefs::git::Committer::from_env_or_now(&remote);
|
||||
|
||||
let refs = self.references_of(remote)?;
|
||||
let refs = self.references_of(&remote)?;
|
||||
let signed = if force {
|
||||
refs.force_save(*remote, committer, self, signer)?
|
||||
refs.force_save(remote, committer, self, signer)?
|
||||
} else {
|
||||
refs.save(*remote, committer, self, signer)?
|
||||
refs.save(remote, committer, self, signer)?
|
||||
};
|
||||
|
||||
Ok(signed)
|
||||
|
|
|
|||
|
|
@ -17,7 +17,6 @@ use crate::git::*;
|
|||
use crate::identity;
|
||||
use crate::identity::doc::DocError;
|
||||
use crate::node::NodeId;
|
||||
use crate::node::device::Device;
|
||||
use crate::storage;
|
||||
use crate::storage::Error;
|
||||
use crate::storage::{
|
||||
|
|
@ -232,25 +231,35 @@ impl<R: storage::WriteRepository> change::Storage for DraftStore<'_, R> {
|
|||
}
|
||||
}
|
||||
|
||||
impl<R> SignRepository for DraftStore<'_, R>
|
||||
impl<Repo> SignRepository for DraftStore<'_, Repo>
|
||||
where
|
||||
R: storage::ReadRepository,
|
||||
Repo: storage::ReadRepository,
|
||||
{
|
||||
fn sign_refs<G: crypto::signature::Signer<crypto::Signature>>(
|
||||
fn sign_refs<Signer>(
|
||||
&self,
|
||||
signer: &Device<G>,
|
||||
) -> Result<storage::refs::SignedRefs, RepositoryError> {
|
||||
signer: &Signer,
|
||||
) -> Result<storage::refs::SignedRefs, RepositoryError>
|
||||
where
|
||||
Signer: crypto::signature::Keypair<VerifyingKey = crypto::PublicKey>,
|
||||
Signer: crypto::signature::Signer<crypto::Signature>,
|
||||
Signer: crypto::signature::Verifier<crypto::Signature>,
|
||||
{
|
||||
// Since this is a draft store, we do not actually want to sign the refs.
|
||||
// Instead, we just return the existing signed refs.
|
||||
let remote = self.repo.remote(signer.public_key())?;
|
||||
let remote = self.repo.remote(&signer.verifying_key())?;
|
||||
|
||||
Ok(remote.refs)
|
||||
}
|
||||
|
||||
fn force_sign_refs<G: crypto::signature::Signer<crypto::Signature>>(
|
||||
fn force_sign_refs<Signer>(
|
||||
&self,
|
||||
signer: &Device<G>,
|
||||
) -> Result<storage::refs::SignedRefs, RepositoryError> {
|
||||
signer: &Signer,
|
||||
) -> Result<storage::refs::SignedRefs, RepositoryError>
|
||||
where
|
||||
Signer: crypto::signature::Keypair<VerifyingKey = crypto::PublicKey>,
|
||||
Signer: crypto::signature::Signer<crypto::Signature>,
|
||||
Signer: crypto::signature::Verifier<crypto::Signature>,
|
||||
{
|
||||
self.sign_refs(signer)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -499,6 +499,7 @@ mod tests {
|
|||
|
||||
use super::*;
|
||||
use crate::assert_matches;
|
||||
|
||||
use crate::node::device::Device;
|
||||
use crate::storage::WriteRepository as _;
|
||||
use crate::{Storage, cob::Title, cob::identity::Identity, rad, test::fixtures};
|
||||
|
|
@ -574,14 +575,14 @@ mod tests {
|
|||
})
|
||||
.unwrap();
|
||||
|
||||
let mut paris_ident = Identity::load_mut(&paris).unwrap();
|
||||
let mut london_ident = Identity::load_mut(&london).unwrap();
|
||||
let mut paris_ident = Identity::load_mut(&paris, &alice).unwrap();
|
||||
let mut london_ident = Identity::load_mut(&london, &alice).unwrap();
|
||||
|
||||
paris_ident
|
||||
.update(Title::new("Add Bob").unwrap(), "", &paris_doc, &alice)
|
||||
.update(Title::new("Add Bob").unwrap(), "", &paris_doc)
|
||||
.unwrap();
|
||||
london_ident
|
||||
.update(Title::new("Add Bob").unwrap(), "", &london_doc, &alice)
|
||||
.update(Title::new("Add Bob").unwrap(), "", &london_doc)
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -11,7 +11,6 @@ use crate::git::fmt;
|
|||
|
||||
use crate::identity::doc::{Doc, DocAt, DocError, RawDoc, RepoId};
|
||||
use crate::node::NodeId;
|
||||
use crate::node::device::Device;
|
||||
|
||||
pub use crate::storage::*;
|
||||
|
||||
|
|
@ -367,17 +366,23 @@ impl WriteRepository for MockRepository {
|
|||
}
|
||||
|
||||
impl SignRepository for MockRepository {
|
||||
fn sign_refs<G: crypto::signature::Signer<crypto::Signature>>(
|
||||
fn sign_refs<Signer>(
|
||||
&self,
|
||||
_signer: &Device<G>,
|
||||
) -> Result<crate::storage::refs::SignedRefs, RepositoryError> {
|
||||
_signer: &Signer,
|
||||
) -> Result<crate::storage::refs::SignedRefs, RepositoryError>
|
||||
where
|
||||
Signer: crypto::signature::Keypair<VerifyingKey = crypto::PublicKey>,
|
||||
Signer: crypto::signature::Signer<crypto::Signature>,
|
||||
{
|
||||
todo!()
|
||||
}
|
||||
|
||||
fn force_sign_refs<G: crypto::signature::Signer<crypto::Signature>>(
|
||||
&self,
|
||||
_signer: &Device<G>,
|
||||
) -> Result<crate::storage::refs::SignedRefs, RepositoryError> {
|
||||
fn force_sign_refs<Signer>(&self, _signer: &Signer) -> Result<refs::SignedRefs, RepositoryError>
|
||||
where
|
||||
Signer: crypto::signature::Keypair<VerifyingKey = crypto::PublicKey>,
|
||||
Signer: crypto::signature::Signer<crypto::Signature>,
|
||||
Signer: crypto::signature::Verifier<crypto::Signature>,
|
||||
{
|
||||
todo!()
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue