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